From patchwork Wed Nov 11 16:20:55 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Alexandru Tachici X-Patchwork-Id: 11898091 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 34DDC14C0 for ; Wed, 11 Nov 2020 16:16:36 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 00F042074B for ; Wed, 11 Nov 2020 16:16:35 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1727543AbgKKQQ3 (ORCPT ); Wed, 11 Nov 2020 11:16:29 -0500 Received: from mx0a-00128a01.pphosted.com ([148.163.135.77]:13158 "EHLO mx0a-00128a01.pphosted.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1727132AbgKKQQR (ORCPT ); Wed, 11 Nov 2020 11:16:17 -0500 Received: from pps.filterd (m0167088.ppops.net [127.0.0.1]) by mx0a-00128a01.pphosted.com (8.16.0.42/8.16.0.42) with SMTP id 0ABGFMe1018622; Wed, 11 Nov 2020 11:16:02 -0500 Received: from nwd2mta3.analog.com ([137.71.173.56]) by mx0a-00128a01.pphosted.com with ESMTP id 34npaax0ry-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Wed, 11 Nov 2020 11:16:02 -0500 Received: from ASHBMBX8.ad.analog.com (ASHBMBX8.ad.analog.com [10.64.17.5]) by nwd2mta3.analog.com (8.14.7/8.14.7) with ESMTP id 0ABGG12Z024333 (version=TLSv1/SSLv3 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128 verify=FAIL); Wed, 11 Nov 2020 11:16:01 -0500 Received: from ASHBCASHYB4.ad.analog.com (10.64.17.132) by ASHBMBX8.ad.analog.com (10.64.17.5) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.1779.2; Wed, 11 Nov 2020 11:16:00 -0500 Received: from ASHBMBX9.ad.analog.com (10.64.17.10) by ASHBCASHYB4.ad.analog.com (10.64.17.132) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.1779.2; Wed, 11 Nov 2020 11:15:59 -0500 Received: from zeus.spd.analog.com (10.66.68.11) by ASHBMBX9.ad.analog.com (10.64.17.10) with Microsoft SMTP Server id 15.1.1779.2 via Frontend Transport; Wed, 11 Nov 2020 11:15:59 -0500 Received: from localhost.localdomain ([10.48.65.12]) by zeus.spd.analog.com (8.15.1/8.15.1) with ESMTP id 0ABGFtWh009875; Wed, 11 Nov 2020 11:15:57 -0500 From: To: , , CC: , , Alexandru Tachici Subject: [PATCH v2 1/3] hwmon: ltc2992: Add support Date: Wed, 11 Nov 2020 18:20:55 +0200 Message-ID: <20201111162057.73055-2-alexandru.tachici@analog.com> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20201111162057.73055-1-alexandru.tachici@analog.com> References: <20201111162057.73055-1-alexandru.tachici@analog.com> MIME-Version: 1.0 X-Proofpoint-Virus-Version: vendor=fsecure engine=2.50.10434:6.0.312,18.0.737 definitions=2020-11-11_07:2020-11-10,2020-11-11 signatures=0 X-Proofpoint-Spam-Details: rule=outbound_notspam policy=outbound score=0 malwarescore=0 priorityscore=1501 mlxlogscore=999 clxscore=1015 lowpriorityscore=0 suspectscore=0 bulkscore=0 impostorscore=0 adultscore=0 spamscore=0 mlxscore=0 phishscore=0 classifier=spam adjust=0 reason=mlx scancount=1 engine=8.12.0-2009150000 definitions=main-2011110096 Precedence: bulk List-ID: X-Mailing-List: linux-hwmon@vger.kernel.org From: Alexandru Tachici LTC2992 is a rail-to-rail system monitor that measures current, voltage, and power of two supplies. Two ADCs simultaneously measure each supply’s current. A third ADC monitors the input voltages and four auxiliary external voltages. Signed-off-by: Alexandru Tachici --- Documentation/hwmon/index.rst | 1 + Documentation/hwmon/ltc2992.rst | 56 +++ drivers/hwmon/Kconfig | 11 + drivers/hwmon/Makefile | 1 + drivers/hwmon/ltc2992.c | 818 ++++++++++++++++++++++++++++++++ 5 files changed, 887 insertions(+) create mode 100644 Documentation/hwmon/ltc2992.rst create mode 100644 drivers/hwmon/ltc2992.c diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst index e6b91ab12978..98575a8b1918 100644 --- a/Documentation/hwmon/index.rst +++ b/Documentation/hwmon/index.rst @@ -100,6 +100,7 @@ Hardware Monitoring Kernel Drivers lm95234 lm95245 lochnagar + ltc2992 ltc2945 ltc2947 ltc2978 diff --git a/Documentation/hwmon/ltc2992.rst b/Documentation/hwmon/ltc2992.rst new file mode 100644 index 000000000000..46aa1aa84a1a --- /dev/null +++ b/Documentation/hwmon/ltc2992.rst @@ -0,0 +1,56 @@ +.. SPDX-License-Identifier: GPL-2.0 + +Kernel driver ltc2992 +===================== + +Supported chips: + * Linear Technology LTC2992 + Prefix: 'ltc2992' + Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/ltc2992.pdf + +Author: Alexandru Tachici + + +Description +----------- + +This driver supports hardware monitoring for Linear Technology LTC2992 power monitor. + +LTC2992 is a rail-to-rail system monitor that measures current, +voltage, and power of two supplies. + +Two ADCs simultaneously measure each supply’s current. A third ADC monitors +the input voltages and four auxiliary external voltages. + + +Sysfs entries +------------- + +The following attributes are supported. Limits are read-write, +all other attributes are read-only. + +in_reset_history Reset all highest/lowest values. + +inX_input Measured voltage. +inX_lowest Minimum measured voltage. +inX_highest Maximum measured voltage. +inX_min Minimum voltage allowed. +inX_max Maximum voltage allowed. +inX_min_alarm An undervoltage occurred. Cleared on read. +inX_max_alarm An overvoltage occurred. Cleared on read. + +currX_input Measured current. +currX_lowest Minimum measured current. +currX_highest Maximum measured current. +currX_min Minimum current allowed. +currX_max Maximum current allowed. +currX_min_alarm An undercurrent occurred. Cleared on read. +currX_max_alarm An overcurrent occurred. Cleared on read. + +powerX_input Measured power. +powerX_input_lowest Minimum measured voltage. +powerX_input_highest Maximum measured voltage. +powerX_min Minimum power. +powerX_max Maximum power. +powerX_min_alarm An underpower occurred. Cleared on read. +powerX_max_alarm An overpower occurred. Cleared on read. diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index a850e4f0e0bd..bf9e387270d6 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -858,6 +858,17 @@ config SENSORS_LTC2990 This driver can also be built as a module. If so, the module will be called ltc2990. +config SENSORS_LTC2992 + tristate "Linear Technology LTC2992" + depends on I2C + help + If you say yes here you get support for Linear Technology LTC2992 + I2C System Monitor. The LTC2992 measures current, voltage, and + power of two supplies. + + This driver can also be built as a module. If so, the module will + be called ltc2992. + config SENSORS_LTC4151 tristate "Linear Technology LTC4151" depends on I2C diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 9db2903b61e5..d6172c4807c4 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -118,6 +118,7 @@ obj-$(CONFIG_SENSORS_LTC2947) += ltc2947-core.o obj-$(CONFIG_SENSORS_LTC2947_I2C) += ltc2947-i2c.o obj-$(CONFIG_SENSORS_LTC2947_SPI) += ltc2947-spi.o obj-$(CONFIG_SENSORS_LTC2990) += ltc2990.o +obj-$(CONFIG_SENSORS_LTC2992) += ltc2992.o obj-$(CONFIG_SENSORS_LTC4151) += ltc4151.o obj-$(CONFIG_SENSORS_LTC4215) += ltc4215.o obj-$(CONFIG_SENSORS_LTC4222) += ltc4222.o diff --git a/drivers/hwmon/ltc2992.c b/drivers/hwmon/ltc2992.c new file mode 100644 index 000000000000..ce679405ac81 --- /dev/null +++ b/drivers/hwmon/ltc2992.c @@ -0,0 +1,818 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +/* + * LTC2992 - Dual Wide Range Power Monitor + * + * Copyright 2020 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define LTC2992_CTRLB 0x01 +#define LTC2992_FAULT1 0x03 +#define LTC2992_POWER1 0x05 +#define LTC2992_POWER1_MAX 0x08 +#define LTC2992_POWER1_MIN 0x0B +#define LTC2992_POWER1_MAX_THRESH 0x0E +#define LTC2992_POWER1_MIN_THRESH 0x11 +#define LTC2992_DSENSE1 0x14 +#define LTC2992_DSENSE1_MAX 0x16 +#define LTC2992_DSENSE1_MIN 0x18 +#define LTC2992_DSENSE1_MAX_THRESH 0x1A +#define LTC2992_DSENSE1_MIN_THRESH 0x1C +#define LTC2992_SENSE1 0x1E +#define LTC2992_SENSE1_MAX 0x20 +#define LTC2992_SENSE1_MIN 0x22 +#define LTC2992_SENSE1_MAX_THRESH 0x24 +#define LTC2992_SENSE1_MIN_THRESH 0x26 +#define LTC2992_G1 0x28 +#define LTC2992_G1_MAX 0x2A +#define LTC2992_G1_MIN 0x2C +#define LTC2992_G1_MAX_THRESH 0x2E +#define LTC2992_G1_MIN_THRESH 0x30 +#define LTC2992_FAULT2 0x35 +#define LTC2992_G2 0x5A +#define LTC2992_G2_MAX 0x5C +#define LTC2992_G2_MIN 0x5E +#define LTC2992_G2_MAX_THRESH 0x60 +#define LTC2992_G2_MIN_THRESH 0x62 +#define LTC2992_G3 0x64 +#define LTC2992_G3_MAX 0x66 +#define LTC2992_G3_MIN 0x68 +#define LTC2992_G3_MAX_THRESH 0x6A +#define LTC2992_G3_MIN_THRESH 0x6C +#define LTC2992_G4 0x6E +#define LTC2992_G4_MAX 0x70 +#define LTC2992_G4_MIN 0x72 +#define LTC2992_G4_MAX_THRESH 0x74 +#define LTC2992_G4_MIN_THRESH 0x76 +#define LTC2992_FAULT3 0x92 + +#define LTC2992_POWER(x) (LTC2992_POWER1 + ((x) * 0x32)) +#define LTC2992_POWER_MAX(x) (LTC2992_POWER1_MAX + ((x) * 0x32)) +#define LTC2992_POWER_MIN(x) (LTC2992_POWER1_MIN + ((x) * 0x32)) +#define LTC2992_POWER_MAX_THRESH(x) (LTC2992_POWER1_MAX_THRESH + ((x) * 0x32)) +#define LTC2992_POWER_MIN_THRESH(x) (LTC2992_POWER1_MIN_THRESH + ((x) * 0x32)) +#define LTC2992_DSENSE(x) (LTC2992_DSENSE1 + ((x) * 0x32)) +#define LTC2992_DSENSE_MAX(x) (LTC2992_DSENSE1_MAX + ((x) * 0x32)) +#define LTC2992_DSENSE_MIN(x) (LTC2992_DSENSE1_MIN + ((x) * 0x32)) +#define LTC2992_DSENSE_MAX_THRESH(x) (LTC2992_DSENSE1_MAX_THRESH + ((x) * 0x32)) +#define LTC2992_DSENSE_MIN_THRESH(x) (LTC2992_DSENSE1_MIN_THRESH + ((x) * 0x32)) +#define LTC2992_SENSE(x) (LTC2992_SENSE1 + ((x) * 0x32)) +#define LTC2992_SENSE_MAX(x) (LTC2992_SENSE1_MAX + ((x) * 0x32)) +#define LTC2992_SENSE_MIN(x) (LTC2992_SENSE1_MIN + ((x) * 0x32)) +#define LTC2992_SENSE_MAX_THRESH(x) (LTC2992_SENSE1_MAX_THRESH + ((x) * 0x32)) +#define LTC2992_SENSE_MIN_THRESH(x) (LTC2992_SENSE1_MIN_THRESH + ((x) * 0x32)) +#define LTC2992_POWER_FAULT(x) (LTC2992_FAULT1 + ((x) * 0x32)) +#define LTC2992_SENSE_FAULT(x) (LTC2992_FAULT1 + ((x) * 0x32)) +#define LTC2992_DSENSE_FAULT(x) (LTC2992_FAULT1 + ((x) * 0x32)) + +/* CTRLB register bitfields */ +#define LTC2992_RESET_HISTORY BIT(3) + +/* FAULT1 FAULT2 registers common bitfields */ +#define LTC2992_POWER_FAULT_MSK(x) (BIT(6) << (x)) +#define LTC2992_DSENSE_FAULT_MSK(x) (BIT(4) << (x)) +#define LTC2992_SENSE_FAULT_MSK(x) (BIT(2) << (x)) + +/* FAULT1 bitfields */ +#define LTC2992_GPIO1_FAULT_MSK(x) (BIT(0) << (x)) + +/* FAULT2 bitfields */ +#define LTC2992_GPIO2_FAULT_MSK(x) (BIT(0) << (x)) + +/* FAULT3 bitfields */ +#define LTC2992_GPIO3_FAULT_MSK(x) (BIT(6) << (x)) +#define LTC2992_GPIO4_FAULT_MSK(x) (BIT(4) << (x)) + +#define LTC2992_IADC_NANOV_LSB 12500 +#define LTC2992_VADC_UV_LSB 25000 +#define LTC2992_VADC_GPIO_UV_LSB 500 + +struct ltc2992_state { + struct i2c_client *client; + struct regmap *regmap; + u32 r_sense_uohm[2]; +}; + +struct ltc2992_gpio_regs { + u8 data; + u8 max; + u8 min; + u8 max_thresh; + u8 min_thresh; + u8 alarm; + u8 min_alarm_msk; + u8 max_alarm_msk; +}; + +static const struct ltc2992_gpio_regs ltc2992_gpio_addr_map[] = { + { + .data = LTC2992_G1, + .max = LTC2992_G1_MAX, + .min = LTC2992_G1_MIN, + .max_thresh = LTC2992_G1_MAX_THRESH, + .min_thresh = LTC2992_G1_MIN_THRESH, + .alarm = LTC2992_FAULT1, + .min_alarm_msk = LTC2992_GPIO1_FAULT_MSK(0), + .max_alarm_msk = LTC2992_GPIO1_FAULT_MSK(1), + }, + { + .data = LTC2992_G2, + .max = LTC2992_G2_MAX, + .min = LTC2992_G2_MIN, + .max_thresh = LTC2992_G2_MAX_THRESH, + .min_thresh = LTC2992_G2_MIN_THRESH, + .alarm = LTC2992_FAULT2, + .min_alarm_msk = LTC2992_GPIO2_FAULT_MSK(0), + .max_alarm_msk = LTC2992_GPIO2_FAULT_MSK(1), + }, + { + .data = LTC2992_G3, + .max = LTC2992_G3_MAX, + .min = LTC2992_G3_MIN, + .max_thresh = LTC2992_G3_MAX_THRESH, + .min_thresh = LTC2992_G3_MIN_THRESH, + .alarm = LTC2992_FAULT3, + .min_alarm_msk = LTC2992_GPIO3_FAULT_MSK(0), + .max_alarm_msk = LTC2992_GPIO3_FAULT_MSK(1), + }, + { + .data = LTC2992_G4, + .max = LTC2992_G4_MAX, + .min = LTC2992_G4_MIN, + .max_thresh = LTC2992_G4_MAX_THRESH, + .min_thresh = LTC2992_G4_MIN_THRESH, + .alarm = LTC2992_FAULT3, + .min_alarm_msk = LTC2992_GPIO4_FAULT_MSK(0), + .max_alarm_msk = LTC2992_GPIO4_FAULT_MSK(1), + }, +}; + +static int ltc2992_read_reg(struct ltc2992_state *st, u8 addr, const u8 reg_len) +{ + u8 regvals[4]; + int ret; + int val; + int i; + + ret = regmap_bulk_read(st->regmap, addr, regvals, reg_len); + if (ret < 0) + return ret; + + val = 0; + for (i = 0; i < reg_len; i++) + val |= regvals[reg_len - i - 1] << (i * 8); + + return val; +} + +static int ltc2992_write_reg(struct ltc2992_state *st, u8 addr, const u8 reg_len, u32 val) +{ + u8 regvals[4]; + int i; + + for (i = 0; i < reg_len; i++) + regvals[reg_len - i - 1] = (val >> (i * 8)) & 0xFF; + + return regmap_bulk_write(st->regmap, addr, regvals, reg_len); +} + +static umode_t ltc2992_is_visible(const void *data, enum hwmon_sensor_types type, u32 attr, + int channel) +{ + const struct ltc2992_state *st = data; + + switch (type) { + case hwmon_chip: + switch (attr) { + case hwmon_chip_in_reset_history: + return 0200; + } + break; + case hwmon_in: + switch (attr) { + case hwmon_in_input: + case hwmon_in_lowest: + case hwmon_in_highest: + case hwmon_in_min_alarm: + case hwmon_in_max_alarm: + return 0444; + case hwmon_in_min: + case hwmon_in_max: + return 0644; + } + break; + case hwmon_curr: + switch (attr) { + case hwmon_curr_input: + case hwmon_curr_lowest: + case hwmon_curr_highest: + case hwmon_curr_min_alarm: + case hwmon_curr_max_alarm: + if (st->r_sense_uohm[channel]) + return 0444; + break; + case hwmon_curr_min: + case hwmon_curr_max: + if (st->r_sense_uohm[channel]) + return 0644; + break; + } + break; + case hwmon_power: + switch (attr) { + case hwmon_power_input: + case hwmon_power_input_lowest: + case hwmon_power_input_highest: + case hwmon_power_min_alarm: + case hwmon_power_max_alarm: + if (st->r_sense_uohm[channel]) + return 0444; + break; + case hwmon_power_min: + case hwmon_power_max: + if (st->r_sense_uohm[channel]) + return 0644; + break; + } + break; + default: + break; + } + + return 0; +} + +static int ltc2992_get_voltage(struct ltc2992_state *st, u32 reg, u32 scale, long *val) +{ + int reg_val; + + reg_val = ltc2992_read_reg(st, reg, 2); + if (reg_val < 0) + return reg_val; + + reg_val = reg_val >> 4; + *val = DIV_ROUND_CLOSEST(reg_val * scale, 1000); + + return 0; +} + +static int ltc2992_set_voltage(struct ltc2992_state *st, u32 reg, u32 scale, long val) +{ + val = DIV_ROUND_CLOSEST(val * 1000, scale); + val = val << 4; + + return ltc2992_write_reg(st, reg, 2, val); +} + +static int ltc2992_read_gpio_alarm(struct ltc2992_state *st, int nr_gpio, u32 attr, long *val) +{ + int reg_val; + u32 mask; + + if (attr == hwmon_in_max_alarm) + mask = ltc2992_gpio_addr_map[nr_gpio].max_alarm_msk; + else + mask = ltc2992_gpio_addr_map[nr_gpio].min_alarm_msk; + + reg_val = ltc2992_read_reg(st, ltc2992_gpio_addr_map[nr_gpio].alarm, 1); + if (reg_val < 0) + return reg_val; + + *val = !!(reg_val & mask); + reg_val &= ~mask; + + return ltc2992_write_reg(st, ltc2992_gpio_addr_map[nr_gpio].alarm, 1, reg_val); +} + +static int ltc2992_read_gpios_in(struct device *dev, u32 attr, int nr_gpio, long *val) +{ + struct ltc2992_state *st = dev_get_drvdata(dev); + u32 reg; + + switch (attr) { + case hwmon_in_input: + reg = ltc2992_gpio_addr_map[nr_gpio].data; + break; + case hwmon_in_lowest: + reg = ltc2992_gpio_addr_map[nr_gpio].min; + break; + case hwmon_in_highest: + reg = ltc2992_gpio_addr_map[nr_gpio].max; + break; + case hwmon_in_min: + reg = ltc2992_gpio_addr_map[nr_gpio].min_thresh; + break; + case hwmon_in_max: + reg = ltc2992_gpio_addr_map[nr_gpio].max_thresh; + break; + case hwmon_in_min_alarm: + case hwmon_in_max_alarm: + return ltc2992_read_gpio_alarm(st, nr_gpio, attr, val); + default: + return -EOPNOTSUPP; + } + + return ltc2992_get_voltage(st, reg, LTC2992_VADC_GPIO_UV_LSB, val); +} + +static int ltc2992_read_in_alarm(struct ltc2992_state *st, int channel, long *val, u32 attr) +{ + u32 reg_val; + u32 mask; + + if (attr == hwmon_in_max_alarm) + mask = LTC2992_SENSE_FAULT_MSK(1); + else + mask = LTC2992_SENSE_FAULT_MSK(0); + + reg_val = ltc2992_read_reg(st, LTC2992_SENSE_FAULT(channel), 1); + if (reg_val < 0) + return reg_val; + + *val = !!(reg_val & mask); + reg_val &= ~mask; + + return ltc2992_write_reg(st, LTC2992_SENSE_FAULT(channel), 1, reg_val); +} + +static int ltc2992_read_in(struct device *dev, u32 attr, int channel, long *val) +{ + struct ltc2992_state *st = dev_get_drvdata(dev); + u32 reg; + + if (channel > 1) + return ltc2992_read_gpios_in(dev, attr, channel - 2, val); + + switch (attr) { + case hwmon_in_input: + reg = LTC2992_SENSE(channel); + break; + case hwmon_in_lowest: + reg = LTC2992_SENSE_MIN(channel); + break; + case hwmon_in_highest: + reg = LTC2992_SENSE_MAX(channel); + break; + case hwmon_in_min: + reg = LTC2992_SENSE_MIN_THRESH(channel); + break; + case hwmon_in_max: + reg = LTC2992_SENSE_MAX_THRESH(channel); + break; + case hwmon_in_min_alarm: + case hwmon_in_max_alarm: + return ltc2992_read_in_alarm(st, channel, val, attr); + default: + return -EOPNOTSUPP; + } + + return ltc2992_get_voltage(st, reg, LTC2992_VADC_UV_LSB, val); +} + +static int ltc2992_get_current(struct ltc2992_state *st, u32 reg, u32 channel, long *val) +{ + u32 reg_val; + + reg_val = ltc2992_read_reg(st, reg, 2); + if (reg_val < 0) + return reg_val; + + reg_val = reg_val >> 4; + *val = DIV_ROUND_CLOSEST(reg_val * LTC2992_IADC_NANOV_LSB, st->r_sense_uohm[channel]); + + return 0; +} + +static int ltc2992_set_current(struct ltc2992_state *st, u32 reg, u32 channel, long val) +{ + u32 reg_val; + + reg_val = DIV_ROUND_CLOSEST(val * st->r_sense_uohm[channel], LTC2992_IADC_NANOV_LSB); + reg_val = reg_val << 4; + + return ltc2992_write_reg(st, reg, 2, reg_val); +} + +static int ltc2992_read_curr_alarm(struct ltc2992_state *st, int channel, long *val, u32 attr) +{ + u32 reg_val; + u32 mask; + + if (attr == hwmon_curr_max_alarm) + mask = LTC2992_DSENSE_FAULT_MSK(1); + else + mask = LTC2992_DSENSE_FAULT_MSK(0); + + reg_val = ltc2992_read_reg(st, LTC2992_DSENSE_FAULT(channel), 1); + if (reg_val < 0) + return reg_val; + + *val = !!(reg_val & mask); + + reg_val &= ~mask; + return ltc2992_write_reg(st, LTC2992_DSENSE_FAULT(channel), 1, reg_val); +} + +static int ltc2992_read_curr(struct device *dev, u32 attr, int channel, long *val) +{ + struct ltc2992_state *st = dev_get_drvdata(dev); + u32 reg; + + switch (attr) { + case hwmon_curr_input: + reg = LTC2992_DSENSE(channel); + break; + case hwmon_curr_lowest: + reg = LTC2992_DSENSE_MIN(channel); + break; + case hwmon_curr_highest: + reg = LTC2992_DSENSE_MAX(channel); + break; + case hwmon_curr_min: + reg = LTC2992_DSENSE_MIN_THRESH(channel); + break; + case hwmon_curr_max: + reg = LTC2992_DSENSE_MAX_THRESH(channel); + break; + case hwmon_curr_min_alarm: + case hwmon_curr_max_alarm: + return ltc2992_read_curr_alarm(st, channel, val, attr); + default: + return -EOPNOTSUPP; + } + + return ltc2992_get_current(st, reg, channel, val); +} + +static int ltc2992_get_power(struct ltc2992_state *st, u32 reg, u32 channel, long *val) +{ + u32 reg_val; + + reg_val = ltc2992_read_reg(st, reg, 3); + if (reg_val < 0) + return reg_val; + + *val = mul_u64_u32_div(reg_val, LTC2992_VADC_UV_LSB * LTC2992_IADC_NANOV_LSB, + st->r_sense_uohm[channel] * 1000); + + return 0; +} + +static int ltc2992_set_power(struct ltc2992_state *st, u32 reg, u32 channel, long val) +{ + u32 reg_val; + + reg_val = mul_u64_u32_div(val, st->r_sense_uohm[channel] * 1000, + LTC2992_VADC_UV_LSB * LTC2992_IADC_NANOV_LSB); + + return ltc2992_write_reg(st, reg, 3, reg_val); +} + +static int ltc2992_read_power_alarm(struct ltc2992_state *st, int channel, long *val, u32 attr) +{ + u32 reg_val; + u32 mask; + + if (attr == hwmon_power_max_alarm) + mask = LTC2992_POWER_FAULT_MSK(1); + else + mask = LTC2992_POWER_FAULT_MSK(0); + + reg_val = ltc2992_read_reg(st, LTC2992_POWER_FAULT(channel), 1); + if (reg_val < 0) + return reg_val; + + *val = !!(reg_val & mask); + reg_val &= ~mask; + + return ltc2992_write_reg(st, LTC2992_POWER_FAULT(channel), 1, reg_val); +} + +static int ltc2992_read_power(struct device *dev, u32 attr, int channel, long *val) +{ + struct ltc2992_state *st = dev_get_drvdata(dev); + u32 reg; + + switch (attr) { + case hwmon_power_input: + reg = LTC2992_POWER(channel); + break; + case hwmon_power_input_lowest: + reg = LTC2992_POWER_MIN(channel); + break; + case hwmon_power_input_highest: + reg = LTC2992_POWER_MAX(channel); + break; + case hwmon_power_min: + reg = LTC2992_POWER_MIN_THRESH(channel); + break; + case hwmon_power_max: + reg = LTC2992_POWER_MAX_THRESH(channel); + break; + case hwmon_power_min_alarm: + case hwmon_power_max_alarm: + return ltc2992_read_power_alarm(st, channel, val, attr); + default: + return -EOPNOTSUPP; + } + + return ltc2992_get_power(st, reg, channel, val); +} + +static int ltc2992_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, + long *val) +{ + switch (type) { + case hwmon_in: + return ltc2992_read_in(dev, attr, channel, val); + case hwmon_curr: + return ltc2992_read_curr(dev, attr, channel, val); + case hwmon_power: + return ltc2992_read_power(dev, attr, channel, val); + default: + return -EOPNOTSUPP; + } +} + +static int ltc2992_write_curr(struct device *dev, u32 attr, int channel, long val) +{ + struct ltc2992_state *st = dev_get_drvdata(dev); + u32 reg; + + switch (attr) { + case hwmon_curr_min: + reg = LTC2992_DSENSE_MIN_THRESH(channel); + break; + case hwmon_curr_max: + reg = LTC2992_DSENSE_MAX_THRESH(channel); + break; + default: + return -EOPNOTSUPP; + } + + return ltc2992_set_current(st, reg, channel, val); +} + +static int ltc2992_write_gpios_in(struct device *dev, u32 attr, int nr_gpio, long val) +{ + struct ltc2992_state *st = dev_get_drvdata(dev); + u32 reg; + + switch (attr) { + case hwmon_in_min: + reg = ltc2992_gpio_addr_map[nr_gpio].min_thresh; + break; + case hwmon_in_max: + reg = ltc2992_gpio_addr_map[nr_gpio].max_thresh; + break; + default: + return -EOPNOTSUPP; + } + + return ltc2992_set_voltage(st, reg, LTC2992_VADC_GPIO_UV_LSB, val); +} + +static int ltc2992_write_in(struct device *dev, u32 attr, int channel, long val) +{ + struct ltc2992_state *st = dev_get_drvdata(dev); + u32 reg = 0; + + if (channel > 1) + return ltc2992_write_gpios_in(dev, attr, channel - 2, val); + + switch (attr) { + case hwmon_in_min: + reg = LTC2992_SENSE_MIN_THRESH(channel); + break; + case hwmon_in_max: + reg = LTC2992_SENSE_MAX_THRESH(channel); + break; + default: + return -EOPNOTSUPP; + } + + return ltc2992_set_voltage(st, reg, LTC2992_VADC_UV_LSB, val); +} + +static int ltc2992_write_power(struct device *dev, u32 attr, int channel, long val) +{ + struct ltc2992_state *st = dev_get_drvdata(dev); + u32 reg = 0; + + switch (attr) { + case hwmon_power_min: + reg = LTC2992_POWER_MIN_THRESH(channel); + break; + case hwmon_power_max: + reg = LTC2992_POWER_MAX_THRESH(channel); + break; + default: + return -EOPNOTSUPP; + } + + return ltc2992_set_power(st, reg, channel, val); +} + +static int ltc2992_write_chip(struct device *dev, u32 attr, int channel, long val) +{ + struct ltc2992_state *st = dev_get_drvdata(dev); + + switch (attr) { + case hwmon_chip_in_reset_history: + return regmap_update_bits(st->regmap, LTC2992_CTRLB, LTC2992_RESET_HISTORY, + LTC2992_RESET_HISTORY); + default: + return -EOPNOTSUPP; + } +} + +static int ltc2992_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, + long val) +{ + switch (type) { + case hwmon_chip: + return ltc2992_write_chip(dev, attr, channel, val); + case hwmon_in: + return ltc2992_write_in(dev, attr, channel, val); + case hwmon_curr: + return ltc2992_write_curr(dev, attr, channel, val); + case hwmon_power: + return ltc2992_write_power(dev, attr, channel, val); + default: + return -EOPNOTSUPP; + } +} + +static const struct hwmon_ops ltc2992_hwmon_ops = { + .is_visible = ltc2992_is_visible, + .read = ltc2992_read, + .write = ltc2992_write, +}; + +static const u32 ltc2992_chip_config[] = { + HWMON_C_IN_RESET_HISTORY, + 0 +}; + +static const struct hwmon_channel_info ltc2992_chip = { + .type = hwmon_chip, + .config = ltc2992_chip_config, +}; + +static const u32 ltc2992_in_config[] = { + HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST | HWMON_I_MIN | HWMON_I_MAX | + HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM, + HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST | HWMON_I_MIN | HWMON_I_MAX | + HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM, + HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST | HWMON_I_MIN | HWMON_I_MAX | + HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM, + HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST | HWMON_I_MIN | HWMON_I_MAX | + HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM, + HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST | HWMON_I_MIN | HWMON_I_MAX | + HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM, + HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST | HWMON_I_MIN | HWMON_I_MAX | + HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM, + 0 +}; + +static const struct hwmon_channel_info ltc2992_in = { + .type = hwmon_in, + .config = ltc2992_in_config, +}; + +static const u32 ltc2992_curr_config[] = { + HWMON_C_INPUT | HWMON_C_LOWEST | HWMON_C_HIGHEST | HWMON_C_MIN | HWMON_C_MAX | + HWMON_C_MIN_ALARM | HWMON_C_MAX_ALARM, + HWMON_C_INPUT | HWMON_C_LOWEST | HWMON_C_HIGHEST | HWMON_C_MIN | HWMON_C_MAX | + HWMON_C_MIN_ALARM | HWMON_C_MAX_ALARM, + 0 +}; + +static const struct hwmon_channel_info ltc2992_curr = { + .type = hwmon_curr, + .config = ltc2992_curr_config, +}; + +static const u32 ltc2992_power_config[] = { + HWMON_P_INPUT | HWMON_P_INPUT_LOWEST | HWMON_P_INPUT_HIGHEST | HWMON_P_MIN | HWMON_P_MAX | + HWMON_P_MIN_ALARM | HWMON_P_MAX_ALARM, + HWMON_P_INPUT | HWMON_P_INPUT_LOWEST | HWMON_P_INPUT_HIGHEST | HWMON_P_MIN | HWMON_P_MAX | + HWMON_P_MIN_ALARM | HWMON_P_MAX_ALARM, + 0 +}; + +static const struct hwmon_channel_info ltc2992_power = { + .type = hwmon_power, + .config = ltc2992_power_config, +}; + +static const struct hwmon_channel_info *ltc2992_info[] = { + <c2992_chip, + <c2992_in, + <c2992_curr, + <c2992_power, + NULL +}; + +static const struct hwmon_chip_info ltc2992_chip_info = { + .ops = <c2992_hwmon_ops, + .info = ltc2992_info, +}; + +static const struct regmap_config ltc2992_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0xE8, +}; + +static int ltc2992_parse_dt(struct ltc2992_state *st) +{ + struct fwnode_handle *fwnode; + struct fwnode_handle *child; + u32 addr; + u32 val; + int ret; + + fwnode = dev_fwnode(&st->client->dev); + + fwnode_for_each_available_child_node(fwnode, child) { + ret = fwnode_property_read_u32(child, "reg", &addr); + if (ret < 0) + return ret; + + if (addr > 1) + return -EINVAL; + + ret = fwnode_property_read_u32(child, "shunt-resistor-micro-ohms", &val); + if (!ret) + st->r_sense_uohm[addr] = val; + } + + return 0; +} + +static int ltc2992_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + struct device *hwmon_dev; + struct ltc2992_state *st; + int ret; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + st = devm_kzalloc(&client->dev, sizeof(*st), GFP_KERNEL); + if (!st) + return -ENOMEM; + + st->client = client; + st->regmap = devm_regmap_init_i2c(client, <c2992_regmap_config); + if (IS_ERR(st->regmap)) + return PTR_ERR(st->regmap); + + ret = ltc2992_parse_dt(st); + if (ret < 0) + return ret; + + hwmon_dev = devm_hwmon_device_register_with_info(&client->dev, client->name, st, + <c2992_chip_info, NULL); + + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const struct of_device_id ltc2992_of_match[] = { + { .compatible = "adi,ltc2992" }, + { } +}; +MODULE_DEVICE_TABLE(of, ltc2992_of_match); + +static const struct i2c_device_id ltc2992_i2c_id[] = { + {"ltc2992", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, ltc2992_i2c_id); + +static struct i2c_driver ltc2992_i2c_driver = { + .driver = { + .name = "ltc2992", + .of_match_table = ltc2992_of_match, + }, + .probe = ltc2992_i2c_probe, + .id_table = ltc2992_i2c_id, +}; + +module_i2c_driver(ltc2992_i2c_driver); + +MODULE_AUTHOR("Alexandru Tachici "); +MODULE_DESCRIPTION("Hwmon driver for Linear Technology 2992"); +MODULE_LICENSE("Dual BSD/GPL"); From patchwork Wed Nov 11 16:20:56 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Alexandru Tachici X-Patchwork-Id: 11898089 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 790E214C0 for ; Wed, 11 Nov 2020 16:16:29 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 630AC2084C for ; Wed, 11 Nov 2020 16:16:29 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1727363AbgKKQQR (ORCPT ); Wed, 11 Nov 2020 11:16:17 -0500 Received: from mx0a-00128a01.pphosted.com ([148.163.135.77]:13628 "EHLO mx0a-00128a01.pphosted.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1727171AbgKKQQQ (ORCPT ); Wed, 11 Nov 2020 11:16:16 -0500 Received: from pps.filterd (m0167088.ppops.net [127.0.0.1]) by mx0a-00128a01.pphosted.com (8.16.0.42/8.16.0.42) with SMTP id 0ABGFaB4018839; Wed, 11 Nov 2020 11:16:04 -0500 Received: from nwd2mta4.analog.com ([137.71.173.58]) by mx0a-00128a01.pphosted.com with ESMTP id 34npaax0s3-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Wed, 11 Nov 2020 11:16:03 -0500 Received: from ASHBMBX9.ad.analog.com (ashbmbx9.ad.analog.com [10.64.17.10]) by nwd2mta4.analog.com (8.14.7/8.14.7) with ESMTP id 0ABGG2S7049565 (version=TLSv1/SSLv3 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128 verify=FAIL); Wed, 11 Nov 2020 11:16:02 -0500 Received: from ASHBCASHYB4.ad.analog.com (10.64.17.132) by ASHBMBX9.ad.analog.com (10.64.17.10) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.1779.2; Wed, 11 Nov 2020 11:16:01 -0500 Received: from ASHBMBX8.ad.analog.com (10.64.17.5) by ASHBCASHYB4.ad.analog.com (10.64.17.132) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.1779.2; Wed, 11 Nov 2020 11:16:01 -0500 Received: from zeus.spd.analog.com (10.66.68.11) by ASHBMBX8.ad.analog.com (10.64.17.5) with Microsoft SMTP Server id 15.1.1779.2 via Frontend Transport; Wed, 11 Nov 2020 11:16:01 -0500 Received: from localhost.localdomain ([10.48.65.12]) by zeus.spd.analog.com (8.15.1/8.15.1) with ESMTP id 0ABGFtWi009875; Wed, 11 Nov 2020 11:15:58 -0500 From: To: , , CC: , , Alexandru Tachici Subject: [PATCH v2 2/3] hwmon: ltc2992: Add support for GPIOs. Date: Wed, 11 Nov 2020 18:20:56 +0200 Message-ID: <20201111162057.73055-3-alexandru.tachici@analog.com> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20201111162057.73055-1-alexandru.tachici@analog.com> References: <20201111162057.73055-1-alexandru.tachici@analog.com> MIME-Version: 1.0 X-Proofpoint-Virus-Version: vendor=fsecure engine=2.50.10434:6.0.312,18.0.737 definitions=2020-11-11_07:2020-11-10,2020-11-11 signatures=0 X-Proofpoint-Spam-Details: rule=outbound_notspam policy=outbound score=0 malwarescore=0 priorityscore=1501 mlxlogscore=999 clxscore=1015 lowpriorityscore=0 suspectscore=0 bulkscore=0 impostorscore=0 adultscore=0 spamscore=0 mlxscore=0 phishscore=0 classifier=spam adjust=0 reason=mlx scancount=1 engine=8.12.0-2009150000 definitions=main-2011110096 Precedence: bulk List-ID: X-Mailing-List: linux-hwmon@vger.kernel.org From: Alexandru Tachici LTC2992 has 4 open-drain GPIOS. This patch exports to user space the 4 GPIOs using the GPIO driver Linux API. Signed-off-by: Alexandru Tachici --- drivers/hwmon/Kconfig | 1 + drivers/hwmon/ltc2992.c | 160 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 160 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index bf9e387270d6..8a8eb42fb1ec 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -861,6 +861,7 @@ config SENSORS_LTC2990 config SENSORS_LTC2992 tristate "Linear Technology LTC2992" depends on I2C + depends on GPIOLIB help If you say yes here you get support for Linear Technology LTC2992 I2C System Monitor. The LTC2992 measures current, voltage, and diff --git a/drivers/hwmon/ltc2992.c b/drivers/hwmon/ltc2992.c index ce679405ac81..78951212ef8b 100644 --- a/drivers/hwmon/ltc2992.c +++ b/drivers/hwmon/ltc2992.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -56,6 +57,9 @@ #define LTC2992_G4_MAX_THRESH 0x74 #define LTC2992_G4_MIN_THRESH 0x76 #define LTC2992_FAULT3 0x92 +#define LTC2992_GPIO_STATUS 0x95 +#define LTC2992_GPIO_IO_CTRL 0x96 +#define LTC2992_GPIO_CTRL 0x97 #define LTC2992_POWER(x) (LTC2992_POWER1 + ((x) * 0x32)) #define LTC2992_POWER_MAX(x) (LTC2992_POWER1_MAX + ((x) * 0x32)) @@ -98,8 +102,18 @@ #define LTC2992_VADC_UV_LSB 25000 #define LTC2992_VADC_GPIO_UV_LSB 500 +#define LTC2992_GPIO_NR 4 +#define LTC2992_GPIO1_BIT 7 +#define LTC2992_GPIO2_BIT 6 +#define LTC2992_GPIO3_BIT 0 +#define LTC2992_GPIO4_BIT 6 +#define LTC2992_GPIO_BIT(x) (LTC2992_GPIO_NR - (x) - 1) + struct ltc2992_state { struct i2c_client *client; + struct gpio_chip gc; + struct mutex gpio_mutex; /* lock for gpio access */ + const char *gpio_names[LTC2992_GPIO_NR]; struct regmap *regmap; u32 r_sense_uohm[2]; }; @@ -113,6 +127,8 @@ struct ltc2992_gpio_regs { u8 alarm; u8 min_alarm_msk; u8 max_alarm_msk; + u8 ctrl; + u8 ctrl_bit; }; static const struct ltc2992_gpio_regs ltc2992_gpio_addr_map[] = { @@ -125,6 +141,8 @@ static const struct ltc2992_gpio_regs ltc2992_gpio_addr_map[] = { .alarm = LTC2992_FAULT1, .min_alarm_msk = LTC2992_GPIO1_FAULT_MSK(0), .max_alarm_msk = LTC2992_GPIO1_FAULT_MSK(1), + .ctrl = LTC2992_GPIO_IO_CTRL, + .ctrl_bit = LTC2992_GPIO1_BIT, }, { .data = LTC2992_G2, @@ -135,6 +153,8 @@ static const struct ltc2992_gpio_regs ltc2992_gpio_addr_map[] = { .alarm = LTC2992_FAULT2, .min_alarm_msk = LTC2992_GPIO2_FAULT_MSK(0), .max_alarm_msk = LTC2992_GPIO2_FAULT_MSK(1), + .ctrl = LTC2992_GPIO_IO_CTRL, + .ctrl_bit = LTC2992_GPIO2_BIT, }, { .data = LTC2992_G3, @@ -145,6 +165,8 @@ static const struct ltc2992_gpio_regs ltc2992_gpio_addr_map[] = { .alarm = LTC2992_FAULT3, .min_alarm_msk = LTC2992_GPIO3_FAULT_MSK(0), .max_alarm_msk = LTC2992_GPIO3_FAULT_MSK(1), + .ctrl = LTC2992_GPIO_IO_CTRL, + .ctrl_bit = LTC2992_GPIO3_BIT, }, { .data = LTC2992_G4, @@ -155,14 +177,20 @@ static const struct ltc2992_gpio_regs ltc2992_gpio_addr_map[] = { .alarm = LTC2992_FAULT3, .min_alarm_msk = LTC2992_GPIO4_FAULT_MSK(0), .max_alarm_msk = LTC2992_GPIO4_FAULT_MSK(1), + .ctrl = LTC2992_GPIO_CTRL, + .ctrl_bit = LTC2992_GPIO4_BIT, }, }; +static const char *ltc2992_gpio_names[LTC2992_GPIO_NR] = { + "GPIO1", "GPIO2", "GPIO3", "GPIO4", +}; + static int ltc2992_read_reg(struct ltc2992_state *st, u8 addr, const u8 reg_len) { u8 regvals[4]; - int ret; int val; + int ret; int i; ret = regmap_bulk_read(st->regmap, addr, regvals, reg_len); @@ -187,6 +215,132 @@ static int ltc2992_write_reg(struct ltc2992_state *st, u8 addr, const u8 reg_len return regmap_bulk_write(st->regmap, addr, regvals, reg_len); } +static int ltc2992_gpio_get(struct gpio_chip *chip, unsigned int offset) +{ + struct ltc2992_state *st = gpiochip_get_data(chip); + unsigned long gpio_status; + int reg; + + mutex_lock(&st->gpio_mutex); + reg = ltc2992_read_reg(st, LTC2992_GPIO_STATUS, 1); + mutex_unlock(&st->gpio_mutex); + + if (reg < 0) + return reg; + + gpio_status = reg; + + return !test_bit(LTC2992_GPIO_BIT(offset), &gpio_status); +} + +static int ltc2992_gpio_get_multiple(struct gpio_chip *chip, unsigned long *mask, + unsigned long *bits) +{ + struct ltc2992_state *st = gpiochip_get_data(chip); + unsigned long gpio_status; + unsigned int gpio_nr; + int reg; + + mutex_lock(&st->gpio_mutex); + reg = ltc2992_read_reg(st, LTC2992_GPIO_STATUS, 1); + mutex_unlock(&st->gpio_mutex); + + if (reg < 0) + return reg; + + gpio_status = reg; + + gpio_nr = 0; + for_each_set_bit_from(gpio_nr, mask, LTC2992_GPIO_NR) { + if (test_bit(LTC2992_GPIO_BIT(gpio_nr), &gpio_status)) + set_bit(gpio_nr, bits); + } + + return 0; +} + +static void ltc2992_gpio_set(struct gpio_chip *chip, unsigned int offset, int value) +{ + struct ltc2992_state *st = gpiochip_get_data(chip); + unsigned long gpio_ctrl; + int reg; + + mutex_lock(&st->gpio_mutex); + reg = ltc2992_read_reg(st, ltc2992_gpio_addr_map[offset].ctrl, 1); + if (reg < 0) { + mutex_unlock(&st->gpio_mutex); + return; + } + + gpio_ctrl = reg; + assign_bit(ltc2992_gpio_addr_map[offset].ctrl_bit, &gpio_ctrl, value); + + ltc2992_write_reg(st, ltc2992_gpio_addr_map[offset].ctrl, 1, gpio_ctrl); + mutex_unlock(&st->gpio_mutex); +} + +static void ltc2992_gpio_set_multiple(struct gpio_chip *chip, unsigned long *mask, + unsigned long *bits) +{ + struct ltc2992_state *st = gpiochip_get_data(chip); + unsigned long gpio_ctrl_io = 0; + unsigned long gpio_ctrl = 0; + unsigned int gpio_nr; + + for_each_set_bit(gpio_nr, mask, LTC2992_GPIO_NR) { + if (gpio_nr < 3) + assign_bit(ltc2992_gpio_addr_map[gpio_nr].ctrl_bit, &gpio_ctrl_io, true); + + if (gpio_nr == 3) + assign_bit(ltc2992_gpio_addr_map[gpio_nr].ctrl_bit, &gpio_ctrl, true); + } + + mutex_lock(&st->gpio_mutex); + ltc2992_write_reg(st, LTC2992_GPIO_IO_CTRL, 1, gpio_ctrl_io); + ltc2992_write_reg(st, LTC2992_GPIO_CTRL, 1, gpio_ctrl); + mutex_unlock(&st->gpio_mutex); +} + +static int ltc2992_config_gpio(struct ltc2992_state *st) +{ + const char *name = dev_name(&st->client->dev); + char *gpio_name; + int ret; + int i; + + ret = ltc2992_write_reg(st, LTC2992_GPIO_IO_CTRL, 1, 0); + if (ret < 0) + return ret; + + mutex_init(&st->gpio_mutex); + + for (i = 0; i < ARRAY_SIZE(st->gpio_names); i++) { + gpio_name = devm_kasprintf(&st->client->dev, GFP_KERNEL, "ltc2992-%x-%s", + st->client->addr, ltc2992_gpio_names[i]); + if (!gpio_name) + return -ENOMEM; + + st->gpio_names[i] = gpio_name; + } + + st->gc.label = name; + st->gc.parent = &st->client->dev; + st->gc.owner = THIS_MODULE; + st->gc.base = -1; + st->gc.names = st->gpio_names; + st->gc.ngpio = ARRAY_SIZE(st->gpio_names); + st->gc.get = ltc2992_gpio_get; + st->gc.get_multiple = ltc2992_gpio_get_multiple; + st->gc.set = ltc2992_gpio_set; + st->gc.set_multiple = ltc2992_gpio_set_multiple; + + ret = devm_gpiochip_add_data(&st->client->dev, &st->gc, st); + if (ret) + dev_err(&st->client->dev, "GPIO registering failed (%d)\n", ret); + + return ret; +} + static umode_t ltc2992_is_visible(const void *data, enum hwmon_sensor_types type, u32 attr, int channel) { @@ -784,6 +938,10 @@ static int ltc2992_i2c_probe(struct i2c_client *client, const struct i2c_device_ if (ret < 0) return ret; + ret = ltc2992_config_gpio(st); + if (ret < 0) + return ret; + hwmon_dev = devm_hwmon_device_register_with_info(&client->dev, client->name, st, <c2992_chip_info, NULL); From patchwork Wed Nov 11 16:20:57 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Alexandru Tachici X-Patchwork-Id: 11898087 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id DEABF1391 for ; Wed, 11 Nov 2020 16:16:24 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id B703D2087D for ; Wed, 11 Nov 2020 16:16:24 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1727459AbgKKQQS (ORCPT ); Wed, 11 Nov 2020 11:16:18 -0500 Received: from mx0a-00128a01.pphosted.com ([148.163.135.77]:17104 "EHLO mx0a-00128a01.pphosted.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1727387AbgKKQQS (ORCPT ); Wed, 11 Nov 2020 11:16:18 -0500 Received: from pps.filterd (m0167089.ppops.net [127.0.0.1]) by mx0a-00128a01.pphosted.com (8.16.0.42/8.16.0.42) with SMTP id 0ABG7h8P031217; Wed, 11 Nov 2020 11:16:04 -0500 Received: from nwd2mta4.analog.com ([137.71.173.58]) by mx0a-00128a01.pphosted.com with ESMTP id 34nsc95tcn-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Wed, 11 Nov 2020 11:16:04 -0500 Received: from SCSQMBX11.ad.analog.com (SCSQMBX11.ad.analog.com [10.77.17.10]) by nwd2mta4.analog.com (8.14.7/8.14.7) with ESMTP id 0ABGG35v049568 (version=TLSv1/SSLv3 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128 verify=FAIL); Wed, 11 Nov 2020 11:16:03 -0500 Received: from SCSQMBX11.ad.analog.com (10.77.17.10) by SCSQMBX11.ad.analog.com (10.77.17.10) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.1779.2; Wed, 11 Nov 2020 08:16:01 -0800 Received: from zeus.spd.analog.com (10.66.68.11) by SCSQMBX11.ad.analog.com (10.77.17.10) with Microsoft SMTP Server id 15.1.1779.2 via Frontend Transport; Wed, 11 Nov 2020 08:16:01 -0800 Received: from localhost.localdomain ([10.48.65.12]) by zeus.spd.analog.com (8.15.1/8.15.1) with ESMTP id 0ABGFtWj009875; Wed, 11 Nov 2020 11:15:59 -0500 From: To: , , CC: , , Alexandru Tachici Subject: [PATCH v2 3/3] dt-binding: hwmon: Add documentation for ltc2992 Date: Wed, 11 Nov 2020 18:20:57 +0200 Message-ID: <20201111162057.73055-4-alexandru.tachici@analog.com> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20201111162057.73055-1-alexandru.tachici@analog.com> References: <20201111162057.73055-1-alexandru.tachici@analog.com> MIME-Version: 1.0 X-Proofpoint-Virus-Version: vendor=fsecure engine=2.50.10434:6.0.312,18.0.737 definitions=2020-11-11_07:2020-11-10,2020-11-11 signatures=0 X-Proofpoint-Spam-Details: rule=outbound_notspam policy=outbound score=0 suspectscore=0 spamscore=0 priorityscore=1501 bulkscore=0 clxscore=1015 lowpriorityscore=0 impostorscore=0 adultscore=0 phishscore=0 mlxlogscore=999 malwarescore=0 mlxscore=0 classifier=spam adjust=0 reason=mlx scancount=1 engine=8.12.0-2009150000 definitions=main-2011110095 Precedence: bulk List-ID: X-Mailing-List: linux-hwmon@vger.kernel.org From: Alexandru Tachici Add documentation for ltc2992. Signed-off-by: Alexandru Tachici Reviewed-by: Rob Herring --- .../bindings/hwmon/adi,ltc2992.yaml | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 Documentation/devicetree/bindings/hwmon/adi,ltc2992.yaml diff --git a/Documentation/devicetree/bindings/hwmon/adi,ltc2992.yaml b/Documentation/devicetree/bindings/hwmon/adi,ltc2992.yaml new file mode 100644 index 000000000000..64a8fcb7bc46 --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/adi,ltc2992.yaml @@ -0,0 +1,80 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/hwmon/adi,ltc2992.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Linear Technology 2992 Power Monitor + +maintainers: + - Alexandru Tachici + +description: | + Linear Technology 2992 Dual Wide Range Power Monitor + https://www.analog.com/media/en/technical-documentation/data-sheets/ltc2992.pdf + +properties: + compatible: + enum: + - adi,ltc2992 + + reg: + maxItems: 1 + + '#address-cells': + const: 1 + + '#size-cells': + const: 0 + + avcc-supply: true + +patternProperties: + "^channel@([0-1])$": + type: object + description: | + Represents the two supplies to be monitored. + + properties: + reg: + description: | + The channel number. LTC2992 can monitor two supplies. + items: + minimum: 0 + maximum: 1 + + shunt-resistor-micro-ohms: + description: + The value of curent sense resistor in microohms. + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + i2c1 { + #address-cells = <1>; + #size-cells = <0>; + + ltc2992@6F { + #address-cells = <1>; + #size-cells = <0>; + + compatible = "adi,ltc2992"; + reg = <0x6F>; + + channel@0 { + reg = <0x0>; + shunt-resistor-micro-ohms = <10000>; + }; + + channel@1 { + reg = <0x1>; + shunt-resistor-micro-ohms = <10000>; + }; + }; + }; +...