linux-hwmon.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH] hwmon: (pmbus) Driver for Maxim MAX16601
@ 2020-05-06 21:49 Guenter Roeck
  2020-05-08  0:54 ` Alex Qiu
  0 siblings, 1 reply; 3+ messages in thread
From: Guenter Roeck @ 2020-05-06 21:49 UTC (permalink / raw)
  To: Hardware Monitoring; +Cc: Jean Delvare, Guenter Roeck, Alex Qiu

MAX16601 is a VR13.HC Dual-Output Voltage Regulator Chipset,
implementing a (8+1) multiphase synchronous buck converter.

Cc: Alex Qiu <xqiu@google.com>
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
---
 Documentation/hwmon/index.rst    |   1 +
 Documentation/hwmon/max16601.rst | 159 ++++++++++++++++
 drivers/hwmon/pmbus/Kconfig      |   9 +
 drivers/hwmon/pmbus/Makefile     |   1 +
 drivers/hwmon/pmbus/max16601.c   | 314 +++++++++++++++++++++++++++++++
 5 files changed, 484 insertions(+)
 create mode 100644 Documentation/hwmon/max16601.rst
 create mode 100644 drivers/hwmon/pmbus/max16601.c

diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst
index 8ef62fd39787..df87ae15a1d4 100644
--- a/Documentation/hwmon/index.rst
+++ b/Documentation/hwmon/index.rst
@@ -106,6 +106,7 @@ Hardware Monitoring Kernel Drivers
    max16064
    max16065
    max1619
+   max16601
    max1668
    max197
    max20730
diff --git a/Documentation/hwmon/max16601.rst b/Documentation/hwmon/max16601.rst
new file mode 100644
index 000000000000..346e74674c51
--- /dev/null
+++ b/Documentation/hwmon/max16601.rst
@@ -0,0 +1,159 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+Kernel driver max16601
+======================
+
+Supported chips:
+
+  * Maxim MAX16601
+
+    Prefix: 'max16601'
+
+    Addresses scanned: -
+
+    Datasheet: Not published
+
+Author: Guenter Roeck <linux@roeck-us.net>
+
+
+Description
+-----------
+
+This driver supports the MAX16601 VR13.HC Dual-Output Voltage Regulator
+Chipset.
+
+The driver is a client driver to the core PMBus driver.
+Please see Documentation/hwmon/pmbus.rst for details on PMBus client drivers.
+
+
+Usage Notes
+-----------
+
+This driver does not auto-detect devices. You will have to instantiate the
+devices explicitly. Please see Documentation/i2c/instantiating-devices.rst for
+details.
+
+
+Platform data support
+---------------------
+
+The driver supports standard PMBus driver platform data.
+
+
+Sysfs entries
+-------------
+
+The following attributes are supported.
+
+======================= =======================================================
+in1_label		"vin1"
+in1_input		VCORE input voltage.
+in1_alarm		Input voltage alarm.
+
+in2_label		"vout1"
+in2_input		VCORE output voltage.
+in2_alarm		Output voltage alarm.
+
+curr1_label		"iin1"
+curr1_input		VCORE input current, derived from duty cycle and output
+			current.
+curr1_max		Maximum input current.
+curr1_max_alarm		Current high alarm.
+
+curr2_label		"iin1.0"
+curr2_input		VCORE phase 0 input current.
+
+curr3_label		"iin1.1"
+curr3_input		VCORE phase 1 input current.
+
+curr4_label		"iin1.2"
+curr4_input		VCORE phase 2 input current.
+
+curr5_label		"iin1.3"
+curr5_input		VCORE phase 3 input current.
+
+curr6_label		"iin1.4"
+curr6_input		VCORE phase 4 input current.
+
+curr7_label		"iin1.5"
+curr7_input		VCORE phase 5 input current.
+
+curr8_label		"iin1.6"
+curr8_input		VCORE phase 6 input current.
+
+curr9_label		"iin1.7"
+curr9_input		VCORE phase 7 input current.
+
+curr10_label		"iin2"
+curr10_input		VCORE input current, derived from sensor element.
+
+curr11_label		"iin3"
+curr11_input		VSA input current.
+
+curr12_label		"iout1"
+curr12_input		VCORE output current.
+curr12_crit		Critical output current.
+curr12_crit_alarm	Output current critical alarm.
+curr12_max		Maximum output current.
+curr12_max_alarm	Output current high alarm.
+
+curr13_label		"iout1.0"
+curr13_input		VCORE phase 0 output current.
+
+curr14_label		"iout1.1"
+curr14_input		VCORE phase 1 output current.
+
+curr15_label		"iout1.2"
+curr15_input		VCORE phase 2 output current.
+
+curr16_label		"iout1.3"
+curr16_input		VCORE phase 3 output current.
+
+curr17_label		"iout1.4"
+curr17_input		VCORE phase 4 output current.
+
+curr18_label		"iout1.5"
+curr18_input		VCORE phase 5 output current.
+
+curr19_label		"iout1.6"
+curr19_input		VCORE phase 6 output current.
+
+curr20_label		"iout1.7"
+curr20_input		VCORE phase 7 output current.
+
+curr21_label		"iout3"
+curr21_input		VSA output current.
+curr21_highest		Historical maximum VSA output current.
+curr21_reset_history	Write any value to reset curr21_highest.
+curr21_crit		Critical output current.
+curr21_crit_alarm	Output current critical alarm.
+curr21_max		Maximum output current.
+curr21_max_alarm	Output current high alarm.
+
+power1_label		"pin1"
+power1_input		Input power, derived from duty cycle and output current.
+power1_alarm		Input power alarm.
+
+power2_label		"pin2"
+power2_input		Input power, derived from input current sensor.
+
+power3_label		"pout"
+power3_input		Output power.
+
+temp1_input		VCORE temperature.
+temp1_crit		Critical high temperature.
+temp1_crit_alarm	Chip temperature critical high alarm.
+temp1_max		Maximum temperature.
+temp1_max_alarm		Chip temperature high alarm.
+
+temp2_input		TSENSE_0 temperature
+temp3_input		TSENSE_1 temperature
+temp4_input		TSENSE_2 temperature
+temp5_input		TSENSE_3 temperature
+
+temp6_input		VSA temperature.
+temp6_crit		Critical high temperature.
+temp6_crit_alarm	Chip temperature critical high alarm.
+temp6_max		Maximum temperature.
+temp6_max_alarm		Chip temperature high alarm.
+======================= =======================================================
diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig
index de12a565006d..a337195b1c39 100644
--- a/drivers/hwmon/pmbus/Kconfig
+++ b/drivers/hwmon/pmbus/Kconfig
@@ -146,6 +146,15 @@ config SENSORS_MAX16064
 	  This driver can also be built as a module. If so, the module will
 	  be called max16064.
 
+config SENSORS_MAX16601
+	tristate "Maxim MAX16601"
+	help
+	  If you say yes here you get hardware monitoring support for Maxim
+	  MAX16601.
+
+	  This driver can also be built as a module. If so, the module will
+	  be called max16601.
+
 config SENSORS_MAX20730
 	tristate "Maxim MAX20730, MAX20734, MAX20743"
 	help
diff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile
index 5feb45806123..c4b15db996ad 100644
--- a/drivers/hwmon/pmbus/Makefile
+++ b/drivers/hwmon/pmbus/Makefile
@@ -17,6 +17,7 @@ obj-$(CONFIG_SENSORS_LM25066)	+= lm25066.o
 obj-$(CONFIG_SENSORS_LTC2978)	+= ltc2978.o
 obj-$(CONFIG_SENSORS_LTC3815)	+= ltc3815.o
 obj-$(CONFIG_SENSORS_MAX16064)	+= max16064.o
+obj-$(CONFIG_SENSORS_MAX16601)	+= max16601.o
 obj-$(CONFIG_SENSORS_MAX20730)	+= max20730.o
 obj-$(CONFIG_SENSORS_MAX20751)	+= max20751.o
 obj-$(CONFIG_SENSORS_MAX31785)	+= max31785.o
diff --git a/drivers/hwmon/pmbus/max16601.c b/drivers/hwmon/pmbus/max16601.c
new file mode 100644
index 000000000000..e10ab394b8de
--- /dev/null
+++ b/drivers/hwmon/pmbus/max16601.c
@@ -0,0 +1,314 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Hardware monitoring driver for Maxim MAX16601
+ *
+ * Implementation notes:
+ *
+ * Ths chip supports two rails, VCORE and VSA. Telemetry information for the
+ * two rails is reported in two subsequent I2C addresses. The driver
+ * instantiates a dummy I2C client at the second I2C address to report
+ * information for the VSA rail in a single instance of the driver.
+ * Telemetry for the VSA rail is reported to the PMBus core in PMBus page 2.
+ *
+ * The chip reports input current using two separate methods. The input current
+ * reported with the standard READ_IIN command is derived from the output
+ * current. The first method is reported to the PMBus core with PMBus page 0,
+ * the second method is reported with PMBus page 1.
+ *
+ * The chip supports reading per-phase temperatures and per-phase input/output
+ * currents for VCORE. Telemetry is reported in vendor specific registers.
+ * The driver translates the vendor specific register values to PMBus standard
+ * register values and reports per-phase information in PMBus page 0.
+ *
+ * Copyright 2019, 2020 Google LLC.
+ */
+
+#include <linux/bits.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+
+#include "pmbus.h"
+
+#define REG_SETPT_DVID		0xd1
+#define  DAC_10MV_MODE		BIT(4)
+#define REG_PHASE_ID		0xf3
+#define  CORE_RAIL_INDICATOR	BIT(7)
+#define REG_IOUT_AVG_PK		0xee
+#define REG_IIN_SENSOR		0xf1
+#define REG_TOTAL_INPUT_POWER	0xf2
+#define REG_PHASE_REPORTING	0xf4
+
+struct max16601_data {
+	struct pmbus_driver_info info;
+	struct i2c_client *vsa;
+	int iout_avg_pkg;
+};
+
+#define to_max16601_data(x) container_of(x, struct max16601_data, info)
+
+static int max16601_read_byte(struct i2c_client *client, int page, int reg)
+{
+	const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
+	struct max16601_data *data = to_max16601_data(info);
+
+	if (page > 0) {
+		if (page == 2)	/* VSA */
+			return i2c_smbus_read_byte_data(data->vsa, reg);
+		return -EOPNOTSUPP;
+	}
+	return -ENODATA;
+}
+
+static int max16601_read_word(struct i2c_client *client, int page, int phase,
+			      int reg)
+{
+	const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
+	struct max16601_data *data = to_max16601_data(info);
+	u8 buf[I2C_SMBUS_BLOCK_MAX + 1];
+	int ret;
+
+	switch (page) {
+	case 0:		/* VCORE */
+		if (phase == 0xff)
+			return -ENODATA;
+		switch (reg) {
+		case PMBUS_READ_IIN:
+		case PMBUS_READ_IOUT:
+		case PMBUS_READ_TEMPERATURE_1:
+			ret = i2c_smbus_write_byte_data(client, REG_PHASE_ID,
+							phase);
+			if (ret)
+				return ret;
+			ret = i2c_smbus_read_block_data(client,
+							REG_PHASE_REPORTING,
+							buf);
+			if (ret < 0)
+				return ret;
+			if (ret < 6)
+				return -EIO;
+			switch (reg) {
+			case PMBUS_READ_TEMPERATURE_1:
+				return buf[1] << 8 | buf[0];
+			case PMBUS_READ_IOUT:
+				return buf[3] << 8 | buf[2];
+			case PMBUS_READ_IIN:
+				return buf[5] << 8 | buf[4];
+			default:
+				break;
+			}
+		}
+		return -EOPNOTSUPP;
+	case 1:		/* VCORE, read IIN/PIN from sensor element */
+		switch (reg) {
+		case PMBUS_READ_IIN:
+			return i2c_smbus_read_word_data(client, REG_IIN_SENSOR);
+		case PMBUS_READ_PIN:
+			return i2c_smbus_read_word_data(client,
+							REG_TOTAL_INPUT_POWER);
+		default:
+			break;
+		}
+		return -EOPNOTSUPP;
+	case 2:		/* VSA */
+		switch (reg) {
+		case PMBUS_VIRT_READ_IOUT_MAX:
+			ret = i2c_smbus_read_word_data(data->vsa,
+						       REG_IOUT_AVG_PK);
+			if (ret < 0)
+				return ret;
+			if (sign_extend32(ret, 10) >
+			    sign_extend32(data->iout_avg_pkg, 10))
+				data->iout_avg_pkg = ret;
+			return data->iout_avg_pkg;
+		case PMBUS_VIRT_RESET_IOUT_HISTORY:
+			return 0;
+		case PMBUS_IOUT_OC_FAULT_LIMIT:
+		case PMBUS_IOUT_OC_WARN_LIMIT:
+		case PMBUS_OT_FAULT_LIMIT:
+		case PMBUS_OT_WARN_LIMIT:
+		case PMBUS_READ_IIN:
+		case PMBUS_READ_IOUT:
+		case PMBUS_READ_TEMPERATURE_1:
+		case PMBUS_STATUS_WORD:
+			return i2c_smbus_read_word_data(data->vsa, reg);
+		default:
+			return -EOPNOTSUPP;
+		}
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+static int max16601_write_byte(struct i2c_client *client, int page, u8 reg)
+{
+	const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
+	struct max16601_data *data = to_max16601_data(info);
+
+	if (page == 2) {
+		if (reg == PMBUS_CLEAR_FAULTS)
+			return i2c_smbus_write_byte(data->vsa, reg);
+		return -EOPNOTSUPP;
+	}
+	return -ENODATA;
+}
+
+static int max16601_write_word(struct i2c_client *client, int page, int reg,
+			       u16 value)
+{
+	const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
+	struct max16601_data *data = to_max16601_data(info);
+
+	switch (page) {
+	case 0:		/* VCORE */
+		return -ENODATA;
+	case 1:		/* VCORE IIN/PIN from sensor element */
+	default:
+		return -EOPNOTSUPP;
+	case 2:		/* VSA */
+		switch (reg) {
+		case PMBUS_VIRT_RESET_IOUT_HISTORY:
+			data->iout_avg_pkg = 0xfc00;
+			return 0;
+		case PMBUS_IOUT_OC_FAULT_LIMIT:
+		case PMBUS_IOUT_OC_WARN_LIMIT:
+		case PMBUS_OT_FAULT_LIMIT:
+		case PMBUS_OT_WARN_LIMIT:
+			return i2c_smbus_write_word_data(data->vsa, reg, value);
+		default:
+			return -EOPNOTSUPP;
+		}
+	}
+}
+
+static int max16601_identify(struct i2c_client *client,
+			     struct pmbus_driver_info *info)
+{
+	int reg;
+
+	reg = i2c_smbus_read_byte_data(client, REG_SETPT_DVID);
+	if (reg < 0)
+		return reg;
+	if (reg & DAC_10MV_MODE)
+		info->vrm_version[0] = vr13;
+	else
+		info->vrm_version[0] = vr12;
+
+	return 0;
+}
+
+static struct pmbus_driver_info max16601_info = {
+	.pages = 3,
+	.format[PSC_VOLTAGE_IN] = linear,
+	.format[PSC_VOLTAGE_OUT] = vid,
+	.format[PSC_CURRENT_IN] = linear,
+	.format[PSC_CURRENT_OUT] = linear,
+	.format[PSC_TEMPERATURE] = linear,
+	.format[PSC_POWER] = linear,
+	.func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_IIN | PMBUS_HAVE_PIN |
+		PMBUS_HAVE_STATUS_INPUT |
+		PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT |
+		PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT |
+		PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP |
+		PMBUS_HAVE_POUT | PMBUS_PAGE_VIRTUAL | PMBUS_PHASE_VIRTUAL,
+	.func[1] = PMBUS_HAVE_IIN | PMBUS_HAVE_PIN | PMBUS_PAGE_VIRTUAL,
+	.func[2] = PMBUS_HAVE_IIN | PMBUS_HAVE_STATUS_INPUT |
+		PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT |
+		PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP | PMBUS_PAGE_VIRTUAL,
+	.phases[0] = 8,
+	.pfunc[0] = PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT | PMBUS_HAVE_TEMP,
+	.pfunc[1] = PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT,
+	.pfunc[2] = PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT | PMBUS_HAVE_TEMP,
+	.pfunc[3] = PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT,
+	.pfunc[4] = PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT | PMBUS_HAVE_TEMP,
+	.pfunc[5] = PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT,
+	.pfunc[6] = PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT | PMBUS_HAVE_TEMP,
+	.pfunc[7] = PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT,
+	.identify = max16601_identify,
+	.read_byte_data = max16601_read_byte,
+	.read_word_data = max16601_read_word,
+	.write_byte = max16601_write_byte,
+	.write_word_data = max16601_write_word,
+};
+
+static void max16601_remove(void *_data)
+{
+	struct max16601_data *data = _data;
+
+	i2c_unregister_device(data->vsa);
+}
+
+static int max16601_probe(struct i2c_client *client,
+			  const struct i2c_device_id *id)
+{
+	struct device *dev = &client->dev;
+	u8 buf[I2C_SMBUS_BLOCK_MAX + 1];
+	struct max16601_data *data;
+	int ret;
+
+	if (!i2c_check_functionality(client->adapter,
+				     I2C_FUNC_SMBUS_READ_BYTE_DATA |
+				     I2C_FUNC_SMBUS_READ_BLOCK_DATA))
+		return -ENODEV;
+
+	ret = i2c_smbus_read_block_data(client, PMBUS_IC_DEVICE_ID, buf);
+	if (ret < 0)
+		return -ENODEV;
+
+	/* PMBUS_IC_DEVICE_ID is expected to return "MAX16601y.xx" */
+	if (ret < 11 || strncmp(buf, "MAX16601", 8)) {
+		buf[ret] = '\0';
+		dev_err(dev, "Unsupported chip '%s'\n", buf);
+		return -ENODEV;
+	}
+
+	ret = i2c_smbus_read_byte_data(client, REG_PHASE_ID);
+	if (ret < 0)
+		return ret;
+	if (!(ret & CORE_RAIL_INDICATOR)) {
+		dev_err(dev,
+			"Driver must be instantiated on CORE rail I2C address\n");
+		return -ENODEV;
+	}
+
+	data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	data->iout_avg_pkg = 0xfc00;
+	data->vsa = i2c_new_dummy_device(client->adapter, client->addr + 1);
+	if (IS_ERR(data->vsa)) {
+		dev_err(dev, "Failed to register VSA client\n");
+		return PTR_ERR(data->vsa);
+	}
+	ret = devm_add_action_or_reset(dev, max16601_remove, data);
+	if (ret)
+		return ret;
+
+	data->info = max16601_info;
+
+	return pmbus_do_probe(client, id, &data->info);
+}
+
+static const struct i2c_device_id max16601_id[] = {
+	{"max16601", 0},
+	{}
+};
+
+MODULE_DEVICE_TABLE(i2c, max16601_id);
+
+static struct i2c_driver max16601_driver = {
+	.driver = {
+		   .name = "max16601",
+		   },
+	.probe = max16601_probe,
+	.remove = pmbus_do_remove,
+	.id_table = max16601_id,
+};
+
+module_i2c_driver(max16601_driver);
+
+MODULE_AUTHOR("Guenter Roeck <linux@roeck-us.net>");
+MODULE_DESCRIPTION("PMBus driver for Maxim MAX16601");
+MODULE_LICENSE("GPL v2");
-- 
2.17.1


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

* Re: [PATCH] hwmon: (pmbus) Driver for Maxim MAX16601
  2020-05-06 21:49 [PATCH] hwmon: (pmbus) Driver for Maxim MAX16601 Guenter Roeck
@ 2020-05-08  0:54 ` Alex Qiu
  2020-05-08  4:29   ` Guenter Roeck
  0 siblings, 1 reply; 3+ messages in thread
From: Alex Qiu @ 2020-05-08  0:54 UTC (permalink / raw)
  To: Guenter Roeck; +Cc: Hardware Monitoring, Jean Delvare

Hi Guenter,

The drivers look good on our system, although more tests are ongoing.
The patch LGTM with minor nit. Thank you very much!

On Wed, May 6, 2020 at 2:49 PM Guenter Roeck <linux@roeck-us.net> wrote:
>
> MAX16601 is a VR13.HC Dual-Output Voltage Regulator Chipset,
> implementing a (8+1) multiphase synchronous buck converter.
>
> Cc: Alex Qiu <xqiu@google.com>
> Signed-off-by: Guenter Roeck <linux@roeck-us.net>
> ---
>  Documentation/hwmon/index.rst    |   1 +
>  Documentation/hwmon/max16601.rst | 159 ++++++++++++++++
>  drivers/hwmon/pmbus/Kconfig      |   9 +
>  drivers/hwmon/pmbus/Makefile     |   1 +
>  drivers/hwmon/pmbus/max16601.c   | 314 +++++++++++++++++++++++++++++++
>  5 files changed, 484 insertions(+)
>  create mode 100644 Documentation/hwmon/max16601.rst
>  create mode 100644 drivers/hwmon/pmbus/max16601.c
>
> diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst
> index 8ef62fd39787..df87ae15a1d4 100644
> --- a/Documentation/hwmon/index.rst
> +++ b/Documentation/hwmon/index.rst
> @@ -106,6 +106,7 @@ Hardware Monitoring Kernel Drivers
>     max16064
>     max16065
>     max1619
> +   max16601
>     max1668
>     max197
>     max20730
> diff --git a/Documentation/hwmon/max16601.rst b/Documentation/hwmon/max16601.rst
> new file mode 100644
> index 000000000000..346e74674c51
> --- /dev/null
> +++ b/Documentation/hwmon/max16601.rst
> @@ -0,0 +1,159 @@
> +.. SPDX-License-Identifier: GPL-2.0
> +
> +Kernel driver max16601
> +======================
> +
> +Supported chips:
> +
> +  * Maxim MAX16601
> +
> +    Prefix: 'max16601'
> +
> +    Addresses scanned: -
> +
> +    Datasheet: Not published
> +
> +Author: Guenter Roeck <linux@roeck-us.net>
> +
> +
> +Description
> +-----------
> +
> +This driver supports the MAX16601 VR13.HC Dual-Output Voltage Regulator
> +Chipset.
> +
> +The driver is a client driver to the core PMBus driver.
> +Please see Documentation/hwmon/pmbus.rst for details on PMBus client drivers.
> +
> +
> +Usage Notes
> +-----------
> +
> +This driver does not auto-detect devices. You will have to instantiate the
> +devices explicitly. Please see Documentation/i2c/instantiating-devices.rst for
> +details.
> +
> +
> +Platform data support
> +---------------------
> +
> +The driver supports standard PMBus driver platform data.
> +
> +
> +Sysfs entries
> +-------------
> +
> +The following attributes are supported.
> +
> +======================= =======================================================
> +in1_label              "vin1"
> +in1_input              VCORE input voltage.
> +in1_alarm              Input voltage alarm.
> +
> +in2_label              "vout1"
> +in2_input              VCORE output voltage.
> +in2_alarm              Output voltage alarm.
> +
> +curr1_label            "iin1"
> +curr1_input            VCORE input current, derived from duty cycle and output
> +                       current.
> +curr1_max              Maximum input current.
> +curr1_max_alarm                Current high alarm.
> +
> +curr2_label            "iin1.0"
> +curr2_input            VCORE phase 0 input current.
> +
> +curr3_label            "iin1.1"
> +curr3_input            VCORE phase 1 input current.
> +
> +curr4_label            "iin1.2"
> +curr4_input            VCORE phase 2 input current.
> +
> +curr5_label            "iin1.3"
> +curr5_input            VCORE phase 3 input current.
> +
> +curr6_label            "iin1.4"
> +curr6_input            VCORE phase 4 input current.
> +
> +curr7_label            "iin1.5"
> +curr7_input            VCORE phase 5 input current.
> +
> +curr8_label            "iin1.6"
> +curr8_input            VCORE phase 6 input current.
> +
> +curr9_label            "iin1.7"
> +curr9_input            VCORE phase 7 input current.
> +
> +curr10_label           "iin2"
> +curr10_input           VCORE input current, derived from sensor element.
> +
> +curr11_label           "iin3"
> +curr11_input           VSA input current.
> +
> +curr12_label           "iout1"
> +curr12_input           VCORE output current.
> +curr12_crit            Critical output current.
> +curr12_crit_alarm      Output current critical alarm.
> +curr12_max             Maximum output current.
> +curr12_max_alarm       Output current high alarm.
> +
> +curr13_label           "iout1.0"
> +curr13_input           VCORE phase 0 output current.
> +
> +curr14_label           "iout1.1"
> +curr14_input           VCORE phase 1 output current.
> +
> +curr15_label           "iout1.2"
> +curr15_input           VCORE phase 2 output current.
> +
> +curr16_label           "iout1.3"
> +curr16_input           VCORE phase 3 output current.
> +
> +curr17_label           "iout1.4"
> +curr17_input           VCORE phase 4 output current.
> +
> +curr18_label           "iout1.5"
> +curr18_input           VCORE phase 5 output current.
> +
> +curr19_label           "iout1.6"
> +curr19_input           VCORE phase 6 output current.
> +
> +curr20_label           "iout1.7"
> +curr20_input           VCORE phase 7 output current.
> +
> +curr21_label           "iout3"
> +curr21_input           VSA output current.
> +curr21_highest         Historical maximum VSA output current.
> +curr21_reset_history   Write any value to reset curr21_highest.
> +curr21_crit            Critical output current.
> +curr21_crit_alarm      Output current critical alarm.
> +curr21_max             Maximum output current.
> +curr21_max_alarm       Output current high alarm.
> +
> +power1_label           "pin1"
> +power1_input           Input power, derived from duty cycle and output current.
> +power1_alarm           Input power alarm.
> +
> +power2_label           "pin2"
> +power2_input           Input power, derived from input current sensor.
> +
> +power3_label           "pout"
> +power3_input           Output power.
> +
> +temp1_input            VCORE temperature.
> +temp1_crit             Critical high temperature.
> +temp1_crit_alarm       Chip temperature critical high alarm.
> +temp1_max              Maximum temperature.
> +temp1_max_alarm                Chip temperature high alarm.
> +
> +temp2_input            TSENSE_0 temperature
> +temp3_input            TSENSE_1 temperature
> +temp4_input            TSENSE_2 temperature
> +temp5_input            TSENSE_3 temperature
> +
> +temp6_input            VSA temperature.
> +temp6_crit             Critical high temperature.
> +temp6_crit_alarm       Chip temperature critical high alarm.
> +temp6_max              Maximum temperature.
> +temp6_max_alarm                Chip temperature high alarm.
> +======================= =======================================================
> diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig
> index de12a565006d..a337195b1c39 100644
> --- a/drivers/hwmon/pmbus/Kconfig
> +++ b/drivers/hwmon/pmbus/Kconfig
> @@ -146,6 +146,15 @@ config SENSORS_MAX16064
>           This driver can also be built as a module. If so, the module will
>           be called max16064.
>
> +config SENSORS_MAX16601
> +       tristate "Maxim MAX16601"
> +       help
> +         If you say yes here you get hardware monitoring support for Maxim
> +         MAX16601.
> +
> +         This driver can also be built as a module. If so, the module will
> +         be called max16601.
> +
>  config SENSORS_MAX20730
>         tristate "Maxim MAX20730, MAX20734, MAX20743"
>         help
> diff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile
> index 5feb45806123..c4b15db996ad 100644
> --- a/drivers/hwmon/pmbus/Makefile
> +++ b/drivers/hwmon/pmbus/Makefile
> @@ -17,6 +17,7 @@ obj-$(CONFIG_SENSORS_LM25066) += lm25066.o
>  obj-$(CONFIG_SENSORS_LTC2978)  += ltc2978.o
>  obj-$(CONFIG_SENSORS_LTC3815)  += ltc3815.o
>  obj-$(CONFIG_SENSORS_MAX16064) += max16064.o
> +obj-$(CONFIG_SENSORS_MAX16601) += max16601.o
>  obj-$(CONFIG_SENSORS_MAX20730) += max20730.o
>  obj-$(CONFIG_SENSORS_MAX20751) += max20751.o
>  obj-$(CONFIG_SENSORS_MAX31785) += max31785.o
> diff --git a/drivers/hwmon/pmbus/max16601.c b/drivers/hwmon/pmbus/max16601.c
> new file mode 100644
> index 000000000000..e10ab394b8de
> --- /dev/null
> +++ b/drivers/hwmon/pmbus/max16601.c
> @@ -0,0 +1,314 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Hardware monitoring driver for Maxim MAX16601
> + *
> + * Implementation notes:
> + *
> + * Ths chip supports two rails, VCORE and VSA. Telemetry information for the
> + * two rails is reported in two subsequent I2C addresses. The driver
> + * instantiates a dummy I2C client at the second I2C address to report
> + * information for the VSA rail in a single instance of the driver.
> + * Telemetry for the VSA rail is reported to the PMBus core in PMBus page 2.
> + *
> + * The chip reports input current using two separate methods. The input current
> + * reported with the standard READ_IIN command is derived from the output
> + * current. The first method is reported to the PMBus core with PMBus page 0,
> + * the second method is reported with PMBus page 1.
> + *
> + * The chip supports reading per-phase temperatures and per-phase input/output
> + * currents for VCORE. Telemetry is reported in vendor specific registers.
> + * The driver translates the vendor specific register values to PMBus standard
> + * register values and reports per-phase information in PMBus page 0.
> + *
> + * Copyright 2019, 2020 Google LLC.
> + */
> +
> +#include <linux/bits.h>
> +#include <linux/i2c.h>
> +#include <linux/init.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +
> +#include "pmbus.h"
> +
> +#define REG_SETPT_DVID         0xd1
> +#define  DAC_10MV_MODE         BIT(4)
> +#define REG_PHASE_ID           0xf3
> +#define  CORE_RAIL_INDICATOR   BIT(7)

Maybe we can move the above two lines down, so the register addresses
become sequential in the source file.

> +#define REG_IOUT_AVG_PK                0xee
> +#define REG_IIN_SENSOR         0xf1
> +#define REG_TOTAL_INPUT_POWER  0xf2
> +#define REG_PHASE_REPORTING    0xf4
> +
> +struct max16601_data {
> +       struct pmbus_driver_info info;
> +       struct i2c_client *vsa;
> +       int iout_avg_pkg;
> +};
> +
> +#define to_max16601_data(x) container_of(x, struct max16601_data, info)
> +
> +static int max16601_read_byte(struct i2c_client *client, int page, int reg)
> +{
> +       const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
> +       struct max16601_data *data = to_max16601_data(info);
> +
> +       if (page > 0) {
> +               if (page == 2)  /* VSA */
> +                       return i2c_smbus_read_byte_data(data->vsa, reg);
> +               return -EOPNOTSUPP;
> +       }
> +       return -ENODATA;
> +}
> +
> +static int max16601_read_word(struct i2c_client *client, int page, int phase,
> +                             int reg)
> +{
> +       const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
> +       struct max16601_data *data = to_max16601_data(info);
> +       u8 buf[I2C_SMBUS_BLOCK_MAX + 1];
> +       int ret;
> +
> +       switch (page) {
> +       case 0:         /* VCORE */
> +               if (phase == 0xff)
> +                       return -ENODATA;
> +               switch (reg) {
> +               case PMBUS_READ_IIN:
> +               case PMBUS_READ_IOUT:
> +               case PMBUS_READ_TEMPERATURE_1:
> +                       ret = i2c_smbus_write_byte_data(client, REG_PHASE_ID,
> +                                                       phase);
> +                       if (ret)
> +                               return ret;
> +                       ret = i2c_smbus_read_block_data(client,
> +                                                       REG_PHASE_REPORTING,
> +                                                       buf);
> +                       if (ret < 0)
> +                               return ret;
> +                       if (ret < 6)
> +                               return -EIO;
> +                       switch (reg) {
> +                       case PMBUS_READ_TEMPERATURE_1:
> +                               return buf[1] << 8 | buf[0];
> +                       case PMBUS_READ_IOUT:
> +                               return buf[3] << 8 | buf[2];
> +                       case PMBUS_READ_IIN:
> +                               return buf[5] << 8 | buf[4];
> +                       default:
> +                               break;
> +                       }
> +               }
> +               return -EOPNOTSUPP;
> +       case 1:         /* VCORE, read IIN/PIN from sensor element */
> +               switch (reg) {
> +               case PMBUS_READ_IIN:
> +                       return i2c_smbus_read_word_data(client, REG_IIN_SENSOR);
> +               case PMBUS_READ_PIN:
> +                       return i2c_smbus_read_word_data(client,
> +                                                       REG_TOTAL_INPUT_POWER);
> +               default:
> +                       break;
> +               }
> +               return -EOPNOTSUPP;
> +       case 2:         /* VSA */
> +               switch (reg) {
> +               case PMBUS_VIRT_READ_IOUT_MAX:
> +                       ret = i2c_smbus_read_word_data(data->vsa,
> +                                                      REG_IOUT_AVG_PK);
> +                       if (ret < 0)
> +                               return ret;
> +                       if (sign_extend32(ret, 10) >
> +                           sign_extend32(data->iout_avg_pkg, 10))
> +                               data->iout_avg_pkg = ret;
> +                       return data->iout_avg_pkg;
> +               case PMBUS_VIRT_RESET_IOUT_HISTORY:
> +                       return 0;
> +               case PMBUS_IOUT_OC_FAULT_LIMIT:
> +               case PMBUS_IOUT_OC_WARN_LIMIT:
> +               case PMBUS_OT_FAULT_LIMIT:
> +               case PMBUS_OT_WARN_LIMIT:
> +               case PMBUS_READ_IIN:
> +               case PMBUS_READ_IOUT:
> +               case PMBUS_READ_TEMPERATURE_1:
> +               case PMBUS_STATUS_WORD:
> +                       return i2c_smbus_read_word_data(data->vsa, reg);
> +               default:
> +                       return -EOPNOTSUPP;
> +               }
> +       default:
> +               return -EOPNOTSUPP;
> +       }
> +}
> +
> +static int max16601_write_byte(struct i2c_client *client, int page, u8 reg)
> +{
> +       const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
> +       struct max16601_data *data = to_max16601_data(info);
> +
> +       if (page == 2) {
> +               if (reg == PMBUS_CLEAR_FAULTS)
> +                       return i2c_smbus_write_byte(data->vsa, reg);
> +               return -EOPNOTSUPP;
> +       }
> +       return -ENODATA;
> +}
> +
> +static int max16601_write_word(struct i2c_client *client, int page, int reg,
> +                              u16 value)
> +{
> +       const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
> +       struct max16601_data *data = to_max16601_data(info);
> +
> +       switch (page) {
> +       case 0:         /* VCORE */
> +               return -ENODATA;
> +       case 1:         /* VCORE IIN/PIN from sensor element */
> +       default:
> +               return -EOPNOTSUPP;
> +       case 2:         /* VSA */
> +               switch (reg) {
> +               case PMBUS_VIRT_RESET_IOUT_HISTORY:
> +                       data->iout_avg_pkg = 0xfc00;
> +                       return 0;
> +               case PMBUS_IOUT_OC_FAULT_LIMIT:
> +               case PMBUS_IOUT_OC_WARN_LIMIT:
> +               case PMBUS_OT_FAULT_LIMIT:
> +               case PMBUS_OT_WARN_LIMIT:
> +                       return i2c_smbus_write_word_data(data->vsa, reg, value);
> +               default:
> +                       return -EOPNOTSUPP;
> +               }
> +       }
> +}
> +
> +static int max16601_identify(struct i2c_client *client,
> +                            struct pmbus_driver_info *info)
> +{
> +       int reg;
> +
> +       reg = i2c_smbus_read_byte_data(client, REG_SETPT_DVID);
> +       if (reg < 0)
> +               return reg;
> +       if (reg & DAC_10MV_MODE)
> +               info->vrm_version[0] = vr13;
> +       else
> +               info->vrm_version[0] = vr12;
> +
> +       return 0;
> +}
> +
> +static struct pmbus_driver_info max16601_info = {
> +       .pages = 3,
> +       .format[PSC_VOLTAGE_IN] = linear,
> +       .format[PSC_VOLTAGE_OUT] = vid,
> +       .format[PSC_CURRENT_IN] = linear,
> +       .format[PSC_CURRENT_OUT] = linear,
> +       .format[PSC_TEMPERATURE] = linear,
> +       .format[PSC_POWER] = linear,
> +       .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_IIN | PMBUS_HAVE_PIN |
> +               PMBUS_HAVE_STATUS_INPUT |
> +               PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT |
> +               PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT |
> +               PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP |
> +               PMBUS_HAVE_POUT | PMBUS_PAGE_VIRTUAL | PMBUS_PHASE_VIRTUAL,
> +       .func[1] = PMBUS_HAVE_IIN | PMBUS_HAVE_PIN | PMBUS_PAGE_VIRTUAL,
> +       .func[2] = PMBUS_HAVE_IIN | PMBUS_HAVE_STATUS_INPUT |
> +               PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT |
> +               PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP | PMBUS_PAGE_VIRTUAL,
> +       .phases[0] = 8,
> +       .pfunc[0] = PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT | PMBUS_HAVE_TEMP,
> +       .pfunc[1] = PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT,
> +       .pfunc[2] = PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT | PMBUS_HAVE_TEMP,
> +       .pfunc[3] = PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT,
> +       .pfunc[4] = PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT | PMBUS_HAVE_TEMP,
> +       .pfunc[5] = PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT,
> +       .pfunc[6] = PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT | PMBUS_HAVE_TEMP,
> +       .pfunc[7] = PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT,
> +       .identify = max16601_identify,
> +       .read_byte_data = max16601_read_byte,
> +       .read_word_data = max16601_read_word,
> +       .write_byte = max16601_write_byte,
> +       .write_word_data = max16601_write_word,
> +};
> +
> +static void max16601_remove(void *_data)
> +{
> +       struct max16601_data *data = _data;
> +
> +       i2c_unregister_device(data->vsa);
> +}
> +
> +static int max16601_probe(struct i2c_client *client,
> +                         const struct i2c_device_id *id)
> +{
> +       struct device *dev = &client->dev;
> +       u8 buf[I2C_SMBUS_BLOCK_MAX + 1];
> +       struct max16601_data *data;
> +       int ret;
> +
> +       if (!i2c_check_functionality(client->adapter,
> +                                    I2C_FUNC_SMBUS_READ_BYTE_DATA |
> +                                    I2C_FUNC_SMBUS_READ_BLOCK_DATA))
> +               return -ENODEV;
> +
> +       ret = i2c_smbus_read_block_data(client, PMBUS_IC_DEVICE_ID, buf);
> +       if (ret < 0)
> +               return -ENODEV;
> +
> +       /* PMBUS_IC_DEVICE_ID is expected to return "MAX16601y.xx" */
> +       if (ret < 11 || strncmp(buf, "MAX16601", 8)) {
> +               buf[ret] = '\0';
> +               dev_err(dev, "Unsupported chip '%s'\n", buf);
> +               return -ENODEV;
> +       }
> +
> +       ret = i2c_smbus_read_byte_data(client, REG_PHASE_ID);
> +       if (ret < 0)
> +               return ret;
> +       if (!(ret & CORE_RAIL_INDICATOR)) {
> +               dev_err(dev,
> +                       "Driver must be instantiated on CORE rail I2C address\n");
> +               return -ENODEV;
> +       }
> +
> +       data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
> +       if (!data)
> +               return -ENOMEM;
> +
> +       data->iout_avg_pkg = 0xfc00;
> +       data->vsa = i2c_new_dummy_device(client->adapter, client->addr + 1);
> +       if (IS_ERR(data->vsa)) {
> +               dev_err(dev, "Failed to register VSA client\n");
> +               return PTR_ERR(data->vsa);
> +       }
> +       ret = devm_add_action_or_reset(dev, max16601_remove, data);
> +       if (ret)
> +               return ret;
> +
> +       data->info = max16601_info;
> +
> +       return pmbus_do_probe(client, id, &data->info);
> +}
> +
> +static const struct i2c_device_id max16601_id[] = {
> +       {"max16601", 0},
> +       {}
> +};
> +
> +MODULE_DEVICE_TABLE(i2c, max16601_id);
> +
> +static struct i2c_driver max16601_driver = {
> +       .driver = {
> +                  .name = "max16601",
> +                  },
> +       .probe = max16601_probe,
> +       .remove = pmbus_do_remove,
> +       .id_table = max16601_id,
> +};
> +
> +module_i2c_driver(max16601_driver);
> +
> +MODULE_AUTHOR("Guenter Roeck <linux@roeck-us.net>");
> +MODULE_DESCRIPTION("PMBus driver for Maxim MAX16601");
> +MODULE_LICENSE("GPL v2");
> --
> 2.17.1
>

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

* Re: [PATCH] hwmon: (pmbus) Driver for Maxim MAX16601
  2020-05-08  0:54 ` Alex Qiu
@ 2020-05-08  4:29   ` Guenter Roeck
  0 siblings, 0 replies; 3+ messages in thread
From: Guenter Roeck @ 2020-05-08  4:29 UTC (permalink / raw)
  To: Alex Qiu; +Cc: Hardware Monitoring, Jean Delvare

On 5/7/20 5:54 PM, Alex Qiu wrote:
> Hi Guenter,
> 
> The drivers look good on our system, although more tests are ongoing.
> The patch LGTM with minor nit. Thank you very much!
> 
> On Wed, May 6, 2020 at 2:49 PM Guenter Roeck <linux@roeck-us.net> wrote:
>>
>> MAX16601 is a VR13.HC Dual-Output Voltage Regulator Chipset,
>> implementing a (8+1) multiphase synchronous buck converter.
>>
>> Cc: Alex Qiu <xqiu@google.com>
>> Signed-off-by: Guenter Roeck <linux@roeck-us.net>
>> ---

[ ... ]

>> +
>> +#define REG_SETPT_DVID         0xd1
>> +#define  DAC_10MV_MODE         BIT(4)
>> +#define REG_PHASE_ID           0xf3
>> +#define  CORE_RAIL_INDICATOR   BIT(7)
> 
> Maybe we can move the above two lines down, so the register addresses
> become sequential in the source file.
> 

Makes sense. Will do.

Thanks,
Guenter



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

end of thread, other threads:[~2020-05-08  4:29 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-05-06 21:49 [PATCH] hwmon: (pmbus) Driver for Maxim MAX16601 Guenter Roeck
2020-05-08  0:54 ` Alex Qiu
2020-05-08  4:29   ` Guenter Roeck

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).