* [PATCH v2 02/16] KVM: arm64: Don't skip per-vcpu NV initialisation
From: Marc Zyngier @ 2026-04-01 10:35 UTC (permalink / raw)
To: kvmarm, kvm, linux-arm-kernel
Cc: Joey Gouly, Suzuki K Poulose, Oliver Upton, Zenghui Yu,
Sascha Bischoff, Mark Brown
In-Reply-To: <20260401103611.357092-1-maz@kernel.org>
Some GICv5-related rework have resulted in the NV sanitisation of
registers being skipped for secondary vcpus, which is a pretty bad
idea.
Hoist the NV init early so that it is always executed.
Reviewed-by: Sascha Bischoff <sascha.bischoff@arm.com>
Fixes: cbd8c958be54a ("KVM: arm64: Return early from kvm_finalize_sys_regs() if guest has run")
Link: https://sashiko.dev/#/patchset/20260319154937.3619520-1-sascha.bischoff%40arm.com
Signed-off-by: Marc Zyngier <maz@kernel.org>
---
arch/arm64/kvm/sys_regs.c | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/arch/arm64/kvm/sys_regs.c b/arch/arm64/kvm/sys_regs.c
index e1001544d4f40..18e2d2fccedb8 100644
--- a/arch/arm64/kvm/sys_regs.c
+++ b/arch/arm64/kvm/sys_regs.c
@@ -5772,6 +5772,12 @@ int kvm_finalize_sys_regs(struct kvm_vcpu *vcpu)
guard(mutex)(&kvm->arch.config_lock);
+ if (vcpu_has_nv(vcpu)) {
+ int ret = kvm_init_nv_sysregs(vcpu);
+ if (ret)
+ return ret;
+ }
+
if (kvm_vm_has_ran_once(kvm))
return 0;
@@ -5820,12 +5826,6 @@ int kvm_finalize_sys_regs(struct kvm_vcpu *vcpu)
kvm_vgic_finalize_idregs(kvm);
}
- if (vcpu_has_nv(vcpu)) {
- int ret = kvm_init_nv_sysregs(vcpu);
- if (ret)
- return ret;
- }
-
return 0;
}
--
2.47.3
^ permalink raw reply related
* [PATCH v2 06/16] KVM: arm64: vgic-v5: Hold config_lock while finalizing GICv5 PPIs
From: Marc Zyngier @ 2026-04-01 10:36 UTC (permalink / raw)
To: kvmarm, kvm, linux-arm-kernel
Cc: Joey Gouly, Suzuki K Poulose, Oliver Upton, Zenghui Yu,
Sascha Bischoff, Mark Brown
In-Reply-To: <20260401103611.357092-1-maz@kernel.org>
Finalizing the PPI state is done without holding any lock, which
means that two vcpus can race against each other and have one zeroing
the state while another one is setting it, or even maybe using it.
Fixing this is done by:
- holding the config lock while performing the initialisation
- checking if SW_PPI has already been advertised, meaning that
we have already completed the initialisation once
Reviewed-by: Sascha Bischoff <sascha.bischoff@arm.com>
Fixes: 8f1fbe2fd2792 ("KVM: arm64: gic-v5: Finalize GICv5 PPIs and generate mask")
Link: https://sashiko.dev/#/patchset/20260319154937.3619520-1-sascha.bischoff%40arm.com
Signed-off-by: Marc Zyngier <maz@kernel.org>
---
arch/arm64/kvm/vgic/vgic-v5.c | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
index 2b6cd5c3f9c2f..119d7d01d0e77 100644
--- a/arch/arm64/kvm/vgic/vgic-v5.c
+++ b/arch/arm64/kvm/vgic/vgic-v5.c
@@ -172,6 +172,16 @@ int vgic_v5_finalize_ppi_state(struct kvm *kvm)
if (!vgic_is_v5(kvm))
return 0;
+ guard(mutex)(&kvm->arch.config_lock);
+
+ /*
+ * If SW_PPI has been advertised, then we know we already
+ * initialised the whole thing, and we can return early. Yes,
+ * this is pretty hackish as far as state tracking goes...
+ */
+ if (test_bit(GICV5_ARCH_PPI_SW_PPI, kvm->arch.vgic.gicv5_vm.vgic_ppi_mask))
+ return 0;
+
/* The PPI state for all VCPUs should be the same. Pick the first. */
vcpu0 = kvm_get_vcpu(kvm, 0);
--
2.47.3
^ permalink raw reply related
* [PATCH v2 00/16] KVM: arm64: First batch of vgic-v5 related fixes
From: Marc Zyngier @ 2026-04-01 10:35 UTC (permalink / raw)
To: kvmarm, kvm, linux-arm-kernel
Cc: Joey Gouly, Suzuki K Poulose, Oliver Upton, Zenghui Yu,
Sascha Bischoff, Mark Brown
This is the second revision of this series attempting to fix a number
of issues reported on the vgic-v5 PPI series.
* From v1 [1]:
- Partially reverted the effective priority mask fix, as Sascha
pointed out the crucial difference in the way GICv5 manages the
PCR compared to GICv[234]'s PMR.
- Added a fix for dealing with the pending HW-backed PPIs.
- Added a workaround for the set_id_regs selftest, already posted
separately, but included here for completeness (and ease of
merging).
Patches on top of my kvm-arm64/vgic-v5-ppi branch.
[1] https://lore.kernel.org/r/20260326153530.3981879-1-maz@kernel.org
Marc Zyngier (16):
KVM: arm64: vgic: Don't reset cpuif/redist addresses at finalize time
KVM: arm64: Don't skip per-vcpu NV initialisation
arm64: Fix field references for ICH_PPI_DVIR[01]_EL2
KVM: arm64: Fix writeable mask for ID_AA64PFR2_EL1
KVM: arm64: Account for RESx bits in __compute_fgt()
KVM: arm64: vgic-v5: Hold config_lock while finalizing GICv5 PPIs
KVM: arm64: vgic-v5: Transfer edge pending state to ICH_PPI_PENDRx_EL2
KVM: arm64: vgic-v5: Cast vgic_apr to u32 to avoid undefined
behaviours
KVM: arm64: vgic-v5: Make the effective priority mask a strict limit
KVM: arm64: vgic-v5: Correctly set dist->ready once initialised
KVM: arm64: Kill arch_timer_context::direct field
KVM: arm64: Remove evaluation of timer state in
kvm_cpu_has_pending_timer()
KVM: arm64: Move GICv5 timer PPI validation into
timer_irqs_are_valid()
KVM: arm64: Correctly plumb ID_AA64PFR2_EL1 into pkvm idreg handling
KVM: arm64: Don't advertises GICv3 in ID_PFR1_EL1 if AArch32 isn't
supported
KVM: arm64: set_id_regs: Allow GICv3 support to be set at runtime
arch/arm64/kvm/arch_timer.c | 32 +++++-------
arch/arm64/kvm/config.c | 4 +-
arch/arm64/kvm/hyp/nvhe/sys_regs.c | 2 +-
arch/arm64/kvm/sys_regs.c | 20 +++----
arch/arm64/kvm/vgic/vgic-init.c | 32 ++++++++----
arch/arm64/kvm/vgic/vgic-v5.c | 24 +++++++--
arch/arm64/tools/sysreg | 4 +-
include/kvm/arm_arch_timer.h | 3 --
.../testing/selftests/kvm/arm64/set_id_regs.c | 52 ++++++++++++++++---
9 files changed, 115 insertions(+), 58 deletions(-)
base-commit: ce29261ec6482de54320c03398eb30e9615aee40
--
2.47.3
^ permalink raw reply
* [PATCH v2 03/16] arm64: Fix field references for ICH_PPI_DVIR[01]_EL2
From: Marc Zyngier @ 2026-04-01 10:35 UTC (permalink / raw)
To: kvmarm, kvm, linux-arm-kernel
Cc: Joey Gouly, Suzuki K Poulose, Oliver Upton, Zenghui Yu,
Sascha Bischoff, Mark Brown
In-Reply-To: <20260401103611.357092-1-maz@kernel.org>
The ICH_PPI_DVIR[01]_EL2 registers should refer to the ICH_PPI_DVIRx_EL2
fields, instead of ICH_PPI_DVIx_EL2.
Reviewed-by: Sascha Bischoff <sascha.bischoff@arm.com>
Fixes: 2808a8337078f ("arm64/sysreg: Add remaining GICv5 ICC_ & ICH_ sysregs for KVM support")
Link: https://sashiko.dev/#/patchset/20260319154937.3619520-1-sascha.bischoff%40arm.com
Signed-off-by: Marc Zyngier <maz@kernel.org>
---
arch/arm64/tools/sysreg | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/arch/arm64/tools/sysreg b/arch/arm64/tools/sysreg
index 51dcca5b2fa6e..3b57cb692c5be 100644
--- a/arch/arm64/tools/sysreg
+++ b/arch/arm64/tools/sysreg
@@ -4888,11 +4888,11 @@ Field 0 DVI0
EndSysregFields
Sysreg ICH_PPI_DVIR0_EL2 3 4 12 10 0
-Fields ICH_PPI_DVIx_EL2
+Fields ICH_PPI_DVIRx_EL2
EndSysreg
Sysreg ICH_PPI_DVIR1_EL2 3 4 12 10 1
-Fields ICH_PPI_DVIx_EL2
+Fields ICH_PPI_DVIRx_EL2
EndSysreg
SysregFields ICH_PPI_ENABLERx_EL2
--
2.47.3
^ permalink raw reply related
* RE: Re: Re: [PATCH 1/2] dt-bindings: gpu: mali-valhall-csf: Document i.MX952 support
From: Guangliu Ding @ 2026-04-01 10:31 UTC (permalink / raw)
To: Liviu Dudau
Cc: Daniel Baluta (OSS), Daniel Almeida, Alice Ryhl, Boris Brezillon,
Steven Price, David Airlie, Simona Vetter, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Frank Li, Sascha Hauer,
Pengutronix Kernel Team, Fabio Estevam,
dri-devel@lists.freedesktop.org, devicetree@vger.kernel.org,
linux-kernel@vger.kernel.org, imx@lists.linux.dev,
linux-arm-kernel@lists.infradead.org, Jiyu Yang
In-Reply-To: <aczv0R0aQRmHKdvt@e142607>
Hi Liviu
> On Wed, Apr 01, 2026 at 09:43:12AM +0000, Guangliu Ding wrote:
> > Hi Daniel
> >
> > > On 4/1/26 11:48, Guangliu Ding wrote:
> > > > [You don't often get email from guangliu.ding@nxp.com. Learn why
> > > > this is important at https://aka.ms/LearnAboutSenderIdentification
> > > > ]
> > > >
> > > > Hi Liviu
> > > >
> > > > Thanks for your review. Please refer to my comments below:
> > > >
> > > >> On Tue, Mar 31, 2026 at 06:12:38PM +0800, Guangliu Ding wrote:
> > > >>> Add compatible string of Mali G310 GPU on i.MX952 board.
> > > >>>
> > > >>> Signed-off-by: Guangliu Ding <guangliu.ding@nxp.com>
> > > >>> Reviewed-by: Jiyu Yang <jiyu.yang@nxp.com>
> > > >>> ---
> > > >>> Documentation/devicetree/bindings/gpu/arm,mali-valhall-csf.yaml
> > > >>> | 1
> > > >>> +
> > > >>> 1 file changed, 1 insertion(+)
> > > >>>
> > > >>> diff --git
> > > >>> a/Documentation/devicetree/bindings/gpu/arm,mali-valhall-csf.yam
> > > >>> l
> > > >> b/Documentation/devicetree/bindings/gpu/arm,mali-valhall-csf.yaml
> > > >>> index 8eccd4338a2b..6a10843a26e2 100644
> > > >>> ---
> > > >>> a/Documentation/devicetree/bindings/gpu/arm,mali-valhall-csf.yam
> > > >>> l
> > > >>> +++ b/Documentation/devicetree/bindings/gpu/arm,mali-valhall-csf
> > > >>> +++ .yam
> > > >>> +++ l
> > > >>> @@ -20,6 +20,7 @@ properties:
> > > >>> - enum:
> > > >>> - mediatek,mt8196-mali
> > > >>> - nxp,imx95-mali # G310
> > > >>> + - nxp,imx952-mali # G310
> > > >> Can you explain why this is needed? Can it not be covered by the
> > > >> existing compatible?
> > > > There are functional differences in GPU module (GPUMIX) between
> > > > i.MX95 and i.MX952. So they cannot be fully covered by a single existing
> compatible.
> > > > On i.MX952, The GPU clock is controlled by hardware GPU auto
> > > > clock-gating mechanism, while the GPU clock is managed explicitly
> > > > by the
> > > driver on i.MX95.
> > > > Because of these behavioral differences, separate compatible
> > > > strings "nxp,imx95-mali" and "nxp,imx952-mali" are needed to allow
> > > > the driver to handle the two variants independently and to keep
> > > > room for future
> > > divergence.
> > >
> > >
> > > This information should be added in the commit message explaining
> > > why
> > >
> > > the change is needed.
> > >
> > >
> > > But then where is the driver code taking care of these diferences?
> > >
> >
> > Yes. Currently the driver does not require "nxp,imx952-mali" string.
> > However, when GPU ipa_counters are enabled to calculate the GPU
> > busy_time/idle_time for GPU DVFS feature, they will conflict with the
> > hardware GPU auto clock‑gating mechanism, causing GPU clock to remain
> always on.
> > In such cases, ipa_counters need to be disabled so that the GPU auto
> > clock‑gating mechanism can operate normally, using "nxp,imx952-mali"
> string.
>
> OK, I understand that you're following guidance from some other senior
> people on how to upstream patches so you've tried to create the smallest
> patchset to ensure that it gets reviewed and accepted, but in this case we need
> to see the other patches as well to decide if your approach is the right one and
> we do need a separate compatible string.
>
> If enabling GPU ipa_counters causes the clocks to get stuck active, that feels
> like a hardware bug, so figuring out how to handle that is more important than
> adding a compatible string.
>
> Either add the patch(es) that use the compatible to this series in v2, or put a
> comment in the commit message on where we can see the driver changes.
>
According to discussions with the GPU vendor, this is a hardware limitation
of Mali-G310 rather than a hardware bug, and it has been addressed in newer
Mali GPU families.
In addition, ipa_counters are not enabled in the current Panthor driver. We observed
this issue with the private Mali DDK where ipa_counters were enabled.
Therefore, keeping the compatible string is necessary to allow for future divergence.
> Best regards,
> Liviu
>
> --
> ====================
> | I would like to |
> | fix the world, |
> | but they're not |
> | giving me the |
> \ source code! /
> ---------------
> ¯\_(ツ)_/¯
^ permalink raw reply
* Re: [PATCH v2 1/2] dmaengine: xilinx_dma: Fix CPU stall in xilinx_dma_poll_timeout
From: Geert Uytterhoeven @ 2026-04-01 10:28 UTC (permalink / raw)
To: Alex Bereza
Cc: Vinod Koul, Frank Li, Michal Simek, Ulf Hansson, Arnd Bergmann,
Tony Lindgren, Kedareswara rao Appana, dmaengine,
linux-arm-kernel, linux-kernel
In-Reply-To: <20260401-fix-atomic-poll-timeout-regression-v2-1-68a265e3770f@bereza.email>
Hi Alex,
Thanks for your patch!
On Wed, 1 Apr 2026 at 11:58, Alex Bereza <alex@bereza.email> wrote:
> Currently when calling xilinx_dma_poll_timeout with delay_us=0 and a
> condition that is never fulfilled, the CPU busy-waits for prolonged time
> and the timeout triggers only with a massive delay causing a CPU stall.
>
> This happens due to a huge underestimation of wall clock time in
> poll_timeout_us_atomic. Commit 7349a69cf312 ("iopoll: Do not use
> timekeeping in read_poll_timeout_atomic()") changed the behavior to no
> longer use ktime_get at the expense of underestimation of wall clock
> time which appears to be very large for delay_us=0. Instead of timing
> out after approximately XILINX_DMA_LOOP_COUNT microseconds, the timeout
> takes XILINX_DMA_LOOP_COUNT * 1000 * (time that the overhead of the for
> loop in poll_timeout_us_atomic takes) which is in the range of several
> minutes for XILINX_DMA_LOOP_COUNT=1000000. Fix this by using a non-zero
> value for delay_us. Use delay_us=10 to keep the delay in the hot path of
> starting DMA transfers minimal but still avoid CPU stalls in case of
> unexpected hardware failures.
>
> One-off measurement with delay_us=0 causes the cpu to busy wait around 7
> minutes in the timeout case. After applying this patch with delay_us=10
> the measured timeout was 1053428 microseconds which is roughly
> equivalent to the expected 1000000 microseconds specified in
> XILINX_DMA_LOOP_COUNT.
>
> Add a constant XILINX_DMA_POLL_DELAY_US for delay_us value.
>
> Fixes: 9495f2648287 ("dmaengine: xilinx_vdma: Use readl_poll_timeout instead of do while loop's")
> Fixes: 7349a69cf312 ("iopoll: Do not use timekeeping in read_poll_timeout_atomic()")
>
Please no blank line between tags.
> Signed-off-by: Alex Bereza <alex@bereza.email>
Gr{oetje,eeting}s,
Geert
--
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- geert@linux-m68k.org
In personal conversations with technical people, I call myself a hacker. But
when I'm talking to journalists I just say "programmer" or something like that.
-- Linus Torvalds
^ permalink raw reply
* Re: [PATCH] iommu/rockchip: fix page table allocation flags for v2 IOMMU
From: Simon Xue @ 2026-04-01 10:22 UTC (permalink / raw)
To: Jonas Karlman, Midgy BALON
Cc: iommu, joro, will, robin.murphy, heiko, linux-arm-kernel,
linux-rockchip, linux-kernel, stable
In-Reply-To: <e622cc9e-8fb0-454a-b88e-dc13cf2ff507@kwiboo.se>
Hi Jonas,
在 2026/4/1 16:41, Jonas Karlman 写道:
> Hi Simon,
>
> On 4/1/2026 9:48 AM, Simon wrote:
>> Hi Midgy,
>>
>> 在 2026/3/31 15:50, Midgy BALON 写道:
>>> commit 2a7e6400f72b ("iommu: rockchip: Allocate tables from all
>>> available memory for IOMMU v2") removed GFP_DMA32 from
>>> iommu_data_ops_v2, reasoning that RK356x and RK3588 IOMMU v2 hardware
>>> supports up to 40-bit physical addresses for page tables. However, the
>>> RK3568 IOMMU page-table walker uses a 32-bit AXI bus: it cannot access
>>> physical addresses above 4 GB regardless of the address encoding range.
>>>
>>> On boards with more than 4 GB of RAM (e.g. 8 GB LPDDR4X), removing
>>> GFP_DMA32 causes two distinct failure modes:
>>>
>>> 1. Direct allocation above 4 GB: iommu_alloc_pages_sz() may return
>>> memory above 0x100000000. The hardware page-table walker issues a
>>> bus error trying to dereference those addresses, causing an IOMMU
>>> fault on the first DMA transaction.
>> Which IP block is hitting this? We'd like to take a look on our end.
> I have seen reports that the NPU MMU on RK3568/RK3566 is having some
> issue using DTE/PTE with >32-bit addresses, maybe it uses a different
> MMU hw revision or has some hw errata?
>
> From my own testing at least the VOP2 MMU on RK3568 (and RK3588) was
> able to handle 40-bit addressable DTE/PTE, hence the original commit
> 2a7e6400f72b ("iommu: rockchip: Allocate tables from all available
> memory for IOMMU v2").
>
> As also mentioned in my reply at [1], maybe the NPU MMU has some hw
> limitation or errata and may need to use a different compatible.
Yes, We are checking internally whether different IOMMU versions
integrated.
I will share what we find once we have results.
> [1] https://lore.kernel.org/r/3cd63b3d-1c5e-4a11-856e-c4aeb5d97d55@kwiboo.se/
>
> Regards,
> Jonas
>
>>> 2. SWIOTLB bounce-buffer poisoning: without GFP_DMA32, page tables land
>>> above the SWIOTLB window. dma_map_single() with DMA_BIT_MASK(32)
>>> then bounces them into a buffer below 4 GB. rk_dte_get_page_table()
>>> returns phys_to_virt() of the bounce buffer address; PTEs are written
>>> there; the next dma_sync_single_for_device(DMA_TO_DEVICE) copies the
>>> original (zero) data back over the bounce buffer, silently erasing the
>>> freshly written PTEs. The IOMMU faults because every PTE reads as zero.
>> This probably need a separate patch. One way to fix it would be to track the
>> original L2 page table base addresses in struct rk_iommu_domain,
>> then have rk_dte_get_page_table() return the tracked address instead of
>> deriving it from the DTE.
>>
>>> Restore GFP_DMA32 (and DMA_BIT_MASK(32)) for iommu_data_ops_v2, which
>>> currently only serves "rockchip,rk3568-iommu" in mainline.
>>>
>>> Tested on Radxa ROCK 3B (RK3568, 8 GB LPDDR4X):
>>> - MobileNetV1 via RKNN: 5.8 ms/inference (IOMMU mode)
>>> - YOLOv5s 640x640 via RKNN: ~57 ms/inference (IOMMU mode)
>>> - No IOMMU faults, correct inference results
>>>
>>> Fixes: 2a7e6400f72b ("iommu: rockchip: Allocate tables from all available memory for IOMMU v2")
>>> Cc: stable@vger.kernel.org
>>> Cc: Jonas Karlman <jonas@kwiboo.se>
>>> Signed-off-by: Midgy BALON <midgy971@gmail.com>
>>> ---
>>> drivers/iommu/rockchip-iommu.c | 4 ++--
>>> 1 file changed, 2 insertions(+), 2 deletions(-)
>>>
>>> diff --git a/drivers/iommu/rockchip-iommu.c b/drivers/iommu/rockchip-iommu.c
>>> index 85f3667e797..8b45db29471 100644
>>> --- a/drivers/iommu/rockchip-iommu.c
>>> +++ b/drivers/iommu/rockchip-iommu.c
>>> @@ -1358,8 +1358,8 @@ static struct rk_iommu_ops iommu_data_ops_v2 = {
>>> .pt_address = &rk_dte_pt_address_v2,
>>> .mk_dtentries = &rk_mk_dte_v2,
>>> .mk_ptentries = &rk_mk_pte_v2,
>>> - .dma_bit_mask = DMA_BIT_MASK(40),
>>> - .gfp_flags = 0,
>>> + .dma_bit_mask = DMA_BIT_MASK(32),
>>> + .gfp_flags = GFP_DMA32,
>>> };
>>>
>>> static const struct of_device_id rk_iommu_dt_ids[] = {
>
^ permalink raw reply
* Re: Re: [PATCH 1/2] dt-bindings: gpu: mali-valhall-csf: Document i.MX952 support
From: Liviu Dudau @ 2026-04-01 10:13 UTC (permalink / raw)
To: Guangliu Ding
Cc: Daniel Baluta (OSS), Daniel Almeida, Alice Ryhl, Boris Brezillon,
Steven Price, David Airlie, Simona Vetter, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Frank Li, Sascha Hauer,
Pengutronix Kernel Team, Fabio Estevam,
dri-devel@lists.freedesktop.org, devicetree@vger.kernel.org,
linux-kernel@vger.kernel.org, imx@lists.linux.dev,
linux-arm-kernel@lists.infradead.org, Jiyu Yang
In-Reply-To: <AM0PR04MB4707B3E37DFE7AEE587DBA2AF350A@AM0PR04MB4707.eurprd04.prod.outlook.com>
On Wed, Apr 01, 2026 at 09:43:12AM +0000, Guangliu Ding wrote:
> Hi Daniel
>
> > On 4/1/26 11:48, Guangliu Ding wrote:
> > > [You don't often get email from guangliu.ding@nxp.com. Learn why this
> > > is important at https://aka.ms/LearnAboutSenderIdentification ]
> > >
> > > Hi Liviu
> > >
> > > Thanks for your review. Please refer to my comments below:
> > >
> > >> On Tue, Mar 31, 2026 at 06:12:38PM +0800, Guangliu Ding wrote:
> > >>> Add compatible string of Mali G310 GPU on i.MX952 board.
> > >>>
> > >>> Signed-off-by: Guangliu Ding <guangliu.ding@nxp.com>
> > >>> Reviewed-by: Jiyu Yang <jiyu.yang@nxp.com>
> > >>> ---
> > >>> Documentation/devicetree/bindings/gpu/arm,mali-valhall-csf.yaml | 1
> > >>> +
> > >>> 1 file changed, 1 insertion(+)
> > >>>
> > >>> diff --git
> > >>> a/Documentation/devicetree/bindings/gpu/arm,mali-valhall-csf.yaml
> > >> b/Documentation/devicetree/bindings/gpu/arm,mali-valhall-csf.yaml
> > >>> index 8eccd4338a2b..6a10843a26e2 100644
> > >>> ---
> > >>> a/Documentation/devicetree/bindings/gpu/arm,mali-valhall-csf.yaml
> > >>> +++ b/Documentation/devicetree/bindings/gpu/arm,mali-valhall-csf.yam
> > >>> +++ l
> > >>> @@ -20,6 +20,7 @@ properties:
> > >>> - enum:
> > >>> - mediatek,mt8196-mali
> > >>> - nxp,imx95-mali # G310
> > >>> + - nxp,imx952-mali # G310
> > >> Can you explain why this is needed? Can it not be covered by the
> > >> existing compatible?
> > > There are functional differences in GPU module (GPUMIX) between i.MX95
> > > and i.MX952. So they cannot be fully covered by a single existing compatible.
> > > On i.MX952, The GPU clock is controlled by hardware GPU auto
> > > clock-gating mechanism, while the GPU clock is managed explicitly by the
> > driver on i.MX95.
> > > Because of these behavioral differences, separate compatible strings
> > > "nxp,imx95-mali" and "nxp,imx952-mali" are needed to allow the driver
> > > to handle the two variants independently and to keep room for future
> > divergence.
> >
> >
> > This information should be added in the commit message explaining why
> >
> > the change is needed.
> >
> >
> > But then where is the driver code taking care of these diferences?
> >
>
> Yes. Currently the driver does not require "nxp,imx952-mali" string.
> However, when GPU ipa_counters are enabled to calculate the GPU busy_time/idle_time for
> GPU DVFS feature, they will conflict with the hardware GPU auto clock‑gating mechanism,
> causing GPU clock to remain always on.
> In such cases, ipa_counters need to be disabled so that the GPU auto clock‑gating
> mechanism can operate normally, using "nxp,imx952-mali" string.
OK, I understand that you're following guidance from some other senior people on how
to upstream patches so you've tried to create the smallest patchset to ensure that
it gets reviewed and accepted, but in this case we need to see the other patches as well
to decide if your approach is the right one and we do need a separate compatible string.
If enabling GPU ipa_counters causes the clocks to get stuck active, that feels like a
hardware bug, so figuring out how to handle that is more important than adding a compatible
string.
Either add the patch(es) that use the compatible to this series in v2, or put a comment in
the commit message on where we can see the driver changes.
Best regards,
Liviu
--
====================
| I would like to |
| fix the world, |
| but they're not |
| giving me the |
\ source code! /
---------------
¯\_(ツ)_/¯
^ permalink raw reply
* [PATCH v2 2/2] arm64: dts: imx952: Describe Mali G310 GPU
From: Guangliu Ding @ 2026-04-01 10:19 UTC (permalink / raw)
To: Daniel Almeida, Alice Ryhl, Boris Brezillon, Steven Price,
Liviu Dudau, David Airlie, Simona Vetter, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Frank Li, Sascha Hauer,
Pengutronix Kernel Team, Fabio Estevam
Cc: dri-devel, devicetree, linux-kernel, imx, linux-arm-kernel,
Guangliu Ding
In-Reply-To: <20260401-master-v2-0-20d3fbcd19d6@nxp.com>
Support Mali G310 GPU on i.MX952 board. Describe this GPU in the DT.
Include dummy GPU voltage regulator and OPP tables.
Signed-off-by: Guangliu Ding <guangliu.ding@nxp.com>
---
arch/arm64/boot/dts/freescale/imx952.dtsi | 36 +++++++++++++++++++++++++++++++
1 file changed, 36 insertions(+)
diff --git a/arch/arm64/boot/dts/freescale/imx952.dtsi b/arch/arm64/boot/dts/freescale/imx952.dtsi
index 91fe4916ac04..0bd3e7fc0674 100644
--- a/arch/arm64/boot/dts/freescale/imx952.dtsi
+++ b/arch/arm64/boot/dts/freescale/imx952.dtsi
@@ -318,6 +318,28 @@ usbphynop2: usbphynop2 {
clock-names = "main_clk";
};
+ gpu_opp_table: opp-table {
+ compatible = "operating-points-v2";
+
+ opp-500000000 {
+ opp-hz = /bits/ 64 <500000000>;
+ opp-hz-real = /bits/ 64 <500000000>;
+ opp-microvolt = <920000>;
+ };
+
+ opp-800000000 {
+ opp-hz = /bits/ 64 <800000000>;
+ opp-hz-real = /bits/ 64 <800000000>;
+ opp-microvolt = <920000>;
+ };
+
+ opp-1000000000 {
+ opp-hz = /bits/ 64 <1000000000>;
+ opp-hz-real = /bits/ 64 <1000000000>;
+ opp-microvolt = <920000>;
+ };
+ };
+
soc {
compatible = "simple-bus";
#address-cells = <2>;
@@ -1262,5 +1284,19 @@ usbmisc2: usbmisc@4c200200 {
reg = <0x0 0x4c200200 0x0 0x200>,
<0x0 0x4c010014 0x0 0x4>;
};
+
+ gpu: gpu@4d900000 {
+ compatible = "nxp,imx952-mali", "arm,mali-valhall-csf";
+ reg = <0 0x4d900000 0 0x480000>;
+ interrupts = <GIC_SPI 288 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 289 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 290 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-names = "gpu", "job", "mmu";
+ clocks = <&scmi_clk IMX952_CLK_GPU>;
+ clock-names = "core";
+ power-domains = <&scmi_devpd IMX952_PD_GPU>;
+ operating-points-v2 = <&gpu_opp_table>;
+ dynamic-power-coefficient = <1013>;
+ };
};
};
--
2.34.1
^ permalink raw reply related
* [PATCH v2 1/2] dt-bindings: gpu: mali-valhall-csf: Document i.MX952 support
From: Guangliu Ding @ 2026-04-01 10:19 UTC (permalink / raw)
To: Daniel Almeida, Alice Ryhl, Boris Brezillon, Steven Price,
Liviu Dudau, David Airlie, Simona Vetter, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Frank Li, Sascha Hauer,
Pengutronix Kernel Team, Fabio Estevam
Cc: dri-devel, devicetree, linux-kernel, imx, linux-arm-kernel,
Guangliu Ding
In-Reply-To: <20260401-master-v2-0-20d3fbcd19d6@nxp.com>
The GPU instance used on NXP i.MX952 is the Mali‑G310,
document support for this variant.
A hardware GPU auto clock‑gating mechanism has been introduced,
enabling GPUMIX to automatically manage the GPU clock. This improves
overall response time.
Signed-off-by: Guangliu Ding <guangliu.ding@nxp.com>
---
Documentation/devicetree/bindings/gpu/arm,mali-valhall-csf.yaml | 1 +
1 file changed, 1 insertion(+)
diff --git a/Documentation/devicetree/bindings/gpu/arm,mali-valhall-csf.yaml b/Documentation/devicetree/bindings/gpu/arm,mali-valhall-csf.yaml
index 8eccd4338a2b..6a10843a26e2 100644
--- a/Documentation/devicetree/bindings/gpu/arm,mali-valhall-csf.yaml
+++ b/Documentation/devicetree/bindings/gpu/arm,mali-valhall-csf.yaml
@@ -20,6 +20,7 @@ properties:
- enum:
- mediatek,mt8196-mali
- nxp,imx95-mali # G310
+ - nxp,imx952-mali # G310
- rockchip,rk3588-mali
- const: arm,mali-valhall-csf # Mali Valhall GPU model/revision is fully discoverable
--
2.34.1
^ permalink raw reply related
* [PATCH v2 0/2] Enable Mali G310 GPU support on i.MX952 board
From: Guangliu Ding @ 2026-04-01 10:19 UTC (permalink / raw)
To: Daniel Almeida, Alice Ryhl, Boris Brezillon, Steven Price,
Liviu Dudau, David Airlie, Simona Vetter, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Frank Li, Sascha Hauer,
Pengutronix Kernel Team, Fabio Estevam
Cc: dri-devel, devicetree, linux-kernel, imx, linux-arm-kernel,
Guangliu Ding
This series enable Mali G310 GPU support on i.MX952 boards, the same GPU
IP as the instance on i.MX95 boards.
Signed-off-by: Guangliu Ding <guangliu.ding@nxp.com>
---
Changes in v2:
- Improve patch description, adding more GPU information.
- Remove Reviewed-by tag.
- Link to v1: https://patch.msgid.link/20260331-master-v1-0-65c8e318d462@nxp.com
---
Guangliu Ding (2):
dt-bindings: gpu: mali-valhall-csf: Document i.MX952 support
arm64: dts: imx952: Describe Mali G310 GPU
.../bindings/gpu/arm,mali-valhall-csf.yaml | 1 +
arch/arm64/boot/dts/freescale/imx952.dtsi | 36 ++++++++++++++++++++++
2 files changed, 37 insertions(+)
---
base-commit: 0138af2472dfdef0d56fc4697416eaa0ff2589bd
change-id: 20260331-master-7ec7ff0fe1b2
Best regards,
--
Guangliu Ding <guangliu.ding@nxp.com>
^ permalink raw reply
* Re: [PATCH v8] arm64: Use static call trampolines when kCFI is enabled
From: Will Deacon @ 2026-04-01 10:03 UTC (permalink / raw)
To: linux-arm-kernel, Ard Biesheuvel
Cc: catalin.marinas, kernel-team, Will Deacon, linux-hardening,
mark.rutland, Ard Biesheuvel, Carlos Llamas, Sami Tolvanen,
Sean Christopherson, Kees Cook, Peter Zijlstra, Will McVicker
In-Reply-To: <20260331110422.301901-2-ardb+git@google.com>
On Tue, 31 Mar 2026 13:04:23 +0200, Ard Biesheuvel wrote:
> Implement arm64 support for the 'unoptimized' static call variety, which
> routes all calls through a trampoline that performs a tail call to the
> chosen function, and wire it up for use when kCFI is enabled. This works
> around an issue with kCFI and generic static calls, where the prototypes
> of default handlers such as __static_call_nop() and __static_call_ret0()
> don't match the expected prototype of the call site, resulting in kCFI
> false positives [0].
>
> [...]
Applied to arm64 (for-next/fixes), thanks!
[1/1] arm64: Use static call trampolines when kCFI is enabled
https://git.kernel.org/arm64/c/e70c2335f889
Cheers,
--
Will
https://fixes.arm64.dev
https://next.arm64.dev
https://will.arm64.dev
^ permalink raw reply
* [PATCH v2 1/2] dmaengine: xilinx_dma: Fix CPU stall in xilinx_dma_poll_timeout
From: Alex Bereza @ 2026-04-01 9:57 UTC (permalink / raw)
To: Vinod Koul, Frank Li, Michal Simek, Geert Uytterhoeven,
Ulf Hansson, Arnd Bergmann, Tony Lindgren, Kedareswara rao Appana
Cc: dmaengine, linux-arm-kernel, linux-kernel, Alex Bereza
In-Reply-To: <20260401-fix-atomic-poll-timeout-regression-v2-0-68a265e3770f@bereza.email>
Currently when calling xilinx_dma_poll_timeout with delay_us=0 and a
condition that is never fulfilled, the CPU busy-waits for prolonged time
and the timeout triggers only with a massive delay causing a CPU stall.
This happens due to a huge underestimation of wall clock time in
poll_timeout_us_atomic. Commit 7349a69cf312 ("iopoll: Do not use
timekeeping in read_poll_timeout_atomic()") changed the behavior to no
longer use ktime_get at the expense of underestimation of wall clock
time which appears to be very large for delay_us=0. Instead of timing
out after approximately XILINX_DMA_LOOP_COUNT microseconds, the timeout
takes XILINX_DMA_LOOP_COUNT * 1000 * (time that the overhead of the for
loop in poll_timeout_us_atomic takes) which is in the range of several
minutes for XILINX_DMA_LOOP_COUNT=1000000. Fix this by using a non-zero
value for delay_us. Use delay_us=10 to keep the delay in the hot path of
starting DMA transfers minimal but still avoid CPU stalls in case of
unexpected hardware failures.
One-off measurement with delay_us=0 causes the cpu to busy wait around 7
minutes in the timeout case. After applying this patch with delay_us=10
the measured timeout was 1053428 microseconds which is roughly
equivalent to the expected 1000000 microseconds specified in
XILINX_DMA_LOOP_COUNT.
Add a constant XILINX_DMA_POLL_DELAY_US for delay_us value.
Fixes: 9495f2648287 ("dmaengine: xilinx_vdma: Use readl_poll_timeout instead of do while loop's")
Fixes: 7349a69cf312 ("iopoll: Do not use timekeeping in read_poll_timeout_atomic()")
Signed-off-by: Alex Bereza <alex@bereza.email>
---
drivers/dma/xilinx/xilinx_dma.c | 14 ++++++++++----
1 file changed, 10 insertions(+), 4 deletions(-)
diff --git a/drivers/dma/xilinx/xilinx_dma.c b/drivers/dma/xilinx/xilinx_dma.c
index 02a05f215614..345a738bab2c 100644
--- a/drivers/dma/xilinx/xilinx_dma.c
+++ b/drivers/dma/xilinx/xilinx_dma.c
@@ -167,6 +167,8 @@
/* Delay loop counter to prevent hardware failure */
#define XILINX_DMA_LOOP_COUNT 1000000
+/* Delay between polls (avoid a delay of 0 to prevent CPU stalls) */
+#define XILINX_DMA_POLL_DELAY_US 10
/* AXI DMA Specific Registers/Offsets */
#define XILINX_DMA_REG_SRCDSTADDR 0x18
@@ -1332,7 +1334,8 @@ static int xilinx_dma_stop_transfer(struct xilinx_dma_chan *chan)
/* Wait for the hardware to halt */
return xilinx_dma_poll_timeout(chan, XILINX_DMA_REG_DMASR, val,
- val & XILINX_DMA_DMASR_HALTED, 0,
+ val & XILINX_DMA_DMASR_HALTED,
+ XILINX_DMA_POLL_DELAY_US,
XILINX_DMA_LOOP_COUNT);
}
@@ -1347,7 +1350,8 @@ static int xilinx_cdma_stop_transfer(struct xilinx_dma_chan *chan)
u32 val;
return xilinx_dma_poll_timeout(chan, XILINX_DMA_REG_DMASR, val,
- val & XILINX_DMA_DMASR_IDLE, 0,
+ val & XILINX_DMA_DMASR_IDLE,
+ XILINX_DMA_POLL_DELAY_US,
XILINX_DMA_LOOP_COUNT);
}
@@ -1364,7 +1368,8 @@ static void xilinx_dma_start(struct xilinx_dma_chan *chan)
/* Wait for the hardware to start */
err = xilinx_dma_poll_timeout(chan, XILINX_DMA_REG_DMASR, val,
- !(val & XILINX_DMA_DMASR_HALTED), 0,
+ !(val & XILINX_DMA_DMASR_HALTED),
+ XILINX_DMA_POLL_DELAY_US,
XILINX_DMA_LOOP_COUNT);
if (err) {
@@ -1780,7 +1785,8 @@ static int xilinx_dma_reset(struct xilinx_dma_chan *chan)
/* Wait for the hardware to finish reset */
err = xilinx_dma_poll_timeout(chan, XILINX_DMA_REG_DMACR, tmp,
- !(tmp & XILINX_DMA_DMACR_RESET), 0,
+ !(tmp & XILINX_DMA_DMACR_RESET),
+ XILINX_DMA_POLL_DELAY_US,
XILINX_DMA_LOOP_COUNT);
if (err) {
--
2.53.0
^ permalink raw reply related
* [PATCH v2 2/2] dmaengine: xilinx_dma: Rename XILINX_DMA_LOOP_COUNT
From: Alex Bereza @ 2026-04-01 9:57 UTC (permalink / raw)
To: Vinod Koul, Frank Li, Michal Simek, Geert Uytterhoeven,
Ulf Hansson, Arnd Bergmann, Tony Lindgren, Kedareswara rao Appana
Cc: dmaengine, linux-arm-kernel, linux-kernel, Alex Bereza
In-Reply-To: <20260401-fix-atomic-poll-timeout-regression-v2-0-68a265e3770f@bereza.email>
Rename XILINX_DMA_LOOP_COUNT to XILINX_DMA_POLL_TIMEOUT_US because the
former is incorrect. It is a timeout value for polling various register
bits in microseconds. It is not a loop count.
Signed-off-by: Alex Bereza <alex@bereza.email>
---
drivers/dma/xilinx/xilinx_dma.c | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/drivers/dma/xilinx/xilinx_dma.c b/drivers/dma/xilinx/xilinx_dma.c
index 345a738bab2c..253c27fd1a0e 100644
--- a/drivers/dma/xilinx/xilinx_dma.c
+++ b/drivers/dma/xilinx/xilinx_dma.c
@@ -165,8 +165,8 @@
#define XILINX_DMA_FLUSH_MM2S 2
#define XILINX_DMA_FLUSH_BOTH 1
-/* Delay loop counter to prevent hardware failure */
-#define XILINX_DMA_LOOP_COUNT 1000000
+/* Timeout for polling various registers */
+#define XILINX_DMA_POLL_TIMEOUT_US 1000000
/* Delay between polls (avoid a delay of 0 to prevent CPU stalls) */
#define XILINX_DMA_POLL_DELAY_US 10
@@ -1336,7 +1336,7 @@ static int xilinx_dma_stop_transfer(struct xilinx_dma_chan *chan)
return xilinx_dma_poll_timeout(chan, XILINX_DMA_REG_DMASR, val,
val & XILINX_DMA_DMASR_HALTED,
XILINX_DMA_POLL_DELAY_US,
- XILINX_DMA_LOOP_COUNT);
+ XILINX_DMA_POLL_TIMEOUT_US);
}
/**
@@ -1352,7 +1352,7 @@ static int xilinx_cdma_stop_transfer(struct xilinx_dma_chan *chan)
return xilinx_dma_poll_timeout(chan, XILINX_DMA_REG_DMASR, val,
val & XILINX_DMA_DMASR_IDLE,
XILINX_DMA_POLL_DELAY_US,
- XILINX_DMA_LOOP_COUNT);
+ XILINX_DMA_POLL_TIMEOUT_US);
}
/**
@@ -1370,7 +1370,7 @@ static void xilinx_dma_start(struct xilinx_dma_chan *chan)
err = xilinx_dma_poll_timeout(chan, XILINX_DMA_REG_DMASR, val,
!(val & XILINX_DMA_DMASR_HALTED),
XILINX_DMA_POLL_DELAY_US,
- XILINX_DMA_LOOP_COUNT);
+ XILINX_DMA_POLL_TIMEOUT_US);
if (err) {
dev_err(chan->dev, "Cannot start channel %p: %x\n",
@@ -1787,7 +1787,7 @@ static int xilinx_dma_reset(struct xilinx_dma_chan *chan)
err = xilinx_dma_poll_timeout(chan, XILINX_DMA_REG_DMACR, tmp,
!(tmp & XILINX_DMA_DMACR_RESET),
XILINX_DMA_POLL_DELAY_US,
- XILINX_DMA_LOOP_COUNT);
+ XILINX_DMA_POLL_TIMEOUT_US);
if (err) {
dev_err(chan->dev, "reset timeout, cr %x, sr %x\n",
--
2.53.0
^ permalink raw reply related
* [PATCH v2 0/2] Fix CPU stall in xilinx_dma_poll_timeout caused by passing delay_us=0
From: Alex Bereza @ 2026-04-01 9:57 UTC (permalink / raw)
To: Vinod Koul, Frank Li, Michal Simek, Geert Uytterhoeven,
Ulf Hansson, Arnd Bergmann, Tony Lindgren, Kedareswara rao Appana
Cc: dmaengine, linux-arm-kernel, linux-kernel, Alex Bereza
Signed-off-by: Alex Bereza <alex@bereza.email>
---
Changes in v2:
- Fixed the Fixes: tags as suggested by Geert Uytterhoeven
<geert+renesas@glider.be> - thanks!
- Split the renaming of XILINX_DMA_LOOP_COUNT into separate commit
- Link to v1: https://patch.msgid.link/20260331-fix-atomic-poll-timeout-regression-v1-1-5b7bd96eaca0@bereza.email
---
Alex Bereza (2):
dmaengine: xilinx_dma: Fix CPU stall in xilinx_dma_poll_timeout
dmaengine: xilinx_dma: Rename XILINX_DMA_LOOP_COUNT
drivers/dma/xilinx/xilinx_dma.c | 26 ++++++++++++++++----------
1 file changed, 16 insertions(+), 10 deletions(-)
---
base-commit: b7560798466a07d9c3fb011698e92c335ab28baf
change-id: 20260330-fix-atomic-poll-timeout-regression-4f4e3baf3fd7
Best regards,
--
Alex Bereza <alex@bereza.email>
^ permalink raw reply
* [PATCH v4 09/11] firmware: arm_scmi: add Powercap MAI get/set support
From: Philip Radford @ 2026-04-01 9:53 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-pm
Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, quic_sibis,
dan.carpenter, d-gole, souvik.chakravarty, Philip Radford
In-Reply-To: <20260401095349.2217840-1-philip.radford@arm.com>
Add support for Power Measurement Averaging Interval (MAI)
get and set operations to the SCMI powercap protocol driver.
Extends scmi_powercap_info to store MAI configuration and
implement MAI get/set via xfer and optional fast-channel
support.
Signed-off-by: Philip Radford <philip.radford@arm.com>
---
drivers/firmware/arm_scmi/powercap.c | 120 +++++++++++++++++++++++++++
include/linux/scmi_protocol.h | 8 ++
2 files changed, 128 insertions(+)
diff --git a/drivers/firmware/arm_scmi/powercap.c b/drivers/firmware/arm_scmi/powercap.c
index 86262eb0f34a..b5879f204b5e 100644
--- a/drivers/firmware/arm_scmi/powercap.c
+++ b/drivers/firmware/arm_scmi/powercap.c
@@ -401,6 +401,34 @@ scmi_powercap_domain_attrs_process(const struct scmi_protocol_handle *ph,
dom_info->notify_powercap_measurement_change =
SUPPORTS_POWERCAP_MEASUREMENTS_CHANGE_NOTIFY(flags);
+ if (PROTOCOL_REV_MAJOR(ph->version) >= 0x3) {
+ struct scmi_msg_resp_powercap_domain_attributes_v3 *resp_v3 = r;
+
+ flags = le32_to_cpu(resp_v3->attributes);
+ if (pinfo->notify_measurements_cmd)
+ dom_info->notify_powercap_measurement_change =
+ SUPPORTS_POWERCAP_MEASUREMENTS_CHANGE_NOTIFY(flags);
+
+ dom_info->mai_config = SUPPORTS_POWERCAP_MAI_CONFIGURATION(flags);
+ dom_info->min_mai = le32_to_cpu(resp_v3->min_mai);
+ dom_info->max_mai = le32_to_cpu(resp_v3->max_mai);
+ dom_info->mai_step = le32_to_cpu(resp_v3->mai_step);
+
+ if (dom_info->mai_config) {
+ ret = scmi_powercap_validate(dom_info->min_mai,
+ dom_info->max_mai,
+ dom_info->mai_step,
+ dom_info->mai_config);
+
+ if (ret) {
+ dev_warn(ph->dev, "Platform reported problem MAI config for domain %d - %s\n",
+ dom_info->id, dom_info->name);
+
+ return ret;
+ }
+ }
+ }
+
dom_info->extended_names = SUPPORTS_EXTENDED_NAMES(flags);
dom_info->async_powercap_cap_set =
@@ -1082,6 +1110,96 @@ static int scmi_powercap_cap_enable_get(const struct scmi_protocol_handle *ph,
return 0;
}
+static int scmi_powercap_xfer_mai_get(const struct scmi_protocol_handle *ph, u32 domain_id,
+ u32 *mai)
+{
+ int ret;
+ struct scmi_xfer *t;
+
+ ret = ph->xops->xfer_get_init(ph, POWERCAP_MAI_GET, sizeof(u32),
+ sizeof(u32), &t);
+
+ if (ret)
+ return ret;
+
+ put_unaligned_le32(domain_id, t->tx.buf);
+
+ ret = ph->xops->do_xfer(ph, t);
+ if (!ret)
+ *mai = get_unaligned_le32(t->rx.buf);
+
+ ph->xops->xfer_put(ph, t);
+ return ret;
+}
+
+static int scmi_powercap_xfer_mai_set(const struct scmi_protocol_handle *ph, u32 domain_id, u32 mai)
+{
+ int ret;
+ struct scmi_xfer *t;
+ struct scmi_msg_powercap_cap_or_pai_set *msg;
+
+ ret = ph->xops->xfer_get_init(ph, POWERCAP_MAI_SET, sizeof(*msg), 0, &t);
+ if (ret)
+ return ret;
+
+ msg = t->tx.buf;
+ msg->domain_id = cpu_to_le32(domain_id);
+ msg->flags = cpu_to_le32(0);
+ msg->value = cpu_to_le32(mai);
+
+ ret = ph->xops->do_xfer(ph, t);
+
+ ph->xops->xfer_put(ph, t);
+ return ret;
+}
+
+static int scmi_powercap_measurements_interval_get(const struct scmi_protocol_handle *ph,
+ u32 domain_id, u32 *val)
+{
+ const struct scmi_powercap_info *pc;
+ struct scmi_fc_info *fci;
+
+ if (!val)
+ return -EINVAL;
+
+ pc = scmi_powercap_dom_info_get(ph, domain_id);
+ if (!pc)
+ return -EINVAL;
+
+ fci = pc->cpli[CPL0].fc_info;
+ if (fci && fci[POWERCAP_FC_MAI].get_addr) {
+ *val = ioread32(fci[POWERCAP_FC_MAI].get_addr);
+ trace_scmi_fc_call(SCMI_PROTOCOL_POWERCAP, POWERCAP_MAI_GET, domain_id, 0, *val, 0);
+ return 0;
+ }
+
+ return scmi_powercap_xfer_mai_get(ph, domain_id, val);
+}
+
+static int scmi_powercap_measurements_interval_set(const struct scmi_protocol_handle *ph,
+ u32 domain_id, u32 val)
+{
+ const struct scmi_powercap_info *pc;
+ struct scmi_fc_info *fci;
+
+ pc = scmi_powercap_dom_info_get(ph, domain_id);
+ if (!pc)
+ return -EINVAL;
+
+ if (!pc->mai_config || !val || val < pc->min_mai || val > pc->max_mai)
+ return -EINVAL;
+
+ fci = pc->cpli[CPL0].fc_info;
+ if (fci && fci[POWERCAP_FC_MAI].set_addr) {
+ iowrite32(val, fci[POWERCAP_FC_MAI].set_addr);
+ ph->hops->fastchannel_db_ring(fci[POWERCAP_FC_MAI].set_db);
+ trace_scmi_fc_call(SCMI_PROTOCOL_POWERCAP, POWERCAP_MAI_SET, domain_id, 0, val, 0);
+ return 0;
+ }
+
+ return scmi_powercap_xfer_mai_set(ph, domain_id, val);
+}
+
static const struct scmi_powercap_proto_ops powercap_proto_ops = {
.num_domains_get = scmi_powercap_num_domains_get,
.info_get = scmi_powercap_dom_info_get,
@@ -1094,6 +1212,8 @@ static const struct scmi_powercap_proto_ops powercap_proto_ops = {
.measurements_get = scmi_powercap_measurements_get,
.measurements_threshold_set = scmi_powercap_measurements_threshold_set,
.measurements_threshold_get = scmi_powercap_measurements_threshold_get,
+ .measurements_interval_get = scmi_powercap_measurements_interval_get,
+ .measurements_interval_set = scmi_powercap_measurements_interval_set,
};
static void scmi_powercap_domain_init_fc(const struct scmi_protocol_handle *ph,
diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h
index d0f6c0102559..73d66281dcc3 100644
--- a/include/linux/scmi_protocol.h
+++ b/include/linux/scmi_protocol.h
@@ -675,6 +675,10 @@ struct scmi_powercap_info {
bool powercap_scale_uw;
bool extended_names;
bool fastchannels;
+ bool mai_config;
+ u32 min_mai;
+ u32 max_mai;
+ u32 mai_step;
char name[SCMI_MAX_STR_SIZE];
unsigned int sustainable_power;
unsigned int accuracy;
@@ -758,6 +762,10 @@ struct scmi_powercap_proto_ops {
int (*measurements_threshold_get)(const struct scmi_protocol_handle *ph,
u32 domain_id, u32 *power_thresh_low,
u32 *power_thresh_high);
+ int (*measurements_interval_get)(const struct scmi_protocol_handle *ph,
+ u32 domain_id, u32 *val);
+ int (*measurements_interval_set)(const struct scmi_protocol_handle *ph,
+ u32 domain_id, u32 val);
};
enum scmi_pinctrl_selector_type {
--
2.47.3
^ permalink raw reply related
* [PATCH v4 11/11] powercap: arm_scmi: Add get_power_uw to synthetic node
From: Philip Radford @ 2026-04-01 9:53 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-pm
Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, quic_sibis,
dan.carpenter, d-gole, souvik.chakravarty, Philip Radford
In-Reply-To: <20260401095349.2217840-1-philip.radford@arm.com>
Exposes the current power usage from the immediate children of
the synthetic (root) powercap node. Iterates over pr->spzones and
sums per-zone power.
Signed-off-by: Philip Radford <philip.radford@arm.com>
---
drivers/powercap/arm_scmi_powercap.c | 29 ++++++++++++++++++++++++++--
1 file changed, 27 insertions(+), 2 deletions(-)
diff --git a/drivers/powercap/arm_scmi_powercap.c b/drivers/powercap/arm_scmi_powercap.c
index 6fc2cab30b05..53fcbf178e8c 100644
--- a/drivers/powercap/arm_scmi_powercap.c
+++ b/drivers/powercap/arm_scmi_powercap.c
@@ -270,9 +270,34 @@ static int instance_root_release(struct powercap_zone *pz)
return 0;
}
-static int instance_root_get_power_uw(struct powercap_zone *pz, u64 *v)
+static int instance_root_get_power_uw(struct powercap_zone *pz, u64 *power_u)
{
- *v = 0;
+ struct scmi_powercap_zone *root = to_scmi_powercap_zone(pz);
+ struct scmi_powercap_zone *child;
+ struct scmi_powercap_root *pr;
+ u64 acc = 0;
+ u64 p;
+ int ret;
+
+ if (!pz || !power_uw)
+ return -EINVAL;
+
+ pr = container_of(root, struct scmi_powercap_root, agent_root);
+ if (!pr)
+ return -ENODEV;
+
+ list_for_each_entry(child, &pr->registered_zones[0], node) {
+ if (child == &pr->agent_root)
+ continue;
+
+ ret = scmi_powercap_get_power_uw(&child->zone, &p);
+ if (!ret)
+ acc += p;
+ else
+ dev_dbg(child->dev, "Failed to read child power: %u\n", ret);
+ }
+
+ *power_uw = acc;
return 0;
}
--
2.47.3
^ permalink raw reply related
* [PATCH v4 10/11] powercap: arm_scmi: Create synthetic parent node for multi-instance
From: Philip Radford @ 2026-04-01 9:53 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-pm
Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, quic_sibis,
dan.carpenter, d-gole, souvik.chakravarty, Philip Radford
In-Reply-To: <20260401095349.2217840-1-philip.radford@arm.com>
SCMI powercap domains are exposed as a flat list and may include
multiple top-level domains without a parent. When registered with
the powercap framework, these appear as independent root zones
with no common hierarchy. The driver probes domains per SCMI
instance, but when registering with the powercap framework, these
would be combined into a single tree. This is particularly evident
in multi-instance setups where zone IDs and parent zones can only
coexist with other zones and parents from the same instance.
Create a synthetic root zone to act as a common parent for all
top-level domains. Creates a single hierarchy and unified entry
point per-instance for userspace.
Signed-off-by: Philip Radford <philip.radford@arm.com>
---
---
drivers/powercap/arm_scmi_powercap.c | 67 ++++++++++++++++++++++++++--
1 file changed, 64 insertions(+), 3 deletions(-)
diff --git a/drivers/powercap/arm_scmi_powercap.c b/drivers/powercap/arm_scmi_powercap.c
index 7f2bb162f96c..6fc2cab30b05 100644
--- a/drivers/powercap/arm_scmi_powercap.c
+++ b/drivers/powercap/arm_scmi_powercap.c
@@ -36,6 +36,7 @@ struct scmi_powercap_root {
struct scmi_powercap_zone *spzones;
struct list_head *registered_zones;
struct list_head scmi_zones;
+ struct scmi_powercap_zone instance_root;
};
static struct powercap_control_type *scmi_top_pcntrl;
@@ -263,12 +264,47 @@ static const struct powercap_zone_constraint_ops constraint_ops = {
.get_name = scmi_powercap_get_name,
};
+/* Multi-agent constraints to meet driver requrements */
+static int instance_root_release(struct powercap_zone *pz)
+{
+ return 0;
+}
+
+static int instance_root_get_power_uw(struct powercap_zone *pz, u64 *v)
+{
+ *v = 0;
+ return 0;
+}
+
+static int instance_root_set_constraint(struct powercap_zone *pz, int cid, u64 v)
+{
+ return -EOPNOTSUPP;
+}
+
+static int instance_root_get_constraint(struct powercap_zone *pz, int cid, u64 *v)
+{
+ return -EOPNOTSUPP;
+}
+
+static const struct powercap_zone_ops instance_root_ops = {
+ .get_max_power_range_uw = scmi_powercap_get_max_power_range_uw,
+ .get_power_uw = instance_root_get_power_uw,
+ .release = instance_root_release,
+};
+
+static const struct powercap_zone_constraint_ops instance_root_const_ops = {
+ .set_power_limit_uw = instance_root_set_constraint,
+ .get_power_limit_uw = instance_root_get_constraint,
+ .set_time_window_us = instance_root_set_constraint,
+ .get_time_window_us = instance_root_get_constraint,
+};
+
static void scmi_powercap_unregister_all_zones(struct scmi_powercap_root *pr)
{
int i;
/* Un-register children zones first starting from the leaves */
- for (i = pr->num_zones - 1; i >= 0; i--) {
+ for (i = pr->num_zones; i >= 0; i--) {
if (!list_empty(&pr->registered_zones[i])) {
struct scmi_powercap_zone *spz;
@@ -313,7 +349,6 @@ static int scmi_powercap_register_zone(struct scmi_powercap_root *pr,
parent ? &parent->zone : NULL,
&zone_ops, spz->info->num_cpli, &constraint_ops);
if (!IS_ERR(z)) {
- spz->height = scmi_powercap_get_zone_height(spz);
spz->registered = true;
list_move(&spz->node, &pr->registered_zones[spz->height]);
dev_dbg(spz->dev, "Registered node %s - parent %s - height:%d\n",
@@ -384,6 +419,8 @@ static int scmi_zones_register(struct device *dev,
struct scmi_powercap_zone *parent;
parent = scmi_powercap_get_parent_zone(spz);
+ if (!parent)
+ parent = &pr->instance_root;
if (parent && !parent->registered) {
zones_stack[sp++] = spz;
spz = parent;
@@ -424,8 +461,11 @@ static int scmi_powercap_probe(struct scmi_device *sdev)
int ret, i;
struct scmi_powercap_root *pr;
struct scmi_powercap_zone *spz;
+ struct scmi_powercap_zone *ir;
struct scmi_protocol_handle *ph;
struct device *dev = &sdev->dev;
+ char *instance_name;
+ struct powercap_zone *z;
if (!sdev->handle)
return -ENODEV;
@@ -453,7 +493,7 @@ static int scmi_powercap_probe(struct scmi_device *sdev)
return -ENOMEM;
/* Allocate for worst possible scenario of maximum tree height. */
- pr->registered_zones = devm_kcalloc(dev, pr->num_zones,
+ pr->registered_zones = devm_kcalloc(dev, pr->num_zones + 1,
sizeof(*pr->registered_zones),
GFP_KERNEL);
if (!pr->registered_zones)
@@ -491,6 +531,27 @@ static int scmi_powercap_probe(struct scmi_device *sdev)
}
}
+ ir = &pr->instance_root;
+ ir->dev = dev;
+ INIT_LIST_HEAD(&ir->node);
+ instance_name = devm_kasprintf(dev, GFP_KERNEL, "instance_%s", dev_name(dev));
+ if (!instance_name)
+ return -ENOMEM;
+
+ z = powercap_register_zone(&ir->zone, scmi_top_pcntrl,
+ instance_name, NULL, &instance_root_ops, 0,
+ &instance_root_const_ops);
+
+ if (IS_ERR(z)) {
+ ret = PTR_ERR(z);
+ dev_err(dev, "Failed to register sysnthetic instance root: %d\n", ret);
+ return ret;
+ }
+
+ ir->registered = true;
+ ir->height = 0;
+ list_add_tail(&ir->node, &pr->registered_zones[0]);
+
/*
* Scan array of retrieved SCMI powercap domains and register them
* recursively starting from the root domains.
--
2.47.3
^ permalink raw reply related
* [PATCH v4 08/11] powercap: arm_scmi: Enable multiple constraints support
From: Philip Radford @ 2026-04-01 9:53 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-pm
Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, quic_sibis,
dan.carpenter, d-gole, souvik.chakravarty, Cristian Marussi,
Rafael J. Wysocki, Philip Radford
In-Reply-To: <20260401095349.2217840-1-philip.radford@arm.com>
From: Cristian Marussi <cristian.marussi@arm.com>
Initialize the domains with all the discovered available constraints,
making available multiple per-domain constraints when the platform has
advertised support for multiple concurrent power limits.
CC: "Rafael J. Wysocki" <rafael@kernel.org>
CC: linux-pm@vger.kernel.org
Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
[Philip: Amended Copyright]
Signed-off-by: Philip Radford <philip.radford@arm.com>
---
drivers/powercap/arm_scmi_powercap.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/powercap/arm_scmi_powercap.c b/drivers/powercap/arm_scmi_powercap.c
index be3007206a74..7f2bb162f96c 100644
--- a/drivers/powercap/arm_scmi_powercap.c
+++ b/drivers/powercap/arm_scmi_powercap.c
@@ -2,7 +2,7 @@
/*
* SCMI Powercap support.
*
- * Copyright (C) 2022 ARM Ltd.
+ * Copyright (C) 2022-2026 ARM Ltd.
*/
#include <linux/device.h>
@@ -311,7 +311,7 @@ static int scmi_powercap_register_zone(struct scmi_powercap_root *pr,
z = powercap_register_zone(&spz->zone, scmi_top_pcntrl, spz->info->name,
parent ? &parent->zone : NULL,
- &zone_ops, 1, &constraint_ops);
+ &zone_ops, spz->info->num_cpli, &constraint_ops);
if (!IS_ERR(z)) {
spz->height = scmi_powercap_get_zone_height(spz);
spz->registered = true;
--
2.47.3
^ permalink raw reply related
* [PATCH v4 07/11] include: trace: Add new parameter to trace_scmi_fc_call
From: Philip Radford @ 2026-04-01 9:53 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-pm
Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, quic_sibis,
dan.carpenter, d-gole, souvik.chakravarty, Cristian Marussi,
Philip Radford
In-Reply-To: <20260401095349.2217840-1-philip.radford@arm.com>
From: Cristian Marussi <cristian.marussi@arm.com>
Since SCMIv4.0 some of the supported Fastchannels can be configured using
an additional parameter like CPL_ID or Capability_ID.
Add equivalent support in the SCMI fastchannel traces to printout also such
parameter and fix all the existent call sites.
When such parameter is not used, it will simply show up as zero.
Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
Signed-off-by: Philip Radford <philip.radford@arm.com>
---
drivers/firmware/arm_scmi/perf.c | 8 ++++----
drivers/firmware/arm_scmi/powercap.c | 12 +++++++-----
include/trace/events/scmi.h | 12 +++++++-----
3 files changed, 18 insertions(+), 14 deletions(-)
diff --git a/drivers/firmware/arm_scmi/perf.c b/drivers/firmware/arm_scmi/perf.c
index 7f283f457e02..88d614e3184b 100644
--- a/drivers/firmware/arm_scmi/perf.c
+++ b/drivers/firmware/arm_scmi/perf.c
@@ -552,7 +552,7 @@ static int __scmi_perf_limits_set(const struct scmi_protocol_handle *ph,
struct scmi_fc_info *fci = &dom->fc_info[PERF_FC_LIMIT];
trace_scmi_fc_call(SCMI_PROTOCOL_PERF, PERF_LIMITS_SET,
- dom->id, min_perf, max_perf);
+ dom->id, 0, min_perf, max_perf);
iowrite32(max_perf, fci->set_addr);
iowrite32(min_perf, fci->set_addr + 4);
ph->hops->fastchannel_db_ring(fci->set_db);
@@ -636,7 +636,7 @@ static int __scmi_perf_limits_get(const struct scmi_protocol_handle *ph,
*max_perf = ioread32(fci->get_addr);
*min_perf = ioread32(fci->get_addr + 4);
trace_scmi_fc_call(SCMI_PROTOCOL_PERF, PERF_LIMITS_GET,
- dom->id, *min_perf, *max_perf);
+ dom->id, 0, *min_perf, *max_perf);
return 0;
}
@@ -706,7 +706,7 @@ static int __scmi_perf_level_set(const struct scmi_protocol_handle *ph,
struct scmi_fc_info *fci = &dom->fc_info[PERF_FC_LEVEL];
trace_scmi_fc_call(SCMI_PROTOCOL_PERF, PERF_LEVEL_SET,
- dom->id, level, 0);
+ dom->id, 0, level, 0);
iowrite32(level, fci->set_addr);
ph->hops->fastchannel_db_ring(fci->set_db);
return 0;
@@ -769,7 +769,7 @@ static int __scmi_perf_level_get(const struct scmi_protocol_handle *ph,
if (dom->fc_info && dom->fc_info[PERF_FC_LEVEL].get_addr) {
*level = ioread32(dom->fc_info[PERF_FC_LEVEL].get_addr);
trace_scmi_fc_call(SCMI_PROTOCOL_PERF, PERF_LEVEL_GET,
- dom->id, *level, 0);
+ dom->id, 0, *level, 0);
return 0;
}
diff --git a/drivers/firmware/arm_scmi/powercap.c b/drivers/firmware/arm_scmi/powercap.c
index b9d50f4e8ae5..86262eb0f34a 100644
--- a/drivers/firmware/arm_scmi/powercap.c
+++ b/drivers/firmware/arm_scmi/powercap.c
@@ -597,7 +597,7 @@ static int __scmi_powercap_cap_get(const struct scmi_protocol_handle *ph,
dom->cpli[cpl_id].fc_info[POWERCAP_FC_CAP].get_addr) {
*power_cap = ioread32(dom->cpli[cpl_id].fc_info[POWERCAP_FC_CAP].get_addr);
trace_scmi_fc_call(SCMI_PROTOCOL_POWERCAP, POWERCAP_CAP_GET,
- dom->id, *power_cap, 0);
+ dom->id, cpl_id, *power_cap, 0);
return 0;
}
@@ -730,7 +730,7 @@ static int __scmi_powercap_cap_set(const struct scmi_protocol_handle *ph,
iowrite32(power_cap, fci->set_addr);
ph->hops->fastchannel_db_ring(fci->set_db);
trace_scmi_fc_call(SCMI_PROTOCOL_POWERCAP, POWERCAP_CAP_SET,
- domain_id, power_cap, 0);
+ domain_id, cpl_id, power_cap, 0);
ret = 0;
} else {
ret = pi->xfer_cap_set(ph, pc, cpl_id, power_cap, ignore_dresp);
@@ -835,7 +835,8 @@ static int scmi_powercap_avg_interval_get(const struct scmi_protocol_handle *ph,
POWERCAP_PAI_GET : POWERCAP_CAI_GET;
*val = ioread32(dom->cpli[cpl_id].fc_info[POWERCAP_FC_XAI].get_addr);
- trace_scmi_fc_call(SCMI_PROTOCOL_POWERCAP, trace_cmd, domain_id, *val, 0);
+ trace_scmi_fc_call(SCMI_PROTOCOL_POWERCAP, trace_cmd, domain_id,
+ cpl_id, *val, 0);
return 0;
}
@@ -908,7 +909,8 @@ static int scmi_powercap_avg_interval_set(const struct scmi_protocol_handle *ph,
POWERCAP_PAI_SET : POWERCAP_CAI_SET;
struct scmi_fc_info *fci = &pc->cpli[cpl_id].fc_info[POWERCAP_FC_XAI];
- trace_scmi_fc_call(SCMI_PROTOCOL_POWERCAP, trace_cmd, domain_id, ivl, 0);
+ trace_scmi_fc_call(SCMI_PROTOCOL_POWERCAP, trace_cmd, domain_id,
+ cpl_id, ivl, 0);
iowrite32(ivl, fci->set_addr);
ph->hops->fastchannel_db_ring(fci->set_db);
return 0;
@@ -961,7 +963,7 @@ static int scmi_powercap_measurements_get(const struct scmi_protocol_handle *ph,
/* See SCMIv4.0 3.10.2 - Payload is 32bit ONLY avg_power */
*avg_ivl = 0;
trace_scmi_fc_call(SCMI_PROTOCOL_POWERCAP, POWERCAP_MEASUREMENTS_GET,
- pc->id, *avg_power, *avg_ivl);
+ pc->id, 0, *avg_power, *avg_ivl);
return 0;
}
diff --git a/include/trace/events/scmi.h b/include/trace/events/scmi.h
index 703b7bb68e44..b03da7323d04 100644
--- a/include/trace/events/scmi.h
+++ b/include/trace/events/scmi.h
@@ -10,13 +10,14 @@
#define TRACE_SCMI_MAX_TAG_LEN 6
TRACE_EVENT(scmi_fc_call,
- TP_PROTO(u8 protocol_id, u8 msg_id, u32 res_id, u32 val1, u32 val2),
- TP_ARGS(protocol_id, msg_id, res_id, val1, val2),
+ TP_PROTO(u8 protocol_id, u8 msg_id, u32 res_id, u32 sub_id, u32 val1, u32 val2),
+ TP_ARGS(protocol_id, msg_id, res_id, sub_id, val1, val2),
TP_STRUCT__entry(
__field(u8, protocol_id)
__field(u8, msg_id)
__field(u32, res_id)
+ __field(u32, sub_id)
__field(u32, val1)
__field(u32, val2)
),
@@ -25,13 +26,14 @@ TRACE_EVENT(scmi_fc_call,
__entry->protocol_id = protocol_id;
__entry->msg_id = msg_id;
__entry->res_id = res_id;
+ __entry->sub_id = sub_id;
__entry->val1 = val1;
__entry->val2 = val2;
),
- TP_printk("pt=%02X msg_id=%02X res_id:%u vals=%u:%u",
- __entry->protocol_id, __entry->msg_id,
- __entry->res_id, __entry->val1, __entry->val2)
+ TP_printk("pt=%02X msg_id=%02X res_id:%u sub_id:%u vals=%u:%u",
+ __entry->protocol_id, __entry->msg_id,
+ __entry->res_id, __entry->sub_id, __entry->val1, __entry->val2)
);
TRACE_EVENT(scmi_xfer_begin,
--
2.47.3
^ permalink raw reply related
* [PATCH v4 06/11] firmware: arm_scmi: Extend powercap report to include MAI
From: Philip Radford @ 2026-04-01 9:53 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-pm
Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, quic_sibis,
dan.carpenter, d-gole, souvik.chakravarty, Philip Radford
In-Reply-To: <20260401095349.2217840-1-philip.radford@arm.com>
Extend scmi_powercap_meas_changed_report to include MAI change
notifications.
Signed-off-by: Philip Radford <philip.radford@arm.com>
---
drivers/firmware/arm_scmi/powercap.c | 20 ++++++++++++--------
include/linux/scmi_protocol.h | 1 +
2 files changed, 13 insertions(+), 8 deletions(-)
diff --git a/drivers/firmware/arm_scmi/powercap.c b/drivers/firmware/arm_scmi/powercap.c
index 1d1188e98d49..b9d50f4e8ae5 100644
--- a/drivers/firmware/arm_scmi/powercap.c
+++ b/drivers/firmware/arm_scmi/powercap.c
@@ -11,6 +11,7 @@
#include <linux/io.h>
#include <linux/module.h>
#include <linux/scmi_protocol.h>
+#include <linux/stddef.h>
#include <trace/events/scmi.h>
@@ -164,6 +165,7 @@ struct scmi_powercap_meas_changed_notify_payld {
__le32 agent_id;
__le32 domain_id;
__le32 power;
+ __le32 mai;
};
struct scmi_msg_powercap_cpc {
@@ -1212,13 +1214,6 @@ static int scmi_powercap_notify(const struct scmi_protocol_handle *ph,
if (ret)
return ret;
- if (enable && !low && !high) {
- dev_err(ph->dev,
- "Invalid Measurements Notify thresholds: %u/%u\n",
- low, high);
- return -EINVAL;
- }
-
ret = ph->xops->xfer_get_init(ph, message_id,
sizeof(*notify), 0, &t);
if (ret)
@@ -1333,14 +1328,23 @@ scmi_powercap_fill_custom_report(const struct scmi_protocol_handle *ph,
{
const struct scmi_powercap_meas_changed_notify_payld *p = payld;
struct scmi_powercap_meas_changed_report *r = report;
+ const size_t sz_v2 = offsetofend(struct scmi_powercap_meas_changed_notify_payld,
+ power);
+ const size_t sz_v3 = sizeof(*p);
- if (sizeof(*p) != payld_sz)
+ if (payld_sz != sz_v2 && payld_sz != sz_v3)
break;
r->timestamp = timestamp;
r->agent_id = le32_to_cpu(p->agent_id);
r->domain_id = le32_to_cpu(p->domain_id);
r->power = le32_to_cpu(p->power);
+
+ if (payld_sz == sz_v3 && PROTOCOL_REV_MAJOR(ph->version) >= 0x3)
+ r->mai = le32_to_cpu(p->mai);
+ else
+ r->mai = 0;
+
*src_id = r->domain_id;
rep = r;
break;
diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h
index 299fa8499b3f..d0f6c0102559 100644
--- a/include/linux/scmi_protocol.h
+++ b/include/linux/scmi_protocol.h
@@ -1134,5 +1134,6 @@ struct scmi_powercap_meas_changed_report {
unsigned int agent_id;
unsigned int domain_id;
unsigned int power;
+ unsigned int mai;
};
#endif /* _LINUX_SCMI_PROTOCOL_H */
--
2.47.3
^ permalink raw reply related
* [PATCH v4 03/11] firmware: arm_scmi: Add SCMIv4.0 Powercap basic support
From: Philip Radford @ 2026-04-01 9:53 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-pm
Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, quic_sibis,
dan.carpenter, d-gole, souvik.chakravarty, Cristian Marussi,
Philip Radford
In-Reply-To: <20260401095349.2217840-1-philip.radford@arm.com>
From: Cristian Marussi <cristian.marussi@arm.com>
Add SCMIv4.0 Powercap support for enumerating multiple CPLs of a domain
when available.
Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
[Philip: Fixed sparse issues where int was expected]
Signed-off-by: Philip Radford <philip.radford@arm.com>
---
drivers/firmware/arm_scmi/powercap.c | 472 +++++++++++++++++++++------
include/linux/scmi_protocol.h | 1 +
2 files changed, 376 insertions(+), 97 deletions(-)
diff --git a/drivers/firmware/arm_scmi/powercap.c b/drivers/firmware/arm_scmi/powercap.c
index 47aa6dde4a52..3291bde78367 100644
--- a/drivers/firmware/arm_scmi/powercap.c
+++ b/drivers/firmware/arm_scmi/powercap.c
@@ -33,6 +33,7 @@ enum scmi_powercap_protocol_cmd {
POWERCAP_CAP_NOTIFY = 0xa,
POWERCAP_MEASUREMENTS_NOTIFY = 0xb,
POWERCAP_DESCRIBE_FASTCHANNEL = 0xc,
+ POWERCAP_CPC_ATTRIBUTES = 0xd,
};
enum {
@@ -69,19 +70,58 @@ struct scmi_msg_resp_powercap_domain_attributes {
__le32 parent_id;
};
+struct scmi_msg_resp_powercap_domain_attributes_v3 {
+ __le32 attributes;
+#define SUPPORTS_POWERCAP_MAI_CONFIGURATION(x) ((x) & BIT(25))
+#define SUPPORTS_POWERCAP_FASTCHANNELS(x) ((x) & BIT(22))
+#define SUPPORTS_POWERCAP_CAP_CHANGE_NOTIFY_V3(x) ((x) & BIT(21))
+#define SUPPORTS_POWERCAP_CAI_CONFIGURATION(x) ((x) & BIT(20))
+ u8 name[SCMI_SHORT_NAME_MAX_SIZE];
+ __le32 min_mai;
+ __le32 max_mai;
+ __le32 mai_step;
+ __le32 min_power_cap;
+ __le32 max_power_cap;
+ __le32 power_cap_step;
+ __le32 sustainable_power;
+ __le32 accuracy;
+ __le32 parent_id;
+ __le32 min_cai;
+ __le32 max_cai;
+ __le32 cai_step;
+};
+
+struct scmi_msg_powercap_get_v3 {
+ __le32 domain_id;
+ __le32 cpli;
+};
+
struct scmi_msg_powercap_set_cap_or_pai {
- __le32 domain;
+ __le32 domain_id;
__le32 flags;
#define CAP_SET_ASYNC BIT(1)
#define CAP_SET_IGNORE_DRESP BIT(0)
__le32 value;
};
+struct scmi_msg_powercap_set_cap_v3 {
+ __le32 domain_id;
+ __le32 cpli;
+ __le32 flags;
+ __le32 power_cap;
+};
+
struct scmi_msg_resp_powercap_cap_set_complete {
- __le32 domain;
+ __le32 domain_id;
__le32 power_cap;
};
+struct scmi_msg_resp_powercap_cap_set_complete_v3 {
+ __le32 domain_id;
+ __le32 power_cap;
+ __le32 cpli;
+};
+
struct scmi_msg_resp_powercap_meas_get {
__le32 power;
__le32 pai;
@@ -112,6 +152,33 @@ struct scmi_powercap_meas_changed_notify_payld {
__le32 power;
};
+struct scmi_msg_powercap_cpc {
+ __le32 domain_id;
+ __le32 desc_index;
+};
+
+struct scmi_msg_resp_powercap_cpc {
+ __le32 num_cpl;
+#define NUM_RETURNED(n) (le32_get_bits((n), GENMASK(15, 0)))
+#define NUM_REMAINING(n) (le32_get_bits((n), GENMASK(31, 16)))
+ struct {
+ __le32 cpli;
+ __le32 flags;
+ __le32 min_power_cap;
+ __le32 max_power_cap;
+ __le32 power_cap_step;
+ __le32 min_cai;
+ __le32 max_cai;
+ __le32 cai_step;
+ u8 name[SCMI_SHORT_NAME_MAX_SIZE];
+ } desc[];
+};
+
+struct scmi_cpls_priv {
+ u32 domain_id;
+ struct scmi_powercap_cpl_info *cpli;
+};
+
struct scmi_powercap_state {
bool enabled;
u32 last_pcap;
@@ -129,6 +196,11 @@ struct powercap_info {
bool notify_measurements_cmd;
struct scmi_powercap_state *states;
struct scmi_powercap_info *powercaps;
+ int (*xfer_cap_get)(const struct scmi_protocol_handle *ph,
+ u32 domain_id, u32 cpl_id, u32 *power_cap);
+ int (*xfer_cap_set)(const struct scmi_protocol_handle *ph,
+ const struct scmi_powercap_info *pc,
+ u32 cpl_id, u32 power_cap, bool ignore_dresp);
};
static enum scmi_powercap_protocol_cmd evt_2_cmd[] = {
@@ -192,111 +264,240 @@ scmi_powercap_validate(unsigned int min_val, unsigned int max_val,
return 0;
}
+static void iter_powercap_cpls_prepare_message(void *message,
+ unsigned int desc_index,
+ const void *priv)
+{
+ struct scmi_msg_powercap_cpc *msg = message;
+ const struct scmi_cpls_priv *p = priv;
+
+ msg->domain_id = cpu_to_le32(p->domain_id);
+ msg->desc_index = cpu_to_le32(desc_index);
+}
+
+static int iter_powercap_cpls_update_state(struct scmi_iterator_state *st,
+ const void *response, void *priv)
+{
+ const struct scmi_msg_resp_powercap_cpc *r = response;
+
+ st->num_returned = NUM_RETURNED(r->num_cpl);
+ st->num_remaining = NUM_REMAINING(r->num_cpl);
+
+ return 0;
+}
+
static int
-scmi_powercap_domain_attributes_get(const struct scmi_protocol_handle *ph,
- struct powercap_info *pinfo,
- struct scmi_powercap_info *dom_info)
+iter_powercap_cpls_process_response(const struct scmi_protocol_handle *ph,
+ const void *response,
+ struct scmi_iterator_state *st, void *priv)
{
- int ret;
- u32 flags;
- struct scmi_xfer *t;
- struct scmi_msg_resp_powercap_domain_attributes *resp;
+ const struct scmi_msg_resp_powercap_cpc *r = response;
+ struct scmi_cpls_priv *p = priv;
+ struct scmi_powercap_cpl_info *cpl;
- ret = ph->xops->xfer_get_init(ph, POWERCAP_DOMAIN_ATTRIBUTES,
- sizeof(dom_info->id), sizeof(*resp), &t);
- if (ret)
- return ret;
+ cpl = &p->cpli[st->desc_index + st->loop_idx];
- put_unaligned_le32(dom_info->id, t->tx.buf);
- resp = t->rx.buf;
+ cpl->id = le32_to_cpu(r->desc[st->loop_idx].cpli);
+ cpl->cap_config = le32_to_cpu(r->desc[st->loop_idx].flags) & BIT(0);
- ret = ph->xops->do_xfer(ph, t);
- if (!ret) {
- flags = le32_to_cpu(resp->attributes);
+ cpl->min_power_cap = le32_to_cpu(r->desc[st->loop_idx].min_power_cap);
+ cpl->max_power_cap = le32_to_cpu(r->desc[st->loop_idx].max_power_cap);
+ cpl->power_cap_step = le32_to_cpu(r->desc[st->loop_idx].power_cap_step);
+ if (!cpl->power_cap_step && cpl->min_power_cap != cpl->max_power_cap)
+ return -EINVAL;
+
+ cpl->min_avg_ivl = le32_to_cpu(r->desc[st->loop_idx].min_cai);
+ cpl->max_avg_ivl = le32_to_cpu(r->desc[st->loop_idx].max_cai);
+ cpl->avg_ivl_step = le32_to_cpu(r->desc[st->loop_idx].cai_step);
+ if (!cpl->avg_ivl_step && cpl->min_avg_ivl != cpl->max_avg_ivl)
+ return -EINVAL;
+
+ cpl->avg_ivl_config = cpl->min_avg_ivl != cpl->max_avg_ivl;
+
+ strscpy(cpl->name, r->desc[st->loop_idx].name, SCMI_SHORT_NAME_MAX_SIZE);
+
+ return 0;
+}
- if (pinfo->notify_cap_cmd)
+static int scmi_powercap_cpls_enumerate(const struct scmi_protocol_handle *ph,
+ struct scmi_powercap_info *dom_info)
+{
+ void *iter;
+ struct scmi_iterator_ops ops = {
+ .prepare_message = iter_powercap_cpls_prepare_message,
+ .update_state = iter_powercap_cpls_update_state,
+ .process_response = iter_powercap_cpls_process_response,
+ };
+ struct scmi_cpls_priv cpriv = {
+ .domain_id = dom_info->id,
+ .cpli = dom_info->cpli,
+ };
+
+ iter = ph->hops->iter_response_init(ph, &ops, dom_info->num_cpli,
+ POWERCAP_CPC_ATTRIBUTES,
+ sizeof(struct scmi_msg_powercap_cpc),
+ &cpriv);
+ if (IS_ERR(iter))
+ return PTR_ERR(iter);
+
+ return ph->hops->iter_response_run(iter);
+}
+
+static int
+scmi_powercap_domain_attrs_process(const struct scmi_protocol_handle *ph,
+ struct powercap_info *pinfo,
+ struct scmi_powercap_info *dom_info, void *r)
+{
+ struct scmi_msg_resp_powercap_domain_attributes *resp = r;
+ u32 flags = le32_to_cpu(resp->attributes);
+ bool cap_config;
+ int ret;
+
+ cap_config = SUPPORTS_POWERCAP_CAP_CONFIGURATION(flags);
+ if (PROTOCOL_REV_MAJOR(ph->version) < 0x3) {
+ dom_info->num_cpli = 1;
+ } else {
+ dom_info->num_cpli = le32_get_bits(resp->attributes,
+ GENMASK(18, 15));
+ if (cap_config && !dom_info->num_cpli)
+ return -EINVAL;
+ }
+
+ dom_info->cpli = devm_kcalloc(ph->dev, dom_info->num_cpli,
+ sizeof(*dom_info->cpli), GFP_KERNEL);
+ if (!dom_info->cpli)
+ return -ENOMEM;
+
+ if (pinfo->notify_cap_cmd) {
+ if (PROTOCOL_REV_MAJOR(ph->version) < 0x3)
dom_info->notify_powercap_cap_change =
SUPPORTS_POWERCAP_CAP_CHANGE_NOTIFY(flags);
- if (pinfo->notify_measurements_cmd)
- dom_info->notify_powercap_measurement_change =
- SUPPORTS_POWERCAP_MEASUREMENTS_CHANGE_NOTIFY(flags);
- dom_info->async_powercap_cap_set =
- SUPPORTS_ASYNC_POWERCAP_CAP_SET(flags);
-
- dom_info->powercap_monitoring =
- SUPPORTS_POWERCAP_MONITORING(flags);
- dom_info->powercap_scale_mw =
- SUPPORTS_POWER_UNITS_MW(flags);
- dom_info->powercap_scale_uw =
- SUPPORTS_POWER_UNITS_UW(flags);
- dom_info->fastchannels =
- SUPPORTS_POWERCAP_FASTCHANNELS(flags);
-
- strscpy(dom_info->name, resp->name, SCMI_SHORT_NAME_MAX_SIZE);
-
- dom_info->sustainable_power =
- le32_to_cpu(resp->sustainable_power);
- dom_info->accuracy = le32_to_cpu(resp->accuracy);
-
- dom_info->parent_id = le32_to_cpu(resp->parent_id);
- if (dom_info->parent_id != SCMI_POWERCAP_ROOT_ZONE_ID &&
- (dom_info->parent_id >= pinfo->num_domains ||
- dom_info->parent_id == dom_info->id)) {
- dev_err(ph->dev,
- "Platform reported inconsistent parent ID for domain %d - %s\n",
- dom_info->id, dom_info->name);
- ret = -ENODEV;
- }
+ else
+ dom_info->notify_powercap_cap_change =
+ SUPPORTS_POWERCAP_CAP_CHANGE_NOTIFY_V3(flags);
+ }
+
+ if (pinfo->notify_measurements_cmd)
+ dom_info->notify_powercap_measurement_change =
+ SUPPORTS_POWERCAP_MEASUREMENTS_CHANGE_NOTIFY(flags);
+
+ dom_info->extended_names = SUPPORTS_EXTENDED_NAMES(flags);
+
+ dom_info->async_powercap_cap_set =
+ SUPPORTS_ASYNC_POWERCAP_CAP_SET(flags);
+
+ dom_info->powercap_monitoring =
+ SUPPORTS_POWERCAP_MONITORING(flags);
+ dom_info->powercap_scale_mw =
+ SUPPORTS_POWER_UNITS_MW(flags);
+ dom_info->powercap_scale_uw =
+ SUPPORTS_POWER_UNITS_UW(flags);
+ dom_info->fastchannels =
+ SUPPORTS_POWERCAP_FASTCHANNELS(flags);
+
+ strscpy(dom_info->name, resp->name, SCMI_SHORT_NAME_MAX_SIZE);
+
+ dom_info->sustainable_power =
+ le32_to_cpu(resp->sustainable_power);
+ dom_info->accuracy = le32_to_cpu(resp->accuracy);
+
+ dom_info->parent_id = le32_to_cpu(resp->parent_id);
+ if (dom_info->parent_id != SCMI_POWERCAP_ROOT_ZONE_ID &&
+ (dom_info->parent_id >= pinfo->num_domains ||
+ dom_info->parent_id == dom_info->id)) {
+ dev_err(ph->dev,
+ "Platform reported inconsistent parent ID for domain %d - %s\n",
+ dom_info->id, dom_info->name);
+ return -ENODEV;
+ }
+ dom_info->cpli[0].id = CPL0;
+ if (PROTOCOL_REV_MAJOR(ph->version) < 0x3)
dom_info->cpli[0].avg_ivl_config =
SUPPORTS_POWERCAP_PAI_CONFIGURATION(flags);
+ else
+ dom_info->cpli[0].avg_ivl_config =
+ SUPPORTS_POWERCAP_CAI_CONFIGURATION(flags);
+
+ if (PROTOCOL_REV_MAJOR(ph->version) < 0x3) {
dom_info->cpli[0].min_avg_ivl = le32_to_cpu(resp->min_pai);
dom_info->cpli[0].max_avg_ivl = le32_to_cpu(resp->max_pai);
dom_info->cpli[0].avg_ivl_step = le32_to_cpu(resp->pai_step);
- ret = scmi_powercap_validate(dom_info->cpli[0].min_avg_ivl,
- dom_info->cpli[0].max_avg_ivl,
- dom_info->cpli[0].avg_ivl_step,
- dom_info->cpli[0].avg_ivl_config);
- if (ret) {
- dev_err(ph->dev,
- "Platform reported inconsistent PAI config for domain %d - %s\n",
- dom_info->id, dom_info->name);
- goto clean;
- }
+ } else {
+ struct scmi_msg_resp_powercap_domain_attributes_v3 *resp = r;
- dom_info->cpli[0].cap_config =
- SUPPORTS_POWERCAP_CAP_CONFIGURATION(flags);
- dom_info->cpli[0].min_power_cap = le32_to_cpu(resp->min_power_cap);
- dom_info->cpli[0].max_power_cap = le32_to_cpu(resp->max_power_cap);
- dom_info->cpli[0].power_cap_step = le32_to_cpu(resp->power_cap_step);
- ret = scmi_powercap_validate(dom_info->cpli[0].min_power_cap,
- dom_info->cpli[0].max_power_cap,
- dom_info->cpli[0].power_cap_step,
- dom_info->cpli[0].cap_config);
- if (ret) {
- dev_err(ph->dev,
- "Platform reported inconsistent CAP config for domain %d - %s\n",
- dom_info->id, dom_info->name);
- goto clean;
- }
+ dom_info->cpli[0].min_avg_ivl = le32_to_cpu(resp->min_cai);
+ dom_info->cpli[0].max_avg_ivl = le32_to_cpu(resp->max_cai);
+ dom_info->cpli[0].avg_ivl_step = le32_to_cpu(resp->cai_step);
+ }
+
+ ret = scmi_powercap_validate(dom_info->cpli[0].min_avg_ivl,
+ dom_info->cpli[0].max_avg_ivl,
+ dom_info->cpli[0].avg_ivl_step,
+ dom_info->cpli[0].avg_ivl_config);
+ if (ret) {
+ dev_err(ph->dev,
+ "Platform reported inconsistent PAI config for domain %d - %s\n",
+ dom_info->id, dom_info->name);
+ return ret;
+ }
- /* Just using same short name */
- strscpy(dom_info->cpli[0].name, dom_info->name,
- SCMI_SHORT_NAME_MAX_SIZE);
+ dom_info->cpli[0].cap_config = cap_config;
+ dom_info->cpli[0].min_power_cap = le32_to_cpu(resp->min_power_cap);
+ dom_info->cpli[0].max_power_cap = le32_to_cpu(resp->max_power_cap);
+ dom_info->cpli[0].power_cap_step = le32_to_cpu(resp->power_cap_step);
+ ret = scmi_powercap_validate(dom_info->cpli[0].min_power_cap,
+ dom_info->cpli[0].max_power_cap,
+ dom_info->cpli[0].power_cap_step,
+ dom_info->cpli[0].cap_config);
+ if (ret) {
+ dev_err(ph->dev,
+ "Platform reported inconsistent CAP config for domain %d - %s\n",
+ dom_info->id, dom_info->name);
+ return ret;
}
+ /* Just using same short name */
+ strscpy(dom_info->cpli[0].name, dom_info->name, SCMI_SHORT_NAME_MAX_SIZE);
+
+ return 0;
+}
+
+static int
+scmi_powercap_domain_attributes_get(const struct scmi_protocol_handle *ph,
+ struct powercap_info *pinfo,
+ struct scmi_powercap_info *dom_info)
+{
+ int ret;
+ struct scmi_xfer *t;
+ struct scmi_msg_resp_powercap_domain_attributes *resp;
+
+ ret = ph->xops->xfer_get_init(ph, POWERCAP_DOMAIN_ATTRIBUTES,
+ sizeof(dom_info->id), 0, &t);
+ if (ret)
+ return ret;
+
+ put_unaligned_le32(dom_info->id, t->tx.buf);
+ resp = t->rx.buf;
+
+ ret = ph->xops->do_xfer(ph, t);
+ if (!ret)
+ ret = scmi_powercap_domain_attrs_process(ph, pinfo, dom_info, resp);
-clean:
ph->xops->xfer_put(ph, t);
/*
* If supported overwrite short name with the extended one;
* on error just carry on and use already provided short name.
*/
- if (!ret && SUPPORTS_EXTENDED_NAMES(flags))
+ if (!ret && dom_info->extended_names)
ph->hops->extended_name_get(ph, POWERCAP_DOMAIN_NAME_GET,
dom_info->id, NULL, dom_info->name,
SCMI_MAX_STR_SIZE);
+ /* When protocol version > 0x3 there can possibly be more than 1 CPLs */
+ if (!ret && dom_info->num_cpli > 1)
+ ret = scmi_powercap_cpls_enumerate(ph, dom_info);
+
return ret;
}
@@ -306,14 +507,7 @@ scmi_powercap_domain_initialize(const struct scmi_protocol_handle *ph,
{
struct scmi_powercap_info *dom_info = pinfo->powercaps + domain;
- dom_info->num_cpli = 1;
- dom_info->cpli = devm_kcalloc(ph->dev, dom_info->num_cpli,
- sizeof(*dom_info->cpli), GFP_KERNEL);
- if (!dom_info->cpli)
- return -ENOMEM;
-
dom_info->id = domain;
- dom_info->cpli[0].id = CPL0;
return scmi_powercap_domain_attributes_get(ph, pinfo, dom_info);
}
@@ -337,7 +531,7 @@ scmi_powercap_dom_info_get(const struct scmi_protocol_handle *ph, u32 domain_id)
}
static int scmi_powercap_xfer_cap_get(const struct scmi_protocol_handle *ph,
- u32 domain_id, u32 *power_cap)
+ u32 domain_id, u32 cpl_id, u32 *power_cap)
{
int ret;
struct scmi_xfer *t;
@@ -348,6 +542,33 @@ static int scmi_powercap_xfer_cap_get(const struct scmi_protocol_handle *ph,
return ret;
put_unaligned_le32(domain_id, t->tx.buf);
+
+ ret = ph->xops->do_xfer(ph, t);
+ if (!ret)
+ *power_cap = get_unaligned_le32(t->rx.buf);
+
+ ph->xops->xfer_put(ph, t);
+
+ return ret;
+}
+
+static int scmi_powercap_xfer_cap_get_v3(const struct scmi_protocol_handle *ph,
+ u32 domain_id, u32 cpl_id,
+ u32 *power_cap)
+{
+ int ret;
+ struct scmi_xfer *t;
+ struct scmi_msg_powercap_get_v3 *msg;
+
+ ret = ph->xops->xfer_get_init(ph, POWERCAP_CAP_GET, sizeof(*msg),
+ sizeof(u32), &t);
+ if (ret)
+ return ret;
+
+ msg = t->tx.buf;
+ msg->domain_id = cpu_to_le32(domain_id);
+ msg->cpli = cpu_to_le32(cpl_id);
+
ret = ph->xops->do_xfer(ph, t);
if (!ret)
*power_cap = get_unaligned_le32(t->rx.buf);
@@ -361,6 +582,8 @@ static int __scmi_powercap_cap_get(const struct scmi_protocol_handle *ph,
const struct scmi_powercap_info *dom,
u32 cpl_id, u32 *power_cap)
{
+ struct powercap_info *pi = ph->get_priv(ph);
+
if (dom->cpli[cpl_id].fc_info &&
dom->cpli[cpl_id].fc_info[POWERCAP_FC_CAP].get_addr) {
*power_cap = ioread32(dom->cpli[cpl_id].fc_info[POWERCAP_FC_CAP].get_addr);
@@ -369,7 +592,7 @@ static int __scmi_powercap_cap_get(const struct scmi_protocol_handle *ph,
return 0;
}
- return scmi_powercap_xfer_cap_get(ph, dom->id, power_cap);
+ return pi->xfer_cap_get(ph, dom->id, cpl_id, power_cap);
}
static int scmi_powercap_cap_get(const struct scmi_protocol_handle *ph,
@@ -402,7 +625,7 @@ static int scmi_powercap_xfer_cap_set(const struct scmi_protocol_handle *ph,
return ret;
msg = t->tx.buf;
- msg->domain = cpu_to_le32(pc->id);
+ msg->domain_id = cpu_to_le32(pc->id);
msg->flags =
cpu_to_le32(FIELD_PREP(CAP_SET_ASYNC, pc->async_powercap_cap_set) |
FIELD_PREP(CAP_SET_IGNORE_DRESP, ignore_dresp));
@@ -416,7 +639,7 @@ static int scmi_powercap_xfer_cap_set(const struct scmi_protocol_handle *ph,
struct scmi_msg_resp_powercap_cap_set_complete *resp;
resp = t->rx.buf;
- if (le32_to_cpu(resp->domain) == pc->id)
+ if (le32_to_cpu(resp->domain_id) == pc->id)
dev_dbg(ph->dev,
"Powercap ID %d CAP set async to %u\n",
pc->id,
@@ -430,6 +653,51 @@ static int scmi_powercap_xfer_cap_set(const struct scmi_protocol_handle *ph,
return ret;
}
+static int scmi_powercap_xfer_cap_set_v3(const struct scmi_protocol_handle *ph,
+ const struct scmi_powercap_info *pc,
+ u32 cpl_id, u32 power_cap,
+ bool ignore_dresp)
+{
+ int ret;
+ struct scmi_xfer *t;
+ struct scmi_msg_powercap_set_cap_v3 *msg;
+
+ ret = ph->xops->xfer_get_init(ph, POWERCAP_CAP_SET,
+ sizeof(*msg), 0, &t);
+ if (ret)
+ return ret;
+
+ msg = t->tx.buf;
+ msg->domain_id = cpu_to_le32(pc->id);
+ msg->cpli = cpu_to_le32(cpl_id);
+ msg->flags =
+ cpu_to_le32(FIELD_PREP(CAP_SET_ASYNC, pc->async_powercap_cap_set) |
+ FIELD_PREP(CAP_SET_IGNORE_DRESP, ignore_dresp));
+ msg->power_cap = cpu_to_le32(power_cap);
+
+ if (!pc->async_powercap_cap_set || ignore_dresp) {
+ ret = ph->xops->do_xfer(ph, t);
+ } else {
+ ret = ph->xops->do_xfer_with_response(ph, t);
+ if (!ret) {
+ struct scmi_msg_resp_powercap_cap_set_complete_v3 *resp;
+
+ resp = t->rx.buf;
+ if (le32_to_cpu(resp->domain_id) == pc->id &&
+ le32_to_cpu(resp->cpli) == pc->cpli[cpl_id].id)
+ dev_dbg(ph->dev,
+ "Powercap ID:%d/CPLI:%d CAP set async to %u\n",
+ pc->id, cpl_id,
+ get_unaligned_le32(&resp->power_cap));
+ else
+ ret = -EPROTO;
+ }
+ }
+
+ ph->xops->xfer_put(ph, t);
+ return ret;
+}
+
static int __scmi_powercap_cap_set(const struct scmi_protocol_handle *ph,
struct powercap_info *pi, u32 domain_id,
u32 cpl_id, u32 power_cap, bool ignore_dresp)
@@ -456,12 +724,12 @@ static int __scmi_powercap_cap_set(const struct scmi_protocol_handle *ph,
domain_id, power_cap, 0);
ret = 0;
} else {
- ret = scmi_powercap_xfer_cap_set(ph, pc, cpl_id, power_cap,
- ignore_dresp);
+ ret = pi->xfer_cap_set(ph, pc, cpl_id, power_cap, ignore_dresp);
}
- /* Save the last explicitly set non-zero powercap value */
- if (PROTOCOL_REV_MAJOR(ph->version) >= 0x2 && !ret && power_cap)
+ /* Save the last explicitly set non-zero powercap value for CPL0 */
+ if (PROTOCOL_REV_MAJOR(ph->version) >= 0x2 && !ret &&
+ cpl_id == CPL0 && power_cap)
pi->states[domain_id].last_pcap = power_cap;
return ret;
@@ -480,8 +748,8 @@ static int scmi_powercap_cap_set(const struct scmi_protocol_handle *ph,
if (!power_cap)
return -EINVAL;
- /* Just log the last set request if acting on a disabled domain */
- if (PROTOCOL_REV_MAJOR(ph->version) >= 0x2 &&
+ /* Just log the last set request on CPL0 on a disabled domain */
+ if (PROTOCOL_REV_MAJOR(ph->version) >= 0x2 && cpl_id == CPL0 &&
!pi->states[domain_id].enabled) {
pi->states[domain_id].last_pcap = power_cap;
return 0;
@@ -554,7 +822,7 @@ static int scmi_powercap_xfer_pai_set(const struct scmi_protocol_handle *ph,
return ret;
msg = t->tx.buf;
- msg->domain = cpu_to_le32(domain_id);
+ msg->domain_id = cpu_to_le32(domain_id);
msg->flags = cpu_to_le32(0);
msg->value = cpu_to_le32(pai);
@@ -1013,6 +1281,16 @@ scmi_powercap_protocol_init(const struct scmi_protocol_handle *ph)
if (!pinfo)
return -ENOMEM;
+ ph->set_priv(ph, pinfo);
+
+ if (PROTOCOL_REV_MAJOR(ph->version) < 0x3) {
+ pinfo->xfer_cap_get = scmi_powercap_xfer_cap_get;
+ pinfo->xfer_cap_set = scmi_powercap_xfer_cap_set;
+ } else {
+ pinfo->xfer_cap_get = scmi_powercap_xfer_cap_get_v3;
+ pinfo->xfer_cap_set = scmi_powercap_xfer_cap_set_v3;
+ }
+
ret = scmi_powercap_attributes_get(ph, pinfo);
if (ret)
return ret;
diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h
index 9918fb30100c..547ab4763a63 100644
--- a/include/linux/scmi_protocol.h
+++ b/include/linux/scmi_protocol.h
@@ -673,6 +673,7 @@ struct scmi_powercap_info {
bool powercap_monitoring;
bool powercap_scale_mw;
bool powercap_scale_uw;
+ bool extended_names;
bool fastchannels;
char name[SCMI_MAX_STR_SIZE];
unsigned int sustainable_power;
--
2.47.3
^ permalink raw reply related
* [PATCH v4 04/11] firmware: arm_scmi: Add SCMIv4.0 Powercap FCs support
From: Philip Radford @ 2026-04-01 9:53 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-pm
Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, quic_sibis,
dan.carpenter, d-gole, souvik.chakravarty, Cristian Marussi,
Philip Radford
In-Reply-To: <20260401095349.2217840-1-philip.radford@arm.com>
From: Cristian Marussi <cristian.marussi@arm.com>
Add support for new SCMIv4.0 Powercap Fastchannels.
Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
[Philip: removed reference to old versioning logic]
Signed-off-by: Philip Radford <philip.radford@arm.com>
---
V2->V4
- moved fastchannel inits outside of loop
- renamed arguments for consistency
---
drivers/firmware/arm_scmi/powercap.c | 331 ++++++++++++++++++---------
1 file changed, 229 insertions(+), 102 deletions(-)
diff --git a/drivers/firmware/arm_scmi/powercap.c b/drivers/firmware/arm_scmi/powercap.c
index 3291bde78367..db5bc4f38ea4 100644
--- a/drivers/firmware/arm_scmi/powercap.c
+++ b/drivers/firmware/arm_scmi/powercap.c
@@ -27,19 +27,25 @@ enum scmi_powercap_protocol_cmd {
POWERCAP_CAP_GET = 0x4,
POWERCAP_CAP_SET = 0x5,
POWERCAP_PAI_GET = 0x6,
+ POWERCAP_MAI_GET = POWERCAP_PAI_GET,
POWERCAP_PAI_SET = 0x7,
+ POWERCAP_MAI_SET = POWERCAP_PAI_SET,
POWERCAP_DOMAIN_NAME_GET = 0x8,
POWERCAP_MEASUREMENTS_GET = 0x9,
POWERCAP_CAP_NOTIFY = 0xa,
POWERCAP_MEASUREMENTS_NOTIFY = 0xb,
POWERCAP_DESCRIBE_FASTCHANNEL = 0xc,
POWERCAP_CPC_ATTRIBUTES = 0xd,
+ POWERCAP_CAI_GET = 0xe,
+ POWERCAP_CAI_SET = 0xf,
};
enum {
POWERCAP_FC_CAP,
- POWERCAP_FC_PAI,
- POWERCAP_FC_MAX,
+ POWERCAP_FC_XAI,
+ POWERCAP_FC_MAI,
+ POWERCAP_FC_MEASUREMENT,
+ POWERCAP_FC_MAX
};
struct scmi_msg_resp_powercap_domain_attributes {
@@ -91,12 +97,12 @@ struct scmi_msg_resp_powercap_domain_attributes_v3 {
__le32 cai_step;
};
-struct scmi_msg_powercap_get_v3 {
+struct scmi_msg_powercap_cap_or_cai_get_v3 {
__le32 domain_id;
__le32 cpli;
};
-struct scmi_msg_powercap_set_cap_or_pai {
+struct scmi_msg_powercap_cap_or_pai_set {
__le32 domain_id;
__le32 flags;
#define CAP_SET_ASYNC BIT(1)
@@ -104,13 +110,20 @@ struct scmi_msg_powercap_set_cap_or_pai {
__le32 value;
};
-struct scmi_msg_powercap_set_cap_v3 {
+struct scmi_msg_powercap_cap_set_v3 {
__le32 domain_id;
__le32 cpli;
__le32 flags;
__le32 power_cap;
};
+struct scmi_msg_powercap_cai_set {
+ __le32 domain_id;
+ __le32 flags;
+ __le32 cai;
+ __le32 cpli;
+};
+
struct scmi_msg_resp_powercap_cap_set_complete {
__le32 domain_id;
__le32 power_cap;
@@ -201,6 +214,10 @@ struct powercap_info {
int (*xfer_cap_set)(const struct scmi_protocol_handle *ph,
const struct scmi_powercap_info *pc,
u32 cpl_id, u32 power_cap, bool ignore_dresp);
+ int (*xfer_avg_ivl_get)(const struct scmi_protocol_handle *ph,
+ u32 domain_id, u32 cpl_id, u32 *ivl);
+ int (*xfer_avg_ivl_set)(const struct scmi_protocol_handle *ph,
+ u32 domain_id, u32 cpl_id, u32 ivl);
};
static enum scmi_powercap_protocol_cmd evt_2_cmd[] = {
@@ -501,17 +518,6 @@ scmi_powercap_domain_attributes_get(const struct scmi_protocol_handle *ph,
return ret;
}
-static int
-scmi_powercap_domain_initialize(const struct scmi_protocol_handle *ph,
- struct powercap_info *pinfo, u32 domain)
-{
- struct scmi_powercap_info *dom_info = pinfo->powercaps + domain;
-
- dom_info->id = domain;
-
- return scmi_powercap_domain_attributes_get(ph, pinfo, dom_info);
-}
-
static int scmi_powercap_num_domains_get(const struct scmi_protocol_handle *ph)
{
struct powercap_info *pi = ph->get_priv(ph);
@@ -558,7 +564,7 @@ static int scmi_powercap_xfer_cap_get_v3(const struct scmi_protocol_handle *ph,
{
int ret;
struct scmi_xfer *t;
- struct scmi_msg_powercap_get_v3 *msg;
+ struct scmi_msg_powercap_cap_or_cai_get_v3 *msg;
ret = ph->xops->xfer_get_init(ph, POWERCAP_CAP_GET, sizeof(*msg),
sizeof(u32), &t);
@@ -617,7 +623,7 @@ static int scmi_powercap_xfer_cap_set(const struct scmi_protocol_handle *ph,
{
int ret;
struct scmi_xfer *t;
- struct scmi_msg_powercap_set_cap_or_pai *msg;
+ struct scmi_msg_powercap_cap_or_pai_set *msg;
ret = ph->xops->xfer_get_init(ph, POWERCAP_CAP_SET,
sizeof(*msg), 0, &t);
@@ -660,7 +666,7 @@ static int scmi_powercap_xfer_cap_set_v3(const struct scmi_protocol_handle *ph,
{
int ret;
struct scmi_xfer *t;
- struct scmi_msg_powercap_set_cap_v3 *msg;
+ struct scmi_msg_powercap_cap_set_v3 *msg;
ret = ph->xops->xfer_get_init(ph, POWERCAP_CAP_SET,
sizeof(*msg), 0, &t);
@@ -759,8 +765,9 @@ static int scmi_powercap_cap_set(const struct scmi_protocol_handle *ph,
power_cap, ignore_dresp);
}
-static int scmi_powercap_xfer_pai_get(const struct scmi_protocol_handle *ph,
- u32 domain_id, u32 *pai)
+static int
+scmi_powercap_xfer_avg_interval_get(const struct scmi_protocol_handle *ph,
+ u32 domain_id, u32 cpl_id, u32 *ivl)
{
int ret;
struct scmi_xfer *t;
@@ -773,58 +780,105 @@ static int scmi_powercap_xfer_pai_get(const struct scmi_protocol_handle *ph,
put_unaligned_le32(domain_id, t->tx.buf);
ret = ph->xops->do_xfer(ph, t);
if (!ret)
- *pai = get_unaligned_le32(t->rx.buf);
+ *ivl = get_unaligned_le32(t->rx.buf);
+
+ ph->xops->xfer_put(ph, t);
+
+ return ret;
+}
+
+static int
+scmi_powercap_xfer_avg_interval_get_v3(const struct scmi_protocol_handle *ph,
+ u32 domain_id, u32 cpl_id, u32 *ivl)
+{
+ int ret;
+ struct scmi_xfer *t;
+ struct scmi_msg_powercap_cap_or_cai_get_v3 *msg;
+
+ ret = ph->xops->xfer_get_init(ph, POWERCAP_CAI_GET, sizeof(*msg),
+ sizeof(u32), &t);
+ if (ret)
+ return ret;
+
+ msg = t->tx.buf;
+ msg->domain_id = cpu_to_le32(domain_id);
+ msg->cpli = cpu_to_le32(cpl_id);
+
+ ret = ph->xops->do_xfer(ph, t);
+ if (!ret)
+ *ivl = get_unaligned_le32(t->rx.buf);
ph->xops->xfer_put(ph, t);
return ret;
}
-static int scmi_powercap_pai_get(const struct scmi_protocol_handle *ph,
- u32 domain_id, u32 cpl_id, u32 *pai)
+static int scmi_powercap_avg_interval_get(const struct scmi_protocol_handle *ph,
+ u32 domain_id, u32 cpl_id, u32 *val)
{
struct scmi_powercap_info *dom;
struct powercap_info *pi = ph->get_priv(ph);
- if (!pai || domain_id >= pi->num_domains)
+ if (!val || domain_id >= pi->num_domains)
return -EINVAL;
dom = pi->powercaps + domain_id;
if (cpl_id >= dom->num_cpli)
return -EINVAL;
- if (dom->cpli[cpl_id].fc_info && dom->cpli[cpl_id].fc_info[POWERCAP_FC_PAI].get_addr) {
- *pai = ioread32(dom->cpli[cpl_id].fc_info[POWERCAP_FC_PAI].get_addr);
- trace_scmi_fc_call(SCMI_PROTOCOL_POWERCAP, POWERCAP_PAI_GET,
- domain_id, *pai, 0);
+ if (dom->cpli[cpl_id].fc_info &&
+ dom->cpli[cpl_id].fc_info[POWERCAP_FC_XAI].get_addr) {
+ int trace_cmd = (PROTOCOL_REV_MAJOR(ph->version) < 0x3) ?
+ POWERCAP_PAI_GET : POWERCAP_CAI_GET;
+
+ *val = ioread32(dom->cpli[cpl_id].fc_info[POWERCAP_FC_XAI].get_addr);
+ trace_scmi_fc_call(SCMI_PROTOCOL_POWERCAP, trace_cmd, domain_id, *val, 0);
return 0;
}
- return scmi_powercap_xfer_pai_get(ph, domain_id, pai);
+ return pi->xfer_avg_ivl_get(ph, domain_id, cpl_id, val);
}
-static int scmi_powercap_avg_interval_get(const struct scmi_protocol_handle *ph,
- u32 domain_id, u32 cpl_id, u32 *val)
+static int
+scmi_powercap_xfer_avg_interval_set(const struct scmi_protocol_handle *ph,
+ u32 domain_id, u32 cpl_id, u32 ivl)
{
- return scmi_powercap_pai_get(ph, domain_id, cpl_id, val);
+ int ret;
+ struct scmi_xfer *t;
+ struct scmi_msg_powercap_cap_or_pai_set *msg;
+
+ ret = ph->xops->xfer_get_init(ph, POWERCAP_PAI_SET, sizeof(*msg), 0, &t);
+ if (ret)
+ return ret;
+
+ msg = t->tx.buf;
+ msg->domain_id = cpu_to_le32(domain_id);
+ msg->flags = cpu_to_le32(0);
+ msg->value = cpu_to_le32(ivl);
+
+ ret = ph->xops->do_xfer(ph, t);
+
+ ph->xops->xfer_put(ph, t);
+ return ret;
}
-static int scmi_powercap_xfer_pai_set(const struct scmi_protocol_handle *ph,
- u32 domain_id, u32 pai)
+static int
+scmi_powercap_xfer_avg_interval_set_v3(const struct scmi_protocol_handle *ph,
+ u32 domain_id, u32 cpl_id, u32 ivl)
{
int ret;
struct scmi_xfer *t;
- struct scmi_msg_powercap_set_cap_or_pai *msg;
+ struct scmi_msg_powercap_cai_set *msg;
- ret = ph->xops->xfer_get_init(ph, POWERCAP_PAI_SET,
- sizeof(*msg), 0, &t);
+ ret = ph->xops->xfer_get_init(ph, POWERCAP_CAI_SET, sizeof(*msg), 0, &t);
if (ret)
return ret;
msg = t->tx.buf;
msg->domain_id = cpu_to_le32(domain_id);
msg->flags = cpu_to_le32(0);
- msg->value = cpu_to_le32(pai);
+ msg->cai = cpu_to_le32(ivl);
+ msg->cpli = cpu_to_le32(cpl_id);
ret = ph->xops->do_xfer(ph, t);
@@ -832,48 +886,42 @@ static int scmi_powercap_xfer_pai_set(const struct scmi_protocol_handle *ph,
return ret;
}
-static int scmi_powercap_pai_set(const struct scmi_protocol_handle *ph,
- u32 domain_id, u32 cpl_id, u32 pai)
+static int scmi_powercap_avg_interval_set(const struct scmi_protocol_handle *ph,
+ u32 domain_id, u32 cpl_id, u32 ivl)
{
const struct scmi_powercap_info *pc;
+ struct powercap_info *pi = ph->get_priv(ph);
pc = scmi_powercap_dom_info_get(ph, domain_id);
if (!pc || cpl_id >= pc->num_cpli || !pc->cpli[cpl_id].avg_ivl_config ||
- !pai || pai < pc->cpli[cpl_id].min_avg_ivl ||
- pai > pc->cpli[cpl_id].max_avg_ivl)
+ !ivl || ivl < pc->cpli[cpl_id].min_avg_ivl ||
+ ivl > pc->cpli[cpl_id].max_avg_ivl)
return -EINVAL;
- if (pc->cpli[cpl_id].fc_info && pc->cpli[cpl_id].fc_info[POWERCAP_FC_PAI].set_addr) {
- struct scmi_fc_info *fci = &pc->cpli[cpl_id].fc_info[POWERCAP_FC_PAI];
+ /* Note that fc_info descriptors for any unsupported FC will be NULL */
+ if (pc->cpli[cpl_id].fc_info &&
+ pc->cpli[cpl_id].fc_info[POWERCAP_FC_XAI].set_addr) {
+ int trace_cmd = (PROTOCOL_REV_MAJOR(ph->version) < 0x3) ?
+ POWERCAP_PAI_SET : POWERCAP_CAI_SET;
+ struct scmi_fc_info *fci = &pc->cpli[cpl_id].fc_info[POWERCAP_FC_XAI];
- trace_scmi_fc_call(SCMI_PROTOCOL_POWERCAP, POWERCAP_PAI_SET,
- domain_id, pai, 0);
- iowrite32(pai, fci->set_addr);
+ trace_scmi_fc_call(SCMI_PROTOCOL_POWERCAP, trace_cmd, domain_id, ivl, 0);
+ iowrite32(ivl, fci->set_addr);
ph->hops->fastchannel_db_ring(fci->set_db);
return 0;
}
- return scmi_powercap_xfer_pai_set(ph, domain_id, pai);
-}
-
-static int scmi_powercap_avg_interval_set(const struct scmi_protocol_handle *ph,
- u32 domain_id, u32 cpl_id, u32 val)
-{
- return scmi_powercap_pai_set(ph, domain_id, cpl_id, val);
+ return pi->xfer_avg_ivl_set(ph, domain_id, cpl_id, ivl);
}
-static int scmi_powercap_measurements_get(const struct scmi_protocol_handle *ph,
- u32 domain_id, u32 *average_power,
- u32 *pai)
+static int
+scmi_powercap_xfer_measurements_get(const struct scmi_protocol_handle *ph,
+ const struct scmi_powercap_info *pc,
+ u32 *avg_power, u32 *avg_ivl)
{
int ret;
struct scmi_xfer *t;
struct scmi_msg_resp_powercap_meas_get *resp;
- const struct scmi_powercap_info *pc;
-
- pc = scmi_powercap_dom_info_get(ph, domain_id);
- if (!pc || !pc->powercap_monitoring || !pai || !average_power)
- return -EINVAL;
ret = ph->xops->xfer_get_init(ph, POWERCAP_MEASUREMENTS_GET,
sizeof(u32), sizeof(*resp), &t);
@@ -881,17 +929,42 @@ static int scmi_powercap_measurements_get(const struct scmi_protocol_handle *ph,
return ret;
resp = t->rx.buf;
- put_unaligned_le32(domain_id, t->tx.buf);
+ put_unaligned_le32(pc->id, t->tx.buf);
ret = ph->xops->do_xfer(ph, t);
if (!ret) {
- *average_power = le32_to_cpu(resp->power);
- *pai = le32_to_cpu(resp->pai);
+ *avg_power = le32_to_cpu(resp->power);
+ *avg_ivl = le32_to_cpu(resp->pai);
}
ph->xops->xfer_put(ph, t);
return ret;
}
+static int scmi_powercap_measurements_get(const struct scmi_protocol_handle *ph,
+ u32 domain_id, u32 *avg_power,
+ u32 *avg_ivl)
+{
+ const struct scmi_powercap_info *pc;
+ struct scmi_fc_info *fci;
+
+ pc = scmi_powercap_dom_info_get(ph, domain_id);
+ if (!pc || !pc->powercap_monitoring || !avg_ivl || !avg_power)
+ return -EINVAL;
+
+ /* Note that fc_info descriptors for any unsupported FC will be NULL */
+ fci = pc->cpli[CPL0].fc_info;
+ if (fci && fci[POWERCAP_FC_MEASUREMENT].get_addr) {
+ *avg_power = ioread32(fci[POWERCAP_FC_MEASUREMENT].get_addr);
+ /* See SCMIv4.0 3.10.2 - Payload is 32bit ONLY avg_power */
+ *avg_ivl = 0;
+ trace_scmi_fc_call(SCMI_PROTOCOL_POWERCAP, POWERCAP_MEASUREMENTS_GET,
+ pc->id, *avg_power, *avg_ivl);
+ return 0;
+ }
+
+ return scmi_powercap_xfer_measurements_get(ph, pc, avg_power, avg_ivl);
+}
+
static int
scmi_powercap_measurements_threshold_get(const struct scmi_protocol_handle *ph,
u32 domain_id, u32 *power_thresh_low,
@@ -1019,37 +1092,85 @@ static const struct scmi_powercap_proto_ops powercap_proto_ops = {
};
static void scmi_powercap_domain_init_fc(const struct scmi_protocol_handle *ph,
- u32 domain, struct scmi_fc_info **p_fc)
+ struct scmi_powercap_info *dom_info)
{
- struct scmi_fc_info *fc;
-
- fc = devm_kcalloc(ph->dev, POWERCAP_FC_MAX, sizeof(*fc), GFP_KERNEL);
- if (!fc)
- return;
-
- ph->hops->fastchannel_init(ph, POWERCAP_DESCRIBE_FASTCHANNEL,
- POWERCAP_CAP_SET, 4, domain, NULL,
- &fc[POWERCAP_FC_CAP].set_addr,
- &fc[POWERCAP_FC_CAP].set_db,
- &fc[POWERCAP_FC_CAP].rate_limit);
-
- ph->hops->fastchannel_init(ph, POWERCAP_DESCRIBE_FASTCHANNEL,
- POWERCAP_CAP_GET, 4, domain, NULL,
- &fc[POWERCAP_FC_CAP].get_addr, NULL,
- &fc[POWERCAP_FC_CAP].rate_limit);
-
- ph->hops->fastchannel_init(ph, POWERCAP_DESCRIBE_FASTCHANNEL,
- POWERCAP_PAI_SET, 4, domain, NULL,
- &fc[POWERCAP_FC_PAI].set_addr,
- &fc[POWERCAP_FC_PAI].set_db,
- &fc[POWERCAP_FC_PAI].rate_limit);
-
- ph->hops->fastchannel_init(ph, POWERCAP_DESCRIBE_FASTCHANNEL,
- POWERCAP_PAI_GET, 4, domain, NULL,
- &fc[POWERCAP_FC_PAI].get_addr, NULL,
- &fc[POWERCAP_FC_PAI].rate_limit);
-
- *p_fc = fc;
+ struct scmi_fc_info *fc_cpl0;
+
+ for (int id = 0; id < dom_info->num_cpli; id++) {
+ struct scmi_fc_info *fc;
+ u32 *cpl_id, zero_cpl_id = 0;
+
+ fc = devm_kcalloc(ph->dev, POWERCAP_FC_MAX, sizeof(*fc), GFP_KERNEL);
+ if (!fc)
+ return;
+
+ /* NOTE THAT when num_cpli == 1 the arg *cpl_id is 0 */
+ cpl_id = (PROTOCOL_REV_MAJOR(ph->version) >= 0x3) ? &id : NULL;
+
+ ph->hops->fastchannel_init(ph, POWERCAP_DESCRIBE_FASTCHANNEL,
+ POWERCAP_CAP_SET, 4, dom_info->id,
+ cpl_id,
+ &fc[POWERCAP_FC_CAP].set_addr,
+ &fc[POWERCAP_FC_CAP].set_db,
+ &fc[POWERCAP_FC_CAP].rate_limit);
+
+ ph->hops->fastchannel_init(ph, POWERCAP_DESCRIBE_FASTCHANNEL,
+ POWERCAP_CAP_GET, 4, dom_info->id,
+ cpl_id,
+ &fc[POWERCAP_FC_CAP].get_addr, NULL,
+ &fc[POWERCAP_FC_CAP].rate_limit);
+
+ if (PROTOCOL_REV_MAJOR(ph->version) >= 0x3) {
+ ph->hops->fastchannel_init(ph, POWERCAP_DESCRIBE_FASTCHANNEL,
+ POWERCAP_CAI_SET, 4,
+ dom_info->id, cpl_id,
+ &fc[POWERCAP_FC_XAI].set_addr,
+ &fc[POWERCAP_FC_XAI].set_db,
+ &fc[POWERCAP_FC_XAI].rate_limit);
+ ph->hops->fastchannel_init(ph, POWERCAP_DESCRIBE_FASTCHANNEL,
+ POWERCAP_CAI_GET, 4,
+ dom_info->id, cpl_id,
+ &fc[POWERCAP_FC_XAI].get_addr, NULL,
+ &fc[POWERCAP_FC_XAI].rate_limit);
+ ph->hops->fastchannel_init(ph, POWERCAP_DESCRIBE_FASTCHANNEL,
+ POWERCAP_MAI_SET, 4,
+ dom_info->id, &zero_cpl_id,
+ &fc[POWERCAP_FC_MAI].set_addr,
+ &fc[POWERCAP_FC_MAI].set_db,
+ &fc[POWERCAP_FC_MAI].rate_limit);
+
+ ph->hops->fastchannel_init(ph, POWERCAP_DESCRIBE_FASTCHANNEL,
+ POWERCAP_MAI_GET, 4,
+ dom_info->id, &zero_cpl_id,
+ &fc[POWERCAP_FC_MAI].get_addr, NULL,
+ &fc[POWERCAP_FC_MAI].rate_limit);
+
+ ph->hops->fastchannel_init(ph, POWERCAP_DESCRIBE_FASTCHANNEL,
+ POWERCAP_MEASUREMENTS_GET, 4,
+ dom_info->id, &zero_cpl_id,
+ &fc[POWERCAP_FC_MEASUREMENT].get_addr, NULL,
+ &fc[POWERCAP_FC_MEASUREMENT].rate_limit);
+ }
+
+ dom_info->cpli[id].fc_info = fc;
+ }
+
+ if (PROTOCOL_REV_MAJOR(ph->version) < 0x3) {
+ fc_cpl0 = dom_info->cpli[CPL0].fc_info;
+ ph->hops->fastchannel_init(ph, POWERCAP_DESCRIBE_FASTCHANNEL,
+ POWERCAP_PAI_SET, 4,
+ dom_info->id, NULL,
+ &fc_cpl0[POWERCAP_FC_XAI].set_addr,
+ &fc_cpl0[POWERCAP_FC_XAI].set_db,
+ &fc_cpl0[POWERCAP_FC_XAI].rate_limit);
+
+ ph->hops->fastchannel_init(ph, POWERCAP_DESCRIBE_FASTCHANNEL,
+ POWERCAP_PAI_GET, 4,
+ dom_info->id, NULL,
+ &fc_cpl0[POWERCAP_FC_XAI].get_addr, NULL,
+ &fc_cpl0[POWERCAP_FC_XAI].rate_limit);
+ }
+
}
static int scmi_powercap_notify(const struct scmi_protocol_handle *ph,
@@ -1286,9 +1407,14 @@ scmi_powercap_protocol_init(const struct scmi_protocol_handle *ph)
if (PROTOCOL_REV_MAJOR(ph->version) < 0x3) {
pinfo->xfer_cap_get = scmi_powercap_xfer_cap_get;
pinfo->xfer_cap_set = scmi_powercap_xfer_cap_set;
+ pinfo->xfer_avg_ivl_get = scmi_powercap_xfer_avg_interval_get;
+ pinfo->xfer_avg_ivl_set = scmi_powercap_xfer_avg_interval_set;
+
} else {
pinfo->xfer_cap_get = scmi_powercap_xfer_cap_get_v3;
pinfo->xfer_cap_set = scmi_powercap_xfer_cap_set_v3;
+ pinfo->xfer_avg_ivl_get = scmi_powercap_xfer_avg_interval_get_v3;
+ pinfo->xfer_avg_ivl_set = scmi_powercap_xfer_avg_interval_set_v3;
}
ret = scmi_powercap_attributes_get(ph, pinfo);
@@ -1313,18 +1439,19 @@ scmi_powercap_protocol_init(const struct scmi_protocol_handle *ph)
* formed and correlated by sane parent-child relationship (if any).
*/
for (domain = 0; domain < pinfo->num_domains; domain++) {
- ret = scmi_powercap_domain_initialize(ph, pinfo, domain);
+ struct scmi_powercap_info *dom_info = pinfo->powercaps + domain;
+
+ dom_info->id = domain;
+ ret = scmi_powercap_domain_attributes_get(ph, pinfo, dom_info);
if (ret)
return ret;
- if (pinfo->powercaps[domain].fastchannels)
- scmi_powercap_domain_init_fc(ph, domain,
- &pinfo->powercaps[domain].cpli[CPL0].fc_info);
+ if (dom_info->fastchannels)
+ scmi_powercap_domain_init_fc(ph, dom_info);
/* Grab initial state when disable is supported. */
if (PROTOCOL_REV_MAJOR(ph->version) >= 0x2) {
- ret = __scmi_powercap_cap_get(ph,
- &pinfo->powercaps[domain], CPL0,
+ ret = __scmi_powercap_cap_get(ph, dom_info, CPL0,
&pinfo->states[domain].last_pcap);
if (ret)
return ret;
--
2.47.3
^ permalink raw reply related
* [PATCH v4 05/11] firmware: arm_scmi: Add SCMIV4.0 Powercap notifications support
From: Philip Radford @ 2026-04-01 9:53 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-pm
Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, quic_sibis,
dan.carpenter, d-gole, souvik.chakravarty, Cristian Marussi,
Philip Radford
In-Reply-To: <20260401095349.2217840-1-philip.radford@arm.com>
From: Cristian Marussi <cristian.marussi@arm.com>
Extend notification support to the new SCMIv4.0 Powercap format that carry
also a CPL identifier where specified.
Since this addition completes SCMIv4.0 Powercap support bump also the
protocol version define.
Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
Signed-off-by: Philip Radford <philip.radford@arm.com>
---
drivers/firmware/arm_scmi/powercap.c | 13 +++++++++----
include/linux/scmi_protocol.h | 3 ++-
2 files changed, 11 insertions(+), 5 deletions(-)
diff --git a/drivers/firmware/arm_scmi/powercap.c b/drivers/firmware/arm_scmi/powercap.c
index db5bc4f38ea4..1d1188e98d49 100644
--- a/drivers/firmware/arm_scmi/powercap.c
+++ b/drivers/firmware/arm_scmi/powercap.c
@@ -18,7 +18,7 @@
#include "notify.h"
/* Updated only after ALL the mandatory features for that version are merged */
-#define SCMI_PROTOCOL_SUPPORTED_VERSION 0x20000
+#define SCMI_PROTOCOL_SUPPORTED_VERSION 0x30000
#define CPL0 0
@@ -156,7 +156,8 @@ struct scmi_powercap_cap_changed_notify_payld {
__le32 agent_id;
__le32 domain_id;
__le32 power_cap;
- __le32 pai;
+ __le32 avg_ivl;
+ __le32 cpli;
};
struct scmi_powercap_meas_changed_notify_payld {
@@ -1312,14 +1313,18 @@ scmi_powercap_fill_custom_report(const struct scmi_protocol_handle *ph,
const struct scmi_powercap_cap_changed_notify_payld *p = payld;
struct scmi_powercap_cap_changed_report *r = report;
- if (sizeof(*p) != payld_sz)
+ if (sizeof(*p) > payld_sz)
break;
r->timestamp = timestamp;
r->agent_id = le32_to_cpu(p->agent_id);
r->domain_id = le32_to_cpu(p->domain_id);
r->power_cap = le32_to_cpu(p->power_cap);
- r->pai = le32_to_cpu(p->pai);
+ r->avg_ivl = le32_to_cpu(p->avg_ivl);
+ if (sizeof(*p) == payld_sz)
+ r->cpli = le32_to_cpu(p->cpli);
+ else
+ r->cpli = 0;
*src_id = r->domain_id;
rep = r;
break;
diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h
index 547ab4763a63..299fa8499b3f 100644
--- a/include/linux/scmi_protocol.h
+++ b/include/linux/scmi_protocol.h
@@ -1125,7 +1125,8 @@ struct scmi_powercap_cap_changed_report {
unsigned int agent_id;
unsigned int domain_id;
unsigned int power_cap;
- unsigned int pai;
+ unsigned int avg_ivl;
+ unsigned int cpli;
};
struct scmi_powercap_meas_changed_report {
--
2.47.3
^ permalink raw reply related
* [PATCH v4 02/11] firmware: arm_scmi: Refactor powercap domain layout
From: Philip Radford @ 2026-04-01 9:53 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-pm
Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, quic_sibis,
dan.carpenter, d-gole, souvik.chakravarty, Cristian Marussi,
Philip Radford
In-Reply-To: <20260401095349.2217840-1-philip.radford@arm.com>
From: Cristian Marussi <cristian.marussi@arm.com>
SCMIv4.0 introduces the idea of an optional Concurrent Power Limit (CPL)
for each powercap domain, where CPL0 coincides with the one and only
per-domain constraint limit that was available in pre-v4.0 SCMI Powercap.
Refactor the powercap domain descriptors and powercap operations to allow
future v4.0 extensions to cope with multiple CPLs.
While at that generalize the powercap protocol API to drop PAI references
in favour of a more generic avg_ivl naming, since from v4.0 the number and
types of averaging intervals will change in a non-backward compatible way,
so let's bury these changes within the protocol layer.
Last but not least, make the necessary changes to the ARM SCMI Powwercap
driver in order to support all of these new capabilities.
No functional change.
Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
[Philip: Adjusted domain_id comparitor in scmi_powercap_pai_get]
Signed-off-by: Philip Radford <philip.radford@arm.com>
---
drivers/firmware/arm_scmi/powercap.c | 182 +++++++++++++++++----------
drivers/powercap/arm_scmi_powercap.c | 50 ++++----
include/linux/scmi_protocol.h | 74 +++++++----
3 files changed, 188 insertions(+), 118 deletions(-)
diff --git a/drivers/firmware/arm_scmi/powercap.c b/drivers/firmware/arm_scmi/powercap.c
index 22aff71c75e9..47aa6dde4a52 100644
--- a/drivers/firmware/arm_scmi/powercap.c
+++ b/drivers/firmware/arm_scmi/powercap.c
@@ -2,7 +2,7 @@
/*
* System Control and Management Interface (SCMI) Powercap Protocol
*
- * Copyright (C) 2022 ARM Ltd.
+ * Copyright (C) 2022-2026 ARM Ltd.
*/
#define pr_fmt(fmt) "SCMI Notifications POWERCAP - " fmt
@@ -20,6 +20,8 @@
/* Updated only after ALL the mandatory features for that version are merged */
#define SCMI_PROTOCOL_SUPPORTED_VERSION 0x20000
+#define CPL0 0
+
enum scmi_powercap_protocol_cmd {
POWERCAP_DOMAIN_ATTRIBUTES = 0x3,
POWERCAP_CAP_GET = 0x4,
@@ -192,27 +194,26 @@ scmi_powercap_validate(unsigned int min_val, unsigned int max_val,
static int
scmi_powercap_domain_attributes_get(const struct scmi_protocol_handle *ph,
- struct powercap_info *pinfo, u32 domain)
+ struct powercap_info *pinfo,
+ struct scmi_powercap_info *dom_info)
{
int ret;
u32 flags;
struct scmi_xfer *t;
- struct scmi_powercap_info *dom_info = pinfo->powercaps + domain;
struct scmi_msg_resp_powercap_domain_attributes *resp;
ret = ph->xops->xfer_get_init(ph, POWERCAP_DOMAIN_ATTRIBUTES,
- sizeof(domain), sizeof(*resp), &t);
+ sizeof(dom_info->id), sizeof(*resp), &t);
if (ret)
return ret;
- put_unaligned_le32(domain, t->tx.buf);
+ put_unaligned_le32(dom_info->id, t->tx.buf);
resp = t->rx.buf;
ret = ph->xops->do_xfer(ph, t);
if (!ret) {
flags = le32_to_cpu(resp->attributes);
- dom_info->id = domain;
if (pinfo->notify_cap_cmd)
dom_info->notify_powercap_cap_change =
SUPPORTS_POWERCAP_CAP_CHANGE_NOTIFY(flags);
@@ -221,12 +222,9 @@ scmi_powercap_domain_attributes_get(const struct scmi_protocol_handle *ph,
SUPPORTS_POWERCAP_MEASUREMENTS_CHANGE_NOTIFY(flags);
dom_info->async_powercap_cap_set =
SUPPORTS_ASYNC_POWERCAP_CAP_SET(flags);
- dom_info->powercap_cap_config =
- SUPPORTS_POWERCAP_CAP_CONFIGURATION(flags);
+
dom_info->powercap_monitoring =
SUPPORTS_POWERCAP_MONITORING(flags);
- dom_info->powercap_pai_config =
- SUPPORTS_POWERCAP_PAI_CONFIGURATION(flags);
dom_info->powercap_scale_mw =
SUPPORTS_POWER_UNITS_MW(flags);
dom_info->powercap_scale_uw =
@@ -236,13 +234,29 @@ scmi_powercap_domain_attributes_get(const struct scmi_protocol_handle *ph,
strscpy(dom_info->name, resp->name, SCMI_SHORT_NAME_MAX_SIZE);
- dom_info->min_pai = le32_to_cpu(resp->min_pai);
- dom_info->max_pai = le32_to_cpu(resp->max_pai);
- dom_info->pai_step = le32_to_cpu(resp->pai_step);
- ret = scmi_powercap_validate(dom_info->min_pai,
- dom_info->max_pai,
- dom_info->pai_step,
- dom_info->powercap_pai_config);
+ dom_info->sustainable_power =
+ le32_to_cpu(resp->sustainable_power);
+ dom_info->accuracy = le32_to_cpu(resp->accuracy);
+
+ dom_info->parent_id = le32_to_cpu(resp->parent_id);
+ if (dom_info->parent_id != SCMI_POWERCAP_ROOT_ZONE_ID &&
+ (dom_info->parent_id >= pinfo->num_domains ||
+ dom_info->parent_id == dom_info->id)) {
+ dev_err(ph->dev,
+ "Platform reported inconsistent parent ID for domain %d - %s\n",
+ dom_info->id, dom_info->name);
+ ret = -ENODEV;
+ }
+
+ dom_info->cpli[0].avg_ivl_config =
+ SUPPORTS_POWERCAP_PAI_CONFIGURATION(flags);
+ dom_info->cpli[0].min_avg_ivl = le32_to_cpu(resp->min_pai);
+ dom_info->cpli[0].max_avg_ivl = le32_to_cpu(resp->max_pai);
+ dom_info->cpli[0].avg_ivl_step = le32_to_cpu(resp->pai_step);
+ ret = scmi_powercap_validate(dom_info->cpli[0].min_avg_ivl,
+ dom_info->cpli[0].max_avg_ivl,
+ dom_info->cpli[0].avg_ivl_step,
+ dom_info->cpli[0].avg_ivl_config);
if (ret) {
dev_err(ph->dev,
"Platform reported inconsistent PAI config for domain %d - %s\n",
@@ -250,13 +264,15 @@ scmi_powercap_domain_attributes_get(const struct scmi_protocol_handle *ph,
goto clean;
}
- dom_info->min_power_cap = le32_to_cpu(resp->min_power_cap);
- dom_info->max_power_cap = le32_to_cpu(resp->max_power_cap);
- dom_info->power_cap_step = le32_to_cpu(resp->power_cap_step);
- ret = scmi_powercap_validate(dom_info->min_power_cap,
- dom_info->max_power_cap,
- dom_info->power_cap_step,
- dom_info->powercap_cap_config);
+ dom_info->cpli[0].cap_config =
+ SUPPORTS_POWERCAP_CAP_CONFIGURATION(flags);
+ dom_info->cpli[0].min_power_cap = le32_to_cpu(resp->min_power_cap);
+ dom_info->cpli[0].max_power_cap = le32_to_cpu(resp->max_power_cap);
+ dom_info->cpli[0].power_cap_step = le32_to_cpu(resp->power_cap_step);
+ ret = scmi_powercap_validate(dom_info->cpli[0].min_power_cap,
+ dom_info->cpli[0].max_power_cap,
+ dom_info->cpli[0].power_cap_step,
+ dom_info->cpli[0].cap_config);
if (ret) {
dev_err(ph->dev,
"Platform reported inconsistent CAP config for domain %d - %s\n",
@@ -264,19 +280,9 @@ scmi_powercap_domain_attributes_get(const struct scmi_protocol_handle *ph,
goto clean;
}
- dom_info->sustainable_power =
- le32_to_cpu(resp->sustainable_power);
- dom_info->accuracy = le32_to_cpu(resp->accuracy);
-
- dom_info->parent_id = le32_to_cpu(resp->parent_id);
- if (dom_info->parent_id != SCMI_POWERCAP_ROOT_ZONE_ID &&
- (dom_info->parent_id >= pinfo->num_domains ||
- dom_info->parent_id == dom_info->id)) {
- dev_err(ph->dev,
- "Platform reported inconsistent parent ID for domain %d - %s\n",
- dom_info->id, dom_info->name);
- ret = -ENODEV;
- }
+ /* Just using same short name */
+ strscpy(dom_info->cpli[0].name, dom_info->name,
+ SCMI_SHORT_NAME_MAX_SIZE);
}
clean:
@@ -288,12 +294,30 @@ scmi_powercap_domain_attributes_get(const struct scmi_protocol_handle *ph,
*/
if (!ret && SUPPORTS_EXTENDED_NAMES(flags))
ph->hops->extended_name_get(ph, POWERCAP_DOMAIN_NAME_GET,
- domain, NULL, dom_info->name,
+ dom_info->id, NULL, dom_info->name,
SCMI_MAX_STR_SIZE);
return ret;
}
+static int
+scmi_powercap_domain_initialize(const struct scmi_protocol_handle *ph,
+ struct powercap_info *pinfo, u32 domain)
+{
+ struct scmi_powercap_info *dom_info = pinfo->powercaps + domain;
+
+ dom_info->num_cpli = 1;
+ dom_info->cpli = devm_kcalloc(ph->dev, dom_info->num_cpli,
+ sizeof(*dom_info->cpli), GFP_KERNEL);
+ if (!dom_info->cpli)
+ return -ENOMEM;
+
+ dom_info->id = domain;
+ dom_info->cpli[0].id = CPL0;
+
+ return scmi_powercap_domain_attributes_get(ph, pinfo, dom_info);
+}
+
static int scmi_powercap_num_domains_get(const struct scmi_protocol_handle *ph)
{
struct powercap_info *pi = ph->get_priv(ph);
@@ -335,10 +359,11 @@ static int scmi_powercap_xfer_cap_get(const struct scmi_protocol_handle *ph,
static int __scmi_powercap_cap_get(const struct scmi_protocol_handle *ph,
const struct scmi_powercap_info *dom,
- u32 *power_cap)
+ u32 cpl_id, u32 *power_cap)
{
- if (dom->fc_info && dom->fc_info[POWERCAP_FC_CAP].get_addr) {
- *power_cap = ioread32(dom->fc_info[POWERCAP_FC_CAP].get_addr);
+ if (dom->cpli[cpl_id].fc_info &&
+ dom->cpli[cpl_id].fc_info[POWERCAP_FC_CAP].get_addr) {
+ *power_cap = ioread32(dom->cpli[cpl_id].fc_info[POWERCAP_FC_CAP].get_addr);
trace_scmi_fc_call(SCMI_PROTOCOL_POWERCAP, POWERCAP_CAP_GET,
dom->id, *power_cap, 0);
return 0;
@@ -348,7 +373,7 @@ static int __scmi_powercap_cap_get(const struct scmi_protocol_handle *ph,
}
static int scmi_powercap_cap_get(const struct scmi_protocol_handle *ph,
- u32 domain_id, u32 *power_cap)
+ u32 domain_id, u32 cpl_id, u32 *power_cap)
{
const struct scmi_powercap_info *dom;
@@ -359,12 +384,13 @@ static int scmi_powercap_cap_get(const struct scmi_protocol_handle *ph,
if (!dom)
return -EINVAL;
- return __scmi_powercap_cap_get(ph, dom, power_cap);
+ return __scmi_powercap_cap_get(ph, dom, cpl_id, power_cap);
}
static int scmi_powercap_xfer_cap_set(const struct scmi_protocol_handle *ph,
const struct scmi_powercap_info *pc,
- u32 power_cap, bool ignore_dresp)
+ u32 cpl_id, u32 power_cap,
+ bool ignore_dresp)
{
int ret;
struct scmi_xfer *t;
@@ -406,21 +432,23 @@ static int scmi_powercap_xfer_cap_set(const struct scmi_protocol_handle *ph,
static int __scmi_powercap_cap_set(const struct scmi_protocol_handle *ph,
struct powercap_info *pi, u32 domain_id,
- u32 power_cap, bool ignore_dresp)
+ u32 cpl_id, u32 power_cap, bool ignore_dresp)
{
int ret = -EINVAL;
const struct scmi_powercap_info *pc;
pc = scmi_powercap_dom_info_get(ph, domain_id);
- if (!pc || !pc->powercap_cap_config)
+ if (!pc || !pc->cpli[cpl_id].cap_config)
return ret;
if (power_cap &&
- (power_cap < pc->min_power_cap || power_cap > pc->max_power_cap))
+ (power_cap < pc->cpli[cpl_id].min_power_cap ||
+ power_cap > pc->cpli[cpl_id].max_power_cap))
return ret;
- if (pc->fc_info && pc->fc_info[POWERCAP_FC_CAP].set_addr) {
- struct scmi_fc_info *fci = &pc->fc_info[POWERCAP_FC_CAP];
+ if (pc->cpli[cpl_id].fc_info &&
+ pc->cpli[cpl_id].fc_info[POWERCAP_FC_CAP].set_addr) {
+ struct scmi_fc_info *fci = &pc->cpli[cpl_id].fc_info[POWERCAP_FC_CAP];
iowrite32(power_cap, fci->set_addr);
ph->hops->fastchannel_db_ring(fci->set_db);
@@ -428,7 +456,7 @@ static int __scmi_powercap_cap_set(const struct scmi_protocol_handle *ph,
domain_id, power_cap, 0);
ret = 0;
} else {
- ret = scmi_powercap_xfer_cap_set(ph, pc, power_cap,
+ ret = scmi_powercap_xfer_cap_set(ph, pc, cpl_id, power_cap,
ignore_dresp);
}
@@ -440,7 +468,7 @@ static int __scmi_powercap_cap_set(const struct scmi_protocol_handle *ph,
}
static int scmi_powercap_cap_set(const struct scmi_protocol_handle *ph,
- u32 domain_id, u32 power_cap,
+ u32 domain_id, u32 cpl_id, u32 power_cap,
bool ignore_dresp)
{
struct powercap_info *pi = ph->get_priv(ph);
@@ -459,7 +487,7 @@ static int scmi_powercap_cap_set(const struct scmi_protocol_handle *ph,
return 0;
}
- return __scmi_powercap_cap_set(ph, pi, domain_id,
+ return __scmi_powercap_cap_set(ph, pi, domain_id, cpl_id,
power_cap, ignore_dresp);
}
@@ -485,7 +513,7 @@ static int scmi_powercap_xfer_pai_get(const struct scmi_protocol_handle *ph,
}
static int scmi_powercap_pai_get(const struct scmi_protocol_handle *ph,
- u32 domain_id, u32 *pai)
+ u32 domain_id, u32 cpl_id, u32 *pai)
{
struct scmi_powercap_info *dom;
struct powercap_info *pi = ph->get_priv(ph);
@@ -494,8 +522,11 @@ static int scmi_powercap_pai_get(const struct scmi_protocol_handle *ph,
return -EINVAL;
dom = pi->powercaps + domain_id;
- if (dom->fc_info && dom->fc_info[POWERCAP_FC_PAI].get_addr) {
- *pai = ioread32(dom->fc_info[POWERCAP_FC_PAI].get_addr);
+ if (cpl_id >= dom->num_cpli)
+ return -EINVAL;
+
+ if (dom->cpli[cpl_id].fc_info && dom->cpli[cpl_id].fc_info[POWERCAP_FC_PAI].get_addr) {
+ *pai = ioread32(dom->cpli[cpl_id].fc_info[POWERCAP_FC_PAI].get_addr);
trace_scmi_fc_call(SCMI_PROTOCOL_POWERCAP, POWERCAP_PAI_GET,
domain_id, *pai, 0);
return 0;
@@ -504,6 +535,12 @@ static int scmi_powercap_pai_get(const struct scmi_protocol_handle *ph,
return scmi_powercap_xfer_pai_get(ph, domain_id, pai);
}
+static int scmi_powercap_avg_interval_get(const struct scmi_protocol_handle *ph,
+ u32 domain_id, u32 cpl_id, u32 *val)
+{
+ return scmi_powercap_pai_get(ph, domain_id, cpl_id, val);
+}
+
static int scmi_powercap_xfer_pai_set(const struct scmi_protocol_handle *ph,
u32 domain_id, u32 pai)
{
@@ -528,17 +565,18 @@ static int scmi_powercap_xfer_pai_set(const struct scmi_protocol_handle *ph,
}
static int scmi_powercap_pai_set(const struct scmi_protocol_handle *ph,
- u32 domain_id, u32 pai)
+ u32 domain_id, u32 cpl_id, u32 pai)
{
const struct scmi_powercap_info *pc;
pc = scmi_powercap_dom_info_get(ph, domain_id);
- if (!pc || !pc->powercap_pai_config || !pai ||
- pai < pc->min_pai || pai > pc->max_pai)
+ if (!pc || cpl_id >= pc->num_cpli || !pc->cpli[cpl_id].avg_ivl_config ||
+ !pai || pai < pc->cpli[cpl_id].min_avg_ivl ||
+ pai > pc->cpli[cpl_id].max_avg_ivl)
return -EINVAL;
- if (pc->fc_info && pc->fc_info[POWERCAP_FC_PAI].set_addr) {
- struct scmi_fc_info *fci = &pc->fc_info[POWERCAP_FC_PAI];
+ if (pc->cpli[cpl_id].fc_info && pc->cpli[cpl_id].fc_info[POWERCAP_FC_PAI].set_addr) {
+ struct scmi_fc_info *fci = &pc->cpli[cpl_id].fc_info[POWERCAP_FC_PAI];
trace_scmi_fc_call(SCMI_PROTOCOL_POWERCAP, POWERCAP_PAI_SET,
domain_id, pai, 0);
@@ -550,6 +588,12 @@ static int scmi_powercap_pai_set(const struct scmi_protocol_handle *ph,
return scmi_powercap_xfer_pai_set(ph, domain_id, pai);
}
+static int scmi_powercap_avg_interval_set(const struct scmi_protocol_handle *ph,
+ u32 domain_id, u32 cpl_id, u32 val)
+{
+ return scmi_powercap_pai_set(ph, domain_id, cpl_id, val);
+}
+
static int scmi_powercap_measurements_get(const struct scmi_protocol_handle *ph,
u32 domain_id, u32 *average_power,
u32 *pai)
@@ -645,11 +689,11 @@ static int scmi_powercap_cap_enable_set(const struct scmi_protocol_handle *ph,
if (!pi->states[domain_id].last_pcap)
return -EINVAL;
- ret = __scmi_powercap_cap_set(ph, pi, domain_id,
+ ret = __scmi_powercap_cap_set(ph, pi, domain_id, CPL0,
pi->states[domain_id].last_pcap,
true);
} else {
- ret = __scmi_powercap_cap_set(ph, pi, domain_id, 0, true);
+ ret = __scmi_powercap_cap_set(ph, pi, domain_id, CPL0, 0, true);
}
if (ret)
@@ -660,7 +704,7 @@ static int scmi_powercap_cap_enable_set(const struct scmi_protocol_handle *ph,
* server could have ignored a disable request and kept enforcing some
* powercap limit requested by other agents.
*/
- ret = scmi_powercap_cap_get(ph, domain_id, &power_cap);
+ ret = scmi_powercap_cap_get(ph, domain_id, CPL0, &power_cap);
if (!ret)
pi->states[domain_id].enabled = !!power_cap;
@@ -682,7 +726,7 @@ static int scmi_powercap_cap_enable_get(const struct scmi_protocol_handle *ph,
* Report always real platform state; platform could have ignored
* a previous disable request. Default true on any error.
*/
- ret = scmi_powercap_cap_get(ph, domain_id, &power_cap);
+ ret = scmi_powercap_cap_get(ph, domain_id, CPL0, &power_cap);
if (!ret)
*enable = !!power_cap;
@@ -699,8 +743,8 @@ static const struct scmi_powercap_proto_ops powercap_proto_ops = {
.cap_set = scmi_powercap_cap_set,
.cap_enable_set = scmi_powercap_cap_enable_set,
.cap_enable_get = scmi_powercap_cap_enable_get,
- .pai_get = scmi_powercap_pai_get,
- .pai_set = scmi_powercap_pai_set,
+ .avg_interval_get = scmi_powercap_avg_interval_get,
+ .avg_interval_set = scmi_powercap_avg_interval_set,
.measurements_get = scmi_powercap_measurements_get,
.measurements_threshold_set = scmi_powercap_measurements_threshold_set,
.measurements_threshold_get = scmi_powercap_measurements_threshold_get,
@@ -991,18 +1035,18 @@ scmi_powercap_protocol_init(const struct scmi_protocol_handle *ph)
* formed and correlated by sane parent-child relationship (if any).
*/
for (domain = 0; domain < pinfo->num_domains; domain++) {
- ret = scmi_powercap_domain_attributes_get(ph, pinfo, domain);
+ ret = scmi_powercap_domain_initialize(ph, pinfo, domain);
if (ret)
return ret;
if (pinfo->powercaps[domain].fastchannels)
scmi_powercap_domain_init_fc(ph, domain,
- &pinfo->powercaps[domain].fc_info);
+ &pinfo->powercaps[domain].cpli[CPL0].fc_info);
/* Grab initial state when disable is supported. */
if (PROTOCOL_REV_MAJOR(ph->version) >= 0x2) {
ret = __scmi_powercap_cap_get(ph,
- &pinfo->powercaps[domain],
+ &pinfo->powercaps[domain], CPL0,
&pinfo->states[domain].last_pcap);
if (ret)
return ret;
diff --git a/drivers/powercap/arm_scmi_powercap.c b/drivers/powercap/arm_scmi_powercap.c
index ab66e9a3b1e2..be3007206a74 100644
--- a/drivers/powercap/arm_scmi_powercap.c
+++ b/drivers/powercap/arm_scmi_powercap.c
@@ -97,7 +97,7 @@ static const struct powercap_zone_ops zone_ops = {
};
static void scmi_powercap_normalize_cap(const struct scmi_powercap_zone *spz,
- u64 power_limit_uw, u32 *norm)
+ u64 power_limit_uw, int cid, u32 *norm)
{
bool scale_mw = spz->info->powercap_scale_mw;
u64 val;
@@ -108,9 +108,9 @@ static void scmi_powercap_normalize_cap(const struct scmi_powercap_zone *spz,
* the range [min_power_cap, max_power_cap] whose bounds are assured to
* be two unsigned 32bits quantities.
*/
- *norm = clamp_t(u32, val, spz->info->min_power_cap,
- spz->info->max_power_cap);
- *norm = rounddown(*norm, spz->info->power_cap_step);
+ *norm = clamp_t(u32, val, spz->info->cpli[cid].min_power_cap,
+ spz->info->cpli[cid].max_power_cap);
+ *norm = rounddown(*norm, spz->info->cpli[cid].power_cap_step);
val = (scale_mw) ? *norm * 1000 : *norm;
if (power_limit_uw != val)
@@ -125,12 +125,12 @@ static int scmi_powercap_set_power_limit_uw(struct powercap_zone *pz, int cid,
struct scmi_powercap_zone *spz = to_scmi_powercap_zone(pz);
u32 norm_power;
- if (!spz->info->powercap_cap_config)
+ if (!spz->info->cpli[cid].cap_config)
return -EINVAL;
- scmi_powercap_normalize_cap(spz, power_uw, &norm_power);
+ scmi_powercap_normalize_cap(spz, power_uw, cid, &norm_power);
- return powercap_ops->cap_set(spz->ph, spz->info->id, norm_power, false);
+ return powercap_ops->cap_set(spz->ph, spz->info->id, cid, norm_power, false);
}
static int scmi_powercap_get_power_limit_uw(struct powercap_zone *pz, int cid,
@@ -140,7 +140,7 @@ static int scmi_powercap_get_power_limit_uw(struct powercap_zone *pz, int cid,
u32 power;
int ret;
- ret = powercap_ops->cap_get(spz->ph, spz->info->id, &power);
+ ret = powercap_ops->cap_get(spz->ph, spz->info->id, cid, &power);
if (ret)
return ret;
@@ -152,19 +152,20 @@ static int scmi_powercap_get_power_limit_uw(struct powercap_zone *pz, int cid,
}
static void scmi_powercap_normalize_time(const struct scmi_powercap_zone *spz,
- u64 time_us, u32 *norm)
+ u64 time_us, int cid, u32 *norm)
{
/*
* This cast is lossless since here @time_us is certain to be within the
- * range [min_pai, max_pai] whose bounds are assured to be two unsigned
- * 32bits quantities.
+ * range [min_avg_ivl, max_avg_ivl] whose bounds are assured to be two
+ * unsigned 32bits quantities.
*/
- *norm = clamp_t(u32, time_us, spz->info->min_pai, spz->info->max_pai);
- *norm = rounddown(*norm, spz->info->pai_step);
+ *norm = clamp_t(u32, time_us, spz->info->cpli[cid].min_avg_ivl,
+ spz->info->cpli[cid].max_avg_ivl);
+ *norm = rounddown(*norm, spz->info->cpli[cid].avg_ivl_step);
if (time_us != *norm)
dev_dbg(spz->dev,
- "Normalized %s:PAI - requested:%llu - normalized:%u\n",
+ "Normalized %s:AVG_IVL - requested:%llu - normalized:%u\n",
spz->info->name, time_us, *norm);
}
@@ -174,12 +175,13 @@ static int scmi_powercap_set_time_window_us(struct powercap_zone *pz, int cid,
struct scmi_powercap_zone *spz = to_scmi_powercap_zone(pz);
u32 norm_pai;
- if (!spz->info->powercap_pai_config)
+ if (!spz->info->cpli[cid].avg_ivl_config)
return -EINVAL;
- scmi_powercap_normalize_time(spz, time_window_us, &norm_pai);
+ scmi_powercap_normalize_time(spz, time_window_us, cid, &norm_pai);
- return powercap_ops->pai_set(spz->ph, spz->info->id, norm_pai);
+ return powercap_ops->avg_interval_set(spz->ph, spz->info->id,
+ cid, norm_pai);
}
static int scmi_powercap_get_time_window_us(struct powercap_zone *pz, int cid,
@@ -189,7 +191,7 @@ static int scmi_powercap_get_time_window_us(struct powercap_zone *pz, int cid,
int ret;
u32 pai;
- ret = powercap_ops->pai_get(spz->ph, spz->info->id, &pai);
+ ret = powercap_ops->avg_interval_get(spz->ph, spz->info->id, cid, &pai);
if (ret)
return ret;
@@ -203,7 +205,7 @@ static int scmi_powercap_get_max_power_uw(struct powercap_zone *pz, int cid,
{
struct scmi_powercap_zone *spz = to_scmi_powercap_zone(pz);
- *max_power_uw = spz->info->max_power_cap;
+ *max_power_uw = spz->info->cpli[cid].max_power_cap;
if (spz->info->powercap_scale_mw)
*max_power_uw *= 1000;
@@ -215,7 +217,7 @@ static int scmi_powercap_get_min_power_uw(struct powercap_zone *pz, int cid,
{
struct scmi_powercap_zone *spz = to_scmi_powercap_zone(pz);
- *min_power_uw = spz->info->min_power_cap;
+ *min_power_uw = spz->info->cpli[cid].min_power_cap;
if (spz->info->powercap_scale_mw)
*min_power_uw *= 1000;
@@ -227,7 +229,7 @@ static int scmi_powercap_get_max_time_window_us(struct powercap_zone *pz,
{
struct scmi_powercap_zone *spz = to_scmi_powercap_zone(pz);
- *time_window_us = spz->info->max_pai;
+ *time_window_us = spz->info->cpli[cid].max_avg_ivl;
return 0;
}
@@ -237,14 +239,16 @@ static int scmi_powercap_get_min_time_window_us(struct powercap_zone *pz,
{
struct scmi_powercap_zone *spz = to_scmi_powercap_zone(pz);
- *time_window_us = (u64)spz->info->min_pai;
+ *time_window_us = (u64)spz->info->cpli[cid].min_avg_ivl;
return 0;
}
static const char *scmi_powercap_get_name(struct powercap_zone *pz, int cid)
{
- return "SCMI power-cap";
+ struct scmi_powercap_zone *spz = to_scmi_powercap_zone(pz);
+
+ return spz->info->cpli[cid].name;
}
static const struct powercap_zone_constraint_ops constraint_ops = {
diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h
index aafaac1496b0..9918fb30100c 100644
--- a/include/linux/scmi_protocol.h
+++ b/include/linux/scmi_protocol.h
@@ -2,7 +2,7 @@
/*
* SCMI Message Protocol driver header
*
- * Copyright (C) 2018-2021 ARM Ltd.
+ * Copyright (C) 2018-2026 ARM Ltd.
*/
#ifndef _LINUX_SCMI_PROTOCOL_H
@@ -609,6 +609,35 @@ struct scmi_voltage_proto_ops {
s32 *volt_uV);
};
+/**
+ * struct scmi_powercap_cpl_info - Describe one CPL - Concurrent Powercap Limit
+ *
+ * @id: CPL ID as advertised by the platform.
+ * @cap_config: CAP configuration support for this CPL.
+ * @min_power_cap: Minimum configurable CAP.
+ * @max_power_cap: Maximum configurable CAP.
+ * @power_cap_step: Step size between two consecutive CAP values.
+ * @avg_ivl_config: Powercap averaging interval configuration support.
+ * @min_avg_ivl: Minimum configurable powercap averaging interval.
+ * @max_avg_ivl: Maximum configurable powercap averaging interval.
+ * @avg_ivl_step: Step size between two consecutive averaging intervals.
+ * @name: name assigned to the Powercap Domain by platform.
+ * @fc_info: Reference to the FastChannels descriptors supported by this CPL
+ */
+struct scmi_powercap_cpl_info {
+ unsigned int id;
+ bool cap_config;
+ unsigned int min_power_cap;
+ unsigned int max_power_cap;
+ unsigned int power_cap_step;
+ bool avg_ivl_config;
+ unsigned int min_avg_ivl;
+ unsigned int max_avg_ivl;
+ unsigned int avg_ivl_step;
+ char name[SCMI_SHORT_NAME_MAX_SIZE];
+ struct scmi_fc_info *fc_info;
+};
+
/**
* struct scmi_powercap_info - Describe one available Powercap domain
*
@@ -617,21 +646,15 @@ struct scmi_voltage_proto_ops {
* @notify_powercap_measurement_change: MEASUREMENTS change notifications
* support.
* @async_powercap_cap_set: Asynchronous CAP set support.
- * @powercap_cap_config: CAP configuration support.
* @powercap_monitoring: Monitoring (measurements) support.
- * @powercap_pai_config: PAI configuration support.
* @powercap_scale_mw: Domain reports power data in milliwatt units.
* @powercap_scale_uw: Domain reports power data in microwatt units.
* Note that, when both @powercap_scale_mw and
* @powercap_scale_uw are set to false, the domain
* reports power data on an abstract linear scale.
+ * @extended_names: Support for long names.
+ * @fastchannels: Support for at least one fastchannel,
* @name: name assigned to the Powercap Domain by platform.
- * @min_pai: Minimum configurable PAI.
- * @max_pai: Maximum configurable PAI.
- * @pai_step: Step size between two consecutive PAI values.
- * @min_power_cap: Minimum configurable CAP.
- * @max_power_cap: Maximum configurable CAP.
- * @power_cap_step: Step size between two consecutive CAP values.
* @sustainable_power: Maximum sustainable power consumption for this domain
* under normal conditions.
* @accuracy: The accuracy with which the power is measured and reported in
@@ -639,30 +662,25 @@ struct scmi_voltage_proto_ops {
* @parent_id: Identifier of the containing parent power capping domain, or the
* value 0xFFFFFFFF if this powercap domain is a root domain not
* contained in any other domain.
+ * @num_cpli: Number of discovered CPLs.
+ * @cpli: Reference to an array holding descriptors to all the discovered CPLs.
*/
struct scmi_powercap_info {
unsigned int id;
bool notify_powercap_cap_change;
bool notify_powercap_measurement_change;
bool async_powercap_cap_set;
- bool powercap_cap_config;
bool powercap_monitoring;
- bool powercap_pai_config;
bool powercap_scale_mw;
bool powercap_scale_uw;
bool fastchannels;
char name[SCMI_MAX_STR_SIZE];
- unsigned int min_pai;
- unsigned int max_pai;
- unsigned int pai_step;
- unsigned int min_power_cap;
- unsigned int max_power_cap;
- unsigned int power_cap_step;
unsigned int sustainable_power;
unsigned int accuracy;
#define SCMI_POWERCAP_ROOT_ZONE_ID 0xFFFFFFFFUL
unsigned int parent_id;
- struct scmi_fc_info *fc_info;
+ unsigned int num_cpli;
+ struct scmi_powercap_cpl_info *cpli;
};
/**
@@ -691,8 +709,12 @@ struct scmi_powercap_info {
* on the system: for this reason @cap_get and @cap_enable_get
* will always report the final platform view of the powercaps.
* @cap_enable_get: get the current CAP enable status for the specified domain.
- * @pai_get: get the current PAI value for the specified domain.
- * @pai_set: set the PAI value for the specified domain to the provided value.
+ * @avg_interval_get: get the current averaging interval value for the specified
+ * domain. This will get the PAI or CAI depending on the used
+ * protocol version.
+ * @avg_interval_set: set the current averaging interval value for the specified
+ * domain. This will set the PAI or CAI depending on the used
+ * protocol version.
* @measurements_get: retrieve the current average power measurements for the
* specified domain and the related PAI upon which is
* calculated.
@@ -716,17 +738,17 @@ struct scmi_powercap_proto_ops {
const struct scmi_powercap_info __must_check *(*info_get)
(const struct scmi_protocol_handle *ph, u32 domain_id);
int (*cap_get)(const struct scmi_protocol_handle *ph, u32 domain_id,
- u32 *power_cap);
+ u32 cpl_id, u32 *power_cap);
int (*cap_set)(const struct scmi_protocol_handle *ph, u32 domain_id,
- u32 power_cap, bool ignore_dresp);
+ u32 cpl_id, u32 power_cap, bool ignore_dresp);
int (*cap_enable_set)(const struct scmi_protocol_handle *ph,
u32 domain_id, bool enable);
int (*cap_enable_get)(const struct scmi_protocol_handle *ph,
u32 domain_id, bool *enable);
- int (*pai_get)(const struct scmi_protocol_handle *ph, u32 domain_id,
- u32 *pai);
- int (*pai_set)(const struct scmi_protocol_handle *ph, u32 domain_id,
- u32 pai);
+ int (*avg_interval_get)(const struct scmi_protocol_handle *ph,
+ u32 domain_id, u32 cpl_id, u32 *val);
+ int (*avg_interval_set)(const struct scmi_protocol_handle *ph,
+ u32 domain_id, u32 cpl_id, u32 val);
int (*measurements_get)(const struct scmi_protocol_handle *ph,
u32 domain_id, u32 *average_power, u32 *pai);
int (*measurements_threshold_set)(const struct scmi_protocol_handle *ph,
--
2.47.3
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox