diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst index a8b3e870d13e..e924e511bc60 100644 --- a/Documentation/hwmon/index.rst +++ b/Documentation/hwmon/index.rst @@ -282,6 +282,7 @@ Hardware Monitoring Kernel Drivers vexpress via686a vt1211 + vt7505 w83627ehf w83627hf w83773g diff --git a/Documentation/hwmon/vt7505.rst b/Documentation/hwmon/vt7505.rst new file mode 100644 index 000000000000..e56dc0055e89 --- /dev/null +++ b/Documentation/hwmon/vt7505.rst @@ -0,0 +1,89 @@ +.. SPDX-License-Identifier: GPL-2.0 + +Kernel driver vt7505 +==================== + +Supported chips: + + * Analog Devices MAX16545 + + Prefix: 'max16545' + + Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/max16545b-max16545c.pdf + + * Analog Devices MAX16550 + + Prefix: 'max16550' + + Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/max16550.pdf + + * Volterra VT7505 + + Prefix: 'vt7505' + + Datasheet: Not publicly available; register-compatible with the MAX16545. + +Author: Georgi Vlaev + +Description +----------- + +This driver supports hardware monitoring for the Analog Devices MAX16545 and +MAX16550, and the Volterra VT7505 PMBus controllers. + +The devices report input and output voltage, input and output current, +input power, and temperature. All values use the PMBus direct data format. +Peak (maximum) values for input voltage, output voltage, output current, +input power and temperature are exposed and can be reset. Writing to any +``*_reset_history`` attribute clears all of the device's peak values. + +The reported current and power values depend on the resistance of the +external current-report resistor connected between the ILOAD pin and +ground. This resistance can be provided via the ``adi,rload-ohms`` +device tree property; a default of 4750 ohms is used when it is absent. + +Sysfs entries +------------- + +The limit and alarm attributes are created by the PMBus core for the limit +registers that the device implements. See +Documentation/hwmon/sysfs-interface.rst for the meaning of each attribute. + +======================= ======================================================== +in1_label "vin" +in1_input Measured input voltage. +in1_min Input undervoltage warning limit. +in1_min_alarm Input undervoltage warning alarm. +in1_lcrit Input undervoltage fault limit. +in1_lcrit_alarm Input undervoltage fault alarm. +in1_highest Historical maximum input voltage. +in1_reset_history Write to reset all peak values. +in2_label "vout1" +in2_input Measured output voltage. +in2_min Output undervoltage warning limit. +in2_min_alarm Output undervoltage warning alarm. +in2_highest Historical maximum output voltage. +in2_reset_history Write to reset all peak values. +curr1_label "iin" +curr1_input Measured input current. +curr1_alarm Input current alarm. +curr2_label "iout1" +curr2_input Measured output current. +curr2_max Output overcurrent warning limit. +curr2_max_alarm Output overcurrent warning alarm. +curr2_highest Historical maximum output current. +curr2_reset_history Write to reset all peak values. +power1_label "pin" +power1_input Measured input power. +power1_max Input overpower warning limit. +power1_alarm Input overpower warning alarm. +power1_input_highest Historical maximum input power. +power1_reset_history Write to reset all peak values. +temp1_input Measured temperature. +temp1_max Overtemperature warning limit. +temp1_max_alarm Overtemperature warning alarm. +temp1_crit Overtemperature fault limit. +temp1_crit_alarm Overtemperature fault alarm. +temp1_highest Historical maximum temperature. +temp1_reset_history Write to reset all peak values. +======================= ======================================================== diff --git a/MAINTAINERS b/MAINTAINERS index 119bc58dbc43..2142c15b9946 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -28972,6 +28972,14 @@ S: Maintained F: Documentation/hwmon/vt1211.rst F: drivers/hwmon/vt1211.c +VT7505 HARDWARE MONITOR DRIVER +M: Sanman Pradhan +L: linux-hwmon@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/hwmon/pmbus/adi,vt7505.yaml +F: Documentation/hwmon/vt7505.rst +F: drivers/hwmon/pmbus/vt7505.c + VT8231 HARDWARE MONITOR DRIVER M: Roger Lucas L: linux-hwmon@vger.kernel.org diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig index 9b4bfdd00310..becc311dacc6 100644 --- a/drivers/hwmon/pmbus/Kconfig +++ b/drivers/hwmon/pmbus/Kconfig @@ -796,6 +796,15 @@ config SENSORS_UCD9200 This driver can also be built as a module. If so, the module will be called ucd9200. +config SENSORS_VT7505 + tristate "Analog Devices MAX16545, MAX16550 and Volterra VT7505" + help + If you say yes here you get hardware monitoring support for Analog + Devices MAX16545, MAX16550 and Volterra VT7505 PMBus controllers. + + This driver can also be built as a module. If so, the module will + be called vt7505. + config SENSORS_XDP710 tristate "Infineon XDP710 family" help diff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile index 080d366809f3..daa4b49bc90f 100644 --- a/drivers/hwmon/pmbus/Makefile +++ b/drivers/hwmon/pmbus/Makefile @@ -75,6 +75,7 @@ obj-$(CONFIG_SENSORS_TPS53679) += tps53679.o obj-$(CONFIG_SENSORS_TPS546D24) += tps546d24.o obj-$(CONFIG_SENSORS_UCD9000) += ucd9000.o obj-$(CONFIG_SENSORS_UCD9200) += ucd9200.o +obj-$(CONFIG_SENSORS_VT7505) += vt7505.o obj-$(CONFIG_SENSORS_XDP710) += xdp710.o obj-$(CONFIG_SENSORS_XDP720) += xdp720.o obj-$(CONFIG_SENSORS_XDPE122) += xdpe12284.o diff --git a/drivers/hwmon/pmbus/vt7505.c b/drivers/hwmon/pmbus/vt7505.c new file mode 100644 index 000000000000..6a66f06eddb8 --- /dev/null +++ b/drivers/hwmon/pmbus/vt7505.c @@ -0,0 +1,287 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Hardware monitoring driver for Analog Devices MAX16545/MAX16550 and + * Volterra VT7505 PMBus controllers. + * + * Copyright 2026 Hewlett Packard Enterprise Development LP + */ + +#include +#include +#include +#include +#include +#include + +#include "pmbus.h" + +#define VT7505_MFR_CONFIG 0xd0 +#define VT7505_CFG_OCP_S_FILT_MASK GENMASK(15, 14) + +#define VT7505_MFR_PEAK_VIN 0xd1 +#define VT7505_MFR_PEAK_IOUT 0xd2 +#define VT7505_MFR_PEAK_PIN 0xd3 +#define VT7505_MFR_PEAK_TEMP 0xd4 +#define VT7505_MFR_CLEAR_PEAKS 0xd5 +#define VT7505_MFR_PEAK_VOUT 0xfd + +#define VT7505_RLOAD_DEFAULT 4750 +/* Largest RLOAD for which coeff * rload / 1000 still fits in s32. */ +#define VT7505_RLOAD_MAX 283383959U + +struct vt7505_chip_data { + int temp_m; + int temp_b; + bool has_ocp_filter; +}; + +static const struct vt7505_chip_data max16545_data = { + .temp_m = 205, + .temp_b = 6545, +}; + +static const struct vt7505_chip_data max16550_data = { + .temp_m = 199, + .temp_b = 7046, + .has_ocp_filter = true, +}; + +static const struct vt7505_chip_data vt7505_data = { + .temp_m = 205, + .temp_b = 6545, + .has_ocp_filter = true, +}; + +static int vt7505_read_word_data(struct i2c_client *client, int page, + int phase, int reg) +{ + switch (reg) { + case PMBUS_VIRT_READ_VIN_MAX: + return pmbus_read_word_data(client, page, phase, + VT7505_MFR_PEAK_VIN); + case PMBUS_VIRT_READ_IOUT_MAX: + return pmbus_read_word_data(client, page, phase, + VT7505_MFR_PEAK_IOUT); + case PMBUS_VIRT_READ_PIN_MAX: + return pmbus_read_word_data(client, page, phase, + VT7505_MFR_PEAK_PIN); + case PMBUS_VIRT_READ_TEMP_MAX: + return pmbus_read_word_data(client, page, phase, + VT7505_MFR_PEAK_TEMP); + case PMBUS_VIRT_READ_VOUT_MAX: + return pmbus_read_word_data(client, page, phase, + VT7505_MFR_PEAK_VOUT); + case PMBUS_VIRT_RESET_VIN_HISTORY: + case PMBUS_VIRT_RESET_IOUT_HISTORY: + case PMBUS_VIRT_RESET_PIN_HISTORY: + case PMBUS_VIRT_RESET_TEMP_HISTORY: + case PMBUS_VIRT_RESET_VOUT_HISTORY: + return 0; + default: + return -ENODATA; + } +} + +static int vt7505_write_word_data(struct i2c_client *client, int page, + int reg, u16 word) +{ + switch (reg) { + /* + * A single reset command clears all peak values. CLEAR_PEAKS is a + * send-byte command; the device NAKs a word or byte-data write to it. + */ + case PMBUS_VIRT_RESET_VIN_HISTORY: + case PMBUS_VIRT_RESET_IOUT_HISTORY: + case PMBUS_VIRT_RESET_PIN_HISTORY: + case PMBUS_VIRT_RESET_TEMP_HISTORY: + case PMBUS_VIRT_RESET_VOUT_HISTORY: + return pmbus_write_byte(client, page, + VT7505_MFR_CLEAR_PEAKS); + default: + return -ENODATA; + } +} + +/* + * None of these controllers implement the standard PMBus WRITE_PROTECT + * (0x10) register, so tell the core not to access it. + */ +static struct pmbus_platform_data vt7505_pdata = { + .flags = PMBUS_NO_WRITE_PROTECT, +}; + +static int vt7505_set_ocp_filter(struct i2c_client *client) +{ + u32 ocp_us; + u8 field; + int ret; + u16 word; + + if (of_property_read_u32(client->dev.of_node, "adi,ocp-severe-filter-us", + &ocp_us)) + return 0; + + switch (ocp_us) { + case 0: + field = 0; + break; + case 1: + field = 1; + break; + case 2: + field = 2; + break; + case 10: + field = 3; + break; + default: + return dev_err_probe(&client->dev, -EINVAL, + "invalid adi,ocp-severe-filter-us value %u\n", + ocp_us); + } + + ret = i2c_smbus_read_word_data(client, VT7505_MFR_CONFIG); + if (ret < 0) + return dev_err_probe(&client->dev, ret, + "failed to read MFR_CONFIG\n"); + + word = ret & ~VT7505_CFG_OCP_S_FILT_MASK; + word |= FIELD_PREP(VT7505_CFG_OCP_S_FILT_MASK, field); + + ret = i2c_smbus_write_word_data(client, VT7505_MFR_CONFIG, word); + if (ret < 0) + return dev_err_probe(&client->dev, ret, + "failed to write MFR_CONFIG\n"); + + return 0; +} + +static void vt7505_set_m(int *m, u32 rload) +{ + u64 val = (u64)*m * rload; + + /* rload is range-checked in probe, so the result fits in int. */ + *m = DIV_ROUND_CLOSEST_ULL(val, 1000); +} + +static int vt7505_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + const struct vt7505_chip_data *chip; + struct pmbus_driver_info *info; + u32 rload; + int ret; + + chip = i2c_get_match_data(client); + if (!chip) + return -ENODEV; + + info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + dev->platform_data = &vt7505_pdata; + + /* + * The m coefficient used in the direct-format current and power + * calculations depends on RLOAD, the external current-report resistor + * connected between the ILOAD pin and ground. Use the default value if + * none is specified. + */ + if (of_property_read_u32(dev->of_node, "adi,rload-ohms", &rload)) + rload = VT7505_RLOAD_DEFAULT; + + if (!rload || rload > VT7505_RLOAD_MAX) + return dev_err_probe(dev, -EINVAL, + "adi,rload-ohms must be 1-%u\n", + VT7505_RLOAD_MAX); + + info->pages = 1; + info->read_word_data = vt7505_read_word_data; + info->write_word_data = vt7505_write_word_data; + + info->format[PSC_VOLTAGE_IN] = direct; + info->format[PSC_VOLTAGE_OUT] = direct; + info->format[PSC_CURRENT_IN] = direct; + info->format[PSC_CURRENT_OUT] = direct; + info->format[PSC_POWER] = direct; + info->format[PSC_TEMPERATURE] = direct; + + /* + * Direct data format coefficients from the device datasheet ("PMBus + * Equation Parameters"). The current and power m coefficients scale + * with RLOAD; vt7505_set_m() applies the 1/1000 factor below, giving + * m = 3.824 * RLOAD for current and 0.895 * RLOAD for power. The + * temperature coefficients are chip specific (see the chip data). + */ + info->m[PSC_VOLTAGE_IN] = 7578; + info->R[PSC_VOLTAGE_IN] = -2; + info->m[PSC_VOLTAGE_OUT] = 7578; + info->R[PSC_VOLTAGE_OUT] = -2; + info->m[PSC_CURRENT_IN] = 3824; + info->b[PSC_CURRENT_IN] = -4300; + info->R[PSC_CURRENT_IN] = -3; + info->m[PSC_CURRENT_OUT] = 3824; + info->b[PSC_CURRENT_OUT] = -4300; + info->R[PSC_CURRENT_OUT] = -3; + info->m[PSC_POWER] = 895; + info->b[PSC_POWER] = -9100; + info->R[PSC_POWER] = -2; + + vt7505_set_m(&info->m[PSC_CURRENT_IN], rload); + vt7505_set_m(&info->m[PSC_CURRENT_OUT], rload); + vt7505_set_m(&info->m[PSC_POWER], rload); + + info->m[PSC_TEMPERATURE] = chip->temp_m; + info->b[PSC_TEMPERATURE] = chip->temp_b; + info->R[PSC_TEMPERATURE] = -2; + + info->func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT | + PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | + PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP | + PMBUS_HAVE_IIN | PMBUS_HAVE_PIN; + + /* + * The severe OCP deglitch filter is programmable on the MAX16550 and + * the VT7505, but fixed on the MAX16545. + */ + if (chip->has_ocp_filter) { + ret = vt7505_set_ocp_filter(client); + if (ret) + return ret; + } + + return pmbus_do_probe(client, info); +} + +static const struct i2c_device_id vt7505_id[] = { + { .name = "max16545", .driver_data = (kernel_ulong_t)&max16545_data }, + { .name = "max16550", .driver_data = (kernel_ulong_t)&max16550_data }, + { .name = "vt7505", .driver_data = (kernel_ulong_t)&vt7505_data }, + { } +}; +MODULE_DEVICE_TABLE(i2c, vt7505_id); + +static const struct of_device_id vt7505_of_match[] = { + { .compatible = "adi,max16545", .data = &max16545_data }, + { .compatible = "adi,max16550", .data = &max16550_data }, + { .compatible = "adi,vt7505", .data = &vt7505_data }, + { } +}; +MODULE_DEVICE_TABLE(of, vt7505_of_match); + +static struct i2c_driver vt7505_driver = { + .driver = { + .name = "vt7505", + .of_match_table = vt7505_of_match, + }, + .probe = vt7505_probe, + .id_table = vt7505_id, +}; +module_i2c_driver(vt7505_driver); + +MODULE_AUTHOR("Georgi Vlaev "); +MODULE_DESCRIPTION("PMBus driver for Analog Devices MAX16545/MAX16550 and Volterra VT7505"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("PMBUS");