lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <20170718033653.10298-4-andrew@aj.id.au>
Date:   Tue, 18 Jul 2017 13:06:53 +0930
From:   Andrew Jeffery <andrew@...id.au>
To:     linux@...ck-us.net, linux-hwmon@...r.kernel.org
Cc:     Andrew Jeffery <andrew@...id.au>, jdelvare@...e.com,
        linux-kernel@...r.kernel.org, joel@....id.au,
        openbmc@...ts.ozlabs.org, msbarth@...ux.vnet.ibm.com,
        mspinler@...ux.vnet.ibm.com
Subject: [RFC PATCH v2 3/3] pmbus: Add MAX31785 driver

The driver features fan control and basic dual-tachometer support.

The fan control makes use of the new virtual registers exposed by the
pmbus core, mixing use of the default implementations with some
overrides via the read/write handlers. FAN_COMMAND_1 on the MAX31785
breaks the values into bands that depend on the RPM or PWM control mode.

The dual tachometer feature is implemented in hardware with a TACHSEL
input to indicate the rotor under measurement, and exposed on the bus by
extending the READ_FAN_SPEED_1 word with two extra bytes*.
The need to read the non-standard four-byte response leads to a cut-down
implementation of i2c_smbus_xfer_emulated() included in the driver.
Further, to expose the second rotor tachometer value to userspace,
virtual fans are implemented by re-routing the FAN_CONFIG_1_2 register
from the undefined (in hardware) pages 23-28 to the same register on
pages 0-5, and similarly re-routing READ_FAN_SPEED_1 requests on 23-28
to pages 0-5 but extracting the second word of the four-byte response.

* The documentation recommends the slower rotor be associated with
TACHSEL=0, which is the input used by the controller's closed-loop fan
management.

Signed-off-by: Andrew Jeffery <andrew@...id.au>
---
v1 -> v2:

* Implement in terms of virtual registers
* Add support for dual-tachometer readings through 'virtual fans' (unused pages)

 drivers/hwmon/pmbus/Kconfig    |  10 ++
 drivers/hwmon/pmbus/Makefile   |   1 +
 drivers/hwmon/pmbus/max31785.c | 372 +++++++++++++++++++++++++++++++++++++++++
 3 files changed, 383 insertions(+)
 create mode 100644 drivers/hwmon/pmbus/max31785.c

diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig
index cad1229b7e17..5f2f3c6c7499 100644
--- a/drivers/hwmon/pmbus/Kconfig
+++ b/drivers/hwmon/pmbus/Kconfig
@@ -95,6 +95,16 @@ config SENSORS_MAX20751
 	  This driver can also be built as a module. If so, the module will
 	  be called max20751.
 
+config SENSORS_MAX31785
+	tristate "Maxim MAX31785 and compatibles"
+	default n
+	help
+	  If you say yes here you get hardware monitoring support for Maxim
+	  MAX31785.
+
+	  This driver can also be built as a module. If so, the module will
+	  be called max31785.
+
 config SENSORS_MAX34440
 	tristate "Maxim MAX34440 and compatibles"
 	default n
diff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile
index 562132054aaf..4ea548a8af46 100644
--- a/drivers/hwmon/pmbus/Makefile
+++ b/drivers/hwmon/pmbus/Makefile
@@ -10,6 +10,7 @@ obj-$(CONFIG_SENSORS_LTC2978)	+= ltc2978.o
 obj-$(CONFIG_SENSORS_LTC3815)	+= ltc3815.o
 obj-$(CONFIG_SENSORS_MAX16064)	+= max16064.o
 obj-$(CONFIG_SENSORS_MAX20751)	+= max20751.o
+obj-$(CONFIG_SENSORS_MAX31785)	+= max31785.o
 obj-$(CONFIG_SENSORS_MAX34440)	+= max34440.o
 obj-$(CONFIG_SENSORS_MAX8688)	+= max8688.o
 obj-$(CONFIG_SENSORS_TPS40422)	+= tps40422.o
diff --git a/drivers/hwmon/pmbus/max31785.c b/drivers/hwmon/pmbus/max31785.c
new file mode 100644
index 000000000000..1baa961f2eb1
--- /dev/null
+++ b/drivers/hwmon/pmbus/max31785.c
@@ -0,0 +1,372 @@
+/*
+ * Copyright (C) 2017 IBM Corp.
+ *
+ * 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.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include "pmbus.h"
+
+#define MFR_FAN_CONFIG_DUAL_TACH	BIT(12)
+#define MFR_FAN_CONFIG_TSFO		BIT(9)
+#define MFR_FAN_CONFIG_TACHO		BIT(8)
+
+#define MAX31785_CAP_DUAL_TACH		BIT(0)
+
+struct max31785 {
+	struct pmbus_driver_info info;
+
+	u32 capabilities;
+};
+
+enum max31785_regs {
+	PMBUS_MFR_FAN_CONFIG		= 0xF1,
+	PMBUS_MFR_READ_FAN_PWM		= 0xF3,
+	PMBUS_MFR_FAN_FAULT_LIMIT	= 0xF5,
+	PMBUS_MFR_FAN_WARN_LIMIT	= 0xF6,
+	PMBUS_MFR_FAN_PWM_AVG		= 0xF8,
+};
+
+#define to_max31785(_c) container_of(pmbus_get_info(_c), struct max31785, info)
+
+static int max31785_read_byte_data(struct i2c_client *client, int page,
+				   int reg)
+{
+	struct max31785 *chip = to_max31785(client);
+	int rv = -ENODATA;
+
+	switch (reg) {
+	case PMBUS_VOUT_MODE:
+		if (page < 23)
+			return -ENODATA;
+
+		return -ENOTSUPP;
+	case PMBUS_FAN_CONFIG_12:
+		if (page < 23)
+			return -ENODATA;
+
+		if (WARN_ON(!(chip->capabilities & MAX31785_CAP_DUAL_TACH)))
+			return -ENOTSUPP;
+
+		rv = pmbus_read_byte_data(client, page - 23, reg);
+		break;
+	}
+
+	return rv;
+}
+
+static long max31785_read_long_data(struct i2c_client *client, int page,
+				    int reg)
+{
+	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] = reg;
+
+	rc = pmbus_set_page(client, page);
+	if (rc < 0)
+		return rc;
+
+	rc = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg));
+	if (rc < 0)
+		return rc;
+
+	rc = (rspbuf[0] << (0 * 8)) | (rspbuf[1] << (1 * 8)) |
+	     (rspbuf[2] << (2 * 8)) | (rspbuf[3] << (3 * 8));
+
+	return rc;
+}
+
+static int max31785_get_pwm(struct i2c_client *client, int page)
+{
+	int config;
+	int command;
+
+	config = pmbus_read_byte_data(client, page, PMBUS_FAN_CONFIG_12);
+	if (config < 0)
+		return config;
+
+	command = pmbus_read_word_data(client, page, PMBUS_FAN_COMMAND_1);
+	if (command < 0)
+		return command;
+
+	if (!(config & PB_FAN_1_RPM)) {
+		if (command >= 0x8000)
+			return 0;
+		else if (command >= 0x2711)
+			return 0x2710;
+		else
+			return command;
+	}
+
+	return 0;
+}
+
+static int max31785_get_pwm_mode(struct i2c_client *client, int page)
+{
+	int config;
+	int command;
+
+	config = pmbus_read_byte_data(client, page, PMBUS_FAN_CONFIG_12);
+	if (config < 0)
+		return config;
+
+	command = pmbus_read_word_data(client, page, PMBUS_FAN_COMMAND_1);
+	if (command < 0)
+		return command;
+
+	if (!(config & PB_FAN_1_RPM)) {
+		if (command >= 0x8000)
+			return 2;
+		else if (command >= 0x2711)
+			return 0;
+		else
+			return 1;
+	}
+
+	return (command >= 0x8000) ? 2 : 1;
+}
+
+static int max31785_read_word_data(struct i2c_client *client, int page,
+				   int reg)
+{
+	struct max31785 *chip = to_max31785(client);
+	long rv = -ENODATA;
+
+	switch (reg) {
+	case PMBUS_READ_FAN_SPEED_1:
+		if (likely(page < 23))
+			return -ENODATA;
+
+		if (WARN_ON(!(chip->capabilities & MAX31785_CAP_DUAL_TACH)))
+			return -ENOTSUPP;
+
+		rv = max31785_read_long_data(client, page - 23, reg);
+		if (rv < 0)
+			return rv;
+
+		rv = (rv >> 16) & 0xffff;
+		break;
+	case PMBUS_VIRT_PWM_1:
+		rv = max31785_get_pwm(client, page);
+		rv *= 255;
+		rv /= 100;
+		break;
+	case PMBUS_VIRT_PWM_ENABLE_1:
+		rv = max31785_get_pwm_mode(client, page);
+		break;
+	}
+
+	if (rv == -ENODATA && page >= 23)
+		return -ENXIO;
+
+	return rv;
+}
+
+static const int max31785_pwm_modes[] = { 0x7fff, 0x2710, 0xffff };
+
+static int max31785_write_word_data(struct i2c_client *client, int page,
+				    int reg, u16 word)
+{
+	int rv = -ENODATA;
+
+	if (page >= 23)
+		return -ENXIO;
+
+	switch (reg) {
+	case PMBUS_VIRT_PWM_ENABLE_1:
+		if (word >= ARRAY_SIZE(max31785_pwm_modes))
+			return -ENOTSUPP;
+
+		rv = pmbus_update_fan(client, page, 0, 0, PB_FAN_1_RPM,
+					 max31785_pwm_modes[word]);
+		break;
+	}
+
+	return rv;
+}
+
+static int max31785_write_byte(struct i2c_client *client, int page, u8 value)
+{
+	if (page < 23)
+		return -ENODATA;
+
+	return -ENOTSUPP;
+}
+
+static struct pmbus_driver_info max31785_info = {
+	.pages = 23,
+
+	.write_word_data = max31785_write_word_data,
+	.read_byte_data = max31785_read_byte_data,
+	.read_word_data = max31785_read_word_data,
+	.write_byte = max31785_write_byte,
+
+	/* RPM */
+	.format[PSC_FAN] = direct,
+	.m[PSC_FAN] = 1,
+	.b[PSC_FAN] = 0,
+	.R[PSC_FAN] = 0,
+	/* PWM */
+	.format[PSC_PWM] = direct,
+	.m[PSC_PWM] = 1,
+	.b[PSC_PWM] = 0,
+	.R[PSC_PWM] = 2,
+	.func[0] = PMBUS_HAVE_FAN12 | PMBUS_HAVE_STATUS_FAN12,
+	.func[1] = PMBUS_HAVE_FAN12 | PMBUS_HAVE_STATUS_FAN12,
+	.func[2] = PMBUS_HAVE_FAN12 | PMBUS_HAVE_STATUS_FAN12,
+	.func[3] = PMBUS_HAVE_FAN12 | PMBUS_HAVE_STATUS_FAN12,
+	.func[4] = PMBUS_HAVE_FAN12 | PMBUS_HAVE_STATUS_FAN12,
+	.func[5] = PMBUS_HAVE_FAN12 | PMBUS_HAVE_STATUS_FAN12,
+
+	.format[PSC_TEMPERATURE] = direct,
+	.m[PSC_TEMPERATURE] = 1,
+	.b[PSC_TEMPERATURE] = 0,
+	.R[PSC_TEMPERATURE] = 2,
+	.func[6]  = PMBUS_HAVE_STATUS_TEMP,
+	.func[7]  = PMBUS_HAVE_STATUS_TEMP,
+	.func[8]  = PMBUS_HAVE_STATUS_TEMP,
+	.func[9]  = PMBUS_HAVE_STATUS_TEMP,
+	.func[10] = PMBUS_HAVE_STATUS_TEMP,
+	.func[11] = PMBUS_HAVE_STATUS_TEMP,
+	.func[12] = PMBUS_HAVE_STATUS_TEMP,
+	.func[13] = PMBUS_HAVE_STATUS_TEMP,
+	.func[14] = PMBUS_HAVE_STATUS_TEMP,
+	.func[15] = PMBUS_HAVE_STATUS_TEMP,
+	.func[16] = PMBUS_HAVE_STATUS_TEMP,
+
+	.format[PSC_VOLTAGE_OUT] = direct,
+	.m[PSC_VOLTAGE_OUT] = 1,
+	.b[PSC_VOLTAGE_OUT] = 0,
+	.R[PSC_VOLTAGE_OUT] = 0,
+	.func[17] = PMBUS_HAVE_STATUS_VOUT,
+	.func[18] = PMBUS_HAVE_STATUS_VOUT,
+	.func[19] = PMBUS_HAVE_STATUS_VOUT,
+	.func[20] = PMBUS_HAVE_STATUS_VOUT,
+	.func[21] = PMBUS_HAVE_STATUS_VOUT,
+	.func[22] = PMBUS_HAVE_STATUS_VOUT,
+};
+
+static int max31785_probe(struct i2c_client *client,
+			  const struct i2c_device_id *id)
+{
+	struct max31785 *chip;
+	int rv;
+	int i;
+
+	chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
+	if (!chip)
+		return -ENOMEM;
+
+	chip->info = max31785_info;
+
+	/*
+	 * Identify the chip firmware and configure capabilities.
+	 *
+	 * Bootstrap with i2c_smbus_*() calls as we need to understand the chip
+	 * capabilities for before invoking pmbus_do_probe(). The pmbus_*()
+	 * calls need access to memory that is only valid after
+	 * pmbus_do_probe().
+	 */
+	rv = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 255);
+	if (rv < 0)
+		return rv;
+
+	rv = i2c_smbus_read_word_data(client, PMBUS_MFR_REVISION);
+	if (rv < 0)
+		return rv;
+
+	if ((rv & 0xff) == 0x40) {
+		chip->capabilities |= MAX31785_CAP_DUAL_TACH;
+
+		/*
+		 * Punt the dual tach virtual fans to non-existent pages. This
+		 * ensures the pwm attributes appear in a contiguous block
+		 */
+		chip->info.pages = 29;
+		chip->info.func[23] = PMBUS_HAVE_FAN12;
+		chip->info.func[24] = PMBUS_HAVE_FAN12;
+		chip->info.func[25] = PMBUS_HAVE_FAN12;
+		chip->info.func[26] = PMBUS_HAVE_FAN12;
+		chip->info.func[27] = PMBUS_HAVE_FAN12;
+		chip->info.func[28] = PMBUS_HAVE_FAN12;
+	}
+
+	rv = pmbus_do_probe(client, id, &chip->info);
+	if (rv < 0)
+		return rv;
+
+	for (i = 0; i < max31785_info.pages; i++) {
+		int reg;
+
+		if (!(max31785_info.func[i] & (PMBUS_HAVE_FAN12)))
+			continue;
+
+		reg = pmbus_read_word_data(client, i, PMBUS_MFR_FAN_CONFIG);
+		if (reg < 0)
+			continue;
+
+		/*
+		 * XXX: Purely for RFC/testing purposes, don't ramp fans on fan
+		 * or temperature sensor fault, or a failure to write
+		 * FAN_COMMAND_1 inside a 10s window (watchdog mode).
+		 *
+		 * The TSFO bit controls both ramping on temp sensor failure
+		 * AND whether FAN_COMMAND_1 is in watchdog mode.
+		 */
+		reg |= MFR_FAN_CONFIG_TSFO | MFR_FAN_CONFIG_TACHO;
+		if (chip->capabilities & MAX31785_CAP_DUAL_TACH)
+			reg |= MFR_FAN_CONFIG_DUAL_TACH;
+		reg &= 0xffff;
+
+		rv = pmbus_write_word_data(client, i, PMBUS_MFR_FAN_CONFIG,
+					   reg);
+	}
+
+	return 0;
+}
+
+static const struct i2c_device_id max31785_id[] = {
+	{ "max31785", 0 },
+	{ },
+};
+
+MODULE_DEVICE_TABLE(i2c, max31785_id);
+
+static struct i2c_driver max31785_driver = {
+	.driver = {
+		.name = "max31785",
+	},
+	.probe = max31785_probe,
+	.remove = pmbus_do_remove,
+	.id_table = max31785_id,
+};
+
+module_i2c_driver(max31785_driver);
+
+MODULE_AUTHOR("Andrew Jeffery <andrew@...id.au>");
+MODULE_DESCRIPTION("PMBus driver for the Maxim MAX31785");
+MODULE_LICENSE("GPL");
-- 
2.11.0

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ