* [PATCH v3 0/4] Add Qualcomm SPEL powercap driver
@ 2026-07-02 17:22 Manaf Meethalavalappu Pallikunhi
2026-07-02 17:22 ` [PATCH v3 1/4] dt-bindings: power: Add common power limit controller schema Manaf Meethalavalappu Pallikunhi
` (3 more replies)
0 siblings, 4 replies; 11+ messages in thread
From: Manaf Meethalavalappu Pallikunhi @ 2026-07-02 17:22 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Rafael J. Wysocki,
Bjorn Andersson, Konrad Dybcio, Daniel Lezcano
Cc: Gaurav Kohli, linux-arm-msm, devicetree, linux-kernel, linux-pm,
Manaf Meethalavalappu Pallikunhi
This patch series introduces support for Qualcomm's SPEL (SoC Power
and Electrical Limits) hardware, which provides power monitoring and
limiting capabilities for various power domains in Qualcomm SoCs.
The SPEL hardware enables:
- Real-time energy consumption monitoring across multiple power domains
- Hardware-enforced power limits with configurable time windows
- Support for up to 4 power limits (PL1-PL4) per domain
- Hierarchical power domain organization (SYS -> SOC -> subsystems)
The driver integrates with the Linux powercap framework, exposing SPEL
capabilities through standard sysfs interfaces. This allows userspace
applications and thermal management daemons to monitor energy consumption
and configure power limits for optimal power/performance balance.
This series introduces a new power/limits directory for power limit
controller bindings. The power-limit-controller.yaml schema is added to
MAINTAINERS under "QUALCOMM SPEL POWERCAP DRIVER" in patch 2/X alongside
the SPEL device specific binding, as this is its primary use case.
Signed-off-by: Manaf Meethalavalappu Pallikunhi <manaf.pallikunhi@oss.qualcomm.com>
---
Changes in v3:
- Created common power-limit-controller schema under power/limits and described
power-limit-controller device common capabilities.(Krzysztof)
- Created SoC-specific qcom,glymur-spel.yaml file instead of generic one and updated
description and commit.(Krzysztof)
- Used FIELD_MODIFY() macro at different places.(Konrad)
- Updated glymur specific domain info table.(Konrad)
- Updated all for-loops iterator declaration to C99 style.(Konrad)
- Fixed use-after-free bugs.(Sashiko)
- Corrected time_unit documention to reflect the logic.(Sashiko)
- Link to v2: https://lore.kernel.org/r/20260620-qcom_spel_driver_upstream-v2-0-a3ee6837c18f@oss.qualcomm.com
Changes in v2:
- Fixed DT issues: corrected unit-address mismatch and made compatible
platform-specific.(Konrad/Krzysztof)
- Cleaned up bitfield handling: switched to GENMASK, FIELD_* macros.(Konrad)
- Converted hex values to lowercase and simplified array definitions.(Konrad)
- used devm_platform_ioremap_resource_byname().(Konrad)
- Refactored data structures: merged domain data into spel_domain_info
and removed hardcoded limits using ARRAY_SIZE().(Daniel)
- Added bounds checks (TIME_WINDOW_MAX) and fixed time window RMW
handling (preserving reserved bits + firmware notify).(Sashiko)
- Resolved use-after-free, replaced allocation strategy, and
added proper cleanup in .release().(Sashiko)
- Updated time window calculation logic.
- Removed cyclic dependency, improved validation logic, and aligned
naming consistency.(Daniel)
- Link to v1: https://lore.kernel.org/r/20260519-qcom_spel_driver_upstream-v1-0-75356d1b7f94@oss.qualcomm.com
---
Manaf Meethalavalappu Pallikunhi (4):
dt-bindings: power: Add common power limit controller schema
dt-bindings: power: limits: Describe Qualcomm SPEL hardware
powercap: qcom: Add SPEL powercap driver
arm64: dts: qcom: glymur: Enable SPEL powercap driver
.../power/limits/power-limit-controller.yaml | 34 +
.../bindings/power/limits/qcom,glymur-spel.yaml | 61 ++
MAINTAINERS | 8 +
arch/arm64/boot/dts/qcom/glymur.dtsi | 10 +
drivers/powercap/Kconfig | 13 +
drivers/powercap/Makefile | 1 +
drivers/powercap/qcom_spel.c | 803 +++++++++++++++++++++
7 files changed, 930 insertions(+)
---
base-commit: 4f441960e691d37c880d2cc004de06bb5b6bd5e4
change-id: 20260702-qcom_spel_driver_upstream-909d37c8d8e8
Best regards,
--
Manaf Meethalavalappu Pallikunhi <manaf.pallikunhi@oss.qualcomm.com>
^ permalink raw reply [flat|nested] 11+ messages in thread* [PATCH v3 1/4] dt-bindings: power: Add common power limit controller schema 2026-07-02 17:22 [PATCH v3 0/4] Add Qualcomm SPEL powercap driver Manaf Meethalavalappu Pallikunhi @ 2026-07-02 17:22 ` Manaf Meethalavalappu Pallikunhi 2026-07-02 17:44 ` sashiko-bot 2026-07-03 6:20 ` Krzysztof Kozlowski 2026-07-02 17:22 ` [PATCH v3 2/4] dt-bindings: power: limits: Describe Qualcomm SPEL hardware Manaf Meethalavalappu Pallikunhi ` (2 subsequent siblings) 3 siblings, 2 replies; 11+ messages in thread From: Manaf Meethalavalappu Pallikunhi @ 2026-07-02 17:22 UTC (permalink / raw) To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Rafael J. Wysocki, Bjorn Andersson, Konrad Dybcio, Daniel Lezcano Cc: Gaurav Kohli, linux-arm-msm, devicetree, linux-kernel, linux-pm, Manaf Meethalavalappu Pallikunhi Add a common device tree binding schema for power limit controller devices. This schema defines the base properties and node naming pattern for hardware blocks that enforce power consumption limits on SoC power domains. Power limit controllers typically provide hardware-enforced power capping, configurable power limits, time window controls, and power or energy monitoring capabilities to prevent thermal overload and maintain system stability. Individual controller bindings should reference this schema and add device-specific properties. Signed-off-by: Manaf Meethalavalappu Pallikunhi <manaf.pallikunhi@oss.qualcomm.com> --- .../power/limits/power-limit-controller.yaml | 34 ++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/Documentation/devicetree/bindings/power/limits/power-limit-controller.yaml b/Documentation/devicetree/bindings/power/limits/power-limit-controller.yaml new file mode 100644 index 000000000000..a00548fdc79e --- /dev/null +++ b/Documentation/devicetree/bindings/power/limits/power-limit-controller.yaml @@ -0,0 +1,34 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/power/limits/power-limit-controller.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Power Limit Controller + +maintainers: + - Manaf Meethalavalappu Pallikunhi <manaf.pallikunhi@oss.qualcomm.com> + +description: + Power limit controllers are hardware blocks that enforce power consumption + limits on SoC power domains to prevent thermal overload, maintain system + stability, and comply with platform power budgets. + + These controllers typically provide + - Hardware-enforced power capping for one or more power domains + - Configurable power limits (e.g., sustained, burst, peak) + - Time window controls for power averaging + - Energy or power monitoring and reporting capabilities + - Power balancing algorithms across domains + + This binding describes the common properties for power limit controller + provider nodes. Individual controller bindings should reference this schema + and add device-specific properties. + +select: false + +properties: + $nodename: + pattern: "^power-limits(@.*)?$" + +additionalProperties: true -- 2.43.0 ^ permalink raw reply related [flat|nested] 11+ messages in thread
* Re: [PATCH v3 1/4] dt-bindings: power: Add common power limit controller schema 2026-07-02 17:22 ` [PATCH v3 1/4] dt-bindings: power: Add common power limit controller schema Manaf Meethalavalappu Pallikunhi @ 2026-07-02 17:44 ` sashiko-bot 2026-07-03 6:20 ` Krzysztof Kozlowski 1 sibling, 0 replies; 11+ messages in thread From: sashiko-bot @ 2026-07-02 17:44 UTC (permalink / raw) To: Manaf Meethalavalappu Pallikunhi; +Cc: conor+dt, devicetree, robh Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Low] YAML formatting disruption due to missing block scalar indicator in description -- commit 10d28ed72d993f07d4586a72f1c37a8d7f35d2eb Author: Manaf Meethalavalappu Pallikunhi <manaf.pallikunhi@oss.qualcomm.com> dt-bindings: power: Add common power limit controller schema This commit adds a common device tree binding schema for power limit controller devices. It defines the base properties and node naming pattern for hardware blocks that enforce power consumption limits on SoC power domains. > diff --git a/Documentation/devicetree/bindings/power/limits/power-limit-controller.yaml b/Documentation/devicetree/bindings/power/limits/power-limit-controller.yaml > new file mode 100644 > index 0000000000000..a00548fdc79e2 > --- /dev/null > +++ b/Documentation/devicetree/bindings/power/limits/power-limit-controller.yaml [ ... ] > +maintainers: > + - Manaf Meethalavalappu Pallikunhi <manaf.pallikunhi@oss.qualcomm.com> > + > +description: [Severity: Low] Should this description field include a block scalar indicator (like | or >)? Without it, the YAML parser will treat the multi-paragraph text as a plain scalar. This will fold the adjacent lines and the bulleted list below into a single run-on sentence, which disrupts the formatting in the generated documentation. > + Power limit controllers are hardware blocks that enforce power consumption > + limits on SoC power domains to prevent thermal overload, maintain system > + stability, and comply with platform power budgets. > + > + These controllers typically provide > + - Hardware-enforced power capping for one or more power domains > + - Configurable power limits (e.g., sustained, burst, peak) > + - Time window controls for power averaging > + - Energy or power monitoring and reporting capabilities > + - Power balancing algorithms across domains -- Sashiko AI review · https://sashiko.dev/#/patchset/20260702-qcom_spel_driver_upstream-v3-0-434d50f0c5b0@oss.qualcomm.com?part=1 ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v3 1/4] dt-bindings: power: Add common power limit controller schema 2026-07-02 17:22 ` [PATCH v3 1/4] dt-bindings: power: Add common power limit controller schema Manaf Meethalavalappu Pallikunhi 2026-07-02 17:44 ` sashiko-bot @ 2026-07-03 6:20 ` Krzysztof Kozlowski 1 sibling, 0 replies; 11+ messages in thread From: Krzysztof Kozlowski @ 2026-07-03 6:20 UTC (permalink / raw) To: Manaf Meethalavalappu Pallikunhi Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Rafael J. Wysocki, Bjorn Andersson, Konrad Dybcio, Daniel Lezcano, Gaurav Kohli, linux-arm-msm, devicetree, linux-kernel, linux-pm On Thu, Jul 02, 2026 at 10:52:54PM +0530, Manaf Meethalavalappu Pallikunhi wrote: > +title: Power Limit Controller > + > +maintainers: > + - Manaf Meethalavalappu Pallikunhi <manaf.pallikunhi@oss.qualcomm.com> > + > +description: You should have | to preserve formatting, > + Power limit controllers are hardware blocks that enforce power consumption > + limits on SoC power domains to prevent thermal overload, maintain system > + stability, and comply with platform power budgets. > + > + These controllers typically provide > + - Hardware-enforced power capping for one or more power domains > + - Configurable power limits (e.g., sustained, burst, peak) > + - Time window controls for power averaging > + - Energy or power monitoring and reporting capabilities > + - Power balancing algorithms across domains > + > + This binding describes the common properties for power limit controller > + provider nodes. Individual controller bindings should reference this schema > + and add device-specific properties. Where are the properties? I see nothing - empty list of properties. > + > +select: false > + > +properties: > + $nodename: > + pattern: "^power-limits(@.*)?$" > + > +additionalProperties: true > > -- > 2.43.0 > ^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v3 2/4] dt-bindings: power: limits: Describe Qualcomm SPEL hardware 2026-07-02 17:22 [PATCH v3 0/4] Add Qualcomm SPEL powercap driver Manaf Meethalavalappu Pallikunhi 2026-07-02 17:22 ` [PATCH v3 1/4] dt-bindings: power: Add common power limit controller schema Manaf Meethalavalappu Pallikunhi @ 2026-07-02 17:22 ` Manaf Meethalavalappu Pallikunhi 2026-07-02 17:30 ` sashiko-bot 2026-07-03 6:21 ` Krzysztof Kozlowski 2026-07-02 17:22 ` [PATCH v3 3/4] powercap: qcom: Add SPEL powercap driver Manaf Meethalavalappu Pallikunhi 2026-07-02 17:22 ` [PATCH v3 4/4] arm64: dts: qcom: glymur: Enable " Manaf Meethalavalappu Pallikunhi 3 siblings, 2 replies; 11+ messages in thread From: Manaf Meethalavalappu Pallikunhi @ 2026-07-02 17:22 UTC (permalink / raw) To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Rafael J. Wysocki, Bjorn Andersson, Konrad Dybcio, Daniel Lezcano Cc: Gaurav Kohli, linux-arm-msm, devicetree, linux-kernel, linux-pm, Manaf Meethalavalappu Pallikunhi The Qualcomm SPEL (SoC Power and Electrical Limits) is a hardware block that provides power monitoring and limiting capabilities for various power domains in Qualcomm SoCs. SPEL enforces power consumption limits to prevent thermal overload, maintain system stability, and comply with platform power budgets. It provides: - Hardware-enforced power capping with configurable power limits - Time window controls for power averaging - Energy counter monitoring for each power domain - Automatic throttling when power limits are exceeded The hardware supports multiple power domains including system (SYS), SoC, CPU clusters, GPU, memory, and peripheral subsystems. Each domain can have independent power limits and monitoring. Add a DeviceTree binding to describe the SPEL block on Qualcomm's SoC. Signed-off-by: Manaf Meethalavalappu Pallikunhi <manaf.pallikunhi@oss.qualcomm.com> --- .../bindings/power/limits/qcom,glymur-spel.yaml | 61 ++++++++++++++++++++++ MAINTAINERS | 7 +++ 2 files changed, 68 insertions(+) diff --git a/Documentation/devicetree/bindings/power/limits/qcom,glymur-spel.yaml b/Documentation/devicetree/bindings/power/limits/qcom,glymur-spel.yaml new file mode 100644 index 000000000000..e8d974cbdc64 --- /dev/null +++ b/Documentation/devicetree/bindings/power/limits/qcom,glymur-spel.yaml @@ -0,0 +1,61 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/power/limits/qcom,glymur-spel.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Qualcomm Glymur SoC Power and Electrical Limits (SPEL) + +maintainers: + - Manaf Meethalavalappu Pallikunhi <manaf.pallikunhi@oss.qualcomm.com> + +description: + The Qualcomm SPEL (SoC Power and Electrical Limits) is a hardware block that + provides power monitoring and limiting capabilities for various power domains + in Qualcomm SoCs. + + SPEL enforces power consumption limits to prevent thermal overload, maintain + system stability, and comply with platform power budgets. It provides + - Hardware-enforced power capping with configurable power limits + - Time window controls for power averaging + - Energy counter monitoring for each power domain + - Automatic throttling when power limits are exceeded + + The hardware supports multiple power domains including system (SYS), SoC, + CPU clusters, GPU, memory, and peripheral subsystems. Each domain can have + independent power limits and monitoring. + +allOf: + - $ref: power-limit-controller.yaml# + +properties: + compatible: + const: qcom,glymur-spel + + reg: + maxItems: 3 + + reg-names: + items: + - const: config + - const: constraints + - const: nodes + +required: + - compatible + - reg + - reg-names + +unevaluatedProperties: false + +examples: + - | + power-limits@ef3b000 { + compatible = "qcom,glymur-spel"; + reg = <0x0ef3b000 0x1000>, + <0x0ef3d000 0x1000>, + <0x0ef3e000 0x1000>; + reg-names = "config", + "constraints", + "nodes"; + }; diff --git a/MAINTAINERS b/MAINTAINERS index db3d8c441e4c..44e90b2d5e85 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -22368,6 +22368,13 @@ F: Documentation/networking/device_drivers/cellular/qualcomm/rmnet.rst F: drivers/net/ethernet/qualcomm/rmnet/ F: include/linux/if_rmnet.h +QUALCOMM SPEL POWERCAP DRIVER +M: Manaf Meethalavalappu Pallikunhi <manaf.pallikunhi@oss.qualcomm.com> +L: linux-arm-msm@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/power/limits/power-limit-controller.yaml +F: Documentation/devicetree/bindings/power/limits/qcom,glymur-spel.yaml + QUALCOMM TEE (QCOMTEE) DRIVER M: Amirreza Zarrabi <amirreza.zarrabi@oss.qualcomm.com> L: linux-arm-msm@vger.kernel.org -- 2.43.0 ^ permalink raw reply related [flat|nested] 11+ messages in thread
* Re: [PATCH v3 2/4] dt-bindings: power: limits: Describe Qualcomm SPEL hardware 2026-07-02 17:22 ` [PATCH v3 2/4] dt-bindings: power: limits: Describe Qualcomm SPEL hardware Manaf Meethalavalappu Pallikunhi @ 2026-07-02 17:30 ` sashiko-bot 2026-07-03 6:21 ` Krzysztof Kozlowski 1 sibling, 0 replies; 11+ messages in thread From: sashiko-bot @ 2026-07-02 17:30 UTC (permalink / raw) To: Manaf Meethalavalappu Pallikunhi; +Cc: devicetree, robh, conor+dt Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Low] The YAML `description` string will lose its structural formatting (specifically the bulleted list) because it is defined as a plain scalar. -- commit 2a8dfbf8a7cefb27618383801964875a7eaac9ea Author: Manaf Meethalavalappu Pallikunhi <manaf.pallikunhi@oss.qualcomm.com> dt-bindings: power: limits: Describe Qualcomm SPEL hardware This commit adds a DeviceTree binding to describe the Qualcomm SPEL (SoC Power and Electrical Limits) hardware block. SPEL provides power monitoring and limiting capabilities for various power domains in Qualcomm SoCs. > diff --git a/Documentation/devicetree/bindings/power/limits/qcom,glymur-spel.yaml b/Documentation/devicetree/bindings/power/limits/qcom,glymur-spel.yaml > new file mode 100644 > --- /dev/null > +++ b/Documentation/devicetree/bindings/power/limits/qcom,glymur-spel.yaml [ ... ] > +maintainers: > + - Manaf Meethalavalappu Pallikunhi <manaf.pallikunhi@oss.qualcomm.com> > + > +description: [Severity: Low] This isn't a bug, but should this use the literal block scalar indicator (description: |) to preserve the line breaks? Because this is currently defined as a plain scalar, the YAML parser will fold single newlines into spaces. This will cause the bulleted list below to collapse into a single unformatted run-on sentence when the documentation is generated by tools like dt_binding_check. > + The Qualcomm SPEL (SoC Power and Electrical Limits) is a hardware block that > + provides power monitoring and limiting capabilities for various power domains > + in Qualcomm SoCs. > + > + SPEL enforces power consumption limits to prevent thermal overload, maintain > + system stability, and comply with platform power budgets. It provides > + - Hardware-enforced power capping with configurable power limits > + - Time window controls for power averaging > + - Energy counter monitoring for each power domain > + - Automatic throttling when power limits are exceeded -- Sashiko AI review · https://sashiko.dev/#/patchset/20260702-qcom_spel_driver_upstream-v3-0-434d50f0c5b0@oss.qualcomm.com?part=2 ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v3 2/4] dt-bindings: power: limits: Describe Qualcomm SPEL hardware 2026-07-02 17:22 ` [PATCH v3 2/4] dt-bindings: power: limits: Describe Qualcomm SPEL hardware Manaf Meethalavalappu Pallikunhi 2026-07-02 17:30 ` sashiko-bot @ 2026-07-03 6:21 ` Krzysztof Kozlowski 1 sibling, 0 replies; 11+ messages in thread From: Krzysztof Kozlowski @ 2026-07-03 6:21 UTC (permalink / raw) To: Manaf Meethalavalappu Pallikunhi Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Rafael J. Wysocki, Bjorn Andersson, Konrad Dybcio, Daniel Lezcano, Gaurav Kohli, linux-arm-msm, devicetree, linux-kernel, linux-pm On Thu, Jul 02, 2026 at 10:52:55PM +0530, Manaf Meethalavalappu Pallikunhi wrote: > + SPEL enforces power consumption limits to prevent thermal overload, maintain > + system stability, and comply with platform power budgets. It provides > + - Hardware-enforced power capping with configurable power limits > + - Time window controls for power averaging > + - Energy counter monitoring for each power domain > + - Automatic throttling when power limits are exceeded > + > + The hardware supports multiple power domains including system (SYS), SoC, > + CPU clusters, GPU, memory, and peripheral subsystems. Each domain can have > + independent power limits and monitoring. > + > +allOf: > + - $ref: power-limit-controller.yaml# Which properties are shared with above schema? > + > +properties: > + compatible: > + const: qcom,glymur-spel > + > + reg: > + maxItems: 3 Best regards, Krzysztof ^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v3 3/4] powercap: qcom: Add SPEL powercap driver 2026-07-02 17:22 [PATCH v3 0/4] Add Qualcomm SPEL powercap driver Manaf Meethalavalappu Pallikunhi 2026-07-02 17:22 ` [PATCH v3 1/4] dt-bindings: power: Add common power limit controller schema Manaf Meethalavalappu Pallikunhi 2026-07-02 17:22 ` [PATCH v3 2/4] dt-bindings: power: limits: Describe Qualcomm SPEL hardware Manaf Meethalavalappu Pallikunhi @ 2026-07-02 17:22 ` Manaf Meethalavalappu Pallikunhi 2026-07-02 17:36 ` sashiko-bot 2026-07-03 6:24 ` Krzysztof Kozlowski 2026-07-02 17:22 ` [PATCH v3 4/4] arm64: dts: qcom: glymur: Enable " Manaf Meethalavalappu Pallikunhi 3 siblings, 2 replies; 11+ messages in thread From: Manaf Meethalavalappu Pallikunhi @ 2026-07-02 17:22 UTC (permalink / raw) To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Rafael J. Wysocki, Bjorn Andersson, Konrad Dybcio, Daniel Lezcano Cc: Gaurav Kohli, linux-arm-msm, devicetree, linux-kernel, linux-pm, Manaf Meethalavalappu Pallikunhi The Qualcomm SoC Power and Electrical Limits (SPEL) provides hardware based power monitoring and limiting capabilities for various power domains including System, SoC, CPU clusters, GPU, and various other subsystems. The driver integrates with the Linux powercap framework, exposing SPEL capabilities through powercap sysfs interfaces. Signed-off-by: Manaf Meethalavalappu Pallikunhi <manaf.pallikunhi@oss.qualcomm.com> --- MAINTAINERS | 1 + drivers/powercap/Kconfig | 13 + drivers/powercap/Makefile | 1 + drivers/powercap/qcom_spel.c | 803 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 818 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 44e90b2d5e85..5e716662782c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -22374,6 +22374,7 @@ L: linux-arm-msm@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/power/limits/power-limit-controller.yaml F: Documentation/devicetree/bindings/power/limits/qcom,glymur-spel.yaml +F: drivers/powercap/qcom_spel.c QUALCOMM TEE (QCOMTEE) DRIVER M: Amirreza Zarrabi <amirreza.zarrabi@oss.qualcomm.com> diff --git a/drivers/powercap/Kconfig b/drivers/powercap/Kconfig index 03c4c796d993..e3a47c653499 100644 --- a/drivers/powercap/Kconfig +++ b/drivers/powercap/Kconfig @@ -93,4 +93,17 @@ config DTPM_DEVFREQ help This enables support for device power limitation based on energy model. + +config QCOM_SPEL + tristate "Qualcomm SPEL Powercap driver" + depends on ARM64 || COMPILE_TEST + help + This enables support for the Qualcomm SoC Power and Electrical + Limits (SPEL) hardware, which allows power limits to be + enforced and monitored on Qualcomm SoCs. + + SPEL provides energy monitoring and power capping for multiple + domains including system, SoC, CPU clusters, GPU, and various + other subsystems. + endif diff --git a/drivers/powercap/Makefile b/drivers/powercap/Makefile index 5ab0dce565b9..8235fb9d3df6 100644 --- a/drivers/powercap/Makefile +++ b/drivers/powercap/Makefile @@ -8,3 +8,4 @@ obj-$(CONFIG_INTEL_RAPL) += intel_rapl_msr.o obj-$(CONFIG_INTEL_RAPL_TPMI) += intel_rapl_tpmi.o obj-$(CONFIG_IDLE_INJECT) += idle_inject.o obj-$(CONFIG_ARM_SCMI_POWERCAP) += arm_scmi_powercap.o +obj-$(CONFIG_QCOM_SPEL) += qcom_spel.o diff --git a/drivers/powercap/qcom_spel.c b/drivers/powercap/qcom_spel.c new file mode 100644 index 000000000000..e4ddf7390391 --- /dev/null +++ b/drivers/powercap/qcom_spel.c @@ -0,0 +1,803 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Qualcomm SPEL (SoC Power and Electrical Limits) Driver + * + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#include <linux/bitfield.h> +#include <linux/device.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> +#include <linux/powercap.h> +#include <linux/slab.h> +#include <linux/types.h> + +/* SPEL register bitmasks */ +#define ENERGY_STATUS_MASK GENMASK(31, 0) + +#define POWER_LIMIT_MASK GENMASK(14, 0) +#define POWER_LIMIT_ENABLE BIT(31) + +#define TIME_WINDOW_MASK_L GENMASK(14, 0) +#define TIME_WINDOW_MASK_H GENMASK(22, 16) +#define TIME_WINDOW_MAX ((FIELD_MAX(TIME_WINDOW_MASK_H) << 15) | \ + FIELD_MAX(TIME_WINDOW_MASK_L)) + +#define ENERGY_UNIT_MASK GENMASK(19, 16) +#define TIME_UNIT_MASK GENMASK(11, 8) +#define POWER_UNIT_MASK GENMASK(2, 0) + +#define LIMITS_CAPABILITY_OFFSET 0x20 +#define ENERGY_RPT_UNIT_OFFSET 0x04 + +#define ENERGY_UNIT_SCALE 1000 + +#define SPEL_DOMAIN_NAME_LENGTH 16 + +/* Domain types */ +enum spel_domain_type { + SPEL_DOMAIN_SYS, + SPEL_DOMAIN_SOC, + SPEL_DOMAIN_CL0, + SPEL_DOMAIN_CL1, + SPEL_DOMAIN_CL2, + SPEL_DOMAIN_IGPU, + SPEL_DOMAIN_DGPU, + SPEL_DOMAIN_NSP, + SPEL_DOMAIN_MMCX, + SPEL_DOMAIN_INFRA, + SPEL_DOMAIN_DRAM, + SPEL_DOMAIN_MDM, + SPEL_DOMAIN_WLAN, + SPEL_DOMAIN_USB1, + SPEL_DOMAIN_USB2, + SPEL_DOMAIN_USB3, + SPEL_DOMAIN_MAX, +}; + +/* Power limit IDs */ +enum spel_power_limit_id { + POWER_LIMIT1, + POWER_LIMIT2, + POWER_LIMIT3, + POWER_LIMIT4, + POWER_LIMITS_MAX, +}; + +/* Unit types for conversion */ +enum unit_type { + POWER_UNIT, + ENERGY_UNIT, + TIME_UNIT, +}; + +/* Power limit operation types */ +enum pl_ops_type { + PL_LIMIT, + PL_TIME_WINDOW, +}; + +static const char * const pl_names[] = { + [POWER_LIMIT1] = "pl1", + [POWER_LIMIT2] = "pl2", + [POWER_LIMIT3] = "pl3", + [POWER_LIMIT4] = "pl4", +}; + +/* Common domain names (can be shared across SoCs) */ +static const char * const spel_domain_names[SPEL_DOMAIN_MAX] = { + [SPEL_DOMAIN_SYS] = "sys", + [SPEL_DOMAIN_SOC] = "soc", + [SPEL_DOMAIN_CL0] = "cl0", + [SPEL_DOMAIN_CL1] = "cl1", + [SPEL_DOMAIN_CL2] = "cl2", + [SPEL_DOMAIN_IGPU] = "igpu", + [SPEL_DOMAIN_DGPU] = "dgpu", + [SPEL_DOMAIN_NSP] = "nsp", + [SPEL_DOMAIN_MMCX] = "mmcx", + [SPEL_DOMAIN_INFRA] = "infra", + [SPEL_DOMAIN_DRAM] = "dram", + [SPEL_DOMAIN_MDM] = "mdm", + [SPEL_DOMAIN_WLAN] = "wlan", + [SPEL_DOMAIN_USB1] = "usb1", + [SPEL_DOMAIN_USB2] = "usb2", + [SPEL_DOMAIN_USB3] = "usb3", +}; + +/* Glymur-specific domain register offsets */ +static const u32 glymur_domain_offsets[SPEL_DOMAIN_MAX] = { + [SPEL_DOMAIN_SYS] = 0x40, + [SPEL_DOMAIN_SOC] = 0x00, + [SPEL_DOMAIN_CL0] = 0x5c, + [SPEL_DOMAIN_CL1] = 0x60, + [SPEL_DOMAIN_CL2] = 0x64, + [SPEL_DOMAIN_IGPU] = 0x08, + [SPEL_DOMAIN_DGPU] = 0x44, + [SPEL_DOMAIN_NSP] = 0x0c, + [SPEL_DOMAIN_MMCX] = 0x10, + [SPEL_DOMAIN_INFRA] = 0x18, + [SPEL_DOMAIN_DRAM] = 0x1c, + [SPEL_DOMAIN_MDM] = 0x48, + [SPEL_DOMAIN_WLAN] = 0x4c, + [SPEL_DOMAIN_USB1] = 0x50, + [SPEL_DOMAIN_USB2] = 0x54, + [SPEL_DOMAIN_USB3] = 0x58, +}; + +/** + * struct spel_constraint_info - Power limit constraint information + * @limit_offset: Register offset for power limit value + * @time_window_offset: Register offset for time window + * @supported_mask: Bit mask in capability register + * @domain_id: Domain this constraint applies to + * @pl_id: Power limit ID (PL1, PL2, etc.) + */ +struct spel_constraint_info { + u32 limit_offset; + u32 time_window_offset; + u32 supported_mask; + enum spel_domain_type domain_id; + int pl_id; +}; + +/* Constraint configuration */ +static const struct spel_constraint_info constraints[] = { + /* SYS domain constraints */ + { 0x10, 0x70, BIT(0), SPEL_DOMAIN_SYS, POWER_LIMIT1 }, + { 0x14, 0x74, BIT(1), SPEL_DOMAIN_SYS, POWER_LIMIT2 }, + { 0x18, 0x78, BIT(2), SPEL_DOMAIN_SYS, POWER_LIMIT3 }, + { 0x1c, 0x7c, BIT(3), SPEL_DOMAIN_SYS, POWER_LIMIT4 }, + /* SoC domain constraints */ + { 0x00, 0x60, BIT(4), SPEL_DOMAIN_SOC, POWER_LIMIT1 }, + { 0x04, 0x64, BIT(5), SPEL_DOMAIN_SOC, POWER_LIMIT2 }, + { 0x08, 0x68, BIT(6), SPEL_DOMAIN_SOC, POWER_LIMIT3 }, + { 0x0c, 0x6c, BIT(7), SPEL_DOMAIN_SOC, POWER_LIMIT4 }, +}; + +/** + * struct spel_domain - SPEL power domain + * @power_zone: Powercap zone + * @lock: Mutex protecting register access + * @sp: Parent sys domain + * @status_reg: Energy counter register + * @name: Domain name + * @id: Domain type ID + */ +struct spel_domain { + struct powercap_zone power_zone; + struct mutex lock; /* Protects register read/write operations */ + void *sp; + void __iomem *status_reg; + char name[SPEL_DOMAIN_NAME_LENGTH]; + enum spel_domain_type id; +}; + +/** + * struct spel_system - SPEL system + * @domains: Array of domains + * @power_zone: Parent powercap zone + * @node_base: Base address for node registers + * @constraint_base: Base address for constraint registers + * @config_base: Base address for config registers + * @control_type: Powercap control type + * @dev: Device pointer for logging + * @limits: Supported power limits per domain + * @power_unit: Power unit in microWatts (common for all domains) + * @energy_unit: Energy unit in nanoJoules (common for all domains) + * @time_unit: Time unit in microseconds (common for all domains) + */ +struct spel_system { + struct spel_domain *domains; + struct powercap_zone *power_zone; + void __iomem *node_base; + void __iomem *constraint_base; + void __iomem *config_base; + struct powercap_control_type *control_type; + struct device *dev; + int limits[SPEL_DOMAIN_MAX]; + unsigned int power_unit; + unsigned int energy_unit; + unsigned int time_unit; +}; + +#define power_zone_to_spel_domain(_zone) \ + container_of(_zone, struct spel_domain, power_zone) + +static bool is_pl_valid(struct spel_domain *sd, int pl) +{ + struct spel_system *sp = sd->sp; + + return !!(sp->limits[sd->id] & BIT(pl)); +} + +static int get_pl_ops_offset(struct spel_domain *sd, int pl, enum pl_ops_type pl_op) +{ + for (int i = 0; i < ARRAY_SIZE(constraints); i++) { + const struct spel_constraint_info *ci = &constraints[i]; + + if (ci->domain_id == sd->id && ci->pl_id == pl) { + switch (pl_op) { + case PL_LIMIT: + return ci->limit_offset; + case PL_TIME_WINDOW: + return ci->time_window_offset; + default: + return -EOPNOTSUPP; + } + } + } + + return -EOPNOTSUPP; +} + +static u64 spel_unit_xlate(struct spel_domain *sd, enum unit_type type, + u64 value, int to_raw) +{ + struct spel_system *sp = sd->sp; + u64 units, scale; + + switch (type) { + case POWER_UNIT: + units = sp->power_unit; + scale = 1; + break; + case ENERGY_UNIT: + units = sp->energy_unit; + scale = ENERGY_UNIT_SCALE; + break; + case TIME_UNIT: + units = sp->time_unit; + scale = 1; + break; + default: + return value; + } + + if (to_raw) + return DIV_ROUND_CLOSEST_ULL(value * scale, units); + + value *= units; + return div64_u64(value, scale); +} + +static int spel_read_pl_data(struct spel_domain *sd, int pl, + enum pl_ops_type pl_op, bool xlate, u64 *data) +{ + struct spel_system *sp = sd->sp; + void __iomem *reg_addr; + u64 value; + int offset; + + if (!is_pl_valid(sd, pl)) + return -EINVAL; + + offset = get_pl_ops_offset(sd, pl, pl_op); + if (offset < 0) + return offset; + + guard(mutex)(&sd->lock); + + reg_addr = sp->constraint_base + offset; + value = readl(reg_addr); + + switch (pl_op) { + case PL_LIMIT: + value = FIELD_GET(POWER_LIMIT_MASK, value); + if (xlate) + *data = spel_unit_xlate(sd, POWER_UNIT, value, 0); + else + *data = value; + break; + case PL_TIME_WINDOW: + /* Decode time window: bits [22:16] are upper 7 bits, [14:0] are lower 15 bits */ + value = (FIELD_GET(TIME_WINDOW_MASK_H, value) << 15) | + FIELD_GET(TIME_WINDOW_MASK_L, value); + if (xlate) + *data = spel_unit_xlate(sd, TIME_UNIT, value, 0); + else + *data = value; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int spel_write_pl_data(struct spel_domain *sd, int pl, + enum pl_ops_type pl_op, unsigned long long value) +{ + struct spel_system *sp = sd->sp; + void __iomem *reg_addr; + u64 reg_val, new_val; + int offset; + + if (!is_pl_valid(sd, pl)) + return -EINVAL; + + offset = get_pl_ops_offset(sd, pl, pl_op); + if (offset < 0) + return offset; + + guard(mutex)(&sd->lock); + + reg_addr = sp->constraint_base + offset; + reg_val = readl(reg_addr); + + switch (pl_op) { + case PL_LIMIT: + new_val = spel_unit_xlate(sd, POWER_UNIT, value, 1); + if (new_val > FIELD_MAX(POWER_LIMIT_MASK)) + return -EINVAL; + + FIELD_MODIFY(POWER_LIMIT_MASK, ®_val, new_val); + + /* + * Enable/Disable PL based on the value: + * - If value is 0, disable the PL (clear enable bit) + * - If value is non-zero, enable the PL (set enable bit) + */ + FIELD_MODIFY(POWER_LIMIT_ENABLE, ®_val, new_val ? 1 : 0); + + writel(reg_val, reg_addr); + return 0; + + case PL_TIME_WINDOW: + /* + * Encode time window: upper 7 bits to [22:16], lower 15 bits to [14:0] + */ + new_val = spel_unit_xlate(sd, TIME_UNIT, value, 1); + if (new_val > TIME_WINDOW_MAX) + return -EINVAL; + + /* Read-modify-write to preserve other bits */ + FIELD_MODIFY(TIME_WINDOW_MASK_H, ®_val, new_val >> 15); + FIELD_MODIFY(TIME_WINDOW_MASK_L, ®_val, new_val & FIELD_MAX(TIME_WINDOW_MASK_L)); + writel(reg_val, reg_addr); + + /* + * Time window register update doesn't trigger firmware interrupt. + * Write to the PL register with current value to trigger the interrupt. + */ + offset = get_pl_ops_offset(sd, pl, PL_LIMIT); + if (offset >= 0) { + reg_addr = sp->constraint_base + offset; + reg_val = readl(reg_addr); + writel(reg_val, reg_addr); + } + return 0; + + default: + return -EINVAL; + } +} + +static int spel_get_energy_counter(struct powercap_zone *power_zone, u64 *energy_raw) +{ + struct spel_domain *sd = power_zone_to_spel_domain(power_zone); + u64 value; + + value = readl(sd->status_reg); + + *energy_raw = spel_unit_xlate(sd, ENERGY_UNIT, value, 0); + + return 0; +} + +static int spel_get_max_energy_counter(struct powercap_zone *pcd_dev, u64 *energy) +{ + struct spel_domain *sd = power_zone_to_spel_domain(pcd_dev); + + *energy = spel_unit_xlate(sd, ENERGY_UNIT, ENERGY_STATUS_MASK, 0); + + return 0; +} + +static int spel_release_zone(struct powercap_zone *power_zone) +{ + struct spel_domain *sd = power_zone_to_spel_domain(power_zone); + + /* + * Free the domains array when the last zone (SYS domain) is released. + * This ensures proper cleanup even if sysfs files are held open during unbind. + */ + if (sd->id == SPEL_DOMAIN_SYS) { + struct spel_domain *domains = sd; + + /* Destroy all mutexes before freeing the domains array */ + for (int i = 0; i < ARRAY_SIZE(spel_domain_names); i++) + mutex_destroy(&domains[i].lock); + + kfree(domains); + } + + return 0; +} + +static int spel_find_nr_power_limit(struct spel_domain *sd) +{ + int nr_pl = 0; + + for (int i = 0; i < ARRAY_SIZE(pl_names); i++) { + if (is_pl_valid(sd, i)) + nr_pl++; + } + + return nr_pl; +} + +static const struct powercap_zone_ops zone_ops = { + .get_energy_uj = spel_get_energy_counter, + .get_max_energy_range_uj = spel_get_max_energy_counter, + .release = spel_release_zone, +}; + +static int spel_constraint_to_pl(struct spel_domain *sd, int cid) +{ + int id = 0; + + for (int i = 0; i < ARRAY_SIZE(pl_names); i++) { + if (is_pl_valid(sd, i) && id++ == cid) + return i; + } + + return -EINVAL; +} + +static int spel_set_power_limit(struct powercap_zone *power_zone, int cid, + u64 power_limit) +{ + struct spel_domain *sd = power_zone_to_spel_domain(power_zone); + int id; + + id = spel_constraint_to_pl(sd, cid); + if (id < 0) + return id; + + return spel_write_pl_data(sd, id, PL_LIMIT, power_limit); +} + +static int spel_get_power_limit(struct powercap_zone *power_zone, int cid, + u64 *data) +{ + struct spel_domain *sd = power_zone_to_spel_domain(power_zone); + u64 val; + int ret, id; + + id = spel_constraint_to_pl(sd, cid); + if (id < 0) + return id; + + ret = spel_read_pl_data(sd, id, PL_LIMIT, true, &val); + if (!ret) + *data = val; + + return ret; +} + +static int spel_set_time_window(struct powercap_zone *power_zone, int cid, + u64 window) +{ + struct spel_domain *sd = power_zone_to_spel_domain(power_zone); + int id; + + id = spel_constraint_to_pl(sd, cid); + if (id < 0) + return id; + + return spel_write_pl_data(sd, id, PL_TIME_WINDOW, window); +} + +static int spel_get_time_window(struct powercap_zone *power_zone, int cid, + u64 *data) +{ + struct spel_domain *sd = power_zone_to_spel_domain(power_zone); + u64 val; + int ret, id; + + id = spel_constraint_to_pl(sd, cid); + if (id < 0) + return id; + + ret = spel_read_pl_data(sd, id, PL_TIME_WINDOW, true, &val); + if (!ret) + *data = val; + + return ret; +} + +static const char *spel_get_constraint_name(struct powercap_zone *power_zone, + int cid) +{ + struct spel_domain *sd = power_zone_to_spel_domain(power_zone); + int id; + + id = spel_constraint_to_pl(sd, cid); + if (id >= 0 && id < ARRAY_SIZE(pl_names)) + return pl_names[id]; + + return NULL; +} + +static const struct powercap_zone_constraint_ops constraint_ops = { + .set_power_limit_uw = spel_set_power_limit, + .get_power_limit_uw = spel_get_power_limit, + .set_time_window_us = spel_set_time_window, + .get_time_window_us = spel_get_time_window, + .get_name = spel_get_constraint_name, +}; + +static void spel_init_domains(struct spel_system *sp) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(spel_domain_names); i++) { + struct spel_domain *sd = &sp->domains[i]; + + sd->sp = sp; + snprintf(sd->name, SPEL_DOMAIN_NAME_LENGTH, "%s", + spel_domain_names[i]); + sd->id = i; + sd->status_reg = sp->node_base + glymur_domain_offsets[i]; + + /* PL1 is always supported (required for powercap registration) */ + sp->limits[i] = BIT(POWER_LIMIT1); + } +} + +static void spel_update_unit(struct spel_system *sp) +{ + u32 value, shift; + + /* Read power_unit and time_unit from offset 0x0 */ + value = readl(sp->config_base); + + /* + * Unit calculation: 1 / (2^shift) + * Masks limit: TIME_UNIT (4 bits, max 15), POWER_UNIT (3 bits, max 7). + */ + shift = FIELD_GET(POWER_UNIT_MASK, value); + sp->power_unit = 1000000 / (1 << shift); + + shift = FIELD_GET(TIME_UNIT_MASK, value); + /* + * Convert to microseconds: base unit is 1ms, divided by 2^shift. + */ + sp->time_unit = 1000 / (1 << shift); + + /* Read energy_unit from ENERGY_RPT_UNIT_OFFSET */ + value = readl(sp->config_base + ENERGY_RPT_UNIT_OFFSET); + + /* + * Unit calculation: 1 / (2^shift) + * Masks limit: ENERGY_UNIT (4 bits, max 15). + */ + shift = FIELD_GET(ENERGY_UNIT_MASK, value); + sp->energy_unit = ENERGY_UNIT_SCALE * 1000000 / (1 << shift); + + dev_dbg(sp->dev, "Units: energy=%dnJ, time=%dus, power=%duW\n", + sp->energy_unit, sp->time_unit, sp->power_unit); +} + +static void spel_detect_powerlimit(struct spel_domain *sd) +{ + struct spel_system *sp = sd->sp; + u32 capabilities; + + capabilities = readl(sp->config_base + LIMITS_CAPABILITY_OFFSET); + + /* + * Detect power limits from hardware capabilities. + * Start from index 1 (POWER_LIMIT2) since PL1 is always enabled in spel_init_domains(). + */ + for (int i = 1; i < ARRAY_SIZE(pl_names); i++) { + for (int j = 0; j < ARRAY_SIZE(constraints); j++) { + const struct spel_constraint_info *ci = &constraints[j]; + + if (ci->domain_id == sd->id && ci->pl_id == i) { + if (capabilities & ci->supported_mask) + sp->limits[sd->id] |= BIT(i); + break; + } + } + } +} + +static int spel_init_system(struct spel_system *sp, struct device *dev) +{ + /* Read unit configuration (common for all domains) */ + spel_update_unit(sp); + + sp->domains = kcalloc(ARRAY_SIZE(spel_domain_names), + sizeof(struct spel_domain), GFP_KERNEL); + if (!sp->domains) + return -ENOMEM; + + spel_init_domains(sp); + + for (int i = 0; i < ARRAY_SIZE(spel_domain_names); i++) { + struct spel_domain *sd = &sp->domains[i]; + + mutex_init(&sd->lock); + spel_detect_powerlimit(sd); + } + + return 0; +} + +static int spel_register_powercap(struct spel_system *sp) +{ + struct spel_domain *sd; + struct powercap_zone *power_zone; + int nr_pl, ret; + + /* Register SYS domain as parent zone */ + sd = &sp->domains[SPEL_DOMAIN_SYS]; + nr_pl = spel_find_nr_power_limit(sd); + + power_zone = powercap_register_zone(&sd->power_zone, + sp->control_type, sd->name, + NULL, &zone_ops, nr_pl, + &constraint_ops); + if (IS_ERR(power_zone)) { + dev_err(sp->dev, "Failed to register power zone %s\n", + sd->name); + return PTR_ERR(power_zone); + } + sp->power_zone = power_zone; + + /* Register other domains as children */ + for (int i = 0; i < ARRAY_SIZE(spel_domain_names); i++) { + struct powercap_zone *parent; + + if (i == SPEL_DOMAIN_SYS) + continue; + + sd = &sp->domains[i]; + + /* SOC is child of SYS, others are children of SOC */ + if (i == SPEL_DOMAIN_SOC) + parent = sp->power_zone; + else + parent = &sp->domains[SPEL_DOMAIN_SOC].power_zone; + + nr_pl = spel_find_nr_power_limit(sd); + power_zone = powercap_register_zone(&sd->power_zone, + sp->control_type, + sd->name, parent, + &zone_ops, nr_pl, + &constraint_ops); + + if (IS_ERR(power_zone)) { + dev_err(sp->dev, "Failed to register power_zone %s\n", + sd->name); + ret = PTR_ERR(power_zone); + /* Unregister in reverse order: children first, then SOC, then SYS */ + for (int j = i - 1; j >= 0; j--) + powercap_unregister_zone(sp->control_type, + &sp->domains[j].power_zone); + return ret; + } + } + + return 0; +} + +static int spel_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct powercap_control_type *ct; + struct spel_system *sp; + int ret; + + sp = kzalloc_obj(struct spel_system); + if (!sp) + return -ENOMEM; + + sp->dev = dev; + + /* Map config registers (units, capabilities) */ + sp->config_base = devm_platform_ioremap_resource_byname(pdev, "config"); + if (IS_ERR(sp->config_base)) { + ret = PTR_ERR(sp->config_base); + goto err_free_sp; + } + + /* Map constraint registers (power limits) */ + sp->constraint_base = devm_platform_ioremap_resource_byname(pdev, "constraints"); + if (IS_ERR(sp->constraint_base)) { + ret = PTR_ERR(sp->constraint_base); + goto err_free_sp; + } + + /* Map spel domain registers (energy counters) */ + sp->node_base = devm_platform_ioremap_resource_byname(pdev, "nodes"); + if (IS_ERR(sp->node_base)) { + ret = PTR_ERR(sp->node_base); + goto err_free_sp; + } + + sp->control_type = powercap_register_control_type(NULL, "qcom-spel", + NULL); + if (IS_ERR(sp->control_type)) { + dev_err(dev, "Failed to register control type\n"); + ret = PTR_ERR(sp->control_type); + goto err_free_sp; + } + + /* Save control_type before it might be freed by spel_release_zone() */ + ct = sp->control_type; + + /* Initialize system and domains */ + ret = spel_init_system(sp, dev); + if (ret) { + dev_err(dev, "Failed to initialize system\n"); + goto err_unregister_control; + } + + ret = spel_register_powercap(sp); + if (ret) { + dev_err(dev, "Failed to register powercap zones\n"); + /* + * If SYS zone was registered, err_cleanup inside spel_register_powercap + * already unregistered all zones and spel_release_zone freed sp->domains. + * If SYS was never registered (sp->power_zone == NULL), free manually. + */ + if (!sp->power_zone) + kfree(sp->domains); + kfree(sp); + powercap_unregister_control_type(ct); + return ret; + } + + platform_set_drvdata(pdev, sp); + + return 0; + +err_unregister_control: + powercap_unregister_control_type(ct); +err_free_sp: + kfree(sp); + return ret; +} + +static void spel_remove(struct platform_device *pdev) +{ + struct spel_system *sp = platform_get_drvdata(pdev); + struct powercap_control_type *ct = sp->control_type; + + /* + * Unregister in reverse order: children first, then SOC, then SYS. + */ + for (int i = ARRAY_SIZE(spel_domain_names) - 1; i >= 0; i--) + powercap_unregister_zone(ct, &sp->domains[i].power_zone); + + powercap_unregister_control_type(ct); + + kfree(sp); +} + +static const struct of_device_id spel_of_match[] = { + { .compatible = "qcom,glymur-spel" }, + { } +}; +MODULE_DEVICE_TABLE(of, spel_of_match); + +static struct platform_driver spel_driver = { + .probe = spel_probe, + .remove = spel_remove, + .driver = { + .name = "qcom_spel", + .of_match_table = spel_of_match, + }, +}; + +module_platform_driver(spel_driver); + +MODULE_DESCRIPTION("Qualcomm SPEL Powercap Driver"); +MODULE_LICENSE("GPL"); -- 2.43.0 ^ permalink raw reply related [flat|nested] 11+ messages in thread
* Re: [PATCH v3 3/4] powercap: qcom: Add SPEL powercap driver 2026-07-02 17:22 ` [PATCH v3 3/4] powercap: qcom: Add SPEL powercap driver Manaf Meethalavalappu Pallikunhi @ 2026-07-02 17:36 ` sashiko-bot 2026-07-03 6:24 ` Krzysztof Kozlowski 1 sibling, 0 replies; 11+ messages in thread From: sashiko-bot @ 2026-07-02 17:36 UTC (permalink / raw) To: Manaf Meethalavalappu Pallikunhi; +Cc: devicetree, conor+dt, robh Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider: - [High] Calculation of `time_unit` can result in 0 due to integer truncation, leading to a division-by-zero kernel panic on sysfs writes. - [High] The `sp->domains` array containing `struct device` objects is freed prematurely when the SYS domain is unregistered, leading to a Use-After-Free. - [High] Double free of the `sp->domains` array occurs in the `spel_probe` error path. - [Medium] Large user inputs for power limits or time windows can silently overflow during translation, bypassing bounds checks. - [Low] The driver unconditionally exposes non-functional `POWER_LIMIT1` sysfs attributes for domains that lack hardware constraints. - [Low] Missing `mutex_destroy()` on initialized domain locks during the early error path in `spel_probe()`. -- commit a896bac2c899d892a5883e225ce50e98f4608e61 Author: Manaf Meethalavalappu Pallikunhi <manaf.pallikunhi@oss.qualcomm.com> powercap: qcom: Add SPEL powercap driver The Qualcomm SoC Power and Electrical Limits (SPEL) provides hardware based power monitoring and limiting capabilities for various power domains including System, SoC, CPU clusters, GPU, and various other subsystems. > diff --git a/drivers/powercap/qcom_spel.c b/drivers/powercap/qcom_spel.c > new file mode 100644 > index 0000000000000..e4ddf73903915 > --- /dev/null > +++ b/drivers/powercap/qcom_spel.c [ ... ] > +static u64 spel_unit_xlate(struct spel_domain *sd, enum unit_type type, > + u64 value, int to_raw) > +{ > + struct spel_system *sp = sd->sp; > + u64 units, scale; > + > + switch (type) { > + case POWER_UNIT: > + units = sp->power_unit; > + scale = 1; > + break; > + case ENERGY_UNIT: > + units = sp->energy_unit; > + scale = ENERGY_UNIT_SCALE; > + break; > + case TIME_UNIT: > + units = sp->time_unit; > + scale = 1; > + break; > + default: > + return value; > + } > + > + if (to_raw) > + return DIV_ROUND_CLOSEST_ULL(value * scale, units); [Severity: High] Can the time_unit calculation result in 0? If the hardware register provides a shift >= 10, the time_unit calculation in spel_update_unit() evaluates to 0 due to integer truncation: sp->time_unit = 1000 / (1 << shift); If units is 0, the DIV_ROUND_CLOSEST_ULL() macro here will perform a division by zero, triggering a kernel panic. [ ... ] > +static int spel_write_pl_data(struct spel_domain *sd, int pl, > + enum pl_ops_type pl_op, unsigned long long value) > +{ > + struct spel_system *sp = sd->sp; > + void __iomem *reg_addr; > + u64 reg_val, new_val; > + int offset; > + > + if (!is_pl_valid(sd, pl)) > + return -EINVAL; > + > + offset = get_pl_ops_offset(sd, pl, pl_op); > + if (offset < 0) > + return offset; > + > + guard(mutex)(&sd->lock); > + > + reg_addr = sp->constraint_base + offset; > + reg_val = readl(reg_addr); > + > + switch (pl_op) { > + case PL_LIMIT: > + new_val = spel_unit_xlate(sd, POWER_UNIT, value, 1); > + if (new_val > FIELD_MAX(POWER_LIMIT_MASK)) [Severity: Medium] Could large user inputs silently overflow during translation? The user-provided 64-bit value via sysfs is passed to spel_unit_xlate(), which uses the DIV_ROUND_CLOSEST_ULL() macro. If the user provides a very large value, the internal addition of units / 2 within the macro could overflow the 64-bit unsigned integer, wrapping around to a small number. This wrapped value would then pass the FIELD_MAX(POWER_LIMIT_MASK) check, converting an intended boundary violation into a near-zero limit that could aggressively throttle the hardware domain. > + return -EINVAL; [ ... ] > +static int spel_release_zone(struct powercap_zone *power_zone) > +{ > + struct spel_domain *sd = power_zone_to_spel_domain(power_zone); > + > + /* > + * Free the domains array when the last zone (SYS domain) is released. > + * This ensures proper cleanup even if sysfs files are held open during unbind. > + */ > + if (sd->id == SPEL_DOMAIN_SYS) { > + struct spel_domain *domains = sd; > + > + /* Destroy all mutexes before freeing the domains array */ > + for (int i = 0; i < ARRAY_SIZE(spel_domain_names); i++) > + mutex_destroy(&domains[i].lock); > + > + kfree(domains); [Severity: High] Does this prematurely free the sp->domains array? The driver allocates all domain device objects in a single contiguous array and frees the entire array when the SYS domain's release callback fires. However, during driver unbind in spel_remove(), unregistering a child domain calls device_unregister(), which synchronously drops the child's reference to its parent. This could allow the SYS domain to reach a refcount of 0 and be released, freeing the domains array while a child domain's device is still in use (e.g., if a user is holding open a sysfs file). When the user later closes the file, the core would dereference the freed memory. > + } > + > + return 0; > +} [ ... ] > +static void spel_init_domains(struct spel_system *sp) > +{ > + unsigned int i; > + > + for (i = 0; i < ARRAY_SIZE(spel_domain_names); i++) { > + struct spel_domain *sd = &sp->domains[i]; > + > + sd->sp = sp; > + snprintf(sd->name, SPEL_DOMAIN_NAME_LENGTH, "%s", > + spel_domain_names[i]); > + sd->id = i; > + sd->status_reg = sp->node_base + glymur_domain_offsets[i]; > + > + /* PL1 is always supported (required for powercap registration) */ > + sp->limits[i] = BIT(POWER_LIMIT1); [Severity: Low] Does this unconditionally expose non-functional POWER_LIMIT1 sysfs attributes for domains that lack hardware constraints? For domains like IGPU that have no constraint register offsets defined in the constraints array, reading or writing the exposed PL1 sysfs files will fall through to get_pl_ops_offset() which returns -EOPNOTSUPP. > + } > +} [ ... ] > +static int spel_probe(struct platform_device *pdev) > +{ [ ... ] > + ret = spel_register_powercap(sp); > + if (ret) { > + dev_err(dev, "Failed to register powercap zones\n"); > + /* > + * If SYS zone was registered, err_cleanup inside spel_register_powercap > + * already unregistered all zones and spel_release_zone freed sp->domains. > + * If SYS was never registered (sp->power_zone == NULL), free manually. > + */ > + if (!sp->power_zone) > + kfree(sp->domains); [Severity: High] Can this result in a double free of the domains array? If powercap_register_zone() for the SYS domain fails deep within its execution (such as during device_add()), it internally cleans up, triggering the spel_release_zone() callback which executes kfree(domains). The registration function would return an error and sp->power_zone remains NULL. The error handler here checks if (!sp->power_zone) and calls kfree(sp->domains) again, mistakenly double-freeing the array because it assumes NULL means the release callback was never invoked. [Severity: Low] If we free sp->domains here before the devices are registered, do we leak the debugging state of the mutexes? spel_init_system() initializes a mutex for each of the 16 domains. If zone registration fails early and we directly free the array, mutex_destroy() is never called. > + kfree(sp); > + powercap_unregister_control_type(ct); > + return ret; > + } -- Sashiko AI review · https://sashiko.dev/#/patchset/20260702-qcom_spel_driver_upstream-v3-0-434d50f0c5b0@oss.qualcomm.com?part=3 ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v3 3/4] powercap: qcom: Add SPEL powercap driver 2026-07-02 17:22 ` [PATCH v3 3/4] powercap: qcom: Add SPEL powercap driver Manaf Meethalavalappu Pallikunhi 2026-07-02 17:36 ` sashiko-bot @ 2026-07-03 6:24 ` Krzysztof Kozlowski 1 sibling, 0 replies; 11+ messages in thread From: Krzysztof Kozlowski @ 2026-07-03 6:24 UTC (permalink / raw) To: Manaf Meethalavalappu Pallikunhi Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Rafael J. Wysocki, Bjorn Andersson, Konrad Dybcio, Daniel Lezcano, Gaurav Kohli, linux-arm-msm, devicetree, linux-kernel, linux-pm On Thu, Jul 02, 2026 at 10:52:56PM +0530, Manaf Meethalavalappu Pallikunhi wrote: > QUALCOMM TEE (QCOMTEE) DRIVER > M: Amirreza Zarrabi <amirreza.zarrabi@oss.qualcomm.com> > diff --git a/drivers/powercap/Kconfig b/drivers/powercap/Kconfig > index 03c4c796d993..e3a47c653499 100644 > --- a/drivers/powercap/Kconfig > +++ b/drivers/powercap/Kconfig > @@ -93,4 +93,17 @@ config DTPM_DEVFREQ > help > This enables support for device power limitation based on > energy model. > + > +config QCOM_SPEL > + tristate "Qualcomm SPEL Powercap driver" > + depends on ARM64 || COMPILE_TEST Why does it depend on ARM64? Where is dependency on ARCH_QCOM? ... > +static int spel_probe(struct platform_device *pdev) > +{ > + struct device *dev = &pdev->dev; > + struct powercap_control_type *ct; > + struct spel_system *sp; > + int ret; > + > + sp = kzalloc_obj(struct spel_system); I miss the point why you are not using devm. You are introducing ordering issues now, because sp is freed before other cleanups are done. > + if (!sp) > + return -ENOMEM; > + > + sp->dev = dev; > + > + /* Map config registers (units, capabilities) */ > + sp->config_base = devm_platform_ioremap_resource_byname(pdev, "config"); > + if (IS_ERR(sp->config_base)) { > + ret = PTR_ERR(sp->config_base); > + goto err_free_sp; > + } > + > + /* Map constraint registers (power limits) */ > + sp->constraint_base = devm_platform_ioremap_resource_byname(pdev, "constraints"); > + if (IS_ERR(sp->constraint_base)) { > + ret = PTR_ERR(sp->constraint_base); > + goto err_free_sp; > + } > + > + /* Map spel domain registers (energy counters) */ > + sp->node_base = devm_platform_ioremap_resource_byname(pdev, "nodes"); > + if (IS_ERR(sp->node_base)) { > + ret = PTR_ERR(sp->node_base); > + goto err_free_sp; > + } > + > + sp->control_type = powercap_register_control_type(NULL, "qcom-spel", > + NULL); > + if (IS_ERR(sp->control_type)) { > + dev_err(dev, "Failed to register control type\n"); syntax is everywhere: ret = dev_err_probe Since years already. Don't upstream your old downstream code, but write something based on current Linux drivers. > + ret = PTR_ERR(sp->control_type); > + goto err_free_sp; > + } > + > + /* Save control_type before it might be freed by spel_release_zone() */ > + ct = sp->control_type; > + > + /* Initialize system and domains */ > + ret = spel_init_system(sp, dev); > + if (ret) { > + dev_err(dev, "Failed to initialize system\n"); dev_err_probe Best regards, Krzysztof ^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v3 4/4] arm64: dts: qcom: glymur: Enable SPEL powercap driver 2026-07-02 17:22 [PATCH v3 0/4] Add Qualcomm SPEL powercap driver Manaf Meethalavalappu Pallikunhi ` (2 preceding siblings ...) 2026-07-02 17:22 ` [PATCH v3 3/4] powercap: qcom: Add SPEL powercap driver Manaf Meethalavalappu Pallikunhi @ 2026-07-02 17:22 ` Manaf Meethalavalappu Pallikunhi 3 siblings, 0 replies; 11+ messages in thread From: Manaf Meethalavalappu Pallikunhi @ 2026-07-02 17:22 UTC (permalink / raw) To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Rafael J. Wysocki, Bjorn Andersson, Konrad Dybcio, Daniel Lezcano Cc: Gaurav Kohli, linux-arm-msm, devicetree, linux-kernel, linux-pm, Manaf Meethalavalappu Pallikunhi The Qualcomm SoC Power and Electrical Limits (SPEL) provides hardware based power monitoring and limiting capabilities for various power domains including System, SoC, CPU clusters, GPU, and various other subsystems for glymur. Signed-off-by: Manaf Meethalavalappu Pallikunhi <manaf.pallikunhi@oss.qualcomm.com> --- arch/arm64/boot/dts/qcom/glymur.dtsi | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/glymur.dtsi b/arch/arm64/boot/dts/qcom/glymur.dtsi index 20b49af7298e..9c942db8c824 100644 --- a/arch/arm64/boot/dts/qcom/glymur.dtsi +++ b/arch/arm64/boot/dts/qcom/glymur.dtsi @@ -5045,6 +5045,16 @@ spmi_bus2: spmi@c48000 { }; }; + power-limits@ef3b000 { + compatible = "qcom,glymur-spel"; + reg = <0x0 0x0ef3b000 0x0 0x1000>, + <0x0 0x0ef3d000 0x0 0x1000>, + <0x0 0x0ef3e000 0x0 0x1000>; + reg-names = "config", + "constraints", + "nodes"; + }; + tlmm: pinctrl@f100000 { compatible = "qcom,glymur-tlmm"; reg = <0x0 0x0f100000 0x0 0xf00000>; -- 2.43.0 ^ permalink raw reply related [flat|nested] 11+ messages in thread
end of thread, other threads:[~2026-07-03 6:25 UTC | newest] Thread overview: 11+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-07-02 17:22 [PATCH v3 0/4] Add Qualcomm SPEL powercap driver Manaf Meethalavalappu Pallikunhi 2026-07-02 17:22 ` [PATCH v3 1/4] dt-bindings: power: Add common power limit controller schema Manaf Meethalavalappu Pallikunhi 2026-07-02 17:44 ` sashiko-bot 2026-07-03 6:20 ` Krzysztof Kozlowski 2026-07-02 17:22 ` [PATCH v3 2/4] dt-bindings: power: limits: Describe Qualcomm SPEL hardware Manaf Meethalavalappu Pallikunhi 2026-07-02 17:30 ` sashiko-bot 2026-07-03 6:21 ` Krzysztof Kozlowski 2026-07-02 17:22 ` [PATCH v3 3/4] powercap: qcom: Add SPEL powercap driver Manaf Meethalavalappu Pallikunhi 2026-07-02 17:36 ` sashiko-bot 2026-07-03 6:24 ` Krzysztof Kozlowski 2026-07-02 17:22 ` [PATCH v3 4/4] arm64: dts: qcom: glymur: Enable " Manaf Meethalavalappu Pallikunhi
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox