* [PATCH v10 0/2] Add support for Microchip EMC1812
From: Marius Cristea @ 2026-04-29 12:58 UTC (permalink / raw)
To: Guenter Roeck, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Jonathan Corbet
Cc: linux-hwmon, devicetree, linux-kernel, linux-doc, Marius Cristea
This is the hwmon driver for EMC1812/13/14/15/33 multichannel Low-Voltage
Remote Diode Sensor Family. The chips in the family have one internal
and different numbers of external channels, ranging from 1 (EMC1812) to
4 channels (EMC1815).
Reading diodes in anti-parallel connection is supported by EMC1814, EMC1815
and EMC1833.
Signed-off-by: Marius Cristea <marius.cristea@microchip.com>
---
Changes in v10:
- made comments more clear into the devicetree binding
- allow channel 0 (internal channel) into devicetree binding
- allow the default name for Channel 0 to be overridden by the Device Tree property
- translate temperature limits to support the hardware's extended temperature range
- update channel count validation to properly account for the internal channel
- return -EOPNOTSUPP if channel is greater than or equal to phys_channels
- Link to v9: https://lore.kernel.org/r/20260403-hw_mon-emc1812-v9-0-1a798f31cf2e@microchip.com
Changes in v9:
- improve the wording in the Documentation/hwmon/emc1812.rst file
- add const to variables in the driver
- initialize the EXT2_BETA_CONFIG only for the pats that support it
- update the writeble regmap table to exclude read-only registers
- Link to v8: https://lore.kernel.org/r/20260310-hw_mon-emc1812-v8-0-bc155727e0d2@microchip.com
Changes in v8:
- remove "address scan" from emc1812.rst documentation
- change the second dimension of emc1812_limit_regs_low[][] to 2
- clamp input value before doing math on it to avoid overflow
- use rounding instead of truncation for 8 bits limit registers
- fix misleading comment when HW ID is not recognized
- Link to v7: https://lore.kernel.org/r/20260223-hw_mon-emc1812-v7-0-51e2676f4e20@microchip.com
Changes in v7:
- driver
- fix an overflow emc1812_set_hyst
- remove unused parameter in emc1812_set_temp
- devicetree binding:
- remove unneeded restrictions not to bloating the binding
- Link to v6: https://lore.kernel.org/r/20260212-hw_mon-emc1812-v6-0-e37e9b38d898@microchip.com
Changes in v6:
- driver
- fix an overflow when writing more then 191875 to limits stored on 8
bits register
- remove "i2c_set_clientdata" from probe
- fix discrepancy where writing 16ms and reading it back returns 15ms
at update interval
- skip setting the ideality factor for channels that are not available
on the device
- devicetree binding:
- change the way interrupts are described/used
- add "microchip,enable-anti-parallel"
- rewrite "allOf" section to be more clear
- Link to v5: https://lore.kernel.org/r/20260205-hw_mon-emc1812-v5-0-232835aefe8f@microchip.com
Changes in v5:
- fix calculation in emc1812_get_limit_temp
- use i2c_get_match_data cover the case when the driver is instantiated
via I2C ID table.
- replace dev_info with dev_warn
- remove some unnecessary truncation on 8 bits
- remove clamping when reading the temerature with hyst
- not change the conversion rate at probe time
- use a generic define to remove duplicate channel_info entries
- Link to v4: https://lore.kernel.org/r/20260127-hw_mon-emc1812-v4-0-6bf636b54847@microchip.com
Changes in v4:
- fix file permissions for read only properties
- fix calculation when the limits are written
- remove the temp_min_hyst because the part doesn't support it
- Link to v3: https://lore.kernel.org/r/20251218-hw_mon-emc1812-v3-0-a123ada7b859@microchip.com
Changes in v3:
- remove mesages that are not helpfull
- fix an issue related to NULL labels
- fix sign/unsign calculation
- replace E2BIG with EINVAL
- use BIT() to create mask
- Link to v2: https://lore.kernel.org/r/20251121-hw_mon-emc1812-v2-0-5b2070f8b778@microchip.com
Changes in v2:
- update the interrupt section from yaml file
- update index.rst
- remove fault condition from internal sensor
- remove unused members from structures
- update the driver to work on systems without device tree or
firmware nodes
- add missing include files
- make NULL labels to be not visible
- corect sign/unsign calculations
- corect possible underflow for limits
- Link to v1: https://lore.kernel.org/r/20251029-hw_mon-emc1812-v1-0-be4fd8af016a@microchip.com
---
Marius Cristea (2):
dt-bindings: hwmon: temperature: add support for EMC1812
hwmon: temperature: add support for EMC1812
.../bindings/hwmon/microchip,emc1812.yaml | 193 ++++
Documentation/hwmon/emc1812.rst | 67 ++
Documentation/hwmon/index.rst | 1 +
MAINTAINERS | 8 +
drivers/hwmon/Kconfig | 11 +
drivers/hwmon/Makefile | 1 +
drivers/hwmon/emc1812.c | 980 +++++++++++++++++++++
7 files changed, 1261 insertions(+)
---
base-commit: d2b2fea3503e5e12b2e28784152937e48bcca6ff
change-id: 20251002-hw_mon-emc1812-f1b806487d10
Best regards,
--
Marius Cristea <marius.cristea@microchip.com>
^ permalink raw reply
* Re: [PATCH v10 2/3] hwmon: ltc4283: Add support for the LTC4283 Swap Controller
From: Nuno Sá @ 2026-04-29 12:33 UTC (permalink / raw)
To: nuno.sa
Cc: linux-gpio, linux-hwmon, devicetree, linux-doc, Guenter Roeck,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Jonathan Corbet,
Linus Walleij, Bartosz Golaszewski
In-Reply-To: <20260428-ltc4283-support-v10-2-4f26f46491c3@analog.com>
On Tue, Apr 28, 2026 at 05:07:27PM +0100, Nuno Sá via B4 Relay wrote:
> From: Nuno Sá <nuno.sa@analog.com>
>
> Support the LTC4283 Hot Swap Controller. The device features programmable
> current limit with foldback and independently adjustable inrush current to
> optimize the MOSFET safe operating area (SOA). The SOA timer limits MOSFET
> temperature rise for reliable protection against overstresses.
>
> An I2C interface and onboard ADC allow monitoring of board current,
> voltage, power, energy, and fault status.
>
> Signed-off-by: Nuno Sá <nuno.sa@analog.com>
> ---
Alright,
Guenter, from the bot review I would say only the LTC4283_VPWR typo in
ltc4283_read_in_alarm() is critical. If you agree and don't see anything
else critical, I'll fix and re-spin.
- Nuno Sá
> Documentation/hwmon/index.rst | 1 +
> Documentation/hwmon/ltc4283.rst | 266 ++++++
> MAINTAINERS | 1 +
> drivers/hwmon/Kconfig | 12 +
> drivers/hwmon/Makefile | 1 +
> drivers/hwmon/ltc4283.c | 1795 +++++++++++++++++++++++++++++++++++++++
> 6 files changed, 2076 insertions(+)
>
> diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst
> index 199f35a75282..d54dda83ab6e 100644
> --- a/Documentation/hwmon/index.rst
> +++ b/Documentation/hwmon/index.rst
> @@ -144,6 +144,7 @@ Hardware Monitoring Kernel Drivers
> ltc4260
> ltc4261
> ltc4282
> + ltc4283
> ltc4286
> macsmc-hwmon
> max127
> diff --git a/Documentation/hwmon/ltc4283.rst b/Documentation/hwmon/ltc4283.rst
> new file mode 100644
> index 000000000000..ba88445e45f4
> --- /dev/null
> +++ b/Documentation/hwmon/ltc4283.rst
> @@ -0,0 +1,266 @@
> +.. SPDX-License-Identifier: GPL-2.0-only
> +
> +Kernel drivers ltc4283
> +==========================================
> +
> +Supported chips:
> +
> + * Analog Devices LTC4283
> +
> + Prefix: 'ltc4283'
> +
> + Addresses scanned: -
> +
> + Datasheet:
> +
> + https://www.analog.com/media/en/technical-documentation/data-sheets/ltc4283.pdf
> +
> +Author: Nuno Sá <nuno.sa@analog.com>
> +
> +Description
> +___________
> +
> +The LTC4283 negative voltage hot swap controller drives an external N-channel
> +MOSFET to allow a board to be safely inserted and removed from a live backplane.
> +The device features programmable current limit with foldback and independently
> +adjustable inrush current to optimize the MOSFET safe operating area (SOA). The
> +SOA timer limits MOSFET temperature rise for reliable protection against
> +overstresses. An I2C interface and onboard gear-shift ADC allow monitoring of
> +board current, voltage, power, energy, and fault status. Additional features
> +respond to input UV/OV, interrupt the host when a fault has occurred, notify
> +when output power is good, detect insertion of a board, turn off the MOSFET
> +if an external supply monitor fails to indicate power good within a timeout
> +period, and auto-reboot after a programmable delay following a host commanded
> +turn-off.
> +
> +Sysfs entries
> +_____________
> +
> +The following attributes are supported. Limits are read-write and all the other
> +attributes are read-only. Note that the VADIOx channels might not be available
> +if the ADIO pins are used as GPIOs (naturally also affects the respective
> +differential channels).
> +
> +======================= ==========================================
> +in0_lcrit_alarm Critical Undervoltage alarm
> +in0_crit_alarm Critical Overvoltage alarm
> +in0_label Channel label (VIN)
> +
> +in1_input Output voltage (mV).
> +in1_min Undervoltage threshold
> +in1_max Overvoltage threshold
> +in1_lowest Lowest measured voltage
> +in1_highest Highest measured voltage
> +in1_reset_history Write 1 to reset history.
> +in1_min_alarm Undervoltage alarm
> +in1_max_alarm Overvoltage alarm
> +in1_label Channel label (VPWR)
> +
> +in2_input Output voltage (mV).
> +in2_min Undervoltage threshold
> +in2_max Overvoltage threshold
> +in2_lowest Lowest measured voltage
> +in2_highest Highest measured voltage
> +in2_reset_history Write 1 to reset history.
> +in2_min_alarm Undervoltage alarm
> +in2_max_alarm Overvoltage alarm
> +in2_enable Enable/Disable monitoring.
> +in2_label Channel label (VADI1)
> +
> +in3_input Output voltage (mV).
> +in3_min Undervoltage threshold
> +in3_max Overvoltage threshold
> +in3_lowest Lowest measured voltage
> +in3_highest Highest measured voltage
> +in3_reset_history Write 1 to reset history.
> +in3_min_alarm Undervoltage alarm
> +in3_max_alarm Overvoltage alarm
> +in3_enable Enable/Disable monitoring.
> +in3_label Channel label (VADI2)
> +
> +in4_input Output voltage (mV).
> +in4_min Undervoltage threshold
> +in4_max Overvoltage threshold
> +in4_lowest Lowest measured voltage
> +in4_highest Highest measured voltage
> +in4_reset_history Write 1 to reset history.
> +in4_min_alarm Undervoltage alarm
> +in4_max_alarm Overvoltage alarm
> +in4_enable Enable/Disable monitoring.
> +in4_label Channel label (VADI3)
> +
> +in5_input Output voltage (mV).
> +in5_min Undervoltage threshold
> +in5_max Overvoltage threshold
> +in5_lowest Lowest measured voltage
> +in5_highest Highest measured voltage
> +in5_reset_history Write 1 to reset history.
> +in5_min_alarm Undervoltage alarm
> +in5_max_alarm Overvoltage alarm
> +in5_enable Enable/Disable monitoring.
> +in5_label Channel label (VADI4)
> +
> +in6_input Output voltage (mV).
> +in6_min Undervoltage threshold
> +in6_max Overvoltage threshold
> +in6_lowest Lowest measured voltage
> +in6_highest Highest measured voltage
> +in6_reset_history Write 1 to reset history.
> +in6_min_alarm Undervoltage alarm
> +in6_max_alarm Overvoltage alarm
> +in6_enable Enable/Disable monitoring.
> +in6_label Channel label (VADIO1)
> +
> +in7_input Output voltage (mV).
> +in7_min Undervoltage threshold
> +in7_max Overvoltage threshold
> +in7_lowest Lowest measured voltage
> +in7_highest Highest measured voltage
> +in7_reset_history Write 1 to reset history.
> +in7_min_alarm Undervoltage alarm
> +in7_max_alarm Overvoltage alarm
> +in7_enable Enable/Disable monitoring.
> +in7_label Channel label (VADIO2)
> +
> +in8_input Output voltage (mV).
> +in8_min Undervoltage threshold
> +in8_max Overvoltage threshold
> +in8_lowest Lowest measured voltage
> +in8_highest Highest measured voltage
> +in8_reset_history Write 1 to reset history.
> +in8_min_alarm Undervoltage alarm
> +in8_max_alarm Overvoltage alarm
> +in8_enable Enable/Disable monitoring.
> +in8_label Channel label (VADIO3)
> +
> +in9_input Output voltage (mV).
> +in9_min Undervoltage threshold
> +in9_max Overvoltage threshold
> +in9_lowest Lowest measured voltage
> +in9_highest Highest measured voltage
> +in9_reset_history Write 1 to reset history.
> +in9_min_alarm Undervoltage alarm
> +in9_max_alarm Overvoltage alarm
> +in9_enable Enable/Disable monitoring.
> +in9_label Channel label (VADIO4)
> +
> +in10_input Output voltage (mV).
> +in10_min Undervoltage threshold
> +in10_max Overvoltage threshold
> +in10_lowest Lowest measured voltage
> +in10_highest Highest measured voltage
> +in10_reset_history Write 1 to reset history.
> +in10_min_alarm Undervoltage alarm
> +in10_max_alarm Overvoltage alarm
> +in10_enable Enable/Disable monitoring.
> +in10_label Channel label (DRNS)
> +
> +in11_input Output voltage (mV).
> +in11_min Undervoltage threshold
> +in11_max Overvoltage threshold
> +in11_lowest Lowest measured voltage
> +in11_highest Highest measured voltage
> +in11_reset_history Write 1 to reset history.
> + Also clears fet bad and short fault logs.
> +in11_min_alarm Undervoltage alarm
> +in11_max_alarm Overvoltage alarm
> +in11_enable Enable/Disable monitoring
> +in11_fault Failure in the MOSFET. Either bad or shorted FET.
> +in11_label Channel label (DRAIN)
> +
> +in12_input Output voltage (mV).
> +in12_min Undervoltage threshold
> +in12_max Overvoltage threshold
> +in12_lowest Lowest measured voltage
> +in12_highest Highest measured voltage
> +in12_reset_history Write 1 to reset history.
> +in12_min_alarm Undervoltage alarm
> +in12_max_alarm Overvoltage alarm
> +in12_enable Enable/Disable monitoring.
> +in12_label Channel label (ADIN2-ADIN1)
> +
> +in13_input Output voltage (mV).
> +in13_min Undervoltage threshold
> +in13_max Overvoltage threshold
> +in13_lowest Lowest measured voltage
> +in13_highest Highest measured voltage
> +in13_reset_history Write 1 to reset history.
> +in13_min_alarm Undervoltage alarm
> +in13_max_alarm Overvoltage alarm
> +in13_enable Enable/Disable monitoring.
> +in13_label Channel label (ADIN4-ADIN3)
> +
> +in14_input Output voltage (mV).
> +in14_min Undervoltage threshold
> +in14_max Overvoltage threshold
> +in14_lowest Lowest measured voltage
> +in14_highest Highest measured voltage
> +in14_reset_history Write 1 to reset history.
> +in14_min_alarm Undervoltage alarm
> +in14_max_alarm Overvoltage alarm
> +in14_enable Enable/Disable monitoring.
> +in14_label Channel label (ADIO2-ADIO1)
> +
> +in15_input Output voltage (mV).
> +in15_min Undervoltage threshold
> +in15_max Overvoltage threshold
> +in15_lowest Lowest measured voltage
> +in15_highest Highest measured voltage
> +in15_reset_history Write 1 to reset history.
> +in15_min_alarm Undervoltage alarm
> +in15_max_alarm Overvoltage alarm
> +in15_enable Enable/Disable monitoring.
> +in15_label Channel label (ADIO4-ADIO3)
> +
> +curr1_input Sense current (mA)
> +curr1_min Undercurrent threshold
> +curr1_max Overcurrent threshold
> +curr1_lowest Lowest measured current
> +curr1_highest Highest measured current
> +curr1_reset_history Write 1 to reset curr1 history.
> + Also clears overcurrent fault logs.
> +curr1_min_alarm Undercurrent alarm
> +curr1_max_alarm Overcurrent alarm
> +curr1_crit_alarm Critical Overcurrent alarm
> +curr1_label Channel label (ISENSE)
> +
> +power1_input Power (in uW)
> +power1_min Low power threshold
> +power1_max High power threshold
> +power1_input_lowest Historical minimum power use
> +power1_input_highest Historical maximum power use
> +power1_reset_history Write 1 to reset power1 history.
> + Also clears power fault logs.
> +power1_min_alarm Low power alarm
> +power1_max_alarm High power alarm
> +power1_label Channel label (Power)
> +
> +energy1_input Measured energy over time (in microJoule)
> +energy1_enable Enable/Disable Energy accumulation
> +======================= ==========================================
> +
> +DebugFs entries
> +_______________
> +
> +The chip also has a fault log register where failures can be logged. Hence,
> +as these are logging events, we give access to them in debugfs. Note that
> +even if some failure is detected in these logs, it does necessarily mean
> +that the failure is still present. As mentioned in the proper Sysfs entries,
> +these logs can be cleared by writing in the proper reset_history attribute.
> +
> +.. warning:: The debugfs interface is subject to change without notice
> + and is only available when the kernel is compiled with
> + ``CONFIG_DEBUG_FS`` defined.
> +
> +``/sys/kernel/debug/i2c/i2c-[X]/[X]-addr/``
> +contains the following attributes:
> +
> +======================= ==========================================
> +power1_failed_fault_log Set to 1 by a power1 fault occurring.
> +power1_good_input_fault_log Set to 1 by a power1 good input fault occurring at PGIO3.
> +in11_fet_short_fault_log Set to 1 when a FET-short fault occurs.
> +in11_fet_bad_fault_log Set to 1 when a FET-BAD fault occurs.
> +in0_lcrit_fault_log Set to 1 by a VIN undervoltage fault occurring.
> +in0_crit_fault_log Set to 1 by a VIN overvoltage fault occurring.
> +curr1_crit_fault_log Set to 1 by an overcurrent fault occurring.
> +======================= ==========================================
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 3f727d7fdfa4..a63833b6fe8b 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -15166,6 +15166,7 @@ M: Nuno Sá <nuno.sa@analog.com>
> L: linux-hwmon@vger.kernel.org
> S: Supported
> F: Documentation/devicetree/bindings/hwmon/adi,ltc4283.yaml
> +F: drivers/hwmon/ltc4283.c
>
> LTC4286 HARDWARE MONITOR DRIVER
> M: Delphine CC Chiu <Delphine_CC_Chiu@Wiwynn.com>
> diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
> index fb847ab40ab4..4d9f500ae6ee 100644
> --- a/drivers/hwmon/Kconfig
> +++ b/drivers/hwmon/Kconfig
> @@ -1157,6 +1157,18 @@ config SENSORS_LTC4282
> This driver can also be built as a module. If so, the module will
> be called ltc4282.
>
> +config SENSORS_LTC4283
> + tristate "Analog Devices LTC4283"
> + depends on I2C
> + select REGMAP_I2C
> + select AUXILIARY_BUS
> + help
> + If you say yes here you get support for Analog Devices LTC4283
> + Negative Voltage Hot Swap Controller I2C interface.
> +
> + This driver can also be built as a module. If so, the module will
> + be called ltc4283.
> +
> config SENSORS_LTQ_CPUTEMP
> bool "Lantiq cpu temperature sensor driver"
> depends on SOC_XWAY
> diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
> index 0fce31b43eb1..b9d7b0287b9c 100644
> --- a/drivers/hwmon/Makefile
> +++ b/drivers/hwmon/Makefile
> @@ -147,6 +147,7 @@ obj-$(CONFIG_SENSORS_LTC4245) += ltc4245.o
> obj-$(CONFIG_SENSORS_LTC4260) += ltc4260.o
> obj-$(CONFIG_SENSORS_LTC4261) += ltc4261.o
> obj-$(CONFIG_SENSORS_LTC4282) += ltc4282.o
> +obj-$(CONFIG_SENSORS_LTC4283) += ltc4283.o
> obj-$(CONFIG_SENSORS_LTQ_CPUTEMP) += ltq-cputemp.o
> obj-$(CONFIG_SENSORS_MACSMC_HWMON) += macsmc-hwmon.o
> obj-$(CONFIG_SENSORS_MAX1111) += max1111.o
> diff --git a/drivers/hwmon/ltc4283.c b/drivers/hwmon/ltc4283.c
> new file mode 100644
> index 000000000000..9b492a8b2227
> --- /dev/null
> +++ b/drivers/hwmon/ltc4283.c
> @@ -0,0 +1,1795 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Analog Devices LTC4283 I2C Negative Voltage Hot Swap Controller (HWMON)
> + *
> + * Copyright 2025 Analog Devices Inc.
> + */
> +#include <linux/auxiliary_bus.h>
> +#include <linux/bitfield.h>
> +#include <linux/bitmap.h>
> +#include <linux/bitops.h>
> +#include <linux/bits.h>
> +
> +#include <linux/debugfs.h>
> +#include <linux/device.h>
> +#include <linux/device/devres.h>
> +#include <linux/hwmon.h>
> +#include <linux/i2c.h>
> +#include <linux/math.h>
> +#include <linux/math64.h>
> +#include <linux/minmax.h>
> +#include <linux/module.h>
> +
> +#include <linux/mod_devicetable.h>
> +#include <linux/property.h>
> +#include <linux/regmap.h>
> +#include <linux/unaligned.h>
> +#include <linux/units.h>
> +
> +#define LTC4283_SYSTEM_STATUS 0x00
> +#define LTC4283_FAULT_STATUS 0x03
> +#define LTC4283_OV_MASK BIT(0)
> +#define LTC4283_UV_MASK BIT(1)
> +#define LTC4283_OC_MASK BIT(2)
> +#define LTC4283_FET_BAD_MASK BIT(3)
> +#define LTC4283_FET_SHORT_MASK BIT(6)
> +#define LTC4283_FAULT_LOG 0x04
> +#define LTC4283_OV_FAULT_MASK BIT(0)
> +#define LTC4283_UV_FAULT_MASK BIT(1)
> +#define LTC4283_OC_FAULT_MASK BIT(2)
> +#define LTC4283_FET_BAD_FAULT_MASK BIT(3)
> +#define LTC4283_PGI_FAULT_MASK BIT(4)
> +#define LTC4283_PWR_FAIL_FAULT_MASK BIT(5)
> +#define LTC4283_FET_SHORT_FAULT_MASK BIT(6)
> +#define LTC4283_ADC_ALM_LOG_1 0x05
> +#define LTC4283_POWER_LOW_ALM BIT(0)
> +#define LTC4283_POWER_HIGH_ALM BIT(1)
> +#define LTC4283_SENSE_LOW_ALM BIT(4)
> +#define LTC4283_SENSE_HIGH_ALM BIT(5)
> +#define LTC4283_ADC_ALM_LOG_2 0x06
> +#define LTC4283_ADC_ALM_LOG_3 0x07
> +#define LTC4283_ADC_ALM_LOG_4 0x08
> +#define LTC4283_ADC_ALM_LOG_5 0x09
> +#define LTC4283_CONTROL_1 0x0a
> +#define LTC4283_RW_PAGE_MASK BIT(0)
> +#define LTC4283_PIGIO2_ACLB_MASK BIT(2)
> +#define LTC4283_PWRGD_RST_CTRL_MASK BIT(3)
> +#define LTC4283_FET_BAD_OFF_MASK BIT(4)
> +#define LTC4283_THERM_TMR_MASK BIT(5)
> +#define LTC4283_DVDT_MASK BIT(6)
> +#define LTC4283_CONTROL_2 0x0b
> +#define LTC4283_OV_RETRY_MASK BIT(0)
> +#define LTC4283_UV_RETRY_MASK BIT(1)
> +#define LTC4283_OC_RETRY_MASK GENMASK(3, 2)
> +#define LTC4283_FET_BAD_RETRY_MASK GENMASK(5, 4)
> +#define LTC4283_EXT_FAULT_RETRY_MASK BIT(7)
> +#define LTC4283_RESERVED_OC 0x0c
> +#define LTC4283_CONFIG_1 0x0d
> +#define LTC4283_FB_MASK GENMASK(3, 2)
> +#define LTC4283_ILIM_MASK GENMASK(7, 4)
> +#define LTC4283_CONFIG_2 0x0e
> +#define LTC4283_COOLING_DL_MASK GENMASK(3, 1)
> +#define LTC4283_FTBD_DL_MASK GENMASK(5, 4)
> +#define LTC4283_CONFIG_3 0x0f
> +#define LTC4283_VPWR_DRNS_MASK BIT(6)
> +#define LTC4283_EXTFLT_TURN_OFF_MASK BIT(7)
> +#define LTC4283_PGIO_CONFIG 0x10
> +#define LTC4283_PGIO1_CFG_MASK GENMASK(1, 0)
> +#define LTC4283_PGIO2_CFG_MASK GENMASK(3, 2)
> +#define LTC4283_PGIO3_CFG_MASK GENMASK(5, 4)
> +#define LTC4283_PGIO4_CFG_MASK GENMASK(7, 6)
> +#define LTC4283_PGIO_CONFIG_2 0x11
> +#define LTC4283_ADC_MASK GENMASK(2, 0)
> +#define LTC4283_ADC_SELECT(c) (0x13 + (c) / 8)
> +#define LTC4283_ADC_SELECT_MASK(c) BIT((c) % 8)
> +#define LTC4283_SENSE_MIN_TH 0x1b
> +#define LTC4283_SENSE_MAX_TH 0x1c
> +#define LTC4283_VPWR_MIN_TH 0x1d
> +#define LTC4283_VPWR_MAX_TH 0x1e
> +#define LTC4283_POWER_MIN_TH 0x1f
> +#define LTC4283_POWER_MAX_TH 0x20
> +#define LTC4283_ADC_2_MIN_TH(c) (0x21 + (c) * 2)
> +#define LTC4283_ADC_2_MAX_TH(c) (0x22 + (c) * 2)
> +#define LTC4283_ADC_2_MIN_TH_DIFF(c) (0x39 + (c) * 2)
> +#define LTC4283_ADC_2_MAX_TH_DIFF(c) (0x3a + (c) * 2)
> +#define LTC4283_SENSE 0x41
> +#define LTC4283_SENSE_MIN 0x42
> +#define LTC4283_SENSE_MAX 0x43
> +#define LTC4283_VPWR 0x44
> +#define LTC4283_VPWR_MIN 0x45
> +#define LTC4283_VPWR_MAX 0x46
> +#define LTC4283_POWER 0x47
> +#define LTC4283_POWER_MIN 0x48
> +#define LTC4283_POWER_MAX 0x49
> +#define LTC4283_RESERVED_68 0x68
> +#define LTC4283_RESERVED_6D 0x6D
> +/* get channels from ADC 2 */
> +#define LTC4283_ADC_2(c) (0x4a + (c) * 3)
> +#define LTC4283_ADC_2_MIN(c) (0x4b + (c) * 3)
> +#define LTC4283_ADC_2_MAX(c) (0x4c + (c) * 3)
> +#define LTC4283_ADC_2_DIFF(c) (0x6e + (c) * 3)
> +#define LTC4283_ADC_2_MIN_DIFF(c) (0x6f + (c) * 3)
> +#define LTC4283_ADC_2_MAX_DIFF(c) (0x70 + (c) * 3)
> +#define LTC4283_ENERGY 0x7a
> +#define LTC4283_METER_CONTROL 0x84
> +#define LTC4283_INTEGRATE_I_MASK BIT(0)
> +#define LTC4283_METER_HALT_MASK BIT(6)
> +#define LTC4283_RESERVED_86 0x86
> +#define LTC4283_RESERVED_8F 0x8F
> +#define LTC4283_FAULT_LOG_CTRL 0x90
> +#define LTC4283_FAULT_LOG_EN_MASK BIT(7)
> +#define LTC4283_RESERVED_91 0x91
> +#define LTC4283_RESERVED_A1 0xA1
> +#define LTC4283_RESERVED_A3 0xA3
> +#define LTC4283_RESERVED_AC 0xAC
> +#define LTC4283_POWER_PLAY_MSB 0xE7
> +#define LTC4283_POWER_PLAY_LSB 0xE8
> +#define LTC4283_RESERVED_F1 0xF1
> +#define LTC4283_RESERVED_FF 0xFF
> +
> +/* also applies for differential channels */
> +#define LTC4283_ADC1_FS_uV 32768
> +#define LTC4283_ADC2_FS_mV 2048
> +#define LTC4283_TCONV_uS 64103
> +#define LTC4283_VILIM_MIN_uV 15000
> +#define LTC4283_VILIM_MAX_uV 30000
> +#define LTC4283_VILIM_RANGE \
> + (LTC4283_VILIM_MAX_uV - LTC4283_VILIM_MIN_uV + 1)
> +
> +#define LTC4283_PGIO_FUNC_GPIO 2
> +#define LTC4283_PGIO2_FUNC_ACLB 3
> +
> +/*
> + * Maximum value for rsense in nano ohms. The reasoning for this value is that
> + * it's the max value for which multiplying by 256 does not overflow long on
> + * 32bits. For the minimum value, is a sane minimum rsense for which power_max
> + * does not overflow 32bits.
> + */
> +#define LTC4283_MAX_RSENSE 1677721599
> +#define LTC4283_MIN_RSENSE 50000
> +
> +/* voltage channels */
> +enum {
> + LTC4283_CHAN_VIN,
> + LTC4283_CHAN_VPWR,
> + LTC4283_CHAN_ADI_1,
> + LTC4283_CHAN_ADI_2,
> + LTC4283_CHAN_ADI_3,
> + LTC4283_CHAN_ADI_4,
> + LTC4283_CHAN_ADIO_1,
> + LTC4283_CHAN_ADIO_2,
> + LTC4283_CHAN_ADIO_3,
> + LTC4283_CHAN_ADIO_4,
> + LTC4283_CHAN_DRNS,
> + LTC4283_CHAN_DRAIN,
> + /* differential channels */
> + LTC4283_CHAN_ADIN12,
> + LTC4283_CHAN_ADIN34,
> + LTC4283_CHAN_ADIO12,
> + LTC4283_CHAN_ADIO34,
> + LTC4283_CHAN_MAX
> +};
> +
> +/* Just for ease of use on the regmap */
> +#define LTC4283_ADIO34_MAX \
> + LTC4283_ADC_2_MAX_DIFF(LTC4283_CHAN_ADIO34 - LTC4283_CHAN_ADIN12)
> +
> +struct ltc4283_hwmon {
> + struct regmap *map;
> + struct i2c_client *client;
> + unsigned long gpio_mask;
> + unsigned long ch_enable_mask;
> + /* in microwatt */
> + unsigned long power_max;
> + /* in millivolt */
> + u32 vsense_max;
> + /* in tenths of microohm*/
> + u32 rsense;
> + bool energy_en;
> + bool ext_fault;
> +};
> +
> +static int ltc4283_read_voltage_word(const struct ltc4283_hwmon *st,
> + u32 reg, u32 fs, long *val)
> +{
> + unsigned int __raw;
> + int ret;
> +
> + ret = regmap_read(st->map, reg, &__raw);
> + if (ret)
> + return ret;
> +
> + *val = DIV_ROUND_CLOSEST(__raw * fs, BIT(16));
> + return 0;
> +}
> +
> +static int ltc4283_read_voltage_byte(const struct ltc4283_hwmon *st,
> + u32 reg, u32 fs, long *val)
> +{
> + int ret;
> + u32 in;
> +
> + ret = regmap_read(st->map, reg, &in);
> + if (ret)
> + return ret;
> +
> + *val = DIV_ROUND_CLOSEST(in * fs, BIT(8));
> + return 0;
> +}
> +
> +static u32 ltc4283_in_reg(u32 attr, u32 channel)
> +{
> + switch (attr) {
> + case hwmon_in_input:
> + if (channel == LTC4283_CHAN_VPWR)
> + return LTC4283_VPWR;
> + if (channel >= LTC4283_CHAN_ADI_1 && channel <= LTC4283_CHAN_DRAIN)
> + return LTC4283_ADC_2(channel - LTC4283_CHAN_ADI_1);
> + return LTC4283_ADC_2_DIFF(channel - LTC4283_CHAN_ADIN12);
> + case hwmon_in_highest:
> + if (channel == LTC4283_CHAN_VPWR)
> + return LTC4283_VPWR_MAX;
> + if (channel >= LTC4283_CHAN_ADI_1 && channel <= LTC4283_CHAN_DRAIN)
> + return LTC4283_ADC_2_MAX(channel - LTC4283_CHAN_ADI_1);
> + return LTC4283_ADC_2_MAX_DIFF(channel - LTC4283_CHAN_ADIN12);
> + case hwmon_in_lowest:
> + if (channel == LTC4283_CHAN_VPWR)
> + return LTC4283_VPWR_MIN;
> + if (channel >= LTC4283_CHAN_ADI_1 && channel <= LTC4283_CHAN_DRAIN)
> + return LTC4283_ADC_2_MIN(channel - LTC4283_CHAN_ADI_1);
> + return LTC4283_ADC_2_MIN_DIFF(channel - LTC4283_CHAN_ADIN12);
> + case hwmon_in_max:
> + if (channel == LTC4283_CHAN_VPWR)
> + return LTC4283_VPWR_MAX_TH;
> + if (channel >= LTC4283_CHAN_ADI_1 && channel <= LTC4283_CHAN_DRAIN)
> + return LTC4283_ADC_2_MAX_TH(channel - LTC4283_CHAN_ADI_1);
> + return LTC4283_ADC_2_MAX_TH_DIFF(channel - LTC4283_CHAN_ADIN12);
> + default:
> + if (channel == LTC4283_CHAN_VPWR)
> + return LTC4283_VPWR_MIN_TH;
> + if (channel >= LTC4283_CHAN_ADI_1 && channel <= LTC4283_CHAN_DRAIN)
> + return LTC4283_ADC_2_MIN_TH(channel - LTC4283_CHAN_ADI_1);
> + return LTC4283_ADC_2_MIN_TH_DIFF(channel - LTC4283_CHAN_ADIN12);
> + }
> +}
> +
> +static int ltc4283_read_in_vals(const struct ltc4283_hwmon *st,
> + u32 attr, u32 channel, long *val)
> +{
> + u32 reg = ltc4283_in_reg(attr, channel);
> + int ret;
> +
> + if (channel < LTC4283_CHAN_ADIN12) {
> + if (attr != hwmon_in_max && attr != hwmon_in_min)
> + return ltc4283_read_voltage_word(st, reg,
> + LTC4283_ADC2_FS_mV,
> + val);
> +
> + return ltc4283_read_voltage_byte(st, reg,
> + LTC4283_ADC2_FS_mV, val);
> + }
> +
> + if (attr != hwmon_in_max && attr != hwmon_in_min)
> + ret = ltc4283_read_voltage_word(st, reg,
> + LTC4283_ADC1_FS_uV, val);
> + else
> + ret = ltc4283_read_voltage_byte(st, reg,
> + LTC4283_ADC1_FS_uV, val);
> + if (ret)
> + return ret;
> +
> + *val = DIV_ROUND_CLOSEST(*val, MILLI);
> + return 0;
> +}
> +
> +static int ltc4283_read_alarm(struct ltc4283_hwmon *st, u32 reg,
> + u32 mask, long *val)
> +{
> + u32 alarm;
> + int ret;
> +
> + ret = regmap_read(st->map, reg, &alarm);
> + if (ret)
> + return ret;
> +
> + *val = !!(alarm & mask);
> +
> + /* If not status/fault logs, clear the alarm after reading it. */
> + if (reg != LTC4283_FAULT_STATUS && reg != LTC4283_FAULT_LOG)
> + return regmap_write(st->map, reg, alarm & ~mask);
> +
> + return 0;
> +}
> +
> +static int ltc4283_read_in_alarm(struct ltc4283_hwmon *st, u32 channel,
> + bool max_alm, long *val)
> +{
> + if (channel == LTC4283_VPWR)
> + return ltc4283_read_alarm(st, LTC4283_ADC_ALM_LOG_1,
> + BIT(2 + max_alm), val);
> +
> + if (channel >= LTC4283_CHAN_ADI_1 && channel <= LTC4283_CHAN_ADI_4) {
> + u32 bit = (channel - LTC4283_CHAN_ADI_1) * 2;
> + /*
> + * Lower channels go to higher bits. We also want to go +1 down
> + * in the min_alarm case.
> + */
> + return ltc4283_read_alarm(st, LTC4283_ADC_ALM_LOG_2,
> + BIT(7 - bit - !max_alm), val);
> + }
> +
> + if (channel >= LTC4283_CHAN_ADIO_1 && channel <= LTC4283_CHAN_ADIO_4) {
> + u32 bit = (channel - LTC4283_CHAN_ADIO_1) * 2;
> +
> + return ltc4283_read_alarm(st, LTC4283_ADC_ALM_LOG_3,
> + BIT(7 - bit - !max_alm), val);
> + }
> +
> + if (channel >= LTC4283_CHAN_ADIN12 && channel <= LTC4283_CHAN_ADIO34) {
> + u32 bit = (channel - LTC4283_CHAN_ADIN12) * 2;
> +
> + return ltc4283_read_alarm(st, LTC4283_ADC_ALM_LOG_5,
> + BIT(7 - bit - !max_alm), val);
> + }
> +
> + if (channel == LTC4283_CHAN_DRNS)
> + return ltc4283_read_alarm(st, LTC4283_ADC_ALM_LOG_4,
> + BIT(6 + max_alm), val);
> +
> + return ltc4283_read_alarm(st, LTC4283_ADC_ALM_LOG_4, BIT(4 + max_alm),
> + val);
> +}
> +
> +static int ltc4283_read_in(struct ltc4283_hwmon *st, u32 attr, u32 channel,
> + long *val)
> +{
> + switch (attr) {
> + case hwmon_in_input:
> + if (!test_bit(channel, &st->ch_enable_mask))
> + return -ENODATA;
> +
> + return ltc4283_read_in_vals(st, attr, channel, val);
> + case hwmon_in_highest:
> + case hwmon_in_lowest:
> + case hwmon_in_max:
> + case hwmon_in_min:
> + return ltc4283_read_in_vals(st, attr, channel, val);
> + case hwmon_in_max_alarm:
> + return ltc4283_read_in_alarm(st, channel, true, val);
> + case hwmon_in_min_alarm:
> + return ltc4283_read_in_alarm(st, channel, false, val);
> + case hwmon_in_crit_alarm:
> + return ltc4283_read_alarm(st, LTC4283_FAULT_STATUS,
> + LTC4283_OV_MASK, val);
> + case hwmon_in_lcrit_alarm:
> + return ltc4283_read_alarm(st, LTC4283_FAULT_STATUS,
> + LTC4283_UV_MASK, val);
> + case hwmon_in_fault:
> + /*
> + * We report failure if we detect either a fer_bad or a
> + * fet_short in the status register.
> + */
> + return ltc4283_read_alarm(st, LTC4283_FAULT_STATUS,
> + LTC4283_FET_BAD_MASK | LTC4283_FET_SHORT_MASK, val);
> + case hwmon_in_enable:
> + *val = test_bit(channel, &st->ch_enable_mask);
> + return 0;
> + default:
> + return -EOPNOTSUPP;
> + }
> + return 0;
> +}
> +
> +static int ltc4283_read_current_word(const struct ltc4283_hwmon *st, u32 reg,
> + long *val)
> +{
> + u64 temp = (u64)LTC4283_ADC1_FS_uV * DECA * MILLI;
> + unsigned int __raw;
> + int ret;
> +
> + ret = regmap_read(st->map, reg, &__raw);
> + if (ret)
> + return ret;
> +
> + *val = DIV64_U64_ROUND_CLOSEST(__raw * temp,
> + BIT_ULL(16) * st->rsense);
> +
> + return 0;
> +}
> +
> +static int ltc4283_read_current_byte(const struct ltc4283_hwmon *st, u32 reg,
> + long *val)
> +{
> + u64 temp = (u64)LTC4283_ADC1_FS_uV * DECA * MILLI;
> + u32 curr;
> + int ret;
> +
> + ret = regmap_read(st->map, reg, &curr);
> + if (ret)
> + return ret;
> +
> + *val = DIV_ROUND_CLOSEST_ULL(curr * temp, BIT(8) * st->rsense);
> + return 0;
> +}
> +
> +static int ltc4283_read_curr(struct ltc4283_hwmon *st, u32 attr, long *val)
> +{
> + switch (attr) {
> + case hwmon_curr_input:
> + return ltc4283_read_current_word(st, LTC4283_SENSE, val);
> + case hwmon_curr_highest:
> + return ltc4283_read_current_word(st, LTC4283_SENSE_MAX, val);
> + case hwmon_curr_lowest:
> + return ltc4283_read_current_word(st, LTC4283_SENSE_MIN, val);
> + case hwmon_curr_max:
> + return ltc4283_read_current_byte(st, LTC4283_SENSE_MAX_TH, val);
> + case hwmon_curr_min:
> + return ltc4283_read_current_byte(st, LTC4283_SENSE_MIN_TH, val);
> + case hwmon_curr_max_alarm:
> + return ltc4283_read_alarm(st, LTC4283_ADC_ALM_LOG_1,
> + LTC4283_SENSE_HIGH_ALM, val);
> + case hwmon_curr_min_alarm:
> + return ltc4283_read_alarm(st, LTC4283_ADC_ALM_LOG_1,
> + LTC4283_SENSE_LOW_ALM, val);
> + case hwmon_curr_crit_alarm:
> + return ltc4283_read_alarm(st, LTC4283_FAULT_STATUS,
> + LTC4283_OC_MASK, val);
> + default:
> + return -EOPNOTSUPP;
> + }
> +}
> +
> +static int ltc4283_read_power_word(const struct ltc4283_hwmon *st,
> + u32 reg, long *val)
> +{
> + u64 temp = (u64)LTC4283_ADC1_FS_uV * LTC4283_ADC2_FS_mV * DECA * MILLI;
> + unsigned int __raw;
> + int ret;
> +
> + ret = regmap_read(st->map, reg, &__raw);
> + if (ret)
> + return ret;
> +
> + /*
> + * Power is given by:
> + * P = CODE(16b) * 32.768mV * 2.048V / (2^16 * Rsense)
> + */
> + *val = DIV64_U64_ROUND_CLOSEST(temp * __raw, BIT_ULL(16) * st->rsense);
> +
> + return 0;
> +}
> +
> +static int ltc4283_read_power_byte(const struct ltc4283_hwmon *st,
> + u32 reg, long *val)
> +{
> + u64 temp = (u64)LTC4283_ADC1_FS_uV * LTC4283_ADC2_FS_mV * DECA * MILLI;
> + u32 power;
> + int ret;
> +
> + ret = regmap_read(st->map, reg, &power);
> + if (ret)
> + return ret;
> +
> + *val = DIV_ROUND_CLOSEST_ULL(power * temp, BIT(8) * st->rsense);
> +
> + return 0;
> +}
> +
> +static int ltc4283_read_power(struct ltc4283_hwmon *st, u32 attr, long *val)
> +{
> + switch (attr) {
> + case hwmon_power_input:
> + return ltc4283_read_power_word(st, LTC4283_POWER, val);
> + case hwmon_power_input_highest:
> + return ltc4283_read_power_word(st, LTC4283_POWER_MAX, val);
> + case hwmon_power_input_lowest:
> + return ltc4283_read_power_word(st, LTC4283_POWER_MIN, val);
> + case hwmon_power_max_alarm:
> + return ltc4283_read_alarm(st, LTC4283_ADC_ALM_LOG_1,
> + LTC4283_POWER_HIGH_ALM, val);
> + case hwmon_power_min_alarm:
> + return ltc4283_read_alarm(st, LTC4283_ADC_ALM_LOG_1,
> + LTC4283_POWER_LOW_ALM, val);
> + case hwmon_power_max:
> + return ltc4283_read_power_byte(st, LTC4283_POWER_MAX_TH, val);
> + case hwmon_power_min:
> + return ltc4283_read_power_byte(st, LTC4283_POWER_MIN_TH, val);
> + default:
> + return -EOPNOTSUPP;
> + }
> +}
> +
> +static int ltc4283_read_energy(struct ltc4283_hwmon *st, u32 attr, s64 *val)
> +{
> + u64 temp = LTC4283_ADC1_FS_uV * LTC4283_ADC2_FS_mV, energy;
> + u8 raw[8] = {};
> + int ret;
> +
> + if (!st->energy_en)
> + return -ENODATA;
> +
> + ret = i2c_smbus_read_i2c_block_data(st->client, LTC4283_ENERGY, 6, raw);
> + if (ret < 0)
> + return ret;
> + if (ret != 6)
> + return -EIO;
> +
> + energy = get_unaligned_be64(raw) >> 16;
> +
> + /*
> + * The formula for energy is given by:
> + * E = CODE(48b) * 32.768mV * 2.048V * Tconv / 2^24 * Rsense
> + *
> + * As Rsense can have tenths of micro-ohm resolution, we need to
> + * multiply by DECA to get microjoule.
> + */
> +
> + /*
> + * Use mul_u64_u64_div_u64() to handle the 128-bit intermediate
> + * product of energy (up to 48 bits) * temp * Tconv without overflow.
> + * Multiply rsense by CENTI to convert from tenths-of-microohm back
> + * to nanoohm so the result comes out in microjoule.
> + */
> + energy = mul_u64_u64_div_u64(energy, temp * LTC4283_TCONV_uS,
> + BIT_ULL(24) * st->rsense * CENTI);
> +
> + *val = energy;
> + return 0;
> +}
> +
> +static int ltc4283_read(struct device *dev, enum hwmon_sensor_types type,
> + u32 attr, int channel, long *val)
> +{
> + struct ltc4283_hwmon *st = dev_get_drvdata(dev);
> +
> + switch (type) {
> + case hwmon_in:
> + return ltc4283_read_in(st, attr, channel, val);
> + case hwmon_curr:
> + return ltc4283_read_curr(st, attr, val);
> + case hwmon_power:
> + return ltc4283_read_power(st, attr, val);
> + case hwmon_energy:
> + *val = st->energy_en;
> + return 0;
> + case hwmon_energy64:
> + return ltc4283_read_energy(st, attr, (s64 *)val);
> + default:
> + return -EOPNOTSUPP;
> + }
> +}
> +
> +static int ltc4283_write_power_byte(const struct ltc4283_hwmon *st, u32 reg,
> + long val)
> +{
> + u64 temp = (u64)LTC4283_ADC1_FS_uV * LTC4283_ADC2_FS_mV * DECA * MILLI;
> + u32 __raw;
> +
> + val = clamp_val(val, 0, st->power_max);
> + __raw = DIV64_U64_ROUND_CLOSEST(val * BIT_ULL(8) * st->rsense, temp);
> +
> + return regmap_write(st->map, reg, __raw);
> +}
> +
> +static int ltc4283_write_power_word(const struct ltc4283_hwmon *st,
> + u32 reg, unsigned long val)
> +{
> + u64 divisor = (u64)LTC4283_ADC1_FS_uV * LTC4283_ADC2_FS_mV * DECA * MILLI;
> + u16 __raw;
> +
> + __raw = mul_u64_u64_div_u64(val, st->rsense * BIT_ULL(16), divisor);
> +
> + return regmap_write(st->map, reg, __raw);
> +}
> +
> +static int ltc4283_reset_power_hist(struct ltc4283_hwmon *st)
> +{
> + int ret;
> +
> + ret = ltc4283_write_power_word(st, LTC4283_POWER_MIN, st->power_max);
> + if (ret)
> + return ret;
> +
> + ret = ltc4283_write_power_word(st, LTC4283_POWER_MAX, 0);
> + if (ret)
> + return ret;
> +
> + /* Clear possible power faults. */
> + return regmap_clear_bits(st->map, LTC4283_FAULT_LOG,
> + LTC4283_PWR_FAIL_FAULT_MASK | LTC4283_PGI_FAULT_MASK);
> +}
> +
> +static int ltc4283_write_power(struct ltc4283_hwmon *st, u32 attr, long val)
> +{
> + switch (attr) {
> + case hwmon_power_max:
> + return ltc4283_write_power_byte(st, LTC4283_POWER_MAX_TH, val);
> + case hwmon_power_min:
> + return ltc4283_write_power_byte(st, LTC4283_POWER_MIN_TH, val);
> + case hwmon_power_reset_history:
> + return ltc4283_reset_power_hist(st);
> + default:
> + return -EOPNOTSUPP;
> + }
> +}
> +
> +static int ltc4283_write_in_history(struct ltc4283_hwmon *st, u32 reg,
> + long lowest, u32 fs)
> +{
> + u32 __raw;
> + int ret;
> +
> + __raw = DIV_ROUND_CLOSEST(BIT(16) * lowest, fs);
> + if (__raw == BIT(16))
> + __raw = U16_MAX;
> +
> + ret = regmap_write(st->map, reg, __raw);
> + if (ret)
> + return ret;
> +
> + return regmap_write(st->map, reg + 1, 0);
> +}
> +
> +static int ltc4283_write_in_byte(const struct ltc4283_hwmon *st,
> + u32 reg, u32 fs, long val)
> +{
> + u32 __raw;
> +
> + val = clamp_val(val, 0, fs);
> + __raw = DIV_ROUND_CLOSEST(val * BIT(8), fs);
> + if (__raw == BIT(8))
> + __raw = U8_MAX;
> +
> + return regmap_write(st->map, reg, __raw);
> +}
> +
> +static int ltc4283_reset_in_hist(struct ltc4283_hwmon *st, u32 channel)
> +{
> + u32 reg, fs;
> + int ret;
> +
> + /*
> + * Make sure to clear possible under/over voltage faults. Otherwise the
> + * chip won't latch on again.
> + */
> + if (channel == LTC4283_CHAN_VIN)
> + return regmap_clear_bits(st->map, LTC4283_FAULT_LOG,
> + LTC4283_OV_FAULT_MASK | LTC4283_UV_FAULT_MASK);
> +
> + if (channel == LTC4283_CHAN_VPWR)
> + return ltc4283_write_in_history(st, LTC4283_VPWR_MIN,
> + LTC4283_ADC2_FS_mV,
> + LTC4283_ADC2_FS_mV);
> +
> + if (channel >= LTC4283_CHAN_ADI_1 && channel <= LTC4283_CHAN_DRAIN) {
> + fs = LTC4283_ADC2_FS_mV;
> + reg = LTC4283_ADC_2_MIN(channel - LTC4283_CHAN_ADI_1);
> + } else {
> + fs = LTC4283_ADC1_FS_uV;
> + reg = LTC4283_ADC_2_MIN_DIFF(channel - LTC4283_CHAN_ADIN12);
> + }
> +
> + ret = ltc4283_write_in_history(st, reg, fs, fs);
> + if (ret)
> + return ret;
> + if (channel != LTC4283_CHAN_DRAIN)
> + return 0;
> +
> + /* Then, let's also clear possible fet faults. Same as above. */
> + return regmap_clear_bits(st->map, LTC4283_FAULT_LOG,
> + LTC4283_FET_BAD_FAULT_MASK | LTC4283_FET_SHORT_FAULT_MASK);
> +}
> +
> +static int ltc4283_write_in_en(struct ltc4283_hwmon *st, u32 channel, bool en)
> +{
> + unsigned int bit, adc_idx = channel - LTC4283_CHAN_ADI_1;
> + unsigned int reg = LTC4283_ADC_SELECT(adc_idx);
> + int ret;
> +
> + bit = LTC4283_ADC_SELECT_MASK(adc_idx);
> + if (channel > LTC4283_CHAN_DRAIN)
> + /* Account for two reserved fields after DRAIN. */
> + bit <<= 2;
> +
> + if (en)
> + ret = regmap_set_bits(st->map, reg, bit);
> + else
> + ret = regmap_clear_bits(st->map, reg, bit);
> + if (ret)
> + return ret;
> +
> + __assign_bit(channel, &st->ch_enable_mask, en);
> + return 0;
> +}
> +
> +static int ltc4283_write_minmax(struct ltc4283_hwmon *st, long val,
> + u32 channel, bool is_max)
> +{
> + u32 reg;
> +
> + if (channel == LTC4283_CHAN_VPWR) {
> + if (is_max)
> + return ltc4283_write_in_byte(st, LTC4283_VPWR_MAX_TH,
> + LTC4283_ADC2_FS_mV, val);
> +
> + return ltc4283_write_in_byte(st, LTC4283_VPWR_MIN_TH,
> + LTC4283_ADC2_FS_mV, val);
> + }
> +
> + if (channel >= LTC4283_CHAN_ADI_1 && channel <= LTC4283_CHAN_DRAIN) {
> + if (is_max) {
> + reg = LTC4283_ADC_2_MAX_TH(channel - LTC4283_CHAN_ADI_1);
> + return ltc4283_write_in_byte(st, reg,
> + LTC4283_ADC2_FS_mV, val);
> + }
> +
> + reg = LTC4283_ADC_2_MIN_TH(channel - LTC4283_CHAN_ADI_1);
> + return ltc4283_write_in_byte(st, reg, LTC4283_ADC2_FS_mV, val);
> + }
> +
> + /* Clamp before multiplying to avoid overflow on any arch. */
> + val = clamp_val(val, 0, LONG_MAX / MILLI);
> +
> + if (is_max) {
> + reg = LTC4283_ADC_2_MAX_TH_DIFF(channel - LTC4283_CHAN_ADIN12);
> + return ltc4283_write_in_byte(st, reg, LTC4283_ADC1_FS_uV,
> + val * MILLI);
> + }
> +
> + reg = LTC4283_ADC_2_MIN_TH_DIFF(channel - LTC4283_CHAN_ADIN12);
> + return ltc4283_write_in_byte(st, reg, LTC4283_ADC1_FS_uV, val * MILLI);
> +}
> +
> +static int ltc4283_write_in(struct ltc4283_hwmon *st, u32 attr, long val,
> + int channel)
> +{
> + switch (attr) {
> + case hwmon_in_max:
> + return ltc4283_write_minmax(st, val, channel, true);
> + case hwmon_in_min:
> + return ltc4283_write_minmax(st, val, channel, false);
> + case hwmon_in_reset_history:
> + return ltc4283_reset_in_hist(st, channel);
> + case hwmon_in_enable:
> + return ltc4283_write_in_en(st, channel, !!val);
> + default:
> + return -EOPNOTSUPP;
> + }
> +}
> +
> +static int ltc4283_write_curr_byte(const struct ltc4283_hwmon *st,
> + u32 reg, long val)
> +{
> + u32 temp = LTC4283_ADC1_FS_uV * DECA * MILLI;
> + u32 reg_val, isense_max;
> +
> + isense_max = DIV_ROUND_CLOSEST(st->vsense_max * MICRO * DECA, st->rsense);
> + val = clamp_val(val, 0, isense_max);
> + reg_val = DIV_ROUND_CLOSEST_ULL(val * BIT_ULL(8) * st->rsense, temp);
> +
> + return regmap_write(st->map, reg, reg_val);
> +}
> +
> +static int ltc4283_write_curr_history(struct ltc4283_hwmon *st)
> +{
> + int ret;
> +
> + ret = ltc4283_write_in_history(st, LTC4283_SENSE_MIN,
> + st->vsense_max * MILLI,
> + LTC4283_ADC1_FS_uV);
> + if (ret)
> + return ret;
> +
> + /* Now, let's also clear possible overcurrent logs. */
> + return regmap_clear_bits(st->map, LTC4283_FAULT_LOG,
> + LTC4283_OC_FAULT_MASK);
> +}
> +
> +static int ltc4283_write_curr(struct ltc4283_hwmon *st, u32 attr, long val)
> +{
> + switch (attr) {
> + case hwmon_curr_max:
> + return ltc4283_write_curr_byte(st, LTC4283_SENSE_MAX_TH, val);
> + case hwmon_curr_min:
> + return ltc4283_write_curr_byte(st, LTC4283_SENSE_MIN_TH, val);
> + case hwmon_curr_reset_history:
> + return ltc4283_write_curr_history(st);
> + default:
> + return -EOPNOTSUPP;
> + }
> +}
> +
> +static int ltc4283_energy_enable_set(struct ltc4283_hwmon *st, long val)
> +{
> + int ret;
> +
> + /* Setting the bit halts the meter. */
> + val = !!val;
> + ret = regmap_update_bits(st->map, LTC4283_METER_CONTROL,
> + LTC4283_METER_HALT_MASK,
> + FIELD_PREP(LTC4283_METER_HALT_MASK, !val));
> + if (ret)
> + return ret;
> +
> + st->energy_en = val;
> +
> + return 0;
> +}
> +
> +static int ltc4283_write(struct device *dev, enum hwmon_sensor_types type,
> + u32 attr, int channel, long val)
> +{
> + struct ltc4283_hwmon *st = dev_get_drvdata(dev);
> +
> + switch (type) {
> + case hwmon_power:
> + return ltc4283_write_power(st, attr, val);
> + case hwmon_in:
> + return ltc4283_write_in(st, attr, val, channel);
> + case hwmon_curr:
> + return ltc4283_write_curr(st, attr, val);
> + case hwmon_energy:
> + return ltc4283_energy_enable_set(st, val);
> + default:
> + return -EOPNOTSUPP;
> + }
> +}
> +
> +static umode_t ltc4283_in_is_visible(const struct ltc4283_hwmon *st,
> + u32 attr, int channel)
> +{
> + /* If ADIO is set as a GPIO, don´t make it visible. */
> + if (channel >= LTC4283_CHAN_ADIO_1 && channel <= LTC4283_CHAN_ADIO_4) {
> + /* ADIOX pins come at index 0 in the gpio mask. */
> + channel -= LTC4283_CHAN_ADIO_1;
> + if (test_bit(channel, &st->gpio_mask))
> + return 0;
> + }
> +
> + /* Also take care of differential channels. */
> + if (channel >= LTC4283_CHAN_ADIO12 && channel <= LTC4283_CHAN_ADIO34) {
> + channel -= LTC4283_CHAN_ADIO12;
> + /* If one channel in the pair is used, make it invisible. */
> + if (test_bit(channel * 2, &st->gpio_mask) ||
> + test_bit(channel * 2 + 1, &st->gpio_mask))
> + return 0;
> + }
> +
> + switch (attr) {
> + case hwmon_in_input:
> + case hwmon_in_highest:
> + case hwmon_in_lowest:
> + case hwmon_in_max_alarm:
> + case hwmon_in_min_alarm:
> + case hwmon_in_label:
> + case hwmon_in_lcrit_alarm:
> + case hwmon_in_crit_alarm:
> + case hwmon_in_fault:
> + return 0444;
> + case hwmon_in_max:
> + case hwmon_in_min:
> + case hwmon_in_enable:
> + return 0644;
> + case hwmon_in_reset_history:
> + return 0200;
> + default:
> + return 0;
> + }
> +}
> +
> +static umode_t ltc4283_curr_is_visible(u32 attr)
> +{
> + switch (attr) {
> + case hwmon_curr_input:
> + case hwmon_curr_highest:
> + case hwmon_curr_lowest:
> + case hwmon_curr_max_alarm:
> + case hwmon_curr_min_alarm:
> + case hwmon_curr_crit_alarm:
> + case hwmon_curr_label:
> + return 0444;
> + case hwmon_curr_max:
> + case hwmon_curr_min:
> + return 0644;
> + case hwmon_curr_reset_history:
> + return 0200;
> + default:
> + return 0;
> + }
> +}
> +
> +static umode_t ltc4283_power_is_visible(u32 attr)
> +{
> + switch (attr) {
> + case hwmon_power_input:
> + case hwmon_power_input_highest:
> + case hwmon_power_input_lowest:
> + case hwmon_power_label:
> + case hwmon_power_max_alarm:
> + case hwmon_power_min_alarm:
> + return 0444;
> + case hwmon_power_max:
> + case hwmon_power_min:
> + return 0644;
> + case hwmon_power_reset_history:
> + return 0200;
> + default:
> + return 0;
> + }
> +}
> +
> +static umode_t ltc4283_is_visible(const void *data,
> + enum hwmon_sensor_types type,
> + u32 attr, int channel)
> +{
> + switch (type) {
> + case hwmon_in:
> + return ltc4283_in_is_visible(data, attr, channel);
> + case hwmon_curr:
> + return ltc4283_curr_is_visible(attr);
> + case hwmon_power:
> + return ltc4283_power_is_visible(attr);
> + case hwmon_energy:
> + /* hwmon_energy_enable */
> + return 0644;
> + case hwmon_energy64:
> + /* hwmon_energy_input */
> + return 0444;
> + default:
> + return 0;
> + }
> +}
> +
> +static const char * const ltc4283_in_strs[] = {
> + "VIN", "VPWR", "VADI1", "VADI2", "VADI3", "VADI4", "VADIO1", "VADIO2",
> + "VADIO3", "VADIO4", "DRNS", "DRAIN", "ADIN2-ADIN1", "ADIN4-ADIN3",
> + "ADIO2-ADIO1", "ADIO4-ADIO3"
> +};
> +
> +static int ltc4283_read_labels(struct device *dev,
> + enum hwmon_sensor_types type,
> + u32 attr, int channel, const char **str)
> +{
> + switch (type) {
> + case hwmon_in:
> + *str = ltc4283_in_strs[channel];
> + return 0;
> + case hwmon_curr:
> + *str = "ISENSE";
> + return 0;
> + case hwmon_power:
> + *str = "Power";
> + return 0;
> + default:
> + return -EOPNOTSUPP;
> + }
> +}
> +
> +/*
> + * Set max limits for ISENSE and Power as that depends on the max voltage on
> + * rsense that is defined in ILIM_ADJUST. This is specially important for power
> + * because for some rsense and vfsout values, if we allow the default raw 255
> + * value, that would overflow long in 32bit archs when reading back the max
> + * power limit.
> + */
> +static int ltc4283_set_max_limits(struct ltc4283_hwmon *st, struct device *dev)
> +{
> + u32 temp = st->vsense_max * DECA * MICRO;
> + int ret;
> +
> + ret = ltc4283_write_in_byte(st, LTC4283_SENSE_MAX_TH, LTC4283_ADC1_FS_uV,
> + st->vsense_max * MILLI);
> + if (ret)
> + return ret;
> +
> + /* Power is given by ISENSE * Vout. */
> + st->power_max = DIV_ROUND_CLOSEST(temp, st->rsense) * LTC4283_ADC2_FS_mV;
> + return ltc4283_write_power_byte(st, LTC4283_POWER_MAX_TH, st->power_max);
> +}
> +
> +static int ltc4283_parse_array_prop(const struct ltc4283_hwmon *st,
> + struct device *dev, const char *prop,
> + const u32 *vals, u32 n_vals)
> +{
> + u32 prop_val;
> + int ret;
> + u32 i;
> +
> + ret = device_property_read_u32(dev, prop, &prop_val);
> + if (ret)
> + return n_vals;
> +
> + for (i = 0; i < n_vals; i++) {
> + if (prop_val != vals[i])
> + continue;
> +
> + return i;
> + }
> +
> + return dev_err_probe(dev, -EINVAL,
> + "Invalid %s property value %u\n", prop, prop_val);
> +}
> +
> +static int ltc4283_get_defaults(struct ltc4283_hwmon *st)
> +{
> + u32 reg_val, ilm_adjust, c;
> + int ret;
> +
> + ret = regmap_read(st->map, LTC4283_METER_CONTROL, ®_val);
> + if (ret)
> + return ret;
> +
> + st->energy_en = !FIELD_GET(LTC4283_METER_HALT_MASK, reg_val);
> +
> + ret = regmap_read(st->map, LTC4283_CONFIG_1, ®_val);
> + if (ret)
> + return ret;
> +
> + ilm_adjust = FIELD_GET(LTC4283_ILIM_MASK, reg_val);
> + st->vsense_max = LTC4283_VILIM_MIN_uV / MILLI + ilm_adjust;
> +
> + ret = regmap_read(st->map, LTC4283_PGIO_CONFIG, ®_val);
> + if (ret)
> + return ret;
> +
> + /* Can be latter overwritten in ltc4283_pgio_config() */
> + if (FIELD_GET(LTC4283_PGIO4_CFG_MASK, reg_val) < LTC4283_PGIO_FUNC_GPIO)
> + st->ext_fault = true;
> +
> + /* VPWR and VIN are always enabled */
> + __set_bit(LTC4283_CHAN_VIN, &st->ch_enable_mask);
> + __set_bit(LTC4283_CHAN_VPWR, &st->ch_enable_mask);
> + for (c = LTC4283_CHAN_ADI_1; c < LTC4283_CHAN_MAX; c++) {
> + u32 chan = c - LTC4283_CHAN_ADI_1, bit;
> +
> + ret = regmap_read(st->map, LTC4283_ADC_SELECT(chan), ®_val);
> + if (ret)
> + return ret;
> +
> + bit = LTC4283_ADC_SELECT_MASK(chan);
> + if (c > LTC4283_CHAN_DRAIN)
> + /* account for two reserved fields after DRAIN */
> + bit <<= 2;
> +
> + if (!(bit & reg_val))
> + continue;
> +
> + __set_bit(c, &st->ch_enable_mask);
> + }
> +
> + return 0;
> +}
> +
> +static const char * const ltc4283_pgio1_funcs[] = {
> + "inverted_power_good", "power_good", "gpio"
> +};
> +
> +static const char * const ltc4283_pgio2_funcs[] = {
> + "inverted_power_good", "power_good", "gpio", "active_current_limiting"
> +};
> +
> +static const char * const ltc4283_pgio3_funcs[] = {
> + "inverted_power_good_input", "power_good_input", "gpio"
> +};
> +
> +static const char * const ltc4283_pgio4_funcs[] = {
> + "inverted_external_fault", "external_fault", "gpio"
> +};
> +
> +enum {
> + LTC4283_PIN_ADIO1,
> + LTC4283_PIN_ADIO2,
> + LTC4283_PIN_ADIO3,
> + LTC4283_PIN_ADIO4,
> + LTC4283_PIN_PGIO1,
> + LTC4283_PIN_PGIO2,
> + LTC4283_PIN_PGIO3,
> + LTC4283_PIN_PGIO4,
> +};
> +
> +static int ltc4283_pgio_config(struct ltc4283_hwmon *st, struct device *dev)
> +{
> + int ret, func;
> +
> + func = device_property_match_property_string(dev, "adi,pgio1-func",
> + ltc4283_pgio1_funcs,
> + ARRAY_SIZE(ltc4283_pgio1_funcs));
> + if (func < 0 && func != -EINVAL)
> + return dev_err_probe(dev, func,
> + "Invalid adi,pgio1-func property\n");
> + if (func >= 0) {
> + if (func == LTC4283_PGIO_FUNC_GPIO) {
> + __set_bit(LTC4283_PIN_PGIO1, &st->gpio_mask);
> + /* If GPIO, default to an input pin. */
> + func++;
> + }
> +
> + ret = regmap_update_bits(st->map, LTC4283_PGIO_CONFIG,
> + LTC4283_PGIO1_CFG_MASK,
> + FIELD_PREP(LTC4283_PGIO1_CFG_MASK, func));
> + if (ret)
> + return ret;
> + }
> +
> + func = device_property_match_property_string(dev, "adi,pgio2-func",
> + ltc4283_pgio2_funcs,
> + ARRAY_SIZE(ltc4283_pgio2_funcs));
> +
> + if (func < 0 && func != -EINVAL)
> + return dev_err_probe(dev, func,
> + "Invalid adi,pgio2-func property\n");
> + if (func >= 0) {
> + if (func != LTC4283_PGIO2_FUNC_ACLB) {
> + if (func == LTC4283_PGIO_FUNC_GPIO) {
> + __set_bit(LTC4283_PIN_PGIO2, &st->gpio_mask);
> + func++;
> + }
> +
> + ret = regmap_update_bits(st->map, LTC4283_PGIO_CONFIG,
> + LTC4283_PGIO2_CFG_MASK,
> + FIELD_PREP(LTC4283_PGIO2_CFG_MASK, func));
> + } else {
> + ret = regmap_set_bits(st->map, LTC4283_CONTROL_1,
> + LTC4283_PIGIO2_ACLB_MASK);
> + }
> +
> + if (ret)
> + return ret;
> + }
> +
> + func = device_property_match_property_string(dev, "adi,pgio3-func",
> + ltc4283_pgio3_funcs,
> + ARRAY_SIZE(ltc4283_pgio3_funcs));
> +
> + if (func < 0 && func != -EINVAL)
> + return dev_err_probe(dev, func,
> + "Invalid adi,pgio3-func property\n");
> + if (func >= 0) {
> + if (func == LTC4283_PGIO_FUNC_GPIO) {
> + __set_bit(LTC4283_PIN_PGIO3, &st->gpio_mask);
> + func++;
> + }
> +
> + ret = regmap_update_bits(st->map, LTC4283_PGIO_CONFIG,
> + LTC4283_PGIO3_CFG_MASK,
> + FIELD_PREP(LTC4283_PGIO3_CFG_MASK, func));
> + if (ret)
> + return ret;
> + }
> +
> + func = device_property_match_property_string(dev, "adi,pgio4-func",
> + ltc4283_pgio4_funcs,
> + ARRAY_SIZE(ltc4283_pgio4_funcs));
> +
> + if (func < 0 && func != -EINVAL)
> + return dev_err_probe(dev, func,
> + "Invalid adi,pgio4-func property\n");
> + if (func >= 0) {
> + if (func == LTC4283_PGIO_FUNC_GPIO) {
> + __set_bit(LTC4283_PIN_PGIO4, &st->gpio_mask);
> + func++;
> + st->ext_fault = false;
> + } else {
> + st->ext_fault = true;
> + }
> +
> + ret = regmap_update_bits(st->map, LTC4283_PGIO_CONFIG,
> + LTC4283_PGIO4_CFG_MASK,
> + FIELD_PREP(LTC4283_PGIO4_CFG_MASK, func));
> + if (ret)
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static int ltc4283_adio_config(struct ltc4283_hwmon *st, struct device *dev,
> + const char *prop, u32 pin)
> +{
> + u32 adc_idx;
> + int ret;
> +
> + if (!device_property_read_bool(dev, prop))
> + return 0;
> +
> + adc_idx = LTC4283_CHAN_ADIO_1 - LTC4283_CHAN_ADI_1 + pin;
> + ret = regmap_clear_bits(st->map, LTC4283_ADC_SELECT(adc_idx),
> + LTC4283_ADC_SELECT_MASK(adc_idx));
> + if (ret)
> + return ret;
> +
> + __set_bit(pin, &st->gpio_mask);
> + return 0;
> +}
> +
> +static int ltc4283_pin_config(struct ltc4283_hwmon *st, struct device *dev)
> +{
> + int ret;
> +
> + ret = ltc4283_pgio_config(st, dev);
> + if (ret)
> + return ret;
> +
> + ret = ltc4283_adio_config(st, dev, "adi,gpio-on-adio1", LTC4283_PIN_ADIO1);
> + if (ret)
> + return ret;
> +
> + ret = ltc4283_adio_config(st, dev, "adi,gpio-on-adio2", LTC4283_PIN_ADIO2);
> + if (ret)
> + return ret;
> +
> + ret = ltc4283_adio_config(st, dev, "adi,gpio-on-adio3", LTC4283_PIN_ADIO3);
> + if (ret)
> + return ret;
> +
> + return ltc4283_adio_config(st, dev, "adi,gpio-on-adio4", LTC4283_PIN_ADIO4);
> +}
> +
> +static const char * const ltc4283_oc_fet_retry[] = {
> + "latch-off", "1", "7", "unlimited"
> +};
> +
> +static const u32 ltc4283_fb_factor[] = {
> + 100, 50, 20, 10
> +};
> +
> +static const u32 ltc4283_cooling_dl[] = {
> + 512, 1002, 2005, 4100, 8190, 16400, 32800, 65600
> +};
> +
> +static const u32 ltc4283_fet_bad_delay[] = {
> + 256, 512, 1002, 2005
> +};
> +
> +static int ltc4283_setup(struct ltc4283_hwmon *st, struct device *dev)
> +{
> + u32 val;
> + int ret;
> +
> + /* The part has an eeprom so let's get the needed defaults from it */
> + ret = ltc4283_get_defaults(st);
> + if (ret)
> + return ret;
> +
> + /*
> + * Default to LTC4283_MIN_RSENSE so we can probe without FW properties.
> + */
> + st->rsense = LTC4283_MIN_RSENSE;
> + ret = device_property_read_u32(dev, "adi,rsense-nano-ohms",
> + &st->rsense);
> + if (!ret) {
> + if (st->rsense < LTC4283_MIN_RSENSE || st->rsense > LTC4283_MAX_RSENSE)
> + return dev_err_probe(dev, -EINVAL,
> + "adi,rsense-nano-ohms(%u) too small or too large [%u %u]\n",
> + st->rsense, LTC4283_MIN_RSENSE, LTC4283_MAX_RSENSE);
> + }
> +
> + /*
> + * The resolution for rsense is tenths of micro (eg: 62.5 uOhm) which
> + * means we need nano in the bindings. However, to make things easier to
> + * handle (with respect to overflows) we divide it by 100 as we don't
> + * really need the last two digits.
> + */
> + st->rsense /= CENTI;
> +
> + ret = device_property_read_u32(dev, "adi,current-limit-sense-microvolt",
> + &st->vsense_max);
> + if (!ret) {
> + u32 reg_val;
> +
> + if (!in_range(st->vsense_max, LTC4283_VILIM_MIN_uV,
> + LTC4283_VILIM_RANGE)) {
> + return dev_err_probe(dev, -EINVAL,
> + "adi,current-limit-sense-microvolt (%u) out of range [%u %u]\n",
> + st->vsense_max, LTC4283_VILIM_MIN_uV,
> + LTC4283_VILIM_MAX_uV);
> + }
> +
> + st->vsense_max /= MILLI;
> + reg_val = FIELD_PREP(LTC4283_ILIM_MASK,
> + st->vsense_max - LTC4283_VILIM_MIN_uV / MILLI);
> + ret = regmap_update_bits(st->map, LTC4283_CONFIG_1,
> + LTC4283_ILIM_MASK, reg_val);
> + if (ret)
> + return ret;
> + }
> +
> + ret = ltc4283_parse_array_prop(st, dev, "adi,current-limit-foldback-factor",
> + ltc4283_fb_factor, ARRAY_SIZE(ltc4283_fb_factor));
> + if (ret < 0)
> + return ret;
> + if (ret < ARRAY_SIZE(ltc4283_fb_factor)) {
> + ret = regmap_update_bits(st->map, LTC4283_CONFIG_1, LTC4283_FB_MASK,
> + FIELD_PREP(LTC4283_FB_MASK, ret));
> + if (ret)
> + return ret;
> + }
> +
> + ret = ltc4283_parse_array_prop(st, dev, "adi,cooling-delay-ms",
> + ltc4283_cooling_dl, ARRAY_SIZE(ltc4283_cooling_dl));
> + if (ret < 0)
> + return ret;
> + if (ret < ARRAY_SIZE(ltc4283_cooling_dl)) {
> + ret = regmap_update_bits(st->map, LTC4283_CONFIG_2, LTC4283_COOLING_DL_MASK,
> + FIELD_PREP(LTC4283_COOLING_DL_MASK, ret));
> + if (ret)
> + return ret;
> + }
> +
> + ret = ltc4283_parse_array_prop(st, dev, "adi,fet-bad-timer-delay-ms",
> + ltc4283_fet_bad_delay, ARRAY_SIZE(ltc4283_fet_bad_delay));
> + if (ret < 0)
> + return ret;
> + if (ret < ARRAY_SIZE(ltc4283_fet_bad_delay)) {
> + ret = regmap_update_bits(st->map, LTC4283_CONFIG_2, LTC4283_FTBD_DL_MASK,
> + FIELD_PREP(LTC4283_FTBD_DL_MASK, ret));
> + if (ret)
> + return ret;
> + }
> +
> + ret = ltc4283_set_max_limits(st, dev);
> + if (ret)
> + return ret;
> +
> + ret = ltc4283_pin_config(st, dev);
> + if (ret)
> + return ret;
> +
> + if (device_property_read_bool(dev, "adi,power-good-reset-on-fet")) {
> + ret = regmap_clear_bits(st->map, LTC4283_CONTROL_1,
> + LTC4283_PWRGD_RST_CTRL_MASK);
> + if (ret)
> + return ret;
> + }
> +
> + if (device_property_read_bool(dev, "adi,fet-turn-off-disable")) {
> + ret = regmap_clear_bits(st->map, LTC4283_CONTROL_1,
> + LTC4283_FET_BAD_OFF_MASK);
> + if (ret)
> + return ret;
> + }
> +
> + if (device_property_read_bool(dev, "adi,tmr-pull-down-disable")) {
> + ret = regmap_set_bits(st->map, LTC4283_CONTROL_1,
> + LTC4283_THERM_TMR_MASK);
> + if (ret)
> + return ret;
> + }
> +
> + if (device_property_read_bool(dev, "adi,dvdt-inrush-control-disable")) {
> + ret = regmap_clear_bits(st->map, LTC4283_CONTROL_1,
> + LTC4283_DVDT_MASK);
> + if (ret)
> + return ret;
> + }
> +
> + if (device_property_read_bool(dev, "adi,undervoltage-retry-disable")) {
> + ret = regmap_clear_bits(st->map, LTC4283_CONTROL_2,
> + LTC4283_UV_RETRY_MASK);
> + if (ret)
> + return ret;
> + }
> +
> + if (device_property_read_bool(dev, "adi,overvoltage-retry-disable")) {
> + ret = regmap_clear_bits(st->map, LTC4283_CONTROL_2,
> + LTC4283_OV_RETRY_MASK);
> + if (ret)
> + return ret;
> + }
> +
> + if (device_property_read_bool(dev, "adi,external-fault-retry-enable")) {
> + if (!st->ext_fault)
> + return dev_err_probe(dev, -EINVAL,
> + "adi,external-fault-retry-enable set but PGIO4 not configured\n");
> + ret = regmap_set_bits(st->map, LTC4283_CONTROL_2,
> + LTC4283_EXT_FAULT_RETRY_MASK);
> + if (ret)
> + return ret;
> + }
> +
> + if (device_property_read_bool(dev, "adi,fault-log-enable")) {
> + ret = regmap_set_bits(st->map, LTC4283_FAULT_LOG_CTRL,
> + LTC4283_FAULT_LOG_EN_MASK);
> + if (ret)
> + return ret;
> + }
> +
> + ret = device_property_match_property_string(dev, "adi,overcurrent-retries",
> + ltc4283_oc_fet_retry,
> + ARRAY_SIZE(ltc4283_oc_fet_retry));
> + /* We still want to catch when an invalid string is given. */
> + if (ret < 0 && ret != -EINVAL)
> + return dev_err_probe(dev, ret,
> + "adi,overcurrent-retries invalid value\n");
> + if (ret >= 0) {
> + ret = regmap_update_bits(st->map, LTC4283_CONTROL_2,
> + LTC4283_OC_RETRY_MASK,
> + FIELD_PREP(LTC4283_OC_RETRY_MASK, ret));
> + if (ret)
> + return ret;
> + }
> +
> + ret = device_property_match_property_string(dev, "adi,fet-bad-retries",
> + ltc4283_oc_fet_retry,
> + ARRAY_SIZE(ltc4283_oc_fet_retry));
> + if (ret < 0 && ret != -EINVAL)
> + return dev_err_probe(dev, ret,
> + "adi,fet-bad-retries invalid value\n");
> + if (ret >= 0) {
> + ret = regmap_update_bits(st->map, LTC4283_CONTROL_2,
> + LTC4283_FET_BAD_RETRY_MASK,
> + FIELD_PREP(LTC4283_FET_BAD_RETRY_MASK, ret));
> + if (ret)
> + return ret;
> + }
> +
> + if (device_property_read_bool(dev, "adi,external-fault-fet-off-enable")) {
> + if (!st->ext_fault)
> + return dev_err_probe(dev, -EINVAL,
> + "adi,external-fault-fet-off-enable set but PGIO4 not configured\n");
> + ret = regmap_set_bits(st->map, LTC4283_CONFIG_3,
> + LTC4283_EXTFLT_TURN_OFF_MASK);
> + if (ret)
> + return ret;
> + }
> +
> + if (device_property_read_bool(dev, "adi,vpower-drns-enable")) {
> + u32 chan = LTC4283_CHAN_DRNS - LTC4283_CHAN_ADI_1;
> +
> + __clear_bit(LTC4283_CHAN_DRNS, &st->ch_enable_mask);
> + /*
> + * Then, let's by default disable DRNS from ADC2 given that it
> + * is already being monitored by the VPWR channel. One can still
> + * enable it later on if needed.
> + */
> + ret = regmap_clear_bits(st->map, LTC4283_ADC_SELECT(chan),
> + LTC4283_ADC_SELECT_MASK(chan));
> + if (ret)
> + return ret;
> +
> + val = 1;
> + } else {
> + val = 0;
> + }
> +
> + ret = regmap_update_bits(st->map, LTC4283_CONFIG_3,
> + LTC4283_VPWR_DRNS_MASK,
> + FIELD_PREP(LTC4283_VPWR_DRNS_MASK, val));
> + if (ret)
> + return ret;
> +
> + /* Make sure the ADC has 12bit resolution since we're assuming that. */
> + ret = regmap_update_bits(st->map, LTC4283_PGIO_CONFIG_2,
> + LTC4283_ADC_MASK,
> + FIELD_PREP(LTC4283_ADC_MASK, 3));
> + if (ret)
> + return ret;
> +
> + /* Energy reads (which are 6 byte block reads) rely on page access */
> + ret = regmap_set_bits(st->map, LTC4283_CONTROL_1, LTC4283_RW_PAGE_MASK);
> + if (ret)
> + return ret;
> +
> + /*
> + * Make sure we are integrating power as we only support reporting
> + * consumed energy.
> + */
> + return regmap_clear_bits(st->map, LTC4283_METER_CONTROL,
> + LTC4283_INTEGRATE_I_MASK);
> +}
> +
> +static const struct hwmon_channel_info * const ltc4283_info[] = {
> + HWMON_CHANNEL_INFO(in,
> + HWMON_I_LCRIT_ALARM | HWMON_I_CRIT_ALARM |
> + HWMON_I_RESET_HISTORY | HWMON_I_LABEL,
> + HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
> + HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
> + HWMON_I_MAX_ALARM | HWMON_I_RESET_HISTORY |
> + HWMON_I_LABEL,
> + HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
> + HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
> + HWMON_I_RESET_HISTORY | HWMON_I_MAX_ALARM |
> + HWMON_I_ENABLE | HWMON_I_LABEL,
> + HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
> + HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
> + HWMON_I_RESET_HISTORY | HWMON_I_MAX_ALARM |
> + HWMON_I_ENABLE | HWMON_I_LABEL,
> + HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
> + HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
> + HWMON_I_RESET_HISTORY | HWMON_I_MAX_ALARM |
> + HWMON_I_ENABLE | HWMON_I_LABEL,
> + HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
> + HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
> + HWMON_I_RESET_HISTORY | HWMON_I_MAX_ALARM |
> + HWMON_I_ENABLE | HWMON_I_LABEL,
> + HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
> + HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
> + HWMON_I_RESET_HISTORY | HWMON_I_MAX_ALARM |
> + HWMON_I_ENABLE | HWMON_I_LABEL,
> + HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
> + HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
> + HWMON_I_RESET_HISTORY | HWMON_I_MAX_ALARM |
> + HWMON_I_ENABLE | HWMON_I_LABEL,
> + HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
> + HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
> + HWMON_I_RESET_HISTORY | HWMON_I_MAX_ALARM |
> + HWMON_I_ENABLE | HWMON_I_LABEL,
> + HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
> + HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
> + HWMON_I_RESET_HISTORY | HWMON_I_MAX_ALARM |
> + HWMON_I_ENABLE | HWMON_I_LABEL,
> + HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
> + HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
> + HWMON_I_RESET_HISTORY | HWMON_I_MAX_ALARM |
> + HWMON_I_ENABLE | HWMON_I_LABEL,
> + HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
> + HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
> + HWMON_I_RESET_HISTORY | HWMON_I_MAX_ALARM |
> + HWMON_I_FAULT | HWMON_I_ENABLE | HWMON_I_LABEL,
> + HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
> + HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
> + HWMON_I_RESET_HISTORY | HWMON_I_MAX_ALARM |
> + HWMON_I_ENABLE | HWMON_I_LABEL,
> + HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
> + HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
> + HWMON_I_RESET_HISTORY | HWMON_I_MAX_ALARM |
> + HWMON_I_ENABLE | HWMON_I_LABEL,
> + HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
> + HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
> + HWMON_I_RESET_HISTORY | HWMON_I_MAX_ALARM |
> + HWMON_I_ENABLE | HWMON_I_LABEL,
> + HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
> + HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
> + HWMON_I_RESET_HISTORY | HWMON_I_MAX_ALARM |
> + HWMON_I_ENABLE | HWMON_I_LABEL),
> + HWMON_CHANNEL_INFO(curr,
> + HWMON_C_INPUT | HWMON_C_LOWEST | HWMON_C_HIGHEST |
> + HWMON_C_MAX | HWMON_C_MIN | HWMON_C_MIN_ALARM |
> + HWMON_C_MAX_ALARM | HWMON_C_CRIT_ALARM |
> + HWMON_C_RESET_HISTORY | HWMON_C_LABEL),
> + HWMON_CHANNEL_INFO(power,
> + HWMON_P_INPUT | HWMON_P_INPUT_LOWEST |
> + HWMON_P_INPUT_HIGHEST | HWMON_P_MAX | HWMON_P_MIN |
> + HWMON_P_MAX_ALARM | HWMON_P_MIN_ALARM |
> + HWMON_P_RESET_HISTORY | HWMON_P_LABEL),
> + HWMON_CHANNEL_INFO(energy,
> + HWMON_E_ENABLE),
> + HWMON_CHANNEL_INFO(energy64,
> + HWMON_E_INPUT),
> + NULL
> +};
> +
> +static const struct hwmon_ops ltc4283_ops = {
> + .read = ltc4283_read,
> + .write = ltc4283_write,
> + .is_visible = ltc4283_is_visible,
> + .read_string = ltc4283_read_labels,
> +};
> +
> +static const struct hwmon_chip_info ltc4283_chip_info = {
> + .ops = <c4283_ops,
> + .info = ltc4283_info,
> +};
> +
> +static int ltc4283_show_fault_log(void *arg, u64 *val, u32 mask)
> +{
> + struct ltc4283_hwmon *st = arg;
> + long alarm;
> + int ret;
> +
> + ret = ltc4283_read_alarm(st, LTC4283_FAULT_LOG, mask, &alarm);
> + if (ret)
> + return ret;
> +
> + *val = alarm;
> +
> + return 0;
> +}
> +
> +static int ltc4283_show_in0_lcrit_fault_log(void *arg, u64 *val)
> +{
> + return ltc4283_show_fault_log(arg, val, LTC4283_UV_FAULT_MASK);
> +}
> +DEFINE_DEBUGFS_ATTRIBUTE(ltc4283_in0_lcrit_fault_log,
> + ltc4283_show_in0_lcrit_fault_log, NULL, "%llu\n");
> +
> +static int ltc4283_show_in0_crit_fault_log(void *arg, u64 *val)
> +{
> + return ltc4283_show_fault_log(arg, val, LTC4283_OV_FAULT_MASK);
> +}
> +DEFINE_DEBUGFS_ATTRIBUTE(ltc4283_in0_crit_fault_log,
> + ltc4283_show_in0_crit_fault_log, NULL, "%llu\n");
> +
> +static int ltc4283_show_fet_bad_fault_log(void *arg, u64 *val)
> +{
> + return ltc4283_show_fault_log(arg, val, LTC4283_FET_BAD_FAULT_MASK);
> +}
> +DEFINE_DEBUGFS_ATTRIBUTE(ltc4283_fet_bad_fault_log,
> + ltc4283_show_fet_bad_fault_log, NULL, "%llu\n");
> +
> +static int ltc4283_show_fet_short_fault_log(void *arg, u64 *val)
> +{
> + return ltc4283_show_fault_log(arg, val, LTC4283_FET_SHORT_FAULT_MASK);
> +}
> +DEFINE_DEBUGFS_ATTRIBUTE(ltc4283_fet_short_fault_log,
> + ltc4283_show_fet_short_fault_log, NULL, "%llu\n");
> +
> +static int ltc4283_show_curr1_crit_fault_log(void *arg, u64 *val)
> +{
> + return ltc4283_show_fault_log(arg, val, LTC4283_OC_FAULT_MASK);
> +}
> +DEFINE_DEBUGFS_ATTRIBUTE(ltc4283_curr1_crit_fault_log,
> + ltc4283_show_curr1_crit_fault_log, NULL, "%llu\n");
> +
> +static int ltc4283_show_power1_failed_fault_log(void *arg, u64 *val)
> +{
> + return ltc4283_show_fault_log(arg, val, LTC4283_PWR_FAIL_FAULT_MASK);
> +}
> +DEFINE_DEBUGFS_ATTRIBUTE(ltc4283_power1_failed_fault_log,
> + ltc4283_show_power1_failed_fault_log, NULL, "%llu\n");
> +
> +static int ltc4283_show_power1_good_input_fault_log(void *arg, u64 *val)
> +{
> + return ltc4283_show_fault_log(arg, val, LTC4283_PGI_FAULT_MASK);
> +}
> +DEFINE_DEBUGFS_ATTRIBUTE(ltc4283_power1_good_input_fault_log,
> + ltc4283_show_power1_good_input_fault_log, NULL, "%llu\n");
> +
> +static void ltc4283_debugfs_init(struct ltc4283_hwmon *st, struct i2c_client *i2c)
> +{
> + debugfs_create_file_unsafe("in0_crit_fault_log", 0400, i2c->debugfs, st,
> + <c4283_in0_crit_fault_log);
> + debugfs_create_file_unsafe("in0_lcrit_fault_log", 0400, i2c->debugfs, st,
> + <c4283_in0_lcrit_fault_log);
> + debugfs_create_file_unsafe("in0_fet_bad_fault_log", 0400, i2c->debugfs, st,
> + <c4283_fet_bad_fault_log);
> + debugfs_create_file_unsafe("in0_fet_short_fault_log", 0400, i2c->debugfs, st,
> + <c4283_fet_short_fault_log);
> + debugfs_create_file_unsafe("curr1_crit_fault_log", 0400, i2c->debugfs, st,
> + <c4283_curr1_crit_fault_log);
> + debugfs_create_file_unsafe("power1_failed_fault_log", 0400, i2c->debugfs, st,
> + <c4283_power1_failed_fault_log);
> + debugfs_create_file_unsafe("power1_good_input_fault_log", 0400, i2c->debugfs,
> + st, <c4283_power1_good_input_fault_log);
> +}
> +
> +static bool ltc4283_is_word_reg(unsigned int reg)
> +{
> + return reg >= LTC4283_SENSE && reg <= LTC4283_ADIO34_MAX;
> +}
> +
> +static int ltc4283_reg_read(void *context, unsigned int reg, unsigned int *val)
> +{
> + struct i2c_client *client = context;
> + int ret;
> +
> + if (ltc4283_is_word_reg(reg))
> + ret = i2c_smbus_read_word_swapped(client, reg);
> + else
> + ret = i2c_smbus_read_byte_data(client, reg);
> +
> + if (ret < 0)
> + return ret;
> +
> + *val = ret;
> + return 0;
> +}
> +
> +static int ltc4283_reg_write(void *context, unsigned int reg, unsigned int val)
> +{
> + struct i2c_client *client = context;
> +
> + if (ltc4283_is_word_reg(reg))
> + return i2c_smbus_write_word_swapped(client, reg, val);
> +
> + return i2c_smbus_write_byte_data(client, reg, val);
> +}
> +
> +static const struct regmap_bus ltc4283_regmap_bus = {
> + .reg_read = ltc4283_reg_read,
> + .reg_write = ltc4283_reg_write,
> +};
> +
> +static bool ltc4283_writable_reg(struct device *dev, unsigned int reg)
> +{
> + switch (reg) {
> + case LTC4283_SYSTEM_STATUS ... LTC4283_FAULT_STATUS:
> + return false;
> + case LTC4283_RESERVED_OC:
> + return false;
> + case LTC4283_RESERVED_86 ... LTC4283_RESERVED_8F:
> + return false;
> + case LTC4283_RESERVED_91 ... LTC4283_RESERVED_A1:
> + return false;
> + case LTC4283_RESERVED_A3:
> + return false;
> + case LTC4283_RESERVED_AC:
> + return false;
> + case LTC4283_POWER_PLAY_MSB ... LTC4283_POWER_PLAY_LSB:
> + return false;
> + case LTC4283_RESERVED_F1 ... LTC4283_RESERVED_FF:
> + return false;
> + default:
> + return true;
> + }
> +}
> +
> +static const struct regmap_config ltc4283_regmap_config = {
> + .reg_bits = 8,
> + .val_bits = 16,
> + .max_register = 0xFF,
> + .writeable_reg = ltc4283_writable_reg,
> +};
> +
> +static int ltc4283_probe(struct i2c_client *client)
> +{
> + struct device *dev = &client->dev, *hwmon;
> + struct auxiliary_device *adev;
> + struct ltc4283_hwmon *st;
> + int ret, id;
> +
> + st = devm_kzalloc(dev, sizeof(*st), GFP_KERNEL);
> + if (!st)
> + return -ENOMEM;
> +
> + if (!i2c_check_functionality(client->adapter,
> + I2C_FUNC_SMBUS_BYTE_DATA |
> + I2C_FUNC_SMBUS_WORD_DATA |
> + I2C_FUNC_SMBUS_READ_I2C_BLOCK))
> + return -EOPNOTSUPP;
> +
> + st->client = client;
> + st->map = devm_regmap_init(dev, <c4283_regmap_bus, client,
> + <c4283_regmap_config);
> + if (IS_ERR(st->map))
> + return dev_err_probe(dev, PTR_ERR(st->map),
> + "Failed to create regmap\n");
> +
> + ret = ltc4283_setup(st, dev);
> + if (ret)
> + return ret;
> +
> + hwmon = devm_hwmon_device_register_with_info(dev, "ltc4283", st,
> + <c4283_chip_info, NULL);
> +
> + if (IS_ERR(hwmon))
> + return PTR_ERR(hwmon);
> +
> + ltc4283_debugfs_init(st, client);
> +
> + if (!st->gpio_mask)
> + return 0;
> +
> + id = (client->adapter->nr << 10) | client->addr;
> + adev = __devm_auxiliary_device_create(dev, KBUILD_MODNAME, "gpio",
> + NULL, id);
> + if (!adev)
> + return dev_err_probe(dev, -ENODEV, "Failed to add GPIO device\n");
> +
> + return 0;
> +}
> +
> +static const struct of_device_id ltc4283_of_match[] = {
> + { .compatible = "adi,ltc4283" },
> + { }
> +};
> +
> +static const struct i2c_device_id ltc4283_i2c_id[] = {
> + { "ltc4283" },
> + { }
> +};
> +MODULE_DEVICE_TABLE(i2c, ltc4283_i2c_id);
> +
> +static struct i2c_driver ltc4283_driver = {
> + .driver = {
> + .name = "ltc4283",
> + .of_match_table = ltc4283_of_match,
> + },
> + .probe = ltc4283_probe,
> + .id_table = ltc4283_i2c_id,
> +};
> +module_i2c_driver(ltc4283_driver);
> +
> +MODULE_AUTHOR("Nuno Sá <nuno.sa@analog.com>");
> +MODULE_DESCRIPTION("LTC4283 Hot Swap Controller driver");
> +MODULE_LICENSE("GPL");
>
> --
> 2.54.0
>
>
^ permalink raw reply
* Re: [PATCH net-next 00/11] net: devmem: support devmem with netkit devices
From: Daniel Borkmann @ 2026-04-29 12:08 UTC (permalink / raw)
To: Bobby Eshleman, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Jonathan Corbet,
Shuah Khan, Alex Shi, Yanteng Si, Dongliang Mu, Michael Chan,
Pavan Chebbi, Joshua Washington, Harshitha Ramamurthy,
Saeed Mahameed, Tariq Toukan, Mark Bloch, Leon Romanovsky,
Alexander Duyck, kernel-team, Nikolay Aleksandrov, Shuah Khan,
David Wei
Cc: netdev, linux-doc, linux-kernel, linux-rdma, bpf, linux-kselftest,
Stanislav Fomichev, Mina Almasry, Bobby Eshleman
In-Reply-To: <20260428-tcp-dm-netkit-v1-0-719280eba4d2@meta.com>
Hi Bobby,
On 4/29/26 12:41 AM, Bobby Eshleman wrote:
> This series enables TCP devmem TX through netkit devices.
>
> Netkit now supports queue leasing. A physical NIC's RX queue can be
> leased to a netkit guest interface inside a container namespace. This
> gives the container a devmem-capable data path on the RX side (bind-rx,
> etc...). On the TX side, the container process binds to its netkit guest
> interface and sends traffic that netkit redirects (via BPF or ip
> forwarding) to the physical NIC for DMA.
[...]
Thanks for working on this, after the RX queue leasing got merged, I've
been looking into the same actually. :)
I think the NETMEM_TX_* enum approach seems reasonable.
What I have a PoC on is to build out TX queue leasing as first-class
symmetric infrastructure to complement the RX queue leasing - basically
I implemented an equivalent to the latter in netdev_nl_queue_create_doit
et al, so you can have independent RX and TX leases and per-queue
accountability, such that ynl queue-get op shows the full picture, and
lastly we could also enable AF_XDP TX-only support through this infra.
Would you be open to collab on integrating both and migrating the devmem
code to work off an TX queue object? Next week is LSF/MM/BPF, are you
there by any chance to catch up in person?
Thanks a lot,
Daniel
^ permalink raw reply
* Re: [PATCH net-next 1/2] dpll: add pin operational state
From: Vadim Fedorenko @ 2026-04-29 10:45 UTC (permalink / raw)
To: Ivan Vecera, netdev
Cc: Arkadiusz Kubalewski, David S. Miller, Donald Hunter,
Eric Dumazet, Jakub Kicinski, Jiri Pirko, Jonathan Corbet,
Michal Schmidt, Paolo Abeni, Pasi Vaananen, Petr Oros,
Prathosh Satish, Shuah Khan, Simon Horman, linux-doc,
linux-kernel
In-Reply-To: <20260428154907.2820654-2-ivecera@redhat.com>
On 28/04/2026 16:49, Ivan Vecera wrote:
> Add pin-operstate enum and operstate_on_dpll_get callback to report
> the actual hardware status of a pin with respect to its parent DPLL
> device. Unlike pin-state (which reflects administrative intent set
> by the user), operstate reflects what the hardware is actually doing.
>
> Defined operational states:
> - active: pin is qualified and actively used by the DPLL
> - standby: pin is qualified but not actively used by the DPLL
> - no-signal: pin does not have a valid signal
> - qual-failed: pin signal failed qualification
>
> The operstate is reported inside the pin-parent-device nested
> attribute alongside the existing state and phase-offset attributes.
>
> Signed-off-by: Ivan Vecera <ivecera@redhat.com>
Reviewed-by: Vadim Fedorenko <vadim.fedorenko@linux.dev>
^ permalink raw reply
* Re: [PATCH] KVM: x86/xen: Add KVM_XEN_VCPU_ATTR_TYPE_WRITE_HYPERCALL_PAGE
From: Paul Durrant @ 2026-04-29 10:36 UTC (permalink / raw)
To: David Woodhouse, Paolo Bonzini, Jonathan Corbet, Shuah Khan,
Sean Christopherson, Thomas Gleixner, Ingo Molnar,
Borislav Petkov, Dave Hansen, x86, H. Peter Anvin, kvm, linux-doc,
linux-kernel, linux-kselftest
In-Reply-To: <c855535b4262ecd41f67734d19e8f48a7f014c2a.camel@infradead.org>
On 28/04/2026 21:12, David Woodhouse wrote:
> From: David Woodhouse <dwmw@amazon.co.uk>
>
> Commit 3617c0ee7decb ("KVM: x86/xen: Only write Xen hypercall page for
> guest writes to MSR") blocked host-initiated writes from triggering the
> Xen hypercall page setup, to fix an SRCU usage violation when the
> hypercall MSR index collides with a real MSR written during vCPU reset.
>
> However, some VMMs legitimately need to trigger hypercall page setup
> from host context. For example, a VMM may intercept the guest's MSR
> write to track an epoch (for kexec/crash recovery), and then replay the
> write as a host-initiated KVM_SET_MSRS to populate the hypercall page.
> The host_initiated check breaks this use case.
>
> Add KVM_XEN_VCPU_ATTR_TYPE_WRITE_HYPERCALL_PAGE as a new vcpu attribute
> that explicitly invokes kvm_xen_write_hypercall_page() under proper
> locking. This gives userspace a safe interface to trigger hypercall page
> setup without going through the MSR write path, preserving the
> host_initiated defence in depth while restoring the lost functionality.
>
> Fixes: 3617c0ee7dec ("KVM: x86/xen: Only write Xen hypercall page for guest writes to MSR")
> Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
> ---
> Documentation/virt/kvm/api.rst | 11 +++
> arch/x86/include/uapi/asm/kvm.h | 3 +
> arch/x86/kvm/x86.c | 3 +-
> arch/x86/kvm/xen.c | 7 ++
> .../selftests/kvm/x86/xen_vmcall_test.c | 96 +++++++++++++++++++
> 5 files changed, 119 insertions(+), 1 deletion(-)
>
Reviewed-by: Paul Durrant <paul@xen.org>
^ permalink raw reply
* [PATCH 1/3] dt-bindings: hwmon: pmbus: add Delta E50SN12051 binding
From: Colin Huang via B4 Relay @ 2026-04-29 9:41 UTC (permalink / raw)
To: Guenter Roeck, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Kevin Chang, Jonathan Corbet, Shuah Khan
Cc: linux-hwmon, devicetree, linux-kernel, linux-doc, Colin Huang,
Colin Huang
In-Reply-To: <20260429-add-e50sn12051-v1-0-c101e6c80bbb@gmail.com>
From: Colin Huang <u8813345@gmail.com>
Add devicetree binding documentation for the Delta E50SN12051
PMBus-compliant device.
Signed-off-by: Colin Huang <u8813345@gmail.com>
---
.../bindings/hwmon/pmbus/delta,e50sn12051.yaml | 40 ++++++++++++++++++++++
1 file changed, 40 insertions(+)
diff --git a/Documentation/devicetree/bindings/hwmon/pmbus/delta,e50sn12051.yaml b/Documentation/devicetree/bindings/hwmon/pmbus/delta,e50sn12051.yaml
new file mode 100644
index 000000000000..362cbbf10bfe
--- /dev/null
+++ b/Documentation/devicetree/bindings/hwmon/pmbus/delta,e50sn12051.yaml
@@ -0,0 +1,40 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+---
+$id: http://devicetree.org/schemas/hwmon/pmbus/delta,e50sn12051.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Delta E50SN12051 PMBus Sensor
+
+maintainers:
+ - Kevin Chang <kevin.chang2@amd.com>
+
+description: |
+ This binding describes the Delta E50SN12051 PMBus sensor.
+ The driver supports reading voltage, current, and temperature
+ using the PMBus interface.
+
+properties:
+ compatible:
+ const: delta,e50sn12051
+
+ reg:
+ maxItems: 1
+ description: I2C bus address of the PMBus device
+
+required:
+ - compatible
+ - reg
+
+additionalProperties: false
+
+examples:
+ - |
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ e50sn12051@40 {
+ compatible = "delta,e50sn12051";
+ reg = <0x40>;
+ };
+ };
--
2.34.1
^ permalink raw reply related
* [PATCH 3/3] hwmon: (pmbus) add support for Delta E50SN12051
From: Colin Huang via B4 Relay @ 2026-04-29 9:42 UTC (permalink / raw)
To: Guenter Roeck, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Kevin Chang, Jonathan Corbet, Shuah Khan
Cc: linux-hwmon, devicetree, linux-kernel, linux-doc, Colin Huang,
Colin Huang
In-Reply-To: <20260429-add-e50sn12051-v1-0-c101e6c80bbb@gmail.com>
From: Colin Huang <u8813345@gmail.com>
Add the pmbus driver for Delta E50SN12051 600W Non-isolated
1/8th Brick DCDC Power Modules.
Signed-off-by: Colin Huang <u8813345@gmail.com>
---
drivers/hwmon/pmbus/Kconfig | 9 ++++++
drivers/hwmon/pmbus/Makefile | 1 +
drivers/hwmon/pmbus/e50sn12051.c | 60 ++++++++++++++++++++++++++++++++++++++++
3 files changed, 70 insertions(+)
diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig
index 8f4bff375ecb..4fbfcbc4a9c5 100644
--- a/drivers/hwmon/pmbus/Kconfig
+++ b/drivers/hwmon/pmbus/Kconfig
@@ -161,6 +161,15 @@ config SENSORS_DPS920AB
This driver can also be built as a module. If so, the module will
be called dps920ab.
+config SENSORS_E50SN12051
+ tristate "Delta E50SN12051 Power Modules"
+ help
+ If you say yes here you get hardware monitoring support for Delta
+ E50SN12051 Power Modules.
+
+ This driver can also be built as a module. If so, the module will
+ be called e50sn12051.
+
config SENSORS_INA233
tristate "Texas Instruments INA233 and compatibles"
help
diff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile
index 7129b62bc00f..fc910bf34fe3 100644
--- a/drivers/hwmon/pmbus/Makefile
+++ b/drivers/hwmon/pmbus/Makefile
@@ -17,6 +17,7 @@ obj-$(CONFIG_SENSORS_FSP_3Y) += fsp-3y.o
obj-$(CONFIG_SENSORS_HAC300S) += hac300s.o
obj-$(CONFIG_SENSORS_IBM_CFFPS) += ibm-cffps.o
obj-$(CONFIG_SENSORS_DPS920AB) += dps920ab.o
+obj-$(CONFIG_SENSORS_E50SN12051) += e50sn12051.o
obj-$(CONFIG_SENSORS_INA233) += ina233.o
obj-$(CONFIG_SENSORS_INSPUR_IPSPS) += inspur-ipsps.o
obj-$(CONFIG_SENSORS_IR35221) += ir35221.o
diff --git a/drivers/hwmon/pmbus/e50sn12051.c b/drivers/hwmon/pmbus/e50sn12051.c
new file mode 100644
index 000000000000..56935e9543b6
--- /dev/null
+++ b/drivers/hwmon/pmbus/e50sn12051.c
@@ -0,0 +1,60 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Hardware monitoring driver for E50SN12051
+ */
+
+#include <linux/debugfs.h>
+#include <linux/i2c.h>
+#include <linux/kstrtox.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include "pmbus.h"
+
+static struct pmbus_driver_info e50sn12051_info = {
+ .pages = 1,
+ .format[PSC_VOLTAGE_IN] = linear,
+ .format[PSC_VOLTAGE_OUT] = linear,
+ .format[PSC_CURRENT_OUT] = linear,
+ .format[PSC_TEMPERATURE] = linear,
+ .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,
+};
+
+static const struct i2c_device_id e50sn12051_id[] = { { "e50sn12051", 0 }, {} };
+MODULE_DEVICE_TABLE(i2c, e50sn12051_id);
+
+static const struct of_device_id e50sn12051_of_match[] = {
+ { .compatible = "delta,e50sn12051" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, e50sn12051_of_match);
+
+static int e50sn12051_probe(struct i2c_client *client)
+{
+ struct pmbus_driver_info *info;
+
+ info = devm_kmemdup(&client->dev, &e50sn12051_info, sizeof(*info),
+ GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+
+ return pmbus_do_probe(client, info);
+}
+
+static struct i2c_driver e50sn12051_driver = {
+ .driver = {
+ .name = "e50sn12051",
+ .of_match_table = e50sn12051_of_match,
+ },
+ .probe = e50sn12051_probe,
+
+ .id_table = e50sn12051_id,
+};
+
+module_i2c_driver(e50sn12051_driver);
+
+MODULE_AUTHOR("Kevin Chang <kevin.chang2@amd.com>");
+MODULE_DESCRIPTION("PMBus driver for E50SN12051");
+MODULE_LICENSE("GPL");
--
2.34.1
^ permalink raw reply related
* [PATCH 2/3] Documentation/hwmon: add Delta E50SN12051 documentation
From: Colin Huang via B4 Relay @ 2026-04-29 9:41 UTC (permalink / raw)
To: Guenter Roeck, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Kevin Chang, Jonathan Corbet, Shuah Khan
Cc: linux-hwmon, devicetree, linux-kernel, linux-doc, Colin Huang,
Colin Huang
In-Reply-To: <20260429-add-e50sn12051-v1-0-c101e6c80bbb@gmail.com>
From: Colin Huang <u8813345@gmail.com>
Document the hardware monitoring support for the Delta E50SN12051
device.
The documentation describes the supported sensors exposed via the
hwmon subsystem, including voltage, current, and temperature measurements.
Signed-off-by: Colin Huang <u8813345@gmail.com>
---
Documentation/hwmon/e50sn12051.rst | 81 ++++++++++++++++++++++++++++++++++++++
Documentation/hwmon/index.rst | 1 +
2 files changed, 82 insertions(+)
diff --git a/Documentation/hwmon/e50sn12051.rst b/Documentation/hwmon/e50sn12051.rst
new file mode 100644
index 000000000000..cbbfa7895d82
--- /dev/null
+++ b/Documentation/hwmon/e50sn12051.rst
@@ -0,0 +1,81 @@
+.. SPDX-License-Identifier: GPL-2.0-or-later
+
+Kernel driver e50sn12051
+========================
+
+Supported chips:
+
+ * Delta E50SN12051
+
+ Prefix: 'e50sn12051'
+
+ Addresses scanned: -
+
+ Datasheet:
+
+ Provided by Delta upon request and NDA
+
+Description
+-----------
+
+E50SN12051 is a 600W non-isolated 1/8th brick DC-DC power module.
+
+This driver supports the E50SN12051 PMBus compliant monitor device.
+
+The device provides measurements for:
+
+* Input voltage
+* Output current
+* Output voltage
+* Temperature
+
+The driver is based on the PMBus core and exposes standard hwmon
+sysfs attributes.
+
+Sysfs attributes
+----------------
+
+======================= =======================================================
+curr1_label "iout1"
+curr1_crit Critical maximum current.
+ From IOUT_OC_FAULT_LIMIT register.
+curr1_crit_alarm Current critical high alarm.
+ From IOUT_OC_FAULT status.
+curr1_input Measured current.
+ From READ_IOUT register.
+curr1_max Maximum current.
+ From IOUT_OC_WARN_LIMIT register.
+curr1_max_alarm Current high alarm.
+ From IOUT_OC_WARN_LIMIT status.
+
+in1_label "vin"
+in1_alarm Input voltage alarm.
+in1_input Measured voltage.
+ From READ_VIN register.
+
+in2_label "vout1"
+in2_crit Critical maximum voltage.
+ From VOUT_OV_FAULT_LIMIT register.
+in2_crit_alarm Voltage critical high alarm.
+ From VOLTAGE_OV_FAULT status.
+in2_input Measured voltage.
+ From READ_VOUT register.
+in2_max Maximum voltage.
+ From VOUT_OV_WARN_LIMIT register.
+in2_max_alarm Voltage high alarm.
+ From VOLTAGE_OV_WARNING status.
+
+temp1_crit Critical high temperature.
+ From OT_FAULT_LIMIT register.
+temp1_crit_alarm Module temperature critical high alarm.
+ Set by comparing READ_TEMPERATURE_1 with OT_FAULT_LIMIT
+ if TEMP_OT_FAULT status is set.
+temp1_input Measured module's hot spot temperature.
+ From READ_TEMPERATURE_1 register.
+temp1_max Maximum temperature.
+ From OT_WARN_LIMIT register.
+temp1_max_alarm Module temperature high alarm.
+ Set by comparing READ_TEMPERATURE_1 with OT_WARN_LIMIT if
+ TEMP_OT_WARNING status is set.
+======================= =======================================================
+
diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst
index 8b655e5d6b68..ba08655fc496 100644
--- a/Documentation/hwmon/index.rst
+++ b/Documentation/hwmon/index.rst
@@ -68,6 +68,7 @@ Hardware Monitoring Kernel Drivers
drivetemp
ds1621
ds620
+ e50sn12051
emc1403
emc2103
emc2305
--
2.34.1
^ permalink raw reply related
* [PATCH 0/3] add support for Delta E50SN12051
From: Colin Huang via B4 Relay @ 2026-04-29 9:41 UTC (permalink / raw)
To: Guenter Roeck, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Kevin Chang, Jonathan Corbet, Shuah Khan
Cc: linux-hwmon, devicetree, linux-kernel, linux-doc, Colin Huang,
Colin Huang
Delta E50SN12051 is a 600W non-isolated 1/8th brick DC-DC power module.
With this series applied, voltage, current and temperature sensors can
be exposed through the standard hwmon interface.
Changes in this series:
- Introduce device-tree binding documentation for the new device
- Add hwmon documentation describing the supported sensors
- Add PMBus driver support for Delta E50SN12051
Signed-off-by: Colin Huang <u8813345@gmail.com>
---
Colin Huang (3):
dt-bindings: hwmon: pmbus: add Delta E50SN12051 binding
Documentation/hwmon: add Delta E50SN12051 documentation
hwmon: (pmbus) add support for Delta E50SN12051
.../bindings/hwmon/pmbus/delta,e50sn12051.yaml | 40 +++++++++++
Documentation/hwmon/e50sn12051.rst | 81 ++++++++++++++++++++++
Documentation/hwmon/index.rst | 1 +
drivers/hwmon/pmbus/Kconfig | 9 +++
drivers/hwmon/pmbus/Makefile | 1 +
drivers/hwmon/pmbus/e50sn12051.c | 60 ++++++++++++++++
6 files changed, 192 insertions(+)
---
base-commit: b584e7d50af502462349910bf4ed30057620b69f
change-id: 20260316-add-e50sn12051-ad828ad6777a
Best regards,
--
Colin Huang <u8813345@gmail.com>
^ permalink raw reply
* Re: [PATCH net-next 1/2] dpll: add pin operational state
From: Jiri Pirko @ 2026-04-29 8:55 UTC (permalink / raw)
To: Ivan Vecera
Cc: netdev, Arkadiusz Kubalewski, David S. Miller, Donald Hunter,
Eric Dumazet, Jakub Kicinski, Jonathan Corbet, Michal Schmidt,
Paolo Abeni, Pasi Vaananen, Petr Oros, Prathosh Satish,
Shuah Khan, Simon Horman, Vadim Fedorenko, linux-doc,
linux-kernel
In-Reply-To: <20260428154907.2820654-2-ivecera@redhat.com>
Tue, Apr 28, 2026 at 05:49:06PM +0200, ivecera@redhat.com wrote:
>Add pin-operstate enum and operstate_on_dpll_get callback to report
>the actual hardware status of a pin with respect to its parent DPLL
>device. Unlike pin-state (which reflects administrative intent set
>by the user), operstate reflects what the hardware is actually doing.
>
>Defined operational states:
> - active: pin is qualified and actively used by the DPLL
> - standby: pin is qualified but not actively used by the DPLL
> - no-signal: pin does not have a valid signal
> - qual-failed: pin signal failed qualification
>
>The operstate is reported inside the pin-parent-device nested
>attribute alongside the existing state and phase-offset attributes.
>
>Signed-off-by: Ivan Vecera <ivecera@redhat.com>
Looks great.
Reviewed-by: Jiri Pirko <jiri@nvidia.com>
Thanks!
^ permalink raw reply
* Re: [PATCH v4 3/3] Documentation: deprecated.rst: kmalloc-family: mark argument as optional
From: Vlastimil Babka @ 2026-04-29 8:01 UTC (permalink / raw)
To: Manuel Ebner, Jonathan Corbet, Shuah Khan, linux-doc, Kees Cook,
linux-kernel
Cc: workflows, linux-mm, Geert Uytterhoeven
In-Reply-To: <20260429072704.311603-2-manuelebner@mailbox.org>
On 4/29/26 09:27, Manuel Ebner wrote:
> put the optional argument (gfp) in square brackets
> add default value = GFP_KERNEL
>
> eg. ptr = kmalloc_obj(*ptr, gfp);
> -> ptr = kmalloc_obj(*ptr [, gfp] );
>
> Signed-off-by: Manuel Ebner <manuelebner@mailbox.org>
Acked-by: Vlastimil Babka (SUSE) <vbabka@kernel.org>
> ---
> Documentation/process/deprecated.rst | 15 ++++++++-------
> 1 file changed, 8 insertions(+), 7 deletions(-)
>
> diff --git a/Documentation/process/deprecated.rst b/Documentation/process/deprecated.rst
> index fed56864d036..ac75b7ecac47 100644
> --- a/Documentation/process/deprecated.rst
> +++ b/Documentation/process/deprecated.rst
> @@ -392,13 +392,14 @@ allocations. For example, these open coded assignments::
>
> become, respectively::
>
> - ptr = kmalloc_obj(*ptr, gfp);
> - ptr = kzalloc_obj(*ptr, gfp);
> - ptr = kmalloc_objs(*ptr, count, gfp);
> - ptr = kzalloc_objs(*ptr, count, gfp);
> - ptr = kmalloc_flex(*ptr, flex_member, count, gfp);
> - __auto_type ptr = kmalloc_obj(struct foo, gfp);
> -
> + ptr = kmalloc_obj(*ptr [, gfp] );
> + ptr = kzalloc_obj(*ptr [, gfp] );
> + ptr = kmalloc_objs(*ptr, count [, gfp] );
> + ptr = kzalloc_objs(*ptr, count [, gfp] );
> + ptr = kmalloc_flex(*ptr, flex_member, count [, gfp] );
> + __auto_type ptr = kmalloc_obj(struct foo [, gfp] );
> +
> +The argument gfp is optional, the default value is GFP_KERNEL.
> If `ptr->flex_member` is annotated with __counted_by(), the allocation
> will automatically fail if `count` is larger than the maximum
> representable value that can be stored in the counter member associated
^ permalink raw reply
* Re: [PATCH v4 2/3] Documentation: RCU: adopt new coding style of type-aware kmalloc-family
From: Vlastimil Babka @ 2026-04-29 8:01 UTC (permalink / raw)
To: Manuel Ebner, Jonathan Corbet, Shuah Khan, linux-doc, rcu
Cc: Kees Cook, linux-mm, Paul E . McKenney
In-Reply-To: <20260429072320.310817-2-manuelebner@mailbox.org>
On 4/29/26 09:23, Manuel Ebner wrote:
> Update Documentation/RCU/* to reflect new type-aware kmalloc-family
> as suggested in commit 2932ba8d9c99 ("slab: Introduce kmalloc_obj()
> and family")
>
> ptr = kmalloc(sizeof(*ptr), gfp);
> -> ptr = kmalloc_obj(*ptr);
>
> Signed-off-by: Manuel Ebner <manuelebner@mailbox.org>
> Acked-by: Paul E. McKenney <paulmck@kernel.org>
Acked-by: Vlastimil Babka (SUSE) <vbabka@kernel.org>
^ permalink raw reply
* Re: [PATCH v4 1/3] Documentation: adopt new coding style of type-aware kmalloc-family
From: Vlastimil Babka @ 2026-04-29 8:00 UTC (permalink / raw)
To: Manuel Ebner, Jonathan Corbet, Shuah Khan, linux-doc
Cc: Kees Cook, linux-kernel, workflows, linux-sound, linux-media,
linux-mm
In-Reply-To: <20260429071445.309733-2-manuelebner@mailbox.org>
On 4/29/26 09:14, Manuel Ebner wrote:
> Update the documentation to reflect new type-aware kmalloc-family as
> suggested in commit 2932ba8d9c99 ("slab: Introduce kmalloc_obj()
> and family")
>
> ptr = kmalloc(sizeof(*ptr), gfp);
> -> ptr = kmalloc_obj(*ptr);
> ptr = kmalloc(sizeof(struct some_obj_name), gfp);
> -> ptr = kmalloc_obj(*ptr);
> ptr = kzalloc(sizeof(*ptr), gfp);
> -> ptr = kzalloc_obj(*ptr);
> ptr = kmalloc_array(count, sizeof(*ptr), gfp);
> -> ptr = kmalloc_objs(*ptr, count);
> ptr = kcalloc(count, sizeof(*ptr), gfp);
> -> ptr = kzalloc_objs(*ptr, count);
>
> Signed-off-by: Manuel Ebner <manuelebner@mailbox.org>
Acked-by: Vlastimil Babka (SUSE) <vbabka@kernel.org>
^ permalink raw reply
* Re: [PATCH v2 1/2] dt-bindings: trivial: Add LX1308 support
From: Krzysztof Kozlowski @ 2026-04-29 7:34 UTC (permalink / raw)
To: Brian Chiang
Cc: Guenter Roeck, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Jonathan Corbet, Shuah Khan, linux-hwmon, devicetree,
linux-kernel, linux-doc
In-Reply-To: <20260428-add-support-lx1308-v2-1-90f115954143@inventec.com>
On Tue, Apr 28, 2026 at 12:19:28PM +0000, Brian Chiang wrote:
> Add device tree bindings for the Luxshare LX1308, a high-efficiency
> 12V 860W DC/DC power module with PMBus interface.
>
> Signed-off-by: Brian Chiang <chiang.brian@inventec.com>
> ---
> Documentation/devicetree/bindings/trivial-devices.yaml | 2 ++
> Documentation/devicetree/bindings/vendor-prefixes.yaml | 2 ++
> 2 files changed, 4 insertions(+)
Acked-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
Best regards,
Krzysztof
^ permalink raw reply
* [PATCH v4 3/3] Documentation: deprecated.rst: kmalloc-family: mark argument as optional
From: Manuel Ebner @ 2026-04-29 7:27 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan, linux-doc, Kees Cook, linux-kernel
Cc: workflows, linux-mm, Geert Uytterhoeven, Manuel Ebner
In-Reply-To: <20260429070759.309110-3-manuelebner@mailbox.org>
put the optional argument (gfp) in square brackets
add default value = GFP_KERNEL
eg. ptr = kmalloc_obj(*ptr, gfp);
-> ptr = kmalloc_obj(*ptr [, gfp] );
Signed-off-by: Manuel Ebner <manuelebner@mailbox.org>
---
Documentation/process/deprecated.rst | 15 ++++++++-------
1 file changed, 8 insertions(+), 7 deletions(-)
diff --git a/Documentation/process/deprecated.rst b/Documentation/process/deprecated.rst
index fed56864d036..ac75b7ecac47 100644
--- a/Documentation/process/deprecated.rst
+++ b/Documentation/process/deprecated.rst
@@ -392,13 +392,14 @@ allocations. For example, these open coded assignments::
become, respectively::
- ptr = kmalloc_obj(*ptr, gfp);
- ptr = kzalloc_obj(*ptr, gfp);
- ptr = kmalloc_objs(*ptr, count, gfp);
- ptr = kzalloc_objs(*ptr, count, gfp);
- ptr = kmalloc_flex(*ptr, flex_member, count, gfp);
- __auto_type ptr = kmalloc_obj(struct foo, gfp);
-
+ ptr = kmalloc_obj(*ptr [, gfp] );
+ ptr = kzalloc_obj(*ptr [, gfp] );
+ ptr = kmalloc_objs(*ptr, count [, gfp] );
+ ptr = kzalloc_objs(*ptr, count [, gfp] );
+ ptr = kmalloc_flex(*ptr, flex_member, count [, gfp] );
+ __auto_type ptr = kmalloc_obj(struct foo [, gfp] );
+
+The argument gfp is optional, the default value is GFP_KERNEL.
If `ptr->flex_member` is annotated with __counted_by(), the allocation
will automatically fail if `count` is larger than the maximum
representable value that can be stored in the counter member associated
--
2.53.0
^ permalink raw reply related
* [PATCH v4 2/3] Documentation: RCU: adopt new coding style of type-aware kmalloc-family
From: Manuel Ebner @ 2026-04-29 7:23 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan, linux-doc, rcu
Cc: Kees Cook, linux-mm, Paul E . McKenney, Manuel Ebner
In-Reply-To: <20260429070759.309110-3-manuelebner@mailbox.org>
Update Documentation/RCU/* to reflect new type-aware kmalloc-family
as suggested in commit 2932ba8d9c99 ("slab: Introduce kmalloc_obj()
and family")
ptr = kmalloc(sizeof(*ptr), gfp);
-> ptr = kmalloc_obj(*ptr);
Signed-off-by: Manuel Ebner <manuelebner@mailbox.org>
Acked-by: Paul E. McKenney <paulmck@kernel.org>
---
Acked-by see
https://lore.kernel.org/linux-doc/7a49fee0-09c8-4a48-9506-d9172ef024b0@paulmck-laptop/
Documentation/RCU/Design/Requirements/Requirements.rst | 6 +++---
Documentation/RCU/listRCU.rst | 2 +-
Documentation/RCU/whatisRCU.rst | 4 ++--
3 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/Documentation/RCU/Design/Requirements/Requirements.rst b/Documentation/RCU/Design/Requirements/Requirements.rst
index b5cdbba3ec2e..faca5a9c8c12 100644
--- a/Documentation/RCU/Design/Requirements/Requirements.rst
+++ b/Documentation/RCU/Design/Requirements/Requirements.rst
@@ -206,7 +206,7 @@ non-\ ``NULL``, locklessly accessing the ``->a`` and ``->b`` fields.
1 bool add_gp_buggy(int a, int b)
2 {
- 3 p = kmalloc(sizeof(*p), GFP_KERNEL);
+ 3 p = kmalloc_obj(*p);
4 if (!p)
5 return -ENOMEM;
6 spin_lock(&gp_lock);
@@ -228,7 +228,7 @@ their rights to reorder this code as follows:
1 bool add_gp_buggy_optimized(int a, int b)
2 {
- 3 p = kmalloc(sizeof(*p), GFP_KERNEL);
+ 3 p = kmalloc_obj(*p);
4 if (!p)
5 return -ENOMEM;
6 spin_lock(&gp_lock);
@@ -264,7 +264,7 @@ shows an example of insertion:
1 bool add_gp(int a, int b)
2 {
- 3 p = kmalloc(sizeof(*p), GFP_KERNEL);
+ 3 p = kmalloc_obj(*p);
4 if (!p)
5 return -ENOMEM;
6 spin_lock(&gp_lock);
diff --git a/Documentation/RCU/listRCU.rst b/Documentation/RCU/listRCU.rst
index d8bb98623c12..48c7272a4ccc 100644
--- a/Documentation/RCU/listRCU.rst
+++ b/Documentation/RCU/listRCU.rst
@@ -276,7 +276,7 @@ The RCU version of audit_upd_rule() is as follows::
list_for_each_entry(e, list, list) {
if (!audit_compare_rule(rule, &e->rule)) {
- ne = kmalloc(sizeof(*entry), GFP_ATOMIC);
+ ne = kmalloc_obj(*entry, GFP_ATOMIC);
if (ne == NULL)
return -ENOMEM;
audit_copy_rule(&ne->rule, &e->rule);
diff --git a/Documentation/RCU/whatisRCU.rst b/Documentation/RCU/whatisRCU.rst
index a1582bd653d1..770aab8ea36a 100644
--- a/Documentation/RCU/whatisRCU.rst
+++ b/Documentation/RCU/whatisRCU.rst
@@ -468,7 +468,7 @@ uses of RCU may be found in listRCU.rst and NMI-RCU.rst.
struct foo *new_fp;
struct foo *old_fp;
- new_fp = kmalloc(sizeof(*new_fp), GFP_KERNEL);
+ new_fp = kmalloc_obj(*new_fp);
spin_lock(&foo_mutex);
old_fp = rcu_dereference_protected(gbl_foo, lockdep_is_held(&foo_mutex));
*new_fp = *old_fp;
@@ -570,7 +570,7 @@ The foo_update_a() function might then be written as follows::
struct foo *new_fp;
struct foo *old_fp;
- new_fp = kmalloc(sizeof(*new_fp), GFP_KERNEL);
+ new_fp = kmalloc_obj(*new_fp);
spin_lock(&foo_mutex);
old_fp = rcu_dereference_protected(gbl_foo, lockdep_is_held(&foo_mutex));
*new_fp = *old_fp;
--
2.53.0
^ permalink raw reply related
* [PATCH v4 1/3] Documentation: adopt new coding style of type-aware kmalloc-family
From: Manuel Ebner @ 2026-04-29 7:14 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan, linux-doc
Cc: Kees Cook, linux-kernel, workflows, linux-sound, linux-media,
linux-mm, Manuel Ebner
In-Reply-To: <20260429070759.309110-3-manuelebner@mailbox.org>
Update the documentation to reflect new type-aware kmalloc-family as
suggested in commit 2932ba8d9c99 ("slab: Introduce kmalloc_obj()
and family")
ptr = kmalloc(sizeof(*ptr), gfp);
-> ptr = kmalloc_obj(*ptr);
ptr = kmalloc(sizeof(struct some_obj_name), gfp);
-> ptr = kmalloc_obj(*ptr);
ptr = kzalloc(sizeof(*ptr), gfp);
-> ptr = kzalloc_obj(*ptr);
ptr = kmalloc_array(count, sizeof(*ptr), gfp);
-> ptr = kmalloc_objs(*ptr, count);
ptr = kcalloc(count, sizeof(*ptr), gfp);
-> ptr = kzalloc_objs(*ptr, count);
Signed-off-by: Manuel Ebner <manuelebner@mailbox.org>
---
Documentation/core-api/kref.rst | 4 ++--
Documentation/core-api/list.rst | 4 ++--
Documentation/driver-api/mailbox.rst | 4 ++--
Documentation/driver-api/media/v4l2-fh.rst | 2 +-
Documentation/kernel-hacking/locking.rst | 4 ++--
Documentation/locking/locktypes.rst | 4 ++--
Documentation/process/coding-style.rst | 8 ++++----
.../sound/kernel-api/writing-an-alsa-driver.rst | 12 ++++++------
Documentation/spi/spi-summary.rst | 4 ++--
.../translations/it_IT/kernel-hacking/locking.rst | 4 ++--
.../translations/it_IT/locking/locktypes.rst | 4 ++--
.../translations/it_IT/process/coding-style.rst | 2 +-
.../translations/sp_SP/process/coding-style.rst | 2 +-
Documentation/translations/zh_CN/core-api/kref.rst | 4 ++--
.../translations/zh_CN/process/coding-style.rst | 2 +-
.../zh_CN/video4linux/v4l2-framework.txt | 2 +-
.../translations/zh_TW/process/coding-style.rst | 2 +-
18 files changed, 36 insertions(+), 36 deletions(-)
diff --git a/Documentation/core-api/kref.rst b/Documentation/core-api/kref.rst
index 8db9ff03d952..1c14c036699d 100644
--- a/Documentation/core-api/kref.rst
+++ b/Documentation/core-api/kref.rst
@@ -40,7 +40,7 @@ kref_init as so::
struct my_data *data;
- data = kmalloc(sizeof(*data), GFP_KERNEL);
+ data = kmalloc_obj(*data);
if (!data)
return -ENOMEM;
kref_init(&data->refcount);
@@ -100,7 +100,7 @@ thread to process::
int rv = 0;
struct my_data *data;
struct task_struct *task;
- data = kmalloc(sizeof(*data), GFP_KERNEL);
+ data = kmalloc_obj(*data);
if (!data)
return -ENOMEM;
kref_init(&data->refcount);
diff --git a/Documentation/core-api/list.rst b/Documentation/core-api/list.rst
index 241464ca0549..86cd0a1b77ea 100644
--- a/Documentation/core-api/list.rst
+++ b/Documentation/core-api/list.rst
@@ -112,7 +112,7 @@ list:
/* State 1 */
- grock = kzalloc(sizeof(*grock), GFP_KERNEL);
+ grock = kzalloc_obj(*grock);
if (!grock)
return -ENOMEM;
grock->name = "Grock";
@@ -123,7 +123,7 @@ list:
/* State 2 */
- dimitri = kzalloc(sizeof(*dimitri), GFP_KERNEL);
+ dimitri = kzalloc_obj(*dimitri);
if (!dimitri)
return -ENOMEM;
dimitri->name = "Dimitri";
diff --git a/Documentation/driver-api/mailbox.rst b/Documentation/driver-api/mailbox.rst
index 463dd032b96c..4bcd73a99115 100644
--- a/Documentation/driver-api/mailbox.rst
+++ b/Documentation/driver-api/mailbox.rst
@@ -87,8 +87,8 @@ a message and a callback function to the API and return immediately).
struct async_pkt ap;
struct sync_pkt sp;
- dc_sync = kzalloc(sizeof(*dc_sync), GFP_KERNEL);
- dc_async = kzalloc(sizeof(*dc_async), GFP_KERNEL);
+ dc_sync = kzalloc_obj(*dc_sync);
+ dc_async = kzalloc_obj(*dc_async);
/* Populate non-blocking mode client */
dc_async->cl.dev = &pdev->dev;
diff --git a/Documentation/driver-api/media/v4l2-fh.rst b/Documentation/driver-api/media/v4l2-fh.rst
index a934caa483a4..38319130ebf5 100644
--- a/Documentation/driver-api/media/v4l2-fh.rst
+++ b/Documentation/driver-api/media/v4l2-fh.rst
@@ -42,7 +42,7 @@ Example:
...
- my_fh = kzalloc(sizeof(*my_fh), GFP_KERNEL);
+ my_fh = kzalloc_obj(*my_fh);
...
diff --git a/Documentation/kernel-hacking/locking.rst b/Documentation/kernel-hacking/locking.rst
index dff0646a717b..d02e62367c4f 100644
--- a/Documentation/kernel-hacking/locking.rst
+++ b/Documentation/kernel-hacking/locking.rst
@@ -442,7 +442,7 @@ to protect the cache and all the objects within it. Here's the code::
{
struct object *obj;
- if ((obj = kmalloc(sizeof(*obj), GFP_KERNEL)) == NULL)
+ if ((obj = kmalloc_obj(*obj)) == NULL)
return -ENOMEM;
strscpy(obj->name, name, sizeof(obj->name));
@@ -517,7 +517,7 @@ which are taken away, and the ``+`` are lines which are added.
struct object *obj;
+ unsigned long flags;
- if ((obj = kmalloc(sizeof(*obj), GFP_KERNEL)) == NULL)
+ if ((obj = kmalloc_obj(*obj)) == NULL)
return -ENOMEM;
@@ -63,30 +64,33 @@
obj->id = id;
diff --git a/Documentation/locking/locktypes.rst b/Documentation/locking/locktypes.rst
index 37b6a5670c2f..ac1ad722a9e7 100644
--- a/Documentation/locking/locktypes.rst
+++ b/Documentation/locking/locktypes.rst
@@ -498,7 +498,7 @@ allocating memory. Thus, on a non-PREEMPT_RT kernel the following code
works perfectly::
raw_spin_lock(&lock);
- p = kmalloc(sizeof(*p), GFP_ATOMIC);
+ p = kmalloc_obj(*p, GFP_ATOMIC);
But this code fails on PREEMPT_RT kernels because the memory allocator is
fully preemptible and therefore cannot be invoked from truly atomic
@@ -507,7 +507,7 @@ while holding normal non-raw spinlocks because they do not disable
preemption on PREEMPT_RT kernels::
spin_lock(&lock);
- p = kmalloc(sizeof(*p), GFP_ATOMIC);
+ p = kmalloc_obj(*p, GFP_ATOMIC);
bit spinlocks
diff --git a/Documentation/process/coding-style.rst b/Documentation/process/coding-style.rst
index 35b381230f6e..a3bf75dc7c88 100644
--- a/Documentation/process/coding-style.rst
+++ b/Documentation/process/coding-style.rst
@@ -936,7 +936,7 @@ used.
---------------------
The kernel provides the following general purpose memory allocators:
-kmalloc(), kzalloc(), kmalloc_array(), kcalloc(), vmalloc(), and
+kmalloc(), kzalloc(), kmalloc_objs(), kzalloc_objs(), vmalloc(), and
vzalloc(). Please refer to the API documentation for further information
about them. :ref:`Documentation/core-api/memory-allocation.rst
<memory_allocation>`
@@ -945,7 +945,7 @@ The preferred form for passing a size of a struct is the following:
.. code-block:: c
- p = kmalloc(sizeof(*p), ...);
+ p = kmalloc_obj(*p, ...);
The alternative form where struct name is spelled out hurts readability and
introduces an opportunity for a bug when the pointer variable type is changed
@@ -959,13 +959,13 @@ The preferred form for allocating an array is the following:
.. code-block:: c
- p = kmalloc_array(n, sizeof(...), ...);
+ p = kmalloc_objs(*ptr, n, ...);
The preferred form for allocating a zeroed array is the following:
.. code-block:: c
- p = kcalloc(n, sizeof(...), ...);
+ p = kzalloc_objs(*ptr, n, ...);
Both forms check for overflow on the allocation size n * sizeof(...),
and return NULL if that occurred.
diff --git a/Documentation/sound/kernel-api/writing-an-alsa-driver.rst b/Documentation/sound/kernel-api/writing-an-alsa-driver.rst
index 895752cbcedd..12433612aa9c 100644
--- a/Documentation/sound/kernel-api/writing-an-alsa-driver.rst
+++ b/Documentation/sound/kernel-api/writing-an-alsa-driver.rst
@@ -266,7 +266,7 @@ to details explained in the following section.
....
/* allocate a chip-specific data with zero filled */
- chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+ chip = kzalloc_obj(*chip);
if (chip == NULL)
return -ENOMEM;
@@ -628,7 +628,7 @@ After allocating a card instance via :c:func:`snd_card_new()`
err = snd_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE,
0, &card);
.....
- chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+ chip = kzalloc_obj(*chip);
The chip record should have the field to hold the card pointer at least,
@@ -747,7 +747,7 @@ destructor and PCI entries. Example code is shown first, below::
return -ENXIO;
}
- chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+ chip = kzalloc_obj(*chip);
if (chip == NULL) {
pci_disable_device(pci);
return -ENOMEM;
@@ -1737,7 +1737,7 @@ callback::
{
struct my_pcm_data *data;
....
- data = kmalloc(sizeof(*data), GFP_KERNEL);
+ data = kmalloc_obj(*data);
substream->runtime->private_data = data;
....
}
@@ -3301,7 +3301,7 @@ You can then pass any pointer value to the ``private_data``. If you
assign private data, you should define a destructor, too. The
destructor function is set in the ``private_free`` field::
- struct mydata *p = kmalloc(sizeof(*p), GFP_KERNEL);
+ struct mydata *p = kmalloc_obj(*p);
hw->private_data = p;
hw->private_free = mydata_free;
@@ -3833,7 +3833,7 @@ chip data individually::
err = snd_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE,
0, &card);
....
- chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+ chip = kzalloc_obj(*chip);
....
card->private_data = chip;
....
diff --git a/Documentation/spi/spi-summary.rst b/Documentation/spi/spi-summary.rst
index 6e21e6f86912..7ad6af76c247 100644
--- a/Documentation/spi/spi-summary.rst
+++ b/Documentation/spi/spi-summary.rst
@@ -249,7 +249,7 @@ And SOC-specific utility code might look something like::
{
struct mysoc_spi_data *pdata2;
- pdata2 = kmalloc(sizeof *pdata2, GFP_KERNEL);
+ pdata2 = kmalloc_obj(*pdata2);
*pdata2 = pdata;
...
if (n == 2) {
@@ -373,7 +373,7 @@ a bus (appearing under /sys/class/spi_master).
return -ENODEV;
/* get memory for driver's per-chip state */
- chip = kzalloc(sizeof *chip, GFP_KERNEL);
+ chip = kzalloc(*chip);
if (!chip)
return -ENOMEM;
spi_set_drvdata(spi, chip);
diff --git a/Documentation/translations/it_IT/kernel-hacking/locking.rst b/Documentation/translations/it_IT/kernel-hacking/locking.rst
index 4c21cf60f775..acca89a3743a 100644
--- a/Documentation/translations/it_IT/kernel-hacking/locking.rst
+++ b/Documentation/translations/it_IT/kernel-hacking/locking.rst
@@ -462,7 +462,7 @@ e tutti gli oggetti che contiene. Ecco il codice::
{
struct object *obj;
- if ((obj = kmalloc(sizeof(*obj), GFP_KERNEL)) == NULL)
+ if ((obj = kmalloc_obj(*obj)) == NULL)
return -ENOMEM;
strscpy(obj->name, name, sizeof(obj->name));
@@ -537,7 +537,7 @@ sono quelle rimosse, mentre quelle ``+`` sono quelle aggiunte.
struct object *obj;
+ unsigned long flags;
- if ((obj = kmalloc(sizeof(*obj), GFP_KERNEL)) == NULL)
+ if ((obj = kmalloc_obj(*obj)) == NULL)
return -ENOMEM;
@@ -63,30 +64,33 @@
obj->id = id;
diff --git a/Documentation/translations/it_IT/locking/locktypes.rst b/Documentation/translations/it_IT/locking/locktypes.rst
index 1c7056283b9d..d5fa36aa05cc 100644
--- a/Documentation/translations/it_IT/locking/locktypes.rst
+++ b/Documentation/translations/it_IT/locking/locktypes.rst
@@ -488,7 +488,7 @@ o rwlock_t. Per esempio, la sezione critica non deve fare allocazioni di
memoria. Su un kernel non-PREEMPT_RT il seguente codice funziona perfettamente::
raw_spin_lock(&lock);
- p = kmalloc(sizeof(*p), GFP_ATOMIC);
+ p = kmalloc_obj(*p, GFP_ATOMIC);
Ma lo stesso codice non funziona su un kernel PREEMPT_RT perché l'allocatore di
memoria può essere oggetto di prelazione e quindi non può essere chiamato in un
@@ -497,7 +497,7 @@ trattiene un blocco *non-raw* perché non disabilitano la prelazione sui kernel
PREEMPT_RT::
spin_lock(&lock);
- p = kmalloc(sizeof(*p), GFP_ATOMIC);
+ p = kmalloc_obj(*p, GFP_ATOMIC);
bit spinlocks
diff --git a/Documentation/translations/it_IT/process/coding-style.rst b/Documentation/translations/it_IT/process/coding-style.rst
index c0dc786b8474..2a499412a2e3 100644
--- a/Documentation/translations/it_IT/process/coding-style.rst
+++ b/Documentation/translations/it_IT/process/coding-style.rst
@@ -943,7 +943,7 @@ Il modo preferito per passare la dimensione di una struttura è il seguente:
.. code-block:: c
- p = kmalloc(sizeof(*p), ...);
+ p = kmalloc_obj(*p, ...);
La forma alternativa, dove il nome della struttura viene scritto interamente,
peggiora la leggibilità e introduce possibili bachi quando il tipo di
diff --git a/Documentation/translations/sp_SP/process/coding-style.rst b/Documentation/translations/sp_SP/process/coding-style.rst
index 7d63aa8426e6..44c93d5f6beb 100644
--- a/Documentation/translations/sp_SP/process/coding-style.rst
+++ b/Documentation/translations/sp_SP/process/coding-style.rst
@@ -955,7 +955,7 @@ La forma preferida para pasar el tamaño de una estructura es la siguiente:
.. code-block:: c
- p = kmalloc(sizeof(*p), ...);
+ p = kmalloc_obj(*p, ...);
La forma alternativa donde se deletrea el nombre de la estructura perjudica
la legibilidad, y presenta una oportunidad para un error cuando se cambia
diff --git a/Documentation/translations/zh_CN/core-api/kref.rst b/Documentation/translations/zh_CN/core-api/kref.rst
index b9902af310c5..fcff01e99852 100644
--- a/Documentation/translations/zh_CN/core-api/kref.rst
+++ b/Documentation/translations/zh_CN/core-api/kref.rst
@@ -52,7 +52,7 @@ kref可以出现在数据结构体中的任何地方。
struct my_data *data;
- data = kmalloc(sizeof(*data), GFP_KERNEL);
+ data = kmalloc_obj(*data);
if (!data)
return -ENOMEM;
kref_init(&data->refcount);
@@ -106,7 +106,7 @@ Kref规则
int rv = 0;
struct my_data *data;
struct task_struct *task;
- data = kmalloc(sizeof(*data), GFP_KERNEL);
+ data = kmalloc_obj(*data);
if (!data)
return -ENOMEM;
kref_init(&data->refcount);
diff --git a/Documentation/translations/zh_CN/process/coding-style.rst b/Documentation/translations/zh_CN/process/coding-style.rst
index 5a342a024c01..55d5da974d89 100644
--- a/Documentation/translations/zh_CN/process/coding-style.rst
+++ b/Documentation/translations/zh_CN/process/coding-style.rst
@@ -813,7 +813,7 @@ Documentation/translations/zh_CN/core-api/memory-allocation.rst 。
.. code-block:: c
- p = kmalloc(sizeof(*p), ...);
+ p = kmalloc_obj(*p, ...);
另外一种传递方式中,sizeof 的操作数是结构体的名字,这样会降低可读性,并且可能
会引入 bug。有可能指针变量类型被改变时,而对应的传递给内存分配函数的 sizeof
diff --git a/Documentation/translations/zh_CN/video4linux/v4l2-framework.txt b/Documentation/translations/zh_CN/video4linux/v4l2-framework.txt
index f0be21a60a0f..ba43c5c4797c 100644
--- a/Documentation/translations/zh_CN/video4linux/v4l2-framework.txt
+++ b/Documentation/translations/zh_CN/video4linux/v4l2-framework.txt
@@ -799,7 +799,7 @@ int my_open(struct file *file)
...
- my_fh = kzalloc(sizeof(*my_fh), GFP_KERNEL);
+ my_fh = kzalloc_obj(*my_fh);
...
diff --git a/Documentation/translations/zh_TW/process/coding-style.rst b/Documentation/translations/zh_TW/process/coding-style.rst
index e2ba97b3d8bb..63c78982a1af 100644
--- a/Documentation/translations/zh_TW/process/coding-style.rst
+++ b/Documentation/translations/zh_TW/process/coding-style.rst
@@ -827,7 +827,7 @@ Documentation/translations/zh_CN/core-api/memory-allocation.rst 。
.. code-block:: c
- p = kmalloc(sizeof(*p), ...);
+ p = kmalloc_obj(*p, ...);
另外一種傳遞方式中,sizeof 的操作數是結構體的名字,這樣會降低可讀性,並且可能
會引入 bug。有可能指針變量類型被改變時,而對應的傳遞給內存分配函數的 sizeof
--
2.53.0
^ permalink raw reply related
* [PATCH v4 0/3] Documentation: adopt new coding style of type-aware kmalloc-family
From: Manuel Ebner @ 2026-04-29 7:08 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan, linux-doc, Kees Cook
Cc: linux-kernel, workflows, linux-sound, rcu, linux-media, linux-mm,
Manuel Ebner
Update the documentation to reflect new type-aware kmalloc-family as
suggested in commit 2932ba8d9c99 ("slab: Introduce kmalloc_obj()
and family")
[v3] -> [v4]:
state the default argument in deprecated.rst [3/3]
[v2] -> [v3]:
remove obvious wrong replacements in [1/3]
add Acked-by: Paul E. McKenney in [2/3]
change how to mark the optional argument in [3/3]
add recipants
--cc="linux-mm@kvack.org"
--to="Kees Cook"
--cc="Geert Uytterhoeven"
[v1] -> [v2]:
put RCU/* in a seperate patch [Patch 2/3]
Omit optional argument (GFP_KERNEL) as suggested by https://lwn.net/Articles/1062856/
deprecated.rst: change the argument gfp to optional [Patch 3/3]
Signed-off-by: Manuel Ebner <manuelebner@mailbox.org>
^ permalink raw reply
* [PATCH 11/11] Documentation: admin-guide: kdump: document linux,no-dump DT property
From: Chen Wandun @ 2026-04-29 6:58 UTC (permalink / raw)
To: kexec, linux-doc, linux-kernel, linux-arm-kernel, loongarch,
linux-riscv, devicetree
Cc: akpm, bhe, rppt, pasha.tatashin, pratyush, ruirui.yang, corbet,
skhan, catalin.marinas, will, chenhuacai, kernel, pjw, palmer,
aou, robh, saravanak, chenwandun, zhaomeijing, everyzhao
In-Reply-To: <20260429065831.1510858-1-chenwandun@lixiang.com>
Describe the new 'linux,no-dump' reserved-memory device tree property
and the automatic exclusion of /memreserve/ entries from the kdump
vmcore.
The section covers:
- The two mechanisms that exclude reserved memory from the vmcore
(firmware /memreserve/ entries and linux,no-dump child nodes).
- Intended use cases (firmware-owned GPU, DSP and modem carveouts).
- Interaction with the existing 'no-map' and 'reusable' flags, with
the silent-ignore precedence implemented by the kernel.
- Architectures honouring the hint (arm64, riscv, loongarch).
- An illustrative reserved-memory DTS snippet.
The DT binding for the property itself is maintained in dt-schema.
Signed-off-by: Chen Wandun <chenwandun@lixiang.com>
---
Documentation/admin-guide/kdump/kdump.rst | 59 +++++++++++++++++++++++
1 file changed, 59 insertions(+)
diff --git a/Documentation/admin-guide/kdump/kdump.rst b/Documentation/admin-guide/kdump/kdump.rst
index 7587caadbae1..c2246888e84d 100644
--- a/Documentation/admin-guide/kdump/kdump.rst
+++ b/Documentation/admin-guide/kdump/kdump.rst
@@ -600,6 +600,65 @@ with /sys/kernel/config/crash_dm_crypt_keys for setup,
3. After the dump-capture kerne get booted, restore the keys to user keyring
echo yes > /sys/kernel/crash_dm_crypt_keys/restore
+Excluding reserved memory regions from the vmcore (device tree)
+===============================================================
+
+On architectures that boot from a device tree and use kexec_file for
+kdump (arm64, riscv, loongarch), specific reserved memory regions can
+be excluded from the ELF PT_LOAD segments of the crash dump.
+
+Two mechanisms contribute to the exclusion:
+
+1) /memreserve/ entries from the FDT header.
+
+ These are firmware-level memory reservations with no associated
+ device tree node and therefore no driver-level description. Their
+ contents are typically firmware scratch areas that carry no value
+ for kernel crash analysis, so they are excluded from the vmcore
+ automatically.
+
+2) Reserved-memory nodes carrying the 'linux,no-dump' property.
+
+ Device tree authors can add this boolean hint to any
+ /reserved-memory child node to request that the kernel skip that
+ region when constructing the elfcorehdr. This is intended for
+ firmware-owned carveouts such as GPU, DSP and modem memory, whose
+ contents tend to significantly inflate the vmcore without aiding
+ kernel crash analysis.
+
+ Example::
+
+ reserved-memory {
+ #address-cells = <2>;
+ #size-cells = <2>;
+ ranges;
+
+ gpu_fw@a0000000 {
+ reg = <0x0 0xa0000000 0x0 0x01000000>;
+ no-map;
+ linux,no-dump;
+ };
+
+ modem_fw@b0000000 {
+ reg = <0x0 0xb0000000 0x0 0x02000000>;
+ linux,no-dump;
+ };
+ };
+
+Interaction with other reserved-memory flags:
+
+- 'no-map': the region is already absent from the kernel linear map,
+ so it does not appear in the vmcore to begin with. Combining
+ 'linux,no-dump' with 'no-map' is harmless but redundant.
+
+- 'reusable': the region is actively used by the kernel for movable
+ page allocations (CMA) and its contents are relevant to crash
+ analysis. 'linux,no-dump' is silently ignored on a reusable region.
+
+The property is an operating-system hint; DTBs that do not set it
+retain the legacy behaviour (all memory is dumped). Architectures
+that do not honour the hint simply ignore it.
+
Contact
=======
--
2.43.0
^ permalink raw reply related
* [PATCH 10/11] loongarch: kdump: exclude no-dump reserved memory regions from vmcore
From: Chen Wandun @ 2026-04-29 6:58 UTC (permalink / raw)
To: kexec, linux-doc, linux-kernel, linux-arm-kernel, loongarch,
linux-riscv, devicetree
Cc: akpm, bhe, rppt, pasha.tatashin, pratyush, ruirui.yang, corbet,
skhan, catalin.marinas, will, chenhuacai, kernel, pjw, palmer,
aou, robh, saravanak, chenwandun, zhaomeijing, everyzhao
In-Reply-To: <20260429065831.1510858-1-chenwandun@lixiang.com>
Apply the same no-dump reserved memory filtering to LoongArch kdump as
was done for arm64. Use of_reserved_mem_exclude_no_dump() to drop
flagged regions from the elfcorehdr PT_LOAD segments, and
of_reserved_mem_no_dump_nr_ranges() to pre-size the crash_mem array.
Signed-off-by: Chen Wandun <chenwandun@lixiang.com>
---
arch/loongarch/kernel/machine_kexec_file.c | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/arch/loongarch/kernel/machine_kexec_file.c b/arch/loongarch/kernel/machine_kexec_file.c
index 5584b798ba46..742fb9affcb9 100644
--- a/arch/loongarch/kernel/machine_kexec_file.c
+++ b/arch/loongarch/kernel/machine_kexec_file.c
@@ -14,6 +14,7 @@
#include <linux/kernel.h>
#include <linux/kexec.h>
#include <linux/memblock.h>
+#include <linux/of_reserved_mem.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/types.h>
@@ -67,6 +68,7 @@ static int prepare_elf_headers(void **addr, unsigned long *sz)
nr_ranges = 2; /* for exclusion of crashkernel region */
for_each_mem_range(i, &start, &end)
nr_ranges++;
+ nr_ranges += of_reserved_mem_no_dump_nr_ranges();
cmem = kmalloc_flex(*cmem, ranges, nr_ranges);
if (!cmem)
@@ -91,6 +93,10 @@ static int prepare_elf_headers(void **addr, unsigned long *sz)
goto out;
}
+ ret = of_reserved_mem_exclude_no_dump(cmem);
+ if (ret < 0)
+ goto out;
+
ret = crash_prepare_elf64_headers(cmem, true, addr, sz);
out:
--
2.43.0
^ permalink raw reply related
* [PATCH 09/11] riscv: kdump: exclude no-dump reserved memory regions from vmcore
From: Chen Wandun @ 2026-04-29 6:58 UTC (permalink / raw)
To: kexec, linux-doc, linux-kernel, linux-arm-kernel, loongarch,
linux-riscv, devicetree
Cc: akpm, bhe, rppt, pasha.tatashin, pratyush, ruirui.yang, corbet,
skhan, catalin.marinas, will, chenhuacai, kernel, pjw, palmer,
aou, robh, saravanak, chenwandun, zhaomeijing, everyzhao
In-Reply-To: <20260429065831.1510858-1-chenwandun@lixiang.com>
Apply the same no-dump reserved memory filtering to RISC-V kdump as was
done for arm64. Use of_reserved_mem_exclude_no_dump() to drop flagged
regions from the elfcorehdr PT_LOAD segments, and
of_reserved_mem_no_dump_nr_ranges() to pre-size the crash_mem array.
Signed-off-by: Chen Wandun <chenwandun@lixiang.com>
---
arch/riscv/kernel/machine_kexec_file.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/arch/riscv/kernel/machine_kexec_file.c b/arch/riscv/kernel/machine_kexec_file.c
index 54e2d9552e93..012885ac9b79 100644
--- a/arch/riscv/kernel/machine_kexec_file.c
+++ b/arch/riscv/kernel/machine_kexec_file.c
@@ -10,6 +10,7 @@
#include <linux/elf.h>
#include <linux/slab.h>
#include <linux/of.h>
+#include <linux/of_reserved_mem.h>
#include <linux/libfdt.h>
#include <linux/types.h>
#include <linux/memblock.h>
@@ -63,6 +64,7 @@ static int prepare_elf_headers(void **addr, unsigned long *sz)
nr_ranges = 1; /* For exclusion of crashkernel region */
walk_system_ram_res(0, -1, &nr_ranges, get_nr_ram_ranges_callback);
+ nr_ranges += of_reserved_mem_no_dump_nr_ranges();
cmem = kmalloc_flex(*cmem, ranges, nr_ranges);
if (!cmem)
@@ -76,6 +78,8 @@ static int prepare_elf_headers(void **addr, unsigned long *sz)
/* Exclude crashkernel region */
ret = crash_exclude_mem_range(cmem, crashk_res.start, crashk_res.end);
+ if (!ret)
+ ret = of_reserved_mem_exclude_no_dump(cmem);
if (!ret)
ret = crash_prepare_elf64_headers(cmem, true, addr, sz);
--
2.43.0
^ permalink raw reply related
* [PATCH 08/11] arm64: kdump: exclude no-dump reserved memory regions from vmcore
From: Chen Wandun @ 2026-04-29 6:58 UTC (permalink / raw)
To: kexec, linux-doc, linux-kernel, linux-arm-kernel, loongarch,
linux-riscv, devicetree
Cc: akpm, bhe, rppt, pasha.tatashin, pratyush, ruirui.yang, corbet,
skhan, catalin.marinas, will, chenhuacai, kernel, pjw, palmer,
aou, robh, saravanak, chenwandun, zhaomeijing, everyzhao
In-Reply-To: <20260429065831.1510858-1-chenwandun@lixiang.com>
Exclude reserved memory regions marked with the linux,no-dump property
from the elfcorehdr PT_LOAD segments when preparing kdump vmcore.
Device firmware memory regions (e.g., GPU, DSP, modem) reserved via
the device tree typically contain data that is not useful for kernel
crash analysis and can significantly increase vmcore size. By honoring
the no_dump flag in the reserved_mem array, these regions are filtered
out from the crash dump, resulting in smaller and more focused vmcore
files.
Use the common of_reserved_mem_exclude_no_dump() helper to perform the
exclusion, and pre-size the crash_mem array via
of_reserved_mem_no_dump_nr_ranges().
Signed-off-by: Chen Wandun <chenwandun@lixiang.com>
Tested-by: Zhao Meijing <zhaomeijing@lixiang.com>
---
arch/arm64/kernel/machine_kexec_file.c | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/arch/arm64/kernel/machine_kexec_file.c b/arch/arm64/kernel/machine_kexec_file.c
index e31fabed378a..7c9c38096c60 100644
--- a/arch/arm64/kernel/machine_kexec_file.c
+++ b/arch/arm64/kernel/machine_kexec_file.c
@@ -17,6 +17,7 @@
#include <linux/memblock.h>
#include <linux/of.h>
#include <linux/of_fdt.h>
+#include <linux/of_reserved_mem.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/types.h>
@@ -51,6 +52,7 @@ static int prepare_elf_headers(void **addr, unsigned long *sz)
nr_ranges = 2; /* for exclusion of crashkernel region */
for_each_mem_range(i, &start, &end)
nr_ranges++;
+ nr_ranges += of_reserved_mem_no_dump_nr_ranges();
cmem = kmalloc_flex(*cmem, ranges, nr_ranges);
if (!cmem)
@@ -75,6 +77,10 @@ static int prepare_elf_headers(void **addr, unsigned long *sz)
goto out;
}
+ ret = of_reserved_mem_exclude_no_dump(cmem);
+ if (ret)
+ goto out;
+
ret = crash_prepare_elf64_headers(cmem, true, addr, sz);
out:
--
2.43.0
^ permalink raw reply related
* [PATCH 07/11] of: reserved_mem: add no-dump crash_mem exclusion helpers
From: Chen Wandun @ 2026-04-29 6:58 UTC (permalink / raw)
To: kexec, linux-doc, linux-kernel, linux-arm-kernel, loongarch,
linux-riscv, devicetree
Cc: akpm, bhe, rppt, pasha.tatashin, pratyush, ruirui.yang, corbet,
skhan, catalin.marinas, will, chenhuacai, kernel, pjw, palmer,
aou, robh, saravanak, chenwandun, zhaomeijing, everyzhao
In-Reply-To: <20260429065831.1510858-1-chenwandun@lixiang.com>
Provide two kdump-oriented helpers so that arch kexec_file code does
not have to open-code the no-dump filtering loop:
- of_reserved_mem_no_dump_nr_ranges() returns the number of reserved
regions flagged with linux,no-dump. Each exclusion may split one
existing crash_mem range into two, so callers use this count to
pre-size their crash_mem allocation.
- of_reserved_mem_exclude_no_dump() walks the reserved_mem[] array
and calls crash_exclude_mem_range() for each no-dump region.
Both helpers are guarded by CONFIG_KEXEC_FILE; empty inline stubs are
provided for the !KEXEC_FILE case so architecture code can call them
unconditionally.
The consumers are added in the following arm64, riscv and loongarch
patches in this series.
Signed-off-by: Chen Wandun <chenwandun@lixiang.com>
Tested-by: Zhao Meijing <zhaomeijing@lixiang.com>
---
drivers/of/of_reserved_mem.c | 54 +++++++++++++++++++++++++++++++++
include/linux/of_reserved_mem.h | 15 +++++++++
2 files changed, 69 insertions(+)
diff --git a/drivers/of/of_reserved_mem.c b/drivers/of/of_reserved_mem.c
index 4b80420da2d2..038056a6408a 100644
--- a/drivers/of/of_reserved_mem.c
+++ b/drivers/of/of_reserved_mem.c
@@ -27,6 +27,10 @@
#include "of_private.h"
+#ifdef CONFIG_KEXEC_FILE
+#include <linux/crash_core.h>
+#endif
+
static struct reserved_mem reserved_mem_array[MAX_RESERVED_REGIONS] __initdata;
static struct reserved_mem *reserved_mem __refdata = reserved_mem_array;
static int total_reserved_mem_cnt = MAX_RESERVED_REGIONS;
@@ -916,6 +920,56 @@ struct reserved_mem *of_reserved_mem_lookup(struct device_node *np)
}
EXPORT_SYMBOL_GPL(of_reserved_mem_lookup);
+#ifdef CONFIG_KEXEC_FILE
+/**
+ * of_reserved_mem_no_dump_nr_ranges() - count reserved regions flagged
+ * with the linux,no-dump property.
+ *
+ * Each such region may split an existing crash_mem range into two when
+ * it is excluded, so callers can use this count to pre-size their
+ * crash_mem allocation.
+ */
+unsigned int of_reserved_mem_no_dump_nr_ranges(void)
+{
+ unsigned int i, n = 0;
+
+ for (i = 0; i < reserved_mem_count; i++)
+ if (reserved_mem[i].no_dump)
+ n++;
+ return n;
+}
+
+/**
+ * of_reserved_mem_exclude_no_dump() - exclude no-dump reserved regions
+ * from a crash_mem list.
+ * @cmem: crash memory list to modify
+ *
+ * Walks the reserved_mem[] array and calls crash_exclude_mem_range() for
+ * every region with no_dump set. Intended to be called from arch kdump
+ * code when constructing the elfcorehdr.
+ *
+ * Returns 0 on success, or a negative error returned by
+ * crash_exclude_mem_range() on the first failure.
+ */
+int of_reserved_mem_exclude_no_dump(struct crash_mem *cmem)
+{
+ unsigned int i;
+ int ret;
+
+ for (i = 0; i < reserved_mem_count; i++) {
+ struct reserved_mem *r = &reserved_mem[i];
+
+ if (!r->no_dump || !r->size)
+ continue;
+ ret = crash_exclude_mem_range(cmem, r->base,
+ r->base + r->size - 1);
+ if (ret)
+ return ret;
+ }
+ return 0;
+}
+#endif /* CONFIG_KEXEC_FILE */
+
/**
* of_reserved_mem_region_to_resource() - Get a reserved memory region as a resource
* @np: node containing 'memory-region' property
diff --git a/include/linux/of_reserved_mem.h b/include/linux/of_reserved_mem.h
index 29674f572673..cd0f88f29579 100644
--- a/include/linux/of_reserved_mem.h
+++ b/include/linux/of_reserved_mem.h
@@ -8,6 +8,7 @@
struct of_phandle_args;
struct reserved_mem_ops;
struct resource;
+struct crash_mem;
struct reserved_mem {
const char *name;
@@ -94,6 +95,20 @@ static inline int of_reserved_mem_region_count(const struct device_node *np)
}
#endif
+#if defined(CONFIG_OF_RESERVED_MEM) && defined(CONFIG_KEXEC_FILE)
+unsigned int of_reserved_mem_no_dump_nr_ranges(void);
+int of_reserved_mem_exclude_no_dump(struct crash_mem *cmem);
+#else
+static inline unsigned int of_reserved_mem_no_dump_nr_ranges(void)
+{
+ return 0;
+}
+static inline int of_reserved_mem_exclude_no_dump(struct crash_mem *cmem)
+{
+ return 0;
+}
+#endif
+
/**
* of_reserved_mem_device_init() - assign reserved memory region to given device
* @dev: Pointer to the device to configure
--
2.43.0
^ permalink raw reply related
* [PATCH 06/11] of: reserved_mem: save /memreserve/ entries into reserved_mem array
From: Chen Wandun @ 2026-04-29 6:58 UTC (permalink / raw)
To: kexec, linux-doc, linux-kernel, linux-arm-kernel, loongarch,
linux-riscv, devicetree
Cc: akpm, bhe, rppt, pasha.tatashin, pratyush, ruirui.yang, corbet,
skhan, catalin.marinas, will, chenhuacai, kernel, pjw, palmer,
aou, robh, saravanak, chenwandun, zhaomeijing, everyzhao
In-Reply-To: <20260429065831.1510858-1-chenwandun@lixiang.com>
Save /memreserve/ entries from the FDT header into the reserved_mem
array so they can be consumed as vmcore filtering metadata by kdump.
/memreserve/ regions hold firmware or bootloader state that is not
useful for kernel crash analysis, so saved /memreserve/ entries
default to no_dump=true and are tagged with name="memreserve" so
consumers can distinguish them from /reserved-memory/ child nodes.
Some DTBs declare the same or overlapping range in both
/memreserve/ and a /reserved-memory/ child. Commit b41328187629
("of: fdt: Scan /memreserve/ last") describes one such case on
Khadas Vim3 where the range is in /memreserve/ and also in a
/reserved-memory/ child carrying no-map. The /reserved-memory/
node's attributes (no-map, reusable, linux,no-dump) are the
explicit declaration and must win over the firmware default,
fdt_reserved_mem_save_memreserve() therefore inherits no_dump from
the overlapping /reserved-memory/ entry rather than silently
applying no_dump=true.
Signed-off-by: Chen Wandun <chenwandun@lixiang.com>
Tested-by: Zhao Meijing <zhaomeijing@lixiang.com>
---
drivers/of/of_reserved_mem.c | 107 +++++++++++++++++++++++++++++------
1 file changed, 91 insertions(+), 16 deletions(-)
diff --git a/drivers/of/of_reserved_mem.c b/drivers/of/of_reserved_mem.c
index ac3d8b837d61..4b80420da2d2 100644
--- a/drivers/of/of_reserved_mem.c
+++ b/drivers/of/of_reserved_mem.c
@@ -252,6 +252,49 @@ static void __init __rmem_check_for_overlap(void)
}
}
+/**
+ * fdt_reserved_mem_save_memreserve() - save a /memreserve/ entry
+ * @base: base address
+ * @size: size
+ *
+ * Save a /memreserve/ range into reserved_mem[] with no_dump=true
+ * as the firmware default. If the range overlaps a /reserved-memory/
+ * child already saved in this pass, inherit that entry's no_dump so
+ * node-level attributes (no-map, reusable, linux,no-dump) win over
+ * the firmware default.
+ */
+static void __init fdt_reserved_mem_save_memreserve(phys_addr_t base,
+ phys_addr_t size)
+{
+ struct reserved_mem *rmem;
+ phys_addr_t end = base + size;
+ bool no_dump = true;
+ int i;
+
+ for (i = 0; i < reserved_mem_count; i++) {
+ phys_addr_t r_base = reserved_mem[i].base;
+ phys_addr_t r_end = r_base + reserved_mem[i].size;
+
+ if (base < r_end && end > r_base) {
+ no_dump = reserved_mem[i].no_dump;
+ break;
+ }
+ }
+
+ if (reserved_mem_count == total_reserved_mem_cnt) {
+ pr_err("not enough space for all defined regions.\n");
+ return;
+ }
+
+ rmem = &reserved_mem[reserved_mem_count];
+ rmem->name = "memreserve";
+ rmem->base = base;
+ rmem->size = size;
+ rmem->no_dump = no_dump;
+
+ reserved_mem_count++;
+}
+
/**
* fdt_scan_reserved_mem_late() - Scan FDT and initialize remaining reserved
* memory regions.
@@ -260,6 +303,9 @@ static void __init __rmem_check_for_overlap(void)
* "static" reserved memory regions, that are defined using the "reg"
* property. Each such region is then initialized with its specific init
* function and stored in the global reserved_mem array.
+ *
+ * In addition, /memreserve/ entries from the FDT header are saved into
+ * the reserved_mem array so they can be consumed as vmcore metadata.
*/
void __init fdt_scan_reserved_mem_late(void)
{
@@ -270,28 +316,32 @@ void __init fdt_scan_reserved_mem_late(void)
if (!fdt)
return;
+ /*
+ * fdt_scan_reserved_mem() has set total_reserved_mem_cnt to the
+ * total number of entries to be saved (reg-based + /memreserve/).
+ * If it is zero there is nothing to allocate, save or check.
+ */
+ if (!total_reserved_mem_cnt)
+ return;
+
+ /*
+ * Allocate up front: /memreserve/ saves below may run on any
+ * path and must write into a memblock-backed array, not the
+ * __initdata reserved_mem_array which is freed at free_initmem().
+ */
+ alloc_reserved_mem_array();
+
node = fdt_path_offset(fdt, "/reserved-memory");
if (node < 0) {
pr_info("Reserved memory: No reserved-memory node in the DT\n");
- return;
+ goto memreserve;
}
if (__reserved_mem_check_root(node)) {
pr_err("Reserved memory: unsupported node format, ignoring\n");
- return;
+ goto memreserve;
}
- /*
- * fdt_scan_reserved_mem() sets total_reserved_mem_cnt to the
- * number of entries that need a slot in reserved_mem[]. If it is
- * zero there is nothing to allocate or save.
- */
- if (!total_reserved_mem_cnt)
- return;
-
- /* Attempt dynamic allocation of a new reserved_mem array */
- alloc_reserved_mem_array();
-
fdt_for_each_subnode(child, fdt, node) {
const char *uname;
int i, len;
@@ -342,6 +392,18 @@ void __init fdt_scan_reserved_mem_late(void)
/* check for overlapping reserved regions */
__rmem_check_for_overlap();
+
+memreserve:
+ /* Save /memreserve/ entries (independent of /reserved-memory) */
+ for (int i = 0; ; i++) {
+ u64 mbase, msize;
+
+ if (fdt_get_mem_rsv(fdt, i, &mbase, &msize))
+ break;
+ if (!msize)
+ break;
+ fdt_reserved_mem_save_memreserve(mbase, msize);
+ }
}
static int __init __reserved_mem_alloc_size(unsigned long node, const char *uname);
@@ -365,11 +427,11 @@ int __init fdt_scan_reserved_mem(void)
node = fdt_path_offset(fdt, "/reserved-memory");
if (node < 0)
- return -ENODEV;
+ goto memreserve;
if (__reserved_mem_check_root(node) != 0) {
pr_err("Reserved memory: unsupported node format, ignoring\n");
- return -EINVAL;
+ goto memreserve;
}
fdt_for_each_subnode(child, fdt, node) {
@@ -406,8 +468,21 @@ int __init fdt_scan_reserved_mem(void)
if (!err)
count++;
}
+
+memreserve:
+ /* Count /memreserve/ entries (independent of /reserved-memory) */
+ for (int i = 0; ; i++) {
+ u64 base, size;
+
+ if (fdt_get_mem_rsv(fdt, i, &base, &size))
+ break;
+ if (!size)
+ break;
+ count++;
+ }
+
total_reserved_mem_cnt = count;
- return 0;
+ return count ? 0 : -ENODEV;
}
/*
--
2.43.0
^ permalink raw reply related
* [PATCH 05/11] of: reserved_mem: add linux,no-dump property support for reserved memory regions
From: Chen Wandun @ 2026-04-29 6:58 UTC (permalink / raw)
To: kexec, linux-doc, linux-kernel, linux-arm-kernel, loongarch,
linux-riscv, devicetree
Cc: akpm, bhe, rppt, pasha.tatashin, pratyush, ruirui.yang, corbet,
skhan, catalin.marinas, will, chenhuacai, kernel, pjw, palmer,
aou, robh, saravanak, chenwandun, zhaomeijing, everyzhao
In-Reply-To: <20260429065831.1510858-1-chenwandun@lixiang.com>
Add a 'no_dump' field to struct reserved_mem and parse the
'linux,no-dump' device tree property during reserved memory node
initialization. This property allows device tree authors to mark
specific reserved memory regions that should be excluded from kdump
vmcore dumps.
Reserved memory regions used by device firmware (e.g., GPU, DSP, modem)
typically contain data that is not useful for kernel crash analysis and
can significantly increase vmcore size. The 'linux,no-dump' property
provides a declarative way to indicate these regions should be filtered
out when constructing the elfcorehdr for kdump.
The property is named with a 'linux,' prefix because kdump/vmcore is
Linux-specific and the property is an OS hint rather than a hardware
description, matching existing properties such as 'linux,cma-default'
and 'linux,usable-memory-range'.
The 'linux,no-dump' property is only effective when the region:
- Does not have 'no-map': these regions are already excluded from
vmcore since they are removed from the linear mapping (MEMBLOCK_NOMAP).
- Does not have 'reusable': CMA reusable regions are actively used by
the kernel for movable page allocations, and their contents are
valuable for crash analysis.
The no-dump status is also printed in the boot log alongside the
existing nomap and reusable flags for diagnostic purposes.
Corresponding dt-schema binding update:
https://github.com/devicetree-org/dt-schema/pull/193
Signed-off-by: Chen Wandun <chenwandun@lixiang.com>
Tested-by: Zhao Meijing <zhaomeijing@lixiang.com>
---
drivers/of/of_reserved_mem.c | 13 ++++++++-----
include/linux/of_reserved_mem.h | 1 +
2 files changed, 9 insertions(+), 5 deletions(-)
diff --git a/drivers/of/of_reserved_mem.c b/drivers/of/of_reserved_mem.c
index 93585af9f8a3..ac3d8b837d61 100644
--- a/drivers/of/of_reserved_mem.c
+++ b/drivers/of/of_reserved_mem.c
@@ -654,6 +654,7 @@ static void __init fdt_init_reserved_mem_node(unsigned long node, const char *un
{
int err = 0;
bool nomap;
+ bool reusable;
struct reserved_mem *rmem = &reserved_mem[reserved_mem_count];
@@ -662,11 +663,14 @@ static void __init fdt_init_reserved_mem_node(unsigned long node, const char *un
return;
}
+ nomap = of_get_flat_dt_prop(node, "no-map", NULL) != NULL;
+ reusable = of_get_flat_dt_prop(node, "reusable", NULL) != NULL;
+
rmem->name = uname;
rmem->base = base;
rmem->size = size;
-
- nomap = of_get_flat_dt_prop(node, "no-map", NULL) != NULL;
+ rmem->no_dump = !nomap && !reusable &&
+ of_get_flat_dt_prop(node, "linux,no-dump", NULL) != NULL;
err = __reserved_mem_init_node(rmem, node);
if (err != 0 && err != -ENODEV) {
@@ -680,13 +684,12 @@ static void __init fdt_init_reserved_mem_node(unsigned long node, const char *un
return;
} else {
phys_addr_t end = rmem->base + rmem->size - 1;
- bool reusable =
- (of_get_flat_dt_prop(node, "reusable", NULL)) != NULL;
- pr_info("%pa..%pa (%lu KiB) %s %s %s\n",
+ pr_info("%pa..%pa (%lu KiB) %s %s %s %s\n",
&rmem->base, &end, (unsigned long)(rmem->size / SZ_1K),
nomap ? "nomap" : "map",
reusable ? "reusable" : "non-reusable",
+ rmem->no_dump ? "no-dump" : "dump",
rmem->name ? rmem->name : "unknown");
}
diff --git a/include/linux/of_reserved_mem.h b/include/linux/of_reserved_mem.h
index e8b20b29fa68..29674f572673 100644
--- a/include/linux/of_reserved_mem.h
+++ b/include/linux/of_reserved_mem.h
@@ -15,6 +15,7 @@ struct reserved_mem {
phys_addr_t base;
phys_addr_t size;
void *priv;
+ bool no_dump;
};
struct reserved_mem_ops {
--
2.43.0
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox