* [PATCH v3 1/2] hwmon: Add support for ltc2947
@ 2019-10-21 15:41 Nuno Sá
2019-10-21 15:41 ` [PATCH v3 2/2] dt-bindings: hwmon: Add ltc2947 documentation Nuno Sá
` (2 more replies)
0 siblings, 3 replies; 7+ messages in thread
From: Nuno Sá @ 2019-10-21 15:41 UTC (permalink / raw)
To: linux-hwmon, devicetree, linux-doc
Cc: Rob Herring, Mark Rutland, Guenter Roeck, Jean Delvare, Jonathan Corbet
The ltc2947 is a high precision power and energy monitor with an
internal sense resistor supporting up to +/- 30A. Three internal no
Latency ADCs ensure accurate measurement of voltage and current, while
high-bandwidth analog multiplication of voltage and current provides
accurate power measurement in a wide range of applications. Internal or
external clocking options enable precise charge and energy measurements.
Signed-off-by: Nuno Sá <nuno.sa@analog.com>
---
Changes in v2:
* Add #include <linux/bits.h>;
* Aemove unneeded dev_err() messages;
* Drop reset flag and calls to mutex_* in resume()/suspend() code;
* Drop fault, overflow, energy max/min and energy alarms attributes;
* Use standard attributes for power;
* Remove unused macros;
* Adjust min/max values per datasheet (on clamp_val() calls);
* Set power max/min on setup().
Changes in v3:
* Add Doc file to index.rst;
* Set the Doc file as restructured text file;
* If/else cleanup as else after return is unnecessary.
Documentation/hwmon/index.rst | 1 +
Documentation/hwmon/ltc2947.rst | 100 +++
MAINTAINERS | 10 +
drivers/hwmon/Kconfig | 27 +
drivers/hwmon/Makefile | 3 +
drivers/hwmon/ltc2947-core.c | 1184 +++++++++++++++++++++++++++++++
drivers/hwmon/ltc2947-i2c.c | 49 ++
drivers/hwmon/ltc2947-spi.c | 50 ++
drivers/hwmon/ltc2947.h | 12 +
9 files changed, 1436 insertions(+)
create mode 100644 Documentation/hwmon/ltc2947.rst
create mode 100644 drivers/hwmon/ltc2947-core.c
create mode 100644 drivers/hwmon/ltc2947-i2c.c
create mode 100644 drivers/hwmon/ltc2947-spi.c
create mode 100644 drivers/hwmon/ltc2947.h
diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst
index 230ad59b462b..dad3bf4ebf63 100644
--- a/Documentation/hwmon/index.rst
+++ b/Documentation/hwmon/index.rst
@@ -90,6 +90,7 @@ Hardware Monitoring Kernel Drivers
lm95245
lochnagar
ltc2945
+ ltc2947
ltc2978
ltc2990
ltc3815
diff --git a/Documentation/hwmon/ltc2947.rst b/Documentation/hwmon/ltc2947.rst
new file mode 100644
index 000000000000..419fc84fe934
--- /dev/null
+++ b/Documentation/hwmon/ltc2947.rst
@@ -0,0 +1,100 @@
+Kernel drivers ltc2947-i2c and ltc2947-spi
+==========================================
+
+Supported chips:
+
+ * Analog Devices LTC2947
+
+ Prefix: 'ltc2947'
+
+ Addresses scanned: -
+
+ Datasheet:
+
+ https://www.analog.com/media/en/technical-documentation/data-sheets/LTC2947.pdf
+
+Author: Nuno Sá <nuno.sa@analog.com>
+
+Description
+___________
+
+The LTC2947 is a high precision power and energy monitor that measures current,
+voltage, power, temperature, charge and energy. The device supports both SPI
+and I2C depending on the chip configuration.
+The device also measures accumulated quantities as energy. It has two banks of
+register's to read/set energy related values. These banks can be configured
+independently to have setups like: energy1 accumulates always and enrgy2 only
+accumulates if current is positive (to check battery charging efficiency for
+example). The device also supports a GPIO pin that can be configured as output
+to control a fan as a function of measured temperature. Then, the GPIO becomes
+active as soon as a temperature reading is higher than a defined threshold. The
+temp2 channel is used to control this thresholds and to read the respective
+alarms.
+
+Sysfs entries
+_____________
+
+The following attributes are supported. Limits are read-write, reset_history
+is write-only and all the other attributes are read-only.
+
+======================= ==========================================
+in0_input VP-VM voltage (mV).
+in0_min Undervoltage threshold
+in0_max Overvoltage threshold
+in0_lowest Lowest measured voltage
+in0_highest Highest measured voltage
+in0_reset_history Write 1 to reset in1 history
+in0_min_alarm Undervoltage alarm
+in0_max_alarm Overvoltage alarm
+in0_label Channel label (VP-VM)
+
+in1_input DVCC voltage (mV)
+in1_min Undervoltage threshold
+in1_max Overvoltage threshold
+in1_lowest Lowest measured voltage
+in1_highest Highest measured voltage
+in1_reset_history Write 1 to reset in2 history
+in1_min_alarm Undervoltage alarm
+in1_max_alarm Overvoltage alarm
+in1_label Channel label (DVCC)
+
+curr1_input IP-IM Sense current (mA)
+curr1_min Undercurrent threshold
+curr1_max Overcurrent threshold
+curr1_lowest Lowest measured current
+curr1_highest Highest measured current
+curr1_reset_history Write 1 to reset curr1 history
+curr1_min_alarm Undercurrent alarm
+curr1_max_alarm Overcurrent alarm
+curr1_label Channel label (IP-IM)
+
+power1_input Power (in uW)
+power1_min Low power threshold
+power1_max High power threshold
+power1_input_lowest Historical minimum power use
+power1_input_highest Historical maximum power use
+power1_reset_history Write 1 to reset power1 history
+power1_min_alarm Low power alarm
+power1_max_alarm High power alarm
+power1_label Channel label (Power)
+
+temp1_input Chip Temperature (in milliC)
+temp1_min Low temperature threshold
+temp1_max High temperature threshold
+temp1_input_lowest Historical minimum temperature use
+temp1_input_highest Historical maximum temperature use
+temp1_reset_history Write 1 to reset temp1 history
+temp1_min_alarm Low temperature alarm
+temp1_max_alarm High temperature alarm
+temp1_label Channel label (Ambient)
+
+temp2_min Low temperature threshold for fan control
+temp2_max High temperature threshold for fan control
+temp2_min_alarm Low temperature fan control alarm
+temp2_max_alarm High temperature fan control alarm
+temp2_label Channel label (TEMPFAN)
+
+energy1_input Measured energy over time (in microJoule)
+
+energy2_input Measured energy over time (in microJoule)
+======================= ==========================================
diff --git a/MAINTAINERS b/MAINTAINERS
index a69e6db80c79..318332b6a411 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -9629,6 +9629,16 @@ S: Maintained
F: Documentation/hwmon/ltc4261.rst
F: drivers/hwmon/ltc4261.c
+LTC2947 HARDWARE MONITOR DRIVER
+M: Nuno Sá <nuno.sa@analog.com>
+W: http://ez.analog.com/community/linux-device-drivers
+L: linux-hwmon@vger.kernel.org
+S: Supported
+F: drivers/hwmon/ltc2947-core.c
+F: drivers/hwmon/ltc2947-spi.c
+F: drivers/hwmon/ltc2947-i2c.c
+F: drivers/hwmon/ltc2947.h
+
LTC4306 I2C MULTIPLEXER DRIVER
M: Michael Hennerich <michael.hennerich@analog.com>
W: http://ez.analog.com/community/linux-device-drivers
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 7b6c4025b827..8c102ea2938b 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -726,6 +726,33 @@ config SENSORS_LTC2945
This driver can also be built as a module. If so, the module will
be called ltc2945.
+config SENSORS_LTC2947
+ tristate
+
+config SENSORS_LTC2947_I2C
+ tristate "Analog Devices LTC2947 High Precision Power and Energy Monitor over I2C"
+ depends on I2C
+ select REGMAP_I2C
+ select SENSORS_LTC2947
+ help
+ If you say yes here you get support for Linear Technology LTC2947
+ I2C High Precision Power and Energy Monitor
+
+ This driver can also be built as a module. If so, the module will
+ be called ltc2947-i2c.
+
+config SENSORS_LTC2947_SPI
+ tristate "Analog Devices LTC2947 High Precision Power and Energy Monitor over SPI"
+ depends on SPI_MASTER
+ select REGMAP_SPI
+ select SENSORS_LTC2947
+ help
+ If you say yes here you get support for Linear Technology LTC2947
+ SPI High Precision Power and Energy Monitor
+
+ This driver can also be built as a module. If so, the module will
+ be called ltc2947-spi.
+
config SENSORS_LTC2990
tristate "Linear Technology LTC2990"
depends on I2C
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index 40c036ea45e6..e416cfded0c4 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -106,6 +106,9 @@ obj-$(CONFIG_SENSORS_LM95234) += lm95234.o
obj-$(CONFIG_SENSORS_LM95241) += lm95241.o
obj-$(CONFIG_SENSORS_LM95245) += lm95245.o
obj-$(CONFIG_SENSORS_LTC2945) += ltc2945.o
+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_LTC4151) += ltc4151.o
obj-$(CONFIG_SENSORS_LTC4215) += ltc4215.o
diff --git a/drivers/hwmon/ltc2947-core.c b/drivers/hwmon/ltc2947-core.c
new file mode 100644
index 000000000000..ce11acfbd2a8
--- /dev/null
+++ b/drivers/hwmon/ltc2947-core.c
@@ -0,0 +1,1184 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Analog Devices LTC2947 high precision power and energy monitor
+ *
+ * Copyright 2019 Analog Devices Inc.
+ */
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/regmap.h>
+
+#include "ltc2947.h"
+
+/* register's */
+#define LTC2947_REG_PAGE_CTRL 0xFF
+#define LTC2947_REG_CTRL 0xF0
+#define LTC2947_REG_TBCTL 0xE9
+#define LTC2947_CONT_MODE_MASK BIT(3)
+#define LTC2947_CONT_MODE(x) FIELD_PREP(LTC2947_CONT_MODE_MASK, x)
+#define LTC2947_PRE_MASK GENMASK(2, 0)
+#define LTC2947_PRE(x) FIELD_PREP(LTC2947_PRE_MASK, x)
+#define LTC2947_DIV_MASK GENMASK(7, 3)
+#define LTC2947_DIV(x) FIELD_PREP(LTC2947_DIV_MASK, x)
+#define LTC2947_SHUTDOWN_MASK BIT(0)
+#define LTC2947_REG_ACCUM_POL 0xE1
+#define LTC2947_ACCUM_POL_1_MASK GENMASK(1, 0)
+#define LTC2947_ACCUM_POL_1(x) FIELD_PREP(LTC2947_ACCUM_POL_1_MASK, x)
+#define LTC2947_ACCUM_POL_2_MASK GENMASK(3, 2)
+#define LTC2947_ACCUM_POL_2(x) FIELD_PREP(LTC2947_ACCUM_POL_2_MASK, x)
+#define LTC2947_REG_ACCUM_DEADBAND 0xE4
+#define LTC2947_REG_GPIOSTATCTL 0x67
+#define LTC2947_GPIO_EN_MASK BIT(0)
+#define LTC2947_GPIO_EN(x) FIELD_PREP(LTC2947_GPIO_EN_MASK, x)
+#define LTC2947_GPIO_FAN_EN_MASK BIT(6)
+#define LTC2947_GPIO_FAN_EN(x) FIELD_PREP(LTC2947_GPIO_FAN_EN_MASK, x)
+#define LTC2947_GPIO_FAN_POL_MASK BIT(7)
+#define LTC2947_GPIO_FAN_POL(x) FIELD_PREP(LTC2947_GPIO_FAN_POL_MASK, x)
+#define LTC2947_REG_GPIO_ACCUM 0xE3
+/* 200Khz */
+#define LTC2947_CLK_MIN 200000
+/* 25Mhz */
+#define LTC2947_CLK_MAX 25000000
+#define PAGE0 0
+#define PAGE1 1
+/* Voltage registers */
+#define LTC2947_REG_VOLTAGE 0xA0
+#define LTC2947_REG_VOLTAGE_MAX 0x50
+#define LTC2947_REG_VOLTAGE_MIN 0x52
+#define LTC2947_REG_VOLTAGE_THRE_H 0x90
+#define LTC2947_REG_VOLTAGE_THRE_L 0x92
+#define LTC2947_REG_DVCC 0xA4
+#define LTC2947_REG_DVCC_MAX 0x58
+#define LTC2947_REG_DVCC_MIN 0x5A
+#define LTC2947_REG_DVCC_THRE_H 0x98
+#define LTC2947_REG_DVCC_THRE_L 0x9A
+#define LTC2947_VOLTAGE_GEN_CHAN 0
+#define LTC2947_VOLTAGE_DVCC_CHAN 1
+/* in mV */
+#define VOLTAGE_MAX 15500
+#define VOLTAGE_MIN -300
+#define VDVCC_MAX 15000
+#define VDVCC_MIN 4750
+/* Current registers */
+#define LTC2947_REG_CURRENT 0x90
+#define LTC2947_REG_CURRENT_MAX 0x40
+#define LTC2947_REG_CURRENT_MIN 0x42
+#define LTC2947_REG_CURRENT_THRE_H 0x80
+#define LTC2947_REG_CURRENT_THRE_L 0x82
+/* in mA */
+#define CURRENT_MAX 30000
+#define CURRENT_MIN -30000
+/* Power registers */
+#define LTC2947_REG_POWER 0x93
+#define LTC2947_REG_POWER_MAX 0x44
+#define LTC2947_REG_POWER_MIN 0x46
+#define LTC2947_REG_POWER_THRE_H 0x84
+#define LTC2947_REG_POWER_THRE_L 0x86
+/* in uW */
+#define POWER_MAX 450000000
+#define POWER_MIN -450000000
+/* Temperature registers */
+#define LTC2947_REG_TEMP 0xA2
+#define LTC2947_REG_TEMP_MAX 0x54
+#define LTC2947_REG_TEMP_MIN 0x56
+#define LTC2947_REG_TEMP_THRE_H 0x94
+#define LTC2947_REG_TEMP_THRE_L 0x96
+#define LTC2947_REG_TEMP_FAN_THRE_H 0x9C
+#define LTC2947_REG_TEMP_FAN_THRE_L 0x9E
+#define LTC2947_TEMP_FAN_CHAN 1
+/* in millidegress Celsius */
+#define TEMP_MAX 85000
+#define TEMP_MIN -40000
+/* Energy registers */
+#define LTC2947_REG_ENERGY1 0x06
+#define LTC2947_REG_ENERGY2 0x16
+/* Status/Alarm/Overflow registers */
+#define LTC2947_REG_STATUS 0x80
+#define LTC2947_REG_STATVT 0x81
+#define LTC2947_REG_STATIP 0x82
+#define LTC2947_REG_STATVDVCC 0x87
+
+#define LTC2947_ALERTS_SIZE (LTC2947_REG_STATVDVCC - LTC2947_REG_STATUS)
+#define LTC2947_MAX_VOLTAGE_MASK BIT(0)
+#define LTC2947_MIN_VOLTAGE_MASK BIT(1)
+#define LTC2947_MAX_CURRENT_MASK BIT(0)
+#define LTC2947_MIN_CURRENT_MASK BIT(1)
+#define LTC2947_MAX_POWER_MASK BIT(2)
+#define LTC2947_MIN_POWER_MASK BIT(3)
+#define LTC2947_MAX_TEMP_MASK BIT(2)
+#define LTC2947_MIN_TEMP_MASK BIT(3)
+#define LTC2947_MAX_TEMP_FAN_MASK BIT(4)
+#define LTC2947_MIN_TEMP_FAN_MASK BIT(5)
+
+struct ltc2947_data {
+ struct regmap *map;
+ struct device *dev;
+ /*
+ * The mutex is needed because the device has 2 memory pages. When
+ * reading/writing the correct page needs to be set so that, the
+ * complete sequence select_page->read/write needs to be protected.
+ */
+ struct mutex lock;
+ u32 lsb_energy;
+ bool gpio_out;
+};
+
+static int __ltc2947_val_read16(const struct ltc2947_data *st, const u8 reg,
+ u64 *val)
+{
+ __be16 __val = 0;
+ int ret;
+
+ ret = regmap_bulk_read(st->map, reg, &__val, 2);
+ if (ret)
+ return ret;
+
+ *val = be16_to_cpu(__val);
+
+ return 0;
+}
+
+static int __ltc2947_val_read24(const struct ltc2947_data *st, const u8 reg,
+ u64 *val)
+{
+ __be32 __val = 0;
+ int ret;
+
+ ret = regmap_bulk_read(st->map, reg, &__val, 3);
+ if (ret)
+ return ret;
+
+ *val = be32_to_cpu(__val) >> 8;
+
+ return 0;
+}
+
+static int __ltc2947_val_read64(const struct ltc2947_data *st, const u8 reg,
+ u64 *val)
+{
+ __be64 __val = 0;
+ int ret;
+
+ ret = regmap_bulk_read(st->map, reg, &__val, 6);
+ if (ret)
+ return ret;
+
+ *val = be64_to_cpu(__val) >> 16;
+
+ return 0;
+}
+
+static int ltc2947_val_read(struct ltc2947_data *st, const u8 reg,
+ const u8 page, const size_t size, s64 *val)
+{
+ int ret;
+ u64 __val = 0;
+
+ mutex_lock(&st->lock);
+
+ ret = regmap_write(st->map, LTC2947_REG_PAGE_CTRL, page);
+ if (ret) {
+ mutex_unlock(&st->lock);
+ return ret;
+ }
+
+ dev_dbg(st->dev, "Read val, reg:%02X, p:%d sz:%zu\n", reg, page,
+ size);
+
+ switch (size) {
+ case 2:
+ ret = __ltc2947_val_read16(st, reg, &__val);
+ break;
+ case 3:
+ ret = __ltc2947_val_read24(st, reg, &__val);
+ break;
+ case 6:
+ ret = __ltc2947_val_read64(st, reg, &__val);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ mutex_unlock(&st->lock);
+
+ if (ret)
+ return ret;
+
+ *val = sign_extend64(__val, (8 * size) - 1);
+
+ dev_dbg(st->dev, "Got s:%lld, u:%016llX\n", *val, __val);
+
+ return 0;
+}
+
+static int __ltc2947_val_write64(const struct ltc2947_data *st, const u8 reg,
+ const u64 val)
+{
+ __be64 __val;
+
+ __val = cpu_to_be64(val << 16);
+ return regmap_bulk_write(st->map, reg, &__val, 6);
+}
+
+static int __ltc2947_val_write16(const struct ltc2947_data *st, const u8 reg,
+ const u16 val)
+{
+ __be16 __val;
+
+ __val = cpu_to_be16(val);
+ return regmap_bulk_write(st->map, reg, &__val, 2);
+}
+
+static int ltc2947_val_write(struct ltc2947_data *st, const u8 reg,
+ const u8 page, const size_t size, const u64 val)
+{
+ int ret;
+
+ mutex_lock(&st->lock);
+ /* set device on correct page */
+ ret = regmap_write(st->map, LTC2947_REG_PAGE_CTRL, page);
+ if (ret) {
+ mutex_unlock(&st->lock);
+ return ret;
+ }
+
+ dev_dbg(st->dev, "Write val, r:%02X, p:%d, sz:%zu, val:%016llX\n",
+ reg, page, size, val);
+
+ switch (size) {
+ case 2:
+ ret = __ltc2947_val_write16(st, reg, val);
+ break;
+ case 6:
+ ret = __ltc2947_val_write64(st, reg, val);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ mutex_unlock(&st->lock);
+
+ return ret;
+}
+
+static int ltc2947_reset_history(struct ltc2947_data *st, const u8 reg_h,
+ const u8 reg_l)
+{
+ int ret;
+ /*
+ * let's reset the tracking register's. Tracking register's have all
+ * 2 bytes size
+ */
+ ret = ltc2947_val_write(st, reg_h, PAGE0, 2, 0x8000U);
+ if (ret)
+ return ret;
+
+ return ltc2947_val_write(st, reg_l, PAGE0, 2, 0x7FFFU);
+}
+
+static int ltc2947_alarm_read(struct ltc2947_data *st, const u8 reg,
+ const u32 mask, long *val)
+{
+ u8 offset = reg - LTC2947_REG_STATUS;
+ /* +1 to include status reg */
+ char alarms[LTC2947_ALERTS_SIZE + 1];
+ int ret = 0;
+
+ memset(alarms, 0, sizeof(alarms));
+
+ mutex_lock(&st->lock);
+
+ ret = regmap_write(st->map, LTC2947_REG_PAGE_CTRL, PAGE0);
+ if (ret)
+ goto unlock;
+
+ dev_dbg(st->dev, "Read alarm, reg:%02X, mask:%02X\n", reg, mask);
+ /*
+ * As stated in the datasheet, when Threshold and Overflow registers
+ * are used, the status and all alert registers must be read in one
+ * multi-byte transaction.
+ */
+ ret = regmap_bulk_read(st->map, LTC2947_REG_STATUS, alarms,
+ sizeof(alarms));
+ if (ret)
+ goto unlock;
+
+ /* get the alarm */
+ *val = !!(alarms[offset] & mask);
+unlock:
+ mutex_unlock(&st->lock);
+ return ret;
+}
+
+static ssize_t ltc2947_show_value(struct device *dev,
+ struct device_attribute *da, char *buf)
+{
+ struct ltc2947_data *st = dev_get_drvdata(dev);
+ struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
+ int ret;
+ s64 val = 0;
+
+ switch (attr->index) {
+ case LTC2947_REG_ENERGY1:
+ case LTC2947_REG_ENERGY2:
+ ret = ltc2947_val_read(st, attr->index, PAGE0, 6, &val);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (ret)
+ return ret;
+
+ /* value in microJoule. st->lsb_energy was multiplied by 10E9 */
+ val = div_s64(val * st->lsb_energy, 1000);
+
+ return sprintf(buf, "%lld\n", val);
+}
+
+static int ltc2947_read_temp(struct device *dev, const u32 attr, long *val,
+ const int channel)
+{
+ int ret;
+ struct ltc2947_data *st = dev_get_drvdata(dev);
+ s64 __val = 0;
+
+ if (channel < 0 || channel > LTC2947_TEMP_FAN_CHAN) {
+ dev_err(st->dev, "Invalid chan%d for temperature", channel);
+ return -EINVAL;
+ }
+
+ switch (attr) {
+ case hwmon_temp_input:
+ ret = ltc2947_val_read(st, LTC2947_REG_TEMP, PAGE0, 2, &__val);
+ break;
+ case hwmon_temp_highest:
+ ret = ltc2947_val_read(st, LTC2947_REG_TEMP_MAX, PAGE0, 2,
+ &__val);
+ break;
+ case hwmon_temp_lowest:
+ ret = ltc2947_val_read(st, LTC2947_REG_TEMP_MIN, PAGE0, 2,
+ &__val);
+ break;
+ case hwmon_temp_max_alarm:
+ if (channel == LTC2947_TEMP_FAN_CHAN)
+ return ltc2947_alarm_read(st, LTC2947_REG_STATVT,
+ LTC2947_MAX_TEMP_FAN_MASK,
+ val);
+
+ return ltc2947_alarm_read(st, LTC2947_REG_STATVT,
+ LTC2947_MAX_TEMP_MASK, val);
+ case hwmon_temp_min_alarm:
+ if (channel == LTC2947_TEMP_FAN_CHAN)
+ return ltc2947_alarm_read(st, LTC2947_REG_STATVT,
+ LTC2947_MIN_TEMP_FAN_MASK,
+ val);
+
+ return ltc2947_alarm_read(st, LTC2947_REG_STATVT,
+ LTC2947_MIN_TEMP_MASK, val);
+ case hwmon_temp_max:
+ if (channel == LTC2947_TEMP_FAN_CHAN)
+ ret = ltc2947_val_read(st, LTC2947_REG_TEMP_FAN_THRE_H,
+ PAGE1, 2, &__val);
+ else
+ ret = ltc2947_val_read(st, LTC2947_REG_TEMP_THRE_H,
+ PAGE1, 2, &__val);
+ break;
+ case hwmon_temp_min:
+ if (channel == LTC2947_TEMP_FAN_CHAN)
+ ret = ltc2947_val_read(st, LTC2947_REG_TEMP_FAN_THRE_L,
+ PAGE1, 2, &__val);
+ else
+ ret = ltc2947_val_read(st, LTC2947_REG_TEMP_THRE_L,
+ PAGE1, 2, &__val);
+ break;
+ default:
+ return -ENOTSUPP;
+ }
+
+ if (ret)
+ return ret;
+
+ /* in milidegrees celcius, temp is given by: */
+ *val = (__val * 204) + 550;
+
+ return 0;
+}
+
+static int ltc2947_read_power(struct device *dev, const u32 attr, long *val)
+{
+ struct ltc2947_data *st = dev_get_drvdata(dev);
+ int ret;
+ u32 lsb = 200000; /* in uW */
+ s64 __val = 0;
+
+ switch (attr) {
+ case hwmon_power_input:
+ ret = ltc2947_val_read(st, LTC2947_REG_POWER, PAGE0, 3, &__val);
+ lsb = 50000;
+ break;
+ case hwmon_power_input_highest:
+ ret = ltc2947_val_read(st, LTC2947_REG_POWER_MAX, PAGE0, 2,
+ &__val);
+ break;
+ case hwmon_power_input_lowest:
+ ret = ltc2947_val_read(st, LTC2947_REG_POWER_MIN, PAGE0, 2,
+ &__val);
+ break;
+ case hwmon_power_max_alarm:
+ return ltc2947_alarm_read(st, LTC2947_REG_STATIP,
+ LTC2947_MAX_POWER_MASK, val);
+ case hwmon_power_min_alarm:
+ return ltc2947_alarm_read(st, LTC2947_REG_STATIP,
+ LTC2947_MIN_POWER_MASK, val);
+ case hwmon_power_max:
+ ret = ltc2947_val_read(st, LTC2947_REG_POWER_THRE_H, PAGE1, 2,
+ &__val);
+ break;
+ case hwmon_power_min:
+ ret = ltc2947_val_read(st, LTC2947_REG_POWER_THRE_L, PAGE1, 2,
+ &__val);
+ break;
+ default:
+ return -ENOTSUPP;
+ }
+
+ if (ret)
+ return ret;
+
+ *val = __val * lsb;
+
+ return 0;
+}
+
+static int ltc2947_read_curr(struct device *dev, const u32 attr, long *val)
+{
+ struct ltc2947_data *st = dev_get_drvdata(dev);
+ int ret;
+ u8 lsb = 12; /* in mA */
+ s64 __val = 0;
+
+ switch (attr) {
+ case hwmon_curr_input:
+ ret = ltc2947_val_read(st, LTC2947_REG_CURRENT, PAGE0, 3,
+ &__val);
+ lsb = 3;
+ break;
+ case hwmon_curr_highest:
+ ret = ltc2947_val_read(st, LTC2947_REG_CURRENT_MAX, PAGE0, 2,
+ &__val);
+ break;
+ case hwmon_curr_lowest:
+ ret = ltc2947_val_read(st, LTC2947_REG_CURRENT_MIN, PAGE0, 2,
+ &__val);
+ break;
+ case hwmon_curr_max_alarm:
+ return ltc2947_alarm_read(st, LTC2947_REG_STATIP,
+ LTC2947_MAX_CURRENT_MASK, val);
+ case hwmon_curr_min_alarm:
+ return ltc2947_alarm_read(st, LTC2947_REG_STATIP,
+ LTC2947_MIN_CURRENT_MASK, val);
+ case hwmon_curr_max:
+ ret = ltc2947_val_read(st, LTC2947_REG_CURRENT_THRE_H, PAGE1, 2,
+ &__val);
+ break;
+ case hwmon_curr_min:
+ ret = ltc2947_val_read(st, LTC2947_REG_CURRENT_THRE_L, PAGE1, 2,
+ &__val);
+ break;
+ default:
+ return -ENOTSUPP;
+ }
+
+ if (ret)
+ return ret;
+
+ *val = __val * lsb;
+
+ return 0;
+}
+
+static int ltc2947_read_in(struct device *dev, const u32 attr, long *val,
+ const int channel)
+{
+ struct ltc2947_data *st = dev_get_drvdata(dev);
+ int ret;
+ u8 lsb = 2; /* in mV */
+ s64 __val = 0;
+
+ if (channel < 0 || channel > LTC2947_VOLTAGE_DVCC_CHAN) {
+ dev_err(st->dev, "Invalid chan%d for voltage", channel);
+ return -EINVAL;
+ }
+
+ switch (attr) {
+ case hwmon_in_input:
+ if (channel == LTC2947_VOLTAGE_DVCC_CHAN) {
+ ret = ltc2947_val_read(st, LTC2947_REG_DVCC, PAGE0, 2,
+ &__val);
+ lsb = 145;
+ } else {
+ ret = ltc2947_val_read(st, LTC2947_REG_VOLTAGE, PAGE0,
+ 2, &__val);
+ }
+ break;
+ case hwmon_in_highest:
+ if (channel == LTC2947_VOLTAGE_DVCC_CHAN) {
+ ret = ltc2947_val_read(st, LTC2947_REG_DVCC_MAX, PAGE0,
+ 2, &__val);
+ lsb = 145;
+ } else {
+ ret = ltc2947_val_read(st, LTC2947_REG_VOLTAGE_MAX,
+ PAGE0, 2, &__val);
+ }
+ break;
+ case hwmon_in_lowest:
+ if (channel == LTC2947_VOLTAGE_DVCC_CHAN) {
+ ret = ltc2947_val_read(st, LTC2947_REG_DVCC_MIN, PAGE0,
+ 2, &__val);
+ lsb = 145;
+ } else {
+ ret = ltc2947_val_read(st, LTC2947_REG_VOLTAGE_MIN,
+ PAGE0, 2, &__val);
+ }
+ break;
+ case hwmon_in_max_alarm:
+ if (channel == LTC2947_VOLTAGE_DVCC_CHAN)
+ return ltc2947_alarm_read(st, LTC2947_REG_STATVDVCC,
+ LTC2947_MAX_VOLTAGE_MASK,
+ val);
+
+ return ltc2947_alarm_read(st, LTC2947_REG_STATVT,
+ LTC2947_MAX_VOLTAGE_MASK, val);
+ case hwmon_in_min_alarm:
+ if (channel == LTC2947_VOLTAGE_DVCC_CHAN)
+ return ltc2947_alarm_read(st, LTC2947_REG_STATVDVCC,
+ LTC2947_MIN_VOLTAGE_MASK,
+ val);
+
+ return ltc2947_alarm_read(st, LTC2947_REG_STATVT,
+ LTC2947_MIN_VOLTAGE_MASK, val);
+ case hwmon_in_max:
+ if (channel == LTC2947_VOLTAGE_DVCC_CHAN) {
+ ret = ltc2947_val_read(st, LTC2947_REG_DVCC_THRE_H,
+ PAGE1, 2, &__val);
+ lsb = 145;
+ } else {
+ ret = ltc2947_val_read(st, LTC2947_REG_VOLTAGE_THRE_H,
+ PAGE1, 2, &__val);
+ }
+ break;
+ case hwmon_in_min:
+ if (channel == LTC2947_VOLTAGE_DVCC_CHAN) {
+ ret = ltc2947_val_read(st, LTC2947_REG_DVCC_THRE_L,
+ PAGE1, 2, &__val);
+ lsb = 145;
+ } else {
+ ret = ltc2947_val_read(st, LTC2947_REG_VOLTAGE_THRE_L,
+ PAGE1, 2, &__val);
+ }
+ break;
+ default:
+ return -ENOTSUPP;
+ }
+
+ if (ret)
+ return ret;
+
+ *val = __val * lsb;
+
+ return 0;
+}
+
+static int ltc2947_read(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
+{
+ switch (type) {
+ case hwmon_in:
+ return ltc2947_read_in(dev, attr, val, channel);
+ case hwmon_curr:
+ return ltc2947_read_curr(dev, attr, val);
+ case hwmon_power:
+ return ltc2947_read_power(dev, attr, val);
+ case hwmon_temp:
+ return ltc2947_read_temp(dev, attr, val, channel);
+ default:
+ return -ENOTSUPP;
+ }
+}
+
+static int ltc2947_write_temp(struct device *dev, const u32 attr,
+ long val, const int channel)
+{
+ struct ltc2947_data *st = dev_get_drvdata(dev);
+
+ if (channel < 0 || channel > LTC2947_TEMP_FAN_CHAN) {
+ dev_err(st->dev, "Invalid chan%d for temperature", channel);
+ return -EINVAL;
+ }
+
+ switch (attr) {
+ case hwmon_temp_reset_history:
+ if (val != 1)
+ return -EINVAL;
+ return ltc2947_reset_history(st, LTC2947_REG_TEMP_MAX,
+ LTC2947_REG_TEMP_MIN);
+ case hwmon_temp_max:
+ val = clamp_val(val, TEMP_MIN, TEMP_MAX);
+ if (channel == LTC2947_TEMP_FAN_CHAN) {
+ if (!st->gpio_out)
+ return -ENOTSUPP;
+
+ return ltc2947_val_write(st,
+ LTC2947_REG_TEMP_FAN_THRE_H, PAGE1, 2,
+ DIV_ROUND_CLOSEST(val - 550, 204));
+ }
+
+ return ltc2947_val_write(st, LTC2947_REG_TEMP_THRE_H, PAGE1, 2,
+ DIV_ROUND_CLOSEST(val - 550, 204));
+ case hwmon_temp_min:
+ val = clamp_val(val, TEMP_MIN, TEMP_MAX);
+ if (channel == LTC2947_TEMP_FAN_CHAN) {
+ if (!st->gpio_out)
+ return -ENOTSUPP;
+
+ return ltc2947_val_write(st,
+ LTC2947_REG_TEMP_FAN_THRE_L, PAGE1, 2,
+ DIV_ROUND_CLOSEST(val - 550, 204));
+ }
+
+ return ltc2947_val_write(st, LTC2947_REG_TEMP_THRE_L, PAGE1, 2,
+ DIV_ROUND_CLOSEST(val - 550, 204));
+ default:
+ return -ENOTSUPP;
+ }
+}
+
+static int ltc2947_write_power(struct device *dev, const u32 attr,
+ long val)
+{
+ struct ltc2947_data *st = dev_get_drvdata(dev);
+
+ switch (attr) {
+ case hwmon_power_reset_history:
+ if (val != 1)
+ return -EINVAL;
+ return ltc2947_reset_history(st, LTC2947_REG_POWER_MAX,
+ LTC2947_REG_POWER_MIN);
+ case hwmon_power_max:
+ val = clamp_val(val, POWER_MIN, POWER_MAX);
+ return ltc2947_val_write(st, LTC2947_REG_POWER_THRE_H, PAGE1, 2,
+ DIV_ROUND_CLOSEST(val, 200000));
+ case hwmon_power_min:
+ val = clamp_val(val, POWER_MIN, POWER_MAX);
+ return ltc2947_val_write(st, LTC2947_REG_POWER_THRE_L, PAGE1, 2,
+ DIV_ROUND_CLOSEST(val, 200000));
+ default:
+ return -ENOTSUPP;
+ }
+}
+
+static int ltc2947_write_curr(struct device *dev, const u32 attr,
+ long val)
+{
+ struct ltc2947_data *st = dev_get_drvdata(dev);
+
+ switch (attr) {
+ case hwmon_curr_reset_history:
+ if (val != 1)
+ return -EINVAL;
+ return ltc2947_reset_history(st, LTC2947_REG_CURRENT_MAX,
+ LTC2947_REG_CURRENT_MIN);
+ case hwmon_curr_max:
+ val = clamp_val(val, CURRENT_MIN, CURRENT_MAX);
+ return ltc2947_val_write(st, LTC2947_REG_CURRENT_THRE_H, PAGE1,
+ 2, DIV_ROUND_CLOSEST(val, 12));
+ case hwmon_curr_min:
+ val = clamp_val(val, CURRENT_MIN, CURRENT_MAX);
+ return ltc2947_val_write(st, LTC2947_REG_CURRENT_THRE_L, PAGE1,
+ 2, DIV_ROUND_CLOSEST(val, 12));
+ default:
+ return -ENOTSUPP;
+ }
+}
+
+static int ltc2947_write_in(struct device *dev, const u32 attr, long val,
+ const int channel)
+{
+ struct ltc2947_data *st = dev_get_drvdata(dev);
+
+ if (channel > LTC2947_VOLTAGE_DVCC_CHAN) {
+ dev_err(st->dev, "Invalid chan%d for voltage", channel);
+ return -EINVAL;
+ }
+
+ switch (attr) {
+ case hwmon_in_reset_history:
+ if (val != 1)
+ return -EINVAL;
+
+ if (channel == LTC2947_VOLTAGE_DVCC_CHAN)
+ return ltc2947_reset_history(st, LTC2947_REG_DVCC_MAX,
+ LTC2947_REG_DVCC_MIN);
+
+ return ltc2947_reset_history(st, LTC2947_REG_VOLTAGE_MAX,
+ LTC2947_REG_VOLTAGE_MIN);
+ case hwmon_in_max:
+ if (channel == LTC2947_VOLTAGE_DVCC_CHAN) {
+ val = clamp_val(val, VDVCC_MIN, VDVCC_MAX);
+ return ltc2947_val_write(st, LTC2947_REG_DVCC_THRE_H,
+ PAGE1, 2,
+ DIV_ROUND_CLOSEST(val, 145));
+ }
+
+ val = clamp_val(val, VOLTAGE_MIN, VOLTAGE_MAX);
+ return ltc2947_val_write(st, LTC2947_REG_VOLTAGE_THRE_H,
+ PAGE1, 2, DIV_ROUND_CLOSEST(val, 2));
+ case hwmon_in_min:
+ if (channel == LTC2947_VOLTAGE_DVCC_CHAN) {
+ val = clamp_val(val, VDVCC_MIN, VDVCC_MAX);
+ return ltc2947_val_write(st, LTC2947_REG_DVCC_THRE_L,
+ PAGE1, 2,
+ DIV_ROUND_CLOSEST(val, 145));
+ }
+
+ val = clamp_val(val, VOLTAGE_MIN, VOLTAGE_MAX);
+ return ltc2947_val_write(st, LTC2947_REG_VOLTAGE_THRE_L,
+ PAGE1, 2, DIV_ROUND_CLOSEST(val, 2));
+ default:
+ return -ENOTSUPP;
+ }
+}
+
+static int ltc2947_write(struct device *dev,
+ enum hwmon_sensor_types type,
+ u32 attr, int channel, long val)
+{
+ switch (type) {
+ case hwmon_in:
+ return ltc2947_write_in(dev, attr, val, channel);
+ case hwmon_curr:
+ return ltc2947_write_curr(dev, attr, val);
+ case hwmon_power:
+ return ltc2947_write_power(dev, attr, val);
+ case hwmon_temp:
+ return ltc2947_write_temp(dev, attr, val, channel);
+ default:
+ return -ENOTSUPP;
+ }
+}
+
+static int ltc2947_read_labels(struct device *dev,
+ enum hwmon_sensor_types type,
+ u32 attr, int channel, const char **str)
+{
+ switch (type) {
+ case hwmon_in:
+ if (channel == LTC2947_VOLTAGE_DVCC_CHAN)
+ *str = "DVCC";
+ else
+ *str = "VP-VM";
+ return 0;
+ case hwmon_curr:
+ *str = "IP-IM";
+ return 0;
+ case hwmon_temp:
+ if (channel == LTC2947_TEMP_FAN_CHAN)
+ *str = "TEMPFAN";
+ else
+ *str = "Ambient";
+ return 0;
+ case hwmon_power:
+ *str = "Power";
+ return 0;
+ default:
+ return -ENOTSUPP;
+ }
+}
+
+static int ltc2947_in_is_visible(const u32 attr)
+{
+ switch (attr) {
+ case hwmon_in_input:
+ case hwmon_in_highest:
+ case hwmon_in_lowest:
+ case hwmon_in_max_alarm:
+ case hwmon_in_min_alarm:
+ case hwmon_in_label:
+ return 0444;
+ case hwmon_in_reset_history:
+ return 0200;
+ case hwmon_in_max:
+ case hwmon_in_min:
+ return 0644;
+ default:
+ return 0;
+ }
+}
+
+static int ltc2947_curr_is_visible(const u32 attr)
+{
+ switch (attr) {
+ case hwmon_curr_input:
+ case hwmon_curr_highest:
+ case hwmon_curr_lowest:
+ case hwmon_curr_max_alarm:
+ case hwmon_curr_min_alarm:
+ case hwmon_curr_label:
+ return 0444;
+ case hwmon_curr_reset_history:
+ return 0200;
+ case hwmon_curr_max:
+ case hwmon_curr_min:
+ return 0644;
+ default:
+ return 0;
+ }
+}
+
+static int ltc2947_power_is_visible(const u32 attr)
+{
+ switch (attr) {
+ case hwmon_power_input:
+ case hwmon_power_input_highest:
+ case hwmon_power_input_lowest:
+ case hwmon_power_label:
+ case hwmon_power_max_alarm:
+ case hwmon_power_min_alarm:
+ return 0444;
+ case hwmon_power_reset_history:
+ return 0200;
+ case hwmon_power_max:
+ case hwmon_power_min:
+ return 0644;
+ default:
+ return 0;
+ }
+}
+
+static int ltc2947_temp_is_visible(const u32 attr)
+{
+ switch (attr) {
+ case hwmon_temp_input:
+ case hwmon_temp_highest:
+ case hwmon_temp_lowest:
+ case hwmon_temp_max_alarm:
+ case hwmon_temp_min_alarm:
+ case hwmon_temp_label:
+ return 0444;
+ case hwmon_temp_reset_history:
+ return 0200;
+ case hwmon_temp_max:
+ case hwmon_temp_min:
+ return 0644;
+ default:
+ return 0;
+ }
+}
+
+static umode_t ltc2947_is_visible(const void *data,
+ enum hwmon_sensor_types type,
+ u32 attr, int channel)
+{
+ switch (type) {
+ case hwmon_in:
+ return ltc2947_in_is_visible(attr);
+ case hwmon_curr:
+ return ltc2947_curr_is_visible(attr);
+ case hwmon_power:
+ return ltc2947_power_is_visible(attr);
+ case hwmon_temp:
+ return ltc2947_temp_is_visible(attr);
+ default:
+ return 0;
+ }
+}
+
+static const struct hwmon_channel_info *ltc2947_info[] = {
+ HWMON_CHANNEL_INFO(in,
+ HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
+ HWMON_I_MAX | HWMON_I_MIN | HWMON_I_RESET_HISTORY |
+ HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM |
+ HWMON_I_LABEL,
+ HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
+ HWMON_I_MAX | HWMON_I_MIN | HWMON_I_RESET_HISTORY |
+ HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM |
+ HWMON_I_LABEL),
+ HWMON_CHANNEL_INFO(curr,
+ HWMON_C_INPUT | HWMON_C_LOWEST | HWMON_C_HIGHEST |
+ HWMON_C_MAX | HWMON_C_MIN | HWMON_C_RESET_HISTORY |
+ HWMON_C_MIN_ALARM | HWMON_C_MAX_ALARM |
+ HWMON_C_LABEL),
+ HWMON_CHANNEL_INFO(power,
+ HWMON_P_INPUT | HWMON_P_INPUT_LOWEST |
+ HWMON_P_INPUT_HIGHEST | HWMON_P_MAX | HWMON_P_MIN |
+ HWMON_P_RESET_HISTORY | HWMON_P_MAX_ALARM |
+ HWMON_P_MIN_ALARM | HWMON_P_LABEL),
+ HWMON_CHANNEL_INFO(temp,
+ HWMON_T_INPUT | HWMON_T_LOWEST | HWMON_T_HIGHEST |
+ HWMON_T_MAX | HWMON_T_MIN | HWMON_T_RESET_HISTORY |
+ HWMON_T_MIN_ALARM | HWMON_T_MAX_ALARM |
+ HWMON_T_LABEL,
+ HWMON_T_MAX_ALARM | HWMON_T_MIN_ALARM | HWMON_T_MAX |
+ HWMON_T_MIN | HWMON_T_LABEL),
+ NULL
+};
+
+static const struct hwmon_ops ltc2947_hwmon_ops = {
+ .is_visible = ltc2947_is_visible,
+ .read = ltc2947_read,
+ .write = ltc2947_write,
+ .read_string = ltc2947_read_labels,
+};
+
+static const struct hwmon_chip_info ltc2947_chip_info = {
+ .ops = <c2947_hwmon_ops,
+ .info = ltc2947_info,
+};
+
+/* energy attributes are 6bytes wide so we need u64 */
+static SENSOR_DEVICE_ATTR(energy1_input, 0444, ltc2947_show_value, NULL,
+ LTC2947_REG_ENERGY1);
+static SENSOR_DEVICE_ATTR(energy2_input, 0444, ltc2947_show_value, NULL,
+ LTC2947_REG_ENERGY2);
+
+static struct attribute *ltc2947_attrs[] = {
+ &sensor_dev_attr_energy1_input.dev_attr.attr,
+ &sensor_dev_attr_energy2_input.dev_attr.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(ltc2947);
+
+static void ltc2947_clk_disable(void *data)
+{
+ struct clk *extclk = data;
+
+ clk_disable_unprepare(extclk);
+}
+
+static int ltc2947_setup(struct ltc2947_data *st)
+{
+ int ret;
+ struct clk *extclk;
+ u32 dummy, deadband, pol;
+ u32 accum[2];
+
+ /* clear status register by reading it */
+ ret = regmap_read(st->map, LTC2947_REG_STATUS, &dummy);
+ if (ret)
+ return ret;
+ /*
+ * Set max/min for power here since the default values x scale
+ * would overflow on 32bit arch
+ */
+ ret = ltc2947_val_write(st, LTC2947_REG_POWER_THRE_H, PAGE1, 2,
+ POWER_MAX / 200000);
+ if (ret)
+ return ret;
+
+ ret = ltc2947_val_write(st, LTC2947_REG_POWER_THRE_L, PAGE1, 2,
+ POWER_MIN / 200000);
+ if (ret)
+ return ret;
+
+ /* check external clock presence */
+ extclk = devm_clk_get(st->dev, NULL);
+ if (!IS_ERR(extclk)) {
+ unsigned long rate_hz;
+ u8 pre = 0, div, tbctl;
+ u64 aux;
+
+ /* let's calculate and set the right valus in TBCTL */
+ rate_hz = clk_get_rate(extclk);
+ if (rate_hz < LTC2947_CLK_MIN || rate_hz > LTC2947_CLK_MAX) {
+ dev_err(st->dev, "Invalid rate:%lu for external clock",
+ rate_hz);
+ return -EINVAL;
+ }
+
+ ret = clk_prepare_enable(extclk);
+ if (ret)
+ return ret;
+
+ ret = devm_add_action_or_reset(st->dev, ltc2947_clk_disable,
+ extclk);
+ if (ret)
+ return ret;
+ /* as in table 1 of the datasheet */
+ if (rate_hz >= LTC2947_CLK_MIN && rate_hz <= 1000000)
+ pre = 0;
+ else if (rate_hz > 1000000 && rate_hz <= 2000000)
+ pre = 1;
+ else if (rate_hz > 2000000 && rate_hz <= 4000000)
+ pre = 2;
+ else if (rate_hz > 4000000 && rate_hz <= 8000000)
+ pre = 3;
+ else if (rate_hz > 8000000 && rate_hz <= 16000000)
+ pre = 4;
+ else if (rate_hz > 16000000 && rate_hz <= LTC2947_CLK_MAX)
+ pre = 5;
+ /*
+ * Div is given by:
+ * floor(fref / (2^PRE * 32768))
+ */
+ div = rate_hz / ((1 << pre) * 32768);
+ tbctl = LTC2947_PRE(pre) | LTC2947_DIV(div);
+
+ ret = regmap_write(st->map, LTC2947_REG_TBCTL, tbctl);
+ if (ret)
+ return ret;
+ /*
+ * The energy lsb is given by (in W*s):
+ * 06416 * (1/fref) * 2^PRE * (DIV + 1)
+ * The value is multiplied by 10E9
+ */
+ aux = (div + 1) * ((1 << pre) * 641600000ULL);
+ st->lsb_energy = DIV_ROUND_CLOSEST_ULL(aux, rate_hz);
+ } else {
+ /* 19.89E-6 * 10E9 */
+ st->lsb_energy = 19890;
+ }
+ ret = of_property_read_u32_array(st->dev->of_node,
+ "adi,accumulator-ctl-pol", accum,
+ ARRAY_SIZE(accum));
+ if (!ret) {
+ u32 accum_reg = LTC2947_ACCUM_POL_1(accum[0]) |
+ LTC2947_ACCUM_POL_2(accum[1]);
+
+ ret = regmap_write(st->map, LTC2947_REG_ACCUM_POL, accum_reg);
+ if (ret)
+ return ret;
+ }
+ ret = of_property_read_u32(st->dev->of_node,
+ "adi,accumulation-deadband-microamp",
+ &deadband);
+ if (!ret) {
+ /* the LSB is the same as the current, so 3mA */
+ ret = regmap_write(st->map, LTC2947_REG_ACCUM_DEADBAND,
+ deadband / (1000 * 3));
+ if (ret)
+ return ret;
+ }
+ /* check gpio cfg */
+ ret = of_property_read_u32(st->dev->of_node, "adi,gpio-out-pol", &pol);
+ if (!ret) {
+ /* setup GPIO as output */
+ u32 gpio_ctl = LTC2947_GPIO_EN(1) | LTC2947_GPIO_FAN_EN(1) |
+ LTC2947_GPIO_FAN_POL(pol);
+
+ st->gpio_out = true;
+ ret = regmap_write(st->map, LTC2947_REG_GPIOSTATCTL, gpio_ctl);
+ if (ret)
+ return ret;
+ }
+ ret = of_property_read_u32_array(st->dev->of_node, "adi,gpio-in-accum",
+ accum, ARRAY_SIZE(accum));
+ if (!ret) {
+ /*
+ * Setup the accum options. The gpioctl is already defined as
+ * input by default.
+ */
+ u32 accum_val = LTC2947_ACCUM_POL_1(accum[0]) |
+ LTC2947_ACCUM_POL_2(accum[1]);
+
+ if (st->gpio_out) {
+ dev_err(st->dev,
+ "Cannot have input gpio config if already configured as output");
+ return -EINVAL;
+ }
+
+ ret = regmap_write(st->map, LTC2947_REG_GPIO_ACCUM, accum_val);
+ if (ret)
+ return ret;
+ }
+
+ /* set continuos mode */
+ return regmap_update_bits(st->map, LTC2947_REG_CTRL,
+ LTC2947_CONT_MODE_MASK, LTC2947_CONT_MODE(1));
+}
+
+int ltc2947_core_probe(struct regmap *map, const char *name)
+{
+ struct ltc2947_data *st;
+ struct device *dev = regmap_get_device(map);
+ struct device *hwmon;
+ int ret;
+
+ st = devm_kzalloc(dev, sizeof(*st), GFP_KERNEL);
+ if (!st)
+ return -ENOMEM;
+
+ st->map = map;
+ st->dev = dev;
+ dev_set_drvdata(dev, st);
+ mutex_init(&st->lock);
+
+ ret = ltc2947_setup(st);
+ if (ret)
+ return ret;
+
+ hwmon = devm_hwmon_device_register_with_info(dev, name, st,
+ <c2947_chip_info,
+ ltc2947_groups);
+ return PTR_ERR_OR_ZERO(hwmon);
+}
+EXPORT_SYMBOL_GPL(ltc2947_core_probe);
+
+static int __maybe_unused ltc2947_resume(struct device *dev)
+{
+ struct ltc2947_data *st = dev_get_drvdata(dev);
+ u32 ctrl = 0;
+ int ret;
+
+ /* dummy read to wake the device */
+ ret = regmap_read(st->map, LTC2947_REG_CTRL, &ctrl);
+ if (ret)
+ return ret;
+ /*
+ * Wait for the device. It takes 100ms to wake up so, 10ms extra
+ * should be enough.
+ */
+ msleep(110);
+ ret = regmap_read(st->map, LTC2947_REG_CTRL, &ctrl);
+ if (ret)
+ return ret;
+ /* ctrl should be 0 */
+ if (ctrl != 0) {
+ dev_err(st->dev, "Device failed to wake up, ctl:%02X\n", ctrl);
+ return -ETIMEDOUT;
+ }
+
+ /* set continuous mode */
+ return regmap_update_bits(st->map, LTC2947_REG_CTRL,
+ LTC2947_CONT_MODE_MASK, LTC2947_CONT_MODE(1));
+}
+
+static int __maybe_unused ltc2947_suspend(struct device *dev)
+{
+ struct ltc2947_data *st = dev_get_drvdata(dev);
+
+ return regmap_update_bits(st->map, LTC2947_REG_CTRL,
+ LTC2947_SHUTDOWN_MASK, 1);
+}
+
+SIMPLE_DEV_PM_OPS(ltc2947_pm_ops, ltc2947_suspend, ltc2947_resume);
+EXPORT_SYMBOL_GPL(ltc2947_pm_ops);
+
+const struct of_device_id ltc2947_of_match[] = {
+ { .compatible = "adi,ltc2947" },
+ {}
+};
+EXPORT_SYMBOL_GPL(ltc2947_of_match);
+MODULE_DEVICE_TABLE(of, ltc2947_of_match);
+
+MODULE_AUTHOR("Nuno Sa <nuno.sa@analog.com>");
+MODULE_DESCRIPTION("LTC2947 power and energy monitor core driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hwmon/ltc2947-i2c.c b/drivers/hwmon/ltc2947-i2c.c
new file mode 100644
index 000000000000..cf6074b110ae
--- /dev/null
+++ b/drivers/hwmon/ltc2947-i2c.c
@@ -0,0 +1,49 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Analog Devices LTC2947 high precision power and energy monitor over I2C
+ *
+ * Copyright 2019 Analog Devices Inc.
+ */
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+
+#include "ltc2947.h"
+
+static const struct regmap_config ltc2947_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+};
+
+static int ltc2947_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct regmap *map;
+
+ map = devm_regmap_init_i2c(i2c, <c2947_regmap_config);
+ if (IS_ERR(map))
+ return PTR_ERR(map);
+
+ return ltc2947_core_probe(map, i2c->name);
+}
+
+static const struct i2c_device_id ltc2947_id[] = {
+ {"ltc2947", 0},
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, ltc2947_id);
+
+static struct i2c_driver ltc2947_driver = {
+ .driver = {
+ .name = "ltc2947",
+ .of_match_table = ltc2947_of_match,
+ .pm = <c2947_pm_ops,
+ },
+ .probe = ltc2947_probe,
+ .id_table = ltc2947_id,
+};
+module_i2c_driver(ltc2947_driver);
+
+MODULE_AUTHOR("Nuno Sa <nuno.sa@analog.com>");
+MODULE_DESCRIPTION("LTC2947 I2C power and energy monitor driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hwmon/ltc2947-spi.c b/drivers/hwmon/ltc2947-spi.c
new file mode 100644
index 000000000000..c24ca569db1b
--- /dev/null
+++ b/drivers/hwmon/ltc2947-spi.c
@@ -0,0 +1,50 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Analog Devices LTC2947 high precision power and energy monitor over SPI
+ *
+ * Copyright 2019 Analog Devices Inc.
+ */
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/regmap.h>
+#include <linux/spi/spi.h>
+
+#include "ltc2947.h"
+
+static const struct regmap_config ltc2947_regmap_config = {
+ .reg_bits = 16,
+ .val_bits = 8,
+ .read_flag_mask = BIT(0),
+};
+
+static int ltc2947_probe(struct spi_device *spi)
+{
+ struct regmap *map;
+
+ map = devm_regmap_init_spi(spi, <c2947_regmap_config);
+ if (IS_ERR(map))
+ return PTR_ERR(map);
+
+ return ltc2947_core_probe(map, spi_get_device_id(spi)->name);
+}
+
+static const struct spi_device_id ltc2947_id[] = {
+ {"ltc2947", 0},
+ {}
+};
+MODULE_DEVICE_TABLE(spi, ltc2947_id);
+
+static struct spi_driver ltc2947_driver = {
+ .driver = {
+ .name = "ltc2947",
+ .of_match_table = ltc2947_of_match,
+ .pm = <c2947_pm_ops,
+ },
+ .probe = ltc2947_probe,
+ .id_table = ltc2947_id,
+};
+module_spi_driver(ltc2947_driver);
+
+MODULE_AUTHOR("Nuno Sa <nuno.sa@analog.com>");
+MODULE_DESCRIPTION("LTC2947 SPI power and energy monitor driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hwmon/ltc2947.h b/drivers/hwmon/ltc2947.h
new file mode 100644
index 000000000000..5b8ff81a3dba
--- /dev/null
+++ b/drivers/hwmon/ltc2947.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _LINUX_LTC2947_H
+#define _LINUX_LTC2947_H
+
+struct regmap;
+
+extern const struct of_device_id ltc2947_of_match[];
+extern const struct dev_pm_ops ltc2947_pm_ops;
+
+int ltc2947_core_probe(struct regmap *map, const char *name);
+
+#endif
--
2.23.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH v3 2/2] dt-bindings: hwmon: Add ltc2947 documentation
2019-10-21 15:41 [PATCH v3 1/2] hwmon: Add support for ltc2947 Nuno Sá
@ 2019-10-21 15:41 ` Nuno Sá
2019-10-24 22:46 ` Rob Herring
2019-10-29 12:30 ` Guenter Roeck
2019-10-29 12:24 ` [PATCH v3 1/2] hwmon: Add support for ltc2947 Guenter Roeck
2019-10-29 12:27 ` Guenter Roeck
2 siblings, 2 replies; 7+ messages in thread
From: Nuno Sá @ 2019-10-21 15:41 UTC (permalink / raw)
To: linux-hwmon, devicetree, linux-doc
Cc: Rob Herring, Mark Rutland, Guenter Roeck, Jean Delvare, Jonathan Corbet
Document the LTC2947 device devicetree bindings.
Signed-off-by: Nuno Sá <nuno.sa@analog.com>
---
Changes in v2:
* Add license identifier;
* Fix the uint32-array properties;
* Set maximum at the same indent as allOf in adi,accumulation-deadband-microamp;
* Set enum at the same indent as allOf in adi,gpio-out-pol;
* Use spi instead of spi0 on the example;
Changes in v3:
* Nothing changed.
.../bindings/hwmon/adi,ltc2947.yaml | 104 ++++++++++++++++++
MAINTAINERS | 1 +
2 files changed, 105 insertions(+)
create mode 100644 Documentation/devicetree/bindings/hwmon/adi,ltc2947.yaml
diff --git a/Documentation/devicetree/bindings/hwmon/adi,ltc2947.yaml b/Documentation/devicetree/bindings/hwmon/adi,ltc2947.yaml
new file mode 100644
index 000000000000..ae04903f34bf
--- /dev/null
+++ b/Documentation/devicetree/bindings/hwmon/adi,ltc2947.yaml
@@ -0,0 +1,104 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/bindings/hwmon/adi,ltc2947.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Analog Devices LTC2947 high precision power and energy monitor
+
+maintainers:
+ - Nuno Sá <nuno.sa@analog.com>
+
+description: |
+ Analog Devices LTC2947 high precision power and energy monitor over SPI or I2C.
+
+ https://www.analog.com/media/en/technical-documentation/data-sheets/LTC2947.pdf
+
+properties:
+ compatible:
+ enum:
+ - adi,ltc2947
+
+ reg:
+ maxItems: 1
+
+ clocks:
+ description:
+ The LTC2947 uses either a trimmed internal oscillator or an external clock
+ as the time base for determining the integration period to represent time,
+ charge and energy. When an external clock is used, this property must be
+ set accordingly.
+ maxItems: 1
+
+ adi,accumulator-ctl-pol:
+ description:
+ This property controls the polarity of current that is accumulated to
+ calculate charge and energy so that, they can be only accumulated for
+ positive current for example. Since there are two sets of registers for
+ the accumulated values, this entry can also have two items which sets
+ energy1/charge1 and energy2/charger2 respectively. Check table 12 of the
+ datasheet for more information on the supported options.
+ allOf:
+ - $ref: /schemas/types.yaml#/definitions/uint32-array
+ - minItems: 2
+ maxItems: 2
+ items:
+ enum: [0, 1, 2, 3]
+ default: 0
+
+ adi,accumulation-deadband-microamp:
+ description:
+ This property controls the Accumulation Dead band which allows to set the
+ level of current below which no accumulation takes place.
+ allOf:
+ - $ref: /schemas/types.yaml#/definitions/uint32
+ maximum: 255
+ default: 0
+
+ adi,gpio-out-pol:
+ description:
+ This property controls the GPIO polarity. Setting it to one makes the GPIO
+ active high, setting it to zero makets it active low. When this property
+ is present, the GPIO is automatically configured as output and set to
+ control a fan as a function of measured temperature.
+ allOf:
+ - $ref: /schemas/types.yaml#/definitions/uint32
+ enum: [0, 1]
+ default: 0
+
+ adi,gpio-in-accum:
+ description:
+ When set, this property sets the GPIO as input. It is then used to control
+ the accumulation of charge, energy and time. This function can be
+ enabled/configured separately for each of the two sets of accumulation
+ registers. Check table 13 of the datasheet for more information on the
+ supported options. This property cannot be used together with
+ adi,gpio-out-pol.
+ allOf:
+ - $ref: /schemas/types.yaml#/definitions/uint32-array
+ - minItems: 2
+ maxItems: 2
+ items:
+ enum: [0, 1, 2]
+ default: 0
+
+required:
+ - compatible
+ - reg
+
+
+examples:
+ - |
+ spi {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ ltc2947_spi: ltc2947@0 {
+ compatible = "adi,ltc2947";
+ reg = <0>;
+ /* accumulation takes place always for energ1/charge1. */
+ /* accumulation only on positive current for energy2/charge2. */
+ adi,accumulator-ctl-pol = <0 1>;
+ };
+ };
+...
diff --git a/MAINTAINERS b/MAINTAINERS
index 318332b6a411..e2ba1a182052 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -9638,6 +9638,7 @@ F: drivers/hwmon/ltc2947-core.c
F: drivers/hwmon/ltc2947-spi.c
F: drivers/hwmon/ltc2947-i2c.c
F: drivers/hwmon/ltc2947.h
+F: Documentation/devicetree/bindings/hwmon/adi,ltc2947.yaml
LTC4306 I2C MULTIPLEXER DRIVER
M: Michael Hennerich <michael.hennerich@analog.com>
--
2.23.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH v3 2/2] dt-bindings: hwmon: Add ltc2947 documentation
2019-10-21 15:41 ` [PATCH v3 2/2] dt-bindings: hwmon: Add ltc2947 documentation Nuno Sá
@ 2019-10-24 22:46 ` Rob Herring
2019-10-25 10:32 ` Sa, Nuno
2019-10-29 12:30 ` Guenter Roeck
1 sibling, 1 reply; 7+ messages in thread
From: Rob Herring @ 2019-10-24 22:46 UTC (permalink / raw)
To: Nuno Sá
Cc: linux-hwmon, devicetree, linux-doc, Mark Rutland, Guenter Roeck,
Jean Delvare, Jonathan Corbet
On Mon, 21 Oct 2019 17:41:15 +0200, =?UTF-8?q?Nuno=20S=C3=A1?= wrote:
> Document the LTC2947 device devicetree bindings.
>
> Signed-off-by: Nuno Sá <nuno.sa@analog.com>
> ---
> Changes in v2:
> * Add license identifier;
> * Fix the uint32-array properties;
> * Set maximum at the same indent as allOf in adi,accumulation-deadband-microamp;
> * Set enum at the same indent as allOf in adi,gpio-out-pol;
> * Use spi instead of spi0 on the example;
>
> Changes in v3:
> * Nothing changed.
>
> .../bindings/hwmon/adi,ltc2947.yaml | 104 ++++++++++++++++++
> MAINTAINERS | 1 +
> 2 files changed, 105 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/hwmon/adi,ltc2947.yaml
>
Please add Acked-by/Reviewed-by tags when posting new versions. However,
there's no need to repost patches *only* to add the tags. The upstream
maintainer will do that for acks received on the version they apply.
If a tag was not added on purpose, please state why and what changed.
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH v3 2/2] dt-bindings: hwmon: Add ltc2947 documentation
2019-10-24 22:46 ` Rob Herring
@ 2019-10-25 10:32 ` Sa, Nuno
0 siblings, 0 replies; 7+ messages in thread
From: Sa, Nuno @ 2019-10-25 10:32 UTC (permalink / raw)
To: robh
Cc: linux, corbet, linux-hwmon, devicetree, linux-doc, mark.rutland,
jdelvare
On Thu, 2019-10-24 at 17:46 -0500, Rob Herring wrote:
>
> On Mon, 21 Oct 2019 17:41:15 +0200, =?UTF-8?q?Nuno=20S=C3=A1?= wrote:
> > Document the LTC2947 device devicetree bindings.
> >
> > Signed-off-by: Nuno Sá <nuno.sa@analog.com>
> > ---
> > Changes in v2:
> > * Add license identifier;
> > * Fix the uint32-array properties;
> > * Set maximum at the same indent as allOf in adi,accumulation-
> > deadband-microamp;
> > * Set enum at the same indent as allOf in adi,gpio-out-pol;
> > * Use spi instead of spi0 on the example;
> >
> > Changes in v3:
> > * Nothing changed.
> >
> > .../bindings/hwmon/adi,ltc2947.yaml | 104
> > ++++++++++++++++++
> > MAINTAINERS | 1 +
> > 2 files changed, 105 insertions(+)
> > create mode 100644
> > Documentation/devicetree/bindings/hwmon/adi,ltc2947.yaml
> >
>
> Please add Acked-by/Reviewed-by tags when posting new versions.
> However,
> there's no need to repost patches *only* to add the tags. The
> upstream
> maintainer will do that for acks received on the version they apply.
>
> If a tag was not added on purpose, please state why and what changed.
Yes, sorry for that. Completely forgot.
Nuno Sá
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH v3 1/2] hwmon: Add support for ltc2947
2019-10-21 15:41 [PATCH v3 1/2] hwmon: Add support for ltc2947 Nuno Sá
2019-10-21 15:41 ` [PATCH v3 2/2] dt-bindings: hwmon: Add ltc2947 documentation Nuno Sá
@ 2019-10-29 12:24 ` Guenter Roeck
2019-10-29 12:27 ` Guenter Roeck
2 siblings, 0 replies; 7+ messages in thread
From: Guenter Roeck @ 2019-10-29 12:24 UTC (permalink / raw)
To: Nuno Sá
Cc: linux-hwmon, devicetree, linux-doc, Rob Herring, Mark Rutland,
Jean Delvare, Jonathan Corbet
On Mon, Oct 21, 2019 at 05:41:14PM +0200, Nuno Sá wrote:
> The ltc2947 is a high precision power and energy monitor with an
> internal sense resistor supporting up to +/- 30A. Three internal no
> Latency ADCs ensure accurate measurement of voltage and current, while
> high-bandwidth analog multiplication of voltage and current provides
> accurate power measurement in a wide range of applications. Internal or
> external clocking options enable precise charge and energy measurements.
>
> Signed-off-by: Nuno Sá <nuno.sa@analog.com>
Applied, with minor change (see below).
Guenter
> ---
> Changes in v2:
> * Add #include <linux/bits.h>;
> * Aemove unneeded dev_err() messages;
> * Drop reset flag and calls to mutex_* in resume()/suspend() code;
> * Drop fault, overflow, energy max/min and energy alarms attributes;
> * Use standard attributes for power;
> * Remove unused macros;
> * Adjust min/max values per datasheet (on clamp_val() calls);
> * Set power max/min on setup().
>
> Changes in v3:
> * Add Doc file to index.rst;
> * Set the Doc file as restructured text file;
> * If/else cleanup as else after return is unnecessary.
>
> Documentation/hwmon/index.rst | 1 +
> Documentation/hwmon/ltc2947.rst | 100 +++
> MAINTAINERS | 10 +
> drivers/hwmon/Kconfig | 27 +
> drivers/hwmon/Makefile | 3 +
> drivers/hwmon/ltc2947-core.c | 1184 +++++++++++++++++++++++++++++++
> drivers/hwmon/ltc2947-i2c.c | 49 ++
> drivers/hwmon/ltc2947-spi.c | 50 ++
> drivers/hwmon/ltc2947.h | 12 +
> 9 files changed, 1436 insertions(+)
> create mode 100644 Documentation/hwmon/ltc2947.rst
> create mode 100644 drivers/hwmon/ltc2947-core.c
> create mode 100644 drivers/hwmon/ltc2947-i2c.c
> create mode 100644 drivers/hwmon/ltc2947-spi.c
> create mode 100644 drivers/hwmon/ltc2947.h
>
> diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst
> index 230ad59b462b..dad3bf4ebf63 100644
> --- a/Documentation/hwmon/index.rst
> +++ b/Documentation/hwmon/index.rst
> @@ -90,6 +90,7 @@ Hardware Monitoring Kernel Drivers
> lm95245
> lochnagar
> ltc2945
> + ltc2947
> ltc2978
> ltc2990
> ltc3815
> diff --git a/Documentation/hwmon/ltc2947.rst b/Documentation/hwmon/ltc2947.rst
> new file mode 100644
> index 000000000000..419fc84fe934
> --- /dev/null
> +++ b/Documentation/hwmon/ltc2947.rst
> @@ -0,0 +1,100 @@
> +Kernel drivers ltc2947-i2c and ltc2947-spi
> +==========================================
> +
> +Supported chips:
> +
> + * Analog Devices LTC2947
> +
> + Prefix: 'ltc2947'
> +
> + Addresses scanned: -
> +
> + Datasheet:
> +
> + https://www.analog.com/media/en/technical-documentation/data-sheets/LTC2947.pdf
> +
> +Author: Nuno Sá <nuno.sa@analog.com>
> +
> +Description
> +___________
> +
> +The LTC2947 is a high precision power and energy monitor that measures current,
> +voltage, power, temperature, charge and energy. The device supports both SPI
> +and I2C depending on the chip configuration.
> +The device also measures accumulated quantities as energy. It has two banks of
> +register's to read/set energy related values. These banks can be configured
> +independently to have setups like: energy1 accumulates always and enrgy2 only
> +accumulates if current is positive (to check battery charging efficiency for
> +example). The device also supports a GPIO pin that can be configured as output
> +to control a fan as a function of measured temperature. Then, the GPIO becomes
> +active as soon as a temperature reading is higher than a defined threshold. The
> +temp2 channel is used to control this thresholds and to read the respective
> +alarms.
> +
> +Sysfs entries
> +_____________
> +
> +The following attributes are supported. Limits are read-write, reset_history
> +is write-only and all the other attributes are read-only.
> +
> +======================= ==========================================
> +in0_input VP-VM voltage (mV).
> +in0_min Undervoltage threshold
> +in0_max Overvoltage threshold
> +in0_lowest Lowest measured voltage
> +in0_highest Highest measured voltage
> +in0_reset_history Write 1 to reset in1 history
> +in0_min_alarm Undervoltage alarm
> +in0_max_alarm Overvoltage alarm
> +in0_label Channel label (VP-VM)
> +
> +in1_input DVCC voltage (mV)
> +in1_min Undervoltage threshold
> +in1_max Overvoltage threshold
> +in1_lowest Lowest measured voltage
> +in1_highest Highest measured voltage
> +in1_reset_history Write 1 to reset in2 history
> +in1_min_alarm Undervoltage alarm
> +in1_max_alarm Overvoltage alarm
> +in1_label Channel label (DVCC)
> +
> +curr1_input IP-IM Sense current (mA)
> +curr1_min Undercurrent threshold
> +curr1_max Overcurrent threshold
> +curr1_lowest Lowest measured current
> +curr1_highest Highest measured current
> +curr1_reset_history Write 1 to reset curr1 history
> +curr1_min_alarm Undercurrent alarm
> +curr1_max_alarm Overcurrent alarm
> +curr1_label Channel label (IP-IM)
> +
> +power1_input Power (in uW)
> +power1_min Low power threshold
> +power1_max High power threshold
> +power1_input_lowest Historical minimum power use
> +power1_input_highest Historical maximum power use
> +power1_reset_history Write 1 to reset power1 history
> +power1_min_alarm Low power alarm
> +power1_max_alarm High power alarm
> +power1_label Channel label (Power)
> +
> +temp1_input Chip Temperature (in milliC)
> +temp1_min Low temperature threshold
> +temp1_max High temperature threshold
> +temp1_input_lowest Historical minimum temperature use
> +temp1_input_highest Historical maximum temperature use
> +temp1_reset_history Write 1 to reset temp1 history
> +temp1_min_alarm Low temperature alarm
> +temp1_max_alarm High temperature alarm
> +temp1_label Channel label (Ambient)
> +
> +temp2_min Low temperature threshold for fan control
> +temp2_max High temperature threshold for fan control
> +temp2_min_alarm Low temperature fan control alarm
> +temp2_max_alarm High temperature fan control alarm
> +temp2_label Channel label (TEMPFAN)
> +
> +energy1_input Measured energy over time (in microJoule)
> +
> +energy2_input Measured energy over time (in microJoule)
> +======================= ==========================================
> diff --git a/MAINTAINERS b/MAINTAINERS
> index a69e6db80c79..318332b6a411 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -9629,6 +9629,16 @@ S: Maintained
> F: Documentation/hwmon/ltc4261.rst
> F: drivers/hwmon/ltc4261.c
>
> +LTC2947 HARDWARE MONITOR DRIVER
> +M: Nuno Sá <nuno.sa@analog.com>
> +W: http://ez.analog.com/community/linux-device-drivers
> +L: linux-hwmon@vger.kernel.org
> +S: Supported
> +F: drivers/hwmon/ltc2947-core.c
> +F: drivers/hwmon/ltc2947-spi.c
> +F: drivers/hwmon/ltc2947-i2c.c
> +F: drivers/hwmon/ltc2947.h
> +
> LTC4306 I2C MULTIPLEXER DRIVER
> M: Michael Hennerich <michael.hennerich@analog.com>
> W: http://ez.analog.com/community/linux-device-drivers
> diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
> index 7b6c4025b827..8c102ea2938b 100644
> --- a/drivers/hwmon/Kconfig
> +++ b/drivers/hwmon/Kconfig
> @@ -726,6 +726,33 @@ config SENSORS_LTC2945
> This driver can also be built as a module. If so, the module will
> be called ltc2945.
>
> +config SENSORS_LTC2947
> + tristate
> +
> +config SENSORS_LTC2947_I2C
> + tristate "Analog Devices LTC2947 High Precision Power and Energy Monitor over I2C"
> + depends on I2C
> + select REGMAP_I2C
> + select SENSORS_LTC2947
> + help
> + If you say yes here you get support for Linear Technology LTC2947
> + I2C High Precision Power and Energy Monitor
> +
> + This driver can also be built as a module. If so, the module will
> + be called ltc2947-i2c.
> +
> +config SENSORS_LTC2947_SPI
> + tristate "Analog Devices LTC2947 High Precision Power and Energy Monitor over SPI"
> + depends on SPI_MASTER
> + select REGMAP_SPI
> + select SENSORS_LTC2947
> + help
> + If you say yes here you get support for Linear Technology LTC2947
> + SPI High Precision Power and Energy Monitor
> +
> + This driver can also be built as a module. If so, the module will
> + be called ltc2947-spi.
> +
> config SENSORS_LTC2990
> tristate "Linear Technology LTC2990"
> depends on I2C
> diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
> index 40c036ea45e6..e416cfded0c4 100644
> --- a/drivers/hwmon/Makefile
> +++ b/drivers/hwmon/Makefile
> @@ -106,6 +106,9 @@ obj-$(CONFIG_SENSORS_LM95234) += lm95234.o
> obj-$(CONFIG_SENSORS_LM95241) += lm95241.o
> obj-$(CONFIG_SENSORS_LM95245) += lm95245.o
> obj-$(CONFIG_SENSORS_LTC2945) += ltc2945.o
> +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_LTC4151) += ltc4151.o
> obj-$(CONFIG_SENSORS_LTC4215) += ltc4215.o
> diff --git a/drivers/hwmon/ltc2947-core.c b/drivers/hwmon/ltc2947-core.c
> new file mode 100644
> index 000000000000..ce11acfbd2a8
> --- /dev/null
> +++ b/drivers/hwmon/ltc2947-core.c
> @@ -0,0 +1,1184 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Analog Devices LTC2947 high precision power and energy monitor
> + *
> + * Copyright 2019 Analog Devices Inc.
> + */
> +#include <linux/bitfield.h>
> +#include <linux/bits.h>
> +#include <linux/clk.h>
> +#include <linux/device.h>
> +#include <linux/hwmon.h>
> +#include <linux/hwmon-sysfs.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/regmap.h>
> +
> +#include "ltc2947.h"
> +
> +/* register's */
> +#define LTC2947_REG_PAGE_CTRL 0xFF
> +#define LTC2947_REG_CTRL 0xF0
> +#define LTC2947_REG_TBCTL 0xE9
> +#define LTC2947_CONT_MODE_MASK BIT(3)
> +#define LTC2947_CONT_MODE(x) FIELD_PREP(LTC2947_CONT_MODE_MASK, x)
> +#define LTC2947_PRE_MASK GENMASK(2, 0)
> +#define LTC2947_PRE(x) FIELD_PREP(LTC2947_PRE_MASK, x)
> +#define LTC2947_DIV_MASK GENMASK(7, 3)
> +#define LTC2947_DIV(x) FIELD_PREP(LTC2947_DIV_MASK, x)
> +#define LTC2947_SHUTDOWN_MASK BIT(0)
> +#define LTC2947_REG_ACCUM_POL 0xE1
> +#define LTC2947_ACCUM_POL_1_MASK GENMASK(1, 0)
> +#define LTC2947_ACCUM_POL_1(x) FIELD_PREP(LTC2947_ACCUM_POL_1_MASK, x)
> +#define LTC2947_ACCUM_POL_2_MASK GENMASK(3, 2)
> +#define LTC2947_ACCUM_POL_2(x) FIELD_PREP(LTC2947_ACCUM_POL_2_MASK, x)
> +#define LTC2947_REG_ACCUM_DEADBAND 0xE4
> +#define LTC2947_REG_GPIOSTATCTL 0x67
> +#define LTC2947_GPIO_EN_MASK BIT(0)
> +#define LTC2947_GPIO_EN(x) FIELD_PREP(LTC2947_GPIO_EN_MASK, x)
> +#define LTC2947_GPIO_FAN_EN_MASK BIT(6)
> +#define LTC2947_GPIO_FAN_EN(x) FIELD_PREP(LTC2947_GPIO_FAN_EN_MASK, x)
> +#define LTC2947_GPIO_FAN_POL_MASK BIT(7)
> +#define LTC2947_GPIO_FAN_POL(x) FIELD_PREP(LTC2947_GPIO_FAN_POL_MASK, x)
> +#define LTC2947_REG_GPIO_ACCUM 0xE3
> +/* 200Khz */
> +#define LTC2947_CLK_MIN 200000
> +/* 25Mhz */
> +#define LTC2947_CLK_MAX 25000000
> +#define PAGE0 0
> +#define PAGE1 1
> +/* Voltage registers */
> +#define LTC2947_REG_VOLTAGE 0xA0
> +#define LTC2947_REG_VOLTAGE_MAX 0x50
> +#define LTC2947_REG_VOLTAGE_MIN 0x52
> +#define LTC2947_REG_VOLTAGE_THRE_H 0x90
> +#define LTC2947_REG_VOLTAGE_THRE_L 0x92
> +#define LTC2947_REG_DVCC 0xA4
> +#define LTC2947_REG_DVCC_MAX 0x58
> +#define LTC2947_REG_DVCC_MIN 0x5A
> +#define LTC2947_REG_DVCC_THRE_H 0x98
> +#define LTC2947_REG_DVCC_THRE_L 0x9A
> +#define LTC2947_VOLTAGE_GEN_CHAN 0
> +#define LTC2947_VOLTAGE_DVCC_CHAN 1
> +/* in mV */
> +#define VOLTAGE_MAX 15500
> +#define VOLTAGE_MIN -300
> +#define VDVCC_MAX 15000
> +#define VDVCC_MIN 4750
> +/* Current registers */
> +#define LTC2947_REG_CURRENT 0x90
> +#define LTC2947_REG_CURRENT_MAX 0x40
> +#define LTC2947_REG_CURRENT_MIN 0x42
> +#define LTC2947_REG_CURRENT_THRE_H 0x80
> +#define LTC2947_REG_CURRENT_THRE_L 0x82
> +/* in mA */
> +#define CURRENT_MAX 30000
> +#define CURRENT_MIN -30000
> +/* Power registers */
> +#define LTC2947_REG_POWER 0x93
> +#define LTC2947_REG_POWER_MAX 0x44
> +#define LTC2947_REG_POWER_MIN 0x46
> +#define LTC2947_REG_POWER_THRE_H 0x84
> +#define LTC2947_REG_POWER_THRE_L 0x86
> +/* in uW */
> +#define POWER_MAX 450000000
> +#define POWER_MIN -450000000
> +/* Temperature registers */
> +#define LTC2947_REG_TEMP 0xA2
> +#define LTC2947_REG_TEMP_MAX 0x54
> +#define LTC2947_REG_TEMP_MIN 0x56
> +#define LTC2947_REG_TEMP_THRE_H 0x94
> +#define LTC2947_REG_TEMP_THRE_L 0x96
> +#define LTC2947_REG_TEMP_FAN_THRE_H 0x9C
> +#define LTC2947_REG_TEMP_FAN_THRE_L 0x9E
> +#define LTC2947_TEMP_FAN_CHAN 1
> +/* in millidegress Celsius */
> +#define TEMP_MAX 85000
> +#define TEMP_MIN -40000
> +/* Energy registers */
> +#define LTC2947_REG_ENERGY1 0x06
> +#define LTC2947_REG_ENERGY2 0x16
> +/* Status/Alarm/Overflow registers */
> +#define LTC2947_REG_STATUS 0x80
> +#define LTC2947_REG_STATVT 0x81
> +#define LTC2947_REG_STATIP 0x82
> +#define LTC2947_REG_STATVDVCC 0x87
> +
> +#define LTC2947_ALERTS_SIZE (LTC2947_REG_STATVDVCC - LTC2947_REG_STATUS)
> +#define LTC2947_MAX_VOLTAGE_MASK BIT(0)
> +#define LTC2947_MIN_VOLTAGE_MASK BIT(1)
> +#define LTC2947_MAX_CURRENT_MASK BIT(0)
> +#define LTC2947_MIN_CURRENT_MASK BIT(1)
> +#define LTC2947_MAX_POWER_MASK BIT(2)
> +#define LTC2947_MIN_POWER_MASK BIT(3)
> +#define LTC2947_MAX_TEMP_MASK BIT(2)
> +#define LTC2947_MIN_TEMP_MASK BIT(3)
> +#define LTC2947_MAX_TEMP_FAN_MASK BIT(4)
> +#define LTC2947_MIN_TEMP_FAN_MASK BIT(5)
> +
> +struct ltc2947_data {
> + struct regmap *map;
> + struct device *dev;
> + /*
> + * The mutex is needed because the device has 2 memory pages. When
> + * reading/writing the correct page needs to be set so that, the
> + * complete sequence select_page->read/write needs to be protected.
> + */
> + struct mutex lock;
> + u32 lsb_energy;
> + bool gpio_out;
> +};
> +
> +static int __ltc2947_val_read16(const struct ltc2947_data *st, const u8 reg,
> + u64 *val)
> +{
> + __be16 __val = 0;
> + int ret;
> +
> + ret = regmap_bulk_read(st->map, reg, &__val, 2);
> + if (ret)
> + return ret;
> +
> + *val = be16_to_cpu(__val);
> +
> + return 0;
> +}
> +
> +static int __ltc2947_val_read24(const struct ltc2947_data *st, const u8 reg,
> + u64 *val)
> +{
> + __be32 __val = 0;
> + int ret;
> +
> + ret = regmap_bulk_read(st->map, reg, &__val, 3);
> + if (ret)
> + return ret;
> +
> + *val = be32_to_cpu(__val) >> 8;
> +
> + return 0;
> +}
> +
> +static int __ltc2947_val_read64(const struct ltc2947_data *st, const u8 reg,
> + u64 *val)
> +{
> + __be64 __val = 0;
> + int ret;
> +
> + ret = regmap_bulk_read(st->map, reg, &__val, 6);
> + if (ret)
> + return ret;
> +
> + *val = be64_to_cpu(__val) >> 16;
> +
> + return 0;
> +}
> +
> +static int ltc2947_val_read(struct ltc2947_data *st, const u8 reg,
> + const u8 page, const size_t size, s64 *val)
> +{
> + int ret;
> + u64 __val = 0;
> +
> + mutex_lock(&st->lock);
> +
> + ret = regmap_write(st->map, LTC2947_REG_PAGE_CTRL, page);
> + if (ret) {
> + mutex_unlock(&st->lock);
> + return ret;
> + }
> +
> + dev_dbg(st->dev, "Read val, reg:%02X, p:%d sz:%zu\n", reg, page,
> + size);
> +
> + switch (size) {
> + case 2:
> + ret = __ltc2947_val_read16(st, reg, &__val);
> + break;
> + case 3:
> + ret = __ltc2947_val_read24(st, reg, &__val);
> + break;
> + case 6:
> + ret = __ltc2947_val_read64(st, reg, &__val);
> + break;
> + default:
> + ret = -EINVAL;
> + break;
> + }
> +
> + mutex_unlock(&st->lock);
> +
> + if (ret)
> + return ret;
> +
> + *val = sign_extend64(__val, (8 * size) - 1);
> +
> + dev_dbg(st->dev, "Got s:%lld, u:%016llX\n", *val, __val);
> +
> + return 0;
> +}
> +
> +static int __ltc2947_val_write64(const struct ltc2947_data *st, const u8 reg,
> + const u64 val)
> +{
> + __be64 __val;
> +
> + __val = cpu_to_be64(val << 16);
> + return regmap_bulk_write(st->map, reg, &__val, 6);
> +}
> +
> +static int __ltc2947_val_write16(const struct ltc2947_data *st, const u8 reg,
> + const u16 val)
> +{
> + __be16 __val;
> +
> + __val = cpu_to_be16(val);
> + return regmap_bulk_write(st->map, reg, &__val, 2);
> +}
> +
> +static int ltc2947_val_write(struct ltc2947_data *st, const u8 reg,
> + const u8 page, const size_t size, const u64 val)
> +{
> + int ret;
> +
> + mutex_lock(&st->lock);
> + /* set device on correct page */
> + ret = regmap_write(st->map, LTC2947_REG_PAGE_CTRL, page);
> + if (ret) {
> + mutex_unlock(&st->lock);
> + return ret;
> + }
> +
> + dev_dbg(st->dev, "Write val, r:%02X, p:%d, sz:%zu, val:%016llX\n",
> + reg, page, size, val);
> +
> + switch (size) {
> + case 2:
> + ret = __ltc2947_val_write16(st, reg, val);
> + break;
> + case 6:
> + ret = __ltc2947_val_write64(st, reg, val);
> + break;
> + default:
> + ret = -EINVAL;
> + break;
> + }
> +
> + mutex_unlock(&st->lock);
> +
> + return ret;
> +}
> +
> +static int ltc2947_reset_history(struct ltc2947_data *st, const u8 reg_h,
> + const u8 reg_l)
> +{
> + int ret;
> + /*
> + * let's reset the tracking register's. Tracking register's have all
> + * 2 bytes size
> + */
> + ret = ltc2947_val_write(st, reg_h, PAGE0, 2, 0x8000U);
> + if (ret)
> + return ret;
> +
> + return ltc2947_val_write(st, reg_l, PAGE0, 2, 0x7FFFU);
> +}
> +
> +static int ltc2947_alarm_read(struct ltc2947_data *st, const u8 reg,
> + const u32 mask, long *val)
> +{
> + u8 offset = reg - LTC2947_REG_STATUS;
> + /* +1 to include status reg */
> + char alarms[LTC2947_ALERTS_SIZE + 1];
> + int ret = 0;
> +
> + memset(alarms, 0, sizeof(alarms));
> +
> + mutex_lock(&st->lock);
> +
> + ret = regmap_write(st->map, LTC2947_REG_PAGE_CTRL, PAGE0);
> + if (ret)
> + goto unlock;
> +
> + dev_dbg(st->dev, "Read alarm, reg:%02X, mask:%02X\n", reg, mask);
> + /*
> + * As stated in the datasheet, when Threshold and Overflow registers
> + * are used, the status and all alert registers must be read in one
> + * multi-byte transaction.
> + */
> + ret = regmap_bulk_read(st->map, LTC2947_REG_STATUS, alarms,
> + sizeof(alarms));
> + if (ret)
> + goto unlock;
> +
> + /* get the alarm */
> + *val = !!(alarms[offset] & mask);
> +unlock:
> + mutex_unlock(&st->lock);
> + return ret;
> +}
> +
> +static ssize_t ltc2947_show_value(struct device *dev,
> + struct device_attribute *da, char *buf)
> +{
> + struct ltc2947_data *st = dev_get_drvdata(dev);
> + struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
> + int ret;
> + s64 val = 0;
> +
> + switch (attr->index) {
> + case LTC2947_REG_ENERGY1:
> + case LTC2947_REG_ENERGY2:
> + ret = ltc2947_val_read(st, attr->index, PAGE0, 6, &val);
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + if (ret)
> + return ret;
> +
> + /* value in microJoule. st->lsb_energy was multiplied by 10E9 */
> + val = div_s64(val * st->lsb_energy, 1000);
> +
> + return sprintf(buf, "%lld\n", val);
> +}
> +
> +static int ltc2947_read_temp(struct device *dev, const u32 attr, long *val,
> + const int channel)
> +{
> + int ret;
> + struct ltc2947_data *st = dev_get_drvdata(dev);
> + s64 __val = 0;
> +
> + if (channel < 0 || channel > LTC2947_TEMP_FAN_CHAN) {
> + dev_err(st->dev, "Invalid chan%d for temperature", channel);
> + return -EINVAL;
> + }
The above range check is unnecessary. I removed it.
> +
> + switch (attr) {
> + case hwmon_temp_input:
> + ret = ltc2947_val_read(st, LTC2947_REG_TEMP, PAGE0, 2, &__val);
> + break;
> + case hwmon_temp_highest:
> + ret = ltc2947_val_read(st, LTC2947_REG_TEMP_MAX, PAGE0, 2,
> + &__val);
> + break;
> + case hwmon_temp_lowest:
> + ret = ltc2947_val_read(st, LTC2947_REG_TEMP_MIN, PAGE0, 2,
> + &__val);
> + break;
> + case hwmon_temp_max_alarm:
> + if (channel == LTC2947_TEMP_FAN_CHAN)
> + return ltc2947_alarm_read(st, LTC2947_REG_STATVT,
> + LTC2947_MAX_TEMP_FAN_MASK,
> + val);
> +
> + return ltc2947_alarm_read(st, LTC2947_REG_STATVT,
> + LTC2947_MAX_TEMP_MASK, val);
> + case hwmon_temp_min_alarm:
> + if (channel == LTC2947_TEMP_FAN_CHAN)
> + return ltc2947_alarm_read(st, LTC2947_REG_STATVT,
> + LTC2947_MIN_TEMP_FAN_MASK,
> + val);
> +
> + return ltc2947_alarm_read(st, LTC2947_REG_STATVT,
> + LTC2947_MIN_TEMP_MASK, val);
> + case hwmon_temp_max:
> + if (channel == LTC2947_TEMP_FAN_CHAN)
> + ret = ltc2947_val_read(st, LTC2947_REG_TEMP_FAN_THRE_H,
> + PAGE1, 2, &__val);
> + else
> + ret = ltc2947_val_read(st, LTC2947_REG_TEMP_THRE_H,
> + PAGE1, 2, &__val);
> + break;
> + case hwmon_temp_min:
> + if (channel == LTC2947_TEMP_FAN_CHAN)
> + ret = ltc2947_val_read(st, LTC2947_REG_TEMP_FAN_THRE_L,
> + PAGE1, 2, &__val);
> + else
> + ret = ltc2947_val_read(st, LTC2947_REG_TEMP_THRE_L,
> + PAGE1, 2, &__val);
> + break;
> + default:
> + return -ENOTSUPP;
> + }
> +
> + if (ret)
> + return ret;
> +
> + /* in milidegrees celcius, temp is given by: */
> + *val = (__val * 204) + 550;
> +
> + return 0;
> +}
> +
> +static int ltc2947_read_power(struct device *dev, const u32 attr, long *val)
> +{
> + struct ltc2947_data *st = dev_get_drvdata(dev);
> + int ret;
> + u32 lsb = 200000; /* in uW */
> + s64 __val = 0;
> +
> + switch (attr) {
> + case hwmon_power_input:
> + ret = ltc2947_val_read(st, LTC2947_REG_POWER, PAGE0, 3, &__val);
> + lsb = 50000;
> + break;
> + case hwmon_power_input_highest:
> + ret = ltc2947_val_read(st, LTC2947_REG_POWER_MAX, PAGE0, 2,
> + &__val);
> + break;
> + case hwmon_power_input_lowest:
> + ret = ltc2947_val_read(st, LTC2947_REG_POWER_MIN, PAGE0, 2,
> + &__val);
> + break;
> + case hwmon_power_max_alarm:
> + return ltc2947_alarm_read(st, LTC2947_REG_STATIP,
> + LTC2947_MAX_POWER_MASK, val);
> + case hwmon_power_min_alarm:
> + return ltc2947_alarm_read(st, LTC2947_REG_STATIP,
> + LTC2947_MIN_POWER_MASK, val);
> + case hwmon_power_max:
> + ret = ltc2947_val_read(st, LTC2947_REG_POWER_THRE_H, PAGE1, 2,
> + &__val);
> + break;
> + case hwmon_power_min:
> + ret = ltc2947_val_read(st, LTC2947_REG_POWER_THRE_L, PAGE1, 2,
> + &__val);
> + break;
> + default:
> + return -ENOTSUPP;
> + }
> +
> + if (ret)
> + return ret;
> +
> + *val = __val * lsb;
> +
> + return 0;
> +}
> +
> +static int ltc2947_read_curr(struct device *dev, const u32 attr, long *val)
> +{
> + struct ltc2947_data *st = dev_get_drvdata(dev);
> + int ret;
> + u8 lsb = 12; /* in mA */
> + s64 __val = 0;
> +
> + switch (attr) {
> + case hwmon_curr_input:
> + ret = ltc2947_val_read(st, LTC2947_REG_CURRENT, PAGE0, 3,
> + &__val);
> + lsb = 3;
> + break;
> + case hwmon_curr_highest:
> + ret = ltc2947_val_read(st, LTC2947_REG_CURRENT_MAX, PAGE0, 2,
> + &__val);
> + break;
> + case hwmon_curr_lowest:
> + ret = ltc2947_val_read(st, LTC2947_REG_CURRENT_MIN, PAGE0, 2,
> + &__val);
> + break;
> + case hwmon_curr_max_alarm:
> + return ltc2947_alarm_read(st, LTC2947_REG_STATIP,
> + LTC2947_MAX_CURRENT_MASK, val);
> + case hwmon_curr_min_alarm:
> + return ltc2947_alarm_read(st, LTC2947_REG_STATIP,
> + LTC2947_MIN_CURRENT_MASK, val);
> + case hwmon_curr_max:
> + ret = ltc2947_val_read(st, LTC2947_REG_CURRENT_THRE_H, PAGE1, 2,
> + &__val);
> + break;
> + case hwmon_curr_min:
> + ret = ltc2947_val_read(st, LTC2947_REG_CURRENT_THRE_L, PAGE1, 2,
> + &__val);
> + break;
> + default:
> + return -ENOTSUPP;
> + }
> +
> + if (ret)
> + return ret;
> +
> + *val = __val * lsb;
> +
> + return 0;
> +}
> +
> +static int ltc2947_read_in(struct device *dev, const u32 attr, long *val,
> + const int channel)
> +{
> + struct ltc2947_data *st = dev_get_drvdata(dev);
> + int ret;
> + u8 lsb = 2; /* in mV */
> + s64 __val = 0;
> +
> + if (channel < 0 || channel > LTC2947_VOLTAGE_DVCC_CHAN) {
> + dev_err(st->dev, "Invalid chan%d for voltage", channel);
> + return -EINVAL;
> + }
> +
> + switch (attr) {
> + case hwmon_in_input:
> + if (channel == LTC2947_VOLTAGE_DVCC_CHAN) {
> + ret = ltc2947_val_read(st, LTC2947_REG_DVCC, PAGE0, 2,
> + &__val);
> + lsb = 145;
> + } else {
> + ret = ltc2947_val_read(st, LTC2947_REG_VOLTAGE, PAGE0,
> + 2, &__val);
> + }
> + break;
> + case hwmon_in_highest:
> + if (channel == LTC2947_VOLTAGE_DVCC_CHAN) {
> + ret = ltc2947_val_read(st, LTC2947_REG_DVCC_MAX, PAGE0,
> + 2, &__val);
> + lsb = 145;
> + } else {
> + ret = ltc2947_val_read(st, LTC2947_REG_VOLTAGE_MAX,
> + PAGE0, 2, &__val);
> + }
> + break;
> + case hwmon_in_lowest:
> + if (channel == LTC2947_VOLTAGE_DVCC_CHAN) {
> + ret = ltc2947_val_read(st, LTC2947_REG_DVCC_MIN, PAGE0,
> + 2, &__val);
> + lsb = 145;
> + } else {
> + ret = ltc2947_val_read(st, LTC2947_REG_VOLTAGE_MIN,
> + PAGE0, 2, &__val);
> + }
> + break;
> + case hwmon_in_max_alarm:
> + if (channel == LTC2947_VOLTAGE_DVCC_CHAN)
> + return ltc2947_alarm_read(st, LTC2947_REG_STATVDVCC,
> + LTC2947_MAX_VOLTAGE_MASK,
> + val);
> +
> + return ltc2947_alarm_read(st, LTC2947_REG_STATVT,
> + LTC2947_MAX_VOLTAGE_MASK, val);
> + case hwmon_in_min_alarm:
> + if (channel == LTC2947_VOLTAGE_DVCC_CHAN)
> + return ltc2947_alarm_read(st, LTC2947_REG_STATVDVCC,
> + LTC2947_MIN_VOLTAGE_MASK,
> + val);
> +
> + return ltc2947_alarm_read(st, LTC2947_REG_STATVT,
> + LTC2947_MIN_VOLTAGE_MASK, val);
> + case hwmon_in_max:
> + if (channel == LTC2947_VOLTAGE_DVCC_CHAN) {
> + ret = ltc2947_val_read(st, LTC2947_REG_DVCC_THRE_H,
> + PAGE1, 2, &__val);
> + lsb = 145;
> + } else {
> + ret = ltc2947_val_read(st, LTC2947_REG_VOLTAGE_THRE_H,
> + PAGE1, 2, &__val);
> + }
> + break;
> + case hwmon_in_min:
> + if (channel == LTC2947_VOLTAGE_DVCC_CHAN) {
> + ret = ltc2947_val_read(st, LTC2947_REG_DVCC_THRE_L,
> + PAGE1, 2, &__val);
> + lsb = 145;
> + } else {
> + ret = ltc2947_val_read(st, LTC2947_REG_VOLTAGE_THRE_L,
> + PAGE1, 2, &__val);
> + }
> + break;
> + default:
> + return -ENOTSUPP;
> + }
> +
> + if (ret)
> + return ret;
> +
> + *val = __val * lsb;
> +
> + return 0;
> +}
> +
> +static int ltc2947_read(struct device *dev, enum hwmon_sensor_types type,
> + u32 attr, int channel, long *val)
> +{
> + switch (type) {
> + case hwmon_in:
> + return ltc2947_read_in(dev, attr, val, channel);
> + case hwmon_curr:
> + return ltc2947_read_curr(dev, attr, val);
> + case hwmon_power:
> + return ltc2947_read_power(dev, attr, val);
> + case hwmon_temp:
> + return ltc2947_read_temp(dev, attr, val, channel);
> + default:
> + return -ENOTSUPP;
> + }
> +}
> +
> +static int ltc2947_write_temp(struct device *dev, const u32 attr,
> + long val, const int channel)
> +{
> + struct ltc2947_data *st = dev_get_drvdata(dev);
> +
> + if (channel < 0 || channel > LTC2947_TEMP_FAN_CHAN) {
> + dev_err(st->dev, "Invalid chan%d for temperature", channel);
> + return -EINVAL;
> + }
> +
> + switch (attr) {
> + case hwmon_temp_reset_history:
> + if (val != 1)
> + return -EINVAL;
> + return ltc2947_reset_history(st, LTC2947_REG_TEMP_MAX,
> + LTC2947_REG_TEMP_MIN);
> + case hwmon_temp_max:
> + val = clamp_val(val, TEMP_MIN, TEMP_MAX);
> + if (channel == LTC2947_TEMP_FAN_CHAN) {
> + if (!st->gpio_out)
> + return -ENOTSUPP;
> +
> + return ltc2947_val_write(st,
> + LTC2947_REG_TEMP_FAN_THRE_H, PAGE1, 2,
> + DIV_ROUND_CLOSEST(val - 550, 204));
> + }
> +
> + return ltc2947_val_write(st, LTC2947_REG_TEMP_THRE_H, PAGE1, 2,
> + DIV_ROUND_CLOSEST(val - 550, 204));
> + case hwmon_temp_min:
> + val = clamp_val(val, TEMP_MIN, TEMP_MAX);
> + if (channel == LTC2947_TEMP_FAN_CHAN) {
> + if (!st->gpio_out)
> + return -ENOTSUPP;
> +
> + return ltc2947_val_write(st,
> + LTC2947_REG_TEMP_FAN_THRE_L, PAGE1, 2,
> + DIV_ROUND_CLOSEST(val - 550, 204));
> + }
> +
> + return ltc2947_val_write(st, LTC2947_REG_TEMP_THRE_L, PAGE1, 2,
> + DIV_ROUND_CLOSEST(val - 550, 204));
> + default:
> + return -ENOTSUPP;
> + }
> +}
> +
> +static int ltc2947_write_power(struct device *dev, const u32 attr,
> + long val)
> +{
> + struct ltc2947_data *st = dev_get_drvdata(dev);
> +
> + switch (attr) {
> + case hwmon_power_reset_history:
> + if (val != 1)
> + return -EINVAL;
> + return ltc2947_reset_history(st, LTC2947_REG_POWER_MAX,
> + LTC2947_REG_POWER_MIN);
> + case hwmon_power_max:
> + val = clamp_val(val, POWER_MIN, POWER_MAX);
> + return ltc2947_val_write(st, LTC2947_REG_POWER_THRE_H, PAGE1, 2,
> + DIV_ROUND_CLOSEST(val, 200000));
> + case hwmon_power_min:
> + val = clamp_val(val, POWER_MIN, POWER_MAX);
> + return ltc2947_val_write(st, LTC2947_REG_POWER_THRE_L, PAGE1, 2,
> + DIV_ROUND_CLOSEST(val, 200000));
> + default:
> + return -ENOTSUPP;
> + }
> +}
> +
> +static int ltc2947_write_curr(struct device *dev, const u32 attr,
> + long val)
> +{
> + struct ltc2947_data *st = dev_get_drvdata(dev);
> +
> + switch (attr) {
> + case hwmon_curr_reset_history:
> + if (val != 1)
> + return -EINVAL;
> + return ltc2947_reset_history(st, LTC2947_REG_CURRENT_MAX,
> + LTC2947_REG_CURRENT_MIN);
> + case hwmon_curr_max:
> + val = clamp_val(val, CURRENT_MIN, CURRENT_MAX);
> + return ltc2947_val_write(st, LTC2947_REG_CURRENT_THRE_H, PAGE1,
> + 2, DIV_ROUND_CLOSEST(val, 12));
> + case hwmon_curr_min:
> + val = clamp_val(val, CURRENT_MIN, CURRENT_MAX);
> + return ltc2947_val_write(st, LTC2947_REG_CURRENT_THRE_L, PAGE1,
> + 2, DIV_ROUND_CLOSEST(val, 12));
> + default:
> + return -ENOTSUPP;
> + }
> +}
> +
> +static int ltc2947_write_in(struct device *dev, const u32 attr, long val,
> + const int channel)
> +{
> + struct ltc2947_data *st = dev_get_drvdata(dev);
> +
> + if (channel > LTC2947_VOLTAGE_DVCC_CHAN) {
> + dev_err(st->dev, "Invalid chan%d for voltage", channel);
> + return -EINVAL;
> + }
> +
> + switch (attr) {
> + case hwmon_in_reset_history:
> + if (val != 1)
> + return -EINVAL;
> +
> + if (channel == LTC2947_VOLTAGE_DVCC_CHAN)
> + return ltc2947_reset_history(st, LTC2947_REG_DVCC_MAX,
> + LTC2947_REG_DVCC_MIN);
> +
> + return ltc2947_reset_history(st, LTC2947_REG_VOLTAGE_MAX,
> + LTC2947_REG_VOLTAGE_MIN);
> + case hwmon_in_max:
> + if (channel == LTC2947_VOLTAGE_DVCC_CHAN) {
> + val = clamp_val(val, VDVCC_MIN, VDVCC_MAX);
> + return ltc2947_val_write(st, LTC2947_REG_DVCC_THRE_H,
> + PAGE1, 2,
> + DIV_ROUND_CLOSEST(val, 145));
> + }
> +
> + val = clamp_val(val, VOLTAGE_MIN, VOLTAGE_MAX);
> + return ltc2947_val_write(st, LTC2947_REG_VOLTAGE_THRE_H,
> + PAGE1, 2, DIV_ROUND_CLOSEST(val, 2));
> + case hwmon_in_min:
> + if (channel == LTC2947_VOLTAGE_DVCC_CHAN) {
> + val = clamp_val(val, VDVCC_MIN, VDVCC_MAX);
> + return ltc2947_val_write(st, LTC2947_REG_DVCC_THRE_L,
> + PAGE1, 2,
> + DIV_ROUND_CLOSEST(val, 145));
> + }
> +
> + val = clamp_val(val, VOLTAGE_MIN, VOLTAGE_MAX);
> + return ltc2947_val_write(st, LTC2947_REG_VOLTAGE_THRE_L,
> + PAGE1, 2, DIV_ROUND_CLOSEST(val, 2));
> + default:
> + return -ENOTSUPP;
> + }
> +}
> +
> +static int ltc2947_write(struct device *dev,
> + enum hwmon_sensor_types type,
> + u32 attr, int channel, long val)
> +{
> + switch (type) {
> + case hwmon_in:
> + return ltc2947_write_in(dev, attr, val, channel);
> + case hwmon_curr:
> + return ltc2947_write_curr(dev, attr, val);
> + case hwmon_power:
> + return ltc2947_write_power(dev, attr, val);
> + case hwmon_temp:
> + return ltc2947_write_temp(dev, attr, val, channel);
> + default:
> + return -ENOTSUPP;
> + }
> +}
> +
> +static int ltc2947_read_labels(struct device *dev,
> + enum hwmon_sensor_types type,
> + u32 attr, int channel, const char **str)
> +{
> + switch (type) {
> + case hwmon_in:
> + if (channel == LTC2947_VOLTAGE_DVCC_CHAN)
> + *str = "DVCC";
> + else
> + *str = "VP-VM";
> + return 0;
> + case hwmon_curr:
> + *str = "IP-IM";
> + return 0;
> + case hwmon_temp:
> + if (channel == LTC2947_TEMP_FAN_CHAN)
> + *str = "TEMPFAN";
> + else
> + *str = "Ambient";
> + return 0;
> + case hwmon_power:
> + *str = "Power";
> + return 0;
> + default:
> + return -ENOTSUPP;
> + }
> +}
> +
> +static int ltc2947_in_is_visible(const u32 attr)
> +{
> + switch (attr) {
> + case hwmon_in_input:
> + case hwmon_in_highest:
> + case hwmon_in_lowest:
> + case hwmon_in_max_alarm:
> + case hwmon_in_min_alarm:
> + case hwmon_in_label:
> + return 0444;
> + case hwmon_in_reset_history:
> + return 0200;
> + case hwmon_in_max:
> + case hwmon_in_min:
> + return 0644;
> + default:
> + return 0;
> + }
> +}
> +
> +static int ltc2947_curr_is_visible(const u32 attr)
> +{
> + switch (attr) {
> + case hwmon_curr_input:
> + case hwmon_curr_highest:
> + case hwmon_curr_lowest:
> + case hwmon_curr_max_alarm:
> + case hwmon_curr_min_alarm:
> + case hwmon_curr_label:
> + return 0444;
> + case hwmon_curr_reset_history:
> + return 0200;
> + case hwmon_curr_max:
> + case hwmon_curr_min:
> + return 0644;
> + default:
> + return 0;
> + }
> +}
> +
> +static int ltc2947_power_is_visible(const u32 attr)
> +{
> + switch (attr) {
> + case hwmon_power_input:
> + case hwmon_power_input_highest:
> + case hwmon_power_input_lowest:
> + case hwmon_power_label:
> + case hwmon_power_max_alarm:
> + case hwmon_power_min_alarm:
> + return 0444;
> + case hwmon_power_reset_history:
> + return 0200;
> + case hwmon_power_max:
> + case hwmon_power_min:
> + return 0644;
> + default:
> + return 0;
> + }
> +}
> +
> +static int ltc2947_temp_is_visible(const u32 attr)
> +{
> + switch (attr) {
> + case hwmon_temp_input:
> + case hwmon_temp_highest:
> + case hwmon_temp_lowest:
> + case hwmon_temp_max_alarm:
> + case hwmon_temp_min_alarm:
> + case hwmon_temp_label:
> + return 0444;
> + case hwmon_temp_reset_history:
> + return 0200;
> + case hwmon_temp_max:
> + case hwmon_temp_min:
> + return 0644;
> + default:
> + return 0;
> + }
> +}
> +
> +static umode_t ltc2947_is_visible(const void *data,
> + enum hwmon_sensor_types type,
> + u32 attr, int channel)
> +{
> + switch (type) {
> + case hwmon_in:
> + return ltc2947_in_is_visible(attr);
> + case hwmon_curr:
> + return ltc2947_curr_is_visible(attr);
> + case hwmon_power:
> + return ltc2947_power_is_visible(attr);
> + case hwmon_temp:
> + return ltc2947_temp_is_visible(attr);
> + default:
> + return 0;
> + }
> +}
> +
> +static const struct hwmon_channel_info *ltc2947_info[] = {
> + HWMON_CHANNEL_INFO(in,
> + HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
> + HWMON_I_MAX | HWMON_I_MIN | HWMON_I_RESET_HISTORY |
> + HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM |
> + HWMON_I_LABEL,
> + HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
> + HWMON_I_MAX | HWMON_I_MIN | HWMON_I_RESET_HISTORY |
> + HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM |
> + HWMON_I_LABEL),
> + HWMON_CHANNEL_INFO(curr,
> + HWMON_C_INPUT | HWMON_C_LOWEST | HWMON_C_HIGHEST |
> + HWMON_C_MAX | HWMON_C_MIN | HWMON_C_RESET_HISTORY |
> + HWMON_C_MIN_ALARM | HWMON_C_MAX_ALARM |
> + HWMON_C_LABEL),
> + HWMON_CHANNEL_INFO(power,
> + HWMON_P_INPUT | HWMON_P_INPUT_LOWEST |
> + HWMON_P_INPUT_HIGHEST | HWMON_P_MAX | HWMON_P_MIN |
> + HWMON_P_RESET_HISTORY | HWMON_P_MAX_ALARM |
> + HWMON_P_MIN_ALARM | HWMON_P_LABEL),
> + HWMON_CHANNEL_INFO(temp,
> + HWMON_T_INPUT | HWMON_T_LOWEST | HWMON_T_HIGHEST |
> + HWMON_T_MAX | HWMON_T_MIN | HWMON_T_RESET_HISTORY |
> + HWMON_T_MIN_ALARM | HWMON_T_MAX_ALARM |
> + HWMON_T_LABEL,
> + HWMON_T_MAX_ALARM | HWMON_T_MIN_ALARM | HWMON_T_MAX |
> + HWMON_T_MIN | HWMON_T_LABEL),
> + NULL
> +};
> +
> +static const struct hwmon_ops ltc2947_hwmon_ops = {
> + .is_visible = ltc2947_is_visible,
> + .read = ltc2947_read,
> + .write = ltc2947_write,
> + .read_string = ltc2947_read_labels,
> +};
> +
> +static const struct hwmon_chip_info ltc2947_chip_info = {
> + .ops = <c2947_hwmon_ops,
> + .info = ltc2947_info,
> +};
> +
> +/* energy attributes are 6bytes wide so we need u64 */
> +static SENSOR_DEVICE_ATTR(energy1_input, 0444, ltc2947_show_value, NULL,
> + LTC2947_REG_ENERGY1);
> +static SENSOR_DEVICE_ATTR(energy2_input, 0444, ltc2947_show_value, NULL,
> + LTC2947_REG_ENERGY2);
> +
> +static struct attribute *ltc2947_attrs[] = {
> + &sensor_dev_attr_energy1_input.dev_attr.attr,
> + &sensor_dev_attr_energy2_input.dev_attr.attr,
> + NULL,
> +};
> +ATTRIBUTE_GROUPS(ltc2947);
> +
> +static void ltc2947_clk_disable(void *data)
> +{
> + struct clk *extclk = data;
> +
> + clk_disable_unprepare(extclk);
> +}
> +
> +static int ltc2947_setup(struct ltc2947_data *st)
> +{
> + int ret;
> + struct clk *extclk;
> + u32 dummy, deadband, pol;
> + u32 accum[2];
> +
> + /* clear status register by reading it */
> + ret = regmap_read(st->map, LTC2947_REG_STATUS, &dummy);
> + if (ret)
> + return ret;
> + /*
> + * Set max/min for power here since the default values x scale
> + * would overflow on 32bit arch
> + */
> + ret = ltc2947_val_write(st, LTC2947_REG_POWER_THRE_H, PAGE1, 2,
> + POWER_MAX / 200000);
> + if (ret)
> + return ret;
> +
> + ret = ltc2947_val_write(st, LTC2947_REG_POWER_THRE_L, PAGE1, 2,
> + POWER_MIN / 200000);
> + if (ret)
> + return ret;
> +
> + /* check external clock presence */
> + extclk = devm_clk_get(st->dev, NULL);
> + if (!IS_ERR(extclk)) {
> + unsigned long rate_hz;
> + u8 pre = 0, div, tbctl;
> + u64 aux;
> +
> + /* let's calculate and set the right valus in TBCTL */
> + rate_hz = clk_get_rate(extclk);
> + if (rate_hz < LTC2947_CLK_MIN || rate_hz > LTC2947_CLK_MAX) {
> + dev_err(st->dev, "Invalid rate:%lu for external clock",
> + rate_hz);
> + return -EINVAL;
> + }
> +
> + ret = clk_prepare_enable(extclk);
> + if (ret)
> + return ret;
> +
> + ret = devm_add_action_or_reset(st->dev, ltc2947_clk_disable,
> + extclk);
> + if (ret)
> + return ret;
> + /* as in table 1 of the datasheet */
> + if (rate_hz >= LTC2947_CLK_MIN && rate_hz <= 1000000)
> + pre = 0;
> + else if (rate_hz > 1000000 && rate_hz <= 2000000)
> + pre = 1;
> + else if (rate_hz > 2000000 && rate_hz <= 4000000)
> + pre = 2;
> + else if (rate_hz > 4000000 && rate_hz <= 8000000)
> + pre = 3;
> + else if (rate_hz > 8000000 && rate_hz <= 16000000)
> + pre = 4;
> + else if (rate_hz > 16000000 && rate_hz <= LTC2947_CLK_MAX)
> + pre = 5;
> + /*
> + * Div is given by:
> + * floor(fref / (2^PRE * 32768))
> + */
> + div = rate_hz / ((1 << pre) * 32768);
> + tbctl = LTC2947_PRE(pre) | LTC2947_DIV(div);
> +
> + ret = regmap_write(st->map, LTC2947_REG_TBCTL, tbctl);
> + if (ret)
> + return ret;
> + /*
> + * The energy lsb is given by (in W*s):
> + * 06416 * (1/fref) * 2^PRE * (DIV + 1)
> + * The value is multiplied by 10E9
> + */
> + aux = (div + 1) * ((1 << pre) * 641600000ULL);
> + st->lsb_energy = DIV_ROUND_CLOSEST_ULL(aux, rate_hz);
> + } else {
> + /* 19.89E-6 * 10E9 */
> + st->lsb_energy = 19890;
> + }
> + ret = of_property_read_u32_array(st->dev->of_node,
> + "adi,accumulator-ctl-pol", accum,
> + ARRAY_SIZE(accum));
> + if (!ret) {
> + u32 accum_reg = LTC2947_ACCUM_POL_1(accum[0]) |
> + LTC2947_ACCUM_POL_2(accum[1]);
> +
> + ret = regmap_write(st->map, LTC2947_REG_ACCUM_POL, accum_reg);
> + if (ret)
> + return ret;
> + }
> + ret = of_property_read_u32(st->dev->of_node,
> + "adi,accumulation-deadband-microamp",
> + &deadband);
> + if (!ret) {
> + /* the LSB is the same as the current, so 3mA */
> + ret = regmap_write(st->map, LTC2947_REG_ACCUM_DEADBAND,
> + deadband / (1000 * 3));
> + if (ret)
> + return ret;
> + }
> + /* check gpio cfg */
> + ret = of_property_read_u32(st->dev->of_node, "adi,gpio-out-pol", &pol);
> + if (!ret) {
> + /* setup GPIO as output */
> + u32 gpio_ctl = LTC2947_GPIO_EN(1) | LTC2947_GPIO_FAN_EN(1) |
> + LTC2947_GPIO_FAN_POL(pol);
> +
> + st->gpio_out = true;
> + ret = regmap_write(st->map, LTC2947_REG_GPIOSTATCTL, gpio_ctl);
> + if (ret)
> + return ret;
> + }
> + ret = of_property_read_u32_array(st->dev->of_node, "adi,gpio-in-accum",
> + accum, ARRAY_SIZE(accum));
> + if (!ret) {
> + /*
> + * Setup the accum options. The gpioctl is already defined as
> + * input by default.
> + */
> + u32 accum_val = LTC2947_ACCUM_POL_1(accum[0]) |
> + LTC2947_ACCUM_POL_2(accum[1]);
> +
> + if (st->gpio_out) {
> + dev_err(st->dev,
> + "Cannot have input gpio config if already configured as output");
> + return -EINVAL;
> + }
> +
> + ret = regmap_write(st->map, LTC2947_REG_GPIO_ACCUM, accum_val);
> + if (ret)
> + return ret;
> + }
> +
> + /* set continuos mode */
> + return regmap_update_bits(st->map, LTC2947_REG_CTRL,
> + LTC2947_CONT_MODE_MASK, LTC2947_CONT_MODE(1));
> +}
> +
> +int ltc2947_core_probe(struct regmap *map, const char *name)
> +{
> + struct ltc2947_data *st;
> + struct device *dev = regmap_get_device(map);
> + struct device *hwmon;
> + int ret;
> +
> + st = devm_kzalloc(dev, sizeof(*st), GFP_KERNEL);
> + if (!st)
> + return -ENOMEM;
> +
> + st->map = map;
> + st->dev = dev;
> + dev_set_drvdata(dev, st);
> + mutex_init(&st->lock);
> +
> + ret = ltc2947_setup(st);
> + if (ret)
> + return ret;
> +
> + hwmon = devm_hwmon_device_register_with_info(dev, name, st,
> + <c2947_chip_info,
> + ltc2947_groups);
> + return PTR_ERR_OR_ZERO(hwmon);
> +}
> +EXPORT_SYMBOL_GPL(ltc2947_core_probe);
> +
> +static int __maybe_unused ltc2947_resume(struct device *dev)
> +{
> + struct ltc2947_data *st = dev_get_drvdata(dev);
> + u32 ctrl = 0;
> + int ret;
> +
> + /* dummy read to wake the device */
> + ret = regmap_read(st->map, LTC2947_REG_CTRL, &ctrl);
> + if (ret)
> + return ret;
> + /*
> + * Wait for the device. It takes 100ms to wake up so, 10ms extra
> + * should be enough.
> + */
> + msleep(110);
> + ret = regmap_read(st->map, LTC2947_REG_CTRL, &ctrl);
> + if (ret)
> + return ret;
> + /* ctrl should be 0 */
> + if (ctrl != 0) {
> + dev_err(st->dev, "Device failed to wake up, ctl:%02X\n", ctrl);
> + return -ETIMEDOUT;
> + }
> +
> + /* set continuous mode */
> + return regmap_update_bits(st->map, LTC2947_REG_CTRL,
> + LTC2947_CONT_MODE_MASK, LTC2947_CONT_MODE(1));
> +}
> +
> +static int __maybe_unused ltc2947_suspend(struct device *dev)
> +{
> + struct ltc2947_data *st = dev_get_drvdata(dev);
> +
> + return regmap_update_bits(st->map, LTC2947_REG_CTRL,
> + LTC2947_SHUTDOWN_MASK, 1);
> +}
> +
> +SIMPLE_DEV_PM_OPS(ltc2947_pm_ops, ltc2947_suspend, ltc2947_resume);
> +EXPORT_SYMBOL_GPL(ltc2947_pm_ops);
> +
> +const struct of_device_id ltc2947_of_match[] = {
> + { .compatible = "adi,ltc2947" },
> + {}
> +};
> +EXPORT_SYMBOL_GPL(ltc2947_of_match);
> +MODULE_DEVICE_TABLE(of, ltc2947_of_match);
> +
> +MODULE_AUTHOR("Nuno Sa <nuno.sa@analog.com>");
> +MODULE_DESCRIPTION("LTC2947 power and energy monitor core driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/hwmon/ltc2947-i2c.c b/drivers/hwmon/ltc2947-i2c.c
> new file mode 100644
> index 000000000000..cf6074b110ae
> --- /dev/null
> +++ b/drivers/hwmon/ltc2947-i2c.c
> @@ -0,0 +1,49 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Analog Devices LTC2947 high precision power and energy monitor over I2C
> + *
> + * Copyright 2019 Analog Devices Inc.
> + */
> +#include <linux/i2c.h>
> +#include <linux/module.h>
> +#include <linux/regmap.h>
> +
> +#include "ltc2947.h"
> +
> +static const struct regmap_config ltc2947_regmap_config = {
> + .reg_bits = 8,
> + .val_bits = 8,
> +};
> +
> +static int ltc2947_probe(struct i2c_client *i2c,
> + const struct i2c_device_id *id)
> +{
> + struct regmap *map;
> +
> + map = devm_regmap_init_i2c(i2c, <c2947_regmap_config);
> + if (IS_ERR(map))
> + return PTR_ERR(map);
> +
> + return ltc2947_core_probe(map, i2c->name);
> +}
> +
> +static const struct i2c_device_id ltc2947_id[] = {
> + {"ltc2947", 0},
> + {}
> +};
> +MODULE_DEVICE_TABLE(i2c, ltc2947_id);
> +
> +static struct i2c_driver ltc2947_driver = {
> + .driver = {
> + .name = "ltc2947",
> + .of_match_table = ltc2947_of_match,
> + .pm = <c2947_pm_ops,
> + },
> + .probe = ltc2947_probe,
> + .id_table = ltc2947_id,
> +};
> +module_i2c_driver(ltc2947_driver);
> +
> +MODULE_AUTHOR("Nuno Sa <nuno.sa@analog.com>");
> +MODULE_DESCRIPTION("LTC2947 I2C power and energy monitor driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/hwmon/ltc2947-spi.c b/drivers/hwmon/ltc2947-spi.c
> new file mode 100644
> index 000000000000..c24ca569db1b
> --- /dev/null
> +++ b/drivers/hwmon/ltc2947-spi.c
> @@ -0,0 +1,50 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Analog Devices LTC2947 high precision power and energy monitor over SPI
> + *
> + * Copyright 2019 Analog Devices Inc.
> + */
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/regmap.h>
> +#include <linux/spi/spi.h>
> +
> +#include "ltc2947.h"
> +
> +static const struct regmap_config ltc2947_regmap_config = {
> + .reg_bits = 16,
> + .val_bits = 8,
> + .read_flag_mask = BIT(0),
> +};
> +
> +static int ltc2947_probe(struct spi_device *spi)
> +{
> + struct regmap *map;
> +
> + map = devm_regmap_init_spi(spi, <c2947_regmap_config);
> + if (IS_ERR(map))
> + return PTR_ERR(map);
> +
> + return ltc2947_core_probe(map, spi_get_device_id(spi)->name);
> +}
> +
> +static const struct spi_device_id ltc2947_id[] = {
> + {"ltc2947", 0},
> + {}
> +};
> +MODULE_DEVICE_TABLE(spi, ltc2947_id);
> +
> +static struct spi_driver ltc2947_driver = {
> + .driver = {
> + .name = "ltc2947",
> + .of_match_table = ltc2947_of_match,
> + .pm = <c2947_pm_ops,
> + },
> + .probe = ltc2947_probe,
> + .id_table = ltc2947_id,
> +};
> +module_spi_driver(ltc2947_driver);
> +
> +MODULE_AUTHOR("Nuno Sa <nuno.sa@analog.com>");
> +MODULE_DESCRIPTION("LTC2947 SPI power and energy monitor driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/hwmon/ltc2947.h b/drivers/hwmon/ltc2947.h
> new file mode 100644
> index 000000000000..5b8ff81a3dba
> --- /dev/null
> +++ b/drivers/hwmon/ltc2947.h
> @@ -0,0 +1,12 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +#ifndef _LINUX_LTC2947_H
> +#define _LINUX_LTC2947_H
> +
> +struct regmap;
> +
> +extern const struct of_device_id ltc2947_of_match[];
> +extern const struct dev_pm_ops ltc2947_pm_ops;
> +
> +int ltc2947_core_probe(struct regmap *map, const char *name);
> +
> +#endif
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH v3 1/2] hwmon: Add support for ltc2947
2019-10-21 15:41 [PATCH v3 1/2] hwmon: Add support for ltc2947 Nuno Sá
2019-10-21 15:41 ` [PATCH v3 2/2] dt-bindings: hwmon: Add ltc2947 documentation Nuno Sá
2019-10-29 12:24 ` [PATCH v3 1/2] hwmon: Add support for ltc2947 Guenter Roeck
@ 2019-10-29 12:27 ` Guenter Roeck
2 siblings, 0 replies; 7+ messages in thread
From: Guenter Roeck @ 2019-10-29 12:27 UTC (permalink / raw)
To: Nuno Sá
Cc: linux-hwmon, devicetree, linux-doc, Rob Herring, Mark Rutland,
Jean Delvare, Jonathan Corbet
On Mon, Oct 21, 2019 at 05:41:14PM +0200, Nuno Sá wrote:
> The ltc2947 is a high precision power and energy monitor with an
> internal sense resistor supporting up to +/- 30A. Three internal no
> Latency ADCs ensure accurate measurement of voltage and current, while
> high-bandwidth analog multiplication of voltage and current provides
> accurate power measurement in a wide range of applications. Internal or
> external clocking options enable precise charge and energy measurements.
>
> Signed-off-by: Nuno Sá <nuno.sa@analog.com>
[ ... ]
> +
> +static ssize_t ltc2947_show_value(struct device *dev,
> + struct device_attribute *da, char *buf)
> +{
> + struct ltc2947_data *st = dev_get_drvdata(dev);
> + struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
> + int ret;
> + s64 val = 0;
> +
> + switch (attr->index) {
> + case LTC2947_REG_ENERGY1:
> + case LTC2947_REG_ENERGY2:
> + ret = ltc2947_val_read(st, attr->index, PAGE0, 6, &val);
> + break;
> + default:
> + return -EINVAL;
> + }
This complexity is also unnecessary: index is either LTC2947_REG_ENERGY1 or
LTC2947_REG_ENERGY2. I removed the case statement when applying.
Guenter
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH v3 2/2] dt-bindings: hwmon: Add ltc2947 documentation
2019-10-21 15:41 ` [PATCH v3 2/2] dt-bindings: hwmon: Add ltc2947 documentation Nuno Sá
2019-10-24 22:46 ` Rob Herring
@ 2019-10-29 12:30 ` Guenter Roeck
1 sibling, 0 replies; 7+ messages in thread
From: Guenter Roeck @ 2019-10-29 12:30 UTC (permalink / raw)
To: Nuno Sá
Cc: linux-hwmon, devicetree, linux-doc, Rob Herring, Mark Rutland,
Jean Delvare, Jonathan Corbet
On Mon, Oct 21, 2019 at 05:41:15PM +0200, Nuno Sá wrote:
> Document the LTC2947 device devicetree bindings.
>
> Signed-off-by: Nuno Sá <nuno.sa@analog.com>
Applied with Rob's Reviewed-by:.
Guenter
> ---
> Changes in v2:
> * Add license identifier;
> * Fix the uint32-array properties;
> * Set maximum at the same indent as allOf in adi,accumulation-deadband-microamp;
> * Set enum at the same indent as allOf in adi,gpio-out-pol;
> * Use spi instead of spi0 on the example;
>
> Changes in v3:
> * Nothing changed.
>
> .../bindings/hwmon/adi,ltc2947.yaml | 104 ++++++++++++++++++
> MAINTAINERS | 1 +
> 2 files changed, 105 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/hwmon/adi,ltc2947.yaml
>
> diff --git a/Documentation/devicetree/bindings/hwmon/adi,ltc2947.yaml b/Documentation/devicetree/bindings/hwmon/adi,ltc2947.yaml
> new file mode 100644
> index 000000000000..ae04903f34bf
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/hwmon/adi,ltc2947.yaml
> @@ -0,0 +1,104 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/bindings/hwmon/adi,ltc2947.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Analog Devices LTC2947 high precision power and energy monitor
> +
> +maintainers:
> + - Nuno Sá <nuno.sa@analog.com>
> +
> +description: |
> + Analog Devices LTC2947 high precision power and energy monitor over SPI or I2C.
> +
> + https://www.analog.com/media/en/technical-documentation/data-sheets/LTC2947.pdf
> +
> +properties:
> + compatible:
> + enum:
> + - adi,ltc2947
> +
> + reg:
> + maxItems: 1
> +
> + clocks:
> + description:
> + The LTC2947 uses either a trimmed internal oscillator or an external clock
> + as the time base for determining the integration period to represent time,
> + charge and energy. When an external clock is used, this property must be
> + set accordingly.
> + maxItems: 1
> +
> + adi,accumulator-ctl-pol:
> + description:
> + This property controls the polarity of current that is accumulated to
> + calculate charge and energy so that, they can be only accumulated for
> + positive current for example. Since there are two sets of registers for
> + the accumulated values, this entry can also have two items which sets
> + energy1/charge1 and energy2/charger2 respectively. Check table 12 of the
> + datasheet for more information on the supported options.
> + allOf:
> + - $ref: /schemas/types.yaml#/definitions/uint32-array
> + - minItems: 2
> + maxItems: 2
> + items:
> + enum: [0, 1, 2, 3]
> + default: 0
> +
> + adi,accumulation-deadband-microamp:
> + description:
> + This property controls the Accumulation Dead band which allows to set the
> + level of current below which no accumulation takes place.
> + allOf:
> + - $ref: /schemas/types.yaml#/definitions/uint32
> + maximum: 255
> + default: 0
> +
> + adi,gpio-out-pol:
> + description:
> + This property controls the GPIO polarity. Setting it to one makes the GPIO
> + active high, setting it to zero makets it active low. When this property
> + is present, the GPIO is automatically configured as output and set to
> + control a fan as a function of measured temperature.
> + allOf:
> + - $ref: /schemas/types.yaml#/definitions/uint32
> + enum: [0, 1]
> + default: 0
> +
> + adi,gpio-in-accum:
> + description:
> + When set, this property sets the GPIO as input. It is then used to control
> + the accumulation of charge, energy and time. This function can be
> + enabled/configured separately for each of the two sets of accumulation
> + registers. Check table 13 of the datasheet for more information on the
> + supported options. This property cannot be used together with
> + adi,gpio-out-pol.
> + allOf:
> + - $ref: /schemas/types.yaml#/definitions/uint32-array
> + - minItems: 2
> + maxItems: 2
> + items:
> + enum: [0, 1, 2]
> + default: 0
> +
> +required:
> + - compatible
> + - reg
> +
> +
> +examples:
> + - |
> + spi {
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + ltc2947_spi: ltc2947@0 {
> + compatible = "adi,ltc2947";
> + reg = <0>;
> + /* accumulation takes place always for energ1/charge1. */
> + /* accumulation only on positive current for energy2/charge2. */
> + adi,accumulator-ctl-pol = <0 1>;
> + };
> + };
> +...
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 318332b6a411..e2ba1a182052 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -9638,6 +9638,7 @@ F: drivers/hwmon/ltc2947-core.c
> F: drivers/hwmon/ltc2947-spi.c
> F: drivers/hwmon/ltc2947-i2c.c
> F: drivers/hwmon/ltc2947.h
> +F: Documentation/devicetree/bindings/hwmon/adi,ltc2947.yaml
>
> LTC4306 I2C MULTIPLEXER DRIVER
> M: Michael Hennerich <michael.hennerich@analog.com>
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2019-10-29 12:30 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-10-21 15:41 [PATCH v3 1/2] hwmon: Add support for ltc2947 Nuno Sá
2019-10-21 15:41 ` [PATCH v3 2/2] dt-bindings: hwmon: Add ltc2947 documentation Nuno Sá
2019-10-24 22:46 ` Rob Herring
2019-10-25 10:32 ` Sa, Nuno
2019-10-29 12:30 ` Guenter Roeck
2019-10-29 12:24 ` [PATCH v3 1/2] hwmon: Add support for ltc2947 Guenter Roeck
2019-10-29 12:27 ` 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).