From 0e4dd5cf7400e4102b5bbd1f729b349eaf3d1200 Mon Sep 17 00:00:00 2001 From: Kent Libetario Date: Tue, 4 Mar 2025 17:13:05 +0800 Subject: [PATCH 1/6] dt-bindings: mfd: add adi,max42500 The MAX42500 component structure of the nodes and properties of the serial interfaces and the I/O ports to access, control and configure the voltage monitors, and to program the power sequence timestamp recordings and the windowed watchdog timing. Signed-off-by: Kent Libetario --- .../devicetree/bindings/mfd/adi,max42500.yaml | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 Documentation/devicetree/bindings/mfd/adi,max42500.yaml diff --git a/Documentation/devicetree/bindings/mfd/adi,max42500.yaml b/Documentation/devicetree/bindings/mfd/adi,max42500.yaml new file mode 100644 index 00000000000000..cb51cd9bc66260 --- /dev/null +++ b/Documentation/devicetree/bindings/mfd/adi,max42500.yaml @@ -0,0 +1,58 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/hwmon/adi,max42500.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Analog Devices MAX42500 Industrial Power System Monitor + +maintainers: + - Kent Libetario + +description: | + Analog Devices MAX42500 Industrial Power System Monitor Family + https://www.analog.com/media/en/technical-documentation/data-sheets/max42500.pdf + +properties: + compatible: + enum: + - adi,max42500 + reg: + maxItems: 1 + + poweroff-gpios: + description: + This property sets the GPIO as output. Then used to control the power and + mode state of the device. This can be enabled optionally to control the + state of comparators in the voltage monitoring and/or trigger timestamp + recording in the flexible power sequence recording functions. Check the + state transition conditions in datasheet for more information. + maxItems: 1 + + sleepoff-gpios: + description: + This property sets the GPIO as output. Then used to control the power and + mode state of the device. This can be enabled and masked optionally to + control the state of comparators in the voltage monitoring and/or trigger + timestamp recording in the flexible power sequence recording functions. + Check the state transition conditions in datasheet for more information. + maxItems: 1 + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + #include + i2c { + #address-cells = <1>; + #size-cells = <0>; + + hwmon@28 { + compatible = "adi,max42500"; + reg = <0x28>; + }; + }; From 979e451b137039d6fdf9d39269f76f27533e8e01 Mon Sep 17 00:00:00 2001 From: Kent Libetario Date: Tue, 4 Mar 2025 17:23:01 +0800 Subject: [PATCH 2/6] mfd: add driver for max42500 The MAX42500 is a multiple function device for power system monitoring, power sequence timestamp recordings and windowed watchdog combined all- together in one SoC package. These features can be used independently or at the same time in a wide range of applications. Signed-off-by: Kent Libetario --- drivers/mfd/Kconfig | 13 +++ drivers/mfd/Makefile | 1 + drivers/mfd/max42500.c | 57 ++++++++++++ include/linux/mfd/max42500.h | 170 +++++++++++++++++++++++++++++++++++ 4 files changed, 241 insertions(+) create mode 100644 drivers/mfd/max42500.c create mode 100644 include/linux/mfd/max42500.h diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 90ce58fd629e5f..60e19d1db3a96d 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -808,6 +808,19 @@ config MFD_MAX14577 additional drivers must be enabled in order to use the functionality of the device. +config MFD_MAX42500 + bool "Analog Devices MAX42500 Industrial Power System Monitor" + depends on I2C + select MFD_CORE + select REGMAP_I2C + help + Say yes here to add support for MAX42500 SoC power-system monitor + with up to seven voltage monitor. The driver also contains a + programmable challenge/response watchdog, which is accessible through + the I2C interface, along with a configurable RESET output. Additional + drivers can be enabled in order to use the following functionalities + of the device: GPIO. + config MFD_MAX77541 tristate "Analog Devices MAX77541/77540 PMIC Support" depends on I2C=y diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index c66f07edcd0e62..9a3358fd41d9d7 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -157,6 +157,7 @@ obj-$(CONFIG_MFD_DA9063) += da9063.o obj-$(CONFIG_MFD_DA9150) += da9150-core.o obj-$(CONFIG_MFD_MAX14577) += max14577.o +obj-$(CONFIG_MFD_MAX42500) += max42500.o obj-$(CONFIG_MFD_MAX77541) += max77541.o obj-$(CONFIG_MFD_MAX77620) += max77620.o obj-$(CONFIG_MFD_MAX77650) += max77650.o diff --git a/drivers/mfd/max42500.c b/drivers/mfd/max42500.c new file mode 100644 index 00000000000000..5564cd9f6d3cb2 --- /dev/null +++ b/drivers/mfd/max42500.c @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * MAX42500 - MFD Power System Monitor + * + * Copyright 2025 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include + +static const struct mfd_cell max42500_subdev[] = { + MFD_CELL_NAME("max42500-hwmon"), + MFD_CELL_NAME("max42500-wdt"), +}; + +static const struct regmap_config max42500_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = MAX42500_REG_CID, +}; + +static int max42500_probe(struct i2c_client *i2c) +{ + struct regmap *map; + + map = devm_regmap_init_i2c(i2c, &max42500_regmap_config); + if (IS_ERR(map)) + return PTR_ERR(map); + + return devm_mfd_add_devices(&i2c->dev, PLATFORM_DEVID_AUTO, + max42500_subdev, ARRAY_SIZE(max42500_subdev), + NULL, 0, NULL); +} + +static const struct of_device_id max42500_of_match[] = { + { .compatible = "adi,max42500" }, + { } +}; +MODULE_DEVICE_TABLE(of, max42500_of_match); + +static struct i2c_driver max42500_driver = { + .driver = { + .name = "max42500", + .of_match_table = max42500_of_match, + }, + .probe = max42500_probe, +}; +module_i2c_driver(max42500_driver); + +MODULE_AUTHOR("Kent Libetario "); +MODULE_DESCRIPTION("MFD driver for MAX42500"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/mfd/max42500.h b/include/linux/mfd/max42500.h new file mode 100644 index 00000000000000..2ecf5185a90aa6 --- /dev/null +++ b/include/linux/mfd/max42500.h @@ -0,0 +1,170 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * MAX42500 - Industrial Power System Monitor + * + * Copyright 2025 Analog Devices Inc. + */ + +#ifndef __MFD_MAX42500_H +#define __MFD_MAX42500_H + +#include +#include + +/* Command registers */ +#define MAX42500_REG_ID 0x00 +#define MAX42500_REG_CONFIG1 0x01 +#define MAX42500_REG_CONFIG2 0x02 +#define MAX42500_REG_VMON 0x03 +#define MAX42500_REG_RSTMAP 0x04 +#define MAX42500_REG_STATOV 0x05 +#define MAX42500_REG_STATUV 0x06 +#define MAX42500_REG_STATOFF 0x07 +#define MAX42500_REG_VIN1 0x08 +#define MAX42500_REG_VIN2 0x09 +#define MAX42500_REG_VIN3 0x0A +#define MAX42500_REG_VIN4 0x0B +#define MAX42500_REG_VIN5 0x0C +#define MAX42500_REG_VINO6 0x0D +#define MAX42500_REG_VINU6 0x0E +#define MAX42500_REG_VINO7 0x0F +#define MAX42500_REG_VINU7 0x10 +#define MAX42500_REG_OVUV1 0x11 +#define MAX42500_REG_OVUV2 0x12 +#define MAX42500_REG_OVUV3 0x13 +#define MAX42500_REG_OVUV4 0x14 +#define MAX42500_REG_OVUV5 0x15 +#define MAX42500_REG_FPSTAT1 0x16 +#define MAX42500_REG_FPSCFG1 0x17 +#define MAX42500_REG_UTIME1 0x18 +#define MAX42500_REG_UTIME2 0x19 +#define MAX42500_REG_UTIME3 0x1A +#define MAX42500_REG_UTIME4 0x1B +#define MAX42500_REG_UTIME5 0x1C +#define MAX42500_REG_UTIME6 0x1D +#define MAX42500_REG_UTIME7 0x1E +#define MAX42500_REG_DTIME1 0x1F +#define MAX42500_REG_DTIME2 0x20 +#define MAX42500_REG_DTIME3 0x21 +#define MAX42500_REG_DTIME4 0x22 +#define MAX42500_REG_DTIME5 0x23 +#define MAX42500_REG_DTIME6 0x24 +#define MAX42500_REG_DTIME7 0x25 +#define MAX42500_REG_WDSTAT 0x26 +#define MAX42500_REG_WDCDIV 0x27 +#define MAX42500_REG_WDCFG1 0x28 +#define MAX42500_REG_WDCFG2 0x29 +#define MAX42500_REG_WDKEY 0x2A +#define MAX42500_REG_WDLOCK 0x2B +#define MAX42500_REG_RSTCTRL 0x2C +#define MAX42500_REG_CID 0x2D + +/* Device chip registers */ +#define MAX42500_CHIP_ID_MASK GENMASK(7, 0) +#define MAX42500_CHIP_ID(x) FIELD_GET(MAX42500_CHIP_ID_MASK, x) +#define MAX42500_CHIP_CID_MASK GENMASK(7, 0) +#define MAX42500_CHIP_CID(x) FIELD_GET(MAX42500_CHIP_CID_MASK, x) +#define MAX42500_CHIP_CFG2_MASK GENMASK(7, 0) +#define MAX42500_CHIP_CFG2(x) FIELD_GET(MAX42500_CHIP_PECE_MASK, x) +#define MAX42500_CHIP_PECE_MASK BIT(0) +#define MAX42500_CHIP_PECE(x) FIELD_GET(MAX42500_CHIP_PECE_MASK, x) +#define MAX42500_CHIP_MBST_MASK BIT(1) +#define MAX42500_CHIP_MBST(x) FIELD_GET(MAX42500_CHIP_MBST_MASK, x) +#define MAX42500_CHIP_RR_MASK BIT(2) +#define MAX42500_CHIP_RR(x) FIELD_GET(MAX42500_CHIP_RR_MASK, x) + +/* Voltage Monitor registers */ +#define MAX42500_VMON_VM1_MASK BIT(0) +#define MAX42500_VMON_VM1(x) FIELD_GET(MAX42500_VMON_VM1_MASK, x) +#define MAX42500_VMON_VM2_MASK BIT(1) +#define MAX42500_VMON_VM2(x) FIELD_GET(MAX42500_VMON_VM2_MASK, x) +#define MAX42500_VMON_VM3_MASK BIT(2) +#define MAX42500_VMON_VM3(x) FIELD_GET(MAX42500_VMON_VM3_MASK, x) +#define MAX42500_VMON_VM4_MASK BIT(3) +#define MAX42500_VMON_VM4(x) FIELD_GET(MAX42500_VMON_VM4_MASK, x) +#define MAX42500_VMON_VM5_MASK BIT(4) +#define MAX42500_VMON_VM5(x) FIELD_GET(MAX42500_VMON_VM5_MASK, x) +#define MAX42500_VMON_VM6_MASK BIT(5) +#define MAX42500_VMON_VM6(x) FIELD_GET(MAX42500_VMON_VM6_MASK, x) +#define MAX42500_VMON_VM7_MASK BIT(6) +#define MAX42500_VMON_VM7(x) FIELD_GET(MAX42500_VMON_VM7_MASK, x) +#define MAX42500_VMON_VMPD_MASK BIT(7) +#define MAX42500_VMON_VMPD(x) FIELD_GET(MAX42500_VMON_VMPD_MASK, x) +#define MAX42500_VMON_RST1_MASK BIT(0) +#define MAX42500_VMON_RST1(x) FIELD_GET(MAX42500_VMON_RST1_MASK, x) +#define MAX42500_VMON_RST2_MASK BIT(1) +#define MAX42500_VMON_RST2(x) FIELD_GET(MAX42500_VMON_RST2_MASK, x) +#define MAX42500_VMON_RST3_MASK BIT(2) +#define MAX42500_VMON_RST3(x) FIELD_GET(MAX42500_VMON_RST3_MASK, x) +#define MAX42500_VMON_RST4_MASK BIT(3) +#define MAX42500_VMON_RST4(x) FIELD_GET(MAX42500_VMON_RST4_MASK, x) +#define MAX42500_VMON_RST5_MASK BIT(4) +#define MAX42500_VMON_RST5(x) FIELD_GET(MAX42500_VMON_RST5_MASK, x) +#define MAX42500_VMON_RST6_MASK BIT(5) +#define MAX42500_VMON_RST6(x) FIELD_GET(MAX42500_VMON_RST6_MASK, x) +#define MAX42500_VMON_RST7_MASK BIT(6) +#define MAX42500_VMON_RST7(x) FIELD_GET(MAX42500_VMON_RST7_MASK, x) +#define MAX42500_VMON_PARM_MASK BIT(7) +#define MAX42500_VMON_PARM(x) FIELD_GET(MAX42500_VMON_PARM_MASK, x) +#define MAX42500_VMON_1VIN5_MASK GENMASK(7, 0) +#define MAX42500_VMON_1VIN5(x) FIELD_GET(MAX42500_VMON_1VIN5_MASK, x) +#define MAX42500_VMON_1VINU5_MASK GENMASK(3, 0) +#define MAX42500_VMON_1VINU5(x) FIELD_GET(MAX42500_VMON_1VINU5_MASK, x) +#define MAX42500_VMON_6VINU7_MASK GENMASK(7, 0) +#define MAX42500_VMON_6VINU7(x) FIELD_GET(MAX42500_VMON_6VINU7_MASK, x) +#define MAX42500_VMON_1VINO5_MASK GENMASK(7, 4) +#define MAX42500_VMON_1VINO5(x) FIELD_GET(MAX42500_VMON_1VINO5_MASK, x) +#define MAX42500_VMON_6VINO7_MASK GENMASK(7, 0) +#define MAX42500_VMON_6VINO7(x) FIELD_GET(MAX42500_VMON_6VINO7_MASK, x) +#define MAX42500_VMON_STATOFF_MASK GENMASK(7, 0) +#define MAX42500_VMON_STATOFF(x) FIELD_GET(MAX42500_VMON_STATOFF_MASK, x) +#define MAX42500_VMON_STATUV_MASK GENMASK(7, 0) +#define MAX42500_VMON_STATUV(x) FIELD_GET(MAX42500_VMON_STATUV_MASK, x) +#define MAX42500_VMON_STATOV_MASK GENMASK(7, 0) +#define MAX42500_VMON_STATOV(x) FIELD_GET(MAX42500_VMON_STATOV_MASK, x) + +/* Sequence recorder registers */ +#define MAX42500_FPSR_FPSTAT1_MASK GENMASK(7, 0) +#define MAX42500_FPSR_FPSTAT1(x) FIELD_GET(MAX42500_FPSR_FPSTAT1_MASK, x) +#define MAX42500_FPSR_FDIV_MASK GENMASK(2, 0) +#define MAX42500_FPSR_FDIV(x) FIELD_GET(MAX42500_FPSR_FDIV_MASK, x) +#define MAX42500_FPSR_FPSEN1_MASK BIT(3) +#define MAX42500_FPSR_FPSEN1(x) FIELD_GET(MAX42500_FPSR_FPSEN1_MASK, x) +#define MAX42500_FPSR_DVALM_MASK BIT(4) +#define MAX42500_FPSR_DVALM(x) FIELD_GET(MAX42500_FPSR_DVALM_MASK, x) +#define MAX42500_FPSR_UVALM_MASK BIT(5) +#define MAX42500_FPSR_UVALM(x) FIELD_GET(MAX42500_FPSR_UVALM_MASK, x) +#define MAX42500_FPSR_DVAL_MASK BIT(6) +#define MAX42500_FPSR_DVAL(x) FIELD_GET(MAX42500_FPSR_DVAL_MASK, x) +#define MAX42500_FPSR_UVAL_MASK BIT(7) +#define MAX42500_FPSR_UVAL(x) FIELD_GET(MAX42500_FPSR_UVAL_MASK, x) +#define MAX42500_FPSR_1UTIM7_MASK GENMASK(7, 0) +#define MAX42500_FPSR_1UTIM7(x) FIELD_GET(MAX42500_FPSR_1UTIM7_MASK, x) +#define MAX42500_FPSR_1DTIM7_MASK GENMASK(7, 0) +#define MAX42500_FPSR_1DTIM7(x) FIELD_GET(MAX42500_FPSR_1DTIM7_MASK, x) + +/* Windowed watchdog register*/ +#define MAX42500_WDOG_WDSTAT_MASK GENMASK(7, 0) +#define MAX42500_WDOG_WDSTAT(x) FIELD_GET(MAX42500_WDOG_WDSTAT_MASK, x) +#define MAX42500_WDOG_WDIV_MASK GENMASK(5, 0) +#define MAX42500_WDOG_WDIV(x) FIELD_GET(MAX42500_WDOG_WDIV_MASK, x) +#define MAX42500_WDOG_SWW_MASK BIT(6) +#define MAX42500_WDOG_SWW(x) FIELD_GET(MAX42500_WDOG_SWW_MASK, x) +#define MAX42500_WDOG_WDOPEN_MASK GENMASK(3, 0) +#define MAX42500_WDOG_WDOPEN(x) FIELD_GET(MAX42500_WDOG_WDOPEN_MASK, x) +#define MAX42500_WDOG_WDCLOSE_MASK GENMASK(7, 4) +#define MAX42500_WDOG_WDCLOSE(x) FIELD_GET(MAX42500_WDOG_WDCLOSE_MASK, x) +#define MAX42500_WDOG_1UD_MASK GENMASK(2, 0) +#define MAX42500_WDOG_1UD(x) FIELD_GET(MAX42500_WDOG_1UD_MASK, x) +#define MAX42500_WDOG_WDEN_MASK BIT(3) +#define MAX42500_WDOG_WDEN(x) FIELD_GET(MAX42500_WDOG_WDEN_MASK, x) +#define MAX42500_WDOG_WDKEY_MASK GENMASK(7, 0) +#define MAX42500_WDOG_WDKEY(x) FIELD_GET(MAX42500_WDOG_WDKEY_MASK, x) +#define MAX42500_WDOG_WDLOCK_MASK BIT(0) +#define MAX42500_WDOG_WDLOCK(x) FIELD_GET(MAX42500_WDOG_WDLOCK_MASK, x) +#define MAX42500_WDOG_RHLD_MASK GENMASK(1, 0) +#define MAX42500_WDOG_RHLD(x) FIELD_GET(MAX42500_WDOG_RHLD_MASK, x) +#define MAX42500_WDOG_MR1_MASK BIT(2) +#define MAX42500_WDOG_MR1(x) FIELD_GET(MAX42500_WDOG_MR1_MASK, x) + +#endif /* __MFD_MAX42500_H */ From e33a857762eecedd1176334b6b58d11f6f15cd98 Mon Sep 17 00:00:00 2001 From: Kent Libetario Date: Tue, 4 Mar 2025 17:24:33 +0800 Subject: [PATCH 3/6] doc: add supported sysfs for max42500 The MAX42500 supports standard power management, hwmon and watchdog ABI and sysfs attribute entries to expose the sensor data of the voltage monitors, power sequence timestamp recordings and windowed watchdog to the userspace. Signed-off-by: Kent Libetario --- Documentation/hwmon/max42500-hwmon.rst | 135 +++++++++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 Documentation/hwmon/max42500-hwmon.rst diff --git a/Documentation/hwmon/max42500-hwmon.rst b/Documentation/hwmon/max42500-hwmon.rst new file mode 100644 index 00000000000000..5a8acaa701c9a2 --- /dev/null +++ b/Documentation/hwmon/max42500-hwmon.rst @@ -0,0 +1,135 @@ +.. SPDX-License-Identifier: GPL-2.0-or-later + +Kernel driver max42500 +====================== + +Supported chips: + * Analog Devices MAX42500 + + Prefix: 'max42500' + + Addresses scanned: I2C 0x28 to 0x2B + + Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/max42500.pdf + +Author: Kent Libetario + +Description +----------- + +This driver supports hardware monitoring of MAX42500 power system with up +to seven voltage monitor inputs. Each input has programmable overvoltage +(OV) and undervoltage (UV) thresholds where two of the inputs support +dynamic voltage scaling (DVS). Additionally, the MAX42500 features a +programmable flexible power sequence recorder that stores timestamps +separately. And also, the MAX42500 has a programmable challenge and +response watchdog with a configurable RESET output. + + +Usage Notes +----------- + +This driver does not auto-detect devices. You will have to instantiate the +devices explicitly. Please see Documentation/i2c/instantiating-devices.rst +for details. + +Due to its multi-functionality, the MAX42500 is split into three drivers: +the mfd driver as the main device, and the hwmon and watchdog drivers as +the sub-devices. The mfd driver is a client to the core driver and +consumed by both the hwmon and watchdog drivers. + +Optionally, two power management GPIOs are provided by the mfd driver and +consumed only by the hwmon sub-device driver. The pins are fixed-outputs +to control the voltage monitor comparators and trigger the power sequence +timestamp recording of the device. Please see the datasheet for details. + +For the two GPIO output pins to be consumed, the device tree will look +like this: + +.. code-block:: + + i2c { + #address-cells = <1>; + #size-cells = <0>; + poweroff-gpios + sleepoff-gpios + + hwmon@28 { + compatible = "adi,max42500"; + reg = <0x28>; + }; + }; + +Otherwise, the two pins maybe omitted in the device tree if unused. + + +Platform data support +--------------------- + +The Hwmon driver supports standard Hwmon ABI driver and sysfs platform +data. While the watchdog driver supports the standard watchdog ABI driver +and sysfs platform data. + + +Hwmon Sysfs entries +------------------- + +The following attributes are supported. Limits are read-write; all other +attributes are read-only. + + +Chip +~~~~ + +======================= ======================================================= +chip_pec Enable or disable PEC: PECE bit in CONFIG1 register +======================= ======================================================= + +In +~~ + +======================= ====================================================== +in[1-7]_label "VMON[1-7]" +in[1-7]_enable Enable or disable voltage monitors: VM1 to VM7 bits of + VMON register +in[1-5]_min Nominal Voltage set point: VIN1 to VIN5 registers +in[1-5]_lcrit IN1-IN5 UV threshold: UV1 to UV5 nibbles of OVUV1 to + OVUV5 registers +in[6-7]_lcrit IN6-IN7 UV threshold: VINU6 to VINU7 registers +in[1-5]_crit IN1-IN5 OV threshold: OV1 to OV5 nibbles of OVUV1 to + OVUV5 registers +in[6-7]_crit IN6-IN7 OV threshold: VINO6 to VINO7 registers +in[1-7]_reset_history Enable or disable reset mapping: IN1 to IN7 bits of + RSTMAP register +======================= ====================================================== + +Power +~~~~~ + +=============================== =============================================== +power[1-7]_label "STATUS[1-7]" +power_enable OFF comparator status: STATOFF register +power_lcrit_alarm UV comparator status: STATUV register +power_crit_alarm OV comparator status: STATOV register +power[1-7]_average_interval_min Power-Down sequence time-stamp: DTIME1 to + DTIME7 registers +power[1-7]_average_interval_max Power-Up sequence time-stamp: UTIME1 to + UTIME7 registers +=============================== =============================================== + + +Watchdog Sysfs entries +---------------------- + +======================= ======================================================= +start Enable the watchdog: WDEN bit of WDCFG2 register +stop Disable the watchdog: WDEN bit of WDCFG2 register +ping Set the watchdog key to the device: WDKEY register +status Watchdog status: WDSTAT register +set_timeout Watchdog Clock Divider: WDIV bits of WDCDIV register +set_pretimeout First Update Extension: 1UD bit of WDCFG2 register +restart Reset Hold or Active Timeout: RHLD bits of RSTCTRL + register +======================= ======================================================= + +.. From 516a2b98fdd8e52e48c384396342a6af51cd44cb Mon Sep 17 00:00:00 2001 From: Kent Libetario Date: Tue, 4 Mar 2025 17:25:57 +0800 Subject: [PATCH 4/6] hwmon: add driver for max42500-hwmon The MAX42500 is a SoC power-system monitor with up to seven voltage monitor inputs that has programmable OV/UV thresholds and support DVS by I2C interface. Also, it has a programmable flexible power sequence recorder that stores the power-up and power-down timestamps separately. Signed-off-by: Kent Libetario --- drivers/hwmon/Kconfig | 12 + drivers/hwmon/Makefile | 1 + drivers/hwmon/max42500-hwmon.c | 1348 ++++++++++++++++++++++++++++++++ 3 files changed, 1361 insertions(+) create mode 100644 drivers/hwmon/max42500-hwmon.c diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index b7fa01e9dd892e..d72debedd2839b 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1131,6 +1131,18 @@ config MAX31827 This driver can also be built as a module. If so, the module will be called max31827. +config SENSORS_MAX42500 + tristate "MAX42500 Industrial Power System Monitor" + depends on MFD_MAX42500 + help + If you say yes here you get support for MAX42500 SoC power-system + monitor with up to seven voltage monitor. The driver also contains a + programmable challenge/response watchdog, which is accessible through + the I2C interface, along with a configurable RESET output. + + This driver can also be built as a module. If so, the module + will be called max42500-hwmon. + config SENSORS_MAX6620 tristate "Maxim MAX6620 fan controller" depends on I2C diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index f324d057535ad9..91605b3368c552 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -145,6 +145,7 @@ obj-$(CONFIG_SENSORS_MAX197) += max197.o obj-$(CONFIG_SENSORS_MAX31722) += max31722.o obj-$(CONFIG_SENSORS_MAX31730) += max31730.o obj-$(CONFIG_SENSORS_MAX31760) += max31760.o +obj-$(CONFIG_SENSORS_MAX42500) += max42500-hwmon.o obj-$(CONFIG_SENSORS_MAX6620) += max6620.o obj-$(CONFIG_SENSORS_MAX6621) += max6621.o obj-$(CONFIG_SENSORS_MAX6639) += max6639.o diff --git a/drivers/hwmon/max42500-hwmon.c b/drivers/hwmon/max42500-hwmon.c new file mode 100644 index 00000000000000..350cc1c9e29e26 --- /dev/null +++ b/drivers/hwmon/max42500-hwmon.c @@ -0,0 +1,1348 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * MAX42500 - Industrial Power System Monitor + * + * Copyright 2025 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +enum MAX42500_chip { + MAX42500_CHIP_LABEL, + MAX42500_CHIP_NAME, + MAX42500_CHIP_RELOAD_OTP_ENABLE, + MAX42500_CHIP_BIST_RESET_ENABLE, + MAX42500_CHIP_PEC_ENABLE, + MAX42500_CHIP_CONFIG_STATUS, + MAX42500_CHIP_MAX +}; + +static const u8 max42500_reg_chip[] = { + MAX42500_REG_ID, + MAX42500_REG_CID, + MAX42500_REG_CONFIG1, + MAX42500_REG_CONFIG1, + MAX42500_REG_CONFIG1, + MAX42500_REG_CONFIG2 +}; + +enum MAX42500_vmon { + MAX42500_VMON_IN1_ENABLE, + MAX42500_VMON_IN2_ENABLE, + MAX42500_VMON_IN3_ENABLE, + MAX42500_VMON_IN4_ENABLE, + MAX42500_VMON_IN5_ENABLE, + MAX42500_VMON_IN6_ENABLE, + MAX42500_VMON_IN7_ENABLE, + MAX42500_VMON_PDN_ENABLE, + MAX42500_VMON_IN1_RESET_ENABLE, + MAX42500_VMON_IN2_RESET_ENABLE, + MAX42500_VMON_IN3_RESET_ENABLE, + MAX42500_VMON_IN4_RESET_ENABLE, + MAX42500_VMON_IN5_RESET_ENABLE, + MAX42500_VMON_IN6_RESET_ENABLE, + MAX42500_VMON_IN7_RESET_ENABLE, + MAX42500_VMON_PAR_ENABLE, + MAX42500_VMON_OV_STATUS, + MAX42500_VMON_UV_STATUS, + MAX42500_VMON_OFF_STATUS, + MAX42500_VMON_IN1_NOMINAL, + MAX42500_VMON_IN2_NOMINAL, + MAX42500_VMON_IN3_NOMINAL, + MAX42500_VMON_IN4_NOMINAL, + MAX42500_VMON_IN5_NOMINAL, + MAX42500_VMON_IN1_OV_THRESH, + MAX42500_VMON_IN2_OV_THRESH, + MAX42500_VMON_IN3_OV_THRESH, + MAX42500_VMON_IN4_OV_THRESH, + MAX42500_VMON_IN5_OV_THRESH, + MAX42500_VMON_IN6_CRIT, + MAX42500_VMON_IN7_CRIT, + MAX42500_VMON_IN1_UV_THRESH, + MAX42500_VMON_IN2_UV_THRESH, + MAX42500_VMON_IN3_UV_THRESH, + MAX42500_VMON_IN4_UV_THRESH, + MAX42500_VMON_IN5_UV_THRESH, + MAX42500_VMON_IN6_LCRIT, + MAX42500_VMON_IN7_LCRIT, + MAX42500_VMON_MAX +}; + +static const u8 max42500_reg_vmon[] = { + MAX42500_REG_VMON, + MAX42500_REG_VMON, + MAX42500_REG_VMON, + MAX42500_REG_VMON, + MAX42500_REG_VMON, + MAX42500_REG_VMON, + MAX42500_REG_VMON, + MAX42500_REG_VMON, + MAX42500_REG_RSTMAP, + MAX42500_REG_RSTMAP, + MAX42500_REG_RSTMAP, + MAX42500_REG_RSTMAP, + MAX42500_REG_RSTMAP, + MAX42500_REG_RSTMAP, + MAX42500_REG_RSTMAP, + MAX42500_REG_RSTMAP, + MAX42500_REG_STATOV, + MAX42500_REG_STATUV, + MAX42500_REG_STATOFF, + MAX42500_REG_VIN1, + MAX42500_REG_VIN2, + MAX42500_REG_VIN3, + MAX42500_REG_VIN4, + MAX42500_REG_VIN5, + MAX42500_REG_OVUV1, + MAX42500_REG_OVUV2, + MAX42500_REG_OVUV3, + MAX42500_REG_OVUV4, + MAX42500_REG_OVUV5, + MAX42500_REG_VINO6, + MAX42500_REG_VINO7, + MAX42500_REG_OVUV1, + MAX42500_REG_OVUV2, + MAX42500_REG_OVUV3, + MAX42500_REG_OVUV4, + MAX42500_REG_OVUV5, + MAX42500_REG_VINU6, + MAX42500_REG_VINU7 +}; + +enum max42500_fpsr { + MAX42500_FPSR_FPS_STATUS, + MAX42500_FPSR_POWERUP_SEQ_CAPTURE_FINISH, + MAX42500_FPSR_POWERDN_SEQ_CAPTURE_FINISH, + MAX42500_FPSR_POWERUP_NO_INTERRUPT_ENABLE, + MAX42500_FPSR_POWERDN_NO_INTERRUPT_ENABLE, + MAX42500_FPSR_FPS_EN1_START_TIMER_ENABLE, + MAX42500_FPSR_FPS_CLOCK_DIVIDER, + MAX42500_FPSR_POWER1_AVERAGE_INTERVAL_MAX, + MAX42500_FPSR_POWER2_AVERAGE_INTERVAL_MAX, + MAX42500_FPSR_POWER3_AVERAGE_INTERVAL_MAX, + MAX42500_FPSR_POWER4_AVERAGE_INTERVAL_MAX, + MAX42500_FPSR_POWER5_AVERAGE_INTERVAL_MAX, + MAX42500_FPSR_POWER6_AVERAGE_INTERVAL_MAX, + MAX42500_FPSR_POWER7_AVERAGE_INTERVAL_MAX, + MAX42500_FPSR_POWER1_AVERAGE_INTERVAL_MIN, + MAX42500_FPSR_POWER2_AVERAGE_INTERVAL_MIN, + MAX42500_FPSR_POWER3_AVERAGE_INTERVAL_MIN, + MAX42500_FPSR_POWER4_AVERAGE_INTERVAL_MIN, + MAX42500_FPSR_POWER5_AVERAGE_INTERVAL_MIN, + MAX42500_FPSR_POWER6_AVERAGE_INTERVAL_MIN, + MAX42500_FPSR_POWER7_AVERAGE_INTERVAL_MIN, + MAX42500_FPSR_MAX +}; + +static const u8 max42500_reg_fpsr[] = { + MAX42500_REG_FPSTAT1, + MAX42500_REG_FPSCFG1, + MAX42500_REG_FPSCFG1, + MAX42500_REG_FPSCFG1, + MAX42500_REG_FPSCFG1, + MAX42500_REG_FPSCFG1, + MAX42500_REG_FPSCFG1, + MAX42500_REG_UTIME1, + MAX42500_REG_UTIME2, + MAX42500_REG_UTIME3, + MAX42500_REG_UTIME4, + MAX42500_REG_UTIME5, + MAX42500_REG_UTIME6, + MAX42500_REG_UTIME7, + MAX42500_REG_DTIME1, + MAX42500_REG_DTIME2, + MAX42500_REG_DTIME3, + MAX42500_REG_DTIME4, + MAX42500_REG_DTIME5, + MAX42500_REG_DTIME6, + MAX42500_REG_DTIME7 +}; + +struct max42500_state { + struct i2c_client *client; + struct regmap *regmap; + struct mutex lock; /* Protects access during data transfer */ + struct gpio_desc *power_gpio; + struct gpio_desc *sleep_gpio; + u8 chip[MAX42500_CHIP_MAX]; + u8 vmon[MAX42500_VMON_MAX]; + u8 fpsr[MAX42500_FPSR_MAX]; +}; + +static int max42500_chip_get_update(struct max42500_state *st, + enum MAX42500_chip index) +{ + unsigned int reg_val; + int ret; + + guard(mutex)(&st->lock); + + ret = regmap_read(st->regmap, max42500_reg_chip[index], ®_val); + if (ret) + return ret; + + switch (index) { + case MAX42500_CHIP_LABEL: /* ID */ + st->chip[index] = MAX42500_CHIP_ID(reg_val); + return 0; + case MAX42500_CHIP_NAME: /* CID */ + st->chip[index] = MAX42500_CHIP_CID(reg_val); + return 0; + case MAX42500_CHIP_CONFIG_STATUS: /* CFG2 */ + st->chip[index] = MAX42500_CHIP_CFG2(reg_val); + return 0; + case MAX42500_CHIP_PEC_ENABLE: /* PECE */ + st->chip[index] = MAX42500_CHIP_PECE(reg_val); + return 0; + case MAX42500_CHIP_BIST_RESET_ENABLE: /* MBST */ + st->chip[index] = MAX42500_CHIP_MBST(reg_val); + return 0; + case MAX42500_CHIP_RELOAD_OTP_ENABLE: /* RR */ + st->chip[index] = MAX42500_CHIP_RR(reg_val); + return 0; + default: + return -EINVAL; + } +} + +static int max42500_vmon_get_update(struct max42500_state *st, + enum MAX42500_vmon index) +{ + unsigned int reg_val; + int ret = 0; + + guard(mutex)(&st->lock); + + ret = regmap_read(st->regmap, max42500_reg_vmon[index], ®_val); + if (ret) + return ret; + + switch (index) { + case MAX42500_VMON_IN1_ENABLE: /* VIN1 */ + st->vmon[index] = MAX42500_VMON_VM1(reg_val); + return 0; + case MAX42500_VMON_IN1_RESET_ENABLE: /* RST1 */ + st->vmon[index] = MAX42500_VMON_RST1(reg_val); + return 0; + case MAX42500_VMON_IN2_ENABLE: /* VIN2 */ + st->vmon[index] = MAX42500_VMON_VM2(reg_val); + return 0; + case MAX42500_VMON_IN2_RESET_ENABLE: /* RST2 */ + st->vmon[index] = MAX42500_VMON_RST2(reg_val); + return 0; + case MAX42500_VMON_IN3_ENABLE: /* VIN3 */ + st->vmon[index] = MAX42500_VMON_VM3(reg_val); + return 0; + case MAX42500_VMON_IN3_RESET_ENABLE: /* RST3 */ + st->vmon[index] = MAX42500_VMON_RST3(reg_val); + return 0; + case MAX42500_VMON_IN4_ENABLE: /* VIN4 */ + st->vmon[index] = MAX42500_VMON_VM4(reg_val); + return 0; + case MAX42500_VMON_IN4_RESET_ENABLE: /* RST4 */ + st->vmon[index] = MAX42500_VMON_RST4(reg_val); + return 0; + case MAX42500_VMON_IN5_ENABLE: /* VIN5 */ + st->vmon[index] = MAX42500_VMON_VM5(reg_val); + return 0; + case MAX42500_VMON_IN5_RESET_ENABLE: /* RST5 */ + st->vmon[index] = MAX42500_VMON_RST5(reg_val); + return 0; + case MAX42500_VMON_IN6_ENABLE: /* VIN6 */ + st->vmon[index] = MAX42500_VMON_VM6(reg_val); + return 0; + case MAX42500_VMON_IN6_RESET_ENABLE: /*RST6 */ + st->vmon[index] = MAX42500_VMON_RST6(reg_val); + return 0; + case MAX42500_VMON_IN7_ENABLE: /* VIN7 */ + st->vmon[index] = MAX42500_VMON_VM7(reg_val); + return 0; + case MAX42500_VMON_IN7_RESET_ENABLE: /* RST7 */ + st->vmon[index] = MAX42500_VMON_RST7(reg_val); + return 0; + case MAX42500_VMON_PDN_ENABLE: /* VMPD */ + st->vmon[index] = MAX42500_VMON_VMPD(reg_val); + return 0; + case MAX42500_VMON_PAR_ENABLE: /* PARM */ + st->vmon[index] = MAX42500_VMON_PARM(reg_val); + return 0; + case MAX42500_VMON_IN1_NOMINAL: /* VIN1 */ + case MAX42500_VMON_IN2_NOMINAL: /* VIN2 */ + case MAX42500_VMON_IN3_NOMINAL: /* VIN3 */ + case MAX42500_VMON_IN4_NOMINAL: /* VIN4 */ + case MAX42500_VMON_IN5_NOMINAL: /* VIN5 */ + st->vmon[index] = MAX42500_VMON_1VIN5(reg_val); + return 0; + case MAX42500_VMON_IN1_UV_THRESH: /* VINU1 */ + case MAX42500_VMON_IN2_UV_THRESH: /* VINU2 */ + case MAX42500_VMON_IN3_UV_THRESH: /* VINU3 */ + case MAX42500_VMON_IN4_UV_THRESH: /* VINU4 */ + case MAX42500_VMON_IN5_UV_THRESH: /* VINU5 */ + st->vmon[index] = MAX42500_VMON_1VINU5(reg_val); + return 0; + case MAX42500_VMON_IN6_LCRIT: /* VINU6 */ + case MAX42500_VMON_IN7_LCRIT: /* VINU7 */ + st->vmon[index] = MAX42500_VMON_6VINU7(reg_val); + return 0; + case MAX42500_VMON_IN1_OV_THRESH: /* VINO1 */ + case MAX42500_VMON_IN2_OV_THRESH: /* VINO2 */ + case MAX42500_VMON_IN3_OV_THRESH: /* VINO3 */ + case MAX42500_VMON_IN4_OV_THRESH: /* VINO4 */ + case MAX42500_VMON_IN5_OV_THRESH: /* VINO5 */ + st->vmon[index] = MAX42500_VMON_1VINO5(reg_val); + return 0; + case MAX42500_VMON_IN6_CRIT: /* VINO6 */ + case MAX42500_VMON_IN7_CRIT: /* VINO7 */ + st->vmon[index] = MAX42500_VMON_6VINO7(reg_val); + return 0; + case MAX42500_VMON_OFF_STATUS: /* STATOFF */ + st->vmon[index] = MAX42500_VMON_STATOFF(reg_val); + return 0; + case MAX42500_VMON_UV_STATUS: /* STATUV */ + st->vmon[index] = MAX42500_VMON_STATUV(reg_val); + return 0; + case MAX42500_VMON_OV_STATUS: /* STATOV */ + st->vmon[index] = MAX42500_VMON_STATOV(reg_val); + return 0; + default: + return -EINVAL; + } +} + +static int max42500_fpsr_get_update(struct max42500_state *st, + enum max42500_fpsr index) +{ + unsigned int reg_val; + int ret; + + guard(mutex)(&st->lock); + + ret = regmap_read(st->regmap, max42500_reg_fpsr[index], ®_val); + if (ret) + return ret; + + switch (index) { + case MAX42500_FPSR_FPS_STATUS: /* FPSTAT1 */ + st->fpsr[index] = MAX42500_FPSR_FPSTAT1(reg_val); + return 0; + case MAX42500_FPSR_FPS_CLOCK_DIVIDER: /* FDIV */ + st->fpsr[index] = MAX42500_FPSR_FDIV(reg_val); + return 0; + case MAX42500_FPSR_FPS_EN1_START_TIMER_ENABLE: /* FPSEN1 */ + st->fpsr[index] = MAX42500_FPSR_FPSEN1(reg_val); + return 0; + case MAX42500_FPSR_POWERDN_NO_INTERRUPT_ENABLE: /* DVALM */ + st->fpsr[index] = MAX42500_FPSR_DVALM(reg_val); + return 0; + case MAX42500_FPSR_POWERUP_NO_INTERRUPT_ENABLE: /* UVALM */ + st->fpsr[index] = MAX42500_FPSR_UVALM(reg_val); + return 0; + case MAX42500_FPSR_POWERDN_SEQ_CAPTURE_FINISH: /* DVAL */ + st->fpsr[index] = MAX42500_FPSR_DVAL(reg_val); + return 0; + case MAX42500_FPSR_POWERUP_SEQ_CAPTURE_FINISH: /* UVAL */ + st->fpsr[index] = MAX42500_FPSR_UVAL(reg_val); + return 0; + case MAX42500_FPSR_POWER1_AVERAGE_INTERVAL_MAX: /* UTIM1 */ + case MAX42500_FPSR_POWER2_AVERAGE_INTERVAL_MAX: /* UTIM2 */ + case MAX42500_FPSR_POWER3_AVERAGE_INTERVAL_MAX: /* UTIM3 */ + case MAX42500_FPSR_POWER4_AVERAGE_INTERVAL_MAX: /* UTIM4 */ + case MAX42500_FPSR_POWER5_AVERAGE_INTERVAL_MAX: /* UTIM5 */ + case MAX42500_FPSR_POWER6_AVERAGE_INTERVAL_MAX: /* UTIM6 */ + case MAX42500_FPSR_POWER7_AVERAGE_INTERVAL_MAX: /* UTIM7 */ + st->fpsr[index] = MAX42500_FPSR_1UTIM7(reg_val); + return 0; + case MAX42500_FPSR_POWER1_AVERAGE_INTERVAL_MIN: /* DTIM1 */ + case MAX42500_FPSR_POWER2_AVERAGE_INTERVAL_MIN: /* DTIM2 */ + case MAX42500_FPSR_POWER3_AVERAGE_INTERVAL_MIN: /* DTIM3 */ + case MAX42500_FPSR_POWER4_AVERAGE_INTERVAL_MIN: /* DTIM4 */ + case MAX42500_FPSR_POWER5_AVERAGE_INTERVAL_MIN: /* DTIM5 */ + case MAX42500_FPSR_POWER6_AVERAGE_INTERVAL_MIN: /* DTIM6 */ + case MAX42500_FPSR_POWER7_AVERAGE_INTERVAL_MIN: /* DTIM7 */ + st->fpsr[index] = MAX42500_FPSR_1DTIM7(reg_val); + return 0; + default: + return -EINVAL; + } +} + +static int max42500_chip_set_update(struct max42500_state *st, long value, + enum MAX42500_chip index) +{ + bool pec_flag = false; + unsigned int reg_mask; + int ret; + + guard(mutex)(&st->lock); + + switch (index) { + case MAX42500_CHIP_PEC_ENABLE: /* PECE */ + reg_mask = MAX42500_CHIP_PECE_MASK; + pec_flag = true; + break; + case MAX42500_CHIP_BIST_RESET_ENABLE: /* MBST */ + reg_mask = MAX42500_CHIP_MBST_MASK; + break; + case MAX42500_CHIP_RELOAD_OTP_ENABLE: /* RR */ + reg_mask = MAX42500_CHIP_RR_MASK; + break; + default: + return -EINVAL; + } + + ret = regmap_update_bits(st->regmap, max42500_reg_chip[index], + reg_mask, value); + if (ret) + return ret; + + /* Set the PEC of the device first then set in here after. */ + if (pec_flag) + st->client->flags |= FIELD_PREP(BIT(I2C_CLIENT_PEC), value); + + /* Update after successful register write */ + st->chip[index] = FIELD_GET(GENMASK(7, 0), value); + + return 0; +} + +static int max42500_vmon_set_update(struct max42500_state *st, long value, + enum MAX42500_vmon index) +{ + unsigned int reg_mask; + int ret; + + guard(mutex)(&st->lock); + + switch (index) { + case MAX42500_VMON_IN1_ENABLE: /* VIN1 */ + reg_mask = MAX42500_VMON_VM1_MASK; + break; + case MAX42500_VMON_IN1_RESET_ENABLE: /* RST1 */ + reg_mask = MAX42500_VMON_RST1_MASK; + break; + case MAX42500_VMON_IN2_ENABLE: /* VIN2 */ + reg_mask = MAX42500_VMON_VM2_MASK; + break; + case MAX42500_VMON_IN2_RESET_ENABLE: /* RST2 */ + reg_mask = MAX42500_VMON_RST2_MASK; + break; + case MAX42500_VMON_IN3_ENABLE: /* VIN3 */ + reg_mask = MAX42500_VMON_VM3_MASK; + break; + case MAX42500_VMON_IN3_RESET_ENABLE: /* RST3 */ + reg_mask = MAX42500_VMON_RST3_MASK; + break; + case MAX42500_VMON_IN4_ENABLE: /* VIN4 */ + reg_mask = MAX42500_VMON_VM4_MASK; + break; + case MAX42500_VMON_IN4_RESET_ENABLE: /* RST4 */ + reg_mask = MAX42500_VMON_RST4_MASK; + break; + case MAX42500_VMON_IN5_ENABLE: /* VIN5 */ + reg_mask = MAX42500_VMON_VM5_MASK; + break; + case MAX42500_VMON_IN5_RESET_ENABLE: /* RST5 */ + reg_mask = MAX42500_VMON_RST5_MASK; + break; + case MAX42500_VMON_IN6_ENABLE: /* VIN6 */ + reg_mask = MAX42500_VMON_VM6_MASK; + break; + case MAX42500_VMON_IN6_RESET_ENABLE: /*RST6 */ + reg_mask = MAX42500_VMON_RST6_MASK; + break; + case MAX42500_VMON_IN7_ENABLE: /* VIN7 */ + reg_mask = MAX42500_VMON_VM7_MASK; + break; + case MAX42500_VMON_IN7_RESET_ENABLE: /* RST7 */ + reg_mask = MAX42500_VMON_RST7_MASK; + break; + case MAX42500_VMON_PDN_ENABLE: /* VMPD */ + reg_mask = MAX42500_VMON_VMPD_MASK; + break; + case MAX42500_VMON_PAR_ENABLE: /* PARM */ + reg_mask = MAX42500_VMON_PARM_MASK; + break; + case MAX42500_VMON_IN1_NOMINAL: /* VIN1 */ + case MAX42500_VMON_IN2_NOMINAL: /* VIN2 */ + case MAX42500_VMON_IN3_NOMINAL: /* VIN3 */ + case MAX42500_VMON_IN4_NOMINAL: /* VIN4 */ + case MAX42500_VMON_IN5_NOMINAL: /* VIN5 */ + reg_mask = MAX42500_VMON_1VIN5_MASK; + break; + case MAX42500_VMON_IN1_UV_THRESH: /* VINU1 */ + case MAX42500_VMON_IN2_UV_THRESH: /* VINU2 */ + case MAX42500_VMON_IN3_UV_THRESH: /* VINU3 */ + case MAX42500_VMON_IN4_UV_THRESH: /* VINU4 */ + case MAX42500_VMON_IN5_UV_THRESH: /* VINU5 */ + reg_mask = MAX42500_VMON_1VINU5_MASK; + break; + case MAX42500_VMON_IN6_LCRIT: /* VINU6 */ + case MAX42500_VMON_IN7_LCRIT: /* VINU7 */ + reg_mask = MAX42500_VMON_6VINU7_MASK; + break; + case MAX42500_VMON_IN1_OV_THRESH: /* VINO1 */ + case MAX42500_VMON_IN2_OV_THRESH: /* VINO2 */ + case MAX42500_VMON_IN3_OV_THRESH: /* VINO3 */ + case MAX42500_VMON_IN4_OV_THRESH: /* VINO4 */ + case MAX42500_VMON_IN5_OV_THRESH: /* VINO5 */ + reg_mask = MAX42500_VMON_1VINO5_MASK; + break; + case MAX42500_VMON_IN6_CRIT: /* VINO6 */ + case MAX42500_VMON_IN7_CRIT: /* VINO7 */ + reg_mask = MAX42500_VMON_6VINO7_MASK; + break; + default: + return -EINVAL; + } + + ret = regmap_update_bits(st->regmap, max42500_reg_vmon[index], + reg_mask, value); + if (ret) + return ret; + + /* Update after successful register write */ + st->vmon[index] = FIELD_GET(GENMASK(7, 0), value); + + return 0; +} + +static int max42500_fpsr_set_update(struct max42500_state *st, long value, + enum max42500_fpsr index) +{ + unsigned int reg_mask; + int ret; + + guard(mutex)(&st->lock); + + switch (index) { + case MAX42500_FPSR_FPS_CLOCK_DIVIDER: /* FDIV */ + reg_mask = MAX42500_FPSR_FDIV_MASK; + break; + case MAX42500_FPSR_FPS_EN1_START_TIMER_ENABLE: /* FPSEN1 */ + reg_mask = MAX42500_FPSR_FPSEN1_MASK; + break; + case MAX42500_FPSR_POWERDN_NO_INTERRUPT_ENABLE: /* DVALM */ + reg_mask = MAX42500_FPSR_DVALM_MASK; + break; + case MAX42500_FPSR_POWERUP_NO_INTERRUPT_ENABLE: /* UVALM */ + reg_mask = MAX42500_FPSR_UVALM_MASK; + break; + case MAX42500_FPSR_POWERDN_SEQ_CAPTURE_FINISH: /* DVAL */ + reg_mask = MAX42500_FPSR_DVAL_MASK; + break; + case MAX42500_FPSR_POWERUP_SEQ_CAPTURE_FINISH: /* UVAL */ + reg_mask = MAX42500_FPSR_UVAL_MASK; + break; + default: + return -EINVAL; + } + + ret = regmap_update_bits(st->regmap, max42500_reg_fpsr[index], + reg_mask, value); + if (ret) + return ret; + + /* Update after successful register write */ + st->fpsr[index] = FIELD_GET(GENMASK(7, 0), value); + + return 0; +} + +static int max42500_vmon_read(struct max42500_state *st, long *value, + enum MAX42500_vmon index) +{ + int ret; + + ret = max42500_vmon_get_update(st, index); + if (ret) + return ret; + + switch (index) { + case MAX42500_VMON_IN1_NOMINAL: /* VIN1 */ + case MAX42500_VMON_IN2_NOMINAL: /* VIN2 */ + case MAX42500_VMON_IN3_NOMINAL: /* VIN3 */ + case MAX42500_VMON_IN4_NOMINAL: /* VIN4 */ + *value = 500000 + (12500 * st->vmon[index]); + return 0; + case MAX42500_VMON_IN5_NOMINAL: /* VIN5 */ + *value = 500000 + (20000 * st->vmon[index]); + return 0; + case MAX42500_VMON_IN6_CRIT: /* VINO6 */ + case MAX42500_VMON_IN6_LCRIT: /* VINU6 */ + case MAX42500_VMON_IN7_CRIT: /* VINO7 */ + case MAX42500_VMON_IN7_LCRIT: /* VINU7 */ + *value = 500000 + (5000 * st->vmon[index]); + return 0; + case MAX42500_VMON_IN1_OV_THRESH: /* VINO1 */ + case MAX42500_VMON_IN2_OV_THRESH: /* VINO2 */ + case MAX42500_VMON_IN3_OV_THRESH: /* VINO3 */ + case MAX42500_VMON_IN4_OV_THRESH: /* VINO4 */ + case MAX42500_VMON_IN5_OV_THRESH: /* VINO5 */ + *value = 102500 + (500 * st->vmon[index]); + return 0; + case MAX42500_VMON_IN1_UV_THRESH: /* VINU1 */ + case MAX42500_VMON_IN2_UV_THRESH: /* VINU2 */ + case MAX42500_VMON_IN3_UV_THRESH: /* VINU3 */ + case MAX42500_VMON_IN4_UV_THRESH: /* VINU4 */ + case MAX42500_VMON_IN5_UV_THRESH: /* VINU5 */ + *value = 97500 - (500 * st->vmon[index]); + return 0; + default: + *value = st->vmon[index]; + return 0; + } +} + +static int max42500_fpsr_read(struct max42500_state *st, long *value, + enum max42500_fpsr index) +{ + u8 clk_div = MAX42500_FPSR_FPS_CLOCK_DIVIDER; + int ret; + + ret = max42500_fpsr_get_update(st, index); + if (ret) + return ret; + + switch (index) { + case MAX42500_FPSR_POWER1_AVERAGE_INTERVAL_MAX: /* UTIM1 */ + case MAX42500_FPSR_POWER2_AVERAGE_INTERVAL_MAX: /* UTIM2 */ + case MAX42500_FPSR_POWER3_AVERAGE_INTERVAL_MAX: /* UTIM3 */ + case MAX42500_FPSR_POWER4_AVERAGE_INTERVAL_MAX: /* UTIM4 */ + case MAX42500_FPSR_POWER5_AVERAGE_INTERVAL_MAX: /* UTIM5 */ + case MAX42500_FPSR_POWER6_AVERAGE_INTERVAL_MAX: /* UTIM6 */ + case MAX42500_FPSR_POWER7_AVERAGE_INTERVAL_MAX: /* UTIM7 */ + case MAX42500_FPSR_POWER1_AVERAGE_INTERVAL_MIN: /* DTIM1 */ + case MAX42500_FPSR_POWER2_AVERAGE_INTERVAL_MIN: /* DTIM2 */ + case MAX42500_FPSR_POWER3_AVERAGE_INTERVAL_MIN: /* DTIM3 */ + case MAX42500_FPSR_POWER4_AVERAGE_INTERVAL_MIN: /* DTIM4 */ + case MAX42500_FPSR_POWER5_AVERAGE_INTERVAL_MIN: /* DTIM5 */ + case MAX42500_FPSR_POWER6_AVERAGE_INTERVAL_MIN: /* DTIM6 */ + case MAX42500_FPSR_POWER7_AVERAGE_INTERVAL_MIN: /* DTIM7 */ + /* Get the latest FPS clock divider for the timestamp */ + ret = max42500_fpsr_get_update(st, clk_div); + if (ret) + return ret; + + *value = (st->fpsr[index] - 1) * 25 * (1 << st->fpsr[clk_div]); + return 0; + default: + *value = st->fpsr[index]; + return 0; + } +} + +static int max42500_vmon_write(struct max42500_state *st, long value, + enum MAX42500_vmon index) +{ + long vmon; + + switch (index) { + case MAX42500_VMON_IN1_NOMINAL: /* VIN1 */ + case MAX42500_VMON_IN2_NOMINAL: /* VIN2 */ + case MAX42500_VMON_IN3_NOMINAL: /* VIN3 */ + case MAX42500_VMON_IN4_NOMINAL: /* VIN4 */ + vmon = (value - 500000) / 12500; + return max42500_vmon_set_update(st, vmon, index); + case MAX42500_VMON_IN5_NOMINAL: /* VIN5 */ + vmon = (value - 500000) / 20000; + return max42500_vmon_set_update(st, vmon, index); + case MAX42500_VMON_IN6_CRIT: /* VINO6 */ + case MAX42500_VMON_IN6_LCRIT: /* VINU6 */ + case MAX42500_VMON_IN7_CRIT: /* VINO7 */ + case MAX42500_VMON_IN7_LCRIT: /* VINU7 */ + vmon = (value - 500000) / 5000; + return max42500_vmon_set_update(st, vmon, index); + case MAX42500_VMON_IN1_OV_THRESH: /* VINO1 */ + case MAX42500_VMON_IN2_OV_THRESH: /* VINO2 */ + case MAX42500_VMON_IN3_OV_THRESH: /* VINO3 */ + case MAX42500_VMON_IN4_OV_THRESH: /* VINO4 */ + case MAX42500_VMON_IN5_OV_THRESH: /* VINO5 */ + vmon = (value - 102500) / 500; + return max42500_vmon_set_update(st, vmon, index); + case MAX42500_VMON_IN1_UV_THRESH: /* VINU1 */ + case MAX42500_VMON_IN2_UV_THRESH: /* VINU2 */ + case MAX42500_VMON_IN3_UV_THRESH: /* VINU3 */ + case MAX42500_VMON_IN4_UV_THRESH: /* VINU4 */ + case MAX42500_VMON_IN5_UV_THRESH: /* VINU5 */ + vmon = (97500 - value) / 500; + return max42500_vmon_set_update(st, vmon, index); + default: + return -EOPNOTSUPP; + } +} + +static int max42500_read_chip(struct max42500_state *st, const u32 attr, + long *val) +{ + int ret; + + switch (attr) { + case hwmon_chip_pec: + ret = max42500_chip_get_update(st, MAX42500_CHIP_PEC_ENABLE); + if (ret) + return ret; + + *val = st->chip[MAX42500_CHIP_PEC_ENABLE]; + return 0; + default: + return -EOPNOTSUPP; + } +} + +static int max42500_read_in(struct max42500_state *st, const u32 attr, + long *val, const int channel) +{ + int index; + + switch (attr) { + case hwmon_in_enable: + index = MAX42500_VMON_IN1_ENABLE + channel; + return max42500_vmon_read(st, val, index); + case hwmon_in_lcrit: + index = MAX42500_VMON_IN1_UV_THRESH + channel; + return max42500_vmon_read(st, val, index); + case hwmon_in_crit: + index = MAX42500_VMON_IN1_OV_THRESH + channel; + return max42500_vmon_read(st, val, index); + case hwmon_in_min: + index = MAX42500_VMON_IN1_NOMINAL + channel; + return max42500_vmon_read(st, val, index); + default: + return -EOPNOTSUPP; + } +} + +static int max42500_read_power(struct max42500_state *st, const u32 attr, + long *val, const int channel) +{ + int index; + + switch (attr) { + case hwmon_power_enable: + return max42500_vmon_read(st, val, MAX42500_VMON_OFF_STATUS); + case hwmon_power_lcrit_alarm: + return max42500_vmon_read(st, val, MAX42500_VMON_UV_STATUS); + case hwmon_power_crit_alarm: + return max42500_vmon_read(st, val, MAX42500_VMON_OV_STATUS); + case hwmon_power_average_interval_max: + index = MAX42500_FPSR_POWER1_AVERAGE_INTERVAL_MAX + channel; + return max42500_fpsr_read(st, val, index); + case hwmon_power_average_interval_min: + index = MAX42500_FPSR_POWER1_AVERAGE_INTERVAL_MIN + channel; + return max42500_fpsr_read(st, val, index); + default: + return -EOPNOTSUPP; + } +} + +static int max42500_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + struct max42500_state *st = dev_get_drvdata(dev); + + switch (type) { + case hwmon_chip: + return max42500_read_chip(st, attr, val); + case hwmon_in: + return max42500_read_in(st, attr, val, channel); + case hwmon_power: + return max42500_read_power(st, attr, val, channel); + default: + return -EOPNOTSUPP; + } +} + +static int max42500_write_chip(struct max42500_state *st, const u32 attr, + long val) +{ + switch (attr) { + case hwmon_chip_pec: + return max42500_chip_set_update(st, val, + MAX42500_CHIP_PEC_ENABLE); + default: + return -EOPNOTSUPP; + } +} + +static int max42500_write_in(struct max42500_state *st, const u32 attr, + long val, const int channel) +{ + int index; + + switch (attr) { + case hwmon_in_enable: + index = MAX42500_VMON_IN1_ENABLE + channel; + return max42500_vmon_write(st, val, index); + case hwmon_in_lcrit: + index = MAX42500_VMON_IN1_UV_THRESH + channel; + return max42500_vmon_write(st, val, index); + case hwmon_in_crit: + index = MAX42500_VMON_IN1_OV_THRESH + channel; + return max42500_vmon_write(st, val, index); + case hwmon_in_min: + index = MAX42500_VMON_IN1_NOMINAL + channel; + return max42500_vmon_write(st, val, index); + case hwmon_in_reset_history: + index = MAX42500_VMON_IN1_RESET_ENABLE + channel; + return max42500_vmon_write(st, val, index); + default: + return -EOPNOTSUPP; + } +} + +static int max42500_write(struct device *dev, + enum hwmon_sensor_types type, + u32 attr, int channel, long val) +{ + struct max42500_state *st = dev_get_drvdata(dev); + + switch (type) { + case hwmon_chip: + return max42500_write_chip(st, attr, val); + case hwmon_in: + return max42500_write_in(st, attr, val, channel); + default: + return -EOPNOTSUPP; + } +} + +static int max42500_read_labels(struct device *dev, + enum hwmon_sensor_types type, + u32 attr, int channel, const char **str) +{ + switch (type) { + case hwmon_in: + *str = "VMON"; + return 0; + case hwmon_power: + *str = "STATUS"; + return 0; + default: + return -EOPNOTSUPP; + } +} + +static int max42500_chip_is_visible(const u32 attr) +{ + switch (attr) { + case hwmon_chip_pec: + return 0644; + default: + return 0; + } +} + +static int max42500_in_is_visible(const u32 attr) +{ + switch (attr) { + case hwmon_in_label: + return 0444; + case hwmon_in_reset_history: + return 0200; + case hwmon_in_enable: + case hwmon_in_lcrit: + case hwmon_in_crit: + case hwmon_in_min: + return 0644; + default: + return 0; + } +} + +static int max42500_power_is_visible(const u32 attr) +{ + switch (attr) { + case hwmon_power_enable: + case hwmon_power_lcrit_alarm: + case hwmon_power_crit_alarm: + case hwmon_power_average_interval_min: + case hwmon_power_average_interval_max: + return 0444; + default: + return 0; + } +} + +static umode_t max42500_is_visible(const void *data, + enum hwmon_sensor_types type, + u32 attr, int channel) +{ + switch (type) { + case hwmon_chip: + return max42500_chip_is_visible(attr); + case hwmon_in: + return max42500_in_is_visible(attr); + case hwmon_power: + return max42500_power_is_visible(attr); + default: + return 0; + } +} + +static const struct hwmon_channel_info * const max42500_info[] = { + HWMON_CHANNEL_INFO(chip, + HWMON_C_PEC), + HWMON_CHANNEL_INFO(in, + HWMON_I_ENABLE | HWMON_I_LCRIT | HWMON_I_CRIT | + HWMON_I_RESET_HISTORY | HWMON_I_LABEL | HWMON_I_MIN, + HWMON_I_ENABLE | HWMON_I_LCRIT | HWMON_I_CRIT | + HWMON_I_RESET_HISTORY | HWMON_I_LABEL | HWMON_I_MIN, + HWMON_I_ENABLE | HWMON_I_LCRIT | HWMON_I_CRIT | + HWMON_I_RESET_HISTORY | HWMON_I_LABEL | HWMON_I_MIN, + HWMON_I_ENABLE | HWMON_I_LCRIT | HWMON_I_CRIT | + HWMON_I_RESET_HISTORY | HWMON_I_LABEL | HWMON_I_MIN, + HWMON_I_ENABLE | HWMON_I_LCRIT | HWMON_I_CRIT | + HWMON_I_RESET_HISTORY | HWMON_I_LABEL | HWMON_I_MIN, + HWMON_I_ENABLE | HWMON_I_LCRIT | HWMON_I_CRIT | + HWMON_I_RESET_HISTORY | HWMON_I_LABEL, + HWMON_I_ENABLE | HWMON_I_LCRIT | HWMON_I_CRIT | + HWMON_I_RESET_HISTORY | HWMON_I_LABEL), + HWMON_CHANNEL_INFO(power, + HWMON_P_ENABLE | + HWMON_P_LCRIT_ALARM | + HWMON_P_CRIT_ALARM | + HWMON_P_AVERAGE_INTERVAL_MIN | + HWMON_P_AVERAGE_INTERVAL_MAX, + HWMON_P_AVERAGE_INTERVAL_MIN | + HWMON_P_AVERAGE_INTERVAL_MAX, + HWMON_P_AVERAGE_INTERVAL_MIN | + HWMON_P_AVERAGE_INTERVAL_MAX, + HWMON_P_AVERAGE_INTERVAL_MIN | + HWMON_P_AVERAGE_INTERVAL_MAX, + HWMON_P_AVERAGE_INTERVAL_MIN | + HWMON_P_AVERAGE_INTERVAL_MAX, + HWMON_P_AVERAGE_INTERVAL_MIN | + HWMON_P_AVERAGE_INTERVAL_MAX, + HWMON_P_AVERAGE_INTERVAL_MIN | + HWMON_P_AVERAGE_INTERVAL_MAX), + NULL +}; + +static const struct hwmon_ops max42500_hwmon_ops = { + .read = max42500_read, + .write = max42500_write, + .is_visible = max42500_is_visible, + .read_string = max42500_read_labels, +}; + +static const struct hwmon_chip_info max42500_chip_info = { + .ops = &max42500_hwmon_ops, + .info = max42500_info, +}; + +static int max42500_show_chip_label_log(void *arg, u64 *val) +{ + struct max42500_state *st = arg; + int ret; + + ret = max42500_chip_get_update(st, MAX42500_CHIP_LABEL); + if (ret) + return ret; + + *val = st->chip[MAX42500_CHIP_LABEL]; + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(max42500_chip_label_log, + max42500_show_chip_label_log, NULL, "%llu\n"); + +static int max42500_show_chip_name_log(void *arg, u64 *val) +{ + struct max42500_state *st = arg; + int ret; + + ret = max42500_chip_get_update(st, MAX42500_CHIP_NAME); + if (ret) + return ret; + + *val = st->chip[MAX42500_CHIP_NAME]; + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(max42500_chip_name_log, + max42500_show_chip_name_log, NULL, "%llu\n"); + +static int max42500_show_chip_otp_enable_log(void *arg, u64 *val) +{ + struct max42500_state *st = arg; + int ret; + + ret = max42500_chip_get_update(st, MAX42500_CHIP_RELOAD_OTP_ENABLE); + if (ret) + return ret; + + *val = st->chip[MAX42500_CHIP_RELOAD_OTP_ENABLE]; + + return 0; +} + +static int max42500_store_chip_otp_enable_log(void *arg, u64 val) +{ + return max42500_chip_set_update(arg, val, + MAX42500_CHIP_RELOAD_OTP_ENABLE); +} +DEFINE_DEBUGFS_ATTRIBUTE(max42500_chip_otp_enable_log, + max42500_show_chip_otp_enable_log, + max42500_store_chip_otp_enable_log, "%llu\n"); + +static int max42500_show_chip_bist_rst_enable_log(void *arg, u64 *val) +{ + struct max42500_state *st = arg; + int ret; + + ret = max42500_chip_get_update(st, MAX42500_CHIP_BIST_RESET_ENABLE); + if (ret) + return ret; + + *val = st->chip[MAX42500_CHIP_BIST_RESET_ENABLE]; + + return 0; +} + +static int max42500_store_chip_bist_rst_enable_log(void *arg, u64 val) +{ + return max42500_chip_set_update(arg, val, + MAX42500_CHIP_BIST_RESET_ENABLE); +} +DEFINE_DEBUGFS_ATTRIBUTE(max42500_chip_bist_rst_enable_log, + max42500_show_chip_bist_rst_enable_log, + max42500_store_chip_bist_rst_enable_log, "%llu\n"); + +static int max42500_show_chip_config_status_log(void *arg, u64 *val) +{ + struct max42500_state *st = arg; + int ret; + + ret = max42500_chip_get_update(st, MAX42500_CHIP_CONFIG_STATUS); + if (ret) + return ret; + + *val = st->chip[MAX42500_CHIP_CONFIG_STATUS]; + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(max42500_chip_config_status_log, + max42500_show_chip_config_status_log, NULL, "%llu\n"); + +static int max42500_show_fpsr_status_log(void *arg, u64 *val) +{ + struct max42500_state *st = arg; + int ret; + + ret = max42500_fpsr_get_update(st, MAX42500_FPSR_FPS_STATUS); + if (ret) + return ret; + + *val = st->fpsr[MAX42500_FPSR_FPS_STATUS]; + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(max42500_fpsr_status_log, + max42500_show_fpsr_status_log, NULL, "%llu\n"); + +static int max42500_show_pwrup_capt_finish_log(void *arg, u64 *val) +{ + struct max42500_state *st = arg; + int ret; + + ret = max42500_fpsr_get_update(st, + MAX42500_FPSR_POWERUP_SEQ_CAPTURE_FINISH); + if (ret) + return ret; + + *val = st->fpsr[MAX42500_FPSR_POWERUP_SEQ_CAPTURE_FINISH]; + + return 0; +} + +static int max42500_store_pwrup_capt_finish_log(void *arg, u64 val) +{ + return max42500_fpsr_set_update(arg, val, + MAX42500_FPSR_POWERUP_SEQ_CAPTURE_FINISH); +} +DEFINE_DEBUGFS_ATTRIBUTE(max42500_pwrup_capt_finish_log, + max42500_show_pwrup_capt_finish_log, + max42500_store_pwrup_capt_finish_log, "%llu\n"); + +static int max42500_show_pwrup_capt_enable_log(void *arg, u64 *val) +{ + struct max42500_state *st = arg; + int ret; + + ret = max42500_fpsr_get_update(st, + MAX42500_FPSR_POWERUP_NO_INTERRUPT_ENABLE); + if (ret) + return ret; + + *val = st->fpsr[MAX42500_FPSR_POWERUP_NO_INTERRUPT_ENABLE]; + + return 0; +} + +static int max42500_store_pwrup_capt_enable_log(void *arg, u64 val) +{ + return max42500_fpsr_set_update(arg, val, + MAX42500_FPSR_POWERUP_NO_INTERRUPT_ENABLE); +} +DEFINE_DEBUGFS_ATTRIBUTE(max42500_pwrup_capt_enable_log, + max42500_show_pwrup_capt_enable_log, + max42500_store_pwrup_capt_enable_log, "%llu\n"); + +static int max42500_show_pwrdn_capt_finish_log(void *arg, u64 *val) +{ + struct max42500_state *st = arg; + int ret; + + ret = max42500_fpsr_get_update(st, + MAX42500_FPSR_POWERDN_SEQ_CAPTURE_FINISH); + if (ret) + return ret; + + *val = st->fpsr[MAX42500_FPSR_POWERDN_SEQ_CAPTURE_FINISH]; + + return 0; +} + +static int max42500_store_pwrdn_capt_finish_log(void *arg, u64 val) +{ + return max42500_fpsr_set_update(arg, val, + MAX42500_FPSR_POWERDN_SEQ_CAPTURE_FINISH); +} +DEFINE_DEBUGFS_ATTRIBUTE(max42500_pwrdn_capt_finish_log, + max42500_show_pwrdn_capt_finish_log, + max42500_store_pwrdn_capt_finish_log, "%llu\n"); + +static int max42500_show_fps_clk_div_log(void *arg, u64 *val) +{ + struct max42500_state *st = arg; + int ret; + + ret = max42500_fpsr_get_update(st, MAX42500_FPSR_FPS_CLOCK_DIVIDER); + if (ret) + return ret; + + *val = st->fpsr[MAX42500_FPSR_FPS_CLOCK_DIVIDER]; + + return 0; +} + +static int max42500_store_fps_clk_div_log(void *arg, u64 val) +{ + return max42500_fpsr_set_update(arg, val, + MAX42500_FPSR_FPS_CLOCK_DIVIDER); +} +DEFINE_DEBUGFS_ATTRIBUTE(max42500_fps_clk_div_log, + max42500_show_fps_clk_div_log, + max42500_store_fps_clk_div_log, "%llu\n"); + +static int max42500_show_pwrdn_capt_enable_log(void *arg, u64 *val) +{ + struct max42500_state *st = arg; + int ret; + + ret = max42500_fpsr_get_update(st, + MAX42500_FPSR_POWERDN_NO_INTERRUPT_ENABLE); + if (ret) + return ret; + + *val = st->fpsr[MAX42500_FPSR_POWERDN_NO_INTERRUPT_ENABLE]; + + return 0; +} + +static int max42500_store_pwrdn_capt_enable_log(void *arg, u64 val) +{ + return max42500_fpsr_set_update(arg, val, + MAX42500_FPSR_POWERDN_NO_INTERRUPT_ENABLE); +} +DEFINE_DEBUGFS_ATTRIBUTE(max42500_pwrdn_capt_enable_log, + max42500_show_pwrdn_capt_enable_log, + max42500_store_pwrdn_capt_enable_log, "%llu\n"); + +static int max42500_show_fps_en1_timer_enable_log(void *arg, u64 *val) +{ + struct max42500_state *st = arg; + int ret; + + ret = max42500_fpsr_get_update(st, + MAX42500_FPSR_FPS_EN1_START_TIMER_ENABLE); + if (ret) + return ret; + + *val = st->fpsr[MAX42500_FPSR_FPS_EN1_START_TIMER_ENABLE]; + + return 0; +} + +static int max42500_store_fps_en1_timer_enable_log(void *arg, u64 val) +{ + return max42500_fpsr_set_update(arg, val, + MAX42500_FPSR_FPS_EN1_START_TIMER_ENABLE); +} +DEFINE_DEBUGFS_ATTRIBUTE(max42500_fps_en1_timer_enable_log, + max42500_show_fps_en1_timer_enable_log, + max42500_store_fps_en1_timer_enable_log, "%llu\n"); + +static void max42500_hwmon_debugfs_remove(void *dir) +{ + debugfs_remove_recursive(dir); +} + +static void max42500_hwmon_debugfs_init(struct max42500_state *st, + struct platform_device *pdev, + const struct device *hwmon) +{ + const char *debugfs_name; + struct dentry *dentry; + int ret; + + if (!IS_ENABLED(CONFIG_DEBUG_FS)) + return; + + debugfs_name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "max42500-%s", + dev_name(hwmon)); + if (!debugfs_name) + return; + + dentry = debugfs_create_dir(debugfs_name, NULL); + if (IS_ERR(dentry)) + return; + + ret = devm_add_action_or_reset(&pdev->dev, + max42500_hwmon_debugfs_remove, + dentry); + if (ret) + return; + + /* Chip registers */ + debugfs_create_file_unsafe("chip_label_log", 0400, dentry, st, + &max42500_chip_label_log); + debugfs_create_file_unsafe("chip_name_log", 0400, dentry, st, + &max42500_chip_name_log); + debugfs_create_file_unsafe("chip_otp_enable_log", 0644, dentry, st, + &max42500_chip_otp_enable_log); + debugfs_create_file_unsafe("chip_bist_rst_enable_log", 0644, dentry, st, + &max42500_chip_bist_rst_enable_log); + debugfs_create_file_unsafe("chip_config_status_log", 0400, dentry, st, + &max42500_chip_config_status_log); + + /* FPSR registers */ + debugfs_create_file_unsafe("fpsr_status_log", 0400, dentry, st, + &max42500_fpsr_status_log); + debugfs_create_file_unsafe("pwrup_capt_finish_log", 0644, dentry, st, + &max42500_pwrup_capt_finish_log); + debugfs_create_file_unsafe("pwrdn_capt_finish_log", 0644, dentry, st, + &max42500_pwrdn_capt_finish_log); + debugfs_create_file_unsafe("pwrup_capt_enable_log", 0644, dentry, st, + &max42500_pwrup_capt_enable_log); + debugfs_create_file_unsafe("pwrdn_capt_enable", 0644, dentry, st, + &max42500_pwrdn_capt_enable_log); + debugfs_create_file_unsafe("fps_clk_div_log", 0644, dentry, st, + &max42500_fps_clk_div_log); + debugfs_create_file_unsafe("fps_en1_timer_enable_log", 0644, dentry, st, + &max42500_fps_en1_timer_enable_log); +} + +static int max42500_hwmon_probe(struct platform_device *pdev) +{ + struct device *parent_dev = pdev->dev.parent; + struct regmap *regmap = dev_get_drvdata(parent_dev); + struct i2c_client *client = to_i2c_client(parent_dev); + struct device *dev = &pdev->dev; + struct device *hwmon_dev; + struct max42500_state *st; + int ret; + + st = devm_kzalloc(&pdev->dev, sizeof(*st), GFP_KERNEL); + if (!st) + return -ENOMEM; + + platform_set_drvdata(pdev, st); + + st->client = client; + st->regmap = regmap; + + device_set_of_node_from_dev(dev, dev->parent); + + st->power_gpio = devm_gpiod_get_optional(dev, "poweroff-gpios", + GPIOD_OUT_HIGH); + if (IS_ERR(st->power_gpio)) + return PTR_ERR(st->power_gpio); + + st->sleep_gpio = devm_gpiod_get_optional(dev, "sleepoff-gpios", + GPIOD_OUT_HIGH); + if (IS_ERR(st->sleep_gpio)) + return PTR_ERR(st->sleep_gpio); + + ret = devm_mutex_init(dev, &st->lock); + if (ret) + return ret; + + hwmon_dev = devm_hwmon_device_register_with_info(dev, "max42500", + st, &max42500_chip_info, + NULL); + + max42500_hwmon_debugfs_init(st, pdev, hwmon_dev); + + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static int max42500_resume(struct device *dev) +{ + struct max42500_state *st = dev_get_drvdata(dev); + + gpiod_set_value(st->power_gpio, GPIOD_OUT_HIGH); + + gpiod_set_value(st->sleep_gpio, + !st->fpsr[MAX42500_FPSR_FPS_EN1_START_TIMER_ENABLE]); + + return 0; +} + +static int max42500_suspend(struct device *dev) +{ + struct max42500_state *st = dev_get_drvdata(dev); + + gpiod_set_value(st->power_gpio, GPIOD_OUT_LOW); + + gpiod_set_value(st->sleep_gpio, + !!st->fpsr[MAX42500_FPSR_FPS_EN1_START_TIMER_ENABLE]); + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(max42500_pm_ops, max42500_suspend, + max42500_resume); + +static const struct platform_device_id max42500_hwmon_id_table[] = { + { "max42500-hwmon" }, + { } +}; +MODULE_DEVICE_TABLE(platform, max42500_hwmon_id_table); + +static struct platform_driver max42500_hwmon_driver = { + .driver = { + .name = "max42500-hwmon", + .pm = pm_sleep_ptr(&max42500_pm_ops), + }, + .probe = max42500_hwmon_probe, + .id_table = max42500_hwmon_id_table, +}; +module_platform_driver(max42500_hwmon_driver); + +MODULE_AUTHOR("Kent Libetario "); +MODULE_DESCRIPTION("Hwmon driver for MAX42500"); +MODULE_LICENSE("GPL"); From ffa2a87c3b556d91831fed194286923a521249e3 Mon Sep 17 00:00:00 2001 From: Kent Libetario Date: Tue, 4 Mar 2025 17:28:33 +0800 Subject: [PATCH 5/6] watchdog: add driver for max42500-wdt The MAX42500 contains a programmable windowed watchdog and can be used either as simple mode windowed watchdog or challenge-and-response mode watchdog, which is accessible through the I2C interface, along with a configurable RESET output. Signed-off-by: Kent Libetario --- drivers/watchdog/Kconfig | 10 + drivers/watchdog/Makefile | 1 + drivers/watchdog/max42500-wdt.c | 599 ++++++++++++++++++++++++++++++++ 3 files changed, 610 insertions(+) create mode 100644 drivers/watchdog/max42500-wdt.c diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 751458959411cc..e748fb96534604 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -703,6 +703,16 @@ config MAX77620_WATCHDOG MAX77620 chips. To compile this driver as a module, choose M here: the module will be called max77620_wdt. +config MAX42500_WDT + tristate "MAX42500 Programmable Windowed Watchdog" + depends on MFD_MAX42500 + select WATCHDOG_CORE + help + This is the driver for the MAX42500 watchdog timer. + Say 'Y' here to enable the watchdog timer support for + MAX42500 chips. To compile this driver as a module, + choose M here: the module will be called max42500-wdt. + config IMX2_WDT tristate "IMX2+ Watchdog" depends on ARCH_MXC || ARCH_LAYERSCAPE || COMPILE_TEST diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index 7eab9de311cb93..a4cfe1e81abf99 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -226,6 +226,7 @@ obj-$(CONFIG_GPIO_WATCHDOG) += gpio_wdt.o obj-$(CONFIG_WDAT_WDT) += wdat_wdt.o obj-$(CONFIG_WM831X_WATCHDOG) += wm831x_wdt.o obj-$(CONFIG_WM8350_WATCHDOG) += wm8350_wdt.o +obj-$(CONFIG_MAX42500_WDT) += max42500-wdt.o obj-$(CONFIG_MAX63XX_WATCHDOG) += max63xx_wdt.o obj-$(CONFIG_MAX77620_WATCHDOG) += max77620_wdt.o obj-$(CONFIG_ZIIRAVE_WATCHDOG) += ziirave_wdt.o diff --git a/drivers/watchdog/max42500-wdt.c b/drivers/watchdog/max42500-wdt.c new file mode 100644 index 00000000000000..813dda580627ad --- /dev/null +++ b/drivers/watchdog/max42500-wdt.c @@ -0,0 +1,599 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * MAX42500 - Programmable Windowed Watchdog + * + * Copyright 2025 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +enum max42500_wdt_wd_mode { + MAX42500_WD_MODE_CH_RESP, + MAX42500_WD_MODE_SIMPLE, + MAX42500_WD_MODE_MAX +}; + +enum max42500_wdt_wdog_attr { + MAX42500_WDOG_WD_STATUS, + MAX42500_WDOG_WD_SIMPLE_MODE_ENABLE, + MAX42500_WDOG_WD_CLOCK_DIVIDER, + MAX42500_WDOG_WD_OPEN_WINDOW_INTERVAL, + MAX42500_WDOG_WD_CLOSE_WINDOW_INTERVAL, + MAX42500_WDOG_WD_ENABLE, + MAX42500_WDOG_WD_FIRST_UPDATE_INTERVAL, + MAX42500_WDOG_WD_KEY, + MAX42500_WDOG_WD_LOCK_ENABLE, + MAX42500_WDOG_WD_RESET_TWO_COUNT_ENABLE, + MAX42500_WDOG_WD_RESET_HOLD_INTERVAL, + MAX42500_WDOG_MAX +}; + +static const u8 max42500_wdt_reg_wdog[] = { + MAX42500_REG_WDSTAT, + MAX42500_REG_WDCDIV, + MAX42500_REG_WDCDIV, + MAX42500_REG_WDCFG1, + MAX42500_REG_WDCFG1, + MAX42500_REG_WDCFG2, + MAX42500_REG_WDCFG2, + MAX42500_REG_WDKEY, + MAX42500_REG_WDLOCK, + MAX42500_REG_RSTCTRL, + MAX42500_REG_RSTCTRL +}; + +struct max42500_wdt_state { + struct watchdog_device wwd; + struct regmap *regmap; + struct mutex lock; /* Protects access during data transfer */ + u8 wdog[MAX42500_WDOG_MAX]; +}; + +/* Watchdog Reset hold time (usec) to Register Conversion */ +static int max42500_wdt_convert_wd_holdreset_to_reg(long value, long *reg_val) +{ + /* Check values if divisible by 8. */ + if (value % 8) + return -EINVAL; + + /* Valid are 0, 8, 16 and 32 only. */ + value = clamp_val(value, 0, 32); + if (value == 0) { + *reg_val = value; + return 0; + } + + /* Test for valid bits 1, 2 or 4. */ + if (((value >> 3) ^ 3)) { + *reg_val = (value >> 4) + 1; + return 0; + } + + return -EINVAL; +} + +/* Watchdog Key Challenge-Response Computation */ +static int max42500_wdt_set_watchdog_key(struct max42500_wdt_state *st, + enum max42500_wdt_wd_mode mode) +{ + unsigned int reg_val = 0; + unsigned int key; + u8 lfsr; + int ret; + + switch (mode) { + case MAX42500_WD_MODE_CH_RESP: + scoped_guard(mutex, &st->lock) { + ret = regmap_read(st->regmap, MAX42500_REG_WDKEY, &key); + if (ret) + return ret; + + /* Linear-Feedback Shift Register (LFSR) Polynomial Equation */ + lfsr = ((key >> 7) ^ (key >> 5) ^ (key >> 4) ^ + (key >> 3)) & 1; + reg_val = (key << 1) | lfsr; + } + + return regmap_write(st->regmap, MAX42500_REG_WDKEY, reg_val); + case MAX42500_WD_MODE_SIMPLE: + /* Any write to WDKEY register will update the watchdog */ + return regmap_write(st->regmap, MAX42500_REG_WDKEY, reg_val); + default: + return -EINVAL; + } +} + +static int max42500_wdt_wdog_get_update(struct max42500_wdt_state *st, + enum max42500_wdt_wdog_attr index) +{ + unsigned int reg_val; + int ret; + + guard(mutex)(&st->lock); + + ret = regmap_read(st->regmap, max42500_wdt_reg_wdog[index], ®_val); + if (ret) + return ret; + + switch (index) { + case MAX42500_WDOG_WD_STATUS: /* WDSTAT */ + st->wdog[index] = MAX42500_WDOG_WDSTAT(reg_val); + return 0; + case MAX42500_WDOG_WD_CLOCK_DIVIDER: /* WDIV */ + st->wdog[index] = MAX42500_WDOG_WDIV(reg_val); + return 0; + case MAX42500_WDOG_WD_SIMPLE_MODE_ENABLE: /* SWW */ + st->wdog[index] = MAX42500_WDOG_SWW(reg_val); + return 0; + case MAX42500_WDOG_WD_OPEN_WINDOW_INTERVAL: /* WDOPEN */ + st->wdog[index] = MAX42500_WDOG_WDOPEN(reg_val); + return 0; + case MAX42500_WDOG_WD_CLOSE_WINDOW_INTERVAL: /* WDCLOSE */ + st->wdog[index] = MAX42500_WDOG_WDCLOSE(reg_val); + return 0; + case MAX42500_WDOG_WD_FIRST_UPDATE_INTERVAL: /* 1UD */ + st->wdog[index] = MAX42500_WDOG_1UD(reg_val); + return 0; + case MAX42500_WDOG_WD_ENABLE: /* WDEN */ + st->wdog[index] = MAX42500_WDOG_WDEN(reg_val); + return 0; + case MAX42500_WDOG_WD_KEY: /* WDKEY */ + st->wdog[index] = MAX42500_WDOG_WDKEY(reg_val); + return 0; + case MAX42500_WDOG_WD_LOCK_ENABLE: /* WDLOCK */ + st->wdog[index] = MAX42500_WDOG_WDLOCK(reg_val); + return 0; + case MAX42500_WDOG_WD_RESET_HOLD_INTERVAL: /* RHLD */ + st->wdog[index] = MAX42500_WDOG_RHLD(reg_val); + return 0; + case MAX42500_WDOG_WD_RESET_TWO_COUNT_ENABLE: /* MR1 */ + st->wdog[index] = MAX42500_WDOG_MR1(reg_val); + return 0; + default: + return -EINVAL; + } +} + +static int max42500_wdt_wdog_set_update(struct max42500_wdt_state *st, + long value, + enum max42500_wdt_wdog_attr index) +{ + unsigned int reg_mask; + int ret; + + guard(mutex)(&st->lock); + + switch (index) { + case MAX42500_WDOG_WD_CLOCK_DIVIDER: /* WDIV */ + reg_mask = MAX42500_WDOG_WDIV_MASK; + break; + case MAX42500_WDOG_WD_SIMPLE_MODE_ENABLE: /* SWW */ + reg_mask = MAX42500_WDOG_SWW_MASK; + break; + case MAX42500_WDOG_WD_OPEN_WINDOW_INTERVAL: /* WDOPEN */ + reg_mask = MAX42500_WDOG_WDOPEN_MASK; + break; + case MAX42500_WDOG_WD_CLOSE_WINDOW_INTERVAL: /* WDCLOSE */ + reg_mask = MAX42500_WDOG_WDCLOSE_MASK; + break; + case MAX42500_WDOG_WD_FIRST_UPDATE_INTERVAL: /* 1UD */ + reg_mask = MAX42500_WDOG_1UD_MASK; + break; + case MAX42500_WDOG_WD_ENABLE: /* WDEN */ + reg_mask = MAX42500_WDOG_WDEN_MASK; + break; + case MAX42500_WDOG_WD_KEY: /* WDKEY */ + reg_mask = MAX42500_WDOG_WDKEY_MASK; + break; + case MAX42500_WDOG_WD_LOCK_ENABLE: /* WDLOCK */ + reg_mask = MAX42500_WDOG_WDLOCK_MASK; + break; + case MAX42500_WDOG_WD_RESET_HOLD_INTERVAL: /* RHLD */ + reg_mask = MAX42500_WDOG_RHLD_MASK; + break; + case MAX42500_WDOG_WD_RESET_TWO_COUNT_ENABLE: /* MR1 */ + reg_mask = MAX42500_WDOG_MR1_MASK; + break; + default: + return -EINVAL; + } + + ret = regmap_update_bits(st->regmap, max42500_wdt_reg_wdog[index], + reg_mask, value); + if (ret) + return ret; + + /* Update after successful register write */ + st->wdog[index] = FIELD_GET(GENMASK(7, 0), value); + + return 0; +} + +static int max42500_wdt_wdog_read(struct max42500_wdt_state *st, long *value, + enum max42500_wdt_wdog_attr index) +{ + u8 clk_div = MAX42500_WDOG_WD_CLOCK_DIVIDER; + u8 opn_win = MAX42500_WDOG_WD_OPEN_WINDOW_INTERVAL; + u8 clo_win = MAX42500_WDOG_WD_CLOSE_WINDOW_INTERVAL; + int ret; + + ret = max42500_wdt_wdog_get_update(st, index); + if (ret) + return ret; + + switch (index) { + case MAX42500_WDOG_WD_CLOCK_DIVIDER: /* WDIV */ + *value = (st->wdog[index] + 1) * 200; + return 0; + case MAX42500_WDOG_WD_OPEN_WINDOW_INTERVAL: /* WDOPEN */ + case MAX42500_WDOG_WD_CLOSE_WINDOW_INTERVAL: /* WDCLOSE */ + ret = max42500_wdt_wdog_get_update(st, clk_div); + if (ret) + return ret; + + *value = ((st->wdog[index] + 1) << 3) * st->wdog[clk_div]; + return 0; + case MAX42500_WDOG_WD_FIRST_UPDATE_INTERVAL: /* 1UD */ + ret = max42500_wdt_wdog_get_update(st, clo_win); + if (ret) + return ret; + + ret = max42500_wdt_wdog_get_update(st, opn_win); + if (ret) + return ret; + + *value = (st->wdog[clo_win] + st->wdog[opn_win]) * + ((st->wdog[index] << 1) + 1); + return 0; + case MAX42500_WDOG_WD_RESET_HOLD_INTERVAL: /* RHLD */ + *value = st->wdog[index] ? (1 << (st->wdog[index] + 3)) : 0; + return 0; + default: + *value = st->wdog[index]; + return 0; + } +} + +static int max42500_wdt_wdog_write(struct max42500_wdt_state *st, long value, + enum max42500_wdt_wdog_attr index) +{ + u8 clk_div = MAX42500_WDOG_WD_CLOCK_DIVIDER; + u8 win_mode = MAX42500_WDOG_WD_SIMPLE_MODE_ENABLE; + u8 opn_win = MAX42500_WDOG_WD_OPEN_WINDOW_INTERVAL; + u8 clo_win = MAX42500_WDOG_WD_CLOSE_WINDOW_INTERVAL; + long wdog; + int ret; + + switch (index) { + case MAX42500_WDOG_WD_CLOCK_DIVIDER: /* WDIV */ + wdog = (value - 200) / 200; + break; + case MAX42500_WDOG_WD_OPEN_WINDOW_INTERVAL: /* WDOPEN */ + case MAX42500_WDOG_WD_CLOSE_WINDOW_INTERVAL: /* WDCLOSE */ + wdog = value / (st->wdog[clk_div] << 3); + break; + case MAX42500_WDOG_WD_FIRST_UPDATE_INTERVAL: /* 1UD */ + wdog = ((value / (st->wdog[clo_win] + st->wdog[opn_win])) + - 1) >> 1; + break; + case MAX42500_WDOG_WD_RESET_HOLD_INTERVAL: /* RHLD */ + /* Valid values are 0, 8, 16, 32 and truncates larger values */ + ret = max42500_wdt_convert_wd_holdreset_to_reg(value, &wdog); + if (ret) + return ret; + break; + case MAX42500_WDOG_WD_KEY: /* WDKEY */ + /* User cannot input watchdog key but can trigger key update */ + ret = max42500_wdt_set_watchdog_key(st, st->wdog[win_mode]); + if (ret) + return ret; + + return 0; + default: + return -EINVAL; + } + + return max42500_wdt_wdog_set_update(st, wdog, index); +} + +static int max42500_wdt_show_wd_simp_mode_enable_log(void *arg, u64 *val) +{ + long wdog_val; + int ret; + + ret = max42500_wdt_wdog_read(arg, &wdog_val, + MAX42500_WDOG_WD_SIMPLE_MODE_ENABLE); + if (ret) + return ret; + + *val = wdog_val; + return 0; +} + +static int max42500_wdt_store_wd_simp_mode_enable_log(void *arg, u64 val) +{ + return max42500_wdt_wdog_write(arg, val, + MAX42500_WDOG_WD_SIMPLE_MODE_ENABLE); +} +DEFINE_DEBUGFS_ATTRIBUTE(max42500_wdt_wd_simp_mode_enable_log, + max42500_wdt_show_wd_simp_mode_enable_log, + max42500_wdt_store_wd_simp_mode_enable_log, "%llu\n"); + +static int max42500_wdt_show_wd_open_win_log(void *arg, u64 *val) +{ + long wdog_val; + int ret; + + ret = max42500_wdt_wdog_read(arg, &wdog_val, + MAX42500_WDOG_WD_OPEN_WINDOW_INTERVAL); + if (ret) + return ret; + + *val = wdog_val; + return 0; +} + +static int max42500_wdt_store_wd_open_win_log(void *arg, u64 val) +{ + return max42500_wdt_wdog_write(arg, val, + MAX42500_WDOG_WD_OPEN_WINDOW_INTERVAL); +} +DEFINE_DEBUGFS_ATTRIBUTE(max42500_wdt_wd_open_win_log, + max42500_wdt_show_wd_open_win_log, + max42500_wdt_store_wd_open_win_log, "%llu\n"); + +static int max42500_wdt_show_wd_close_win_log(void *arg, u64 *val) +{ + long wdog_val; + int ret; + + ret = max42500_wdt_wdog_read(arg, &wdog_val, + MAX42500_WDOG_WD_CLOSE_WINDOW_INTERVAL); + if (ret) + return ret; + + *val = wdog_val; + return 0; +} + +static int max42500_wdt_store_wd_close_win_log(void *arg, u64 val) +{ + return max42500_wdt_wdog_write(arg, val, + MAX42500_WDOG_WD_CLOSE_WINDOW_INTERVAL); +} +DEFINE_DEBUGFS_ATTRIBUTE(max42500_wdt_wd_close_win_log, + max42500_wdt_show_wd_close_win_log, + max42500_wdt_store_wd_close_win_log, "%llu\n"); + +static int max42500_wdt_show_wd_lock_enablelog(void *arg, u64 *val) +{ + long wdog_val; + int ret; + + ret = max42500_wdt_wdog_read(arg, &wdog_val, + MAX42500_WDOG_WD_LOCK_ENABLE); + if (ret) + return ret; + + *val = wdog_val; + return 0; +} + +static int max42500_wdt_store_wd_lock_enable_log(void *arg, u64 val) +{ + return max42500_wdt_wdog_write(arg, val, + MAX42500_WDOG_WD_LOCK_ENABLE); +} +DEFINE_DEBUGFS_ATTRIBUTE(max42500_wdt_wd_lock_enable_log, + max42500_wdt_show_wd_lock_enablelog, + max42500_wdt_store_wd_lock_enable_log, "%llu\n"); + +static int max42500_wdt_show_wd_rst2_cnt_enable_log(void *arg, u64 *val) +{ + long wdog_val; + int ret; + + ret = max42500_wdt_wdog_read(arg, &wdog_val, + MAX42500_WDOG_WD_RESET_TWO_COUNT_ENABLE); + if (ret) + return ret; + + *val = wdog_val; + return 0; +} + +static int max42500_wdt_store_wd_rst2_cnt_enable_log(void *arg, u64 val) +{ + return max42500_wdt_wdog_write(arg, val, + MAX42500_WDOG_WD_RESET_TWO_COUNT_ENABLE); +} +DEFINE_DEBUGFS_ATTRIBUTE(max42500_wdt_wd_rst2_cnt_enable_log, + max42500_wdt_show_wd_rst2_cnt_enable_log, + max42500_wdt_store_wd_rst2_cnt_enable_log, "%llu\n"); + +static void max42500_wdt_debugfs_remove(void *dir) +{ + debugfs_remove_recursive(dir); +} + +static void max42500_wdt_debugfs_init(struct max42500_wdt_state *st, + struct platform_device *pdev, + const struct device *watchdog) +{ + const char *debugfs_name; + struct dentry *dentry; + int ret; + + if (!IS_ENABLED(CONFIG_DEBUG_FS)) + return; + + debugfs_name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "max42500-%s", + dev_name(watchdog)); + if (!debugfs_name) + return; + + dentry = debugfs_create_dir(debugfs_name, NULL); + if (IS_ERR(dentry)) + return; + + ret = devm_add_action_or_reset(&pdev->dev, max42500_wdt_debugfs_remove, + dentry); + if (ret) + return; + + /* Watchdog registers */ + debugfs_create_file_unsafe("wd_simp_mode_enable_log", 0644, dentry, st, + &max42500_wdt_wd_simp_mode_enable_log); + debugfs_create_file_unsafe("wd_open_win_log", 0644, dentry, st, + &max42500_wdt_wd_open_win_log); + debugfs_create_file_unsafe("wd_close_win_log", 0644, dentry, st, + &max42500_wdt_wd_close_win_log); + debugfs_create_file_unsafe("wd_lock_enable_log", 0644, dentry, st, + &max42500_wdt_wd_lock_enable_log); + debugfs_create_file_unsafe("wd_rst2_cnt_enable_log", 0644, dentry, st, + &max42500_wdt_wd_rst2_cnt_enable_log); +} + +static int max42500_wdt_start(struct watchdog_device *wdd) +{ + return max42500_wdt_wdog_write(watchdog_get_drvdata(wdd), 1, + MAX42500_WDOG_WD_ENABLE); +} + +static int max42500_wdt_stop(struct watchdog_device *wdd) +{ + return max42500_wdt_wdog_write(watchdog_get_drvdata(wdd), 0, + MAX42500_WDOG_WD_ENABLE); +} + +static int max42500_wdt_ping(struct watchdog_device *wdd) +{ + return max42500_wdt_wdog_write(watchdog_get_drvdata(wdd), 0, + MAX42500_WDOG_WD_KEY); +} + +static unsigned int max42500_wdt_status(struct watchdog_device *wdd) +{ + long wdog_val; + int ret; + + ret = max42500_wdt_wdog_read(watchdog_get_drvdata(wdd), &wdog_val, + MAX42500_WDOG_WD_STATUS); + if (ret) + return MAX42500_WDOG_WDSTAT_MASK; + + return wdog_val; +} + +static int max42500_wdt_set_timeout(struct watchdog_device *wdd, + unsigned int val) +{ + return max42500_wdt_wdog_write(watchdog_get_drvdata(wdd), val, + MAX42500_WDOG_WD_CLOCK_DIVIDER); +} + +static int max42500_wdt_set_pretimeout(struct watchdog_device *wdd, + unsigned int val) +{ + return max42500_wdt_wdog_write(watchdog_get_drvdata(wdd), val, + MAX42500_WDOG_WD_FIRST_UPDATE_INTERVAL); +} + +static int max42500_wdt_restart(struct watchdog_device *wdd, + unsigned long val, void *info) +{ + return max42500_wdt_wdog_write(watchdog_get_drvdata(wdd), val, + MAX42500_WDOG_WD_RESET_HOLD_INTERVAL); +} + +static const struct watchdog_ops max42500_wdt_watchdog_ops = { + .owner = THIS_MODULE, + .start = max42500_wdt_start, + .stop = max42500_wdt_stop, + .ping = max42500_wdt_ping, + .status = max42500_wdt_status, + .set_timeout = max42500_wdt_set_timeout, + .set_pretimeout = max42500_wdt_set_pretimeout, + .restart = max42500_wdt_restart, +}; + +static const struct watchdog_info max42500_wdt_watchdog_info = { + .options = WDIOF_KEEPALIVEPING, + .identity = "max42500_wdt Watchdog", +}; + +static int max42500_wdt_probe(struct platform_device *pdev) +{ + struct device *parent_dev = pdev->dev.parent; + struct regmap *regmap = dev_get_drvdata(parent_dev); + struct device *dev = &pdev->dev; + struct watchdog_device *wdog_dev; + struct max42500_wdt_state *st; + long open_wdt, close_wdt; + int ret; + + st = devm_kzalloc(&pdev->dev, sizeof(*st), GFP_KERNEL); + if (!st) + return -ENOMEM; + + st->regmap = regmap; + + ret = devm_mutex_init(dev, &st->lock); + if (ret) + return ret; + + wdog_dev = &st->wwd; + wdog_dev->parent = &pdev->dev; + wdog_dev->info = &max42500_wdt_watchdog_info; + wdog_dev->ops = &max42500_wdt_watchdog_ops; + + ret = max42500_wdt_wdog_read(st, &open_wdt, + MAX42500_WDOG_WD_OPEN_WINDOW_INTERVAL); + if (ret) + return ret; + + wdog_dev->min_timeout = open_wdt; + + ret = max42500_wdt_wdog_read(st, &close_wdt, + MAX42500_WDOG_WD_OPEN_WINDOW_INTERVAL); + if (ret) + return ret; + + wdog_dev->max_timeout = open_wdt + close_wdt; + + platform_set_drvdata(pdev, st); + + max42500_wdt_debugfs_init(st, pdev, dev); + + watchdog_set_drvdata(wdog_dev, st); + + return devm_watchdog_register_device(dev, wdog_dev); +} + +static const struct platform_device_id max42500_wdt_id_table[] = { + { "max42500-wdt" }, + { } +}; +MODULE_DEVICE_TABLE(platform, max42500_wdt_id_table); + +static struct platform_driver max42500_wdt_driver = { + .driver = { + .name = "max42500-wdt", + }, + .probe = max42500_wdt_probe, + .id_table = max42500_wdt_id_table, +}; +module_platform_driver(max42500_wdt_driver); + +MODULE_AUTHOR("Kent Libetario "); +MODULE_DESCRIPTION("Watchdog driver for MAX42500"); +MODULE_LICENSE("GPL"); From ff692c07223d29a224fe92fe32547c432c0e5c7f Mon Sep 17 00:00:00 2001 From: Kent Libetario Date: Tue, 4 Mar 2025 17:29:35 +0800 Subject: [PATCH 6/6] Kconfig.adi: imply SENSORS_MAX42500 Add entry for the MAX42500 driver Signed-off-by: Kent Libetario --- Kconfig.adi | 2 ++ drivers/hwmon/Kconfig.adi | 1 + 2 files changed, 3 insertions(+) diff --git a/Kconfig.adi b/Kconfig.adi index 307c1bab7b03c7..30e894a1bab123 100644 --- a/Kconfig.adi +++ b/Kconfig.adi @@ -96,6 +96,8 @@ config KERNEL_ALL_ADI_DRIVERS imply REGULATOR_MAX77857 imply REGULATOR_MAX77541 imply MFD_MAX77541 + imply MAX42500_WDT + imply MFD_MAX42500 source "drivers/clk/Kconfig.adi" source "drivers/hwmon/Kconfig.adi" diff --git a/drivers/hwmon/Kconfig.adi b/drivers/hwmon/Kconfig.adi index db613969acc86c..df7e06f8e46de0 100644 --- a/drivers/hwmon/Kconfig.adi +++ b/drivers/hwmon/Kconfig.adi @@ -44,4 +44,5 @@ config HWMON_ALL_ADI_DRIVERS imply MAX31827 imply SENSORS_MAX31760 imply SENSORS_LT7182S + imply SENSORS_MAX42500