linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v3 0/3] hwmon: ltc2992: Add support
@ 2020-12-03  7:11 alexandru.tachici
  2020-12-03  7:11 ` [PATCH v3 1/3] " alexandru.tachici
                   ` (2 more replies)
  0 siblings, 3 replies; 7+ messages in thread
From: alexandru.tachici @ 2020-12-03  7:11 UTC (permalink / raw)
  To: linux-hwmon, linux-kernel, devicetree
  Cc: Weston.Sapia, Brad.Lovell, Sal.Afzal, robh+dt, linux, Alexandru Tachici

From: Alexandru Tachici <alexandru.tachici@analog.com>

LTC2992 is a rail-to-rail system monitor that
measures current, voltage, and power of two supplies.

Two ADCs simultaneously measure each supply’s current.
A third ADC monitors the input voltages and four
auxiliary external voltages (GPIOs).

1. Use hwmon to create sysfs entries for current, voltage
and power of two 0V to 100V supplies. Create sysfs entries
for voltage sensed on the 4 GPIO pins.

2. Expose to userspace the 4 open-drain GPIOs provided by ltc2992.

3. DT bindings for ltc2992.

Alexandru Tachici (3):
  hwmon: ltc2992: Add support
  hwmon: ltc2992: Add support for GPIOs.
  dt-binding: hwmon: Add documentation for ltc2992

Changelog v2 -> v3:
- removed unnecessary includes
- removed unnecessary initialization of 'reg' in ltc2992_write_in() and ltc2992_write_power().
- removed i2c_check_functionality() in ltc2992_i2c_probe

 .../bindings/hwmon/adi,ltc2992.yaml           |  80 ++
 Documentation/hwmon/index.rst                 |   1 +
 Documentation/hwmon/ltc2992.rst               |  56 +
 drivers/hwmon/Kconfig                         |  12 +
 drivers/hwmon/Makefile                        |   1 +
 drivers/hwmon/ltc2992.c                       | 971 ++++++++++++++++++
 6 files changed, 1121 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/hwmon/adi,ltc2992.yaml
 create mode 100644 Documentation/hwmon/ltc2992.rst
 create mode 100644 drivers/hwmon/ltc2992.c

-- 
2.20.1


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

* [PATCH v3 1/3] hwmon: ltc2992: Add support
  2020-12-03  7:11 [PATCH v3 0/3] hwmon: ltc2992: Add support alexandru.tachici
@ 2020-12-03  7:11 ` alexandru.tachici
  2020-12-04 15:21   ` Guenter Roeck
  2020-12-03  7:11 ` [PATCH v3 2/3] hwmon: ltc2992: Add support for GPIOs alexandru.tachici
  2020-12-03  7:11 ` [PATCH v3 3/3] dt-binding: hwmon: Add documentation for ltc2992 alexandru.tachici
  2 siblings, 1 reply; 7+ messages in thread
From: alexandru.tachici @ 2020-12-03  7:11 UTC (permalink / raw)
  To: linux-hwmon, linux-kernel, devicetree
  Cc: Weston.Sapia, Brad.Lovell, Sal.Afzal, robh+dt, linux, Alexandru Tachici

From: Alexandru Tachici <alexandru.tachici@analog.com>

LTC2992 is a rail-to-rail system monitor that
measures current, voltage, and power of two supplies.

Two ADCs simultaneously measure each supply’s current.
A third ADC monitors the input voltages and four
auxiliary external voltages.

Signed-off-by: Alexandru Tachici <alexandru.tachici@analog.com>
---
 Documentation/hwmon/index.rst   |   1 +
 Documentation/hwmon/ltc2992.rst |  56 +++
 drivers/hwmon/Kconfig           |  11 +
 drivers/hwmon/Makefile          |   1 +
 drivers/hwmon/ltc2992.c         | 813 ++++++++++++++++++++++++++++++++
 5 files changed, 882 insertions(+)
 create mode 100644 Documentation/hwmon/ltc2992.rst
 create mode 100644 drivers/hwmon/ltc2992.c

diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst
index e6b91ab12978..98575a8b1918 100644
--- a/Documentation/hwmon/index.rst
+++ b/Documentation/hwmon/index.rst
@@ -100,6 +100,7 @@ Hardware Monitoring Kernel Drivers
    lm95234
    lm95245
    lochnagar
+   ltc2992
    ltc2945
    ltc2947
    ltc2978
diff --git a/Documentation/hwmon/ltc2992.rst b/Documentation/hwmon/ltc2992.rst
new file mode 100644
index 000000000000..46aa1aa84a1a
--- /dev/null
+++ b/Documentation/hwmon/ltc2992.rst
@@ -0,0 +1,56 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+Kernel driver ltc2992
+=====================
+
+Supported chips:
+  * Linear Technology LTC2992
+    Prefix: 'ltc2992'
+    Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/ltc2992.pdf
+
+Author: Alexandru Tachici <alexandru.tachici@analog.com>
+
+
+Description
+-----------
+
+This driver supports hardware monitoring for Linear Technology LTC2992 power monitor.
+
+LTC2992 is a rail-to-rail system monitor that measures current,
+voltage, and power of two supplies.
+
+Two ADCs simultaneously measure each supply’s current. A third ADC monitors
+the input voltages and four auxiliary external voltages.
+
+
+Sysfs entries
+-------------
+
+The following attributes are supported. Limits are read-write,
+all other attributes are read-only.
+
+in_reset_history	Reset all highest/lowest values.
+
+inX_input		Measured voltage.
+inX_lowest		Minimum measured voltage.
+inX_highest		Maximum measured voltage.
+inX_min			Minimum voltage allowed.
+inX_max			Maximum voltage allowed.
+inX_min_alarm		An undervoltage occurred. Cleared on read.
+inX_max_alarm		An overvoltage occurred. Cleared on read.
+
+currX_input		Measured current.
+currX_lowest		Minimum measured current.
+currX_highest		Maximum measured current.
+currX_min		Minimum current allowed.
+currX_max		Maximum current allowed.
+currX_min_alarm		An undercurrent occurred. Cleared on read.
+currX_max_alarm		An overcurrent occurred. Cleared on read.
+
+powerX_input		Measured power.
+powerX_input_lowest	Minimum measured voltage.
+powerX_input_highest	Maximum measured voltage.
+powerX_min		Minimum power.
+powerX_max		Maximum power.
+powerX_min_alarm	An underpower occurred. Cleared on read.
+powerX_max_alarm	An overpower occurred. Cleared on read.
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index a850e4f0e0bd..bf9e387270d6 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -858,6 +858,17 @@ config SENSORS_LTC2990
 	  This driver can also be built as a module. If so, the module will
 	  be called ltc2990.
 
+config SENSORS_LTC2992
+	tristate "Linear Technology LTC2992"
+	depends on I2C
+	help
+	  If you say yes here you get support for Linear Technology LTC2992
+	  I2C System Monitor. The LTC2992 measures current, voltage, and
+	  power of two supplies.
+
+	  This driver can also be built as a module. If so, the module will
+	  be called ltc2992.
+
 config SENSORS_LTC4151
 	tristate "Linear Technology LTC4151"
 	depends on I2C
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index 9db2903b61e5..d6172c4807c4 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -118,6 +118,7 @@ obj-$(CONFIG_SENSORS_LTC2947)	+= ltc2947-core.o
 obj-$(CONFIG_SENSORS_LTC2947_I2C) += ltc2947-i2c.o
 obj-$(CONFIG_SENSORS_LTC2947_SPI) += ltc2947-spi.o
 obj-$(CONFIG_SENSORS_LTC2990)	+= ltc2990.o
+obj-$(CONFIG_SENSORS_LTC2992)	+= ltc2992.o
 obj-$(CONFIG_SENSORS_LTC4151)	+= ltc4151.o
 obj-$(CONFIG_SENSORS_LTC4215)	+= ltc4215.o
 obj-$(CONFIG_SENSORS_LTC4222)	+= ltc4222.o
diff --git a/drivers/hwmon/ltc2992.c b/drivers/hwmon/ltc2992.c
new file mode 100644
index 000000000000..c11d585a9600
--- /dev/null
+++ b/drivers/hwmon/ltc2992.c
@@ -0,0 +1,813 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+/*
+ * LTC2992 - Dual Wide Range Power Monitor
+ *
+ * Copyright 2020 Analog Devices Inc.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/err.h>
+#include <linux/hwmon.h>
+#include <linux/i2c.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+
+#define LTC2992_CTRLB			0x01
+#define LTC2992_FAULT1			0x03
+#define LTC2992_POWER1			0x05
+#define LTC2992_POWER1_MAX		0x08
+#define LTC2992_POWER1_MIN		0x0B
+#define LTC2992_POWER1_MAX_THRESH	0x0E
+#define LTC2992_POWER1_MIN_THRESH	0x11
+#define LTC2992_DSENSE1			0x14
+#define LTC2992_DSENSE1_MAX		0x16
+#define LTC2992_DSENSE1_MIN		0x18
+#define LTC2992_DSENSE1_MAX_THRESH	0x1A
+#define LTC2992_DSENSE1_MIN_THRESH	0x1C
+#define LTC2992_SENSE1			0x1E
+#define LTC2992_SENSE1_MAX		0x20
+#define LTC2992_SENSE1_MIN		0x22
+#define LTC2992_SENSE1_MAX_THRESH	0x24
+#define LTC2992_SENSE1_MIN_THRESH	0x26
+#define LTC2992_G1			0x28
+#define LTC2992_G1_MAX			0x2A
+#define LTC2992_G1_MIN			0x2C
+#define LTC2992_G1_MAX_THRESH		0x2E
+#define LTC2992_G1_MIN_THRESH		0x30
+#define LTC2992_FAULT2			0x35
+#define LTC2992_G2			0x5A
+#define LTC2992_G2_MAX			0x5C
+#define LTC2992_G2_MIN			0x5E
+#define LTC2992_G2_MAX_THRESH		0x60
+#define LTC2992_G2_MIN_THRESH		0x62
+#define LTC2992_G3			0x64
+#define LTC2992_G3_MAX			0x66
+#define LTC2992_G3_MIN			0x68
+#define LTC2992_G3_MAX_THRESH		0x6A
+#define LTC2992_G3_MIN_THRESH		0x6C
+#define LTC2992_G4			0x6E
+#define LTC2992_G4_MAX			0x70
+#define LTC2992_G4_MIN			0x72
+#define LTC2992_G4_MAX_THRESH		0x74
+#define LTC2992_G4_MIN_THRESH		0x76
+#define LTC2992_FAULT3			0x92
+
+#define LTC2992_POWER(x)		(LTC2992_POWER1 + ((x) * 0x32))
+#define LTC2992_POWER_MAX(x)		(LTC2992_POWER1_MAX + ((x) * 0x32))
+#define LTC2992_POWER_MIN(x)		(LTC2992_POWER1_MIN + ((x) * 0x32))
+#define LTC2992_POWER_MAX_THRESH(x)	(LTC2992_POWER1_MAX_THRESH + ((x) * 0x32))
+#define LTC2992_POWER_MIN_THRESH(x)	(LTC2992_POWER1_MIN_THRESH + ((x) * 0x32))
+#define LTC2992_DSENSE(x)		(LTC2992_DSENSE1 + ((x) * 0x32))
+#define LTC2992_DSENSE_MAX(x)		(LTC2992_DSENSE1_MAX + ((x) * 0x32))
+#define LTC2992_DSENSE_MIN(x)		(LTC2992_DSENSE1_MIN + ((x) * 0x32))
+#define LTC2992_DSENSE_MAX_THRESH(x)	(LTC2992_DSENSE1_MAX_THRESH + ((x) * 0x32))
+#define LTC2992_DSENSE_MIN_THRESH(x)	(LTC2992_DSENSE1_MIN_THRESH + ((x) * 0x32))
+#define LTC2992_SENSE(x)		(LTC2992_SENSE1 + ((x) * 0x32))
+#define LTC2992_SENSE_MAX(x)		(LTC2992_SENSE1_MAX + ((x) * 0x32))
+#define LTC2992_SENSE_MIN(x)		(LTC2992_SENSE1_MIN + ((x) * 0x32))
+#define LTC2992_SENSE_MAX_THRESH(x)	(LTC2992_SENSE1_MAX_THRESH + ((x) * 0x32))
+#define LTC2992_SENSE_MIN_THRESH(x)	(LTC2992_SENSE1_MIN_THRESH + ((x) * 0x32))
+#define LTC2992_POWER_FAULT(x)		(LTC2992_FAULT1 + ((x) * 0x32))
+#define LTC2992_SENSE_FAULT(x)		(LTC2992_FAULT1 + ((x) * 0x32))
+#define LTC2992_DSENSE_FAULT(x)		(LTC2992_FAULT1 + ((x) * 0x32))
+
+/* CTRLB register bitfields */
+#define LTC2992_RESET_HISTORY		BIT(3)
+
+/* FAULT1 FAULT2 registers common bitfields */
+#define LTC2992_POWER_FAULT_MSK(x)	(BIT(6) << (x))
+#define LTC2992_DSENSE_FAULT_MSK(x)	(BIT(4) << (x))
+#define LTC2992_SENSE_FAULT_MSK(x)	(BIT(2) << (x))
+
+/* FAULT1 bitfields */
+#define LTC2992_GPIO1_FAULT_MSK(x)	(BIT(0) << (x))
+
+/* FAULT2 bitfields */
+#define LTC2992_GPIO2_FAULT_MSK(x)	(BIT(0) << (x))
+
+/* FAULT3 bitfields */
+#define LTC2992_GPIO3_FAULT_MSK(x)	(BIT(6) << (x))
+#define LTC2992_GPIO4_FAULT_MSK(x)	(BIT(4) << (x))
+
+#define LTC2992_IADC_NANOV_LSB		12500
+#define LTC2992_VADC_UV_LSB		25000
+#define LTC2992_VADC_GPIO_UV_LSB	500
+
+struct ltc2992_state {
+	struct i2c_client		*client;
+	struct regmap			*regmap;
+	u32				r_sense_uohm[2];
+};
+
+struct ltc2992_gpio_regs {
+	u8	data;
+	u8	max;
+	u8	min;
+	u8	max_thresh;
+	u8	min_thresh;
+	u8	alarm;
+	u8	min_alarm_msk;
+	u8	max_alarm_msk;
+};
+
+static const struct ltc2992_gpio_regs ltc2992_gpio_addr_map[] = {
+	{
+		.data = LTC2992_G1,
+		.max = LTC2992_G1_MAX,
+		.min = LTC2992_G1_MIN,
+		.max_thresh = LTC2992_G1_MAX_THRESH,
+		.min_thresh = LTC2992_G1_MIN_THRESH,
+		.alarm = LTC2992_FAULT1,
+		.min_alarm_msk = LTC2992_GPIO1_FAULT_MSK(0),
+		.max_alarm_msk = LTC2992_GPIO1_FAULT_MSK(1),
+	},
+	{
+		.data = LTC2992_G2,
+		.max = LTC2992_G2_MAX,
+		.min = LTC2992_G2_MIN,
+		.max_thresh = LTC2992_G2_MAX_THRESH,
+		.min_thresh = LTC2992_G2_MIN_THRESH,
+		.alarm = LTC2992_FAULT2,
+		.min_alarm_msk = LTC2992_GPIO2_FAULT_MSK(0),
+		.max_alarm_msk = LTC2992_GPIO2_FAULT_MSK(1),
+	},
+	{
+		.data = LTC2992_G3,
+		.max = LTC2992_G3_MAX,
+		.min = LTC2992_G3_MIN,
+		.max_thresh = LTC2992_G3_MAX_THRESH,
+		.min_thresh = LTC2992_G3_MIN_THRESH,
+		.alarm = LTC2992_FAULT3,
+		.min_alarm_msk = LTC2992_GPIO3_FAULT_MSK(0),
+		.max_alarm_msk = LTC2992_GPIO3_FAULT_MSK(1),
+	},
+	{
+		.data = LTC2992_G4,
+		.max = LTC2992_G4_MAX,
+		.min = LTC2992_G4_MIN,
+		.max_thresh = LTC2992_G4_MAX_THRESH,
+		.min_thresh = LTC2992_G4_MIN_THRESH,
+		.alarm = LTC2992_FAULT3,
+		.min_alarm_msk = LTC2992_GPIO4_FAULT_MSK(0),
+		.max_alarm_msk = LTC2992_GPIO4_FAULT_MSK(1),
+	},
+};
+
+static int ltc2992_read_reg(struct ltc2992_state *st, u8 addr, const u8 reg_len)
+{
+	u8 regvals[4];
+	int ret;
+	int val;
+	int i;
+
+	ret = regmap_bulk_read(st->regmap, addr, regvals, reg_len);
+	if (ret < 0)
+		return ret;
+
+	val = 0;
+	for (i = 0; i < reg_len; i++)
+		val |= regvals[reg_len - i - 1] << (i * 8);
+
+	return val;
+}
+
+static int ltc2992_write_reg(struct ltc2992_state *st, u8 addr, const u8 reg_len, u32 val)
+{
+	u8 regvals[4];
+	int i;
+
+	for (i = 0; i < reg_len; i++)
+		regvals[reg_len - i - 1] = (val >> (i * 8)) & 0xFF;
+
+	return regmap_bulk_write(st->regmap, addr, regvals, reg_len);
+}
+
+static umode_t ltc2992_is_visible(const void *data, enum hwmon_sensor_types type, u32 attr,
+				  int channel)
+{
+	const struct ltc2992_state *st = data;
+
+	switch (type) {
+	case hwmon_chip:
+		switch (attr) {
+		case hwmon_chip_in_reset_history:
+			return 0200;
+		}
+		break;
+	case hwmon_in:
+		switch (attr) {
+		case hwmon_in_input:
+		case hwmon_in_lowest:
+		case hwmon_in_highest:
+		case hwmon_in_min_alarm:
+		case hwmon_in_max_alarm:
+			return 0444;
+		case hwmon_in_min:
+		case hwmon_in_max:
+			return 0644;
+		}
+		break;
+	case hwmon_curr:
+		switch (attr) {
+		case hwmon_curr_input:
+		case hwmon_curr_lowest:
+		case hwmon_curr_highest:
+		case hwmon_curr_min_alarm:
+		case hwmon_curr_max_alarm:
+			if (st->r_sense_uohm[channel])
+				return 0444;
+			break;
+		case hwmon_curr_min:
+		case hwmon_curr_max:
+			if (st->r_sense_uohm[channel])
+				return 0644;
+			break;
+		}
+		break;
+	case hwmon_power:
+		switch (attr) {
+		case hwmon_power_input:
+		case hwmon_power_input_lowest:
+		case hwmon_power_input_highest:
+		case hwmon_power_min_alarm:
+		case hwmon_power_max_alarm:
+			if (st->r_sense_uohm[channel])
+				return 0444;
+			break;
+		case hwmon_power_min:
+		case hwmon_power_max:
+			if (st->r_sense_uohm[channel])
+				return 0644;
+			break;
+		}
+		break;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static int ltc2992_get_voltage(struct ltc2992_state *st, u32 reg, u32 scale, long *val)
+{
+	int reg_val;
+
+	reg_val = ltc2992_read_reg(st, reg, 2);
+	if (reg_val < 0)
+		return reg_val;
+
+	reg_val = reg_val >> 4;
+	*val = DIV_ROUND_CLOSEST(reg_val * scale, 1000);
+
+	return 0;
+}
+
+static int ltc2992_set_voltage(struct ltc2992_state *st, u32 reg, u32 scale, long val)
+{
+	val = DIV_ROUND_CLOSEST(val * 1000, scale);
+	val = val << 4;
+
+	return ltc2992_write_reg(st, reg, 2, val);
+}
+
+static int ltc2992_read_gpio_alarm(struct ltc2992_state *st, int nr_gpio, u32 attr, long *val)
+{
+	int reg_val;
+	u32 mask;
+
+	if (attr == hwmon_in_max_alarm)
+		mask = ltc2992_gpio_addr_map[nr_gpio].max_alarm_msk;
+	else
+		mask = ltc2992_gpio_addr_map[nr_gpio].min_alarm_msk;
+
+	reg_val = ltc2992_read_reg(st, ltc2992_gpio_addr_map[nr_gpio].alarm, 1);
+	if (reg_val < 0)
+		return reg_val;
+
+	*val = !!(reg_val & mask);
+	reg_val &= ~mask;
+
+	return ltc2992_write_reg(st, ltc2992_gpio_addr_map[nr_gpio].alarm, 1, reg_val);
+}
+
+static int ltc2992_read_gpios_in(struct device *dev, u32 attr, int nr_gpio, long *val)
+{
+	struct ltc2992_state *st = dev_get_drvdata(dev);
+	u32 reg;
+
+	switch (attr) {
+	case hwmon_in_input:
+		reg = ltc2992_gpio_addr_map[nr_gpio].data;
+		break;
+	case hwmon_in_lowest:
+		reg = ltc2992_gpio_addr_map[nr_gpio].min;
+		break;
+	case hwmon_in_highest:
+		reg = ltc2992_gpio_addr_map[nr_gpio].max;
+		break;
+	case hwmon_in_min:
+		reg = ltc2992_gpio_addr_map[nr_gpio].min_thresh;
+		break;
+	case hwmon_in_max:
+		reg = ltc2992_gpio_addr_map[nr_gpio].max_thresh;
+		break;
+	case hwmon_in_min_alarm:
+	case hwmon_in_max_alarm:
+		return ltc2992_read_gpio_alarm(st, nr_gpio, attr, val);
+	default:
+		return -EOPNOTSUPP;
+	}
+
+	return ltc2992_get_voltage(st, reg, LTC2992_VADC_GPIO_UV_LSB, val);
+}
+
+static int ltc2992_read_in_alarm(struct ltc2992_state *st, int channel, long *val, u32 attr)
+{
+	u32 reg_val;
+	u32 mask;
+
+	if (attr == hwmon_in_max_alarm)
+		mask = LTC2992_SENSE_FAULT_MSK(1);
+	else
+		mask = LTC2992_SENSE_FAULT_MSK(0);
+
+	reg_val = ltc2992_read_reg(st, LTC2992_SENSE_FAULT(channel), 1);
+	if (reg_val < 0)
+		return reg_val;
+
+	*val = !!(reg_val & mask);
+	reg_val &= ~mask;
+
+	return ltc2992_write_reg(st, LTC2992_SENSE_FAULT(channel), 1, reg_val);
+}
+
+static int ltc2992_read_in(struct device *dev, u32 attr, int channel, long *val)
+{
+	struct ltc2992_state *st = dev_get_drvdata(dev);
+	u32 reg;
+
+	if (channel > 1)
+		return ltc2992_read_gpios_in(dev, attr, channel - 2, val);
+
+	switch (attr) {
+	case hwmon_in_input:
+		reg = LTC2992_SENSE(channel);
+		break;
+	case hwmon_in_lowest:
+		reg = LTC2992_SENSE_MIN(channel);
+		break;
+	case hwmon_in_highest:
+		reg = LTC2992_SENSE_MAX(channel);
+		break;
+	case hwmon_in_min:
+		reg = LTC2992_SENSE_MIN_THRESH(channel);
+		break;
+	case hwmon_in_max:
+		reg = LTC2992_SENSE_MAX_THRESH(channel);
+		break;
+	case hwmon_in_min_alarm:
+	case hwmon_in_max_alarm:
+		return ltc2992_read_in_alarm(st, channel, val, attr);
+	default:
+		return -EOPNOTSUPP;
+	}
+
+	return ltc2992_get_voltage(st, reg, LTC2992_VADC_UV_LSB, val);
+}
+
+static int ltc2992_get_current(struct ltc2992_state *st, u32 reg, u32 channel, long *val)
+{
+	u32 reg_val;
+
+	reg_val = ltc2992_read_reg(st, reg, 2);
+	if (reg_val < 0)
+		return reg_val;
+
+	reg_val = reg_val >> 4;
+	*val = DIV_ROUND_CLOSEST(reg_val * LTC2992_IADC_NANOV_LSB, st->r_sense_uohm[channel]);
+
+	return 0;
+}
+
+static int ltc2992_set_current(struct ltc2992_state *st, u32 reg, u32 channel, long val)
+{
+	u32 reg_val;
+
+	reg_val = DIV_ROUND_CLOSEST(val * st->r_sense_uohm[channel], LTC2992_IADC_NANOV_LSB);
+	reg_val = reg_val << 4;
+
+	return ltc2992_write_reg(st, reg, 2, reg_val);
+}
+
+static int ltc2992_read_curr_alarm(struct ltc2992_state *st, int channel, long *val, u32 attr)
+{
+	u32 reg_val;
+	u32 mask;
+
+	if (attr == hwmon_curr_max_alarm)
+		mask = LTC2992_DSENSE_FAULT_MSK(1);
+	else
+		mask = LTC2992_DSENSE_FAULT_MSK(0);
+
+	reg_val = ltc2992_read_reg(st, LTC2992_DSENSE_FAULT(channel), 1);
+	if (reg_val < 0)
+		return reg_val;
+
+	*val = !!(reg_val & mask);
+
+	reg_val &= ~mask;
+	return ltc2992_write_reg(st, LTC2992_DSENSE_FAULT(channel), 1, reg_val);
+}
+
+static int ltc2992_read_curr(struct device *dev, u32 attr, int channel, long *val)
+{
+	struct ltc2992_state *st = dev_get_drvdata(dev);
+	u32 reg;
+
+	switch (attr) {
+	case hwmon_curr_input:
+		reg = LTC2992_DSENSE(channel);
+		break;
+	case hwmon_curr_lowest:
+		reg = LTC2992_DSENSE_MIN(channel);
+		break;
+	case hwmon_curr_highest:
+		reg = LTC2992_DSENSE_MAX(channel);
+		break;
+	case hwmon_curr_min:
+		reg = LTC2992_DSENSE_MIN_THRESH(channel);
+		break;
+	case hwmon_curr_max:
+		reg = LTC2992_DSENSE_MAX_THRESH(channel);
+		break;
+	case hwmon_curr_min_alarm:
+	case hwmon_curr_max_alarm:
+		return ltc2992_read_curr_alarm(st, channel, val, attr);
+	default:
+		return -EOPNOTSUPP;
+	}
+
+	return ltc2992_get_current(st, reg, channel, val);
+}
+
+static int ltc2992_get_power(struct ltc2992_state *st, u32 reg, u32 channel, long *val)
+{
+	u32 reg_val;
+
+	reg_val = ltc2992_read_reg(st, reg, 3);
+	if (reg_val < 0)
+		return reg_val;
+
+	*val = mul_u64_u32_div(reg_val, LTC2992_VADC_UV_LSB * LTC2992_IADC_NANOV_LSB,
+			       st->r_sense_uohm[channel] * 1000);
+
+	return 0;
+}
+
+static int ltc2992_set_power(struct ltc2992_state *st, u32 reg, u32 channel, long val)
+{
+	u32 reg_val;
+
+	reg_val = mul_u64_u32_div(val, st->r_sense_uohm[channel] * 1000,
+				  LTC2992_VADC_UV_LSB * LTC2992_IADC_NANOV_LSB);
+
+	return ltc2992_write_reg(st, reg, 3, reg_val);
+}
+
+static int ltc2992_read_power_alarm(struct ltc2992_state *st, int channel, long *val, u32 attr)
+{
+	u32 reg_val;
+	u32 mask;
+
+	if (attr == hwmon_power_max_alarm)
+		mask = LTC2992_POWER_FAULT_MSK(1);
+	else
+		mask = LTC2992_POWER_FAULT_MSK(0);
+
+	reg_val = ltc2992_read_reg(st, LTC2992_POWER_FAULT(channel), 1);
+	if (reg_val < 0)
+		return reg_val;
+
+	*val = !!(reg_val & mask);
+	reg_val &= ~mask;
+
+	return ltc2992_write_reg(st, LTC2992_POWER_FAULT(channel), 1, reg_val);
+}
+
+static int ltc2992_read_power(struct device *dev, u32 attr, int channel, long *val)
+{
+	struct ltc2992_state *st = dev_get_drvdata(dev);
+	u32 reg;
+
+	switch (attr) {
+	case hwmon_power_input:
+		reg = LTC2992_POWER(channel);
+		break;
+	case hwmon_power_input_lowest:
+		reg = LTC2992_POWER_MIN(channel);
+		break;
+	case hwmon_power_input_highest:
+		reg = LTC2992_POWER_MAX(channel);
+		break;
+	case hwmon_power_min:
+		reg = LTC2992_POWER_MIN_THRESH(channel);
+		break;
+	case hwmon_power_max:
+		reg = LTC2992_POWER_MAX_THRESH(channel);
+		break;
+	case hwmon_power_min_alarm:
+	case hwmon_power_max_alarm:
+		return ltc2992_read_power_alarm(st, channel, val, attr);
+	default:
+		return -EOPNOTSUPP;
+	}
+
+	return ltc2992_get_power(st, reg, channel, val);
+}
+
+static int ltc2992_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel,
+			long *val)
+{
+	switch (type) {
+	case hwmon_in:
+		return ltc2992_read_in(dev, attr, channel, val);
+	case hwmon_curr:
+		return ltc2992_read_curr(dev, attr, channel, val);
+	case hwmon_power:
+		return ltc2992_read_power(dev, attr, channel, val);
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+static int ltc2992_write_curr(struct device *dev, u32 attr, int channel, long val)
+{
+	struct ltc2992_state *st = dev_get_drvdata(dev);
+	u32 reg;
+
+	switch (attr) {
+	case hwmon_curr_min:
+		reg = LTC2992_DSENSE_MIN_THRESH(channel);
+		break;
+	case hwmon_curr_max:
+		reg = LTC2992_DSENSE_MAX_THRESH(channel);
+		break;
+	default:
+		return -EOPNOTSUPP;
+	}
+
+	return ltc2992_set_current(st, reg, channel, val);
+}
+
+static int ltc2992_write_gpios_in(struct device *dev, u32 attr, int nr_gpio, long val)
+{
+	struct ltc2992_state *st = dev_get_drvdata(dev);
+	u32 reg;
+
+	switch (attr) {
+	case hwmon_in_min:
+		reg = ltc2992_gpio_addr_map[nr_gpio].min_thresh;
+		break;
+	case hwmon_in_max:
+		reg = ltc2992_gpio_addr_map[nr_gpio].max_thresh;
+		break;
+	default:
+		return -EOPNOTSUPP;
+	}
+
+	return ltc2992_set_voltage(st, reg, LTC2992_VADC_GPIO_UV_LSB, val);
+}
+
+static int ltc2992_write_in(struct device *dev, u32 attr, int channel, long val)
+{
+	struct ltc2992_state *st = dev_get_drvdata(dev);
+	u32 reg;
+
+	if (channel > 1)
+		return ltc2992_write_gpios_in(dev, attr, channel - 2, val);
+
+	switch (attr) {
+	case hwmon_in_min:
+		reg = LTC2992_SENSE_MIN_THRESH(channel);
+		break;
+	case hwmon_in_max:
+		reg = LTC2992_SENSE_MAX_THRESH(channel);
+		break;
+	default:
+		return -EOPNOTSUPP;
+	}
+
+	return ltc2992_set_voltage(st, reg, LTC2992_VADC_UV_LSB, val);
+}
+
+static int ltc2992_write_power(struct device *dev, u32 attr, int channel, long val)
+{
+	struct ltc2992_state *st = dev_get_drvdata(dev);
+	u32 reg;
+
+	switch (attr) {
+	case hwmon_power_min:
+		reg = LTC2992_POWER_MIN_THRESH(channel);
+		break;
+	case hwmon_power_max:
+		reg = LTC2992_POWER_MAX_THRESH(channel);
+		break;
+	default:
+		return -EOPNOTSUPP;
+	}
+
+	return ltc2992_set_power(st, reg, channel, val);
+}
+
+static int ltc2992_write_chip(struct device *dev, u32 attr, int channel, long val)
+{
+	struct ltc2992_state *st = dev_get_drvdata(dev);
+
+	switch (attr) {
+	case hwmon_chip_in_reset_history:
+		return regmap_update_bits(st->regmap, LTC2992_CTRLB, LTC2992_RESET_HISTORY,
+					  LTC2992_RESET_HISTORY);
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+static int ltc2992_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel,
+			 long val)
+{
+	switch (type) {
+	case hwmon_chip:
+		return ltc2992_write_chip(dev, attr, channel, val);
+	case hwmon_in:
+		return ltc2992_write_in(dev, attr, channel, val);
+	case hwmon_curr:
+		return ltc2992_write_curr(dev, attr, channel, val);
+	case hwmon_power:
+		return ltc2992_write_power(dev, attr, channel, val);
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+static const struct hwmon_ops ltc2992_hwmon_ops = {
+	.is_visible = ltc2992_is_visible,
+	.read = ltc2992_read,
+	.write = ltc2992_write,
+};
+
+static const u32 ltc2992_chip_config[] = {
+	HWMON_C_IN_RESET_HISTORY,
+	0
+};
+
+static const struct hwmon_channel_info ltc2992_chip = {
+	.type = hwmon_chip,
+	.config = ltc2992_chip_config,
+};
+
+static const u32 ltc2992_in_config[] = {
+	HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST | HWMON_I_MIN | HWMON_I_MAX |
+	HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM,
+	HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST | HWMON_I_MIN | HWMON_I_MAX |
+	HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM,
+	HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST | HWMON_I_MIN | HWMON_I_MAX |
+	HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM,
+	HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST | HWMON_I_MIN | HWMON_I_MAX |
+	HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM,
+	HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST | HWMON_I_MIN | HWMON_I_MAX |
+	HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM,
+	HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST | HWMON_I_MIN | HWMON_I_MAX |
+	HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM,
+	0
+};
+
+static const struct hwmon_channel_info ltc2992_in = {
+	.type = hwmon_in,
+	.config = ltc2992_in_config,
+};
+
+static const u32 ltc2992_curr_config[] = {
+	HWMON_C_INPUT | HWMON_C_LOWEST | HWMON_C_HIGHEST | HWMON_C_MIN | HWMON_C_MAX |
+	HWMON_C_MIN_ALARM | HWMON_C_MAX_ALARM,
+	HWMON_C_INPUT | HWMON_C_LOWEST | HWMON_C_HIGHEST | HWMON_C_MIN | HWMON_C_MAX |
+	HWMON_C_MIN_ALARM | HWMON_C_MAX_ALARM,
+	0
+};
+
+static const struct hwmon_channel_info ltc2992_curr = {
+	.type = hwmon_curr,
+	.config = ltc2992_curr_config,
+};
+
+static const u32 ltc2992_power_config[] = {
+	HWMON_P_INPUT | HWMON_P_INPUT_LOWEST | HWMON_P_INPUT_HIGHEST | HWMON_P_MIN | HWMON_P_MAX |
+	HWMON_P_MIN_ALARM | HWMON_P_MAX_ALARM,
+	HWMON_P_INPUT | HWMON_P_INPUT_LOWEST | HWMON_P_INPUT_HIGHEST | HWMON_P_MIN | HWMON_P_MAX |
+	HWMON_P_MIN_ALARM | HWMON_P_MAX_ALARM,
+	0
+};
+
+static const struct hwmon_channel_info ltc2992_power = {
+	.type = hwmon_power,
+	.config = ltc2992_power_config,
+};
+
+static const struct hwmon_channel_info *ltc2992_info[] = {
+	&ltc2992_chip,
+	&ltc2992_in,
+	&ltc2992_curr,
+	&ltc2992_power,
+	NULL
+};
+
+static const struct hwmon_chip_info ltc2992_chip_info = {
+	.ops = &ltc2992_hwmon_ops,
+	.info = ltc2992_info,
+};
+
+static const struct regmap_config ltc2992_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.max_register = 0xE8,
+};
+
+static int ltc2992_parse_dt(struct ltc2992_state *st)
+{
+	struct fwnode_handle *fwnode;
+	struct fwnode_handle *child;
+	u32 addr;
+	u32 val;
+	int ret;
+
+	fwnode = dev_fwnode(&st->client->dev);
+
+	fwnode_for_each_available_child_node(fwnode, child) {
+		ret = fwnode_property_read_u32(child, "reg", &addr);
+		if (ret < 0)
+			return ret;
+
+		if (addr > 1)
+			return -EINVAL;
+
+		ret = fwnode_property_read_u32(child, "shunt-resistor-micro-ohms", &val);
+		if (!ret)
+			st->r_sense_uohm[addr] = val;
+	}
+
+	return 0;
+}
+
+static int ltc2992_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id)
+{
+	struct device *hwmon_dev;
+	struct ltc2992_state *st;
+	int ret;
+
+	st = devm_kzalloc(&client->dev, sizeof(*st), GFP_KERNEL);
+	if (!st)
+		return -ENOMEM;
+
+	st->client = client;
+	st->regmap = devm_regmap_init_i2c(client, &ltc2992_regmap_config);
+	if (IS_ERR(st->regmap))
+		return PTR_ERR(st->regmap);
+
+	ret = ltc2992_parse_dt(st);
+	if (ret < 0)
+		return ret;
+
+	hwmon_dev = devm_hwmon_device_register_with_info(&client->dev, client->name, st,
+							 &ltc2992_chip_info, NULL);
+
+	return PTR_ERR_OR_ZERO(hwmon_dev);
+}
+
+static const struct of_device_id ltc2992_of_match[] = {
+	{ .compatible = "adi,ltc2992" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, ltc2992_of_match);
+
+static const struct i2c_device_id ltc2992_i2c_id[] = {
+	{"ltc2992", 0},
+	{}
+};
+MODULE_DEVICE_TABLE(i2c, ltc2992_i2c_id);
+
+static struct i2c_driver ltc2992_i2c_driver = {
+	.driver = {
+		.name = "ltc2992",
+		.of_match_table = ltc2992_of_match,
+	},
+	.probe    = ltc2992_i2c_probe,
+	.id_table = ltc2992_i2c_id,
+};
+
+module_i2c_driver(ltc2992_i2c_driver);
+
+MODULE_AUTHOR("Alexandru Tachici <alexandru.tachici@analog.com>");
+MODULE_DESCRIPTION("Hwmon driver for Linear Technology 2992");
+MODULE_LICENSE("Dual BSD/GPL");
-- 
2.20.1


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

* [PATCH v3 2/3] hwmon: ltc2992: Add support for GPIOs.
  2020-12-03  7:11 [PATCH v3 0/3] hwmon: ltc2992: Add support alexandru.tachici
  2020-12-03  7:11 ` [PATCH v3 1/3] " alexandru.tachici
@ 2020-12-03  7:11 ` alexandru.tachici
  2020-12-04 15:21   ` Guenter Roeck
  2020-12-03  7:11 ` [PATCH v3 3/3] dt-binding: hwmon: Add documentation for ltc2992 alexandru.tachici
  2 siblings, 1 reply; 7+ messages in thread
From: alexandru.tachici @ 2020-12-03  7:11 UTC (permalink / raw)
  To: linux-hwmon, linux-kernel, devicetree
  Cc: Weston.Sapia, Brad.Lovell, Sal.Afzal, robh+dt, linux, Alexandru Tachici

From: Alexandru Tachici <alexandru.tachici@analog.com>

LTC2992 has 4 open-drain GPIOS. This patch exports to user
space the 4 GPIOs using the GPIO driver Linux API.

Signed-off-by: Alexandru Tachici <alexandru.tachici@analog.com>
---
 drivers/hwmon/Kconfig   |   1 +
 drivers/hwmon/ltc2992.c | 160 +++++++++++++++++++++++++++++++++++++++-
 2 files changed, 160 insertions(+), 1 deletion(-)

diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index bf9e387270d6..8a8eb42fb1ec 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -861,6 +861,7 @@ config SENSORS_LTC2990
 config SENSORS_LTC2992
 	tristate "Linear Technology LTC2992"
 	depends on I2C
+	depends on GPIOLIB
 	help
 	  If you say yes here you get support for Linear Technology LTC2992
 	  I2C System Monitor. The LTC2992 measures current, voltage, and
diff --git a/drivers/hwmon/ltc2992.c b/drivers/hwmon/ltc2992.c
index c11d585a9600..69dbb5aa5dc2 100644
--- a/drivers/hwmon/ltc2992.c
+++ b/drivers/hwmon/ltc2992.c
@@ -8,6 +8,7 @@
 #include <linux/bitfield.h>
 #include <linux/bitops.h>
 #include <linux/err.h>
+#include <linux/gpio/driver.h>
 #include <linux/hwmon.h>
 #include <linux/i2c.h>
 #include <linux/kernel.h>
@@ -54,6 +55,9 @@
 #define LTC2992_G4_MAX_THRESH		0x74
 #define LTC2992_G4_MIN_THRESH		0x76
 #define LTC2992_FAULT3			0x92
+#define LTC2992_GPIO_STATUS		0x95
+#define LTC2992_GPIO_IO_CTRL		0x96
+#define LTC2992_GPIO_CTRL		0x97
 
 #define LTC2992_POWER(x)		(LTC2992_POWER1 + ((x) * 0x32))
 #define LTC2992_POWER_MAX(x)		(LTC2992_POWER1_MAX + ((x) * 0x32))
@@ -96,8 +100,18 @@
 #define LTC2992_VADC_UV_LSB		25000
 #define LTC2992_VADC_GPIO_UV_LSB	500
 
+#define LTC2992_GPIO_NR		4
+#define LTC2992_GPIO1_BIT	7
+#define LTC2992_GPIO2_BIT	6
+#define LTC2992_GPIO3_BIT	0
+#define LTC2992_GPIO4_BIT	6
+#define LTC2992_GPIO_BIT(x)	(LTC2992_GPIO_NR - (x) - 1)
+
 struct ltc2992_state {
 	struct i2c_client		*client;
+	struct gpio_chip		gc;
+	struct mutex			gpio_mutex; /* lock for gpio access */
+	const char			*gpio_names[LTC2992_GPIO_NR];
 	struct regmap			*regmap;
 	u32				r_sense_uohm[2];
 };
@@ -111,6 +125,8 @@ struct ltc2992_gpio_regs {
 	u8	alarm;
 	u8	min_alarm_msk;
 	u8	max_alarm_msk;
+	u8	ctrl;
+	u8	ctrl_bit;
 };
 
 static const struct ltc2992_gpio_regs ltc2992_gpio_addr_map[] = {
@@ -123,6 +139,8 @@ static const struct ltc2992_gpio_regs ltc2992_gpio_addr_map[] = {
 		.alarm = LTC2992_FAULT1,
 		.min_alarm_msk = LTC2992_GPIO1_FAULT_MSK(0),
 		.max_alarm_msk = LTC2992_GPIO1_FAULT_MSK(1),
+		.ctrl = LTC2992_GPIO_IO_CTRL,
+		.ctrl_bit = LTC2992_GPIO1_BIT,
 	},
 	{
 		.data = LTC2992_G2,
@@ -133,6 +151,8 @@ static const struct ltc2992_gpio_regs ltc2992_gpio_addr_map[] = {
 		.alarm = LTC2992_FAULT2,
 		.min_alarm_msk = LTC2992_GPIO2_FAULT_MSK(0),
 		.max_alarm_msk = LTC2992_GPIO2_FAULT_MSK(1),
+		.ctrl = LTC2992_GPIO_IO_CTRL,
+		.ctrl_bit = LTC2992_GPIO2_BIT,
 	},
 	{
 		.data = LTC2992_G3,
@@ -143,6 +163,8 @@ static const struct ltc2992_gpio_regs ltc2992_gpio_addr_map[] = {
 		.alarm = LTC2992_FAULT3,
 		.min_alarm_msk = LTC2992_GPIO3_FAULT_MSK(0),
 		.max_alarm_msk = LTC2992_GPIO3_FAULT_MSK(1),
+		.ctrl = LTC2992_GPIO_IO_CTRL,
+		.ctrl_bit = LTC2992_GPIO3_BIT,
 	},
 	{
 		.data = LTC2992_G4,
@@ -153,14 +175,20 @@ static const struct ltc2992_gpio_regs ltc2992_gpio_addr_map[] = {
 		.alarm = LTC2992_FAULT3,
 		.min_alarm_msk = LTC2992_GPIO4_FAULT_MSK(0),
 		.max_alarm_msk = LTC2992_GPIO4_FAULT_MSK(1),
+		.ctrl = LTC2992_GPIO_CTRL,
+		.ctrl_bit = LTC2992_GPIO4_BIT,
 	},
 };
 
+static const char *ltc2992_gpio_names[LTC2992_GPIO_NR] = {
+	"GPIO1", "GPIO2", "GPIO3", "GPIO4",
+};
+
 static int ltc2992_read_reg(struct ltc2992_state *st, u8 addr, const u8 reg_len)
 {
 	u8 regvals[4];
-	int ret;
 	int val;
+	int ret;
 	int i;
 
 	ret = regmap_bulk_read(st->regmap, addr, regvals, reg_len);
@@ -185,6 +213,132 @@ static int ltc2992_write_reg(struct ltc2992_state *st, u8 addr, const u8 reg_len
 	return regmap_bulk_write(st->regmap, addr, regvals, reg_len);
 }
 
+static int ltc2992_gpio_get(struct gpio_chip *chip, unsigned int offset)
+{
+	struct ltc2992_state *st = gpiochip_get_data(chip);
+	unsigned long gpio_status;
+	int reg;
+
+	mutex_lock(&st->gpio_mutex);
+	reg = ltc2992_read_reg(st, LTC2992_GPIO_STATUS, 1);
+	mutex_unlock(&st->gpio_mutex);
+
+	if (reg < 0)
+		return reg;
+
+	gpio_status = reg;
+
+	return !test_bit(LTC2992_GPIO_BIT(offset), &gpio_status);
+}
+
+static int ltc2992_gpio_get_multiple(struct gpio_chip *chip, unsigned long *mask,
+				     unsigned long *bits)
+{
+	struct ltc2992_state *st = gpiochip_get_data(chip);
+	unsigned long gpio_status;
+	unsigned int gpio_nr;
+	int reg;
+
+	mutex_lock(&st->gpio_mutex);
+	reg = ltc2992_read_reg(st, LTC2992_GPIO_STATUS, 1);
+	mutex_unlock(&st->gpio_mutex);
+
+	if (reg < 0)
+		return reg;
+
+	gpio_status = reg;
+
+	gpio_nr = 0;
+	for_each_set_bit_from(gpio_nr, mask, LTC2992_GPIO_NR) {
+		if (test_bit(LTC2992_GPIO_BIT(gpio_nr), &gpio_status))
+			set_bit(gpio_nr, bits);
+	}
+
+	return 0;
+}
+
+static void ltc2992_gpio_set(struct gpio_chip *chip, unsigned int offset, int value)
+{
+	struct ltc2992_state *st = gpiochip_get_data(chip);
+	unsigned long gpio_ctrl;
+	int reg;
+
+	mutex_lock(&st->gpio_mutex);
+	reg = ltc2992_read_reg(st, ltc2992_gpio_addr_map[offset].ctrl, 1);
+	if (reg < 0) {
+		mutex_unlock(&st->gpio_mutex);
+		return;
+	}
+
+	gpio_ctrl = reg;
+	assign_bit(ltc2992_gpio_addr_map[offset].ctrl_bit, &gpio_ctrl, value);
+
+	ltc2992_write_reg(st, ltc2992_gpio_addr_map[offset].ctrl, 1, gpio_ctrl);
+	mutex_unlock(&st->gpio_mutex);
+}
+
+static void ltc2992_gpio_set_multiple(struct gpio_chip *chip, unsigned long *mask,
+				      unsigned long *bits)
+{
+	struct ltc2992_state *st = gpiochip_get_data(chip);
+	unsigned long gpio_ctrl_io = 0;
+	unsigned long gpio_ctrl = 0;
+	unsigned int gpio_nr;
+
+	for_each_set_bit(gpio_nr, mask, LTC2992_GPIO_NR) {
+		if (gpio_nr < 3)
+			assign_bit(ltc2992_gpio_addr_map[gpio_nr].ctrl_bit, &gpio_ctrl_io, true);
+
+		if (gpio_nr == 3)
+			assign_bit(ltc2992_gpio_addr_map[gpio_nr].ctrl_bit, &gpio_ctrl, true);
+	}
+
+	mutex_lock(&st->gpio_mutex);
+	ltc2992_write_reg(st, LTC2992_GPIO_IO_CTRL, 1, gpio_ctrl_io);
+	ltc2992_write_reg(st, LTC2992_GPIO_CTRL, 1, gpio_ctrl);
+	mutex_unlock(&st->gpio_mutex);
+}
+
+static int ltc2992_config_gpio(struct ltc2992_state *st)
+{
+	const char *name = dev_name(&st->client->dev);
+	char *gpio_name;
+	int ret;
+	int i;
+
+	ret = ltc2992_write_reg(st, LTC2992_GPIO_IO_CTRL, 1, 0);
+	if (ret < 0)
+		return ret;
+
+	mutex_init(&st->gpio_mutex);
+
+	for (i = 0; i < ARRAY_SIZE(st->gpio_names); i++) {
+		gpio_name = devm_kasprintf(&st->client->dev, GFP_KERNEL, "ltc2992-%x-%s",
+					   st->client->addr, ltc2992_gpio_names[i]);
+		if (!gpio_name)
+			return -ENOMEM;
+
+		st->gpio_names[i] = gpio_name;
+	}
+
+	st->gc.label = name;
+	st->gc.parent = &st->client->dev;
+	st->gc.owner = THIS_MODULE;
+	st->gc.base = -1;
+	st->gc.names = st->gpio_names;
+	st->gc.ngpio = ARRAY_SIZE(st->gpio_names);
+	st->gc.get = ltc2992_gpio_get;
+	st->gc.get_multiple = ltc2992_gpio_get_multiple;
+	st->gc.set = ltc2992_gpio_set;
+	st->gc.set_multiple = ltc2992_gpio_set_multiple;
+
+	ret = devm_gpiochip_add_data(&st->client->dev, &st->gc, st);
+	if (ret)
+		dev_err(&st->client->dev, "GPIO registering failed (%d)\n", ret);
+
+	return ret;
+}
+
 static umode_t ltc2992_is_visible(const void *data, enum hwmon_sensor_types type, u32 attr,
 				  int channel)
 {
@@ -779,6 +933,10 @@ static int ltc2992_i2c_probe(struct i2c_client *client, const struct i2c_device_
 	if (ret < 0)
 		return ret;
 
+	ret = ltc2992_config_gpio(st);
+	if (ret < 0)
+		return ret;
+
 	hwmon_dev = devm_hwmon_device_register_with_info(&client->dev, client->name, st,
 							 &ltc2992_chip_info, NULL);
 
-- 
2.20.1


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

* [PATCH v3 3/3] dt-binding: hwmon: Add documentation for ltc2992
  2020-12-03  7:11 [PATCH v3 0/3] hwmon: ltc2992: Add support alexandru.tachici
  2020-12-03  7:11 ` [PATCH v3 1/3] " alexandru.tachici
  2020-12-03  7:11 ` [PATCH v3 2/3] hwmon: ltc2992: Add support for GPIOs alexandru.tachici
@ 2020-12-03  7:11 ` alexandru.tachici
  2020-12-04 15:22   ` Guenter Roeck
  2 siblings, 1 reply; 7+ messages in thread
From: alexandru.tachici @ 2020-12-03  7:11 UTC (permalink / raw)
  To: linux-hwmon, linux-kernel, devicetree
  Cc: Weston.Sapia, Brad.Lovell, Sal.Afzal, robh+dt, linux, Alexandru Tachici

From: Alexandru Tachici <alexandru.tachici@analog.com>

Add documentation for ltc2992.

Signed-off-by: Alexandru Tachici <alexandru.tachici@analog.com>
---
 .../bindings/hwmon/adi,ltc2992.yaml           | 80 +++++++++++++++++++
 1 file changed, 80 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/hwmon/adi,ltc2992.yaml

diff --git a/Documentation/devicetree/bindings/hwmon/adi,ltc2992.yaml b/Documentation/devicetree/bindings/hwmon/adi,ltc2992.yaml
new file mode 100644
index 000000000000..64a8fcb7bc46
--- /dev/null
+++ b/Documentation/devicetree/bindings/hwmon/adi,ltc2992.yaml
@@ -0,0 +1,80 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/hwmon/adi,ltc2992.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Linear Technology 2992 Power Monitor
+
+maintainers:
+  - Alexandru Tachici <alexandru.tachici@analog.com>
+
+description: |
+  Linear Technology 2992 Dual Wide Range Power Monitor
+  https://www.analog.com/media/en/technical-documentation/data-sheets/ltc2992.pdf
+
+properties:
+  compatible:
+    enum:
+      - adi,ltc2992
+
+  reg:
+    maxItems: 1
+
+  '#address-cells':
+    const: 1
+
+  '#size-cells':
+    const: 0
+
+  avcc-supply: true
+
+patternProperties:
+  "^channel@([0-1])$":
+    type: object
+    description: |
+      Represents the two supplies to be monitored.
+
+    properties:
+      reg:
+        description: |
+          The channel number. LTC2992 can monitor two supplies.
+        items:
+          minimum: 0
+          maximum: 1
+
+      shunt-resistor-micro-ohms:
+        description:
+          The value of curent sense resistor in microohms.
+
+required:
+  - compatible
+  - reg
+
+additionalProperties: false
+
+examples:
+  - |
+    i2c1 {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        ltc2992@6F {
+                #address-cells = <1>;
+                #size-cells = <0>;
+
+                compatible = "adi,ltc2992";
+                reg = <0x6F>;
+
+                channel@0 {
+                        reg = <0x0>;
+                        shunt-resistor-micro-ohms = <10000>;
+                };
+
+                channel@1 {
+                        reg = <0x1>;
+                        shunt-resistor-micro-ohms = <10000>;
+                };
+        };
+    };
+...
-- 
2.20.1


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

* Re: [PATCH v3 1/3] hwmon: ltc2992: Add support
  2020-12-03  7:11 ` [PATCH v3 1/3] " alexandru.tachici
@ 2020-12-04 15:21   ` Guenter Roeck
  0 siblings, 0 replies; 7+ messages in thread
From: Guenter Roeck @ 2020-12-04 15:21 UTC (permalink / raw)
  To: alexandru.tachici
  Cc: linux-hwmon, linux-kernel, devicetree, Weston.Sapia, Brad.Lovell,
	Sal.Afzal, robh+dt

On Thu, Dec 03, 2020 at 09:11:53AM +0200, alexandru.tachici@analog.com wrote:
> From: Alexandru Tachici <alexandru.tachici@analog.com>
> 
> LTC2992 is a rail-to-rail system monitor that
> measures current, voltage, and power of two supplies.
> 
> Two ADCs simultaneously measure each supply’s current.
> A third ADC monitors the input voltages and four
> auxiliary external voltages.
> 
> Signed-off-by: Alexandru Tachici <alexandru.tachici@analog.com>

Applied.

Thanks,
Guenter

> ---
>  Documentation/hwmon/index.rst   |   1 +
>  Documentation/hwmon/ltc2992.rst |  56 +++
>  drivers/hwmon/Kconfig           |  11 +
>  drivers/hwmon/Makefile          |   1 +
>  drivers/hwmon/ltc2992.c         | 813 ++++++++++++++++++++++++++++++++
>  5 files changed, 882 insertions(+)
>  create mode 100644 Documentation/hwmon/ltc2992.rst
>  create mode 100644 drivers/hwmon/ltc2992.c
> 
> diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst
> index e6b91ab12978..98575a8b1918 100644
> --- a/Documentation/hwmon/index.rst
> +++ b/Documentation/hwmon/index.rst
> @@ -100,6 +100,7 @@ Hardware Monitoring Kernel Drivers
>     lm95234
>     lm95245
>     lochnagar
> +   ltc2992
>     ltc2945
>     ltc2947
>     ltc2978
> diff --git a/Documentation/hwmon/ltc2992.rst b/Documentation/hwmon/ltc2992.rst
> new file mode 100644
> index 000000000000..46aa1aa84a1a
> --- /dev/null
> +++ b/Documentation/hwmon/ltc2992.rst
> @@ -0,0 +1,56 @@
> +.. SPDX-License-Identifier: GPL-2.0
> +
> +Kernel driver ltc2992
> +=====================
> +
> +Supported chips:
> +  * Linear Technology LTC2992
> +    Prefix: 'ltc2992'
> +    Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/ltc2992.pdf
> +
> +Author: Alexandru Tachici <alexandru.tachici@analog.com>
> +
> +
> +Description
> +-----------
> +
> +This driver supports hardware monitoring for Linear Technology LTC2992 power monitor.
> +
> +LTC2992 is a rail-to-rail system monitor that measures current,
> +voltage, and power of two supplies.
> +
> +Two ADCs simultaneously measure each supply’s current. A third ADC monitors
> +the input voltages and four auxiliary external voltages.
> +
> +
> +Sysfs entries
> +-------------
> +
> +The following attributes are supported. Limits are read-write,
> +all other attributes are read-only.
> +
> +in_reset_history	Reset all highest/lowest values.
> +
> +inX_input		Measured voltage.
> +inX_lowest		Minimum measured voltage.
> +inX_highest		Maximum measured voltage.
> +inX_min			Minimum voltage allowed.
> +inX_max			Maximum voltage allowed.
> +inX_min_alarm		An undervoltage occurred. Cleared on read.
> +inX_max_alarm		An overvoltage occurred. Cleared on read.
> +
> +currX_input		Measured current.
> +currX_lowest		Minimum measured current.
> +currX_highest		Maximum measured current.
> +currX_min		Minimum current allowed.
> +currX_max		Maximum current allowed.
> +currX_min_alarm		An undercurrent occurred. Cleared on read.
> +currX_max_alarm		An overcurrent occurred. Cleared on read.
> +
> +powerX_input		Measured power.
> +powerX_input_lowest	Minimum measured voltage.
> +powerX_input_highest	Maximum measured voltage.
> +powerX_min		Minimum power.
> +powerX_max		Maximum power.
> +powerX_min_alarm	An underpower occurred. Cleared on read.
> +powerX_max_alarm	An overpower occurred. Cleared on read.
> diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
> index a850e4f0e0bd..bf9e387270d6 100644
> --- a/drivers/hwmon/Kconfig
> +++ b/drivers/hwmon/Kconfig
> @@ -858,6 +858,17 @@ config SENSORS_LTC2990
>  	  This driver can also be built as a module. If so, the module will
>  	  be called ltc2990.
>  
> +config SENSORS_LTC2992
> +	tristate "Linear Technology LTC2992"
> +	depends on I2C
> +	help
> +	  If you say yes here you get support for Linear Technology LTC2992
> +	  I2C System Monitor. The LTC2992 measures current, voltage, and
> +	  power of two supplies.
> +
> +	  This driver can also be built as a module. If so, the module will
> +	  be called ltc2992.
> +
>  config SENSORS_LTC4151
>  	tristate "Linear Technology LTC4151"
>  	depends on I2C
> diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
> index 9db2903b61e5..d6172c4807c4 100644
> --- a/drivers/hwmon/Makefile
> +++ b/drivers/hwmon/Makefile
> @@ -118,6 +118,7 @@ obj-$(CONFIG_SENSORS_LTC2947)	+= ltc2947-core.o
>  obj-$(CONFIG_SENSORS_LTC2947_I2C) += ltc2947-i2c.o
>  obj-$(CONFIG_SENSORS_LTC2947_SPI) += ltc2947-spi.o
>  obj-$(CONFIG_SENSORS_LTC2990)	+= ltc2990.o
> +obj-$(CONFIG_SENSORS_LTC2992)	+= ltc2992.o
>  obj-$(CONFIG_SENSORS_LTC4151)	+= ltc4151.o
>  obj-$(CONFIG_SENSORS_LTC4215)	+= ltc4215.o
>  obj-$(CONFIG_SENSORS_LTC4222)	+= ltc4222.o
> diff --git a/drivers/hwmon/ltc2992.c b/drivers/hwmon/ltc2992.c
> new file mode 100644
> index 000000000000..c11d585a9600
> --- /dev/null
> +++ b/drivers/hwmon/ltc2992.c
> @@ -0,0 +1,813 @@
> +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +/*
> + * LTC2992 - Dual Wide Range Power Monitor
> + *
> + * Copyright 2020 Analog Devices Inc.
> + */
> +
> +#include <linux/bitfield.h>
> +#include <linux/bitops.h>
> +#include <linux/err.h>
> +#include <linux/hwmon.h>
> +#include <linux/i2c.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/property.h>
> +#include <linux/regmap.h>
> +
> +#define LTC2992_CTRLB			0x01
> +#define LTC2992_FAULT1			0x03
> +#define LTC2992_POWER1			0x05
> +#define LTC2992_POWER1_MAX		0x08
> +#define LTC2992_POWER1_MIN		0x0B
> +#define LTC2992_POWER1_MAX_THRESH	0x0E
> +#define LTC2992_POWER1_MIN_THRESH	0x11
> +#define LTC2992_DSENSE1			0x14
> +#define LTC2992_DSENSE1_MAX		0x16
> +#define LTC2992_DSENSE1_MIN		0x18
> +#define LTC2992_DSENSE1_MAX_THRESH	0x1A
> +#define LTC2992_DSENSE1_MIN_THRESH	0x1C
> +#define LTC2992_SENSE1			0x1E
> +#define LTC2992_SENSE1_MAX		0x20
> +#define LTC2992_SENSE1_MIN		0x22
> +#define LTC2992_SENSE1_MAX_THRESH	0x24
> +#define LTC2992_SENSE1_MIN_THRESH	0x26
> +#define LTC2992_G1			0x28
> +#define LTC2992_G1_MAX			0x2A
> +#define LTC2992_G1_MIN			0x2C
> +#define LTC2992_G1_MAX_THRESH		0x2E
> +#define LTC2992_G1_MIN_THRESH		0x30
> +#define LTC2992_FAULT2			0x35
> +#define LTC2992_G2			0x5A
> +#define LTC2992_G2_MAX			0x5C
> +#define LTC2992_G2_MIN			0x5E
> +#define LTC2992_G2_MAX_THRESH		0x60
> +#define LTC2992_G2_MIN_THRESH		0x62
> +#define LTC2992_G3			0x64
> +#define LTC2992_G3_MAX			0x66
> +#define LTC2992_G3_MIN			0x68
> +#define LTC2992_G3_MAX_THRESH		0x6A
> +#define LTC2992_G3_MIN_THRESH		0x6C
> +#define LTC2992_G4			0x6E
> +#define LTC2992_G4_MAX			0x70
> +#define LTC2992_G4_MIN			0x72
> +#define LTC2992_G4_MAX_THRESH		0x74
> +#define LTC2992_G4_MIN_THRESH		0x76
> +#define LTC2992_FAULT3			0x92
> +
> +#define LTC2992_POWER(x)		(LTC2992_POWER1 + ((x) * 0x32))
> +#define LTC2992_POWER_MAX(x)		(LTC2992_POWER1_MAX + ((x) * 0x32))
> +#define LTC2992_POWER_MIN(x)		(LTC2992_POWER1_MIN + ((x) * 0x32))
> +#define LTC2992_POWER_MAX_THRESH(x)	(LTC2992_POWER1_MAX_THRESH + ((x) * 0x32))
> +#define LTC2992_POWER_MIN_THRESH(x)	(LTC2992_POWER1_MIN_THRESH + ((x) * 0x32))
> +#define LTC2992_DSENSE(x)		(LTC2992_DSENSE1 + ((x) * 0x32))
> +#define LTC2992_DSENSE_MAX(x)		(LTC2992_DSENSE1_MAX + ((x) * 0x32))
> +#define LTC2992_DSENSE_MIN(x)		(LTC2992_DSENSE1_MIN + ((x) * 0x32))
> +#define LTC2992_DSENSE_MAX_THRESH(x)	(LTC2992_DSENSE1_MAX_THRESH + ((x) * 0x32))
> +#define LTC2992_DSENSE_MIN_THRESH(x)	(LTC2992_DSENSE1_MIN_THRESH + ((x) * 0x32))
> +#define LTC2992_SENSE(x)		(LTC2992_SENSE1 + ((x) * 0x32))
> +#define LTC2992_SENSE_MAX(x)		(LTC2992_SENSE1_MAX + ((x) * 0x32))
> +#define LTC2992_SENSE_MIN(x)		(LTC2992_SENSE1_MIN + ((x) * 0x32))
> +#define LTC2992_SENSE_MAX_THRESH(x)	(LTC2992_SENSE1_MAX_THRESH + ((x) * 0x32))
> +#define LTC2992_SENSE_MIN_THRESH(x)	(LTC2992_SENSE1_MIN_THRESH + ((x) * 0x32))
> +#define LTC2992_POWER_FAULT(x)		(LTC2992_FAULT1 + ((x) * 0x32))
> +#define LTC2992_SENSE_FAULT(x)		(LTC2992_FAULT1 + ((x) * 0x32))
> +#define LTC2992_DSENSE_FAULT(x)		(LTC2992_FAULT1 + ((x) * 0x32))
> +
> +/* CTRLB register bitfields */
> +#define LTC2992_RESET_HISTORY		BIT(3)
> +
> +/* FAULT1 FAULT2 registers common bitfields */
> +#define LTC2992_POWER_FAULT_MSK(x)	(BIT(6) << (x))
> +#define LTC2992_DSENSE_FAULT_MSK(x)	(BIT(4) << (x))
> +#define LTC2992_SENSE_FAULT_MSK(x)	(BIT(2) << (x))
> +
> +/* FAULT1 bitfields */
> +#define LTC2992_GPIO1_FAULT_MSK(x)	(BIT(0) << (x))
> +
> +/* FAULT2 bitfields */
> +#define LTC2992_GPIO2_FAULT_MSK(x)	(BIT(0) << (x))
> +
> +/* FAULT3 bitfields */
> +#define LTC2992_GPIO3_FAULT_MSK(x)	(BIT(6) << (x))
> +#define LTC2992_GPIO4_FAULT_MSK(x)	(BIT(4) << (x))
> +
> +#define LTC2992_IADC_NANOV_LSB		12500
> +#define LTC2992_VADC_UV_LSB		25000
> +#define LTC2992_VADC_GPIO_UV_LSB	500
> +
> +struct ltc2992_state {
> +	struct i2c_client		*client;
> +	struct regmap			*regmap;
> +	u32				r_sense_uohm[2];
> +};
> +
> +struct ltc2992_gpio_regs {
> +	u8	data;
> +	u8	max;
> +	u8	min;
> +	u8	max_thresh;
> +	u8	min_thresh;
> +	u8	alarm;
> +	u8	min_alarm_msk;
> +	u8	max_alarm_msk;
> +};
> +
> +static const struct ltc2992_gpio_regs ltc2992_gpio_addr_map[] = {
> +	{
> +		.data = LTC2992_G1,
> +		.max = LTC2992_G1_MAX,
> +		.min = LTC2992_G1_MIN,
> +		.max_thresh = LTC2992_G1_MAX_THRESH,
> +		.min_thresh = LTC2992_G1_MIN_THRESH,
> +		.alarm = LTC2992_FAULT1,
> +		.min_alarm_msk = LTC2992_GPIO1_FAULT_MSK(0),
> +		.max_alarm_msk = LTC2992_GPIO1_FAULT_MSK(1),
> +	},
> +	{
> +		.data = LTC2992_G2,
> +		.max = LTC2992_G2_MAX,
> +		.min = LTC2992_G2_MIN,
> +		.max_thresh = LTC2992_G2_MAX_THRESH,
> +		.min_thresh = LTC2992_G2_MIN_THRESH,
> +		.alarm = LTC2992_FAULT2,
> +		.min_alarm_msk = LTC2992_GPIO2_FAULT_MSK(0),
> +		.max_alarm_msk = LTC2992_GPIO2_FAULT_MSK(1),
> +	},
> +	{
> +		.data = LTC2992_G3,
> +		.max = LTC2992_G3_MAX,
> +		.min = LTC2992_G3_MIN,
> +		.max_thresh = LTC2992_G3_MAX_THRESH,
> +		.min_thresh = LTC2992_G3_MIN_THRESH,
> +		.alarm = LTC2992_FAULT3,
> +		.min_alarm_msk = LTC2992_GPIO3_FAULT_MSK(0),
> +		.max_alarm_msk = LTC2992_GPIO3_FAULT_MSK(1),
> +	},
> +	{
> +		.data = LTC2992_G4,
> +		.max = LTC2992_G4_MAX,
> +		.min = LTC2992_G4_MIN,
> +		.max_thresh = LTC2992_G4_MAX_THRESH,
> +		.min_thresh = LTC2992_G4_MIN_THRESH,
> +		.alarm = LTC2992_FAULT3,
> +		.min_alarm_msk = LTC2992_GPIO4_FAULT_MSK(0),
> +		.max_alarm_msk = LTC2992_GPIO4_FAULT_MSK(1),
> +	},
> +};
> +
> +static int ltc2992_read_reg(struct ltc2992_state *st, u8 addr, const u8 reg_len)
> +{
> +	u8 regvals[4];
> +	int ret;
> +	int val;
> +	int i;
> +
> +	ret = regmap_bulk_read(st->regmap, addr, regvals, reg_len);
> +	if (ret < 0)
> +		return ret;
> +
> +	val = 0;
> +	for (i = 0; i < reg_len; i++)
> +		val |= regvals[reg_len - i - 1] << (i * 8);
> +
> +	return val;
> +}
> +
> +static int ltc2992_write_reg(struct ltc2992_state *st, u8 addr, const u8 reg_len, u32 val)
> +{
> +	u8 regvals[4];
> +	int i;
> +
> +	for (i = 0; i < reg_len; i++)
> +		regvals[reg_len - i - 1] = (val >> (i * 8)) & 0xFF;
> +
> +	return regmap_bulk_write(st->regmap, addr, regvals, reg_len);
> +}
> +
> +static umode_t ltc2992_is_visible(const void *data, enum hwmon_sensor_types type, u32 attr,
> +				  int channel)
> +{
> +	const struct ltc2992_state *st = data;
> +
> +	switch (type) {
> +	case hwmon_chip:
> +		switch (attr) {
> +		case hwmon_chip_in_reset_history:
> +			return 0200;
> +		}
> +		break;
> +	case hwmon_in:
> +		switch (attr) {
> +		case hwmon_in_input:
> +		case hwmon_in_lowest:
> +		case hwmon_in_highest:
> +		case hwmon_in_min_alarm:
> +		case hwmon_in_max_alarm:
> +			return 0444;
> +		case hwmon_in_min:
> +		case hwmon_in_max:
> +			return 0644;
> +		}
> +		break;
> +	case hwmon_curr:
> +		switch (attr) {
> +		case hwmon_curr_input:
> +		case hwmon_curr_lowest:
> +		case hwmon_curr_highest:
> +		case hwmon_curr_min_alarm:
> +		case hwmon_curr_max_alarm:
> +			if (st->r_sense_uohm[channel])
> +				return 0444;
> +			break;
> +		case hwmon_curr_min:
> +		case hwmon_curr_max:
> +			if (st->r_sense_uohm[channel])
> +				return 0644;
> +			break;
> +		}
> +		break;
> +	case hwmon_power:
> +		switch (attr) {
> +		case hwmon_power_input:
> +		case hwmon_power_input_lowest:
> +		case hwmon_power_input_highest:
> +		case hwmon_power_min_alarm:
> +		case hwmon_power_max_alarm:
> +			if (st->r_sense_uohm[channel])
> +				return 0444;
> +			break;
> +		case hwmon_power_min:
> +		case hwmon_power_max:
> +			if (st->r_sense_uohm[channel])
> +				return 0644;
> +			break;
> +		}
> +		break;
> +	default:
> +		break;
> +	}
> +
> +	return 0;
> +}
> +
> +static int ltc2992_get_voltage(struct ltc2992_state *st, u32 reg, u32 scale, long *val)
> +{
> +	int reg_val;
> +
> +	reg_val = ltc2992_read_reg(st, reg, 2);
> +	if (reg_val < 0)
> +		return reg_val;
> +
> +	reg_val = reg_val >> 4;
> +	*val = DIV_ROUND_CLOSEST(reg_val * scale, 1000);
> +
> +	return 0;
> +}
> +
> +static int ltc2992_set_voltage(struct ltc2992_state *st, u32 reg, u32 scale, long val)
> +{
> +	val = DIV_ROUND_CLOSEST(val * 1000, scale);
> +	val = val << 4;
> +
> +	return ltc2992_write_reg(st, reg, 2, val);
> +}
> +
> +static int ltc2992_read_gpio_alarm(struct ltc2992_state *st, int nr_gpio, u32 attr, long *val)
> +{
> +	int reg_val;
> +	u32 mask;
> +
> +	if (attr == hwmon_in_max_alarm)
> +		mask = ltc2992_gpio_addr_map[nr_gpio].max_alarm_msk;
> +	else
> +		mask = ltc2992_gpio_addr_map[nr_gpio].min_alarm_msk;
> +
> +	reg_val = ltc2992_read_reg(st, ltc2992_gpio_addr_map[nr_gpio].alarm, 1);
> +	if (reg_val < 0)
> +		return reg_val;
> +
> +	*val = !!(reg_val & mask);
> +	reg_val &= ~mask;
> +
> +	return ltc2992_write_reg(st, ltc2992_gpio_addr_map[nr_gpio].alarm, 1, reg_val);
> +}
> +
> +static int ltc2992_read_gpios_in(struct device *dev, u32 attr, int nr_gpio, long *val)
> +{
> +	struct ltc2992_state *st = dev_get_drvdata(dev);
> +	u32 reg;
> +
> +	switch (attr) {
> +	case hwmon_in_input:
> +		reg = ltc2992_gpio_addr_map[nr_gpio].data;
> +		break;
> +	case hwmon_in_lowest:
> +		reg = ltc2992_gpio_addr_map[nr_gpio].min;
> +		break;
> +	case hwmon_in_highest:
> +		reg = ltc2992_gpio_addr_map[nr_gpio].max;
> +		break;
> +	case hwmon_in_min:
> +		reg = ltc2992_gpio_addr_map[nr_gpio].min_thresh;
> +		break;
> +	case hwmon_in_max:
> +		reg = ltc2992_gpio_addr_map[nr_gpio].max_thresh;
> +		break;
> +	case hwmon_in_min_alarm:
> +	case hwmon_in_max_alarm:
> +		return ltc2992_read_gpio_alarm(st, nr_gpio, attr, val);
> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +
> +	return ltc2992_get_voltage(st, reg, LTC2992_VADC_GPIO_UV_LSB, val);
> +}
> +
> +static int ltc2992_read_in_alarm(struct ltc2992_state *st, int channel, long *val, u32 attr)
> +{
> +	u32 reg_val;
> +	u32 mask;
> +
> +	if (attr == hwmon_in_max_alarm)
> +		mask = LTC2992_SENSE_FAULT_MSK(1);
> +	else
> +		mask = LTC2992_SENSE_FAULT_MSK(0);
> +
> +	reg_val = ltc2992_read_reg(st, LTC2992_SENSE_FAULT(channel), 1);
> +	if (reg_val < 0)
> +		return reg_val;
> +
> +	*val = !!(reg_val & mask);
> +	reg_val &= ~mask;
> +
> +	return ltc2992_write_reg(st, LTC2992_SENSE_FAULT(channel), 1, reg_val);
> +}
> +
> +static int ltc2992_read_in(struct device *dev, u32 attr, int channel, long *val)
> +{
> +	struct ltc2992_state *st = dev_get_drvdata(dev);
> +	u32 reg;
> +
> +	if (channel > 1)
> +		return ltc2992_read_gpios_in(dev, attr, channel - 2, val);
> +
> +	switch (attr) {
> +	case hwmon_in_input:
> +		reg = LTC2992_SENSE(channel);
> +		break;
> +	case hwmon_in_lowest:
> +		reg = LTC2992_SENSE_MIN(channel);
> +		break;
> +	case hwmon_in_highest:
> +		reg = LTC2992_SENSE_MAX(channel);
> +		break;
> +	case hwmon_in_min:
> +		reg = LTC2992_SENSE_MIN_THRESH(channel);
> +		break;
> +	case hwmon_in_max:
> +		reg = LTC2992_SENSE_MAX_THRESH(channel);
> +		break;
> +	case hwmon_in_min_alarm:
> +	case hwmon_in_max_alarm:
> +		return ltc2992_read_in_alarm(st, channel, val, attr);
> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +
> +	return ltc2992_get_voltage(st, reg, LTC2992_VADC_UV_LSB, val);
> +}
> +
> +static int ltc2992_get_current(struct ltc2992_state *st, u32 reg, u32 channel, long *val)
> +{
> +	u32 reg_val;
> +
> +	reg_val = ltc2992_read_reg(st, reg, 2);
> +	if (reg_val < 0)
> +		return reg_val;
> +
> +	reg_val = reg_val >> 4;
> +	*val = DIV_ROUND_CLOSEST(reg_val * LTC2992_IADC_NANOV_LSB, st->r_sense_uohm[channel]);
> +
> +	return 0;
> +}
> +
> +static int ltc2992_set_current(struct ltc2992_state *st, u32 reg, u32 channel, long val)
> +{
> +	u32 reg_val;
> +
> +	reg_val = DIV_ROUND_CLOSEST(val * st->r_sense_uohm[channel], LTC2992_IADC_NANOV_LSB);
> +	reg_val = reg_val << 4;
> +
> +	return ltc2992_write_reg(st, reg, 2, reg_val);
> +}
> +
> +static int ltc2992_read_curr_alarm(struct ltc2992_state *st, int channel, long *val, u32 attr)
> +{
> +	u32 reg_val;
> +	u32 mask;
> +
> +	if (attr == hwmon_curr_max_alarm)
> +		mask = LTC2992_DSENSE_FAULT_MSK(1);
> +	else
> +		mask = LTC2992_DSENSE_FAULT_MSK(0);
> +
> +	reg_val = ltc2992_read_reg(st, LTC2992_DSENSE_FAULT(channel), 1);
> +	if (reg_val < 0)
> +		return reg_val;
> +
> +	*val = !!(reg_val & mask);
> +
> +	reg_val &= ~mask;
> +	return ltc2992_write_reg(st, LTC2992_DSENSE_FAULT(channel), 1, reg_val);
> +}
> +
> +static int ltc2992_read_curr(struct device *dev, u32 attr, int channel, long *val)
> +{
> +	struct ltc2992_state *st = dev_get_drvdata(dev);
> +	u32 reg;
> +
> +	switch (attr) {
> +	case hwmon_curr_input:
> +		reg = LTC2992_DSENSE(channel);
> +		break;
> +	case hwmon_curr_lowest:
> +		reg = LTC2992_DSENSE_MIN(channel);
> +		break;
> +	case hwmon_curr_highest:
> +		reg = LTC2992_DSENSE_MAX(channel);
> +		break;
> +	case hwmon_curr_min:
> +		reg = LTC2992_DSENSE_MIN_THRESH(channel);
> +		break;
> +	case hwmon_curr_max:
> +		reg = LTC2992_DSENSE_MAX_THRESH(channel);
> +		break;
> +	case hwmon_curr_min_alarm:
> +	case hwmon_curr_max_alarm:
> +		return ltc2992_read_curr_alarm(st, channel, val, attr);
> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +
> +	return ltc2992_get_current(st, reg, channel, val);
> +}
> +
> +static int ltc2992_get_power(struct ltc2992_state *st, u32 reg, u32 channel, long *val)
> +{
> +	u32 reg_val;
> +
> +	reg_val = ltc2992_read_reg(st, reg, 3);
> +	if (reg_val < 0)
> +		return reg_val;
> +
> +	*val = mul_u64_u32_div(reg_val, LTC2992_VADC_UV_LSB * LTC2992_IADC_NANOV_LSB,
> +			       st->r_sense_uohm[channel] * 1000);
> +
> +	return 0;
> +}
> +
> +static int ltc2992_set_power(struct ltc2992_state *st, u32 reg, u32 channel, long val)
> +{
> +	u32 reg_val;
> +
> +	reg_val = mul_u64_u32_div(val, st->r_sense_uohm[channel] * 1000,
> +				  LTC2992_VADC_UV_LSB * LTC2992_IADC_NANOV_LSB);
> +
> +	return ltc2992_write_reg(st, reg, 3, reg_val);
> +}
> +
> +static int ltc2992_read_power_alarm(struct ltc2992_state *st, int channel, long *val, u32 attr)
> +{
> +	u32 reg_val;
> +	u32 mask;
> +
> +	if (attr == hwmon_power_max_alarm)
> +		mask = LTC2992_POWER_FAULT_MSK(1);
> +	else
> +		mask = LTC2992_POWER_FAULT_MSK(0);
> +
> +	reg_val = ltc2992_read_reg(st, LTC2992_POWER_FAULT(channel), 1);
> +	if (reg_val < 0)
> +		return reg_val;
> +
> +	*val = !!(reg_val & mask);
> +	reg_val &= ~mask;
> +
> +	return ltc2992_write_reg(st, LTC2992_POWER_FAULT(channel), 1, reg_val);
> +}
> +
> +static int ltc2992_read_power(struct device *dev, u32 attr, int channel, long *val)
> +{
> +	struct ltc2992_state *st = dev_get_drvdata(dev);
> +	u32 reg;
> +
> +	switch (attr) {
> +	case hwmon_power_input:
> +		reg = LTC2992_POWER(channel);
> +		break;
> +	case hwmon_power_input_lowest:
> +		reg = LTC2992_POWER_MIN(channel);
> +		break;
> +	case hwmon_power_input_highest:
> +		reg = LTC2992_POWER_MAX(channel);
> +		break;
> +	case hwmon_power_min:
> +		reg = LTC2992_POWER_MIN_THRESH(channel);
> +		break;
> +	case hwmon_power_max:
> +		reg = LTC2992_POWER_MAX_THRESH(channel);
> +		break;
> +	case hwmon_power_min_alarm:
> +	case hwmon_power_max_alarm:
> +		return ltc2992_read_power_alarm(st, channel, val, attr);
> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +
> +	return ltc2992_get_power(st, reg, channel, val);
> +}
> +
> +static int ltc2992_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel,
> +			long *val)
> +{
> +	switch (type) {
> +	case hwmon_in:
> +		return ltc2992_read_in(dev, attr, channel, val);
> +	case hwmon_curr:
> +		return ltc2992_read_curr(dev, attr, channel, val);
> +	case hwmon_power:
> +		return ltc2992_read_power(dev, attr, channel, val);
> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +}
> +
> +static int ltc2992_write_curr(struct device *dev, u32 attr, int channel, long val)
> +{
> +	struct ltc2992_state *st = dev_get_drvdata(dev);
> +	u32 reg;
> +
> +	switch (attr) {
> +	case hwmon_curr_min:
> +		reg = LTC2992_DSENSE_MIN_THRESH(channel);
> +		break;
> +	case hwmon_curr_max:
> +		reg = LTC2992_DSENSE_MAX_THRESH(channel);
> +		break;
> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +
> +	return ltc2992_set_current(st, reg, channel, val);
> +}
> +
> +static int ltc2992_write_gpios_in(struct device *dev, u32 attr, int nr_gpio, long val)
> +{
> +	struct ltc2992_state *st = dev_get_drvdata(dev);
> +	u32 reg;
> +
> +	switch (attr) {
> +	case hwmon_in_min:
> +		reg = ltc2992_gpio_addr_map[nr_gpio].min_thresh;
> +		break;
> +	case hwmon_in_max:
> +		reg = ltc2992_gpio_addr_map[nr_gpio].max_thresh;
> +		break;
> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +
> +	return ltc2992_set_voltage(st, reg, LTC2992_VADC_GPIO_UV_LSB, val);
> +}
> +
> +static int ltc2992_write_in(struct device *dev, u32 attr, int channel, long val)
> +{
> +	struct ltc2992_state *st = dev_get_drvdata(dev);
> +	u32 reg;
> +
> +	if (channel > 1)
> +		return ltc2992_write_gpios_in(dev, attr, channel - 2, val);
> +
> +	switch (attr) {
> +	case hwmon_in_min:
> +		reg = LTC2992_SENSE_MIN_THRESH(channel);
> +		break;
> +	case hwmon_in_max:
> +		reg = LTC2992_SENSE_MAX_THRESH(channel);
> +		break;
> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +
> +	return ltc2992_set_voltage(st, reg, LTC2992_VADC_UV_LSB, val);
> +}
> +
> +static int ltc2992_write_power(struct device *dev, u32 attr, int channel, long val)
> +{
> +	struct ltc2992_state *st = dev_get_drvdata(dev);
> +	u32 reg;
> +
> +	switch (attr) {
> +	case hwmon_power_min:
> +		reg = LTC2992_POWER_MIN_THRESH(channel);
> +		break;
> +	case hwmon_power_max:
> +		reg = LTC2992_POWER_MAX_THRESH(channel);
> +		break;
> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +
> +	return ltc2992_set_power(st, reg, channel, val);
> +}
> +
> +static int ltc2992_write_chip(struct device *dev, u32 attr, int channel, long val)
> +{
> +	struct ltc2992_state *st = dev_get_drvdata(dev);
> +
> +	switch (attr) {
> +	case hwmon_chip_in_reset_history:
> +		return regmap_update_bits(st->regmap, LTC2992_CTRLB, LTC2992_RESET_HISTORY,
> +					  LTC2992_RESET_HISTORY);
> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +}
> +
> +static int ltc2992_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel,
> +			 long val)
> +{
> +	switch (type) {
> +	case hwmon_chip:
> +		return ltc2992_write_chip(dev, attr, channel, val);
> +	case hwmon_in:
> +		return ltc2992_write_in(dev, attr, channel, val);
> +	case hwmon_curr:
> +		return ltc2992_write_curr(dev, attr, channel, val);
> +	case hwmon_power:
> +		return ltc2992_write_power(dev, attr, channel, val);
> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +}
> +
> +static const struct hwmon_ops ltc2992_hwmon_ops = {
> +	.is_visible = ltc2992_is_visible,
> +	.read = ltc2992_read,
> +	.write = ltc2992_write,
> +};
> +
> +static const u32 ltc2992_chip_config[] = {
> +	HWMON_C_IN_RESET_HISTORY,
> +	0
> +};
> +
> +static const struct hwmon_channel_info ltc2992_chip = {
> +	.type = hwmon_chip,
> +	.config = ltc2992_chip_config,
> +};
> +
> +static const u32 ltc2992_in_config[] = {
> +	HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST | HWMON_I_MIN | HWMON_I_MAX |
> +	HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM,
> +	HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST | HWMON_I_MIN | HWMON_I_MAX |
> +	HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM,
> +	HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST | HWMON_I_MIN | HWMON_I_MAX |
> +	HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM,
> +	HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST | HWMON_I_MIN | HWMON_I_MAX |
> +	HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM,
> +	HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST | HWMON_I_MIN | HWMON_I_MAX |
> +	HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM,
> +	HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST | HWMON_I_MIN | HWMON_I_MAX |
> +	HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM,
> +	0
> +};
> +
> +static const struct hwmon_channel_info ltc2992_in = {
> +	.type = hwmon_in,
> +	.config = ltc2992_in_config,
> +};
> +
> +static const u32 ltc2992_curr_config[] = {
> +	HWMON_C_INPUT | HWMON_C_LOWEST | HWMON_C_HIGHEST | HWMON_C_MIN | HWMON_C_MAX |
> +	HWMON_C_MIN_ALARM | HWMON_C_MAX_ALARM,
> +	HWMON_C_INPUT | HWMON_C_LOWEST | HWMON_C_HIGHEST | HWMON_C_MIN | HWMON_C_MAX |
> +	HWMON_C_MIN_ALARM | HWMON_C_MAX_ALARM,
> +	0
> +};
> +
> +static const struct hwmon_channel_info ltc2992_curr = {
> +	.type = hwmon_curr,
> +	.config = ltc2992_curr_config,
> +};
> +
> +static const u32 ltc2992_power_config[] = {
> +	HWMON_P_INPUT | HWMON_P_INPUT_LOWEST | HWMON_P_INPUT_HIGHEST | HWMON_P_MIN | HWMON_P_MAX |
> +	HWMON_P_MIN_ALARM | HWMON_P_MAX_ALARM,
> +	HWMON_P_INPUT | HWMON_P_INPUT_LOWEST | HWMON_P_INPUT_HIGHEST | HWMON_P_MIN | HWMON_P_MAX |
> +	HWMON_P_MIN_ALARM | HWMON_P_MAX_ALARM,
> +	0
> +};
> +
> +static const struct hwmon_channel_info ltc2992_power = {
> +	.type = hwmon_power,
> +	.config = ltc2992_power_config,
> +};
> +
> +static const struct hwmon_channel_info *ltc2992_info[] = {
> +	&ltc2992_chip,
> +	&ltc2992_in,
> +	&ltc2992_curr,
> +	&ltc2992_power,
> +	NULL
> +};
> +
> +static const struct hwmon_chip_info ltc2992_chip_info = {
> +	.ops = &ltc2992_hwmon_ops,
> +	.info = ltc2992_info,
> +};
> +
> +static const struct regmap_config ltc2992_regmap_config = {
> +	.reg_bits = 8,
> +	.val_bits = 8,
> +	.max_register = 0xE8,
> +};
> +
> +static int ltc2992_parse_dt(struct ltc2992_state *st)
> +{
> +	struct fwnode_handle *fwnode;
> +	struct fwnode_handle *child;
> +	u32 addr;
> +	u32 val;
> +	int ret;
> +
> +	fwnode = dev_fwnode(&st->client->dev);
> +
> +	fwnode_for_each_available_child_node(fwnode, child) {
> +		ret = fwnode_property_read_u32(child, "reg", &addr);
> +		if (ret < 0)
> +			return ret;
> +
> +		if (addr > 1)
> +			return -EINVAL;
> +
> +		ret = fwnode_property_read_u32(child, "shunt-resistor-micro-ohms", &val);
> +		if (!ret)
> +			st->r_sense_uohm[addr] = val;
> +	}
> +
> +	return 0;
> +}
> +
> +static int ltc2992_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id)
> +{
> +	struct device *hwmon_dev;
> +	struct ltc2992_state *st;
> +	int ret;
> +
> +	st = devm_kzalloc(&client->dev, sizeof(*st), GFP_KERNEL);
> +	if (!st)
> +		return -ENOMEM;
> +
> +	st->client = client;
> +	st->regmap = devm_regmap_init_i2c(client, &ltc2992_regmap_config);
> +	if (IS_ERR(st->regmap))
> +		return PTR_ERR(st->regmap);
> +
> +	ret = ltc2992_parse_dt(st);
> +	if (ret < 0)
> +		return ret;
> +
> +	hwmon_dev = devm_hwmon_device_register_with_info(&client->dev, client->name, st,
> +							 &ltc2992_chip_info, NULL);
> +
> +	return PTR_ERR_OR_ZERO(hwmon_dev);
> +}
> +
> +static const struct of_device_id ltc2992_of_match[] = {
> +	{ .compatible = "adi,ltc2992" },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(of, ltc2992_of_match);
> +
> +static const struct i2c_device_id ltc2992_i2c_id[] = {
> +	{"ltc2992", 0},
> +	{}
> +};
> +MODULE_DEVICE_TABLE(i2c, ltc2992_i2c_id);
> +
> +static struct i2c_driver ltc2992_i2c_driver = {
> +	.driver = {
> +		.name = "ltc2992",
> +		.of_match_table = ltc2992_of_match,
> +	},
> +	.probe    = ltc2992_i2c_probe,
> +	.id_table = ltc2992_i2c_id,
> +};
> +
> +module_i2c_driver(ltc2992_i2c_driver);
> +
> +MODULE_AUTHOR("Alexandru Tachici <alexandru.tachici@analog.com>");
> +MODULE_DESCRIPTION("Hwmon driver for Linear Technology 2992");
> +MODULE_LICENSE("Dual BSD/GPL");
> -- 
> 2.20.1
> 

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

* Re: [PATCH v3 2/3] hwmon: ltc2992: Add support for GPIOs.
  2020-12-03  7:11 ` [PATCH v3 2/3] hwmon: ltc2992: Add support for GPIOs alexandru.tachici
@ 2020-12-04 15:21   ` Guenter Roeck
  0 siblings, 0 replies; 7+ messages in thread
From: Guenter Roeck @ 2020-12-04 15:21 UTC (permalink / raw)
  To: alexandru.tachici
  Cc: linux-hwmon, linux-kernel, devicetree, Weston.Sapia, Brad.Lovell,
	Sal.Afzal, robh+dt

On Thu, Dec 03, 2020 at 09:11:54AM +0200, alexandru.tachici@analog.com wrote:
> From: Alexandru Tachici <alexandru.tachici@analog.com>
> 
> LTC2992 has 4 open-drain GPIOS. This patch exports to user
> space the 4 GPIOs using the GPIO driver Linux API.
> 
> Signed-off-by: Alexandru Tachici <alexandru.tachici@analog.com>

Applied.

Thanks,
Guenter

> ---
>  drivers/hwmon/Kconfig   |   1 +
>  drivers/hwmon/ltc2992.c | 160 +++++++++++++++++++++++++++++++++++++++-
>  2 files changed, 160 insertions(+), 1 deletion(-)
> 
> diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
> index bf9e387270d6..8a8eb42fb1ec 100644
> --- a/drivers/hwmon/Kconfig
> +++ b/drivers/hwmon/Kconfig
> @@ -861,6 +861,7 @@ config SENSORS_LTC2990
>  config SENSORS_LTC2992
>  	tristate "Linear Technology LTC2992"
>  	depends on I2C
> +	depends on GPIOLIB
>  	help
>  	  If you say yes here you get support for Linear Technology LTC2992
>  	  I2C System Monitor. The LTC2992 measures current, voltage, and
> diff --git a/drivers/hwmon/ltc2992.c b/drivers/hwmon/ltc2992.c
> index c11d585a9600..69dbb5aa5dc2 100644
> --- a/drivers/hwmon/ltc2992.c
> +++ b/drivers/hwmon/ltc2992.c
> @@ -8,6 +8,7 @@
>  #include <linux/bitfield.h>
>  #include <linux/bitops.h>
>  #include <linux/err.h>
> +#include <linux/gpio/driver.h>
>  #include <linux/hwmon.h>
>  #include <linux/i2c.h>
>  #include <linux/kernel.h>
> @@ -54,6 +55,9 @@
>  #define LTC2992_G4_MAX_THRESH		0x74
>  #define LTC2992_G4_MIN_THRESH		0x76
>  #define LTC2992_FAULT3			0x92
> +#define LTC2992_GPIO_STATUS		0x95
> +#define LTC2992_GPIO_IO_CTRL		0x96
> +#define LTC2992_GPIO_CTRL		0x97
>  
>  #define LTC2992_POWER(x)		(LTC2992_POWER1 + ((x) * 0x32))
>  #define LTC2992_POWER_MAX(x)		(LTC2992_POWER1_MAX + ((x) * 0x32))
> @@ -96,8 +100,18 @@
>  #define LTC2992_VADC_UV_LSB		25000
>  #define LTC2992_VADC_GPIO_UV_LSB	500
>  
> +#define LTC2992_GPIO_NR		4
> +#define LTC2992_GPIO1_BIT	7
> +#define LTC2992_GPIO2_BIT	6
> +#define LTC2992_GPIO3_BIT	0
> +#define LTC2992_GPIO4_BIT	6
> +#define LTC2992_GPIO_BIT(x)	(LTC2992_GPIO_NR - (x) - 1)
> +
>  struct ltc2992_state {
>  	struct i2c_client		*client;
> +	struct gpio_chip		gc;
> +	struct mutex			gpio_mutex; /* lock for gpio access */
> +	const char			*gpio_names[LTC2992_GPIO_NR];
>  	struct regmap			*regmap;
>  	u32				r_sense_uohm[2];
>  };
> @@ -111,6 +125,8 @@ struct ltc2992_gpio_regs {
>  	u8	alarm;
>  	u8	min_alarm_msk;
>  	u8	max_alarm_msk;
> +	u8	ctrl;
> +	u8	ctrl_bit;
>  };
>  
>  static const struct ltc2992_gpio_regs ltc2992_gpio_addr_map[] = {
> @@ -123,6 +139,8 @@ static const struct ltc2992_gpio_regs ltc2992_gpio_addr_map[] = {
>  		.alarm = LTC2992_FAULT1,
>  		.min_alarm_msk = LTC2992_GPIO1_FAULT_MSK(0),
>  		.max_alarm_msk = LTC2992_GPIO1_FAULT_MSK(1),
> +		.ctrl = LTC2992_GPIO_IO_CTRL,
> +		.ctrl_bit = LTC2992_GPIO1_BIT,
>  	},
>  	{
>  		.data = LTC2992_G2,
> @@ -133,6 +151,8 @@ static const struct ltc2992_gpio_regs ltc2992_gpio_addr_map[] = {
>  		.alarm = LTC2992_FAULT2,
>  		.min_alarm_msk = LTC2992_GPIO2_FAULT_MSK(0),
>  		.max_alarm_msk = LTC2992_GPIO2_FAULT_MSK(1),
> +		.ctrl = LTC2992_GPIO_IO_CTRL,
> +		.ctrl_bit = LTC2992_GPIO2_BIT,
>  	},
>  	{
>  		.data = LTC2992_G3,
> @@ -143,6 +163,8 @@ static const struct ltc2992_gpio_regs ltc2992_gpio_addr_map[] = {
>  		.alarm = LTC2992_FAULT3,
>  		.min_alarm_msk = LTC2992_GPIO3_FAULT_MSK(0),
>  		.max_alarm_msk = LTC2992_GPIO3_FAULT_MSK(1),
> +		.ctrl = LTC2992_GPIO_IO_CTRL,
> +		.ctrl_bit = LTC2992_GPIO3_BIT,
>  	},
>  	{
>  		.data = LTC2992_G4,
> @@ -153,14 +175,20 @@ static const struct ltc2992_gpio_regs ltc2992_gpio_addr_map[] = {
>  		.alarm = LTC2992_FAULT3,
>  		.min_alarm_msk = LTC2992_GPIO4_FAULT_MSK(0),
>  		.max_alarm_msk = LTC2992_GPIO4_FAULT_MSK(1),
> +		.ctrl = LTC2992_GPIO_CTRL,
> +		.ctrl_bit = LTC2992_GPIO4_BIT,
>  	},
>  };
>  
> +static const char *ltc2992_gpio_names[LTC2992_GPIO_NR] = {
> +	"GPIO1", "GPIO2", "GPIO3", "GPIO4",
> +};
> +
>  static int ltc2992_read_reg(struct ltc2992_state *st, u8 addr, const u8 reg_len)
>  {
>  	u8 regvals[4];
> -	int ret;
>  	int val;
> +	int ret;
>  	int i;
>  
>  	ret = regmap_bulk_read(st->regmap, addr, regvals, reg_len);
> @@ -185,6 +213,132 @@ static int ltc2992_write_reg(struct ltc2992_state *st, u8 addr, const u8 reg_len
>  	return regmap_bulk_write(st->regmap, addr, regvals, reg_len);
>  }
>  
> +static int ltc2992_gpio_get(struct gpio_chip *chip, unsigned int offset)
> +{
> +	struct ltc2992_state *st = gpiochip_get_data(chip);
> +	unsigned long gpio_status;
> +	int reg;
> +
> +	mutex_lock(&st->gpio_mutex);
> +	reg = ltc2992_read_reg(st, LTC2992_GPIO_STATUS, 1);
> +	mutex_unlock(&st->gpio_mutex);
> +
> +	if (reg < 0)
> +		return reg;
> +
> +	gpio_status = reg;
> +
> +	return !test_bit(LTC2992_GPIO_BIT(offset), &gpio_status);
> +}
> +
> +static int ltc2992_gpio_get_multiple(struct gpio_chip *chip, unsigned long *mask,
> +				     unsigned long *bits)
> +{
> +	struct ltc2992_state *st = gpiochip_get_data(chip);
> +	unsigned long gpio_status;
> +	unsigned int gpio_nr;
> +	int reg;
> +
> +	mutex_lock(&st->gpio_mutex);
> +	reg = ltc2992_read_reg(st, LTC2992_GPIO_STATUS, 1);
> +	mutex_unlock(&st->gpio_mutex);
> +
> +	if (reg < 0)
> +		return reg;
> +
> +	gpio_status = reg;
> +
> +	gpio_nr = 0;
> +	for_each_set_bit_from(gpio_nr, mask, LTC2992_GPIO_NR) {
> +		if (test_bit(LTC2992_GPIO_BIT(gpio_nr), &gpio_status))
> +			set_bit(gpio_nr, bits);
> +	}
> +
> +	return 0;
> +}
> +
> +static void ltc2992_gpio_set(struct gpio_chip *chip, unsigned int offset, int value)
> +{
> +	struct ltc2992_state *st = gpiochip_get_data(chip);
> +	unsigned long gpio_ctrl;
> +	int reg;
> +
> +	mutex_lock(&st->gpio_mutex);
> +	reg = ltc2992_read_reg(st, ltc2992_gpio_addr_map[offset].ctrl, 1);
> +	if (reg < 0) {
> +		mutex_unlock(&st->gpio_mutex);
> +		return;
> +	}
> +
> +	gpio_ctrl = reg;
> +	assign_bit(ltc2992_gpio_addr_map[offset].ctrl_bit, &gpio_ctrl, value);
> +
> +	ltc2992_write_reg(st, ltc2992_gpio_addr_map[offset].ctrl, 1, gpio_ctrl);
> +	mutex_unlock(&st->gpio_mutex);
> +}
> +
> +static void ltc2992_gpio_set_multiple(struct gpio_chip *chip, unsigned long *mask,
> +				      unsigned long *bits)
> +{
> +	struct ltc2992_state *st = gpiochip_get_data(chip);
> +	unsigned long gpio_ctrl_io = 0;
> +	unsigned long gpio_ctrl = 0;
> +	unsigned int gpio_nr;
> +
> +	for_each_set_bit(gpio_nr, mask, LTC2992_GPIO_NR) {
> +		if (gpio_nr < 3)
> +			assign_bit(ltc2992_gpio_addr_map[gpio_nr].ctrl_bit, &gpio_ctrl_io, true);
> +
> +		if (gpio_nr == 3)
> +			assign_bit(ltc2992_gpio_addr_map[gpio_nr].ctrl_bit, &gpio_ctrl, true);
> +	}
> +
> +	mutex_lock(&st->gpio_mutex);
> +	ltc2992_write_reg(st, LTC2992_GPIO_IO_CTRL, 1, gpio_ctrl_io);
> +	ltc2992_write_reg(st, LTC2992_GPIO_CTRL, 1, gpio_ctrl);
> +	mutex_unlock(&st->gpio_mutex);
> +}
> +
> +static int ltc2992_config_gpio(struct ltc2992_state *st)
> +{
> +	const char *name = dev_name(&st->client->dev);
> +	char *gpio_name;
> +	int ret;
> +	int i;
> +
> +	ret = ltc2992_write_reg(st, LTC2992_GPIO_IO_CTRL, 1, 0);
> +	if (ret < 0)
> +		return ret;
> +
> +	mutex_init(&st->gpio_mutex);
> +
> +	for (i = 0; i < ARRAY_SIZE(st->gpio_names); i++) {
> +		gpio_name = devm_kasprintf(&st->client->dev, GFP_KERNEL, "ltc2992-%x-%s",
> +					   st->client->addr, ltc2992_gpio_names[i]);
> +		if (!gpio_name)
> +			return -ENOMEM;
> +
> +		st->gpio_names[i] = gpio_name;
> +	}
> +
> +	st->gc.label = name;
> +	st->gc.parent = &st->client->dev;
> +	st->gc.owner = THIS_MODULE;
> +	st->gc.base = -1;
> +	st->gc.names = st->gpio_names;
> +	st->gc.ngpio = ARRAY_SIZE(st->gpio_names);
> +	st->gc.get = ltc2992_gpio_get;
> +	st->gc.get_multiple = ltc2992_gpio_get_multiple;
> +	st->gc.set = ltc2992_gpio_set;
> +	st->gc.set_multiple = ltc2992_gpio_set_multiple;
> +
> +	ret = devm_gpiochip_add_data(&st->client->dev, &st->gc, st);
> +	if (ret)
> +		dev_err(&st->client->dev, "GPIO registering failed (%d)\n", ret);
> +
> +	return ret;
> +}
> +
>  static umode_t ltc2992_is_visible(const void *data, enum hwmon_sensor_types type, u32 attr,
>  				  int channel)
>  {
> @@ -779,6 +933,10 @@ static int ltc2992_i2c_probe(struct i2c_client *client, const struct i2c_device_
>  	if (ret < 0)
>  		return ret;
>  
> +	ret = ltc2992_config_gpio(st);
> +	if (ret < 0)
> +		return ret;
> +
>  	hwmon_dev = devm_hwmon_device_register_with_info(&client->dev, client->name, st,
>  							 &ltc2992_chip_info, NULL);
>  
> -- 
> 2.20.1
> 

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

* Re: [PATCH v3 3/3] dt-binding: hwmon: Add documentation for ltc2992
  2020-12-03  7:11 ` [PATCH v3 3/3] dt-binding: hwmon: Add documentation for ltc2992 alexandru.tachici
@ 2020-12-04 15:22   ` Guenter Roeck
  0 siblings, 0 replies; 7+ messages in thread
From: Guenter Roeck @ 2020-12-04 15:22 UTC (permalink / raw)
  To: alexandru.tachici
  Cc: linux-hwmon, linux-kernel, devicetree, Weston.Sapia, Brad.Lovell,
	Sal.Afzal, robh+dt

On Thu, Dec 03, 2020 at 09:11:55AM +0200, alexandru.tachici@analog.com wrote:
> From: Alexandru Tachici <alexandru.tachici@analog.com>
> 
> Add documentation for ltc2992.
> 
> Signed-off-by: Alexandru Tachici <alexandru.tachici@analog.com>

Side note: You are supposed to keep previous Reviewed-by: tags unless
there was a change.

Applied, with Rob's Reviewed-by: tag added back.

Guenter

> ---
>  .../bindings/hwmon/adi,ltc2992.yaml           | 80 +++++++++++++++++++
>  1 file changed, 80 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/hwmon/adi,ltc2992.yaml
> 
> diff --git a/Documentation/devicetree/bindings/hwmon/adi,ltc2992.yaml b/Documentation/devicetree/bindings/hwmon/adi,ltc2992.yaml
> new file mode 100644
> index 000000000000..64a8fcb7bc46
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/hwmon/adi,ltc2992.yaml
> @@ -0,0 +1,80 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/hwmon/adi,ltc2992.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Linear Technology 2992 Power Monitor
> +
> +maintainers:
> +  - Alexandru Tachici <alexandru.tachici@analog.com>
> +
> +description: |
> +  Linear Technology 2992 Dual Wide Range Power Monitor
> +  https://www.analog.com/media/en/technical-documentation/data-sheets/ltc2992.pdf
> +
> +properties:
> +  compatible:
> +    enum:
> +      - adi,ltc2992
> +
> +  reg:
> +    maxItems: 1
> +
> +  '#address-cells':
> +    const: 1
> +
> +  '#size-cells':
> +    const: 0
> +
> +  avcc-supply: true
> +
> +patternProperties:
> +  "^channel@([0-1])$":
> +    type: object
> +    description: |
> +      Represents the two supplies to be monitored.
> +
> +    properties:
> +      reg:
> +        description: |
> +          The channel number. LTC2992 can monitor two supplies.
> +        items:
> +          minimum: 0
> +          maximum: 1
> +
> +      shunt-resistor-micro-ohms:
> +        description:
> +          The value of curent sense resistor in microohms.
> +
> +required:
> +  - compatible
> +  - reg
> +
> +additionalProperties: false
> +
> +examples:
> +  - |
> +    i2c1 {
> +        #address-cells = <1>;
> +        #size-cells = <0>;
> +
> +        ltc2992@6F {
> +                #address-cells = <1>;
> +                #size-cells = <0>;
> +
> +                compatible = "adi,ltc2992";
> +                reg = <0x6F>;
> +
> +                channel@0 {
> +                        reg = <0x0>;
> +                        shunt-resistor-micro-ohms = <10000>;
> +                };
> +
> +                channel@1 {
> +                        reg = <0x1>;
> +                        shunt-resistor-micro-ohms = <10000>;
> +                };
> +        };
> +    };
> +...
> -- 
> 2.20.1
> 

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

end of thread, other threads:[~2020-12-04 15:23 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-12-03  7:11 [PATCH v3 0/3] hwmon: ltc2992: Add support alexandru.tachici
2020-12-03  7:11 ` [PATCH v3 1/3] " alexandru.tachici
2020-12-04 15:21   ` Guenter Roeck
2020-12-03  7:11 ` [PATCH v3 2/3] hwmon: ltc2992: Add support for GPIOs alexandru.tachici
2020-12-04 15:21   ` Guenter Roeck
2020-12-03  7:11 ` [PATCH v3 3/3] dt-binding: hwmon: Add documentation for ltc2992 alexandru.tachici
2020-12-04 15:22   ` 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).