Linux Power Management development
 help / color / mirror / Atom feed
* [PATCH v2 1/2] dt-bindings: interconnect: document the RPMh Network-On-Chip interconnect in Hawi SoC
From: Vivek Aknurwar @ 2026-04-06 23:04 UTC (permalink / raw)
  To: Georgi Djakov, Rob Herring, Krzysztof Kozlowski, Conor Dooley
  Cc: linux-arm-msm, linux-pm, devicetree, linux-kernel, Mike Tipton,
	Vivek Aknurwar
In-Reply-To: <20260406-icc-hawi-v2-0-6cfee87a1d25@oss.qualcomm.com>

Document the RPMh Network-On-Chip Interconnect of the Hawi platform.

Signed-off-by: Vivek Aknurwar <vivek.aknurwar@oss.qualcomm.com>
---
 .../bindings/interconnect/qcom,hawi-rpmh.yaml      | 131 ++++++++++++++++
 include/dt-bindings/interconnect/qcom,hawi-rpmh.h  | 164 +++++++++++++++++++++
 2 files changed, 295 insertions(+)

diff --git a/Documentation/devicetree/bindings/interconnect/qcom,hawi-rpmh.yaml b/Documentation/devicetree/bindings/interconnect/qcom,hawi-rpmh.yaml
new file mode 100644
index 000000000000..49a2dca5db62
--- /dev/null
+++ b/Documentation/devicetree/bindings/interconnect/qcom,hawi-rpmh.yaml
@@ -0,0 +1,131 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/interconnect/qcom,hawi-rpmh.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Qualcomm RPMh Network-On-Chip Interconnect on Hawi
+
+maintainers:
+  - Vivek Aknurwar <vivek.aknurwar@oss.qualcomm.com>
+
+description: |
+  RPMh interconnect providers support system bandwidth requirements through
+  RPMh hardware accelerators known as Bus Clock Manager (BCM). The provider is
+  able to communicate with the BCM through the Resource State Coordinator (RSC)
+  associated with each execution environment. Provider nodes must point to at
+  least one RPMh device child node pertaining to their RSC and each provider
+  can map to multiple RPMh resources.
+
+  See also: include/dt-bindings/interconnect/qcom,hawi-rpmh.h
+
+properties:
+  compatible:
+    enum:
+      - qcom,hawi-aggre1-noc
+      - qcom,hawi-clk-virt
+      - qcom,hawi-cnoc-main
+      - qcom,hawi-gem-noc
+      - qcom,hawi-llclpi-noc
+      - qcom,hawi-lpass-ag-noc
+      - qcom,hawi-lpass-lpiaon-noc
+      - qcom,hawi-lpass-lpicx-noc
+      - qcom,hawi-mc-virt
+      - qcom,hawi-mmss-noc
+      - qcom,hawi-nsp-noc
+      - qcom,hawi-pcie-anoc
+      - qcom,hawi-stdst-cfg
+      - qcom,hawi-stdst-main
+      - qcom,hawi-system-noc
+
+  reg:
+    maxItems: 1
+
+  clocks:
+    minItems: 2
+    maxItems: 3
+
+required:
+  - compatible
+
+allOf:
+  - $ref: qcom,rpmh-common.yaml#
+  - if:
+      properties:
+        compatible:
+          contains:
+            enum:
+              - qcom,hawi-clk-virt
+              - qcom,hawi-mc-virt
+    then:
+      properties:
+        reg: false
+    else:
+      required:
+        - reg
+
+  - if:
+      properties:
+        compatible:
+          contains:
+            enum:
+              - qcom,hawi-pcie-anoc
+    then:
+      properties:
+        clocks:
+          items:
+            - description: aggre-NOC PCIe AXI clock
+            - description: cfg-NOC PCIe a-NOC AHB clock
+
+  - if:
+      properties:
+        compatible:
+          contains:
+            enum:
+              - qcom,hawi-aggre1-noc
+    then:
+      properties:
+        clocks:
+          items:
+            - description: aggre UFS PHY AXI clock
+            - description: aggre USB3 PRIM AXI clock
+            - description: RPMH CC IPA clock
+
+  - if:
+      properties:
+        compatible:
+          contains:
+            enum:
+              - qcom,hawi-aggre1-noc
+              - qcom,hawi-pcie-anoc
+    then:
+      required:
+        - clocks
+    else:
+      properties:
+        clocks: false
+
+unevaluatedProperties: false
+
+examples:
+  - |
+    soc {
+      #address-cells = <2>;
+      #size-cells = <2>;
+
+      clk_virt: interconnect-0 {
+        compatible = "qcom,hawi-clk-virt";
+        #interconnect-cells = <2>;
+        qcom,bcm-voters = <&apps_bcm_voter>;
+      };
+
+      aggre_noc: interconnect@f00000 {
+        compatible = "qcom,hawi-aggre1-noc";
+        reg = <0x0 0xf00000 0x0 0x54400>;
+        #interconnect-cells = <2>;
+        clocks = <&gcc_aggre_ufs_phy_axi_clk>,
+                 <&gcc_aggre_usb3_prim_axi_clk>,
+                 <&rpmhcc_ipa_clk>;
+        qcom,bcm-voters = <&apps_bcm_voter>;
+      };
+    };
diff --git a/include/dt-bindings/interconnect/qcom,hawi-rpmh.h b/include/dt-bindings/interconnect/qcom,hawi-rpmh.h
new file mode 100644
index 000000000000..75312cbbb80e
--- /dev/null
+++ b/include/dt-bindings/interconnect/qcom,hawi-rpmh.h
@@ -0,0 +1,164 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#ifndef __DT_BINDINGS_INTERCONNECT_QCOM_HAWI_H
+#define __DT_BINDINGS_INTERCONNECT_QCOM_HAWI_H
+
+#define MASTER_QSPI_0				0
+#define MASTER_QUP_2				1
+#define MASTER_QUP_3				2
+#define MASTER_QUP_4				3
+#define MASTER_CRYPTO				4
+#define MASTER_IPA				5
+#define MASTER_QUP_1				6
+#define MASTER_SOCCP_PROC				7
+#define MASTER_QDSS_ETR				8
+#define MASTER_QDSS_ETR_1				9
+#define MASTER_SDCC_2				10
+#define MASTER_SDCC_4				11
+#define MASTER_UFS_MEM				12
+#define MASTER_USB3				13
+#define SLAVE_A1NOC_SNOC				14
+
+#define MASTER_DDR_EFF_VETO				0
+#define MASTER_QUP_CORE_0				1
+#define MASTER_QUP_CORE_1				2
+#define MASTER_QUP_CORE_2				3
+#define MASTER_QUP_CORE_3				4
+#define MASTER_QUP_CORE_4				5
+#define SLAVE_DDR_EFF_VETO				6
+#define SLAVE_QUP_CORE_0				7
+#define SLAVE_QUP_CORE_1				8
+#define SLAVE_QUP_CORE_2				9
+#define SLAVE_QUP_CORE_3				10
+#define SLAVE_QUP_CORE_4				11
+
+#define MASTER_GEM_NOC_CNOC				0
+#define MASTER_GEM_NOC_PCIE_SNOC				1
+#define SLAVE_AOSS				2
+#define SLAVE_IPA_CFG				3
+#define SLAVE_IPC_ROUTER_FENCE				4
+#define SLAVE_SOCCP				5
+#define SLAVE_TME_CFG				6
+#define SLAVE_CNOC_CFG				7
+#define SLAVE_DDRSS_CFG				8
+#define SLAVE_IMEM				9
+#define SLAVE_PCIE_0				10
+
+#define MASTER_GIC				0
+#define MASTER_GPU_TCU				1
+#define MASTER_SYS_TCU				2
+#define MASTER_APPSS_PROC				3
+#define MASTER_GFX3D				4
+#define MASTER_LPASS_GEM_NOC				5
+#define MASTER_MSS_PROC				6
+#define MASTER_MNOC_HF_MEM_NOC				7
+#define MASTER_MNOC_SF_MEM_NOC				8
+#define MASTER_COMPUTE_NOC				9
+#define MASTER_ANOC_PCIE_GEM_NOC				10
+#define MASTER_QPACE				11
+#define MASTER_SNOC_SF_MEM_NOC				12
+#define MASTER_WLAN_Q6				13
+#define SLAVE_GEM_NOC_CNOC				14
+#define SLAVE_LLCC				15
+#define SLAVE_MEM_NOC_PCIE_SNOC				16
+
+#define MASTER_LPIAON_NOC_LLCLPI_NOC				0
+#define SLAVE_LPASS_LPI_CC				1
+#define SLAVE_LLCC_ISLAND				2
+#define SLAVE_SERVICE_LLCLPI_NOC				3
+#define SLAVE_SERVICE_LLCLPI_NOC_CHIPCX				4
+
+#define MASTER_LPIAON_NOC				0
+#define SLAVE_LPASS_GEM_NOC				1
+
+#define MASTER_LPASS_LPINOC				0
+#define SLAVE_LPIAON_NOC_LLCLPI_NOC				1
+#define SLAVE_LPIAON_NOC_LPASS_AG_NOC				2
+
+#define MASTER_LPASS_PROC				0
+#define SLAVE_LPICX_NOC_LPIAON_NOC				1
+
+#define MASTER_LLCC				0
+#define MASTER_DDR_RT				1
+#define SLAVE_EBI1				2
+#define SLAVE_DDR_RT				3
+
+#define MASTER_CAMNOC_HF				0
+#define MASTER_CAMNOC_NRT_ICP_SF				1
+#define MASTER_CAMNOC_RT_CDM_SF				2
+#define MASTER_CAMNOC_SF				3
+#define MASTER_MDP				4
+#define MASTER_MDSS_DCP				5
+#define MASTER_CDSP_HCP				6
+#define MASTER_VIDEO_CV_PROC				7
+#define MASTER_VIDEO_EVA				8
+#define MASTER_VIDEO_MVP				9
+#define MASTER_VIDEO_V_PROC				10
+#define SLAVE_MNOC_HF_MEM_NOC				11
+#define SLAVE_MNOC_SF_MEM_NOC				12
+
+#define MASTER_CDSP_PROC				0
+#define SLAVE_CDSP_MEM_NOC				1
+
+#define MASTER_PCIE_ANOC_CFG				0
+#define MASTER_PCIE_0				1
+#define MASTER_PCIE_1				2
+#define SLAVE_ANOC_PCIE_GEM_NOC				3
+#define SLAVE_SERVICE_PCIE_ANOC				4
+
+#define MASTER_CFG_CENTER				0
+#define MASTER_CFG_EAST				1
+#define MASTER_CFG_MM				2
+#define MASTER_CFG_NORTH				3
+#define MASTER_CFG_SOUTH				4
+#define MASTER_CFG_SOUTHWEST				5
+#define SLAVE_AHB2PHY_SOUTH				6
+#define SLAVE_BOOT_ROM				7
+#define SLAVE_CAMERA_CFG				8
+#define SLAVE_CLK_CTL				9
+#define SLAVE_CRYPTO_CFG				10
+#define SLAVE_DISPLAY_CFG				11
+#define SLAVE_EVA_CFG				12
+#define SLAVE_GFX3D_CFG				13
+#define SLAVE_I2C				14
+#define SLAVE_IMEM_CFG				15
+#define SLAVE_IPC_ROUTER_CFG				16
+#define SLAVE_IRIS_CFG				17
+#define SLAVE_CNOC_MSS				18
+#define SLAVE_PCIE_0_CFG				19
+#define SLAVE_PCIE_1_CFG				20
+#define SLAVE_PRNG				21
+#define SLAVE_QSPI_0				22
+#define SLAVE_QUP_1				23
+#define SLAVE_QUP_2				24
+#define SLAVE_QUP_3				25
+#define SLAVE_QUP_4				26
+#define SLAVE_SDCC_2				27
+#define SLAVE_SDCC_4				28
+#define SLAVE_TLMM				29
+#define SLAVE_UFS_MEM_CFG				30
+#define SLAVE_USB3				31
+#define SLAVE_VSENSE_CTRL_CFG				32
+#define SLAVE_PCIE_ANOC_CFG				33
+#define SLAVE_QDSS_CFG				34
+#define SLAVE_QDSS_STM				35
+#define SLAVE_TCSR				36
+#define SLAVE_TCU				37
+
+#define MASTER_CNOC_STARDUST				0
+#define SLAVE_STARDUST_CENTER_CFG				1
+#define SLAVE_STARDUST_EAST_CFG				2
+#define SLAVE_STARDUST_MM_CFG				3
+#define SLAVE_STARDUST_NORTH_CFG				4
+#define SLAVE_STARDUST_SOUTH_CFG				5
+#define SLAVE_STARDUST_SOUTHWEST_CFG				6
+
+#define MASTER_A1NOC_SNOC				0
+#define MASTER_APSS_NOC				1
+#define MASTER_CNOC_SNOC				2
+#define SLAVE_SNOC_GEM_NOC_SF				3
+
+#endif

-- 
2.34.1


^ permalink raw reply related

* [PATCH v2 0/2] interconnect: qcom: Add support for upcoming Hawi SoC
From: Vivek Aknurwar @ 2026-04-06 23:04 UTC (permalink / raw)
  To: Georgi Djakov, Rob Herring, Krzysztof Kozlowski, Conor Dooley
  Cc: linux-arm-msm, linux-pm, devicetree, linux-kernel, Mike Tipton,
	Vivek Aknurwar, Krzysztof Kozlowski

Add interconnect bindings and RPMh-based interconnect
driver support for the upcoming Qualcomm Hawi SoC.

Signed-off-by: Vivek Aknurwar <vivek.aknurwar@oss.qualcomm.com>
---
Changes in v2:
- Fix warning reported by dt_binding_check.
- Collected Acked-bys
- Link to v1: https://lore.kernel.org/r/20260330-icc-hawi-v1-0-4b54a9e7d38c@oss.qualcomm.com

---
Vivek Aknurwar (2):
      dt-bindings: interconnect: document the RPMh Network-On-Chip interconnect in Hawi SoC
      interconnect: qcom: add Hawi interconnect provider driver

 .../bindings/interconnect/qcom,hawi-rpmh.yaml      |  131 ++
 drivers/interconnect/qcom/Kconfig                  |    9 +
 drivers/interconnect/qcom/Makefile                 |    2 +
 drivers/interconnect/qcom/hawi.c                   | 2021 ++++++++++++++++++++
 include/dt-bindings/interconnect/qcom,hawi-rpmh.h  |  164 ++
 5 files changed, 2327 insertions(+)
---
base-commit: e3b32dcb9f23e3c3927ef3eec6a5842a988fb574
change-id: 20260311-icc-hawi-d6dc165f8935

Best regards,
-- 
Vivek Aknurwar <vivek.aknurwar@oss.qualcomm.com>


^ permalink raw reply

* Re: [PATCH] dt-bindings: opp-v2: Fix example 3 CPU reg value
From: Rob Herring (Arm) @ 2026-04-06 22:44 UTC (permalink / raw)
  To: Vivian Wang
  Cc: Krzysztof Kozlowski, Viresh Kumar, linux-kernel, linux-pm,
	Viresh Kumar, devicetree, Nishanth Menon, Conor Dooley,
	Stephen Boyd
In-Reply-To: <20260403-dt-bindings-opp-v2-hex-cpu-reg-v1-1-38a4968ab515@iscas.ac.cn>


On Fri, 03 Apr 2026 18:34:29 +0800, Vivian Wang wrote:
> Example 3 is a dual-cluster example, meaning that the CPU nodes should
> have reg values 0x0, 0x1, 0x100, 0x101. The example incorrectly uses
> decimal 0, 1, 100, 101 instead, which seems unintended. Use the correct
> hexadecimal values.
> 
> Even though the value doesn't change for the first two CPUs, 0 and 1 in
> example 3 are changed to 0x0 and 0x1 respectively for consistency. Other
> examples all have reg less than 10, so they have not been changed.
> 
> Signed-off-by: Vivian Wang <wangruikang@iscas.ac.cn>
> ---
> Found while trying to figure out if cpu@* unit addresses are supposed to
> be decimal or hexadecimal. This is AFAICT the only place in-tree where
> an arm/arm64 DTS uses multi-digit decimal. See also:
> 
> - https://lore.kernel.org/devicetree-spec/00ddad5a-02f5-474e-af9c-11ce7716ddfc@iscas.ac.cn/
> - https://github.com/devicetree-org/devicetree-specification/issues/86
> ---
>  Documentation/devicetree/bindings/opp/opp-v2.yaml | 8 ++++----
>  1 file changed, 4 insertions(+), 4 deletions(-)
> 

Applied, thanks!


^ permalink raw reply

* Re: [PATCH v3 6/6] thermal: core: Suspend thermal zones later and resume them earlier
From: Armin Wolf @ 2026-04-06 21:40 UTC (permalink / raw)
  To: Rafael J. Wysocki, Linux PM; +Cc: Daniel Lezcano, LKML, Lukasz Luba
In-Reply-To: <3715915.iIbC2pHGDl@rafael.j.wysocki>

Am 06.04.26 um 18:16 schrieb Rafael J. Wysocki:

> From: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
>
> To avoid some undesirable interactions between thermal zone suspend
> and resume with user space that is running when those operations are
> carried out, move them closer to the suspend and resume of devices,
> respectively, by updating dpm_prepare() to carry out thermal zone
> suspend and dpm_complete() to start thermal zone resume (that will
> continue asynchronously).
>
> This also makes the code easier to follow by removing one, arguably
> redundant, level of indirection represented by the thermal PM notifier.

The patch looks good to me. In the future maybe we could track PM dependencies
between thermal zones and cooling devices using device links instead, but for
now keeping the already existing logic looks fine to me.

Reviewed-by: Armin Wolf <W_Armin@gmx.de>

> Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
> ---
>
> v2 -> v3:
>     * Rebase on top of the v3 of the previous patch
>
> v1 -> v2:
>     * Reorder with respect to the previous patch
>     * Use thermal_class_unavailable() to avoid running code that should
>       not run without the thermal class
>     * Suspend thermal zones after disabling device probing and resume
>       them before enabling device probing for better synchronization
>
> ---
>   drivers/base/power/main.c      |    5 +++
>   drivers/thermal/thermal_core.c |   60 ++++++++++++-----------------------------
>   include/linux/thermal.h        |    6 ++++
>   3 files changed, 29 insertions(+), 42 deletions(-)
>
> --- a/drivers/base/power/main.c
> +++ b/drivers/base/power/main.c
> @@ -33,6 +33,7 @@
>   #include <trace/events/power.h>
>   #include <linux/cpufreq.h>
>   #include <linux/devfreq.h>
> +#include <linux/thermal.h>
>   #include <linux/timer.h>
>   #include <linux/nmi.h>
>   
> @@ -1282,6 +1283,8 @@ void dpm_complete(pm_message_t state)
>   	list_splice(&list, &dpm_list);
>   	mutex_unlock(&dpm_list_mtx);
>   
> +	/* Start resuming thermal control */
> +	thermal_pm_complete();
>   	/* Allow device probing and trigger re-probing of deferred devices */
>   	device_unblock_probing();
>   	trace_suspend_resume(TPS("dpm_complete"), state.event, false);
> @@ -2225,6 +2228,8 @@ int dpm_prepare(pm_message_t state)
>   	 * instead. The normal behavior will be restored in dpm_complete().
>   	 */
>   	device_block_probing();
> +	/* Suspend thermal control. */
> +	thermal_pm_prepare();
>   
>   	mutex_lock(&dpm_list_mtx);
>   	while (!list_empty(&dpm_list) && !error) {
> --- a/drivers/thermal/thermal_core.c
> +++ b/drivers/thermal/thermal_core.c
> @@ -1838,7 +1838,7 @@ static void thermal_zone_pm_prepare(stru
>   	cancel_delayed_work(&tz->poll_queue);
>   }
>   
> -static void thermal_pm_notify_prepare(void)
> +static void __thermal_pm_prepare(void)
>   {
>   	struct thermal_zone_device *tz;
>   
> @@ -1850,6 +1850,19 @@ static void thermal_pm_notify_prepare(vo
>   		thermal_zone_pm_prepare(tz);
>   }
>   
> +void thermal_pm_prepare(void)
> +{
> +	if (thermal_class_unavailable)
> +		return;
> +
> +	__thermal_pm_prepare();
> +	/*
> +	 * Allow any leftover thermal work items already on the worqueue to
> +	 * complete so they don't get in the way later.
> +	 */
> +	flush_workqueue(thermal_wq);
> +}
> +
>   static void thermal_zone_pm_complete(struct thermal_zone_device *tz)
>   {
>   	guard(thermal_zone)(tz);
> @@ -1866,10 +1879,13 @@ static void thermal_zone_pm_complete(str
>   	mod_delayed_work(thermal_wq, &tz->poll_queue, 0);
>   }
>   
> -static void thermal_pm_notify_complete(void)
> +void thermal_pm_complete(void)
>   {
>   	struct thermal_zone_device *tz;
>   
> +	if (thermal_class_unavailable)
> +		return;
> +
>   	guard(mutex)(&thermal_list_lock);
>   
>   	thermal_pm_suspended = false;
> @@ -1878,41 +1894,6 @@ static void thermal_pm_notify_complete(v
>   		thermal_zone_pm_complete(tz);
>   }
>   
> -static int thermal_pm_notify(struct notifier_block *nb,
> -			     unsigned long mode, void *_unused)
> -{
> -	switch (mode) {
> -	case PM_HIBERNATION_PREPARE:
> -	case PM_RESTORE_PREPARE:
> -	case PM_SUSPEND_PREPARE:
> -		thermal_pm_notify_prepare();
> -		/*
> -		 * Allow any leftover thermal work items already on the
> -		 * worqueue to complete so they don't get in the way later.
> -		 */
> -		flush_workqueue(thermal_wq);
> -		break;
> -	case PM_POST_HIBERNATION:
> -	case PM_POST_RESTORE:
> -	case PM_POST_SUSPEND:
> -		thermal_pm_notify_complete();
> -		break;
> -	default:
> -		break;
> -	}
> -	return 0;
> -}
> -
> -static struct notifier_block thermal_pm_nb = {
> -	.notifier_call = thermal_pm_notify,
> -	/*
> -	 * Run at the lowest priority to avoid interference between the thermal
> -	 * zone resume work items spawned by thermal_pm_notify() and the other
> -	 * PM notifiers.
> -	 */
> -	.priority = INT_MIN,
> -};
> -
>   static int __init thermal_init(void)
>   {
>   	int result;
> @@ -1939,11 +1920,6 @@ static int __init thermal_init(void)
>   
>   	thermal_class_unavailable = false;
>   
> -	result = register_pm_notifier(&thermal_pm_nb);
> -	if (result)
> -		pr_warn("Thermal: Can not register suspend notifier, return %d\n",
> -			result);
> -
>   	return 0;
>   
>   unregister_governors:
> --- a/include/linux/thermal.h
> +++ b/include/linux/thermal.h
> @@ -273,6 +273,9 @@ bool thermal_trip_is_bound_to_cdev(struc
>   int thermal_zone_device_enable(struct thermal_zone_device *tz);
>   int thermal_zone_device_disable(struct thermal_zone_device *tz);
>   void thermal_zone_device_critical(struct thermal_zone_device *tz);
> +
> +void thermal_pm_prepare(void);
> +void thermal_pm_complete(void);
>   #else
>   static inline struct thermal_zone_device *thermal_zone_device_register_with_trips(
>   					const char *type,
> @@ -350,6 +353,9 @@ static inline int thermal_zone_device_en
>   
>   static inline int thermal_zone_device_disable(struct thermal_zone_device *tz)
>   { return -ENODEV; }
> +
> +static inline void thermal_pm_prepare(void) {}
> +static inline void thermal_pm_complete(void) {}
>   #endif /* CONFIG_THERMAL */
>   
>   #endif /* __THERMAL_H__ */
>
>
>

^ permalink raw reply

* [PATCH v1 9/9] power: supply: max17042_battery: Treat MAX17055 VChg as explicit DT setting
From: Vincent Cloutier @ 2026-04-06 20:57 UTC (permalink / raw)
  To: Sebastian Reichel
  Cc: Hans de Goede, Krzysztof Kozlowski, Marek Szyprowski,
	Sebastian Krzyszkowiak, Purism Kernel Team, linux-pm,
	linux-kernel, Vincent Cloutier
In-Reply-To: <20260406205759.493288-1-vincent.cloutier@icloud.com>

From: Vincent Cloutier <vincent@cloutier.co>

Handle `voltage-max-design-microvolt` as an explicit `ModelCfg.VChg`
setting for MAX17055 so batteries at or below 4.25 V clear the default
bit instead of silently keeping it set.

Signed-off-by: Vincent Cloutier <vincent@cloutier.co>
---
 drivers/power/supply/max17042_battery.c | 14 ++++++++++----
 1 file changed, 10 insertions(+), 4 deletions(-)

diff --git a/drivers/power/supply/max17042_battery.c b/drivers/power/supply/max17042_battery.c
index 5bf15c19fc5d..ad6a341b8a4e 100644
--- a/drivers/power/supply/max17042_battery.c
+++ b/drivers/power/supply/max17042_battery.c
@@ -812,8 +812,13 @@ static inline void max17042_override_por_values(struct max17042_chip *chip)
 		max17042_override_por(map, MAX17047_V_empty, config->vempty);
 	}
 
-	if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17055)
+	if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17055) {
 		max17042_override_por(map, MAX17055_ModelCfg, config->model_cfg);
+		/* VChg is 1 by default, so allow it to be set to 0 */
+		regmap_update_bits(map, MAX17055_ModelCfg,
+				   MAX17055_MODELCFG_VCHG_BIT,
+				   config->model_cfg);
+	}
 }
 
 static int max17042_init_chip(struct max17042_chip *chip)
@@ -1039,7 +1044,7 @@ static int max17042_apply_battery_properties(struct max17042_chip *chip,
 			(info->charge_full_design_uah != -EINVAL ||
 			 info->charge_term_current_ua != -EINVAL)) ||
 		       (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17055 &&
-			info->voltage_max_design_uv > 4250000);
+			info->voltage_max_design_uv != -EINVAL);
 	if (!needs_config)
 		return 0;
 
@@ -1068,8 +1073,9 @@ static int max17042_apply_battery_properties(struct max17042_chip *chip,
 	}
 
 	if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17055 &&
-	    info->voltage_max_design_uv > 4250000) {
-		chip->config_data->model_cfg = MAX17055_MODELCFG_VCHG_BIT;
+	    info->voltage_max_design_uv != -EINVAL) {
+		if (info->voltage_max_design_uv > 4250000)
+			chip->config_data->model_cfg = MAX17055_MODELCFG_VCHG_BIT;
 		chip->enable_por_init = true;
 	}
 
-- 
2.53.0


^ permalink raw reply related

* [PATCH v1 8/9] power: supply: Read MAX17042 battery info before registration
From: Vincent Cloutier @ 2026-04-06 20:57 UTC (permalink / raw)
  To: Sebastian Reichel
  Cc: Hans de Goede, Krzysztof Kozlowski, Marek Szyprowski,
	Sebastian Krzyszkowiak, Purism Kernel Team, linux-pm,
	linux-kernel, Vincent Cloutier
In-Reply-To: <20260406205759.493288-1-vincent.cloutier@icloud.com>

From: Vincent Cloutier <vincent@cloutier.co>

Rework the MAX17042 `monitored-battery` path so the driver can consume DT
battery information before registering its power-supply device.

To do that, factor the parser in `power_supply_core` behind a new
`devm_power_supply_get_battery_info()` helper and let battery drivers
pass the pre-parsed `battery_info` through `struct power_supply_config`.
MAX17042 can then apply the design-capacity and termination-current data
before registration without exposing the battery too early or reparsing
the same node later in the class core.

Signed-off-by: Vincent Cloutier <vincent@cloutier.co>
---
 drivers/power/supply/max17042_battery.c  |  62 ++++++++
 drivers/power/supply/power_supply_core.c | 188 ++++++++++++++---------
 include/linux/power/max17042_battery.h   |   1 +
 include/linux/power_supply.h             |  10 ++
 4 files changed, 187 insertions(+), 74 deletions(-)

diff --git a/drivers/power/supply/max17042_battery.c b/drivers/power/supply/max17042_battery.c
index 793164bd55d9..5bf15c19fc5d 100644
--- a/drivers/power/supply/max17042_battery.c
+++ b/drivers/power/supply/max17042_battery.c
@@ -1025,6 +1025,57 @@ static int max17042_init_defaults(struct max17042_chip *chip)
 	return 0;
 }
 
+static int max17042_apply_battery_properties(struct max17042_chip *chip,
+					     struct power_supply_battery_info *info)
+{
+	struct device *dev = chip->dev;
+	bool needs_config;
+	u64 data64;
+
+	if (!info)
+		return 0;
+
+	needs_config = (chip->enable_current_sense &&
+			(info->charge_full_design_uah != -EINVAL ||
+			 info->charge_term_current_ua != -EINVAL)) ||
+		       (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17055 &&
+			info->voltage_max_design_uv > 4250000);
+	if (!needs_config)
+		return 0;
+
+	if (!chip->config_data) {
+		chip->config_data = devm_kzalloc(dev,
+						 sizeof(*chip->config_data),
+						 GFP_KERNEL);
+		if (!chip->config_data)
+			return -ENOMEM;
+	}
+
+	if (chip->enable_current_sense &&
+	    info->charge_full_design_uah != -EINVAL) {
+		data64 = (u64)info->charge_full_design_uah * chip->r_sns;
+		do_div(data64, MAX17042_CAPACITY_LSB);
+		chip->config_data->design_cap = (u16)data64;
+		chip->enable_por_init = true;
+	}
+
+	if (chip->enable_current_sense &&
+	    info->charge_term_current_ua != -EINVAL) {
+		data64 = (u64)info->charge_term_current_ua * chip->r_sns;
+		do_div(data64, MAX17042_CURRENT_LSB);
+		chip->config_data->ichgt_term = (u16)data64;
+		chip->enable_por_init = true;
+	}
+
+	if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17055 &&
+	    info->voltage_max_design_uv > 4250000) {
+		chip->config_data->model_cfg = MAX17055_MODELCFG_VCHG_BIT;
+		chip->enable_por_init = true;
+	}
+
+	return 0;
+}
+
 static const struct regmap_config max17042_regmap_config = {
 	.name = "max17042",
 	.reg_bits = 8,
@@ -1060,6 +1111,7 @@ static int max17042_probe(struct i2c_client *client, struct device *dev, int irq
 	const struct power_supply_desc *max17042_desc = &max17042_psy_desc;
 	struct power_supply_config psy_cfg = {};
 	struct max17042_chip *chip;
+	struct power_supply_battery_info *battery_info = NULL;
 	int ret;
 	u32 val;
 	bool use_default_config = false;
@@ -1106,12 +1158,22 @@ static int max17042_probe(struct i2c_client *client, struct device *dev, int irq
 	if (chip->r_sns == 0)
 		chip->r_sns = MAX17042_DEFAULT_SNS_RESISTOR;
 
+	ret = devm_power_supply_get_battery_info(dev, &battery_info);
+	if (ret && ret != -ENODEV && ret != -ENOENT)
+		return ret;
+
+	ret = max17042_apply_battery_properties(chip, battery_info);
+	if (ret)
+		return ret;
+
 	if (!chip->enable_current_sense) {
 		regmap_write(chip->regmap, MAX17042_CGAIN, 0x0000);
 		regmap_write(chip->regmap, MAX17042_MiscCFG, 0x0003);
 		regmap_write(chip->regmap, MAX17042_LearnCFG, 0x0007);
 	}
 
+	psy_cfg.battery_info = battery_info;
+
 	chip->battery = devm_power_supply_register(dev, max17042_desc,
 						   &psy_cfg);
 	if (IS_ERR(chip->battery))
diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c
index a446d3d086fc..7ca3ed34095e 100644
--- a/drivers/power/supply/power_supply_core.c
+++ b/drivers/power/supply/power_supply_core.c
@@ -577,20 +577,76 @@ struct power_supply *devm_power_supply_get_by_reference(struct device *dev,
 }
 EXPORT_SYMBOL_GPL(devm_power_supply_get_by_reference);
 
-int power_supply_get_battery_info(struct power_supply *psy,
-				  struct power_supply_battery_info **info_out)
+static void __power_supply_put_battery_info(struct device *dev,
+					    struct power_supply_battery_info *info)
+{
+	int i;
+
+	for (i = 0; i < POWER_SUPPLY_OCV_TEMP_MAX; i++) {
+		if (info->ocv_table[i])
+			devm_kfree(dev, info->ocv_table[i]);
+	}
+
+	if (info->resist_table)
+		devm_kfree(dev, info->resist_table);
+
+	devm_kfree(dev, info);
+}
+
+static void power_supply_init_battery_info(struct power_supply_battery_info *info)
+{
+	int index;
+
+	info->technology = POWER_SUPPLY_TECHNOLOGY_UNKNOWN;
+	info->energy_full_design_uwh = -EINVAL;
+	info->charge_full_design_uah = -EINVAL;
+	info->voltage_min_design_uv = -EINVAL;
+	info->voltage_max_design_uv = -EINVAL;
+	info->precharge_current_ua = -EINVAL;
+	info->charge_term_current_ua = -EINVAL;
+	info->constant_charge_current_max_ua = -EINVAL;
+	info->constant_charge_voltage_max_uv = -EINVAL;
+	info->tricklecharge_current_ua = -EINVAL;
+	info->precharge_voltage_max_uv = -EINVAL;
+	info->charge_restart_voltage_uv = -EINVAL;
+	info->overvoltage_limit_uv = -EINVAL;
+	info->maintenance_charge = NULL;
+	info->alert_low_temp_charge_current_ua = -EINVAL;
+	info->alert_low_temp_charge_voltage_uv = -EINVAL;
+	info->alert_high_temp_charge_current_ua = -EINVAL;
+	info->alert_high_temp_charge_voltage_uv = -EINVAL;
+	info->temp_ambient_alert_min = INT_MIN;
+	info->temp_ambient_alert_max = INT_MAX;
+	info->temp_alert_min = INT_MIN;
+	info->temp_alert_max = INT_MAX;
+	info->temp_min = INT_MIN;
+	info->temp_max = INT_MAX;
+	info->factory_internal_resistance_uohm = -EINVAL;
+	info->resist_table = NULL;
+	info->bti_resistance_ohm = -EINVAL;
+	info->bti_resistance_tolerance = -EINVAL;
+
+	for (index = 0; index < POWER_SUPPLY_OCV_TEMP_MAX; index++) {
+		info->ocv_table[index] = NULL;
+		info->ocv_temp[index] = -EINVAL;
+		info->ocv_table_size[index] = -EINVAL;
+	}
+}
+
+static int __power_supply_get_battery_info(struct device *dev,
+					   struct fwnode_handle *srcnode,
+					   struct power_supply_battery_info **info_out)
 {
 	struct power_supply_resistance_temp_table *resist_table;
 	struct power_supply_battery_info *info;
-	struct fwnode_handle *srcnode, *fwnode;
+	struct fwnode_handle *fwnode;
 	const char *value;
 	int err, len, index, proplen;
 	u32 *propdata __free(kfree) = NULL;
 	u32 min_max[2];
 
-	srcnode = dev_fwnode(&psy->dev);
-	if (!srcnode && psy->dev.parent)
-		srcnode = dev_fwnode(psy->dev.parent);
+	if (!srcnode)
+		return -ENODEV;
 
 	fwnode = fwnode_find_reference(srcnode, "monitored-battery", 0);
 	if (IS_ERR(fwnode))
@@ -602,7 +658,7 @@ int power_supply_get_battery_info(struct power_supply *psy,
 
 
 	/* Try static batteries first */
-	err = samsung_sdi_battery_get_info(&psy->dev, value, &info);
+	err = samsung_sdi_battery_get_info(dev, value, &info);
 	if (!err)
 		goto out_ret_pointer;
 	else if (err == -ENODEV)
@@ -617,46 +673,13 @@ int power_supply_get_battery_info(struct power_supply *psy,
 		goto out_put_node;
 	}
 
-	info = devm_kzalloc(&psy->dev, sizeof(*info), GFP_KERNEL);
+	info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL);
 	if (!info) {
 		err = -ENOMEM;
 		goto out_put_node;
 	}
 
-	info->technology                     = POWER_SUPPLY_TECHNOLOGY_UNKNOWN;
-	info->energy_full_design_uwh         = -EINVAL;
-	info->charge_full_design_uah         = -EINVAL;
-	info->voltage_min_design_uv          = -EINVAL;
-	info->voltage_max_design_uv          = -EINVAL;
-	info->precharge_current_ua           = -EINVAL;
-	info->charge_term_current_ua         = -EINVAL;
-	info->constant_charge_current_max_ua = -EINVAL;
-	info->constant_charge_voltage_max_uv = -EINVAL;
-	info->tricklecharge_current_ua       = -EINVAL;
-	info->precharge_voltage_max_uv       = -EINVAL;
-	info->charge_restart_voltage_uv      = -EINVAL;
-	info->overvoltage_limit_uv           = -EINVAL;
-	info->maintenance_charge             = NULL;
-	info->alert_low_temp_charge_current_ua = -EINVAL;
-	info->alert_low_temp_charge_voltage_uv = -EINVAL;
-	info->alert_high_temp_charge_current_ua = -EINVAL;
-	info->alert_high_temp_charge_voltage_uv = -EINVAL;
-	info->temp_ambient_alert_min         = INT_MIN;
-	info->temp_ambient_alert_max         = INT_MAX;
-	info->temp_alert_min                 = INT_MIN;
-	info->temp_alert_max                 = INT_MAX;
-	info->temp_min                       = INT_MIN;
-	info->temp_max                       = INT_MAX;
-	info->factory_internal_resistance_uohm  = -EINVAL;
-	info->resist_table                   = NULL;
-	info->bti_resistance_ohm             = -EINVAL;
-	info->bti_resistance_tolerance       = -EINVAL;
-
-	for (index = 0; index < POWER_SUPPLY_OCV_TEMP_MAX; index++) {
-		info->ocv_table[index]       = NULL;
-		info->ocv_temp[index]        = -EINVAL;
-		info->ocv_table_size[index]  = -EINVAL;
-	}
+	power_supply_init_battery_info(info);
 
 	/* The property and field names below must correspond to elements
 	 * in enum power_supply_property. For reasoning, see
@@ -678,7 +701,7 @@ int power_supply_get_battery_info(struct power_supply *psy,
 		else if (!strcmp("lithium-ion-manganese-oxide", value))
 			info->technology = POWER_SUPPLY_TECHNOLOGY_LiMn;
 		else
-			dev_warn(&psy->dev, "%s unknown battery type\n", value);
+			dev_warn(dev, "%s unknown battery type\n", value);
 	}
 
 	fwnode_property_read_u32(fwnode, "energy-full-design-microwatt-hours",
@@ -729,7 +752,7 @@ int power_supply_get_battery_info(struct power_supply *psy,
 		err = len;
 		goto out_put_node;
 	} else if (len > POWER_SUPPLY_OCV_TEMP_MAX) {
-		dev_err(&psy->dev, "Too many temperature values\n");
+		dev_err(dev, "Too many temperature values\n");
 		err = -EINVAL;
 		goto out_put_node;
 	} else if (len > 0) {
@@ -744,28 +767,28 @@ int power_supply_get_battery_info(struct power_supply *psy,
 		char *propname __free(kfree) = kasprintf(GFP_KERNEL, "ocv-capacity-table-%d",
 							 index);
 		if (!propname) {
-			power_supply_put_battery_info(psy, info);
+			__power_supply_put_battery_info(dev, info);
 			err = -ENOMEM;
 			goto out_put_node;
 		}
 		proplen = fwnode_property_count_u32(fwnode, propname);
 		if (proplen < 0 || proplen % 2 != 0) {
-			dev_err(&psy->dev, "failed to get %s\n", propname);
-			power_supply_put_battery_info(psy, info);
+			dev_err(dev, "failed to get %s\n", propname);
+			__power_supply_put_battery_info(dev, info);
 			err = -EINVAL;
 			goto out_put_node;
 		}
 
 		u32 *propdata __free(kfree) = kcalloc(proplen, sizeof(*propdata), GFP_KERNEL);
 		if (!propdata) {
-			power_supply_put_battery_info(psy, info);
+			__power_supply_put_battery_info(dev, info);
 			err = -EINVAL;
 			goto out_put_node;
 		}
 		err = fwnode_property_read_u32_array(fwnode, propname, propdata, proplen);
 		if (err < 0) {
-			dev_err(&psy->dev, "failed to get %s\n", propname);
-			power_supply_put_battery_info(psy, info);
+			dev_err(dev, "failed to get %s\n", propname);
+			__power_supply_put_battery_info(dev, info);
 			goto out_put_node;
 		}
 
@@ -773,9 +796,9 @@ int power_supply_get_battery_info(struct power_supply *psy,
 		info->ocv_table_size[index] = tab_len;
 
 		info->ocv_table[index] = table =
-			devm_kcalloc(&psy->dev, tab_len, sizeof(*table), GFP_KERNEL);
+			devm_kcalloc(dev, tab_len, sizeof(*table), GFP_KERNEL);
 		if (!info->ocv_table[index]) {
-			power_supply_put_battery_info(psy, info);
+			__power_supply_put_battery_info(dev, info);
 			err = -ENOMEM;
 			goto out_put_node;
 		}
@@ -791,14 +814,14 @@ int power_supply_get_battery_info(struct power_supply *psy,
 		err = 0;
 		goto out_ret_pointer;
 	} else if (proplen < 0 || proplen % 2 != 0) {
-		power_supply_put_battery_info(psy, info);
+		__power_supply_put_battery_info(dev, info);
 		err = (proplen < 0) ? proplen : -EINVAL;
 		goto out_put_node;
 	}
 
 	propdata = kcalloc(proplen, sizeof(*propdata), GFP_KERNEL);
 	if (!propdata) {
-		power_supply_put_battery_info(psy, info);
+		__power_supply_put_battery_info(dev, info);
 		err = -ENOMEM;
 		goto out_put_node;
 	}
@@ -806,17 +829,17 @@ int power_supply_get_battery_info(struct power_supply *psy,
 	err = fwnode_property_read_u32_array(fwnode, "resistance-temp-table",
 					     propdata, proplen);
 	if (err < 0) {
-		power_supply_put_battery_info(psy, info);
+		__power_supply_put_battery_info(dev, info);
 		goto out_put_node;
 	}
 
 	info->resist_table_size = proplen / 2;
-	info->resist_table = resist_table = devm_kcalloc(&psy->dev,
-							 info->resist_table_size,
-							 sizeof(*resist_table),
-							 GFP_KERNEL);
+	info->resist_table = resist_table = devm_kcalloc(dev,
+						 info->resist_table_size,
+						 sizeof(*resist_table),
+						 GFP_KERNEL);
 	if (!info->resist_table) {
-		power_supply_put_battery_info(psy, info);
+		__power_supply_put_battery_info(dev, info);
 		err = -ENOMEM;
 		goto out_put_node;
 	}
@@ -834,22 +857,35 @@ int power_supply_get_battery_info(struct power_supply *psy,
 	fwnode_handle_put(fwnode);
 	return err;
 }
-EXPORT_SYMBOL_GPL(power_supply_get_battery_info);
 
-void power_supply_put_battery_info(struct power_supply *psy,
-				   struct power_supply_battery_info *info)
+int devm_power_supply_get_battery_info(struct device *dev,
+					      struct power_supply_battery_info **info_out)
 {
-	int i;
+	struct fwnode_handle *srcnode = dev_fwnode(dev);
 
-	for (i = 0; i < POWER_SUPPLY_OCV_TEMP_MAX; i++) {
-		if (info->ocv_table[i])
-			devm_kfree(&psy->dev, info->ocv_table[i]);
-	}
+	if (!srcnode && dev->parent)
+		srcnode = dev_fwnode(dev->parent);
 
-	if (info->resist_table)
-		devm_kfree(&psy->dev, info->resist_table);
+	return __power_supply_get_battery_info(dev, srcnode, info_out);
+}
+EXPORT_SYMBOL_GPL(devm_power_supply_get_battery_info);
+
+int power_supply_get_battery_info(struct power_supply *psy,
+				  struct power_supply_battery_info **info_out)
+{
+	struct fwnode_handle *srcnode = dev_fwnode(&psy->dev);
+
+	if (!srcnode && psy->dev.parent)
+		srcnode = dev_fwnode(psy->dev.parent);
 
-	devm_kfree(&psy->dev, info);
+	return __power_supply_get_battery_info(&psy->dev, srcnode, info_out);
+}
+EXPORT_SYMBOL_GPL(power_supply_get_battery_info);
+
+void power_supply_put_battery_info(struct power_supply *psy,
+				   struct power_supply_battery_info *info)
+{
+	__power_supply_put_battery_info(&psy->dev, info);
 }
 EXPORT_SYMBOL_GPL(power_supply_put_battery_info);
 
@@ -1620,9 +1656,13 @@ __power_supply_register(struct device *parent,
 	 * expose battery data to userspace for battery devices.
 	 */
 	if (desc->type == POWER_SUPPLY_TYPE_BATTERY) {
-		rc = power_supply_get_battery_info(psy, &psy->battery_info);
-		if (rc && rc != -ENODEV && rc != -ENOENT)
-			goto check_supplies_failed;
+		if (cfg && cfg->battery_info) {
+			psy->battery_info = cfg->battery_info;
+		} else {
+			rc = power_supply_get_battery_info(psy, &psy->battery_info);
+			if (rc && rc != -ENODEV && rc != -ENOENT)
+				goto check_supplies_failed;
+		}
 	}
 
 	spin_lock_init(&psy->changed_lock);
diff --git a/include/linux/power/max17042_battery.h b/include/linux/power/max17042_battery.h
index 6b6e01d3e499..e9f2118e57c8 100644
--- a/include/linux/power/max17042_battery.h
+++ b/include/linux/power/max17042_battery.h
@@ -24,6 +24,7 @@
 #define MAX17042_CHARACTERIZATION_DATA_SIZE 48
 
 #define MAX17055_MODELCFG_REFRESH_BIT	BIT(15)
+#define MAX17055_MODELCFG_VCHG_BIT	BIT(10)
 
 enum max17042_register {
 	MAX17042_STATUS		= 0x00,
diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h
index 360ffdf272da..f9fca7bb6e24 100644
--- a/include/linux/power_supply.h
+++ b/include/linux/power_supply.h
@@ -231,6 +231,7 @@ union power_supply_propval {
 
 struct device_node;
 struct power_supply;
+struct power_supply_battery_info;
 
 /* Run-time specific power supply configuration */
 struct power_supply_config {
@@ -239,6 +240,12 @@ struct power_supply_config {
 	/* Driver private data */
 	void *drv_data;
 
+	/*
+	 * Optional pre-parsed battery info for battery supplies.
+	 * The pointed-to data must remain valid for the power supply lifetime.
+	 */
+	struct power_supply_battery_info *battery_info;
+
 	/* Device specific sysfs attributes */
 	const struct attribute_group **attr_grp;
 
@@ -814,6 +821,9 @@ extern struct power_supply *power_supply_get_by_reference(struct fwnode_handle *
 extern struct power_supply *devm_power_supply_get_by_reference(
 				    struct device *dev, const char *property);
 
+extern int devm_power_supply_get_battery_info(struct device *dev,
+					      struct power_supply_battery_info **info_out);
+
 extern const enum power_supply_property power_supply_battery_info_properties[];
 extern const size_t power_supply_battery_info_properties_size;
 extern int power_supply_get_battery_info(struct power_supply *psy,
-- 
2.53.0


^ permalink raw reply related

* [PATCH v1 7/9] power: supply: max17042_battery: use ModelCfg refresh on max17055
From: Vincent Cloutier @ 2026-04-06 20:57 UTC (permalink / raw)
  To: Sebastian Reichel
  Cc: Hans de Goede, Krzysztof Kozlowski, Marek Szyprowski,
	Sebastian Krzyszkowiak, Purism Kernel Team, linux-pm,
	linux-kernel, Vincent Cloutier
In-Reply-To: <20260406205759.493288-1-vincent.cloutier@icloud.com>

From: Sebastian Krzyszkowiak <sebastian.krzyszkowiak@puri.sm>

Unlike other models, max17055 doesn't require cell characterization data
and operates on a smaller set of input variables (`DesignCap`, `VEmpty`,
`IChgTerm`, and `ModelCfg`). Those values can be filled in through
`max17042_override_por_values()`, but the refresh bit has to be set
afterward in order to make them apply.

Signed-off-by: Sebastian Krzyszkowiak <sebastian.krzyszkowiak@puri.sm>
Signed-off-by: Vincent Cloutier <vincent@cloutier.co>
---
 drivers/power/supply/max17042_battery.c | 66 +++++++++++++++----------
 include/linux/power/max17042_battery.h  |  3 ++
 2 files changed, 42 insertions(+), 27 deletions(-)

diff --git a/drivers/power/supply/max17042_battery.c b/drivers/power/supply/max17042_battery.c
index a96427bf07d0..793164bd55d9 100644
--- a/drivers/power/supply/max17042_battery.c
+++ b/drivers/power/supply/max17042_battery.c
@@ -811,6 +811,9 @@ static inline void max17042_override_por_values(struct max17042_chip *chip)
 	    (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17055)) {
 		max17042_override_por(map, MAX17047_V_empty, config->vempty);
 	}
+
+	if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17055)
+		max17042_override_por(map, MAX17055_ModelCfg, config->model_cfg);
 }
 
 static int max17042_init_chip(struct max17042_chip *chip)
@@ -819,44 +822,53 @@ static int max17042_init_chip(struct max17042_chip *chip)
 	int ret;
 
 	max17042_override_por_values(chip);
+
+	if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17055) {
+		regmap_write_bits(map, MAX17055_ModelCfg,
+				  MAX17055_MODELCFG_REFRESH_BIT,
+				  MAX17055_MODELCFG_REFRESH_BIT);
+	}
+
 	/* After Power up, the MAX17042 requires 500mS in order
 	 * to perform signal debouncing and initial SOC reporting
 	 */
 	msleep(500);
 
-	/* Initialize configuration */
-	max17042_write_config_regs(chip);
+	if (chip->chip_type != MAXIM_DEVICE_TYPE_MAX17055) {
+		/* Initialize configuration */
+		max17042_write_config_regs(chip);
 
-	/* write cell characterization data */
-	ret = max17042_init_model(chip);
-	if (ret) {
-		dev_err(chip->dev, "%s init failed\n",
-			__func__);
-		return -EIO;
-	}
+		/* write cell characterization data */
+		ret = max17042_init_model(chip);
+		if (ret) {
+			dev_err(chip->dev, "%s init failed\n",
+				__func__);
+			return -EIO;
+		}
 
-	ret = max17042_verify_model_lock(chip);
-	if (ret) {
-		dev_err(chip->dev, "%s lock verify failed\n",
-			__func__);
-		return -EIO;
-	}
-	/* write custom parameters */
-	max17042_write_custom_regs(chip);
+		ret = max17042_verify_model_lock(chip);
+		if (ret) {
+			dev_err(chip->dev, "%s lock verify failed\n",
+				__func__);
+			return -EIO;
+		}
+		/* write custom parameters */
+		max17042_write_custom_regs(chip);
 
-	/* update capacity params */
-	max17042_update_capacity_regs(chip);
+		/* update capacity params */
+		max17042_update_capacity_regs(chip);
 
-	/* delay must be atleast 350mS to allow VFSOC
-	 * to be calculated from the new configuration
-	 */
-	msleep(350);
+		/* delay must be atleast 350mS to allow VFSOC
+		 * to be calculated from the new configuration
+		 */
+		msleep(350);
 
-	/* reset vfsoc0 reg */
-	max17042_reset_vfsoc0_reg(chip);
+		/* reset vfsoc0 reg */
+		max17042_reset_vfsoc0_reg(chip);
 
-	/* load new capacity params */
-	max17042_load_new_capacity_params(chip);
+		/* load new capacity params */
+		max17042_load_new_capacity_params(chip);
+	}
 
 	/* Init complete, Clear the POR bit */
 	regmap_update_bits(map, MAX17042_STATUS, STATUS_POR_BIT, 0x0);
diff --git a/include/linux/power/max17042_battery.h b/include/linux/power/max17042_battery.h
index 36817da50548..6b6e01d3e499 100644
--- a/include/linux/power/max17042_battery.h
+++ b/include/linux/power/max17042_battery.h
@@ -23,6 +23,8 @@
 
 #define MAX17042_CHARACTERIZATION_DATA_SIZE 48
 
+#define MAX17055_MODELCFG_REFRESH_BIT	BIT(15)
+
 enum max17042_register {
 	MAX17042_STATUS		= 0x00,
 	MAX17042_VALRT_Th	= 0x01,
@@ -198,6 +200,7 @@ struct max17042_config_data {
 	u16	full_soc_thresh;	/* 0x13 */
 	u16	design_cap;	/* 0x18 */
 	u16	ichgt_term;	/* 0x1E */
+	u16	model_cfg;	/* 0xDB */
 
 	/* MG3 config */
 	u16	at_rate;	/* 0x04 */
-- 
2.53.0

^ permalink raw reply related

* [PATCH v1 6/9] power: supply: max17042_battery: Remove unused platform-data plumbing
From: Vincent Cloutier @ 2026-04-06 20:57 UTC (permalink / raw)
  To: Sebastian Reichel
  Cc: Hans de Goede, Krzysztof Kozlowski, Marek Szyprowski,
	Sebastian Krzyszkowiak, Purism Kernel Team, linux-pm,
	linux-kernel, Vincent Cloutier
In-Reply-To: <20260406205759.493288-1-vincent.cloutier@icloud.com>

From: Vincent Cloutier <vincent@cloutier.co>

No in-tree user still provides `max17042_platform_data` or
`max17042_reg_data`. Move the simple runtime fields into
`struct max17042_chip`, populate them directly from DT or the default
hardware state, and drop the unused public platform-data interface.

While here, write the MAX17047/MAX17050 default `FullSOCThr` value
directly in probe instead of carrying it through an `init_data` table.

Signed-off-by: Vincent Cloutier <vincent@cloutier.co>
---
 drivers/power/supply/max17042_battery.c | 204 +++++++++++-------------
 include/linux/power/max17042_battery.h  |  29 ----
 2 files changed, 89 insertions(+), 144 deletions(-)

diff --git a/drivers/power/supply/max17042_battery.c b/drivers/power/supply/max17042_battery.c
index b93fedf183b1..a96427bf07d0 100644
--- a/drivers/power/supply/max17042_battery.c
+++ b/drivers/power/supply/max17042_battery.c
@@ -68,10 +68,17 @@ struct max17042_chip {
 	struct regmap *regmap;
 	struct power_supply *battery;
 	enum max170xx_chip_type chip_type;
-	struct max17042_platform_data *pdata;
+	struct max17042_config_data *config_data;
 	struct work_struct work;
 	int    init_complete;
 	int    irq;
+	bool   enable_current_sense;
+	bool   enable_por_init;
+	unsigned int r_sns;
+	int    vmin;	/* in millivolts */
+	int    vmax;	/* in millivolts */
+	int    temp_min;	/* in tenths of degree Celsius */
+	int    temp_max;	/* in tenths of degree Celsius */
 };
 
 static enum power_supply_property max17042_battery_props[] = {
@@ -163,7 +170,7 @@ static int max17042_get_status(struct max17042_chip *chip, int *status)
 	 * Even though we are supplied, we may still be discharging if the
 	 * supply is e.g. only delivering 5V 0.5A. Check current if available.
 	 */
-	if (!chip->pdata->enable_current_sense) {
+	if (!chip->enable_current_sense) {
 		*status = POWER_SUPPLY_STATUS_CHARGING;
 		return 0;
 	}
@@ -205,12 +212,12 @@ static int max17042_get_battery_health(struct max17042_chip *chip, int *health)
 	/* Convert to millivolts */
 	vbatt /= 1000;
 
-	if (vavg < chip->pdata->vmin) {
+	if (vavg < chip->vmin) {
 		*health = POWER_SUPPLY_HEALTH_DEAD;
 		goto out;
 	}
 
-	if (vbatt > chip->pdata->vmax + MAX17042_VMAX_TOLERANCE) {
+	if (vbatt > chip->vmax + MAX17042_VMAX_TOLERANCE) {
 		*health = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
 		goto out;
 	}
@@ -219,12 +226,12 @@ static int max17042_get_battery_health(struct max17042_chip *chip, int *health)
 	if (ret < 0)
 		goto health_error;
 
-	if (temp < chip->pdata->temp_min) {
+	if (temp < chip->temp_min) {
 		*health = POWER_SUPPLY_HEALTH_COLD;
 		goto out;
 	}
 
-	if (temp > chip->pdata->temp_max) {
+	if (temp > chip->temp_max) {
 		*health = POWER_SUPPLY_HEALTH_OVERHEAT;
 		goto out;
 	}
@@ -325,7 +332,7 @@ static int max17042_get_property(struct power_supply *psy,
 		val->intval = data * MAX17042_VOLTAGE_LSB;
 		break;
 	case POWER_SUPPLY_PROP_CAPACITY:
-		if (chip->pdata->enable_current_sense)
+		if (chip->enable_current_sense)
 			ret = regmap_read(map, MAX17042_RepSOC, &data);
 		else
 			ret = regmap_read(map, MAX17042_VFSOC, &data);
@@ -340,7 +347,7 @@ static int max17042_get_property(struct power_supply *psy,
 			return ret;
 
 		data64 = data * MAX17042_CAPACITY_LSB;
-		do_div(data64, chip->pdata->r_sns);
+		do_div(data64, chip->r_sns);
 		val->intval = data64;
 		break;
 	case POWER_SUPPLY_PROP_CHARGE_FULL:
@@ -349,7 +356,7 @@ static int max17042_get_property(struct power_supply *psy,
 			return ret;
 
 		data64 = data * MAX17042_CAPACITY_LSB;
-		do_div(data64, chip->pdata->r_sns);
+		do_div(data64, chip->r_sns);
 		val->intval = data64;
 		break;
 	case POWER_SUPPLY_PROP_CHARGE_NOW:
@@ -358,7 +365,7 @@ static int max17042_get_property(struct power_supply *psy,
 			return ret;
 
 		data64 = data * MAX17042_CAPACITY_LSB;
-		do_div(data64, chip->pdata->r_sns);
+		do_div(data64, chip->r_sns);
 		val->intval = data64;
 		break;
 	case POWER_SUPPLY_PROP_CHARGE_COUNTER:
@@ -367,7 +374,7 @@ static int max17042_get_property(struct power_supply *psy,
 			return ret;
 
 		data64 = sign_extend64(data, 15) * MAX17042_CAPACITY_LSB;
-		val->intval = div_s64(data64, chip->pdata->r_sns);
+		val->intval = div_s64(data64, chip->r_sns);
 		break;
 	case POWER_SUPPLY_PROP_TEMP:
 		ret = max17042_get_temperature(chip, &val->intval);
@@ -389,10 +396,10 @@ static int max17042_get_property(struct power_supply *psy,
 		val->intval = sign_extend32(data >> 8, 7) * 10;
 		break;
 	case POWER_SUPPLY_PROP_TEMP_MIN:
-		val->intval = chip->pdata->temp_min;
+		val->intval = chip->temp_min;
 		break;
 	case POWER_SUPPLY_PROP_TEMP_MAX:
-		val->intval = chip->pdata->temp_max;
+		val->intval = chip->temp_max;
 		break;
 	case POWER_SUPPLY_PROP_HEALTH:
 		ret = max17042_get_battery_health(chip, &val->intval);
@@ -403,25 +410,25 @@ static int max17042_get_property(struct power_supply *psy,
 		val->intval = POWER_SUPPLY_SCOPE_SYSTEM;
 		break;
 	case POWER_SUPPLY_PROP_CURRENT_NOW:
-		if (chip->pdata->enable_current_sense) {
+		if (chip->enable_current_sense) {
 			ret = regmap_read(map, MAX17042_Current, &data);
 			if (ret < 0)
 				return ret;
 
 			data64 = sign_extend64(data, 15) * MAX17042_CURRENT_LSB;
-			val->intval = div_s64(data64, chip->pdata->r_sns);
+			val->intval = div_s64(data64, chip->r_sns);
 		} else {
 			return -EINVAL;
 		}
 		break;
 	case POWER_SUPPLY_PROP_CURRENT_AVG:
-		if (chip->pdata->enable_current_sense) {
+		if (chip->enable_current_sense) {
 			ret = regmap_read(map, MAX17042_AvgCurrent, &data);
 			if (ret < 0)
 				return ret;
 
 			data64 = sign_extend64(data, 15) * MAX17042_CURRENT_LSB;
-			val->intval = div_s64(data64, chip->pdata->r_sns);
+			val->intval = div_s64(data64, chip->r_sns);
 		} else {
 			return -EINVAL;
 		}
@@ -432,7 +439,7 @@ static int max17042_get_property(struct power_supply *psy,
 			return ret;
 
 		data64 = data * MAX17042_CURRENT_LSB;
-		val->intval = div_s64(data64, chip->pdata->r_sns);
+		val->intval = div_s64(data64, chip->r_sns);
 		break;
 	case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW:
 		ret = regmap_read(map, MAX17042_TTE, &data);
@@ -562,7 +569,7 @@ static inline void max17042_write_model_data(struct max17042_chip *chip,
 
 	for (i = 0; i < size; i++)
 		regmap_write(map, addr + i,
-			chip->pdata->config_data->cell_char_tbl[i]);
+			chip->config_data->cell_char_tbl[i]);
 }
 
 static inline void max17042_read_model_data(struct max17042_chip *chip,
@@ -597,7 +604,7 @@ static inline int max17042_model_data_compare(struct max17042_chip *chip,
 static int max17042_init_model(struct max17042_chip *chip)
 {
 	int ret;
-	int table_size = ARRAY_SIZE(chip->pdata->config_data->cell_char_tbl);
+	int table_size = ARRAY_SIZE(chip->config_data->cell_char_tbl);
 	u16 *temp_data;
 
 	temp_data = kcalloc(table_size, sizeof(*temp_data), GFP_KERNEL);
@@ -612,7 +619,7 @@ static int max17042_init_model(struct max17042_chip *chip)
 
 	ret = max17042_model_data_compare(
 		chip,
-		chip->pdata->config_data->cell_char_tbl,
+		chip->config_data->cell_char_tbl,
 		temp_data,
 		table_size);
 
@@ -625,7 +632,7 @@ static int max17042_init_model(struct max17042_chip *chip)
 static int max17042_verify_model_lock(struct max17042_chip *chip)
 {
 	int i;
-	int table_size = ARRAY_SIZE(chip->pdata->config_data->cell_char_tbl);
+	int table_size = ARRAY_SIZE(chip->config_data->cell_char_tbl);
 	u16 *temp_data;
 	int ret = 0;
 
@@ -645,7 +652,7 @@ static int max17042_verify_model_lock(struct max17042_chip *chip)
 
 static void max17042_write_config_regs(struct max17042_chip *chip)
 {
-	struct max17042_config_data *config = chip->pdata->config_data;
+	struct max17042_config_data *config = chip->config_data;
 	struct regmap *map = chip->regmap;
 
 	regmap_write(map, MAX17042_CONFIG, config->config);
@@ -662,7 +669,7 @@ static void max17042_write_config_regs(struct max17042_chip *chip)
 
 static void  max17042_write_custom_regs(struct max17042_chip *chip)
 {
-	struct max17042_config_data *config = chip->pdata->config_data;
+	struct max17042_config_data *config = chip->config_data;
 	struct regmap *map = chip->regmap;
 
 	max17042_write_verify_reg(map, MAX17042_RCOMP0, config->rcomp0);
@@ -686,7 +693,7 @@ static void  max17042_write_custom_regs(struct max17042_chip *chip)
 
 static void max17042_update_capacity_regs(struct max17042_chip *chip)
 {
-	struct max17042_config_data *config = chip->pdata->config_data;
+	struct max17042_config_data *config = chip->config_data;
 	struct regmap *map = chip->regmap;
 
 	max17042_write_verify_reg(map, MAX17042_FullCAP,
@@ -712,7 +719,7 @@ static void max17042_load_new_capacity_params(struct max17042_chip *chip)
 	u32 full_cap0, rep_cap, dq_acc, vfSoc;
 	u32 rem_cap;
 
-	struct max17042_config_data *config = chip->pdata->config_data;
+	struct max17042_config_data *config = chip->config_data;
 	struct regmap *map = chip->regmap;
 
 	regmap_read(map, MAX17042_FullCAP0, &full_cap0);
@@ -744,14 +751,14 @@ static void max17042_load_new_capacity_params(struct max17042_chip *chip)
 }
 
 /*
- * Block write all the override values coming from platform data.
+ * Block write all the override values coming from config_data.
  * This function MUST be called before the POR initialization procedure
  * specified by maxim.
  */
 static inline void max17042_override_por_values(struct max17042_chip *chip)
 {
 	struct regmap *map = chip->regmap;
-	struct max17042_config_data *config = chip->pdata->config_data;
+	struct max17042_config_data *config = chip->config_data;
 
 	max17042_override_por(map, MAX17042_TGAIN, config->tgain);
 	max17042_override_por(map, MAX17042_TOFF, config->toff);
@@ -864,7 +871,7 @@ static void max17042_set_soc_threshold(struct max17042_chip *chip, u16 off)
 	/* program interrupt thresholds such that we should
 	 * get interrupt for every 'off' perc change in the soc
 	 */
-	if (chip->pdata->enable_current_sense)
+	if (chip->enable_current_sense)
 		regmap_read(map, MAX17042_RepSOC, &soc);
 	else
 		regmap_read(map, MAX17042_VFSOC, &soc);
@@ -880,7 +887,7 @@ static void max17042_set_critical_soc_threshold(struct max17042_chip *chip)
 	struct regmap *map = chip->regmap;
 	u32 soc;
 
-	if (chip->pdata->enable_current_sense)
+	if (chip->enable_current_sense)
 		regmap_read(map, MAX17042_RepSOC, &soc);
 	else
 		regmap_read(map, MAX17042_VFSOC, &soc);
@@ -943,8 +950,8 @@ static void max17042_init_worker(struct work_struct *work)
 				struct max17042_chip, work);
 	int ret;
 
-	/* Initialize registers according to values from the platform data */
-	if (chip->pdata->enable_por_init && chip->pdata->config_data) {
+	/* Initialize registers according to values from config_data */
+	if (chip->enable_por_init && chip->config_data) {
 		ret = max17042_init_chip(chip);
 		if (ret)
 			return;
@@ -954,102 +961,56 @@ static void max17042_init_worker(struct work_struct *work)
 }
 
 #ifdef CONFIG_OF
-static struct max17042_platform_data *
-max17042_get_of_pdata(struct max17042_chip *chip)
+static int max17042_parse_dt(struct max17042_chip *chip)
 {
-	struct device *dev = chip->dev;
-	struct device_node *np = dev->of_node;
+	struct device_node *np = chip->dev->of_node;
 	u32 prop;
-	struct max17042_platform_data *pdata;
-
-	pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
-	if (!pdata)
-		return NULL;
 
 	/*
 	 * Require current sense resistor value to be specified for
 	 * current-sense functionality to be enabled at all.
 	 */
 	if (of_property_read_u32(np, "maxim,rsns-microohm", &prop) == 0) {
-		pdata->r_sns = prop;
-		pdata->enable_current_sense = true;
+		chip->r_sns = prop;
+		chip->enable_current_sense = true;
 	}
 
-	if (of_property_read_s32(np, "maxim,cold-temp", &pdata->temp_min))
-		pdata->temp_min = INT_MIN;
-	if (of_property_read_s32(np, "maxim,over-heat-temp", &pdata->temp_max))
-		pdata->temp_max = INT_MAX;
-	if (of_property_read_s32(np, "maxim,dead-volt", &pdata->vmin))
-		pdata->vmin = INT_MIN;
-	if (of_property_read_s32(np, "maxim,over-volt", &pdata->vmax))
-		pdata->vmax = INT_MAX;
+	if (of_property_read_s32(np, "maxim,cold-temp", &chip->temp_min))
+		chip->temp_min = INT_MIN;
+	if (of_property_read_s32(np, "maxim,over-heat-temp", &chip->temp_max))
+		chip->temp_max = INT_MAX;
+	if (of_property_read_s32(np, "maxim,dead-volt", &chip->vmin))
+		chip->vmin = INT_MIN;
+	if (of_property_read_s32(np, "maxim,over-volt", &chip->vmax))
+		chip->vmax = INT_MAX;
 
-	return pdata;
+	return 0;
 }
 #endif
 
-static struct max17042_reg_data max17047_default_pdata_init_regs[] = {
-	/*
-	 * Some firmwares do not set FullSOCThr, Enable End-of-Charge Detection
-	 * when the voltage FG reports 95%, as recommended in the datasheet.
-	 */
-	{ MAX17047_FullSOCThr, MAX17042_BATTERY_FULL << 8 },
-};
-
-static struct max17042_platform_data *
-max17042_get_default_pdata(struct max17042_chip *chip)
+static int max17042_init_defaults(struct max17042_chip *chip)
 {
-	struct device *dev = chip->dev;
-	struct max17042_platform_data *pdata;
 	int ret, misc_cfg;
 
 	/*
-	 * The MAX17047 gets used on x86 where we might not have pdata, assume
+	 * The MAX17047 gets used on x86 where we might not have DT, assume
 	 * the firmware will already have initialized the fuel-gauge and provide
 	 * default values for the non init bits to make things work.
 	 */
-	pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
-	if (!pdata)
-		return pdata;
-
-	if ((chip->chip_type == MAXIM_DEVICE_TYPE_MAX17047) ||
-	    (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17050)) {
-		pdata->init_data = max17047_default_pdata_init_regs;
-		pdata->num_init_data =
-			ARRAY_SIZE(max17047_default_pdata_init_regs);
-	}
 
 	ret = regmap_read(chip->regmap, MAX17042_MiscCFG, &misc_cfg);
 	if (ret < 0)
-		return NULL;
+		return ret;
 
 	/* If bits 0-1 are set to 3 then only Voltage readings are used */
-	if ((misc_cfg & 0x3) == 0x3)
-		pdata->enable_current_sense = false;
-	else
-		pdata->enable_current_sense = true;
+	chip->enable_current_sense = (misc_cfg & 0x3) != 0x3;
 
-	pdata->vmin = MAX17042_DEFAULT_VMIN;
-	pdata->vmax = MAX17042_DEFAULT_VMAX;
-	pdata->temp_min = MAX17042_DEFAULT_TEMP_MIN;
-	pdata->temp_max = MAX17042_DEFAULT_TEMP_MAX;
+	chip->vmin = MAX17042_DEFAULT_VMIN;
+	chip->vmax = MAX17042_DEFAULT_VMAX;
+	chip->temp_min = MAX17042_DEFAULT_TEMP_MIN;
+	chip->temp_max = MAX17042_DEFAULT_TEMP_MAX;
 
-	return pdata;
-}
-
-static struct max17042_platform_data *
-max17042_get_pdata(struct max17042_chip *chip)
-{
-	struct device *dev = chip->dev;
-
-#ifdef CONFIG_OF
-	if (dev->of_node)
-		return max17042_get_of_pdata(chip);
-#endif
-	if (dev->platform_data)
-		return dev->platform_data;
-
-	return max17042_get_default_pdata(chip);
+	return 0;
 }
 
 static const struct regmap_config max17042_regmap_config = {
@@ -1088,8 +1049,8 @@ static int max17042_probe(struct i2c_client *client, struct device *dev, int irq
 	struct power_supply_config psy_cfg = {};
 	struct max17042_chip *chip;
 	int ret;
-	int i;
 	u32 val;
+	bool use_default_config = false;
 
 	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA))
 		return -EIO;
@@ -1106,10 +1067,19 @@ static int max17042_probe(struct i2c_client *client, struct device *dev, int irq
 		return -EINVAL;
 	}
 
-	chip->pdata = max17042_get_pdata(chip);
-	if (!chip->pdata) {
-		dev_err(dev, "no platform data provided\n");
-		return -EINVAL;
+
+#ifdef CONFIG_OF
+	if (dev->of_node) {
+		ret = max17042_parse_dt(chip);
+		if (ret)
+			return ret;
+	} else
+#endif
+	{
+		ret = max17042_init_defaults(chip);
+		if (ret)
+			return ret;
+		use_default_config = true;
 	}
 
 	dev_set_drvdata(dev, chip);
@@ -1118,19 +1088,13 @@ static int max17042_probe(struct i2c_client *client, struct device *dev, int irq
 
 	/* When current is not measured,
 	 * CURRENT_NOW and CURRENT_AVG properties should be invisible. */
-	if (!chip->pdata->enable_current_sense)
+	if (!chip->enable_current_sense)
 		max17042_desc = &max17042_no_current_sense_psy_desc;
 
-	if (chip->pdata->r_sns == 0)
-		chip->pdata->r_sns = MAX17042_DEFAULT_SNS_RESISTOR;
+	if (chip->r_sns == 0)
+		chip->r_sns = MAX17042_DEFAULT_SNS_RESISTOR;
 
-	if (chip->pdata->init_data)
-		for (i = 0; i < chip->pdata->num_init_data; i++)
-			regmap_write(chip->regmap,
-					chip->pdata->init_data[i].addr,
-					chip->pdata->init_data[i].data);
-
-	if (!chip->pdata->enable_current_sense) {
+	if (!chip->enable_current_sense) {
 		regmap_write(chip->regmap, MAX17042_CGAIN, 0x0000);
 		regmap_write(chip->regmap, MAX17042_MiscCFG, 0x0003);
 		regmap_write(chip->regmap, MAX17042_LearnCFG, 0x0007);
@@ -1142,6 +1106,16 @@ static int max17042_probe(struct i2c_client *client, struct device *dev, int irq
 		return dev_err_probe(dev, PTR_ERR(chip->battery),
 				     "failed: power supply register\n");
 
+	/*
+	 * Some firmwares do not set FullSOCThr, Enable End-of-Charge Detection
+	 * when the voltage FG reports 95%, as recommended in the datasheet.
+	 */
+	if (use_default_config &&
+	    (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17047 ||
+	     chip->chip_type == MAXIM_DEVICE_TYPE_MAX17050))
+		regmap_write(chip->regmap, MAX17047_FullSOCThr,
+			     MAX17042_BATTERY_FULL << 8);
+
 	if (irq) {
 		unsigned int flags = IRQF_ONESHOT | IRQF_SHARED | IRQF_PROBE_SHARED;
 
diff --git a/include/linux/power/max17042_battery.h b/include/linux/power/max17042_battery.h
index c417abd2ab70..36817da50548 100644
--- a/include/linux/power/max17042_battery.h
+++ b/include/linux/power/max17042_battery.h
@@ -177,16 +177,6 @@ enum max170xx_chip_type {
 	MAXIM_DEVICE_TYPE_NUM
 };
 
-/*
- * used for setting a register to a desired value
- * addr : address for a register
- * data : setting value for the register
- */
-struct max17042_reg_data {
-	u8 addr;
-	u16 data;
-};
-
 struct max17042_config_data {
 	/* External current sense resistor value in milli-ohms */
 	u32	cur_sense_val;
@@ -244,23 +234,4 @@ struct max17042_config_data {
 	u16	cell_char_tbl[MAX17042_CHARACTERIZATION_DATA_SIZE];
 } __packed;
 
-struct max17042_platform_data {
-	struct max17042_reg_data *init_data;
-	struct max17042_config_data *config_data;
-	int num_init_data; /* Number of enties in init_data array */
-	bool enable_current_sense;
-	bool enable_por_init; /* Use POR init from Maxim appnote */
-
-	/*
-	 * R_sns in micro-ohms.
-	 * default 10000 (if r_sns = 0) as it is the recommended value by
-	 * the datasheet although it can be changed by board designers.
-	 */
-	unsigned int r_sns;
-	int         vmin;	/* in millivolts */
-	int         vmax;	/* in millivolts */
-	int         temp_min;	/* in tenths of degree Celsius */
-	int         temp_max;	/* in tenths of degree Celsius */
-};
-
 #endif /* __MAX17042_BATTERY_H_ */
-- 
2.53.0


^ permalink raw reply related

* [PATCH v1 5/9] power: supply: max17042_battery: Keep only critical alerts during suspend
From: Vincent Cloutier @ 2026-04-06 20:57 UTC (permalink / raw)
  To: Sebastian Reichel
  Cc: Hans de Goede, Krzysztof Kozlowski, Marek Szyprowski,
	Sebastian Krzyszkowiak, Purism Kernel Team, linux-pm,
	linux-kernel, Vincent Cloutier
In-Reply-To: <20260406205759.493288-1-vincent.cloutier@icloud.com>

From: Vincent Cloutier <vincent@cloutier.co>

Disable MAX17055 dSOCi while the system is suspended so state-of-charge changes do not wake the system repeatedly. Leave SALRT armed for the critical low-battery threshold and restore runtime alert handling on resume.

Signed-off-by: Vincent Cloutier <vincent@cloutier.co>
---
 drivers/power/supply/max17042_battery.c | 15 +++++++++++++--
 1 file changed, 13 insertions(+), 2 deletions(-)

diff --git a/drivers/power/supply/max17042_battery.c b/drivers/power/supply/max17042_battery.c
index 09bb1babb0d6..b93fedf183b1 100644
--- a/drivers/power/supply/max17042_battery.c
+++ b/drivers/power/supply/max17042_battery.c
@@ -903,6 +903,16 @@ static void max17042_enable_soc_alerts(struct max17042_chip *chip)
 	max17042_set_soc_threshold(chip, 1);
 }
 
+static void max17042_suspend_soc_alerts(struct max17042_chip *chip)
+{
+	if (chip->chip_type != MAXIM_DEVICE_TYPE_MAX17055)
+		return;
+
+	regmap_update_bits(chip->regmap, MAX17055_Config2,
+			   CFG2_DSOCI_BIT_ENBL, 0);
+	max17042_set_critical_soc_threshold(chip);
+}
+
 static irqreturn_t max17042_thread_handler(int id, void *dev)
 {
 	struct max17042_chip *chip = dev;
@@ -1220,6 +1230,7 @@ static int max17042_suspend(struct device *dev)
 	 */
 	if (chip->irq) {
 		disable_irq(chip->irq);
+		max17042_suspend_soc_alerts(chip);
 		enable_irq_wake(chip->irq);
 	}
 
@@ -1233,8 +1244,8 @@ static int max17042_resume(struct device *dev)
 	if (chip->irq) {
 		disable_irq_wake(chip->irq);
 		enable_irq(chip->irq);
-		/* re-program the SOC thresholds to 1% change */
-		max17042_set_soc_threshold(chip, 1);
+		/* re-arm runtime SOC alerts */
+		max17042_enable_soc_alerts(chip);
 	}
 
 	return 0;
-- 
2.53.0


^ permalink raw reply related

* [PATCH v1 4/9] power: supply: max17042_battery: Route MAX17055 SOC alerts through dSOCi
From: Vincent Cloutier @ 2026-04-06 20:57 UTC (permalink / raw)
  To: Sebastian Reichel
  Cc: Hans de Goede, Krzysztof Kozlowski, Marek Szyprowski,
	Sebastian Krzyszkowiak, Purism Kernel Team, linux-pm,
	linux-kernel, Vincent Cloutier
In-Reply-To: <20260406205759.493288-1-vincent.cloutier@icloud.com>

From: Vincent Cloutier <vincent@cloutier.co>

Use MAX17055 dSOCi for ordinary 1% state-of-charge notifications and leave SALRT configured for the critical low-battery threshold instead of reprogramming the SALRT window on every alert.

Signed-off-by: Vincent Cloutier <vincent@cloutier.co>
---
 drivers/power/supply/max17042_battery.c | 39 +++++++++++++++++++++++--
 1 file changed, 36 insertions(+), 3 deletions(-)

diff --git a/drivers/power/supply/max17042_battery.c b/drivers/power/supply/max17042_battery.c
index 31007ba122e5..09bb1babb0d6 100644
--- a/drivers/power/supply/max17042_battery.c
+++ b/drivers/power/supply/max17042_battery.c
@@ -27,6 +27,7 @@
 /* Status register bits */
 #define STATUS_POR_BIT         (1 << 1)
 #define STATUS_BST_BIT         (1 << 3)
+#define STATUS_DSOCI_BIT       (1 << 7)
 #define STATUS_VMN_BIT         (1 << 8)
 #define STATUS_TMN_BIT         (1 << 9)
 #define STATUS_SMN_BIT         (1 << 10)
@@ -38,6 +39,7 @@
 
 /* Interrupt mask bits */
 #define CFG_ALRT_BIT_ENBL	(1 << 2)
+#define CFG2_DSOCI_BIT_ENBL	(1 << 7)
 
 #define VFSOC0_LOCK		0x0000
 #define VFSOC0_UNLOCK		0x0080
@@ -52,6 +54,8 @@
 
 #define MAX17042_VMAX_TOLERANCE		50 /* 50 mV */
 
+#define MAX17042_CRITICAL_SOC		0x03
+
 #define MAX17042_CURRENT_LSB		1562500ll /* 1.5625µV/Rsense */
 #define MAX17042_CAPACITY_LSB		5000000ll /* 5.0µVH/Rsense */
 #define MAX17042_TIME_LSB		5625 / 1000 /* s */
@@ -871,6 +875,34 @@ static void max17042_set_soc_threshold(struct max17042_chip *chip, u16 off)
 	regmap_write(map, MAX17042_SALRT_Th, soc_tr);
 }
 
+static void max17042_set_critical_soc_threshold(struct max17042_chip *chip)
+{
+	struct regmap *map = chip->regmap;
+	u32 soc;
+
+	if (chip->pdata->enable_current_sense)
+		regmap_read(map, MAX17042_RepSOC, &soc);
+	else
+		regmap_read(map, MAX17042_VFSOC, &soc);
+
+	regmap_write(map, MAX17042_SALRT_Th,
+		     ((soc >> 8) >= MAX17042_CRITICAL_SOC) ?
+		     0xff00 + MAX17042_CRITICAL_SOC : 0xff00);
+}
+
+static void max17042_enable_soc_alerts(struct max17042_chip *chip)
+{
+	if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17055) {
+		regmap_update_bits(chip->regmap, MAX17055_Config2,
+				   CFG2_DSOCI_BIT_ENBL,
+				   CFG2_DSOCI_BIT_ENBL);
+		max17042_set_critical_soc_threshold(chip);
+		return;
+	}
+
+	max17042_set_soc_threshold(chip, 1);
+}
+
 static irqreturn_t max17042_thread_handler(int id, void *dev)
 {
 	struct max17042_chip *chip = dev;
@@ -881,9 +913,10 @@ static irqreturn_t max17042_thread_handler(int id, void *dev)
 	if (ret)
 		return IRQ_HANDLED;
 
-	if ((val & STATUS_SMN_BIT) || (val & STATUS_SMX_BIT)) {
+	if ((val & STATUS_SMN_BIT) || (val & STATUS_SMX_BIT) ||
+	    (val & STATUS_DSOCI_BIT)) {
 		dev_dbg(chip->dev, "SOC threshold INTR\n");
-		max17042_set_soc_threshold(chip, 1);
+		max17042_enable_soc_alerts(chip);
 	}
 
 	/* we implicitly handle all alerts via power_supply_changed */
@@ -1111,7 +1144,7 @@ static int max17042_probe(struct i2c_client *client, struct device *dev, int irq
 			regmap_update_bits(chip->regmap, MAX17042_CONFIG,
 					CFG_ALRT_BIT_ENBL,
 					CFG_ALRT_BIT_ENBL);
-			max17042_set_soc_threshold(chip, 1);
+			max17042_enable_soc_alerts(chip);
 		} else {
 			irq = 0;
 			if (ret != -EBUSY)
-- 
2.53.0

^ permalink raw reply related

* [PATCH v1 3/9] power: supply: max17042_battery: Use dev_err_probe for power supply registration
From: Vincent Cloutier @ 2026-04-06 20:57 UTC (permalink / raw)
  To: Sebastian Reichel
  Cc: Hans de Goede, Krzysztof Kozlowski, Marek Szyprowski,
	Sebastian Krzyszkowiak, Purism Kernel Team, linux-pm,
	linux-kernel, Vincent Cloutier
In-Reply-To: <20260406205759.493288-1-vincent.cloutier@icloud.com>

From: Vincent Cloutier <vincent@cloutier.co>

Use dev_err_probe() for power-supply registration failures so deferred probe does not spam the log and the original error code is preserved.

Signed-off-by: Vincent Cloutier <vincent@cloutier.co>
---
 drivers/power/supply/max17042_battery.c | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/drivers/power/supply/max17042_battery.c b/drivers/power/supply/max17042_battery.c
index ddeba965ab5b..31007ba122e5 100644
--- a/drivers/power/supply/max17042_battery.c
+++ b/drivers/power/supply/max17042_battery.c
@@ -1095,10 +1095,9 @@ static int max17042_probe(struct i2c_client *client, struct device *dev, int irq
 
 	chip->battery = devm_power_supply_register(dev, max17042_desc,
 						   &psy_cfg);
-	if (IS_ERR(chip->battery)) {
-		dev_err(dev, "failed: power supply register\n");
-		return PTR_ERR(chip->battery);
-	}
+	if (IS_ERR(chip->battery))
+		return dev_err_probe(dev, PTR_ERR(chip->battery),
+				     "failed: power supply register\n");
 
 	if (irq) {
 		unsigned int flags = IRQF_ONESHOT | IRQF_SHARED | IRQF_PROBE_SHARED;
-- 
2.53.0


^ permalink raw reply related

* [PATCH v1 2/9] power: supply: max17042_battery: Use Current register in get_status
From: Vincent Cloutier @ 2026-04-06 20:57 UTC (permalink / raw)
  To: Sebastian Reichel
  Cc: Hans de Goede, Krzysztof Kozlowski, Marek Szyprowski,
	Sebastian Krzyszkowiak, Purism Kernel Team, linux-pm,
	linux-kernel
In-Reply-To: <20260406205759.493288-1-vincent.cloutier@icloud.com>

From: Sebastian Krzyszkowiak <sebastian.krzyszkowiak@puri.sm>

It can take a while for AvgCurrent to adjust after (un)plugging the charger. Use the instantaneous value in order to not confuse the userspace.

While at that, don't do unit conversion of the read value. The current code was prone to overflows and we only care about the sign anyway.

Signed-off-by: Sebastian Krzyszkowiak <sebastian.krzyszkowiak@puri.sm>
---
 drivers/power/supply/max17042_battery.c | 9 ++++-----
 1 file changed, 4 insertions(+), 5 deletions(-)

diff --git a/drivers/power/supply/max17042_battery.c b/drivers/power/supply/max17042_battery.c
index a6798b9a1c82..ddeba965ab5b 100644
--- a/drivers/power/supply/max17042_battery.c
+++ b/drivers/power/supply/max17042_battery.c
@@ -119,7 +119,7 @@ static int max17042_get_temperature(struct max17042_chip *chip, int *temp)
 static int max17042_get_status(struct max17042_chip *chip, int *status)
 {
 	int ret, charge_full, charge_now;
-	int avg_current;
+	int current_now;
 	u32 data;
 
 	ret = power_supply_am_i_supplied(chip->battery);
@@ -164,14 +164,13 @@ static int max17042_get_status(struct max17042_chip *chip, int *status)
 		return 0;
 	}
 
-	ret = regmap_read(chip->regmap, MAX17042_AvgCurrent, &data);
+	ret = regmap_read(chip->regmap, MAX17042_Current, &data);
 	if (ret < 0)
 		return ret;
 
-	avg_current = sign_extend32(data, 15);
-	avg_current *= 1562500 / chip->pdata->r_sns;
+	current_now = sign_extend32(data, 15);
 
-	if (avg_current > 0)
+	if (current_now > 0)
 		*status = POWER_SUPPLY_STATUS_CHARGING;
 	else
 		*status = POWER_SUPPLY_STATUS_DISCHARGING;
-- 
2.53.0

^ permalink raw reply related

* [PATCH v1 1/9] power: supply: max17042_battery: Put LSB units into defines
From: Vincent Cloutier @ 2026-04-06 20:57 UTC (permalink / raw)
  To: Sebastian Reichel
  Cc: Hans de Goede, Krzysztof Kozlowski, Marek Szyprowski,
	Sebastian Krzyszkowiak, Purism Kernel Team, linux-pm,
	linux-kernel
In-Reply-To: <20260406205759.493288-1-vincent.cloutier@icloud.com>

From: Sebastian Krzyszkowiak <sebastian.krzyszkowiak@puri.sm>

Signed-off-by: Sebastian Krzyszkowiak <sebastian.krzyszkowiak@puri.sm>
---
 drivers/power/supply/max17042_battery.c | 36 ++++++++++++++-----------
 1 file changed, 21 insertions(+), 15 deletions(-)

diff --git a/drivers/power/supply/max17042_battery.c b/drivers/power/supply/max17042_battery.c
index acea176101fa..a6798b9a1c82 100644
--- a/drivers/power/supply/max17042_battery.c
+++ b/drivers/power/supply/max17042_battery.c
@@ -52,6 +52,13 @@
 
 #define MAX17042_VMAX_TOLERANCE		50 /* 50 mV */
 
+#define MAX17042_CURRENT_LSB		1562500ll /* 1.5625µV/Rsense */
+#define MAX17042_CAPACITY_LSB		5000000ll /* 5.0µVH/Rsense */
+#define MAX17042_TIME_LSB		5625 / 1000 /* s */
+#define MAX17042_VOLTAGE_LSB		625 / 8 /* µV */
+#define MAX17042_RESISTANCE_LSB	1 / 4096 /* Ω */
+#define MAX17042_TEMPERATURE_LSB	1 / 256 /* °C */
+
 struct max17042_chip {
 	struct device *dev;
 	struct regmap *regmap;
@@ -105,8 +112,7 @@ static int max17042_get_temperature(struct max17042_chip *chip, int *temp)
 
 	*temp = sign_extend32(data, 15);
 	/* The value is converted into deci-centigrade scale */
-	/* Units of LSB = 1 / 256 degree Celsius */
-	*temp = *temp * 10 / 256;
+	*temp = *temp * 10 * MAX17042_TEMPERATURE_LSB;
 	return 0;
 }
 
@@ -183,7 +189,7 @@ static int max17042_get_battery_health(struct max17042_chip *chip, int *health)
 		goto health_error;
 
 	/* bits [0-3] unused */
-	vavg = val * 625 / 8;
+	vavg = val * MAX17042_VOLTAGE_LSB;
 	/* Convert to millivolts */
 	vavg /= 1000;
 
@@ -192,7 +198,7 @@ static int max17042_get_battery_health(struct max17042_chip *chip, int *health)
 		goto health_error;
 
 	/* bits [0-3] unused */
-	vbatt = val * 625 / 8;
+	vbatt = val * MAX17042_VOLTAGE_LSB;
 	/* Convert to millivolts */
 	vbatt /= 1000;
 
@@ -299,21 +305,21 @@ static int max17042_get_property(struct power_supply *psy,
 		if (ret < 0)
 			return ret;
 
-		val->intval = data * 625 / 8;
+		val->intval = data * MAX17042_VOLTAGE_LSB;
 		break;
 	case POWER_SUPPLY_PROP_VOLTAGE_AVG:
 		ret = regmap_read(map, MAX17042_AvgVCELL, &data);
 		if (ret < 0)
 			return ret;
 
-		val->intval = data * 625 / 8;
+		val->intval = data * MAX17042_VOLTAGE_LSB;
 		break;
 	case POWER_SUPPLY_PROP_VOLTAGE_OCV:
 		ret = regmap_read(map, MAX17042_OCVInternal, &data);
 		if (ret < 0)
 			return ret;
 
-		val->intval = data * 625 / 8;
+		val->intval = data * MAX17042_VOLTAGE_LSB;
 		break;
 	case POWER_SUPPLY_PROP_CAPACITY:
 		if (chip->pdata->enable_current_sense)
@@ -330,7 +336,7 @@ static int max17042_get_property(struct power_supply *psy,
 		if (ret < 0)
 			return ret;
 
-		data64 = data * 5000000ll;
+		data64 = data * MAX17042_CAPACITY_LSB;
 		do_div(data64, chip->pdata->r_sns);
 		val->intval = data64;
 		break;
@@ -339,7 +345,7 @@ static int max17042_get_property(struct power_supply *psy,
 		if (ret < 0)
 			return ret;
 
-		data64 = data * 5000000ll;
+		data64 = data * MAX17042_CAPACITY_LSB;
 		do_div(data64, chip->pdata->r_sns);
 		val->intval = data64;
 		break;
@@ -348,7 +354,7 @@ static int max17042_get_property(struct power_supply *psy,
 		if (ret < 0)
 			return ret;
 
-		data64 = data * 5000000ll;
+		data64 = data * MAX17042_CAPACITY_LSB;
 		do_div(data64, chip->pdata->r_sns);
 		val->intval = data64;
 		break;
@@ -357,7 +363,7 @@ static int max17042_get_property(struct power_supply *psy,
 		if (ret < 0)
 			return ret;
 
-		data64 = sign_extend64(data, 15) * 5000000ll;
+		data64 = sign_extend64(data, 15) * MAX17042_CAPACITY_LSB;
 		val->intval = div_s64(data64, chip->pdata->r_sns);
 		break;
 	case POWER_SUPPLY_PROP_TEMP:
@@ -399,7 +405,7 @@ static int max17042_get_property(struct power_supply *psy,
 			if (ret < 0)
 				return ret;
 
-			data64 = sign_extend64(data, 15) * 1562500ll;
+			data64 = sign_extend64(data, 15) * MAX17042_CURRENT_LSB;
 			val->intval = div_s64(data64, chip->pdata->r_sns);
 		} else {
 			return -EINVAL;
@@ -411,7 +417,7 @@ static int max17042_get_property(struct power_supply *psy,
 			if (ret < 0)
 				return ret;
 
-			data64 = sign_extend64(data, 15) * 1562500ll;
+			data64 = sign_extend64(data, 15) * MAX17042_CURRENT_LSB;
 			val->intval = div_s64(data64, chip->pdata->r_sns);
 		} else {
 			return -EINVAL;
@@ -422,7 +428,7 @@ static int max17042_get_property(struct power_supply *psy,
 		if (ret < 0)
 			return ret;
 
-		data64 = data * 1562500ll;
+		data64 = data * MAX17042_CURRENT_LSB;
 		val->intval = div_s64(data64, chip->pdata->r_sns);
 		break;
 	case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW:
@@ -430,7 +436,7 @@ static int max17042_get_property(struct power_supply *psy,
 		if (ret < 0)
 			return ret;
 
-		val->intval = data * 5625 / 1000;
+		val->intval = data * MAX17042_TIME_LSB;
 		break;
 	default:
 		return -EINVAL;
-- 
2.53.0

^ permalink raw reply related

* [PATCH v1 0/9] power: supply: max17042_battery: improve MAX17055 support
From: Vincent Cloutier @ 2026-04-06 20:57 UTC (permalink / raw)
  To: Sebastian Reichel
  Cc: Hans de Goede, Krzysztof Kozlowski, Marek Szyprowski,
	Sebastian Krzyszkowiak, Purism Kernel Team, linux-pm,
	linux-kernel, Vincent Cloutier

From: Vincent Cloutier <vincent@cloutier.co>

Hi,

This series refreshes the Librem 5 MAX17055 fuel-gauge work for upstream
on top of current `mainline/master`.

Original series:
https://lore.kernel.org/lkml/20220318001048.20922-1-sebastian.krzyszkowiak@puri.sm/

Compared to the earlier posted version, the main follow-up changes are:

- keep the original arithmetic form of the fractional `MAX17042_*_LSB`
  expressions while still moving the unit conversions into shared defines
- remove the unused board-file style platform-data wrapper from the driver
- factor the `monitored-battery` parser behind a generic
  `devm_power_supply_get_battery_info()` helper so MAX17042 can read DT
  battery data before registration
- keep `voltage-max-design-microvolt` as an explicit MAX17055
  `ModelCfg.VChg` setting so batteries at or below 4.25 V clear the
  default bit

The attribution split is intentional:

- patches 1, 2, and 7 preserve the original signed Purism downstream work
  from Sebastian Krzyszkowiak
- the remaining patches are cleanups and follow-ups on top of current
  mainline code

Tested on Librem 5.

Sebastian Krzyszkowiak (3):
  power: supply: max17042_battery: Put LSB units into defines
  power: supply: max17042_battery: Use Current register in get_status
  power: supply: max17042_battery: use ModelCfg refresh on max17055

Vincent Cloutier (6):
  power: supply: max17042_battery: Use dev_err_probe for power supply
    registration
  power: supply: max17042_battery: Route MAX17055 SOC alerts through
    dSOCi
  power: supply: max17042_battery: Keep only critical alerts during
    suspend
  power: supply: max17042_battery: Remove unused platform-data plumbing
  power: supply: Read MAX17042 battery info before registration
  power: supply: max17042_battery: Treat MAX17055 VChg as explicit DT
    setting

 drivers/power/supply/max17042_battery.c  | 428 ++++++++++++++---------
 drivers/power/supply/power_supply_core.c | 188 ++++++----
 include/linux/power/max17042_battery.h   |  33 +-
 include/linux/power_supply.h             |  10 +
 4 files changed, 393 insertions(+), 266 deletions(-)


base-commit: 46b513250491a7bfc97d98791dbe6a10bcc8129d
-- 
2.53.0

^ permalink raw reply

* Re: [PATCH v3 2/3] thermal: spacemit: k1: Add thermal sensor support
From: Anand Moon @ 2026-04-06 18:45 UTC (permalink / raw)
  To: Shuwei Wu
  Cc: Rafael J. Wysocki, Daniel Lezcano, Zhang Rui, Lukasz Luba,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Yixun Lan,
	Philipp Zabel, Paul Walmsley, Palmer Dabbelt, Albert Ou,
	Alexandre Ghiti, linux-pm, devicetree, linux-riscv, spacemit,
	linux-kernel
In-Reply-To: <20260119-patchv2-k1-thermal-v3-2-3d82c9ebe8a4@163.com>

Hi Shuwei,

On Mon, 19 Jan 2026 at 08:13, Shuwei Wu <shuweiwoo@163.com> wrote:
>
> The thermal sensor on K1 supports monitoring five temperature zones.
> The driver registers these sensors with the thermal framework
> and supports standard operations:
> - Reading temperature (millidegree Celsius)
> - Setting high/low thresholds for interrupts
>
> Signed-off-by: Shuwei Wu <shuweiwoo@163.com>

Reviewed-by: Anand Moon <linux.amoon@gmail.com>
Tested-by: Anand Moon <linux.amoon@gmail.com>

Thanks
-Anand

^ permalink raw reply

* [GIT PULL] cpupower next update for Linux 7.1-rc1
From: Shuah Khan @ 2026-04-06 18:40 UTC (permalink / raw)
  To: Rafael J. Wysocki
  Cc: shuah, Shuah Khan, Thomas Renninger, John B. Wyatt IV, John Kacur,
	Thomas Renninger, linux-pm, linux-kernel

[-- Attachment #1: Type: text/plain, Size: 2803 bytes --]

Hi Rafael,

Please pull the cpupower next update for Linux 7.1-rc1.

- Fixes errors in cpupower-frequency-info short option names
   to its manpage.
- Fixes cpupower-idle-info perf option name to its manpage.
- Adds boost and epp options to cpupower-frequency-info to its
   manpage.
- Adds description for perf-bias option to cpupower-info to its
   manpage.
- Removes unnecessary extern declarations from getopt.h in arguments
   parsing functions in cpufreq-set, cpuidle-info, cpuidle-set,
   cpupower-info, and cpupower-set utilities. These functions are
   defined getopt.h file.

diff is attached.

thanks,
-- Shuah

----------------------------------------------------------------
The following changes since commit c369299895a591d96745d6492d4888259b004a9e:

   Linux 7.0-rc5 (2026-03-22 14:42:17 -0700)

are available in the Git repository at:

   git://git.kernel.org/pub/scm/linux/kernel/git/shuah/linux tags/linux-cpupower-7.1-rc1

for you to fetch changes up to 2fd3b83cacfb9160b896fb26117328eeb0598c54:

   cpupower: remove extern declarations in cmd functions (2026-04-06 11:25:32 -0600)

----------------------------------------------------------------
linux-cpupower-7.1-rc1

- Fixes errors in cpupower-frequency-info short option names
   to its manpage.
- Fixes cpupower-idle-info perf option name to its manpage.
- Adds boost and epp options to cpupower-frequency-info to its
   manpage.
- Adds description for perf-bias option to cpupower-info to its
   manpage.
- Removes unnecessary extern declarations from getopt.h in arguments
   parsing functions in cpufreq-set, cpuidle-info, cpuidle-set,
   cpupower-info, and cpupower-set utilities. These functions are
   defined getopt.h file.

----------------------------------------------------------------
Kaushlendra Kumar (1):
       cpupower: remove extern declarations in cmd functions

Roberto Ricci (4):
       cpupower-idle-info.1: fix short option names
       cpupower-frequency-info.1: use the proper name of the --perf option
       cpupower-frequency-info.1: document --boost and --epp options
       cpupower-info.1: describe the --perf-bias option

  tools/power/cpupower/man/cpupower-frequency-info.1 | 8 +++++++-
  tools/power/cpupower/man/cpupower-idle-info.1      | 4 ++--
  tools/power/cpupower/man/cpupower-info.1           | 9 ++++++++-
  tools/power/cpupower/utils/cpufreq-info.c          | 2 --
  tools/power/cpupower/utils/cpufreq-set.c           | 2 --
  tools/power/cpupower/utils/cpuidle-info.c          | 2 --
  tools/power/cpupower/utils/cpuidle-set.c           | 2 --
  tools/power/cpupower/utils/cpupower-info.c         | 2 --
  tools/power/cpupower/utils/cpupower-set.c          | 2 --
  9 files changed, 17 insertions(+), 16 deletions(-)
----------------------------------------------------------------

[-- Attachment #2: linux-cpupower-7.1-rc1.diff --]
[-- Type: text/x-patch, Size: 5598 bytes --]

diff --git a/tools/power/cpupower/man/cpupower-frequency-info.1 b/tools/power/cpupower/man/cpupower-frequency-info.1
index 47fdd7218748..b0d69c9adcbd 100644
--- a/tools/power/cpupower/man/cpupower-frequency-info.1
+++ b/tools/power/cpupower/man/cpupower-frequency-info.1
@@ -32,6 +32,12 @@ Gets the currently used cpufreq policy.
 \fB\-g\fR \fB\-\-governors\fR
 Determines available cpufreq governors.
 .TP  
+\fB\-b\fR \fB\-\-boost\fR
+Gets the current boost state support.
+.TP
+\fB\-z\fR \fB\-\-epp\fR
+Gets the current EPP (energy performance preference).
+.TP
 \fB\-r\fR \fB\-\-related\-cpus\fR
 Determines which CPUs run at the same hardware frequency.
 .TP  
@@ -53,7 +59,7 @@ human\-readable output for the \-f, \-w, \-s and \-y parameters.
 \fB\-n\fR \fB\-\-no-rounding\fR
 Output frequencies and latencies without rounding off values.
 .TP  
-\fB\-c\fR \fB\-\-perf\fR
+\fB\-c\fR \fB\-\-performance\fR
 Get performances and frequencies capabilities of CPPC, by reading it from hardware (only available on the hardware with CPPC).
 .TP
 .SH "REMARKS"
diff --git a/tools/power/cpupower/man/cpupower-idle-info.1 b/tools/power/cpupower/man/cpupower-idle-info.1
index 20b6345c53ad..b2f92aba5f5b 100644
--- a/tools/power/cpupower/man/cpupower-idle-info.1
+++ b/tools/power/cpupower/man/cpupower-idle-info.1
@@ -11,10 +11,10 @@ A tool which prints out per cpu idle information helpful to developers and inter
 .SH "OPTIONS"
 .LP
 .TP
-\fB\-f\fR \fB\-\-silent\fR
+\fB\-s\fR \fB\-\-silent\fR
 Only print a summary of all available C-states in the system.
 .TP
-\fB\-e\fR \fB\-\-proc\fR
+\fB\-o\fR \fB\-\-proc\fR
 deprecated.
 Prints out idle information in old /proc/acpi/processor/*/power format. This
 interface has been removed from the kernel for quite some time, do not let
diff --git a/tools/power/cpupower/man/cpupower-info.1 b/tools/power/cpupower/man/cpupower-info.1
index 340bcd0be7de..1f42d8c388a0 100644
--- a/tools/power/cpupower/man/cpupower-info.1
+++ b/tools/power/cpupower/man/cpupower-info.1
@@ -3,7 +3,7 @@
 cpupower\-info \- Shows processor power related kernel or hardware configurations
 .SH SYNOPSIS
 .ft B
-.B cpupower info [ \-b ]
+.B cpupower info [\fIoptions\fP]
 
 .SH DESCRIPTION
 \fBcpupower info \fP shows kernel configurations or processor hardware
@@ -13,6 +13,13 @@ Some options are platform wide, some affect single cores. By default values
 of core zero are displayed only. cpupower --cpu all cpuinfo will show the
 settings of all cores, see cpupower(1) how to choose specific cores.
 
+.SH "OPTIONS"
+.LP
+.TP
+\fB\-b\fR \fB\-\-perf-bias\fR
+Gets the current performance bias value.
+.TP
+
 .SH "SEE ALSO"
 Options are described in detail in:
 
diff --git a/tools/power/cpupower/utils/cpufreq-info.c b/tools/power/cpupower/utils/cpufreq-info.c
index 5fe01e516817..5a242b491a9d 100644
--- a/tools/power/cpupower/utils/cpufreq-info.c
+++ b/tools/power/cpupower/utils/cpufreq-info.c
@@ -542,8 +542,6 @@ static struct option info_opts[] = {
 
 int cmd_freq_info(int argc, char **argv)
 {
-	extern char *optarg;
-	extern int optind, opterr, optopt;
 	int ret = 0, cont = 1;
 	unsigned int cpu = 0;
 	unsigned int human = 0;
diff --git a/tools/power/cpupower/utils/cpufreq-set.c b/tools/power/cpupower/utils/cpufreq-set.c
index c5e60a39cfa6..06cd4b280132 100644
--- a/tools/power/cpupower/utils/cpufreq-set.c
+++ b/tools/power/cpupower/utils/cpufreq-set.c
@@ -195,8 +195,6 @@ static int do_one_cpu(unsigned int cpu, struct cpufreq_policy *new_pol,
 
 int cmd_freq_set(int argc, char **argv)
 {
-	extern char *optarg;
-	extern int optind, opterr, optopt;
 	int ret = 0, cont = 1;
 	int double_parm = 0, related = 0, policychange = 0;
 	unsigned long freq = 0;
diff --git a/tools/power/cpupower/utils/cpuidle-info.c b/tools/power/cpupower/utils/cpuidle-info.c
index 81b4763a97d6..ccb37125bd37 100644
--- a/tools/power/cpupower/utils/cpuidle-info.c
+++ b/tools/power/cpupower/utils/cpuidle-info.c
@@ -139,8 +139,6 @@ static inline void cpuidle_exit(int fail)
 
 int cmd_idle_info(int argc, char **argv)
 {
-	extern char *optarg;
-	extern int optind, opterr, optopt;
 	int ret = 0, cont = 1, output_param = 0, verbose = 1;
 	unsigned int cpu = 0;
 
diff --git a/tools/power/cpupower/utils/cpuidle-set.c b/tools/power/cpupower/utils/cpuidle-set.c
index a551d1d4ac51..703094f1343c 100644
--- a/tools/power/cpupower/utils/cpuidle-set.c
+++ b/tools/power/cpupower/utils/cpuidle-set.c
@@ -24,8 +24,6 @@ static struct option info_opts[] = {
 
 int cmd_idle_set(int argc, char **argv)
 {
-	extern char *optarg;
-	extern int optind, opterr, optopt;
 	int ret = 0, cont = 1, param = 0, disabled;
 	unsigned long long latency = 0, state_latency;
 	unsigned int cpu = 0, idlestate = 0, idlestates = 0;
diff --git a/tools/power/cpupower/utils/cpupower-info.c b/tools/power/cpupower/utils/cpupower-info.c
index 18fd7751f509..79154d71e498 100644
--- a/tools/power/cpupower/utils/cpupower-info.c
+++ b/tools/power/cpupower/utils/cpupower-info.c
@@ -28,8 +28,6 @@ static void print_wrong_arg_exit(void)
 
 int cmd_info(int argc, char **argv)
 {
-	extern char *optarg;
-	extern int optind, opterr, optopt;
 	unsigned int cpu;
 	struct utsname uts;
 
diff --git a/tools/power/cpupower/utils/cpupower-set.c b/tools/power/cpupower/utils/cpupower-set.c
index 550a942e72ce..c2176b9fa57d 100644
--- a/tools/power/cpupower/utils/cpupower-set.c
+++ b/tools/power/cpupower/utils/cpupower-set.c
@@ -33,8 +33,6 @@ static void print_wrong_arg_exit(void)
 
 int cmd_set(int argc, char **argv)
 {
-	extern char *optarg;
-	extern int optind, opterr, optopt;
 	unsigned int cpu;
 	struct utsname uts;
 

^ permalink raw reply related

* Re: [PATCH] cpufreq: CPPC: add autonomous mode boot parameter support
From: Sumit Gupta @ 2026-04-06 18:08 UTC (permalink / raw)
  To: Pierre Gondois
  Cc: linux-tegra, linux-kernel, linux-doc, zhenglifeng1, treding,
	viresh.kumar, jonathanh, vsethi, ionela.voinescu, ksitaraman,
	sanjayc, zhanjie9, corbet, mochs, skhan, bbasu, rdunlap, linux-pm,
	mario.limonciello, rafael, sumitg
In-Reply-To: <4b1f100b-e699-43c1-a06b-5545056d174c@arm.com>

Hi Pierre,

Thank you for the comments.
Sorry for late reply as I was on vacation.


On 24/03/26 23:48, Pierre Gondois wrote:
> External email: Use caution opening links or attachments
>
>
> Hello Sumit,
>
> On 3/17/26 16:10, Sumit Gupta wrote:
>> Add kernel boot parameter 'cppc_cpufreq.auto_sel_mode' to enable CPPC
>> autonomous performance selection on all CPUs at system startup without
>> requiring runtime sysfs manipulation. When autonomous mode is enabled,
>> the hardware automatically adjusts CPU performance based on workload
>> demands using Energy Performance Preference (EPP) hints.
>>
>> When auto_sel_mode=1:
>> - Configure all CPUs for autonomous operation on first init
>> - Set EPP to performance preference (0x0)
>> - Use HW min/max when set; otherwise program from policy limits (caps)
>> - Clamp desired_perf to bounds before enabling autonomous mode
>> - Hardware controls frequency instead of the OS governor
>>
>> The boot parameter is applied only during first policy initialization.
>> On hotplug, skip applying it so that the user's runtime sysfs
>> configuration is preserved.
>>
>> Reviewed-by: Randy Dunlap <rdunlap@infradead.org> (Documentation)
>> Signed-off-by: Sumit Gupta <sumitg@nvidia.com>
>> ---
>> Part 1 [1] of this series was applied for 7.1 and present in next.
>> Sending this patch as reworked version of 'patch 11' from [2] based
>> on next.
>>
>> [1] 
>> https://lore.kernel.org/lkml/20260206142658.72583-1-sumitg@nvidia.com/
>> [2] 
>> https://lore.kernel.org/lkml/20251223121307.711773-1-sumitg@nvidia.com/
>> ---
>>   .../admin-guide/kernel-parameters.txt         | 13 +++
>>   drivers/cpufreq/cppc_cpufreq.c                | 84 +++++++++++++++++--
>>   2 files changed, 92 insertions(+), 5 deletions(-)
>>
>> diff --git a/Documentation/admin-guide/kernel-parameters.txt 
>> b/Documentation/admin-guide/kernel-parameters.txt
>> index fa6171b5fdd5..de4b4c89edfe 100644
>> --- a/Documentation/admin-guide/kernel-parameters.txt
>> +++ b/Documentation/admin-guide/kernel-parameters.txt
>> @@ -1060,6 +1060,19 @@ Kernel parameters
>>                       policy to use. This governor must be registered 
>> in the
>>                       kernel before the cpufreq driver probes.
>>
>> +     cppc_cpufreq.auto_sel_mode=
>> +                     [CPU_FREQ] Enable ACPI CPPC autonomous performance
>> +                     selection. When enabled, hardware automatically 
>> adjusts
>> +                     CPU frequency on all CPUs based on workload 
>> demands.
>> +                     In Autonomous mode, Energy Performance 
>> Preference (EPP)
>> +                     hints guide hardware toward performance (0x0) 
>> or energy
>> +                     efficiency (0xff).
>> +                     Requires ACPI CPPC autonomous selection 
>> register support.
>> +                     Format: <bool>
>> +                     Default: 0 (disabled)
>> +                     0: use cpufreq governors
>> +                     1: enable if supported by hardware
>> +
>>       cpu_init_udelay=N
>>                       [X86,EARLY] Delay for N microsec between assert 
>> and de-assert
>>                       of APIC INIT to start processors.  This delay 
>> occurs
>> diff --git a/drivers/cpufreq/cppc_cpufreq.c 
>> b/drivers/cpufreq/cppc_cpufreq.c
>> index 5dfb109cf1f4..49c148b2a0a4 100644
>> --- a/drivers/cpufreq/cppc_cpufreq.c
>> +++ b/drivers/cpufreq/cppc_cpufreq.c
>> @@ -28,6 +28,9 @@
>>
>>   static struct cpufreq_driver cppc_cpufreq_driver;
>>
>> +/* Autonomous Selection boot parameter */
>> +static bool auto_sel_mode;
>> +
>>   #ifdef CONFIG_ACPI_CPPC_CPUFREQ_FIE
>>   static enum {
>>       FIE_UNSET = -1,
>> @@ -708,11 +711,74 @@ static int cppc_cpufreq_cpu_init(struct 
>> cpufreq_policy *policy)
>>       policy->cur = cppc_perf_to_khz(caps, caps->highest_perf);
>>       cpu_data->perf_ctrls.desired_perf = caps->highest_perf;
>>
>> -     ret = cppc_set_perf(cpu, &cpu_data->perf_ctrls);
>> -     if (ret) {
>> -             pr_debug("Err setting perf value:%d on CPU:%d. ret:%d\n",
>> -                      caps->highest_perf, cpu, ret);
>> -             goto out;
>> +     /*
>> +      * Enable autonomous mode on first init if boot param is set.
>> +      * Check last_governor to detect first init and skip if auto_sel
>> +      * is already enabled.
>> +      */
> If the goal is to set autosel only once at the driver init,
> shouldn't this be done in cppc_cpufreq_init() ?
> I understand that cpu_data doesn't exist yet in
> cppc_cpufreq_init(), but this seems more appropriate to do
> it there IMO.
>
> This means the cpudata should be updated accordingly
> in this cppc_cpufreq_cpu_init() function.

In an earlier version [1], the setup was in cppc_cpufreq_init() but
was moved to cppc_cpufreq_cpu_init() to improve per-CPU error handling.
Keeping the setup in cppc_cpufreq_init() helps to avoid the last_governor
check. We can warn for a CPU failing to enable and continue so other
CPUs keep autonomous mode.
cppc_cpufreq_cpu_init() would then just check the auto_sel state
from register and sync policy limits from min/max_perf registers when
autonomous mode is active.
Please let me know your thoughts.

[1] 
https://lore.kernel.org/lkml/5593d364-ca37-41c5-b33f-f7e245d6d626@nvidia.com/


>
>> +     if (auto_sel_mode && policy->last_governor[0] == '\0' &&
>> +         !cpu_data->perf_ctrls.auto_sel) {
>> +             /* Enable CPPC - optional register, some platforms need 
>> it */
> The documentation of the CPPC Enable Register is subject to
> interpretation, but IIUC the field should be set to use the CPPC
> controls, so I assume this should be set in cppc_cpufreq_init()
> instead ?

Agree that the CPPC Enable is about using the CPPC control path
in general and not only for autonomous selection.
Will move cppc_set_enable() into cppc_cpufreq_init() or outside the
autonomous mode block in cppc_cpufreq_cpu_init() as per conclusion
of previous comment.

>> +             ret = cppc_set_enable(cpu, true);
>> +             if (ret && ret != -EOPNOTSUPP)
>> +                     pr_warn("Failed to enable CPPC for CPU%d 
>> (%d)\n", cpu, ret);
>> +
>> +             /*
>> +              * Prefer HW min/max_perf when set; otherwise program from
>> +              * policy limits derived earlier from caps.
>> +              * Clamp desired_perf to bounds and sync policy->cur.
>> +              */
>> +             if (!cpu_data->perf_ctrls.min_perf || 
>> !cpu_data->perf_ctrls.max_perf)
>
> The function doesn't seem to exist.

It is newly added in [2].
Don't need to call it if we move the setup to cppc_cpufreq_init().

[2] 
https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git/commit/?id=ea3db45ae476889a1ba0ab3617e6afdeeefbda3d 



>
>> + cppc_cpufreq_update_perf_limits(cpu_data, policy);
>> +
>> +             cpu_data->perf_ctrls.desired_perf =
>> +                     clamp_t(u32, cpu_data->perf_ctrls.desired_perf,
>> +                             cpu_data->perf_ctrls.min_perf,
>> +                             cpu_data->perf_ctrls.max_perf);
>> +
>> +             policy->cur = cppc_perf_to_khz(caps,
>> + cpu_data->perf_ctrls.desired_perf);
>> +
>
> Maybe this should also be done in cppc_cpufreq_init()
> if the auto_sel_mode parameter is set ?

Yes.

>
>> +             /* EPP is optional - some platforms may not support it */
>> +             ret = cppc_set_epp(cpu, CPPC_EPP_PERFORMANCE_PREF);
>> +             if (ret && ret != -EOPNOTSUPP)
>> +                     pr_warn("Failed to set EPP for CPU%d (%d)\n", 
>> cpu, ret);
>> +             else if (!ret)
>> +                     cpu_data->perf_ctrls.energy_perf = 
>> CPPC_EPP_PERFORMANCE_PREF;
>> +
>> +             ret = cppc_set_perf(cpu, &cpu_data->perf_ctrls);
>> +             if (ret) {
>> +                     pr_debug("Err setting perf for autonomous mode 
>> CPU:%d ret:%d\n",
>> +                              cpu, ret);
>> +                     goto out;
>> +             }
>> +
>> +             ret = cppc_set_auto_sel(cpu, true);
>> +             if (ret && ret != -EOPNOTSUPP) {
>> +                     pr_warn("Failed autonomous config for CPU%d 
>> (%d)\n",
>> +                             cpu, ret);
>> +                     goto out;
>> +             }
>> +             if (!ret)
>> +                     cpu_data->perf_ctrls.auto_sel = true;
>> +     }
>> +
>> +     if (cpu_data->perf_ctrls.auto_sel) {
>
> There is a patchset ongoing which tries to remove
> setting policy->min/max from driver initialization.
> Indeed, these values are only temporarily valid,
> until the governor override them.
> It is not sure yet the patch will be accepted though.
>
> https://lore.kernel.org/lkml/20260317101753.2284763-4-pierre.gondois@arm.com/ 
>


You are right that policy->min/max from .init() are temporary today
as cpufreq_set_policy() overwrites them before the governor starts.

On my test platform (highest == nominal, lowest_nonlinear == lowest),
this had no visible effect because the BIOS bounds and cpuinfo range
end up identical. But on platforms where they differ, the governor
would widen the range to full cpuinfo limits.

I think your patch [3] fixes this by giving these the right semantic as
initial QoS requests. With it, cpufreq_set_policy() preserves the policy
limits set from min/max_perf registers in .init(), which can either be
BIOS values on first boot or last user configured values before hotplug.

I will update the comment in v2 to reflect QoS seeding intent.

I see that the first two patches of your series [3] is applied for 7.1.
Do you plan to send the pending patch (3/4) from [3]?

[3] 
https://lore.kernel.org/lkml/20260317101753.2284763-4-pierre.gondois@arm.com/


>
>
>> +             /* Sync policy limits from HW when autonomous mode is 
>> active */
>> +             policy->min = cppc_perf_to_khz(caps,
>> + cpu_data->perf_ctrls.min_perf ?:
>> + caps->lowest_nonlinear_perf);
>> +             policy->max = cppc_perf_to_khz(caps,
>> + cpu_data->perf_ctrls.max_perf ?:
>> + caps->nominal_perf);
>> +     } else {
>> +             /* Normal mode: governors control frequency */
>> +             ret = cppc_set_perf(cpu, &cpu_data->perf_ctrls);
>> +             if (ret) {
>> +                     pr_debug("Err setting perf value:%d on CPU:%d. 
>> ret:%d\n",
>> +                              caps->highest_perf, cpu, ret);
>> +                     goto out;
>> +             }
>>       }
>>
>>       cppc_cpufreq_cpu_fie_init(policy);
>> @@ -1038,10 +1104,18 @@ static int __init cppc_cpufreq_init(void)
>>
>>   static void __exit cppc_cpufreq_exit(void)
>>   {
>> +     unsigned int cpu;
>> +
>> +     for_each_present_cpu(cpu)
>> +             cppc_set_auto_sel(cpu, false);
>
> If the firmware has a default EPP value, it means that loading
> and the unloading the driver will reset this default EPP value.
> Maybe the initial EPP value and/or the auto_sel value should be
> cached somewhere and restored on exit ?
> I don't know if this is actually an issue, this is just to signal it.

The auto_sel_mode boot path programs EPP to performance preference(0),
not the firmware’s previous value. On unload we only call
cppc_set_auto_sel(false); we do not restore EPP, min/max perf,
or other CPPC fields to firmware defaults.

Thank you,
Sumit Gupta

....



^ permalink raw reply

* Re: [PATCH] dt-bindings: thermal: idle: Correct node name in the example
From: Rob Herring (Arm) @ 2026-04-06 16:51 UTC (permalink / raw)
  To: Krzysztof Kozlowski
  Cc: Daniel Lezcano, Conor Dooley, Krzysztof Kozlowski,
	Rafael J. Wysocki, linux-kernel, linux-pm, Zhang Rui, Lukasz Luba,
	devicetree
In-Reply-To: <20260406145104.36472-2-krzysztof.kozlowski@oss.qualcomm.com>


On Mon, 06 Apr 2026 16:51:05 +0200, Krzysztof Kozlowski wrote:
> Thermal bindings expect the node name with all the zones to be named
> 'thermal-zones' (hyphen instead of underscore) and also DTS coding style
> is not to use underscores for node names, so adjust the example code.
> 
> Signed-off-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
> ---
>  Documentation/devicetree/bindings/thermal/thermal-idle.yaml | 2 +-
>  1 file changed, 1 insertion(+), 1 deletion(-)
> 

My bot found errors running 'make dt_binding_check' on your patch:

yamllint warnings/errors:

dtschema/dtc warnings/errors:
/builds/robherring/dt-review-ci/linux/Documentation/devicetree/bindings/thermal/thermal-idle.example.dtb: thermal-zones: 'cpu' does not match any of the regexes: '^[a-zA-Z][a-zA-Z0-9\\-]{1,10}-thermal$', '^pinctrl-[0-9]+$'
	from schema $id: http://devicetree.org/schemas/thermal/thermal-zones.yaml

doc reference errors (make refcheckdocs):

See https://patchwork.kernel.org/project/devicetree/patch/20260406145104.36472-2-krzysztof.kozlowski@oss.qualcomm.com

The base for the series is generally the latest rc1. A different dependency
should be noted in *this* patch.

If you already ran 'make dt_binding_check' and didn't see the above
error(s), then make sure 'yamllint' is installed and dt-schema is up to
date:

pip3 install dtschema --upgrade

Please check and re-submit after running the above command yourself. Note
that DT_SCHEMA_FILES can be set to your schema file to speed up checking
your schema. However, it must be unset to test all examples with your schema.


^ permalink raw reply

* [PATCH v3 0/6] thermal: core: Fixes, simplifications and suspend/resume relocation
From: Rafael J. Wysocki @ 2026-04-06 16:04 UTC (permalink / raw)
  To: Linux PM; +Cc: Daniel Lezcano, LKML, Lukasz Luba, Armin Wolf

Hi All,

This is an update of

https://lore.kernel.org/linux-pm/6277980.lOV4Wx5bFT@rafael.j.wysocki/

that adds two fixes to it and updates the last two patches of the
original series.

This series is intended for 7.1 (it applies on top of linux-next).

If fixes the thermal zone removal and registration rollback path by
addressing race possible race conditions and a memory leak in that
core (patches [1-2/6]), removes a redundant check (patch [3/6]), changes
the thermal workqueue to an unbound and non-freezable one (patch [4/6]),
changes the allocation of thermal_class to static (patch [5/4]), and
relocates the suspend and resume of thermal zones closer to the suspend
and resume of devices, respectively (patch [6/6]).

Thanks!




^ permalink raw reply

* [PATCH v3 1/6] thermal: core: Fix thermal zone governor cleanup issues
From: Rafael J. Wysocki @ 2026-04-06 16:07 UTC (permalink / raw)
  To: Linux PM; +Cc: Daniel Lezcano, LKML, Lukasz Luba, Armin Wolf
In-Reply-To: <5119690.31r3eYUQgx@rafael.j.wysocki>

From: Rafael J. Wysocki <rafael.j.wysocki@intel.com>

If thermal_zone_device_register_with_trips() fails after adding
a thermal governor to the thermal zone being registered, the
governor is not removed from it as appropriate which may lead to
a memory leak, so address this by adding the governor cleanup to
the rollback path.

In turn, thermal_zone_device_unregister() calls thermal_set_governor()
without acquiring the thermal zone lock beforehand which may race with
a governor update via sysfs and may lead to a use-after-free in that
case, so address it by placing the cleanup thermal_set_governor()
call after the wait_for_completion() one, which reflects the
registration error path ordering.

Fixes: e33df1d2f3a0 ("thermal: let governors have private data for each thermal zone")
Cc: All applicable <stable@vger.kernel.org>
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
---

v2 -> v3: New patch

---
 drivers/thermal/thermal_core.c |    9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)

--- a/drivers/thermal/thermal_core.c
+++ b/drivers/thermal/thermal_core.c
@@ -1618,7 +1618,7 @@ thermal_zone_device_register_with_trips(
 	/* Add nodes that are always present via .groups */
 	result = thermal_zone_create_device_groups(tz);
 	if (result)
-		goto remove_id;
+		goto remove_governor;
 
 	result = device_register(&tz->device);
 	if (result)
@@ -1649,6 +1649,8 @@ unregister:
 release_device:
 	put_device(&tz->device);
 	wait_for_completion(&tz->removal);
+remove_governor:
+	thermal_set_governor(tz, NULL);
 remove_id:
 	ida_free(&thermal_tz_ida, id);
 free_tzp:
@@ -1731,8 +1733,6 @@ void thermal_zone_device_unregister(stru
 
 	cancel_delayed_work_sync(&tz->poll_queue);
 
-	thermal_set_governor(tz, NULL);
-
 	thermal_thresholds_exit(tz);
 	thermal_remove_hwmon_sysfs(tz);
 	ida_free(&thermal_tz_ida, tz->id);
@@ -1744,6 +1744,9 @@ void thermal_zone_device_unregister(stru
 	thermal_notify_tz_delete(tz);
 
 	wait_for_completion(&tz->removal);
+
+	thermal_set_governor(tz, NULL);
+
 	kfree(tz->tzp);
 	kfree(tz);
 }




^ permalink raw reply

* [PATCH v3 2/6] thermal: core: Free thermal zone ID later during removal
From: Rafael J. Wysocki @ 2026-04-06 16:09 UTC (permalink / raw)
  To: Linux PM; +Cc: Daniel Lezcano, LKML, Lukasz Luba, Armin Wolf
In-Reply-To: <5119690.31r3eYUQgx@rafael.j.wysocki>

From: Rafael J. Wysocki <rafael.j.wysocki@intel.com>

The thermal zone removal ordering is different from the thermal zone
registration rollback path ordering and the former is arguably
problematic because freeing a thermal zone ID prematurely may cause
it to be used during the registration of another thermal zone which
may fail as a result.

Prevent that from occurring by changing the thermal zone removal
ordering to reflect the thermal zone registration rollback path
ordering.

Fixes: b31ef8285b19 ("thermal core: convert ID allocation to IDA")
Cc: All applicable <stable@vger.kernel.org>
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
---

v2 -> v3: New patch

---
 drivers/thermal/thermal_core.c |    5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

--- a/drivers/thermal/thermal_core.c
+++ b/drivers/thermal/thermal_core.c
@@ -1735,8 +1735,6 @@ void thermal_zone_device_unregister(stru
 
 	thermal_thresholds_exit(tz);
 	thermal_remove_hwmon_sysfs(tz);
-	ida_free(&thermal_tz_ida, tz->id);
-	ida_destroy(&tz->ida);
 
 	device_del(&tz->device);
 	put_device(&tz->device);
@@ -1747,6 +1745,9 @@ void thermal_zone_device_unregister(stru
 
 	thermal_set_governor(tz, NULL);
 
+	ida_free(&thermal_tz_ida, tz->id);
+	ida_destroy(&tz->ida);
+
 	kfree(tz->tzp);
 	kfree(tz);
 }




^ permalink raw reply

* [PATCH v3 3/6] thermal: core: Drop redundant check from thermal_zone_device_update()
From: Rafael J. Wysocki @ 2026-04-06 16:10 UTC (permalink / raw)
  To: Linux PM; +Cc: Daniel Lezcano, LKML, Lukasz Luba, Armin Wolf
In-Reply-To: <5119690.31r3eYUQgx@rafael.j.wysocki>

From: Rafael J. Wysocki <rafael.j.wysocki@intel.com>

Since __thermal_zone_device_update() checks if tz->state is
TZ_STATE_READY and bails out immediately otherwise, it is not
necessary to check the thermal_zone_is_present() return value in
thermal_zone_device_update().  Namely, tz->state is equal to
TZ_STATE_FLAG_INIT initially and that flag is only cleared in
thermal_zone_init_complete() after adding tz to the list of thermal
zones, and thermal_zone_exit() sets TZ_STATE_FLAG_EXIT in tz->state
while removing tz from that list.  Thus tz->state is not TZ_STATE_READY
when tz is not in the list and the check mentioned above is redundant.

Accordingly, drop the redundant thermal_zone_is_present() check from
thermal_zone_device_update() and drop the former altogether because it
has no more users.

No intentional functional impact.

Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
---

v1 -> v3: No changes

---
 drivers/thermal/thermal_core.c |    8 +-------
 1 file changed, 1 insertion(+), 7 deletions(-)

--- a/drivers/thermal/thermal_core.c
+++ b/drivers/thermal/thermal_core.c
@@ -702,18 +702,12 @@ int thermal_zone_device_disable(struct t
 }
 EXPORT_SYMBOL_GPL(thermal_zone_device_disable);
 
-static bool thermal_zone_is_present(struct thermal_zone_device *tz)
-{
-	return !list_empty(&tz->node);
-}
-
 void thermal_zone_device_update(struct thermal_zone_device *tz,
 				enum thermal_notify_event event)
 {
 	guard(thermal_zone)(tz);
 
-	if (thermal_zone_is_present(tz))
-		__thermal_zone_device_update(tz, event);
+	__thermal_zone_device_update(tz, event);
 }
 EXPORT_SYMBOL_GPL(thermal_zone_device_update);
 




^ permalink raw reply

* [PATCH v3 4/6] thermal: core: Change thermal_wq to be unbound and not freezable
From: Rafael J. Wysocki @ 2026-04-06 16:11 UTC (permalink / raw)
  To: Linux PM; +Cc: Daniel Lezcano, LKML, Lukasz Luba, Armin Wolf
In-Reply-To: <5119690.31r3eYUQgx@rafael.j.wysocki>

From: Rafael J. Wysocki <rafael.j.wysocki@intel.com>

The thermal workqueue doesn't need to be freezable or per-CPU, so drop
WQ_FREEZABLE and WQ_PERCPU from the flags when allocating it.

No intentional functional impact.

Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
---

v1 -> v3: No changes

---
 drivers/thermal/thermal_core.c |    3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

--- a/drivers/thermal/thermal_core.c
+++ b/drivers/thermal/thermal_core.c
@@ -1919,8 +1919,7 @@ static int __init thermal_init(void)
 	if (result)
 		goto error;
 
-	thermal_wq = alloc_workqueue("thermal_events",
-				      WQ_FREEZABLE | WQ_POWER_EFFICIENT | WQ_PERCPU, 0);
+	thermal_wq = alloc_workqueue("thermal_events", WQ_POWER_EFFICIENT, 0);
 	if (!thermal_wq) {
 		result = -ENOMEM;
 		goto unregister_netlink;




^ permalink raw reply

* [PATCH v3 5/6] thermal: core: Allocate thermal_class statically
From: Rafael J. Wysocki @ 2026-04-06 16:14 UTC (permalink / raw)
  To: Linux PM; +Cc: Daniel Lezcano, LKML, Lukasz Luba, Armin Wolf
In-Reply-To: <5119690.31r3eYUQgx@rafael.j.wysocki>

From: Rafael J. Wysocki <rafael.j.wysocki@intel.com>

Define thermal_class as a static structure to simplify thermal_init()
and to simplify thermal class availability checks that will need to
be carried out during the suspend and resume of thermal zones after
subsequent changes.

Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
---

v2 -> v3:
   * Use static variable thermal_class_unavailable (instead of a function)
     for checking if thermal_class is available.

v1 -> v2:
   * Reorder with respect to the next patch to allow the latter to be simpler
   * Add thermal_class_unavailable() (the next patch uses it too)

---
 drivers/thermal/thermal_core.c |   30 ++++++++++++------------------
 1 file changed, 12 insertions(+), 18 deletions(-)

--- a/drivers/thermal/thermal_core.c
+++ b/drivers/thermal/thermal_core.c
@@ -977,7 +977,11 @@ static void thermal_release(struct devic
 	}
 }
 
-static struct class *thermal_class;
+static const struct class thermal_class = {
+	.name = "thermal",
+	.dev_release = thermal_release,
+};
+static bool thermal_class_unavailable __ro_after_init = true;
 
 static inline
 void print_bind_err_msg(struct thermal_zone_device *tz,
@@ -1070,7 +1074,7 @@ __thermal_cooling_device_register(struct
 	    !ops->set_cur_state)
 		return ERR_PTR(-EINVAL);
 
-	if (!thermal_class)
+	if (thermal_class_unavailable)
 		return ERR_PTR(-ENODEV);
 
 	cdev = kzalloc_obj(*cdev);
@@ -1093,7 +1097,7 @@ __thermal_cooling_device_register(struct
 	cdev->np = np;
 	cdev->ops = ops;
 	cdev->updated = false;
-	cdev->device.class = thermal_class;
+	cdev->device.class = &thermal_class;
 	cdev->devdata = devdata;
 
 	ret = cdev->ops->get_max_state(cdev, &cdev->max_state);
@@ -1541,7 +1545,7 @@ thermal_zone_device_register_with_trips(
 	if (polling_delay && passive_delay > polling_delay)
 		return ERR_PTR(-EINVAL);
 
-	if (!thermal_class)
+	if (thermal_class_unavailable)
 		return ERR_PTR(-ENODEV);
 
 	tz = kzalloc_flex(*tz, trips, num_trips);
@@ -1577,7 +1581,7 @@ thermal_zone_device_register_with_trips(
 	if (!tz->ops.critical)
 		tz->ops.critical = thermal_zone_device_critical;
 
-	tz->device.class = thermal_class;
+	tz->device.class = &thermal_class;
 	tz->devdata = devdata;
 	tz->num_trips = num_trips;
 	for_each_trip_desc(tz, td) {
@@ -1929,21 +1933,11 @@ static int __init thermal_init(void)
 	if (result)
 		goto destroy_workqueue;
 
-	thermal_class = kzalloc_obj(*thermal_class);
-	if (!thermal_class) {
-		result = -ENOMEM;
+	result = class_register(&thermal_class);
+	if (result)
 		goto unregister_governors;
-	}
 
-	thermal_class->name = "thermal";
-	thermal_class->dev_release = thermal_release;
-
-	result = class_register(thermal_class);
-	if (result) {
-		kfree(thermal_class);
-		thermal_class = NULL;
-		goto unregister_governors;
-	}
+	thermal_class_unavailable = false;
 
 	result = register_pm_notifier(&thermal_pm_nb);
 	if (result)




^ permalink raw reply

* [PATCH v3 6/6] thermal: core: Suspend thermal zones later and resume them earlier
From: Rafael J. Wysocki @ 2026-04-06 16:16 UTC (permalink / raw)
  To: Linux PM; +Cc: Daniel Lezcano, LKML, Lukasz Luba, Armin Wolf
In-Reply-To: <5119690.31r3eYUQgx@rafael.j.wysocki>

From: Rafael J. Wysocki <rafael.j.wysocki@intel.com>

To avoid some undesirable interactions between thermal zone suspend
and resume with user space that is running when those operations are
carried out, move them closer to the suspend and resume of devices,
respectively, by updating dpm_prepare() to carry out thermal zone
suspend and dpm_complete() to start thermal zone resume (that will
continue asynchronously).

This also makes the code easier to follow by removing one, arguably
redundant, level of indirection represented by the thermal PM notifier.

Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
---

v2 -> v3:
   * Rebase on top of the v3 of the previous patch

v1 -> v2:
   * Reorder with respect to the previous patch
   * Use thermal_class_unavailable() to avoid running code that should
     not run without the thermal class
   * Suspend thermal zones after disabling device probing and resume
     them before enabling device probing for better synchronization

---
 drivers/base/power/main.c      |    5 +++
 drivers/thermal/thermal_core.c |   60 ++++++++++++-----------------------------
 include/linux/thermal.h        |    6 ++++
 3 files changed, 29 insertions(+), 42 deletions(-)

--- a/drivers/base/power/main.c
+++ b/drivers/base/power/main.c
@@ -33,6 +33,7 @@
 #include <trace/events/power.h>
 #include <linux/cpufreq.h>
 #include <linux/devfreq.h>
+#include <linux/thermal.h>
 #include <linux/timer.h>
 #include <linux/nmi.h>
 
@@ -1282,6 +1283,8 @@ void dpm_complete(pm_message_t state)
 	list_splice(&list, &dpm_list);
 	mutex_unlock(&dpm_list_mtx);
 
+	/* Start resuming thermal control */
+	thermal_pm_complete();
 	/* Allow device probing and trigger re-probing of deferred devices */
 	device_unblock_probing();
 	trace_suspend_resume(TPS("dpm_complete"), state.event, false);
@@ -2225,6 +2228,8 @@ int dpm_prepare(pm_message_t state)
 	 * instead. The normal behavior will be restored in dpm_complete().
 	 */
 	device_block_probing();
+	/* Suspend thermal control. */
+	thermal_pm_prepare();
 
 	mutex_lock(&dpm_list_mtx);
 	while (!list_empty(&dpm_list) && !error) {
--- a/drivers/thermal/thermal_core.c
+++ b/drivers/thermal/thermal_core.c
@@ -1838,7 +1838,7 @@ static void thermal_zone_pm_prepare(stru
 	cancel_delayed_work(&tz->poll_queue);
 }
 
-static void thermal_pm_notify_prepare(void)
+static void __thermal_pm_prepare(void)
 {
 	struct thermal_zone_device *tz;
 
@@ -1850,6 +1850,19 @@ static void thermal_pm_notify_prepare(vo
 		thermal_zone_pm_prepare(tz);
 }
 
+void thermal_pm_prepare(void)
+{
+	if (thermal_class_unavailable)
+		return;
+
+	__thermal_pm_prepare();
+	/*
+	 * Allow any leftover thermal work items already on the worqueue to
+	 * complete so they don't get in the way later.
+	 */
+	flush_workqueue(thermal_wq);
+}
+
 static void thermal_zone_pm_complete(struct thermal_zone_device *tz)
 {
 	guard(thermal_zone)(tz);
@@ -1866,10 +1879,13 @@ static void thermal_zone_pm_complete(str
 	mod_delayed_work(thermal_wq, &tz->poll_queue, 0);
 }
 
-static void thermal_pm_notify_complete(void)
+void thermal_pm_complete(void)
 {
 	struct thermal_zone_device *tz;
 
+	if (thermal_class_unavailable)
+		return;
+
 	guard(mutex)(&thermal_list_lock);
 
 	thermal_pm_suspended = false;
@@ -1878,41 +1894,6 @@ static void thermal_pm_notify_complete(v
 		thermal_zone_pm_complete(tz);
 }
 
-static int thermal_pm_notify(struct notifier_block *nb,
-			     unsigned long mode, void *_unused)
-{
-	switch (mode) {
-	case PM_HIBERNATION_PREPARE:
-	case PM_RESTORE_PREPARE:
-	case PM_SUSPEND_PREPARE:
-		thermal_pm_notify_prepare();
-		/*
-		 * Allow any leftover thermal work items already on the
-		 * worqueue to complete so they don't get in the way later.
-		 */
-		flush_workqueue(thermal_wq);
-		break;
-	case PM_POST_HIBERNATION:
-	case PM_POST_RESTORE:
-	case PM_POST_SUSPEND:
-		thermal_pm_notify_complete();
-		break;
-	default:
-		break;
-	}
-	return 0;
-}
-
-static struct notifier_block thermal_pm_nb = {
-	.notifier_call = thermal_pm_notify,
-	/*
-	 * Run at the lowest priority to avoid interference between the thermal
-	 * zone resume work items spawned by thermal_pm_notify() and the other
-	 * PM notifiers.
-	 */
-	.priority = INT_MIN,
-};
-
 static int __init thermal_init(void)
 {
 	int result;
@@ -1939,11 +1920,6 @@ static int __init thermal_init(void)
 
 	thermal_class_unavailable = false;
 
-	result = register_pm_notifier(&thermal_pm_nb);
-	if (result)
-		pr_warn("Thermal: Can not register suspend notifier, return %d\n",
-			result);
-
 	return 0;
 
 unregister_governors:
--- a/include/linux/thermal.h
+++ b/include/linux/thermal.h
@@ -273,6 +273,9 @@ bool thermal_trip_is_bound_to_cdev(struc
 int thermal_zone_device_enable(struct thermal_zone_device *tz);
 int thermal_zone_device_disable(struct thermal_zone_device *tz);
 void thermal_zone_device_critical(struct thermal_zone_device *tz);
+
+void thermal_pm_prepare(void);
+void thermal_pm_complete(void);
 #else
 static inline struct thermal_zone_device *thermal_zone_device_register_with_trips(
 					const char *type,
@@ -350,6 +353,9 @@ static inline int thermal_zone_device_en
 
 static inline int thermal_zone_device_disable(struct thermal_zone_device *tz)
 { return -ENODEV; }
+
+static inline void thermal_pm_prepare(void) {}
+static inline void thermal_pm_complete(void) {}
 #endif /* CONFIG_THERMAL */
 
 #endif /* __THERMAL_H__ */




^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox