* Re: [Pre-CfP] LPC2026: Power management and thermal control micro-conference
From: Daniel Lezcano @ 2026-04-20 11:06 UTC (permalink / raw)
To: Rafael J. Wysocki
Cc: Linux PM, Daniel Lezcano, Lukasz Luba, Morten Rasmussen,
Sudeep Holla, Ulf Hansson, Christian Loehle, Artem Bityutskiy,
Ricardo Neri, Srinivas Pandruvada, Viresh Kumar, Pierre Gondois,
Dietmar Eggemann, amit.kucheria
In-Reply-To: <CAJZ5v0hb-RT5ZaRyea+ge5jMsv1g4i-0ONpc2O4uqKe+6A+bsw@mail.gmail.com>
On 4/20/26 12:22, Rafael J. Wysocki wrote:
> On Fri, Apr 17, 2026 at 5:05 PM Daniel Lezcano
> <daniel.lezcano@oss.qualcomm.com> wrote:
>>
>>
>> Resending with the correct email address for Amit :/
>>
>> On 4/17/26 17:02, Daniel Lezcano wrote:
>>>
>>> + Amit Kucheria
>>>
>>> On 3/26/26 15:35, Rafael J. Wysocki wrote:
>>>> Hi All,
>>>>
>>>> I'm looking for topic suggestions to be included in the power
>>>> management and thermal control micro-conference submission for
>>>> LPC2026. The deadline for submitting LPC u-conf proposals is April
>>>> 23, so if there are any topics you'd like to be covered, please let me
>>>> know within the next 2 weeks.
>>>
>>> I've some topics to be addressed regarding thermal issues, limitations
>>> and enhancements, suspend/resume limitations, powercap enhancements,
>>> user space resource management and may be power supply.
>>>
>>> I'm still in the process of collecting information, sorting them out and
>>> doing a selection of what is the most relevant for LPC. I should send
>>> the selection in a couple of days (may be 3)
>>>
>>> Is one title + a couple of sentences fine for each topic as pre-CfP ?
>
> A title line itself should be sufficient - I only need a list of
> prospective topics to be added to the proposal.
>
> Also, the deadline for proposal submissions is April 23, so if you can
> send me something earlier, please do so.
Yes, will do
^ permalink raw reply
* Re: [PATCH v3 3/8] wifi: ath10k: snoc: support powering on the device via pwrseq
From: Luca Weiss @ 2026-04-20 11:14 UTC (permalink / raw)
To: Dmitry Baryshkov, Luca Weiss
Cc: Liam Girdwood, Mark Brown, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Bartosz Golaszewski, Marcel Holtmann,
Luiz Augusto von Dentz, Jeff Johnson, Bjorn Andersson,
Konrad Dybcio, Manivannan Sadhasivam, Vinod Koul,
Balakrishna Godavarthi, Matthias Kaehlcke, linux-arm-msm,
linux-kernel, devicetree, linux-bluetooth, linux-wireless, ath10k,
linux-pm, Krzysztof Kozlowski, Bartosz Golaszewski
In-Reply-To: <hdypom3nioc6tk26gh647imy5ykhcjqvknideilnbc2b5p7eo7@hm7fsscleutf>
On Sat Apr 18, 2026 at 9:38 PM CEST, Dmitry Baryshkov wrote:
> On Thu, Apr 16, 2026 at 12:06:09PM +0200, Luca Weiss wrote:
>> Hi Dmitry,
>>
>> On Mon Jan 19, 2026 at 6:07 PM CET, Dmitry Baryshkov wrote:
>> > The WCN39xx family of WiFi/BT chips incorporates a simple PMU, spreading
>> > voltages over internal rails. Implement support for using powersequencer
>> > for this family of ATH10k devices in addition to using regulators.
>> >
>> > Reviewed-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
>> > Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
>> > ---
>> > drivers/net/wireless/ath/ath10k/snoc.c | 53 ++++++++++++++++++++++++++++++++--
>> > drivers/net/wireless/ath/ath10k/snoc.h | 3 ++
>> > 2 files changed, 53 insertions(+), 3 deletions(-)
>> >
>> > + ar_snoc->pwrseq = devm_pwrseq_get(&pdev->dev, "wlan");
>> > + if (IS_ERR(ar_snoc->pwrseq)) {
>> > + ret = PTR_ERR(ar_snoc->pwrseq);
>> > + ar_snoc->pwrseq = NULL;
>> > + if (ret != -EPROBE_DEFER)
>> > + goto err_free_irq;
>>
>> I'm fairly sure this is now broken with CONFIG_POWER_SEQUENCING=n since
>> then pwrseq_get() is returning ERR_PTR(-ENOSYS) which is not handled
>> here.
>>
>> I'm observing my ath10k_snoc is now failing to probe "with error -38"
>> which definitely seems to be related, but I haven't debugged it further
>> yet.
>
> Posted https://patch.msgid.link/20260418-ath10k-snoc-pwrseq-v1-1-832594ba3294@oss.qualcomm.com
Thanks Dmitry!
^ permalink raw reply
* Re: [PATCH] cpufreq: CPPC: add autonomous mode boot parameter support
From: Sumit Gupta @ 2026-04-20 13:07 UTC (permalink / raw)
To: Pierre Gondois
Cc: linux-tegra@vger.kernel.org, linux-kernel@vger.kernel.org,
linux-doc@vger.kernel.org, zhenglifeng1@huawei.com,
Thierry Reding, viresh.kumar@linaro.org, Jon Hunter, Vikram Sethi,
ionela.voinescu@arm.com, Krishna Sitaraman, Sanjay Chandrashekara,
zhanjie9@hisilicon.com, corbet@lwn.net, Matt Ochs,
skhan@linuxfoundation.org, Bibek Basu, rdunlap@infradead.org,
linux-pm@vger.kernel.org, mario.limonciello@amd.com,
rafael@kernel.org, sumitg
In-Reply-To: <208360b1-36a5-419d-80f4-431914407f61@arm.com>
>>> On 3/17/26 16:10, Sumit Gupta wrote:
>>>> Add kernel boot parameter 'cppc_cpufreq.auto_sel_mode' to enable CPPC
>>>> autonomous performance selection on all CPUs at system startup without
>>>> requiring runtime sysfs manipulation. When autonomous mode is enabled,
>>>> the hardware automatically adjusts CPU performance based on workload
>>>> demands using Energy Performance Preference (EPP) hints.
>>>>
>>>> When auto_sel_mode=1:
>>>> - Configure all CPUs for autonomous operation on first init
>>>> - Set EPP to performance preference (0x0)
>>>> - Use HW min/max when set; otherwise program from policy limits (caps)
>>>> - Clamp desired_perf to bounds before enabling autonomous mode
>>>> - Hardware controls frequency instead of the OS governor
>>>>
>>>> The boot parameter is applied only during first policy initialization.
>>>> On hotplug, skip applying it so that the user's runtime sysfs
>>>> configuration is preserved.
>>>>
>>>> Reviewed-by: Randy Dunlap <rdunlap@infradead.org> (Documentation)
>>>> Signed-off-by: Sumit Gupta <sumitg@nvidia.com>
>>>> ---
>>>> Part 1 [1] of this series was applied for 7.1 and present in next.
>>>> Sending this patch as reworked version of 'patch 11' from [2] based
>>>> on next.
>>>>
>>>> [1]
>>>> https://lore.kernel.org/lkml/20260206142658.72583-1-sumitg@nvidia.com/
>>>> [2]
>>>> https://lore.kernel.org/lkml/20251223121307.711773-1-sumitg@nvidia.com/
>>>> ---
>>>> .../admin-guide/kernel-parameters.txt | 13 +++
>>>> drivers/cpufreq/cppc_cpufreq.c | 84
>>>> +++++++++++++++++--
>>>> 2 files changed, 92 insertions(+), 5 deletions(-)
>>>>
>>>> diff --git a/Documentation/admin-guide/kernel-parameters.txt
>>>> b/Documentation/admin-guide/kernel-parameters.txt
>>>> index fa6171b5fdd5..de4b4c89edfe 100644
>>>> --- a/Documentation/admin-guide/kernel-parameters.txt
>>>> +++ b/Documentation/admin-guide/kernel-parameters.txt
>>>> @@ -1060,6 +1060,19 @@ Kernel parameters
>>>> policy to use. This governor must be
>>>> registered in the
>>>> kernel before the cpufreq driver probes.
>>>>
>>>> + cppc_cpufreq.auto_sel_mode=
>>>> + [CPU_FREQ] Enable ACPI CPPC autonomous
>>>> performance
>>>> + selection. When enabled, hardware
>>>> automatically adjusts
>>>> + CPU frequency on all CPUs based on workload
>>>> demands.
>>>> + In Autonomous mode, Energy Performance
>>>> Preference (EPP)
>>>> + hints guide hardware toward performance (0x0)
>>>> or energy
>>>> + efficiency (0xff).
>>>> + Requires ACPI CPPC autonomous selection
>>>> register support.
>>>> + Format: <bool>
>>>> + Default: 0 (disabled)
>>>> + 0: use cpufreq governors
>>>> + 1: enable if supported by hardware
>>>> +
>>>> cpu_init_udelay=N
>>>> [X86,EARLY] Delay for N microsec between
>>>> assert and de-assert
>>>> of APIC INIT to start processors. This delay
>>>> occurs
>>>> diff --git a/drivers/cpufreq/cppc_cpufreq.c
>>>> b/drivers/cpufreq/cppc_cpufreq.c
>>>> index 5dfb109cf1f4..49c148b2a0a4 100644
>>>> --- a/drivers/cpufreq/cppc_cpufreq.c
>>>> +++ b/drivers/cpufreq/cppc_cpufreq.c
>>>> @@ -28,6 +28,9 @@
>>>>
>>>> static struct cpufreq_driver cppc_cpufreq_driver;
>>>>
>>>> +/* Autonomous Selection boot parameter */
>>>> +static bool auto_sel_mode;
>>>> +
>>>> #ifdef CONFIG_ACPI_CPPC_CPUFREQ_FIE
>>>> static enum {
>>>> FIE_UNSET = -1,
>>>> @@ -708,11 +711,74 @@ static int cppc_cpufreq_cpu_init(struct
>>>> cpufreq_policy *policy)
>>>> policy->cur = cppc_perf_to_khz(caps, caps->highest_perf);
>>>> cpu_data->perf_ctrls.desired_perf = caps->highest_perf;
>>>>
>>>> - ret = cppc_set_perf(cpu, &cpu_data->perf_ctrls);
>>>> - if (ret) {
>>>> - pr_debug("Err setting perf value:%d on CPU:%d. ret:%d\n",
>>>> - caps->highest_perf, cpu, ret);
>>>> - goto out;
>>>> + /*
>>>> + * Enable autonomous mode on first init if boot param is set.
>>>> + * Check last_governor to detect first init and skip if auto_sel
>>>> + * is already enabled.
>>>> + */
>>> If the goal is to set autosel only once at the driver init,
>>> shouldn't this be done in cppc_cpufreq_init() ?
>>> I understand that cpu_data doesn't exist yet in
>>> cppc_cpufreq_init(), but this seems more appropriate to do
>>> it there IMO.
>>>
>>> This means the cpudata should be updated accordingly
>>> in this cppc_cpufreq_cpu_init() function.
>> In an earlier version [1], the setup was in cppc_cpufreq_init() but
>> was moved to cppc_cpufreq_cpu_init() to improve per-CPU error handling.
>> Keeping the setup in cppc_cpufreq_init() helps to avoid the last_governor
>> check. We can warn for a CPU failing to enable and continue so other
>> CPUs keep autonomous mode.
>> cppc_cpufreq_cpu_init() would then just check the auto_sel state
>> from register and sync policy limits from min/max_perf registers when
>> autonomous mode is active.
>> Please let me know your thoughts.
> FWIU the auto_sel_mode module parameter allows to
> configure the default auto_sel_mode when the driver is
> first loaded, so there should not need to check that again
> whenever cppc_cpufreq_cpu_init() is called.
> Maybe Ionela saw something we didn't see ?
AFAIU, the concern in that review [1] was about error handling as the
earlier version disabled auto_sel on all CPUs if any single CPU failed.
Per-CPU error handling in cppc_cpufreq_init() (warn and continue)
addresses that. Can't think of more reason.
Do you have anything in mind?
>
> Also just to be sure, should it still be possible to change
> the auto_sel_mode through the sysfs if the driver was
> loaded with auto_sel_mode=1 ?
>
Yes, the per-CPU auto_select sysfs attribute works independently of the
boot param. Users can enable or disable auto_sel on any CPU at runtime
via sysfs, regardless of how the driver was loaded. The boot param only
sets the initial state.
>> [1]
>> https://lore.kernel.org/lkml/5593d364-ca37-41c5-b33f-f7e245d6d626@nvidia.com/
>>
>>
>>>> + if (auto_sel_mode && policy->last_governor[0] == '\0' &&
>>>> + !cpu_data->perf_ctrls.auto_sel) {
>>>> + /* Enable CPPC - optional register, some platforms
>>>> need it */
>>> The documentation of the CPPC Enable Register is subject to
>>> interpretation, but IIUC the field should be set to use the CPPC
>>> controls, so I assume this should be set in cppc_cpufreq_init()
>>> instead ?
>> Agree that the CPPC Enable is about using the CPPC control path
>> in general and not only for autonomous selection.
>> Will move cppc_set_enable() into cppc_cpufreq_init() or outside the
>> autonomous mode block in cppc_cpufreq_cpu_init() as per conclusion
>> of previous comment.
>>
>>>> + ret = cppc_set_enable(cpu, true);
>>>> + if (ret && ret != -EOPNOTSUPP)
>>>> + pr_warn("Failed to enable CPPC for CPU%d
>>>> (%d)\n", cpu, ret);
>>>> +
>>>> + /*
>>>> + * Prefer HW min/max_perf when set; otherwise program
>>>> from
>>>> + * policy limits derived earlier from caps.
>>>> + * Clamp desired_perf to bounds and sync policy->cur.
>>>> + */
>>>> + if (!cpu_data->perf_ctrls.min_perf ||
>>>> !cpu_data->perf_ctrls.max_perf)
>>> The function doesn't seem to exist.
>> It is newly added in [2].
>> Don't need to call it if we move the setup to cppc_cpufreq_init().
> Ah ok right thanks.
>
>
>> [2]
>> https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git/commit/?id=ea3db45ae476889a1ba0ab3617e6afdeeefbda3d
>>
>>
>>
>>>> + cppc_cpufreq_update_perf_limits(cpu_data, policy);
>>>> +
>>>> + cpu_data->perf_ctrls.desired_perf =
>>>> + clamp_t(u32, cpu_data->perf_ctrls.desired_perf,
>>>> + cpu_data->perf_ctrls.min_perf,
>>>> + cpu_data->perf_ctrls.max_perf);
>>>> +
>>>> + policy->cur = cppc_perf_to_khz(caps,
>>>> + cpu_data->perf_ctrls.desired_perf);
>>>> +
>>> Maybe this should also be done in cppc_cpufreq_init()
>>> if the auto_sel_mode parameter is set ?
>> Yes.
>>
>>>> + /* EPP is optional - some platforms may not support it */
>>>> + ret = cppc_set_epp(cpu, CPPC_EPP_PERFORMANCE_PREF);
>>>> + if (ret && ret != -EOPNOTSUPP)
>>>> + pr_warn("Failed to set EPP for CPU%d (%d)\n",
>>>> cpu, ret);
>>>> + else if (!ret)
>>>> + cpu_data->perf_ctrls.energy_perf =
>>>> CPPC_EPP_PERFORMANCE_PREF;
>>>> +
>>>> + ret = cppc_set_perf(cpu, &cpu_data->perf_ctrls);
>>>> + if (ret) {
>>>> + pr_debug("Err setting perf for autonomous mode
>>>> CPU:%d ret:%d\n",
>>>> + cpu, ret);
>>>> + goto out;
>>>> + }
>>>> +
>>>> + ret = cppc_set_auto_sel(cpu, true);
>>>> + if (ret && ret != -EOPNOTSUPP) {
>>>> + pr_warn("Failed autonomous config for CPU%d
>>>> (%d)\n",
>>>> + cpu, ret);
>>>> + goto out;
>>>> + }
>>>> + if (!ret)
>>>> + cpu_data->perf_ctrls.auto_sel = true;
>>>> + }
>>>> +
>>>> + if (cpu_data->perf_ctrls.auto_sel) {
>>> There is a patchset ongoing which tries to remove
>>> setting policy->min/max from driver initialization.
>>> Indeed, these values are only temporarily valid,
>>> until the governor override them.
>>> It is not sure yet the patch will be accepted though.
>>>
>>> https://lore.kernel.org/lkml/20260317101753.2284763-4-pierre.gondois@arm.com/
>>>
>>
>> You are right that policy->min/max from .init() are temporary today
>> as cpufreq_set_policy() overwrites them before the governor starts.
>>
>> On my test platform (highest == nominal, lowest_nonlinear == lowest),
>> this had no visible effect because the BIOS bounds and cpuinfo range
>> end up identical. But on platforms where they differ, the governor
>> would widen the range to full cpuinfo limits.
>>
>> I think your patch [3] fixes this by giving these the right semantic as
>> initial QoS requests. With it, cpufreq_set_policy() preserves the policy
>> limits set from min/max_perf registers in .init(), which can either be
>> BIOS values on first boot or last user configured values before hotplug.
>>
>> I will update the comment in v2 to reflect QoS seeding intent.
>>
>> I see that the first two patches of your series [3] is applied for 7.1.
>> Do you plan to send the pending patch (3/4) from [3]?
>>
> I need to ping Viresh to check if this is still relevant.
>
>
>> [3]
>> https://lore.kernel.org/lkml/20260317101753.2284763-4-pierre.gondois@arm.com/
>>
>>
>>>
>>>> + /* Sync policy limits from HW when autonomous mode is
>>>> active */
>>>> + policy->min = cppc_perf_to_khz(caps,
>>>> + cpu_data->perf_ctrls.min_perf ?:
>>>> + caps->lowest_nonlinear_perf);
>>>> + policy->max = cppc_perf_to_khz(caps,
>>>> + cpu_data->perf_ctrls.max_perf ?:
>>>> + caps->nominal_perf);
>>>> + } else {
>>>> + /* Normal mode: governors control frequency */
>>>> + ret = cppc_set_perf(cpu, &cpu_data->perf_ctrls);
>>>> + if (ret) {
>>>> + pr_debug("Err setting perf value:%d on CPU:%d.
>>>> ret:%d\n",
>>>> + caps->highest_perf, cpu, ret);
>>>> + goto out;
>>>> + }
>>>> }
>>>>
>>>> cppc_cpufreq_cpu_fie_init(policy);
>>>> @@ -1038,10 +1104,18 @@ static int __init cppc_cpufreq_init(void)
>>>>
>>>> static void __exit cppc_cpufreq_exit(void)
>>>> {
>>>> + unsigned int cpu;
>>>> +
>>>> + for_each_present_cpu(cpu)
>>>> + cppc_set_auto_sel(cpu, false);
>>> If the firmware has a default EPP value, it means that loading
>>> and the unloading the driver will reset this default EPP value.
>>> Maybe the initial EPP value and/or the auto_sel value should be
>>> cached somewhere and restored on exit ?
>>> I don't know if this is actually an issue, this is just to signal it.
>> The auto_sel_mode boot path programs EPP to performance preference(0),
>> not the firmware’s previous value. On unload we only call
>> cppc_set_auto_sel(false); we do not restore EPP, min/max perf,
>> or other CPPC fields to firmware defaults.
> Yes right, so loading/unloading the driver might change the
> default EPP value.
Acknowledged.
With auto_sel_mode, load/unload can leave EPP and other CPPC fields
different from the firmware defaults at boot.
We can add explicit cache-and-restore on exit in a follow-up
if that is desired.
Thank you,
Sumit Gupta
^ permalink raw reply
* Re: [PATCH] cpufreq: CPPC: add autonomous mode boot parameter support
From: Sumit Gupta @ 2026-04-20 13:13 UTC (permalink / raw)
To: Viresh Kumar, Pierre Gondois
Cc: linux-tegra, linux-kernel, linux-doc, zhenglifeng1, treding,
jonathanh, vsethi, ionela.voinescu, ksitaraman, sanjayc, zhanjie9,
corbet, mochs, skhan, bbasu, rdunlap, linux-pm, mario.limonciello,
rafael, sumitg
In-Reply-To: <zfoorh6tza4taswyr5zhxqn4rhcqzq4rtvz46eigoy25muxfls@tlbuypuwocvm>
On 13/04/26 11:21, Viresh Kumar wrote:
> External email: Use caution opening links or attachments
>
>
> On 10-04-26, 15:47, Pierre Gondois wrote:
>> I need to ping Viresh to check if this is still relevant.
> I think its okay to clear the min/max state in the kernel once and for all if
> you think it is not done nicely. As discussed earlier, try that in a fresh
> series which only does that part.
>
> --
> viresh
Thanks Pierre and Viresh.
In autonomous mode, the min/max_perf HW registers directly control the
frequency range the hardware operates in, so the values programmed in
.init() need to survive through the governor.
I verified this on a platform where lowest_nonlinear_perf != lowest_perf,
the min_perf register ends up at lowest_perf instead of the intended
lowest_nonlinear_perf.
Pierre's QoS seeding patch would fix this.
Happy to test once it's sent.
Thank you,
Sumit Gupta
^ permalink raw reply
* [PATCH AUTOSEL 6.18] tools/power turbostat: Fix --show/--hide for individual cpuidle counters
From: Sasha Levin @ 2026-04-20 13:20 UTC (permalink / raw)
To: patches, stable
Cc: Artem Bityutskiy, Len Brown, Sasha Levin, lenb, linux-pm,
linux-kernel
In-Reply-To: <20260420132314.1023554-1-sashal@kernel.org>
From: Artem Bityutskiy <artem.bityutskiy@linux.intel.com>
[ Upstream commit b6398bc2ef3a78f1be37ba01ae0a5eedaee47803 ]
Problem: individual swidle counter names (C1, C1+, C1-, etc.) cannot be
selected via --show/--hide due to two bugs in probe_cpuidle_counts():
1. The function returns immediately when BIC_cpuidle is not enabled,
without checking deferred_add_index.
2. The deferred name check runs against name_buf before the trailing
newline is stripped, so is_deferred_add("C1\n") never matches "C1".
Fix:
1. Relax the early return to pass through when deferred names are
queued.
2. Strip the trailing newline from name_buf before performing deferred
name checks.
3. Check each suffixed variant (C1+, C1, C1-) individually so that
e.g. "--show C1+" enables only the requested metric.
In addition, introduce a helper function to avoid repeating the
condition (readability cleanup).
Fixes: ec4acd3166d8 ("tools/power turbostat: disable "cpuidle" invocation counters, by default")
Signed-off-by: Artem Bityutskiy <artem.bityutskiy@linux.intel.com>
Signed-off-by: Len Brown <len.brown@intel.com>
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
LLM Generated explanations, may be completely bogus:
Error: Failed to generate final synthesis
tools/power/x86/turbostat/turbostat.c | 35 ++++++++++++++++-----------
1 file changed, 21 insertions(+), 14 deletions(-)
diff --git a/tools/power/x86/turbostat/turbostat.c b/tools/power/x86/turbostat/turbostat.c
index 67dfd3eaad014..48677f1846347 100644
--- a/tools/power/x86/turbostat/turbostat.c
+++ b/tools/power/x86/turbostat/turbostat.c
@@ -10890,6 +10890,14 @@ void probe_cpuidle_residency(void)
}
}
+static bool cpuidle_counter_wanted(char *name)
+{
+ if (is_deferred_skip(name))
+ return false;
+
+ return DO_BIC(BIC_cpuidle) || is_deferred_add(name);
+}
+
void probe_cpuidle_counts(void)
{
char path[64];
@@ -10899,7 +10907,7 @@ void probe_cpuidle_counts(void)
int min_state = 1024, max_state = 0;
char *sp;
- if (!DO_BIC(BIC_cpuidle))
+ if (!DO_BIC(BIC_cpuidle) && !deferred_add_index)
return;
for (state = 10; state >= 0; --state) {
@@ -10914,12 +10922,6 @@ void probe_cpuidle_counts(void)
remove_underbar(name_buf);
- if (!DO_BIC(BIC_cpuidle) && !is_deferred_add(name_buf))
- continue;
-
- if (is_deferred_skip(name_buf))
- continue;
-
/* truncate "C1-HSW\n" to "C1", or truncate "C1\n" to "C1" */
sp = strchr(name_buf, '-');
if (!sp)
@@ -10934,16 +10936,19 @@ void probe_cpuidle_counts(void)
* Add 'C1+' for C1, and so on. The 'below' sysfs file always contains 0 for
* the last state, so do not add it.
*/
-
*sp = '+';
*(sp + 1) = '\0';
- sprintf(path, "cpuidle/state%d/below", state);
- add_counter(0, path, name_buf, 64, SCOPE_CPU, COUNTER_ITEMS, FORMAT_DELTA, SYSFS_PERCPU, 0);
+ if (cpuidle_counter_wanted(name_buf)) {
+ sprintf(path, "cpuidle/state%d/below", state);
+ add_counter(0, path, name_buf, 64, SCOPE_CPU, COUNTER_ITEMS, FORMAT_DELTA, SYSFS_PERCPU, 0);
+ }
}
*sp = '\0';
- sprintf(path, "cpuidle/state%d/usage", state);
- add_counter(0, path, name_buf, 64, SCOPE_CPU, COUNTER_ITEMS, FORMAT_DELTA, SYSFS_PERCPU, 0);
+ if (cpuidle_counter_wanted(name_buf)) {
+ sprintf(path, "cpuidle/state%d/usage", state);
+ add_counter(0, path, name_buf, 64, SCOPE_CPU, COUNTER_ITEMS, FORMAT_DELTA, SYSFS_PERCPU, 0);
+ }
/*
* The 'above' sysfs file always contains 0 for the shallowest state (smallest
@@ -10952,8 +10957,10 @@ void probe_cpuidle_counts(void)
if (state != min_state) {
*sp = '-';
*(sp + 1) = '\0';
- sprintf(path, "cpuidle/state%d/above", state);
- add_counter(0, path, name_buf, 64, SCOPE_CPU, COUNTER_ITEMS, FORMAT_DELTA, SYSFS_PERCPU, 0);
+ if (cpuidle_counter_wanted(name_buf)) {
+ sprintf(path, "cpuidle/state%d/above", state);
+ add_counter(0, path, name_buf, 64, SCOPE_CPU, COUNTER_ITEMS, FORMAT_DELTA, SYSFS_PERCPU, 0);
+ }
}
}
}
--
2.53.0
^ permalink raw reply related
* [PATCH AUTOSEL 7.0-5.10] power: supply: sbs-manager: normalize return value of gpio_get
From: Sasha Levin @ 2026-04-20 13:20 UTC (permalink / raw)
To: patches, stable
Cc: Dmitry Torokhov, Linus Walleij, Bartosz Golaszewski,
Sebastian Reichel, Sasha Levin, sre, linux-pm, linux-kernel
In-Reply-To: <20260420132314.1023554-1-sashal@kernel.org>
From: Dmitry Torokhov <dmitry.torokhov@gmail.com>
[ Upstream commit 5c2ffc0b215a884dbc961d4737f636067348b8bd ]
The GPIO get callback is expected to return 0 or 1 (or a negative error
code). Ensure that the value returned by sbsm_gpio_get_value() is
normalized to the [0, 1] range.
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
Reviewed-by: Linus Walleij <linusw@kernel.org>
Reviewed-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
Link: https://patch.msgid.link/aZYoL2MnTYU5FuQh@google.com
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
LLM Generated explanations, may be completely bogus:
Now I have a complete understanding. Let me compile the analysis.
## PHASE 1: COMMIT MESSAGE FORENSICS
**Step 1.1 - Subject Line Parsing:**
Record: Subsystem: `power: supply: sbs-manager` | Action verb:
"normalize" | Summary: Normalize the return value of the GPIO `get`
callback to the [0, 1] range.
**Step 1.2 - Commit Tags:**
Record:
- Signed-off-by: Dmitry Torokhov (author)
- Reviewed-by: Linus Walleij (GPIO maintainer)
- Reviewed-by: Bartosz Golaszewski (GPIO maintainer)
- Link: https://patch.msgid.link/aZYoL2MnTYU5FuQh@google.com
- Signed-off-by: Sebastian Reichel (power supply maintainer)
- NO Fixes: tag, NO Cc: stable tag (unlike sibling patches)
**Step 1.3 - Commit Body Analysis:**
Record: Bug description: The GPIO `.get()` callback is contractually
required to return 0, 1, or a negative error code.
`sbsm_gpio_get_value()` was returning `ret & BIT(off)`, which yields
`BIT(off)` = 1, 2, 4, 8 for `off` = 0, 1, 2, 3 respectively. Values of
2, 4, 8 violate the API contract. The fix applies `!!()` to normalize.
**Step 1.4 - Hidden Bug Fix Detection:**
Record: "Normalize" is a bug-fix verb. This is a real bug fix disguised
as a "normalization" — the driver's GPIO callback was violating the
gpio_chip API contract.
## PHASE 2: DIFF ANALYSIS
**Step 2.1 - Inventory:**
Record: Single file `drivers/power/supply/sbs-manager.c`, 1 line changed
(1+/1-). Function affected: `sbsm_gpio_get_value()`. Scope: surgical.
**Step 2.2 - Code Flow:**
Record:
- Before: `return ret & BIT(off)` returns `BIT(off)` value (1, 2, 4, 8)
when the bit is set
- After: `return !!(ret & BIT(off))` returns 0 or 1 as required by the
API
**Step 2.3 - Bug Mechanism:**
Record: Category (g) Logic / correctness fix — API contract violation.
The `ngpio = SBSM_MAX_BATS = 4`, so `off` takes values 0-3 corresponding
to 4 smart batteries. For `off=0`, `BIT(0)=1` happens to be valid. For
`off=1,2,3`, the raw return is 2, 4, 8 — invalid.
**Step 2.4 - Fix Quality:**
Record: The fix is obviously correct. `!!()` is the idiomatic C
conversion to 0/1 boolean. No regression risk.
## PHASE 3: GIT HISTORY INVESTIGATION
**Step 3.1 - Blame:**
Record: The buggy code was present since the driver was introduced in
commit `dbc4deda03fe6` ("power: Adds support for Smart Battery System
Manager") in v4.15 (2017). The bug was latent for years.
**Step 3.2 - Follow Fixes: (None in this commit, but related commit):**
Record: The gpiolib wrapper that makes the invalid return value actually
matter is commit `86ef402d805d` ("gpiolib: sanitize the return value of
gpio_chip::get()") in v6.15-rc1. This wrapper rejects any value > 1 by
returning -EBADE.
**Step 3.3 - File History:**
Record: Recent changes to sbs-manager.c are minor (probe conversions,
fwnode updates). No prerequisites for this fix.
**Step 3.4 - Author Context:**
Record: Dmitry Torokhov submitted 3 sibling commits across multiple
subsystems for the SAME class of bug:
- `e2fa075d5ce19 iio: adc: ti-ads7950: normalize return value of
gpio_get` (with Fixes: + Cc: stable)
- `2bb995e6155cb net: phy: qcom: qca807x: normalize return value of
gpio_get` (with Fixes:)
- `5c2ffc0b215a8 power: supply: sbs-manager: normalize return value of
gpio_get` (this commit, no Fixes/stable)
**Step 3.5 - Dependencies:**
Record: No dependencies. Standalone fix.
## PHASE 4: MAILING LIST RESEARCH
**Step 4.1 - Original Discussion:**
Record: `b4 dig -c 5c2ffc0b215a8` found the thread at
https://lore.kernel.org/all/aZYoL2MnTYU5FuQh@google.com/. Single patch
(v1), two positive Reviewed-by responses, applied by the power supply
maintainer.
**Step 4.2 - Reviewers:**
Record: Linus Walleij (GPIO maintainer), Bartosz Golaszewski (GPIO
maintainer) both Reviewed-by. Sebastian Reichel (power supply
maintainer) applied. Appropriate maintainer review coverage.
**Step 4.3 - Bug Report:**
Record: No external bug report. The fix was found as part of Dmitry
Torokhov auditing drivers for return value compliance after the sanitize
wrapper exposed the issue.
**Step 4.4 - Related Patches:**
Record: Part of a broader effort by Dmitry to fix drivers that violated
the new API contract. See sibling patches above.
**Step 4.5 - Stable Discussion:**
Record: No explicit stable discussion for this specific commit. However,
the CLOSELY related commit `ec2cceadfae72` ("gpiolib: normalize the
return value of gc->get() on behalf of buggy drivers") explicitly has
`Cc: stable@vger.kernel.org` and `Fixes: 86ef402d805d`, acknowledging
that the sanitize change broke multiple drivers. That commit references
`aZSkqGTqMp_57qC7@google.com` as a closes link and was co-reported by
Dmitry.
## PHASE 5: CODE SEMANTIC ANALYSIS
**Step 5.1 - Key Functions:**
Record: `sbsm_gpio_get_value()` — assigned to `gc->get` at
`drivers/power/supply/sbs-manager.c:287`.
**Step 5.2 - Callers:**
Record: Called via the gpiolib layer `gpiochip_get()` which is called
from `gpio_chip_get_value()` and `gpio_chip_get_multiple()`. Any
consumer that calls `gpiod_get_value()` on these GPIO lines routes
through this function.
**Step 5.3 - Callees:**
Record: Calls `sbsm_read_word()` which performs an SMBus read on the
hardware.
**Step 5.4 - Reachability:**
Record: Reachable from userspace via GPIO character device ioctls
(GPIO_V2_LINE_VALUES_IOCTL), sysfs GPIO interface, or any in-kernel
consumer. With the SBS manager hardware present and a userspace tool
like `gpioget`, the buggy path is trivially reached.
**Step 5.5 - Similar Patterns:**
Record: Sibling fixes `e2fa075d5ce19` (ti-ads7950), `2bb995e6155cb`
(qca807x). The qca807x fix is ALREADY in stable 6.17.y
(`cb8f0a3857386`), 6.18.y, 6.19.y — establishing precedent that this
class of fix is appropriate for stable.
## PHASE 6: CROSS-REFERENCING AND STABLE TREE ANALYSIS
**Step 6.1 - Buggy Code in Stable:**
Record: `sbsm_gpio_get_value()` with the buggy `ret & BIT(off)` exists
identically in 6.17.y, 6.18.y, 6.19.y stable trees. For older trees
(5.10.y, 5.15.y, 6.1.y, 6.6.y, 6.12.y), the gpiolib wrapper
`86ef402d805d` is NOT present, so the bug is latent (API contract
violation without functional consequence).
**Step 6.2 - Backport Complications:**
Record: The patch applies cleanly to 6.17.y, 6.18.y, 6.19.y (verified
code is identical). For older stable trees, the patch would also apply
but the functional benefit is minimal until the wrapper lands there.
**Step 6.3 - Related Fixes in Stable:**
Record: Qca807x normalize fix IS already in 6.17.y (`cb8f0a3857386`),
6.18.y, 6.19.y (`554e8f2fbce86`). The alternative "normalize-on-behalf"
wrapper fix `ec2cceadfae72` has `Cc: stable` but has not yet landed in
these stable trees (verified: all three still have the EBADE-returning
version).
## PHASE 7: SUBSYSTEM AND MAINTAINER CONTEXT
**Step 7.1 - Subsystem:**
Record: `drivers/power/supply/` — power supply (battery) subsystem.
PERIPHERAL criticality (specific hardware), but affects laptops/embedded
systems with SBS-compliant multi-battery setups.
**Step 7.2 - Activity:**
Record: sbs-manager is a mature, low-activity driver (since 2017). Used
in real products with LTC1760 and similar chips.
## PHASE 8: IMPACT AND RISK ASSESSMENT
**Step 8.1 - Affected Users:**
Record: Users of SBS-compliant smart battery manager chips (LTC1760 and
others). Target: laptops, industrial embedded devices with multiple hot-
swappable batteries. Specifically affected on kernels 6.15+ that have
the gpiolib sanitize wrapper.
**Step 8.2 - Trigger:**
Record: Triggered automatically whenever userspace (or any in-kernel
consumer) reads the state of battery 2, 3, or 4 via the exposed GPIO
lines on an SBS manager. For battery 1 (`off=0`), `BIT(0)=1` escapes the
check. Trigger is deterministic — not a race.
**Step 8.3 - Failure Mode Severity:**
Record: On stable trees 6.17.y/6.18.y/6.19.y that have the EBADE
wrapper, reading batteries 2-4 returns `-EBADE` instead of the actual
state. Userspace tools that read these GPIOs see errors. This is a
functional regression: MEDIUM severity (broken hardware functionality,
but not a crash/corruption).
**Step 8.4 - Risk-Benefit:**
Record:
- BENEFIT: Restores correct GPIO reporting for SBS multi-battery systems
on 6.17+.
- RISK: Extremely low — single-line `!!` normalization, impossible to
regress correctness. Returns 0 or 1 as API requires.
- Ratio: Very favorable — trivial risk, real user benefit.
## PHASE 9: FINAL SYNTHESIS
**Step 9.1 - Evidence Compilation:**
FOR backporting:
- Fixes a real functional regression on stable 6.17.y/6.18.y/6.19.y
(batteries 2-4 return -EBADE)
- One-line, obviously correct `!!` normalization
- Reviewed by both GPIO maintainers (Linus Walleij, Bartosz Golaszewski)
- Applied by power supply maintainer (Sebastian Reichel)
- Precedent: sibling commit for qca807x (same fix pattern) is already in
6.17.y, 6.18.y, 6.19.y stable
- Applies cleanly (identical code in stable trees)
AGAINST backporting:
- Original submission had no `Fixes:` tag or `Cc: stable` (unlike
sibling ti-ads7950 patch)
- On older stable trees (<6.15) without the gpiolib wrapper, the bug is
purely theoretical
UNRESOLVED: Whether `ec2cceadfae72` will be backported first and make
this technically unnecessary — but even so, this patch properly fixes
the driver rather than relying on a workaround.
**Step 9.2 - Stable Rules Checklist:**
1. Obviously correct and tested: YES — trivial `!!`, reviewed by
maintainers
2. Real bug: YES — -EBADE returned for real GPIO reads on 6.17+
3. Important issue: YES (MEDIUM) — broken functionality on SBS multi-
battery systems
4. Small and contained: YES — 1 line
5. No new features: YES
6. Applies to stable: YES
**Step 9.3 - Exception Categories:**
Not a device-ID/quirk addition, but it's a small, surgical correctness
fix. Meets core stable criteria on its own merits.
**Step 9.4 - Decision:**
The patch fixes a real regression on stable kernels 6.17.y, 6.18.y, and
6.19.y (which contain the gpiolib sanitize wrapper from v6.15). It's a
one-line, obviously correct normalization endorsed by two GPIO subsystem
maintainers and the power supply maintainer. The sibling qca807x fix has
already been accepted into these same stable trees, establishing clear
precedent. For older stable trees lacking the wrapper, the fix is still
beneficial as a correctness improvement with zero risk.
## Verification
- [Phase 1] Parsed tags: confirmed author, two Reviewed-by entries from
GPIO maintainers, applied by power supply maintainer; no Fixes/Cc-
stable tags (unlike sibling ti-ads7950 patch)
- [Phase 2] Diff analysis: verified in original commit that exactly one
line changes `ret & BIT(off)` to `!!(ret & BIT(off))`
- [Phase 2] Verified `ngpio = SBSM_MAX_BATS = 4` via Read of
`drivers/power/supply/sbs-manager.c`, confirming off = 0..3 and
BIT(off) = 1, 2, 4, 8
- [Phase 3] `git log` showed driver introduced in `dbc4deda03fe6`
(v4.15, 2017), buggy code present since inception
- [Phase 3] `git log --grep="normalize return value"` found 2 sibling
Dmitry Torokhov commits (ti-ads7950 with `Cc: Stable`, qca807x with
`Fixes: 86ef402d805d`)
- [Phase 3] `git show 86ef402d805d` confirmed gpiolib wrapper returns
`-EBADE` for ret > 1
- [Phase 3] `git describe --contains 86ef402d805d` → `v6.15-rc1` —
confirms wrapper is in v6.15+
- [Phase 4] `b4 dig -c 5c2ffc0b215a8` located thread at
lore.kernel.org/all/aZYoL2MnTYU5FuQh@google.com/
- [Phase 4] Read `/tmp/sbsm_thread.mbox`: confirmed single-version
submission, positive reviews, quick application
- [Phase 4] Identified related commit `ec2cceadfae72` with `Cc:
stable@vger.kernel.org` acknowledging the wrapper broke drivers
- [Phase 5] Confirmed `sbsm_gpio_get_value` is assigned to `gc->get` and
reachable from userspace GPIO APIs
- [Phase 6] Verified with `git show stable-
push/linux-6.17.y:drivers/gpio/gpiolib.c` that 6.17.y has the EBADE-
returning wrapper
- [Phase 6] Verified same for 6.18.y and 6.19.y
- [Phase 6] Verified 5.10/5.15/6.1/6.6/6.12 do NOT have the wrapper (bug
is latent there)
- [Phase 6] Verified via `git show stable-
push/linux-6.17.y:drivers/power/supply/sbs-manager.c` that buggy `ret
& BIT(off)` is present
- [Phase 6] Verified qca807x sibling fix is already in
6.17.y/6.18.y/6.19.y (`cb8f0a3857386`, `554e8f2fbce86`)
- [Phase 6] Verified `ec2cceadfae72` alternative fix is NOT yet in those
stable trees
- [Phase 8] Failure mode: on 6.17/6.18/6.19 the wrapper converts
BIT(1/2/3) to -EBADE — verified by reading the wrapper implementation
**YES**
drivers/power/supply/sbs-manager.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/power/supply/sbs-manager.c b/drivers/power/supply/sbs-manager.c
index 6fe526222f7f4..343ad4ab4082c 100644
--- a/drivers/power/supply/sbs-manager.c
+++ b/drivers/power/supply/sbs-manager.c
@@ -199,7 +199,7 @@ static int sbsm_gpio_get_value(struct gpio_chip *gc, unsigned int off)
if (ret < 0)
return ret;
- return ret & BIT(off);
+ return !!(ret & BIT(off));
}
/*
--
2.53.0
^ permalink raw reply related
* [PATCH AUTOSEL 6.18] tools/power/turbostat: Fix microcode patch level output for AMD/Hygon
From: Sasha Levin @ 2026-04-20 13:20 UTC (permalink / raw)
To: patches, stable
Cc: Serhii Pievniev, Len Brown, Sasha Levin, lenb, patryk.wlazlyn,
linux-pm, linux-kernel
In-Reply-To: <20260420132314.1023554-1-sashal@kernel.org>
From: Serhii Pievniev <spevnev16@gmail.com>
[ Upstream commit a444083286434ec1fd127c5da11a3091e6013008 ]
turbostat always used the same logic to read the microcode patch level,
which is correct for Intel but not for AMD/Hygon.
While Intel stores the patch level in the upper 32 bits of MSR, AMD
stores it in the lower 32 bits, which causes turbostat to report the
microcode version as 0x0 on AMD/Hygon.
Fix by shifting right by 32 for non-AMD/Hygon, preserving the existing
behavior for Intel and unknown vendors.
Fixes: 3e4048466c39 ("tools/power turbostat: Add --no-msr option")
Signed-off-by: Serhii Pievniev <spevnev16@gmail.com>
Signed-off-by: Len Brown <len.brown@intel.com>
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
LLM Generated explanations, may be completely bogus:
Error: Failed to generate final synthesis
tools/power/x86/turbostat/turbostat.c | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/tools/power/x86/turbostat/turbostat.c b/tools/power/x86/turbostat/turbostat.c
index 1b5ca2f4e92ff..67dfd3eaad014 100644
--- a/tools/power/x86/turbostat/turbostat.c
+++ b/tools/power/x86/turbostat/turbostat.c
@@ -8842,10 +8842,13 @@ void process_cpuid()
edx_flags = edx;
if (!no_msr) {
- if (get_msr(sched_getcpu(), MSR_IA32_UCODE_REV, &ucode_patch))
+ if (get_msr(sched_getcpu(), MSR_IA32_UCODE_REV, &ucode_patch)) {
warnx("get_msr(UCODE)");
- else
+ } else {
ucode_patch_valid = true;
+ if (!authentic_amd && !hygon_genuine)
+ ucode_patch >>= 32;
+ }
}
/*
@@ -8860,7 +8863,7 @@ void process_cpuid()
fprintf(outf, "CPUID(1): family:model:stepping 0x%x:%x:%x (%d:%d:%d)",
family, model, stepping, family, model, stepping);
if (ucode_patch_valid)
- fprintf(outf, " microcode 0x%x", (unsigned int)((ucode_patch >> 32) & 0xFFFFFFFF));
+ fprintf(outf, " microcode 0x%x", (unsigned int)ucode_patch);
fputc('\n', outf);
fprintf(outf, "CPUID(0x80000000): max_extended_levels: 0x%x\n", max_extended_level);
--
2.53.0
^ permalink raw reply related
* Re: [PATCH v5 00/21] Virtual Swap Space
From: Nhat Pham @ 2026-04-20 16:02 UTC (permalink / raw)
To: Kairui Song
Cc: Liam.Howlett, akpm, apopple, axelrasmussen, baohua, baolin.wang,
bhe, byungchul, cgroups, chengming.zhou, chrisl, corbet, david,
dev.jain, gourry, hannes, hughd, jannh, joshua.hahnjy, lance.yang,
lenb, linux-doc, linux-kernel, linux-mm, linux-pm,
lorenzo.stoakes, matthew.brost, mhocko, muchun.song, npache,
pavel, peterx, peterz, pfalcato, rafael, rakie.kim,
roman.gushchin, rppt, ryan.roberts, shakeel.butt, shikemeng,
surenb, tglx, vbabka, weixugc, ying.huang, yosry.ahmed, yuanchu,
zhengqi.arch, ziy, kernel-team, riel
In-Reply-To: <CAMgjq7AiUr_Ntj51qoqvV+=XbEATjr7S4MH+rgD32T5pHfF7mg@mail.gmail.com>
On Mon, Mar 23, 2026 at 3:09 AM Kairui Song <ryncsn@gmail.com> wrote:
>
> On Sat, Mar 21, 2026 at 3:29 AM Nhat Pham <nphamcs@gmail.com> wrote:
> > This patch series is based on 6.19. There are a couple more
> > swap-related changes in mainline that I would need to coordinate
> > with, but I still want to send this out as an update for the
> > regressions reported by Kairui Song in [15]. It's probably easier
> > to just build this thing rather than dig through that series of
> > emails to get the fix patch :)
> >
> > Changelog:
> > * v4 -> v5:
> > * Fix a deadlock in memcg1_swapout (reported by syzbot [16]).
> > * Replace VM_WARN_ON(!spin_is_locked()) with lockdep_assert_held(),
> > and use guard(rcu) in vswap_cpu_dead
> > (reported by Peter Zijlstra [17]).
> > * v3 -> v4:
> > * Fix poor swap free batching behavior to alleviate a regression
> > (reported by Kairui Song).
>
> I tested the v5 (including the batched-free hotfix) and am still
> seeing significant regressions in both sequential and concurrent swap
> workloads
>
> Thanks for the update as I can see It's a lot of thoughtful work.
> Actually I did run some tests already with your previously posted
> hotfix based on v3. I didn't update the result because very
> unfortunately, I still see a major performance regression even with a
> very simple setup.
>
> BTW there seems a simpler way to reproduce that, just use memhog:
> sudo mkswap /dev/pmem0; sudo swapon /dev/pmem0; time memhog 48G; sudo swapoff -a
>
> Before:
> (I'm using fish shell on that test machine so this is fish time format):
> ________________________________________________________
> Executed in 20.80 secs fish external
> usr time 5.14 secs 0.00 millis 5.14 secs
> sys time 15.65 secs 1.17 millis 15.65 secs
> ________________________________________________________
> Executed in 21.69 secs fish external
> usr time 5.31 secs 725.00 micros 5.31 secs
> sys time 16.36 secs 579.00 micros 16.36 secs
> ________________________________________________________
> Executed in 21.86 secs fish external
> usr time 5.39 secs 1.02 millis 5.39 secs
> sys time 16.46 secs 0.27 millis 16.46 secs
>
> After:
> ________________________________________________________
> Executed in 30.77 secs fish external
> usr time 5.16 secs 767.00 micros 5.16 secs
> sys time 25.59 secs 580.00 micros 25.59 secs
> ________________________________________________________
> Executed in 37.47 secs fish external
> usr time 5.48 secs 0.00 micros 5.48 secs
> sys time 31.98 secs 674.00 micros 31.98 secs
> ________________________________________________________
> Executed in 31.34 secs fish external
> usr time 5.22 secs 0.00 millis 5.22 secs
> sys time 26.09 secs 1.30 millis 26.09 secs
>
> It's obviously a lot slower.
>
> pmem may seem rare but SSDs are good at sequential, and memhog uses
> the same filled page and backend like ZRAM has extremely low overhead
> for same filled pages. Results with ZRAM are very similar, and many
> production workloads have massive amounts of samefill memory.
>
> For example on the Android phone I'm using right now at this moment:
> # cat /sys/block/zram0/mm_stat
> 4283899904 1317373036 1370259456 0 1475977216 116457 1991851
> 87273 1793760
> ~450M of samefill page in ZRAM, we may see more on some server
> workload. And I'm seeing similar memhog results with ZRAM, pmem is
> just easier to setup and less noisy. also simulates high speed
> storage.
>
> I also ran the previous usemem matrix, which seems better than V3 but
> still pretty bad:
> Test: usemem --init-time -O -n 1 56G, 16G mem, 48G swap, avgs of 8 run.
> Before:
> Throughput (Sum): 528.98 MB/s Throughput (Mean): 526.113333 MB/s Free
> Latency: 3037932.888889
> After:
> Throughput (Sum): 453.74 MB/s Throughput (Mean): 454.875000 MB/s Free
> Latency: 5001144.500000 (~10%, 64% slower)
>
> I'm not sure why our results differ so much — perhaps different LRU
> settings, memory pressure ratios, or THP/mTHP configs? Here's my exact
> config in the attachment. Also includes the full log and info, with
> all debug options disabled for close to production. I ran it 8 times
> and just attached the first result log, it's all similar anyway, my
> test framework reboot the machine after each test run to reduce any
> potential noise.
>
> And the above tests are only about sequential performance, concurrent
> ones seem worse:
> Test: usemem --init-time -O -R -n 32 622M, 16G mem, 48G swap, avgs of 8 run.
> Before:
> Throughput (Sum): 5467.51 MB/s Throughput (Mean): 170.04 MB/s Free
> Latency: 28648.65
> After:
> Throughput (Sum): 4914.86 MB/s Throughput (Mean): 152.74 MB/s Free
> Latency: 67789.81 (~10%, 230% slower)
For this test case, I took my 16G (a bit less than that technically)
52 cores host, using zram as the backend and MGLRU, for a spin.
Keeping the same parameters as your usemem command, unfortunately, led
to massive thrashing (even with baseline kernel) - unfortunately zram
still used physical memory so the overcommit level is too large
(especially with random access pattern, i.e the -R flag).
I then tried reducing the 622M part to 480M, but the problem with that
is VSS5 did not show any regression - probably because the
overcommitting is too low, or not enough concurrency. I had to push
the concurrency up to 52 workers, allocating 300M each (which is
slightly more memory allocated overall than the 480 x 32 case), to
finally show the regression you reported. Variance was very big with 8
runs though (what I normally use for usemem these days), so I had to
do 20 runs per kernel - fortunately these runs are fast:
Metric baseline vss_v5 new_opt_v2 cc_v2
real (s) 15.0 +/- 0.8 18.3 +/- 1.8 15.1 +/- 1.0 14.7 +/- 1.0
sys (s) 396.4 +/- 31.1 511.9 +/- 60.3 404.1 +/- 34.5 392.4 +/- 39.9
tput (KB/s) 28188 +/- 6996 23287 +/- 6629 27999 +/- 6623 28744 +/- 7015
free (ms) 101.1 +/- 52.4 91.4 +/- 41.5 93.1 +/- 43.8 97.6 +/- 49.5
% real n/a +22.4% +0.7% -1.7%
% sys n/a +29.1% +1.9% -1.0%
% tput n/a -17.4% -0.7% +2.0%
% free n/a -9.6% -7.9% -3.5%
(I realized I mangled the output last time of the "memory reclaim
metrics table" table due to auto line break. Let's hope this is
better).
Strangely, no free regression. Hmmm.
But real, sys, and throughput regression are real. The optimizations
do close the gap to within noise level here too.
^ permalink raw reply
* Re: [PATCH v3 1/8] dt-bindings: mfd: khadas: Add new compatible for Khadas VIM4 MCU
From: Conor Dooley @ 2026-04-20 16:22 UTC (permalink / raw)
To: Ronald Claveau
Cc: Neil Armstrong, Lee Jones, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Andi Shyti, Kevin Hilman, Jerome Brunet,
Martin Blumenstingl, Beniamino Galvani, Rafael J. Wysocki,
Daniel Lezcano, Zhang Rui, Lukasz Luba, Liam Girdwood, Mark Brown,
linux-amlogic, devicetree, linux-kernel, linux-i2c,
linux-arm-kernel, linux-pm
In-Reply-To: <20260417-add-mcu-fan-khadas-vim4-v3-1-a6a7f570b11b@aliel.fr>
[-- Attachment #1: Type: text/plain, Size: 1338 bytes --]
On Fri, Apr 17, 2026 at 06:27:17PM +0200, Ronald Claveau wrote:
> The Khadas VIM4 MCU register is slightly different
> from previous boards' MCU.
> This board also features a switchable power source for its fan.
>
> Signed-off-by: Ronald Claveau <linux-kernel-dev@aliel.fr>
> ---
> Documentation/devicetree/bindings/mfd/khadas,mcu.yaml | 5 +++++
> 1 file changed, 5 insertions(+)
>
> diff --git a/Documentation/devicetree/bindings/mfd/khadas,mcu.yaml b/Documentation/devicetree/bindings/mfd/khadas,mcu.yaml
> index 084960fd5a1fd..a80718f7595ce 100644
> --- a/Documentation/devicetree/bindings/mfd/khadas,mcu.yaml
> +++ b/Documentation/devicetree/bindings/mfd/khadas,mcu.yaml
> @@ -18,6 +18,7 @@ properties:
> compatible:
> enum:
> - khadas,mcu # MCU revision is discoverable
> + - khadas,vim4-mcu # Different MCU variant, not discoverable
>
> "#cooling-cells": # Only needed for boards having FAN control feature
> const: 2
> @@ -25,6 +26,10 @@ properties:
> reg:
> maxItems: 1
>
> + fan-supply:
> + description: Phandle to the regulator that powers the fan.
> + $ref: /schemas/types.yaml#/definitions/phandle
Can you limit this by compatible please?
pw-bot: changes-requested
> +
> required:
> - compatible
> - reg
>
> --
> 2.49.0
>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply
* Re: [PATCH v11 12/14] cpuidle/poll_state: Wait for need-resched via tif_need_resched_relaxed_wait()
From: Okanovic, Haris @ 2026-04-20 16:57 UTC (permalink / raw)
To: ankur.a.arora@oracle.com
Cc: joao.m.martins@oracle.com, xueshuai@linux.alibaba.com,
david.laight.linux@gmail.com, boris.ostrovsky@oracle.com,
memxor@gmail.com, ashok.bhat@arm.com, zhenglifeng1@huawei.com,
konrad.wilk@oracle.com, cl@gentwo.org, akpm@linux-foundation.org,
linux-kernel@vger.kernel.org, catalin.marinas@arm.com,
ast@kernel.org, rdunlap@infradead.org, daniel.lezcano@linaro.org,
arnd@arndb.de, linux-arch@vger.kernel.org, will@kernel.org,
mark.rutland@arm.com, peterz@infradead.org, bpf@vger.kernel.org,
linux-arm-kernel@lists.infradead.org, Okanovic, Haris,
rafael@kernel.org, linux-pm@vger.kernel.org
In-Reply-To: <20260408122538.3610871-13-ankur.a.arora@oracle.com>
On Wed, 2026-04-08 at 17:55 +0530, Ankur Arora wrote:
> CAUTION: This email originated from outside of the organization. Do not click links or open attachments unless you can confirm the sender and know the content is safe.
>
>
>
> The inner loop in poll_idle() polls over the thread_info flags,
> waiting to see if the thread has TIF_NEED_RESCHED set. The loop
> exits once the condition is met, or if the poll time limit has
> been exceeded.
>
> To minimize the number of instructions executed in each iteration,
> the time check is rate-limited. In addition, each loop iteration
> executes cpu_relax() which on certain platforms provides a hint to
> the pipeline that the loop busy-waits, allowing the processor to
> reduce power consumption.
>
> Switch over to tif_need_resched_relaxed_wait() instead, since that
> provides exactly that.
>
> However, since we want to minimize power consumption in idle, building
> of cpuidle/poll_state.c continues to depend on CONFIG_ARCH_HAS_CPU_RELAX
> as that serves as an indicator that the platform supports an optimized
> version of tif_need_resched_relaxed_wait() (via
> smp_cond_load_acquire_timeout()).
>
> Cc: Rafael J. Wysocki <rafael@kernel.org>
> Cc: Daniel Lezcano <daniel.lezcano@linaro.org>
> Cc: linux-pm@vger.kernel.org
> Suggested-by: Rafael J. Wysocki <rafael@kernel.org>
> Acked-by: Rafael J. Wysocki (Intel) <rafael@kernel.org>
> Signed-off-by: Ankur Arora <ankur.a.arora@oracle.com>
> ---
> drivers/cpuidle/poll_state.c | 21 +--------------------
> 1 file changed, 1 insertion(+), 20 deletions(-)
>
> diff --git a/drivers/cpuidle/poll_state.c b/drivers/cpuidle/poll_state.c
> index c7524e4c522a..7443b3e971ba 100644
> --- a/drivers/cpuidle/poll_state.c
> +++ b/drivers/cpuidle/poll_state.c
> @@ -6,41 +6,22 @@
> #include <linux/cpuidle.h>
> #include <linux/export.h>
> #include <linux/irqflags.h>
> -#include <linux/sched.h>
> -#include <linux/sched/clock.h>
> #include <linux/sched/idle.h>
> #include <linux/sprintf.h>
> #include <linux/types.h>
>
> -#define POLL_IDLE_RELAX_COUNT 200
> -
> static int __cpuidle poll_idle(struct cpuidle_device *dev,
> struct cpuidle_driver *drv, int index)
> {
> - u64 time_start;
> -
> - time_start = local_clock_noinstr();
> -
> dev->poll_time_limit = false;
>
> raw_local_irq_enable();
> if (!current_set_polling_and_test()) {
> - unsigned int loop_count = 0;
> u64 limit;
>
> limit = cpuidle_poll_time(drv, dev);
>
> - while (!need_resched()) {
> - cpu_relax();
> - if (loop_count++ < POLL_IDLE_RELAX_COUNT)
> - continue;
> -
> - loop_count = 0;
> - if (local_clock_noinstr() - time_start > limit) {
> - dev->poll_time_limit = true;
> - break;
> - }
> - }
> + dev->poll_time_limit = !tif_need_resched_relaxed_wait(limit);
> }
> raw_local_irq_disable();
>
> --
> 2.31.1
>
Hi Ankur,
Tested atop latest mainline d60bc1401 with the rest of your haltpoll
changes from separate thread:
~10% improvement in `perf sched bench pipe` micro and ~4-6% throughput
improvements in mysql,
postgresql, cassandra, and memcached in under-loaded configurations.
Tested on AWS Graviton3 and
Graviton4, ARM Neoverse V1 and V2 cores respectively.
I hope this series can merge soon. It's been stuck in review for more
than 2 years.
Tested-by: Haris Okanovic <harisokn@amazon.com>
Regards,
Haris Okanovic
AWS Graviton Software
^ permalink raw reply
* [PATCH v3 00/10] thermal: samsung: Add support for Google GS101 TMU
From: Tudor Ambarus @ 2026-04-20 17:39 UTC (permalink / raw)
To: Rafael J. Wysocki, Zhang Rui, Lukasz Luba, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Krzysztof Kozlowski,
Alim Akhtar, Bartlomiej Zolnierkiewicz, Kees Cook,
Gustavo A. R. Silva, Peter Griffin, André Draszik,
Daniel Lezcano, Sylwester Nawrocki, Chanwoo Choi,
Michael Turquette, Stephen Boyd, Lee Jones
Cc: willmcvicker, jyescas, shin.son, linux-samsung-soc, linux-kernel,
linux-pm, devicetree, linux-arm-kernel, linux-hardening,
linux-clk, Tudor Ambarus, Krzysztof Kozlowski
Add support for the Thermal Management Unit (TMU) on the Google GS101
SoC.
The GS101 TMU implementation utilizes a hybrid architecture where
management is shared between the kernel and the Alive Clock and
Power Manager (ACPM) firmware. This hybrid ACPM TMU architecture is
also present on other Samsung Exynos SoCs (e.g., AutoV920, Exynos850).
Dependencies
============
- firmware patches 2, 3, 4, 5, 6: required by the thermal driver
(patch 7).
- bindings (patch 1): required for DTS validation.
- thermal driver patch 7: required by defconfig (patch 10) - logical
dependency.
Given the thermal driver is a new addition, I suggest everything to go
through the Samsung SoC tree, with ACKs from the Thermal maintainers.
The MFD and clk maintainers are included because of the cleanup patches
(3 and 4). ACPM updated some structures that the mfd and clk client
drivers are using, so these patches shall naturally go via the Samsung
SoC tree.
If the Thermal maintainers prefer to take the bindings and the thermal
driver patches via their tree we'll need:
- an immutable branch containing the firmware patches (2, 3, 4, 5, 6)
from the Samsung SoC tree to serve as a base for the thermal driver.
- an immutable branch containing the bindings and the thermal driver
from the thermal tree to serve as a base for the dts and defconfig.
Architecture Overview
=====================
The hardware supports two parallel control paths. For this
implementation, responsibilities are split as follows:
1. Kernel Responsibility:
- maintain direct memory-mapped access to the interrupt pending
(INTPEND) registers to identify thermal events.
- map physical hardware interrupts to logical thermal zones.
- coordinate functional operations through the ACPM IPC protocol.
2. Firmware Responsibility (ACPM):
- handle sensor initialization.
- manage thermal thresholds configuration.
- perform temperature acquisition and expose data via IPC.
Sensor Mapping (One-to-Many)
============================
The SoC contains multiple physical temperature sensors, but the ACPM
firmware abstracts these into logical groups (Clusters) for reporting:
- ACPM Sensor 0 (Big Cluster): Aggregates physical sensors 0, 6, 7, 8, 9.
- ACPM Sensor 1 (Mid Cluster): Aggregates physical sensors 4, 5.
- ACPM Sensor 2 (Little Cluster): Aggregates physical sensors 1, 2.
The driver maps physical interrupt bits back to these logical parents.
When an interrupt fires, the driver checks the bitmask in the INTPEND
registers and updates the corresponding logical thermal zone.
Signed-off-by: Tudor Ambarus <tudor.ambarus@linaro.org>
---
Changes in v3:
- thermal driver: use .set_trips() instead of .set_trip_point()
- new cleaning/prerequisite patches for firmware/acpm:
- firmware: samsung: acpm: Make acpm_ops const and access via pointer
- firmware: samsung: acpm: Drop redundant _ops suffix in acpm_ops members
- firmware: samsung: acpm: Consolidate transfer initialization helper
- firmware: acpm: TMU helpers - check return value from the firmware
- overall change: emphasize that the ACPM TMU hibrid approach applies to
other Samsung SoCs as well (Exynos850, AutoV920).
- dts: drop active trip points, update trip point values
- collect R-b tags
- Link to v2: https://lore.kernel.org/r/20260119-acpm-tmu-v2-0-e02a834f04c6@linaro.org
Changes in v2:
- architecture: switch from a syscon/MFD approach to a thermal-sensor
node with a phandle to the ACPM interface
- bindings: address Krzysztof's feedback, drop redundencies,
interrupts description.
- firmware: introduce devm_acpm_get_by_phandle() to standardize IPC
handle acquisition.
- thermal driver: drop compatible's data and use the static data from
the driver directly.
- defconfig, make EXYNOS_ACPM_THERMAL a module
- Link to v1: https://lore.kernel.org/r/20260114-acpm-tmu-v1-0-cfe56d93e90f@linaro.org
---
Tudor Ambarus (10):
dt-bindings: thermal: Add Google GS101 TMU
firmware: samsung: acpm: Consolidate transfer initialization helper
firmware: samsung: acpm: Drop redundant _ops suffix in acpm_ops members
firmware: samsung: acpm: Make acpm_ops const and access via pointer
firmware: samsung: acpm: Add TMU protocol support
firmware: samsung: acpm: Add devm_acpm_get_by_phandle helper
thermal: samsung: Add Exynos ACPM TMU driver GS101
MAINTAINERS: Add entry for Samsung Exynos ACPM thermal driver
arm64: dts: exynos: gs101: Add thermal management unit
arm64: defconfig: enable Exynos ACPM thermal support
.../bindings/thermal/google,gs101-tmu-top.yaml | 68 +++
MAINTAINERS | 8 +
arch/arm64/boot/dts/exynos/google/gs101-tmu.dtsi | 136 ++++++
arch/arm64/boot/dts/exynos/google/gs101.dtsi | 18 +
arch/arm64/configs/defconfig | 1 +
drivers/clk/samsung/clk-acpm.c | 8 +-
drivers/firmware/samsung/Makefile | 1 +
drivers/firmware/samsung/exynos-acpm-dvfs.c | 17 +-
drivers/firmware/samsung/exynos-acpm-pmic.c | 20 +-
drivers/firmware/samsung/exynos-acpm-tmu.c | 240 +++++++++
drivers/firmware/samsung/exynos-acpm-tmu.h | 28 ++
drivers/firmware/samsung/exynos-acpm.c | 94 +++-
drivers/firmware/samsung/exynos-acpm.h | 2 +
drivers/mfd/sec-acpm.c | 6 +-
drivers/thermal/samsung/Kconfig | 17 +
drivers/thermal/samsung/Makefile | 2 +
drivers/thermal/samsung/acpm-tmu.c | 539 +++++++++++++++++++++
.../linux/firmware/samsung/exynos-acpm-protocol.h | 32 +-
18 files changed, 1176 insertions(+), 61 deletions(-)
---
base-commit: c1f49dea2b8f335813d3b348fd39117fb8efb428
change-id: 20260113-acpm-tmu-27e21f0e2c3b
Best regards,
--
Tudor Ambarus <tudor.ambarus@linaro.org>
^ permalink raw reply
* [PATCH v3 01/10] dt-bindings: thermal: Add Google GS101 TMU
From: Tudor Ambarus @ 2026-04-20 17:39 UTC (permalink / raw)
To: Rafael J. Wysocki, Zhang Rui, Lukasz Luba, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Krzysztof Kozlowski,
Alim Akhtar, Bartlomiej Zolnierkiewicz, Kees Cook,
Gustavo A. R. Silva, Peter Griffin, André Draszik,
Daniel Lezcano, Sylwester Nawrocki, Chanwoo Choi,
Michael Turquette, Stephen Boyd, Lee Jones
Cc: willmcvicker, jyescas, shin.son, linux-samsung-soc, linux-kernel,
linux-pm, devicetree, linux-arm-kernel, linux-hardening,
linux-clk, Tudor Ambarus, Krzysztof Kozlowski
In-Reply-To: <20260420-acpm-tmu-v3-0-3dc8e93f0b26@linaro.org>
Document the Thermal Management Unit (TMU) found on the Google GS101 SoC.
The GS101 TMU utilizes a hybrid control model shared between the
Application Processor (AP) and the ACPM (Alive Clock and Power Manager)
firmware. This hybrid ACPM TMU architecture is also present on other
Samsung Exynos SoCs (e.g., AutoV920, Exynos850).
While the TMU is a standard memory-mapped IP block, on this platform
the AP's direct register access is restricted to the interrupt pending
(INTPEND) registers for event identification. High-level functional
tasks, such as sensor initialization, threshold programming, and
temperature reads, are delegated to the ACPM firmware.
Signed-off-by: Tudor Ambarus <tudor.ambarus@linaro.org>
Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
---
.../bindings/thermal/google,gs101-tmu-top.yaml | 68 ++++++++++++++++++++++
1 file changed, 68 insertions(+)
diff --git a/Documentation/devicetree/bindings/thermal/google,gs101-tmu-top.yaml b/Documentation/devicetree/bindings/thermal/google,gs101-tmu-top.yaml
new file mode 100644
index 000000000000..d0eb2393d581
--- /dev/null
+++ b/Documentation/devicetree/bindings/thermal/google,gs101-tmu-top.yaml
@@ -0,0 +1,68 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/thermal/google,gs101-tmu-top.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Samsung Exynos ACPM Thermal Management Unit (TMU)
+
+maintainers:
+ - Tudor Ambarus <tudor.ambarus@linaro.org>
+
+description:
+ The Samsung Exynos ACPM TMU is a thermal sensor block found on Exynos
+ based platforms (such as Google GS101 and Exynos850). It supports
+ both direct register-level access and firmware-mediated management
+ via the ACPM (Alive Clock and Power Manager) firmware.
+
+ On these platforms, the hardware is managed in a hybrid fashion. The
+ Application Processor (AP) maintains direct memory-mapped access
+ exclusively to the interrupt pending registers to identify thermal
+ events. All other functional aspects - including sensor
+ initialization, threshold configuration, and temperature acquisition
+ - are handled by the ACPM firmware. The AP coordinates these
+ operations through the ACPM IPC protocol.
+
+properties:
+ compatible:
+ const: google,gs101-tmu-top
+
+ reg:
+ maxItems: 1
+
+ clocks:
+ items:
+ - description: APB peripheral clock (PCLK) for TMU register access.
+
+ interrupts:
+ maxItems: 1
+
+ "#thermal-sensor-cells":
+ const: 1
+
+ samsung,acpm-ipc:
+ $ref: /schemas/types.yaml#/definitions/phandle
+ description: Phandle to the ACPM IPC node.
+
+required:
+ - compatible
+ - reg
+ - clocks
+ - interrupts
+ - "#thermal-sensor-cells"
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+ #include <dt-bindings/clock/google,gs101.h>
+
+ thermal-sensor@100a0000 {
+ compatible = "google,gs101-tmu-top";
+ reg = <0x100a0000 0x800>;
+ clocks = <&cmu_misc CLK_GOUT_MISC_TMU_TOP_PCLK>;
+ interrupts = <GIC_SPI 769 IRQ_TYPE_LEVEL_HIGH 0>;
+ #thermal-sensor-cells = <1>;
+ samsung,acpm-ipc = <&acpm_ipc>;
+ };
--
2.54.0.rc1.555.g9c883467ad-goog
^ permalink raw reply related
* [PATCH v3 03/10] firmware: samsung: acpm: Drop redundant _ops suffix in acpm_ops members
From: Tudor Ambarus @ 2026-04-20 17:39 UTC (permalink / raw)
To: Rafael J. Wysocki, Zhang Rui, Lukasz Luba, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Krzysztof Kozlowski,
Alim Akhtar, Bartlomiej Zolnierkiewicz, Kees Cook,
Gustavo A. R. Silva, Peter Griffin, André Draszik,
Daniel Lezcano, Sylwester Nawrocki, Chanwoo Choi,
Michael Turquette, Stephen Boyd, Lee Jones
Cc: willmcvicker, jyescas, shin.son, linux-samsung-soc, linux-kernel,
linux-pm, devicetree, linux-arm-kernel, linux-hardening,
linux-clk, Tudor Ambarus
In-Reply-To: <20260420-acpm-tmu-v3-0-3dc8e93f0b26@linaro.org>
Rename the `dvfs_ops` and `pmic_ops` members of `struct acpm_ops` to
`dvfs` and `pmic` respectively.
Since these members are housed within the `acpm_ops` structure and
utilize the `acpm_*_ops` types, the `_ops` suffix on the variable names
creates unnecessary redundancy (e.g., `handle.ops.dvfs_ops`).
This cleanup removes the stuttering, leading to cleaner consumer code.
Signed-off-by: Tudor Ambarus <tudor.ambarus@linaro.org>
---
drivers/clk/samsung/clk-acpm.c | 8 ++++----
drivers/firmware/samsung/exynos-acpm.c | 4 ++--
drivers/mfd/sec-acpm.c | 6 +++---
include/linux/firmware/samsung/exynos-acpm-protocol.h | 4 ++--
4 files changed, 11 insertions(+), 11 deletions(-)
diff --git a/drivers/clk/samsung/clk-acpm.c b/drivers/clk/samsung/clk-acpm.c
index d8944160793a..93667777094c 100644
--- a/drivers/clk/samsung/clk-acpm.c
+++ b/drivers/clk/samsung/clk-acpm.c
@@ -68,8 +68,8 @@ static unsigned long acpm_clk_recalc_rate(struct clk_hw *hw,
{
struct acpm_clk *clk = to_acpm_clk(hw);
- return clk->handle->ops.dvfs_ops.get_rate(clk->handle,
- clk->mbox_chan_id, clk->id);
+ return clk->handle->ops.dvfs.get_rate(clk->handle, clk->mbox_chan_id,
+ clk->id);
}
static int acpm_clk_determine_rate(struct clk_hw *hw,
@@ -89,8 +89,8 @@ static int acpm_clk_set_rate(struct clk_hw *hw, unsigned long rate,
{
struct acpm_clk *clk = to_acpm_clk(hw);
- return clk->handle->ops.dvfs_ops.set_rate(clk->handle,
- clk->mbox_chan_id, clk->id, rate);
+ return clk->handle->ops.dvfs.set_rate(clk->handle, clk->mbox_chan_id,
+ clk->id, rate);
}
static const struct clk_ops acpm_clk_ops = {
diff --git a/drivers/firmware/samsung/exynos-acpm.c b/drivers/firmware/samsung/exynos-acpm.c
index 8b2529e50328..39d3d2317659 100644
--- a/drivers/firmware/samsung/exynos-acpm.c
+++ b/drivers/firmware/samsung/exynos-acpm.c
@@ -616,8 +616,8 @@ static int acpm_channels_init(struct acpm_info *acpm)
*/
static void acpm_setup_ops(struct acpm_info *acpm)
{
- struct acpm_dvfs_ops *dvfs_ops = &acpm->handle.ops.dvfs_ops;
- struct acpm_pmic_ops *pmic_ops = &acpm->handle.ops.pmic_ops;
+ struct acpm_dvfs_ops *dvfs_ops = &acpm->handle.ops.dvfs;
+ struct acpm_pmic_ops *pmic_ops = &acpm->handle.ops.pmic;
dvfs_ops->set_rate = acpm_dvfs_set_rate;
dvfs_ops->get_rate = acpm_dvfs_get_rate;
diff --git a/drivers/mfd/sec-acpm.c b/drivers/mfd/sec-acpm.c
index 0e23b9d9f7ee..9e15b260b8df 100644
--- a/drivers/mfd/sec-acpm.c
+++ b/drivers/mfd/sec-acpm.c
@@ -391,7 +391,7 @@ static int sec_pmic_acpm_bus_write(void *context, const void *data,
{
struct sec_pmic_acpm_bus_context *ctx = context;
struct acpm_handle *acpm = ctx->shared->acpm;
- const struct acpm_pmic_ops *pmic_ops = &acpm->ops.pmic_ops;
+ const struct acpm_pmic_ops *pmic_ops = &acpm->ops.pmic;
size_t val_count = count - BITS_TO_BYTES(ACPM_ADDR_BITS);
const u8 *d = data;
const u8 *vals = &d[BITS_TO_BYTES(ACPM_ADDR_BITS)];
@@ -411,7 +411,7 @@ static int sec_pmic_acpm_bus_read(void *context, const void *reg_buf, size_t reg
{
struct sec_pmic_acpm_bus_context *ctx = context;
struct acpm_handle *acpm = ctx->shared->acpm;
- const struct acpm_pmic_ops *pmic_ops = &acpm->ops.pmic_ops;
+ const struct acpm_pmic_ops *pmic_ops = &acpm->ops.pmic;
const u8 *r = reg_buf;
u8 reg;
@@ -430,7 +430,7 @@ static int sec_pmic_acpm_bus_reg_update_bits(void *context, unsigned int reg, un
{
struct sec_pmic_acpm_bus_context *ctx = context;
struct acpm_handle *acpm = ctx->shared->acpm;
- const struct acpm_pmic_ops *pmic_ops = &acpm->ops.pmic_ops;
+ const struct acpm_pmic_ops *pmic_ops = &acpm->ops.pmic;
return pmic_ops->update_reg(acpm, ctx->shared->acpm_chan_id, ctx->type, reg & 0xff,
ctx->shared->speedy_channel, val, mask);
diff --git a/include/linux/firmware/samsung/exynos-acpm-protocol.h b/include/linux/firmware/samsung/exynos-acpm-protocol.h
index 13f17dc4443b..62a3eb450067 100644
--- a/include/linux/firmware/samsung/exynos-acpm-protocol.h
+++ b/include/linux/firmware/samsung/exynos-acpm-protocol.h
@@ -35,8 +35,8 @@ struct acpm_pmic_ops {
};
struct acpm_ops {
- struct acpm_dvfs_ops dvfs_ops;
- struct acpm_pmic_ops pmic_ops;
+ struct acpm_dvfs_ops dvfs;
+ struct acpm_pmic_ops pmic;
};
/**
--
2.54.0.rc1.555.g9c883467ad-goog
^ permalink raw reply related
* [PATCH v3 04/10] firmware: samsung: acpm: Make acpm_ops const and access via pointer
From: Tudor Ambarus @ 2026-04-20 17:39 UTC (permalink / raw)
To: Rafael J. Wysocki, Zhang Rui, Lukasz Luba, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Krzysztof Kozlowski,
Alim Akhtar, Bartlomiej Zolnierkiewicz, Kees Cook,
Gustavo A. R. Silva, Peter Griffin, André Draszik,
Daniel Lezcano, Sylwester Nawrocki, Chanwoo Choi,
Michael Turquette, Stephen Boyd, Lee Jones
Cc: willmcvicker, jyescas, shin.son, linux-samsung-soc, linux-kernel,
linux-pm, devicetree, linux-arm-kernel, linux-hardening,
linux-clk, Tudor Ambarus
In-Reply-To: <20260420-acpm-tmu-v3-0-3dc8e93f0b26@linaro.org>
Replace the embedded `struct acpm_ops` inside `struct acpm_handle` with
a pointer to a `const struct acpm_ops`.
Previously, the operations structure was embedded directly within the
handle and populated dynamically at runtime via `acpm_setup_ops()`.
This resulted in mutable function pointers and unnecessary per-instance
memory overhead.
By defining `exynos_acpm_driver_ops` statically as a `const` structure,
the function pointers are now safely housed in the read-only `.rodata`
section. This improves security by preventing function pointer
overwrites, saves memory, and slightly reduces initialization overhead
in `acpm_probe()`.
Consequently, update all consumer drivers (clk, mfd) to access the
operations via the new pointer indirection (`->ops->`). Finally, fix
the previously empty kernel-doc description for the ops member to
reflect its new pointer nature.
Signed-off-by: Tudor Ambarus <tudor.ambarus@linaro.org>
---
drivers/clk/samsung/clk-acpm.c | 8 ++---
drivers/firmware/samsung/exynos-acpm.c | 36 ++++++++++------------
drivers/mfd/sec-acpm.c | 6 ++--
.../linux/firmware/samsung/exynos-acpm-protocol.h | 4 +--
4 files changed, 25 insertions(+), 29 deletions(-)
diff --git a/drivers/clk/samsung/clk-acpm.c b/drivers/clk/samsung/clk-acpm.c
index 93667777094c..953ca8d5720a 100644
--- a/drivers/clk/samsung/clk-acpm.c
+++ b/drivers/clk/samsung/clk-acpm.c
@@ -68,8 +68,8 @@ static unsigned long acpm_clk_recalc_rate(struct clk_hw *hw,
{
struct acpm_clk *clk = to_acpm_clk(hw);
- return clk->handle->ops.dvfs.get_rate(clk->handle, clk->mbox_chan_id,
- clk->id);
+ return clk->handle->ops->dvfs.get_rate(clk->handle, clk->mbox_chan_id,
+ clk->id);
}
static int acpm_clk_determine_rate(struct clk_hw *hw,
@@ -89,8 +89,8 @@ static int acpm_clk_set_rate(struct clk_hw *hw, unsigned long rate,
{
struct acpm_clk *clk = to_acpm_clk(hw);
- return clk->handle->ops.dvfs.set_rate(clk->handle, clk->mbox_chan_id,
- clk->id, rate);
+ return clk->handle->ops->dvfs.set_rate(clk->handle, clk->mbox_chan_id,
+ clk->id, rate);
}
static const struct clk_ops acpm_clk_ops = {
diff --git a/drivers/firmware/samsung/exynos-acpm.c b/drivers/firmware/samsung/exynos-acpm.c
index 39d3d2317659..4f2ad84cd783 100644
--- a/drivers/firmware/samsung/exynos-acpm.c
+++ b/drivers/firmware/samsung/exynos-acpm.c
@@ -610,30 +610,26 @@ static int acpm_channels_init(struct acpm_info *acpm)
return 0;
}
-/**
- * acpm_setup_ops() - setup the operations structures.
- * @acpm: pointer to the driver data.
- */
-static void acpm_setup_ops(struct acpm_info *acpm)
-{
- struct acpm_dvfs_ops *dvfs_ops = &acpm->handle.ops.dvfs;
- struct acpm_pmic_ops *pmic_ops = &acpm->handle.ops.pmic;
-
- dvfs_ops->set_rate = acpm_dvfs_set_rate;
- dvfs_ops->get_rate = acpm_dvfs_get_rate;
-
- pmic_ops->read_reg = acpm_pmic_read_reg;
- pmic_ops->bulk_read = acpm_pmic_bulk_read;
- pmic_ops->write_reg = acpm_pmic_write_reg;
- pmic_ops->bulk_write = acpm_pmic_bulk_write;
- pmic_ops->update_reg = acpm_pmic_update_reg;
-}
-
static void acpm_clk_pdev_unregister(void *data)
{
platform_device_unregister(data);
}
+static const struct acpm_ops exynos_acpm_driver_ops = {
+ .dvfs = {
+ .set_rate = acpm_dvfs_set_rate,
+ .get_rate = acpm_dvfs_get_rate,
+ },
+
+ .pmic = {
+ .read_reg = acpm_pmic_read_reg,
+ .bulk_read = acpm_pmic_bulk_read,
+ .write_reg = acpm_pmic_write_reg,
+ .bulk_write = acpm_pmic_bulk_write,
+ .update_reg = acpm_pmic_update_reg,
+ },
+};
+
static int acpm_probe(struct platform_device *pdev)
{
const struct acpm_match_data *match_data;
@@ -674,7 +670,7 @@ static int acpm_probe(struct platform_device *pdev)
if (ret)
return ret;
- acpm_setup_ops(acpm);
+ acpm->handle.ops = &exynos_acpm_driver_ops;
platform_set_drvdata(pdev, acpm);
diff --git a/drivers/mfd/sec-acpm.c b/drivers/mfd/sec-acpm.c
index 9e15b260b8df..3397d13d3b7f 100644
--- a/drivers/mfd/sec-acpm.c
+++ b/drivers/mfd/sec-acpm.c
@@ -391,7 +391,7 @@ static int sec_pmic_acpm_bus_write(void *context, const void *data,
{
struct sec_pmic_acpm_bus_context *ctx = context;
struct acpm_handle *acpm = ctx->shared->acpm;
- const struct acpm_pmic_ops *pmic_ops = &acpm->ops.pmic;
+ const struct acpm_pmic_ops *pmic_ops = &acpm->ops->pmic;
size_t val_count = count - BITS_TO_BYTES(ACPM_ADDR_BITS);
const u8 *d = data;
const u8 *vals = &d[BITS_TO_BYTES(ACPM_ADDR_BITS)];
@@ -411,7 +411,7 @@ static int sec_pmic_acpm_bus_read(void *context, const void *reg_buf, size_t reg
{
struct sec_pmic_acpm_bus_context *ctx = context;
struct acpm_handle *acpm = ctx->shared->acpm;
- const struct acpm_pmic_ops *pmic_ops = &acpm->ops.pmic;
+ const struct acpm_pmic_ops *pmic_ops = &acpm->ops->pmic;
const u8 *r = reg_buf;
u8 reg;
@@ -430,7 +430,7 @@ static int sec_pmic_acpm_bus_reg_update_bits(void *context, unsigned int reg, un
{
struct sec_pmic_acpm_bus_context *ctx = context;
struct acpm_handle *acpm = ctx->shared->acpm;
- const struct acpm_pmic_ops *pmic_ops = &acpm->ops.pmic;
+ const struct acpm_pmic_ops *pmic_ops = &acpm->ops->pmic;
return pmic_ops->update_reg(acpm, ctx->shared->acpm_chan_id, ctx->type, reg & 0xff,
ctx->shared->speedy_channel, val, mask);
diff --git a/include/linux/firmware/samsung/exynos-acpm-protocol.h b/include/linux/firmware/samsung/exynos-acpm-protocol.h
index 62a3eb450067..e13d9ac73ff6 100644
--- a/include/linux/firmware/samsung/exynos-acpm-protocol.h
+++ b/include/linux/firmware/samsung/exynos-acpm-protocol.h
@@ -41,10 +41,10 @@ struct acpm_ops {
/**
* struct acpm_handle - Reference to an initialized protocol instance
- * @ops:
+ * @ops: pointer to the constant ACPM protocol operations.
*/
struct acpm_handle {
- struct acpm_ops ops;
+ const struct acpm_ops *ops;
};
struct device;
--
2.54.0.rc1.555.g9c883467ad-goog
^ permalink raw reply related
* [PATCH v3 02/10] firmware: samsung: acpm: Consolidate transfer initialization helper
From: Tudor Ambarus @ 2026-04-20 17:39 UTC (permalink / raw)
To: Rafael J. Wysocki, Zhang Rui, Lukasz Luba, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Krzysztof Kozlowski,
Alim Akhtar, Bartlomiej Zolnierkiewicz, Kees Cook,
Gustavo A. R. Silva, Peter Griffin, André Draszik,
Daniel Lezcano, Sylwester Nawrocki, Chanwoo Choi,
Michael Turquette, Stephen Boyd, Lee Jones
Cc: willmcvicker, jyescas, shin.son, linux-samsung-soc, linux-kernel,
linux-pm, devicetree, linux-arm-kernel, linux-hardening,
linux-clk, Tudor Ambarus
In-Reply-To: <20260420-acpm-tmu-v3-0-3dc8e93f0b26@linaro.org>
Both the DVFS and PMIC ACPM sub-drivers implement their own identical
local helper functions (acpm_dvfs_set_xfer and acpm_pmic_set_xfer) to
initialize the acpm_xfer structure before sending an IPC message.
Move this logic into a single centralized helper, acpm_set_xfer(),
in the core ACPM driver to reduce boilerplate and code duplication.
In addition to cleaning up the DVFS and PMIC implementations, this
centralized method will also be utilized by the upcoming Exynos ACPM
Thermal Management Unit (TMU) driver.
Signed-off-by: Tudor Ambarus <tudor.ambarus@linaro.org>
---
drivers/firmware/samsung/exynos-acpm-dvfs.c | 17 ++---------------
drivers/firmware/samsung/exynos-acpm-pmic.c | 20 +++++---------------
drivers/firmware/samsung/exynos-acpm.c | 23 +++++++++++++++++++++++
drivers/firmware/samsung/exynos-acpm.h | 2 ++
4 files changed, 32 insertions(+), 30 deletions(-)
diff --git a/drivers/firmware/samsung/exynos-acpm-dvfs.c b/drivers/firmware/samsung/exynos-acpm-dvfs.c
index 06bdf62dea1f..7266312ef5a6 100644
--- a/drivers/firmware/samsung/exynos-acpm-dvfs.c
+++ b/drivers/firmware/samsung/exynos-acpm-dvfs.c
@@ -21,19 +21,6 @@
#define ACPM_DVFS_FREQ_REQ 0
#define ACPM_DVFS_FREQ_GET 1
-static void acpm_dvfs_set_xfer(struct acpm_xfer *xfer, u32 *cmd, size_t cmdlen,
- unsigned int acpm_chan_id, bool response)
-{
- xfer->acpm_chan_id = acpm_chan_id;
- xfer->txcnt = cmdlen;
- xfer->txd = cmd;
-
- if (response) {
- xfer->rxcnt = cmdlen;
- xfer->rxd = cmd;
- }
-}
-
static void acpm_dvfs_init_set_rate_cmd(u32 cmd[4], unsigned int clk_id,
unsigned long rate)
{
@@ -51,7 +38,7 @@ int acpm_dvfs_set_rate(struct acpm_handle *handle,
u32 cmd[4];
acpm_dvfs_init_set_rate_cmd(cmd, clk_id, rate);
- acpm_dvfs_set_xfer(&xfer, cmd, ARRAY_SIZE(cmd), acpm_chan_id, false);
+ acpm_set_xfer(&xfer, cmd, ARRAY_SIZE(cmd), acpm_chan_id, false);
return acpm_do_xfer(handle, &xfer);
}
@@ -71,7 +58,7 @@ unsigned long acpm_dvfs_get_rate(struct acpm_handle *handle,
int ret;
acpm_dvfs_init_get_rate_cmd(cmd, clk_id);
- acpm_dvfs_set_xfer(&xfer, cmd, ARRAY_SIZE(cmd), acpm_chan_id, true);
+ acpm_set_xfer(&xfer, cmd, ARRAY_SIZE(cmd), acpm_chan_id, true);
ret = acpm_do_xfer(handle, &xfer);
if (ret)
diff --git a/drivers/firmware/samsung/exynos-acpm-pmic.c b/drivers/firmware/samsung/exynos-acpm-pmic.c
index 0c50993cc9a8..f032f2c69685 100644
--- a/drivers/firmware/samsung/exynos-acpm-pmic.c
+++ b/drivers/firmware/samsung/exynos-acpm-pmic.c
@@ -58,16 +58,6 @@ static inline u32 acpm_pmic_get_bulk(u32 data, unsigned int i)
return (data >> (ACPM_PMIC_BULK_SHIFT * i)) & ACPM_PMIC_BULK_MASK;
}
-static void acpm_pmic_set_xfer(struct acpm_xfer *xfer, u32 *cmd, size_t cmdlen,
- unsigned int acpm_chan_id)
-{
- xfer->txd = cmd;
- xfer->rxd = cmd;
- xfer->txcnt = cmdlen;
- xfer->rxcnt = cmdlen;
- xfer->acpm_chan_id = acpm_chan_id;
-}
-
static void acpm_pmic_init_read_cmd(u32 cmd[4], u8 type, u8 reg, u8 chan)
{
cmd[0] = FIELD_PREP(ACPM_PMIC_TYPE, type) |
@@ -86,7 +76,7 @@ int acpm_pmic_read_reg(struct acpm_handle *handle,
int ret;
acpm_pmic_init_read_cmd(cmd, type, reg, chan);
- acpm_pmic_set_xfer(&xfer, cmd, ARRAY_SIZE(cmd), acpm_chan_id);
+ acpm_set_xfer(&xfer, cmd, ARRAY_SIZE(cmd), acpm_chan_id, true);
ret = acpm_do_xfer(handle, &xfer);
if (ret)
@@ -119,7 +109,7 @@ int acpm_pmic_bulk_read(struct acpm_handle *handle,
return -EINVAL;
acpm_pmic_init_bulk_read_cmd(cmd, type, reg, chan, count);
- acpm_pmic_set_xfer(&xfer, cmd, ARRAY_SIZE(cmd), acpm_chan_id);
+ acpm_set_xfer(&xfer, cmd, ARRAY_SIZE(cmd), acpm_chan_id, true);
ret = acpm_do_xfer(handle, &xfer);
if (ret)
@@ -159,7 +149,7 @@ int acpm_pmic_write_reg(struct acpm_handle *handle,
int ret;
acpm_pmic_init_write_cmd(cmd, type, reg, chan, value);
- acpm_pmic_set_xfer(&xfer, cmd, ARRAY_SIZE(cmd), acpm_chan_id);
+ acpm_set_xfer(&xfer, cmd, ARRAY_SIZE(cmd), acpm_chan_id, true);
ret = acpm_do_xfer(handle, &xfer);
if (ret)
@@ -199,7 +189,7 @@ int acpm_pmic_bulk_write(struct acpm_handle *handle,
return -EINVAL;
acpm_pmic_init_bulk_write_cmd(cmd, type, reg, chan, count, buf);
- acpm_pmic_set_xfer(&xfer, cmd, ARRAY_SIZE(cmd), acpm_chan_id);
+ acpm_set_xfer(&xfer, cmd, ARRAY_SIZE(cmd), acpm_chan_id, true);
ret = acpm_do_xfer(handle, &xfer);
if (ret)
@@ -229,7 +219,7 @@ int acpm_pmic_update_reg(struct acpm_handle *handle,
int ret;
acpm_pmic_init_update_cmd(cmd, type, reg, chan, value, mask);
- acpm_pmic_set_xfer(&xfer, cmd, ARRAY_SIZE(cmd), acpm_chan_id);
+ acpm_set_xfer(&xfer, cmd, ARRAY_SIZE(cmd), acpm_chan_id, true);
ret = acpm_do_xfer(handle, &xfer);
if (ret)
diff --git a/drivers/firmware/samsung/exynos-acpm.c b/drivers/firmware/samsung/exynos-acpm.c
index 16c46ed60837..8b2529e50328 100644
--- a/drivers/firmware/samsung/exynos-acpm.c
+++ b/drivers/firmware/samsung/exynos-acpm.c
@@ -463,6 +463,29 @@ int acpm_do_xfer(struct acpm_handle *handle, const struct acpm_xfer *xfer)
return acpm_wait_for_message_response(achan, xfer);
}
+/**
+ * acpm_set_xfer() - initialize an ACPM IPC transfer structure.
+ * @xfer: pointer to the ACPM transfer structure that is being initialized.
+ * @cmd: pointer to the buffer containing the command to be transmitted
+ * to the ACPM firmware.
+ * @cmdlen: size (count) of the command.
+ * @acpm_chan_id: mailbox channel identifier.
+ * @response: boolean flag indicating whether the kernel expects the ACPM
+ * firmware to send a reply to this specific command.
+ */
+void acpm_set_xfer(struct acpm_xfer *xfer, u32 *cmd, size_t cmdlen,
+ unsigned int acpm_chan_id, bool response)
+{
+ xfer->acpm_chan_id = acpm_chan_id;
+ xfer->txcnt = cmdlen;
+ xfer->txd = cmd;
+
+ if (response) {
+ xfer->rxcnt = cmdlen;
+ xfer->rxd = cmd;
+ }
+}
+
/**
* acpm_chan_shmem_get_params() - get channel parameters and addresses of the
* TX/RX queues.
diff --git a/drivers/firmware/samsung/exynos-acpm.h b/drivers/firmware/samsung/exynos-acpm.h
index 5df8354dc96c..3d8e33040444 100644
--- a/drivers/firmware/samsung/exynos-acpm.h
+++ b/drivers/firmware/samsung/exynos-acpm.h
@@ -17,6 +17,8 @@ struct acpm_xfer {
struct acpm_handle;
+void acpm_set_xfer(struct acpm_xfer *xfer, u32 *cmd, size_t cmdlen,
+ unsigned int acpm_chan_id, bool response);
int acpm_do_xfer(struct acpm_handle *handle,
const struct acpm_xfer *xfer);
--
2.54.0.rc1.555.g9c883467ad-goog
^ permalink raw reply related
* [PATCH v3 06/10] firmware: samsung: acpm: Add devm_acpm_get_by_phandle helper
From: Tudor Ambarus @ 2026-04-20 17:39 UTC (permalink / raw)
To: Rafael J. Wysocki, Zhang Rui, Lukasz Luba, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Krzysztof Kozlowski,
Alim Akhtar, Bartlomiej Zolnierkiewicz, Kees Cook,
Gustavo A. R. Silva, Peter Griffin, André Draszik,
Daniel Lezcano, Sylwester Nawrocki, Chanwoo Choi,
Michael Turquette, Stephen Boyd, Lee Jones
Cc: willmcvicker, jyescas, shin.son, linux-samsung-soc, linux-kernel,
linux-pm, devicetree, linux-arm-kernel, linux-hardening,
linux-clk, Tudor Ambarus
In-Reply-To: <20260420-acpm-tmu-v3-0-3dc8e93f0b26@linaro.org>
Introduce devm_acpm_get_by_phandle() to standardize how consumer
drivers acquire a handle to the ACPM IPC interface. Enforce the
use of the "samsung,acpm-ipc" property name across the SoC and
simplify the boilerplate code in client drivers.
The first consumer of this helper is the Exynos ACPM Thermal Management
Unit (TMU) driver. The TMU utilizes a hybrid management approach: direct
register access from the Application Processor (AP) is restricted to the
interrupt pending (INTPEND) registers for event identification.
High-level functional tasks, such as sensor initialization, threshold
programming, and temperature reads, are delegated to the ACPM firmware
via this IPC interface.
Signed-off-by: Tudor Ambarus <tudor.ambarus@linaro.org>
---
drivers/firmware/samsung/exynos-acpm.c | 23 ++++++++++++++++++++++
.../linux/firmware/samsung/exynos-acpm-protocol.h | 6 ++++++
2 files changed, 29 insertions(+)
diff --git a/drivers/firmware/samsung/exynos-acpm.c b/drivers/firmware/samsung/exynos-acpm.c
index d4afd6b535e4..15c10fbb2920 100644
--- a/drivers/firmware/samsung/exynos-acpm.c
+++ b/drivers/firmware/samsung/exynos-acpm.c
@@ -797,6 +797,29 @@ struct acpm_handle *devm_acpm_get_by_node(struct device *dev,
}
EXPORT_SYMBOL_GPL(devm_acpm_get_by_node);
+/**
+ * devm_acpm_get_by_phandle - Resource managed lookup of the standardized
+ * "samsung,acpm-ipc" handle.
+ * @dev: consumer device
+ *
+ * Returns a pointer to the acpm_handle on success, or an ERR_PTR on failure.
+ */
+struct acpm_handle *devm_acpm_get_by_phandle(struct device *dev)
+{
+ struct acpm_handle *handle;
+ struct device_node *np;
+
+ np = of_parse_phandle(dev->of_node, "samsung,acpm-ipc", 0);
+ if (!np)
+ return ERR_PTR(-ENODEV);
+
+ handle = devm_acpm_get_by_node(dev, np);
+ of_node_put(np);
+
+ return handle;
+}
+EXPORT_SYMBOL_GPL(devm_acpm_get_by_phandle);
+
static const struct acpm_match_data acpm_gs101 = {
.initdata_base = ACPM_GS101_INITDATA_BASE,
.acpm_clk_dev_name = "gs101-acpm-clk",
diff --git a/include/linux/firmware/samsung/exynos-acpm-protocol.h b/include/linux/firmware/samsung/exynos-acpm-protocol.h
index 8511c3c3983b..9df4c514ebde 100644
--- a/include/linux/firmware/samsung/exynos-acpm-protocol.h
+++ b/include/linux/firmware/samsung/exynos-acpm-protocol.h
@@ -70,6 +70,7 @@ struct device;
#if IS_ENABLED(CONFIG_EXYNOS_ACPM_PROTOCOL)
struct acpm_handle *devm_acpm_get_by_node(struct device *dev,
struct device_node *np);
+struct acpm_handle *devm_acpm_get_by_phandle(struct device *dev);
#else
static inline struct acpm_handle *devm_acpm_get_by_node(struct device *dev,
@@ -77,6 +78,11 @@ static inline struct acpm_handle *devm_acpm_get_by_node(struct device *dev,
{
return NULL;
}
+
+static inline struct acpm_handle *devm_acpm_get_by_phandle(struct device *dev)
+{
+ return NULL;
+}
#endif
#endif /* __EXYNOS_ACPM_PROTOCOL_H */
--
2.54.0.rc1.555.g9c883467ad-goog
^ permalink raw reply related
* [PATCH v3 05/10] firmware: samsung: acpm: Add TMU protocol support
From: Tudor Ambarus @ 2026-04-20 17:39 UTC (permalink / raw)
To: Rafael J. Wysocki, Zhang Rui, Lukasz Luba, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Krzysztof Kozlowski,
Alim Akhtar, Bartlomiej Zolnierkiewicz, Kees Cook,
Gustavo A. R. Silva, Peter Griffin, André Draszik,
Daniel Lezcano, Sylwester Nawrocki, Chanwoo Choi,
Michael Turquette, Stephen Boyd, Lee Jones
Cc: willmcvicker, jyescas, shin.son, linux-samsung-soc, linux-kernel,
linux-pm, devicetree, linux-arm-kernel, linux-hardening,
linux-clk, Tudor Ambarus, Krzysztof Kozlowski
In-Reply-To: <20260420-acpm-tmu-v3-0-3dc8e93f0b26@linaro.org>
The Thermal Management Unit (TMU) on the Google GS101 SoC is managed
through a hybrid model shared between the kernel and the Alive Clock
and Power Manager (ACPM) firmware.
Add the protocol helpers required to communicate with the ACPM for
thermal operations, including initialization, threshold configuration,
temperature reading, and system suspend/resume handshakes.
Signed-off-by: Tudor Ambarus <tudor.ambarus@linaro.org>
Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
---
drivers/firmware/samsung/Makefile | 1 +
drivers/firmware/samsung/exynos-acpm-tmu.c | 240 +++++++++++++++++++++
drivers/firmware/samsung/exynos-acpm-tmu.h | 28 +++
drivers/firmware/samsung/exynos-acpm.c | 12 ++
.../linux/firmware/samsung/exynos-acpm-protocol.h | 18 ++
5 files changed, 299 insertions(+)
diff --git a/drivers/firmware/samsung/Makefile b/drivers/firmware/samsung/Makefile
index 80d4f89b33a9..5a6f72bececf 100644
--- a/drivers/firmware/samsung/Makefile
+++ b/drivers/firmware/samsung/Makefile
@@ -3,4 +3,5 @@
acpm-protocol-objs := exynos-acpm.o
acpm-protocol-objs += exynos-acpm-pmic.o
acpm-protocol-objs += exynos-acpm-dvfs.o
+acpm-protocol-objs += exynos-acpm-tmu.o
obj-$(CONFIG_EXYNOS_ACPM_PROTOCOL) += acpm-protocol.o
diff --git a/drivers/firmware/samsung/exynos-acpm-tmu.c b/drivers/firmware/samsung/exynos-acpm-tmu.c
new file mode 100644
index 000000000000..d1ebe2472ed9
--- /dev/null
+++ b/drivers/firmware/samsung/exynos-acpm-tmu.c
@@ -0,0 +1,240 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright 2020 Samsung Electronics Co., Ltd.
+ * Copyright 2020 Google LLC.
+ * Copyright 2026 Linaro Ltd.
+ */
+
+#include <linux/array_size.h>
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/firmware/samsung/exynos-acpm-protocol.h>
+#include <linux/ktime.h>
+#include <linux/types.h>
+#include <linux/units.h>
+
+#include "exynos-acpm.h"
+#include "exynos-acpm-tmu.h"
+
+/* IPC Request Types */
+#define ACPM_TMU_INIT 0x01
+#define ACPM_TMU_READ_TEMP 0x02
+#define ACPM_TMU_SUSPEND 0x04
+#define ACPM_TMU_RESUME 0x10
+#define ACPM_TMU_THRESHOLD 0x11
+#define ACPM_TMU_INTEN 0x12
+#define ACPM_TMU_CONTROL 0x13
+#define ACPM_TMU_IRQ_CLEAR 0x14
+
+#define ACPM_TMU_TX_DATA_LEN 8
+#define ACPM_TMU_RX_DATA_LEN 7
+
+struct acpm_tmu_tx {
+ u16 ctx;
+ u16 fw_use;
+ u8 type;
+ u8 rsvd0;
+ u8 tzid;
+ u8 rsvd1;
+ u8 data[ACPM_TMU_TX_DATA_LEN];
+} __packed;
+
+struct acpm_tmu_rx {
+ u16 ctx;
+ u16 fw_use;
+ u8 type;
+ s8 ret;
+ u8 tzid;
+ s8 temp;
+ u8 rsvd;
+ u8 data[ACPM_TMU_RX_DATA_LEN];
+} __packed;
+
+union acpm_tmu_msg {
+ u32 data[4];
+ struct acpm_tmu_tx tx;
+ struct acpm_tmu_rx rx;
+} __packed;
+
+static int acpm_tmu_to_linux_err(s8 fw_err)
+{
+ /*
+ * ACPM_TMU_INIT uses BIT(0) and BIT(1) of msg.rx.ret to flag APM
+ * capabilities. Treat zero and all positive values as success.
+ */
+ if (fw_err >= 0)
+ return 0;
+
+ if (fw_err == -1)
+ return -EACCES;
+
+ return -EIO;
+}
+
+int acpm_tmu_init(struct acpm_handle *handle, unsigned int acpm_chan_id)
+{
+ union acpm_tmu_msg msg = {0};
+ struct acpm_xfer xfer;
+ int ret;
+
+ msg.tx.type = ACPM_TMU_INIT;
+ acpm_set_xfer(&xfer, msg.data, ARRAY_SIZE(msg.data), acpm_chan_id,
+ true);
+
+ ret = acpm_do_xfer(handle, &xfer);
+ if (ret)
+ return ret;
+
+ return acpm_tmu_to_linux_err(msg.rx.ret);
+}
+
+int acpm_tmu_read_temp(struct acpm_handle *handle, unsigned int acpm_chan_id,
+ u8 tz, int *temp)
+{
+ union acpm_tmu_msg msg = {0};
+ struct acpm_xfer xfer;
+ int ret;
+
+ msg.tx.type = ACPM_TMU_READ_TEMP;
+ msg.tx.tzid = tz;
+
+ acpm_set_xfer(&xfer, msg.data, ARRAY_SIZE(msg.data), acpm_chan_id,
+ true);
+
+ ret = acpm_do_xfer(handle, &xfer);
+ if (ret)
+ return ret;
+
+ ret = acpm_tmu_to_linux_err(msg.rx.ret);
+ if (ret)
+ return ret;
+
+ *temp = msg.rx.temp;
+
+ return 0;
+}
+
+int acpm_tmu_set_threshold(struct acpm_handle *handle,
+ unsigned int acpm_chan_id, u8 tz,
+ const u8 temperature[8], size_t tlen)
+{
+ union acpm_tmu_msg msg = {0};
+ struct acpm_xfer xfer;
+ int i, ret;
+
+ if (tlen > ACPM_TMU_TX_DATA_LEN)
+ return -EINVAL;
+
+ msg.tx.type = ACPM_TMU_THRESHOLD;
+ msg.tx.tzid = tz;
+
+ for (i = 0; i < tlen; i++)
+ msg.tx.data[i] = temperature[i];
+
+ acpm_set_xfer(&xfer, msg.data, ARRAY_SIZE(msg.data), acpm_chan_id,
+ true);
+
+ ret = acpm_do_xfer(handle, &xfer);
+ if (ret)
+ return ret;
+
+ return acpm_tmu_to_linux_err(msg.rx.ret);
+}
+
+int acpm_tmu_set_interrupt_enable(struct acpm_handle *handle,
+ unsigned int acpm_chan_id, u8 tz, u8 inten)
+{
+ union acpm_tmu_msg msg = {0};
+ struct acpm_xfer xfer;
+ int ret;
+
+ msg.tx.type = ACPM_TMU_INTEN;
+ msg.tx.tzid = tz;
+ msg.tx.data[0] = inten;
+
+ acpm_set_xfer(&xfer, msg.data, ARRAY_SIZE(msg.data), acpm_chan_id,
+ true);
+
+ ret = acpm_do_xfer(handle, &xfer);
+ if (ret)
+ return ret;
+
+ return acpm_tmu_to_linux_err(msg.rx.ret);
+}
+
+int acpm_tmu_tz_control(struct acpm_handle *handle, unsigned int acpm_chan_id,
+ u8 tz, bool enable)
+{
+ union acpm_tmu_msg msg = {0};
+ struct acpm_xfer xfer;
+ int ret;
+
+ msg.tx.type = ACPM_TMU_CONTROL;
+ msg.tx.tzid = tz;
+ msg.tx.data[0] = enable ? 1 : 0;
+
+ acpm_set_xfer(&xfer, msg.data, ARRAY_SIZE(msg.data), acpm_chan_id,
+ true);
+
+ ret = acpm_do_xfer(handle, &xfer);
+ if (ret)
+ return ret;
+
+ return acpm_tmu_to_linux_err(msg.rx.ret);
+}
+
+int acpm_tmu_clear_tz_irq(struct acpm_handle *handle, unsigned int acpm_chan_id,
+ u8 tz)
+{
+ union acpm_tmu_msg msg = {0};
+ struct acpm_xfer xfer;
+ int ret;
+
+ msg.tx.type = ACPM_TMU_IRQ_CLEAR;
+ msg.tx.tzid = tz;
+
+ acpm_set_xfer(&xfer, msg.data, ARRAY_SIZE(msg.data), acpm_chan_id,
+ true);
+
+ ret = acpm_do_xfer(handle, &xfer);
+ if (ret)
+ return ret;
+
+ return acpm_tmu_to_linux_err(msg.rx.ret);
+}
+
+int acpm_tmu_suspend(struct acpm_handle *handle, unsigned int acpm_chan_id)
+{
+ union acpm_tmu_msg msg = {0};
+ struct acpm_xfer xfer;
+ int ret;
+
+ msg.tx.type = ACPM_TMU_SUSPEND;
+
+ acpm_set_xfer(&xfer, msg.data, ARRAY_SIZE(msg.data), acpm_chan_id,
+ true);
+
+ ret = acpm_do_xfer(handle, &xfer);
+ if (ret)
+ return ret;
+
+ return acpm_tmu_to_linux_err(msg.rx.ret);
+}
+
+int acpm_tmu_resume(struct acpm_handle *handle, unsigned int acpm_chan_id)
+{
+ union acpm_tmu_msg msg = {0};
+ struct acpm_xfer xfer;
+ int ret;
+
+ msg.tx.type = ACPM_TMU_RESUME;
+
+ acpm_set_xfer(&xfer, msg.data, ARRAY_SIZE(msg.data), acpm_chan_id,
+ true);
+
+ ret = acpm_do_xfer(handle, &xfer);
+ if (ret)
+ return ret;
+
+ return acpm_tmu_to_linux_err(msg.rx.ret);
+}
diff --git a/drivers/firmware/samsung/exynos-acpm-tmu.h b/drivers/firmware/samsung/exynos-acpm-tmu.h
new file mode 100644
index 000000000000..8b89f29fda67
--- /dev/null
+++ b/drivers/firmware/samsung/exynos-acpm-tmu.h
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright 2020 Samsung Electronics Co., Ltd.
+ * Copyright 2020 Google LLC.
+ * Copyright 2026 Linaro Ltd.
+ */
+#ifndef __EXYNOS_ACPM_TMU_H__
+#define __EXYNOS_ACPM_TMU_H__
+
+#include <linux/types.h>
+
+struct acpm_handle;
+
+int acpm_tmu_init(struct acpm_handle *handle, unsigned int acpm_chan_id);
+int acpm_tmu_read_temp(struct acpm_handle *handle, unsigned int acpm_chan_id,
+ u8 tz, int *temp);
+int acpm_tmu_set_threshold(struct acpm_handle *handle,
+ unsigned int acpm_chan_id, u8 tz,
+ const u8 temperature[8], size_t tlen);
+int acpm_tmu_set_interrupt_enable(struct acpm_handle *handle,
+ unsigned int acpm_chan_id, u8 tz, u8 inten);
+int acpm_tmu_tz_control(struct acpm_handle *handle, unsigned int acpm_chan_id,
+ u8 tz, bool enable);
+int acpm_tmu_clear_tz_irq(struct acpm_handle *handle, unsigned int acpm_chan_id,
+ u8 tz);
+int acpm_tmu_suspend(struct acpm_handle *handle, unsigned int acpm_chan_id);
+int acpm_tmu_resume(struct acpm_handle *handle, unsigned int acpm_chan_id);
+#endif /* __EXYNOS_ACPM_TMU_H__ */
diff --git a/drivers/firmware/samsung/exynos-acpm.c b/drivers/firmware/samsung/exynos-acpm.c
index 4f2ad84cd783..d4afd6b535e4 100644
--- a/drivers/firmware/samsung/exynos-acpm.c
+++ b/drivers/firmware/samsung/exynos-acpm.c
@@ -31,6 +31,7 @@
#include "exynos-acpm.h"
#include "exynos-acpm-dvfs.h"
#include "exynos-acpm-pmic.h"
+#include "exynos-acpm-tmu.h"
#define ACPM_PROTOCOL_SEQNUM GENMASK(21, 16)
@@ -628,6 +629,17 @@ static const struct acpm_ops exynos_acpm_driver_ops = {
.bulk_write = acpm_pmic_bulk_write,
.update_reg = acpm_pmic_update_reg,
},
+
+ .tmu = {
+ .init = acpm_tmu_init,
+ .read_temp = acpm_tmu_read_temp,
+ .set_threshold = acpm_tmu_set_threshold,
+ .set_interrupt_enable = acpm_tmu_set_interrupt_enable,
+ .tz_control = acpm_tmu_tz_control,
+ .clear_tz_irq = acpm_tmu_clear_tz_irq,
+ .suspend = acpm_tmu_suspend,
+ .resume = acpm_tmu_resume,
+ },
};
static int acpm_probe(struct platform_device *pdev)
diff --git a/include/linux/firmware/samsung/exynos-acpm-protocol.h b/include/linux/firmware/samsung/exynos-acpm-protocol.h
index e13d9ac73ff6..8511c3c3983b 100644
--- a/include/linux/firmware/samsung/exynos-acpm-protocol.h
+++ b/include/linux/firmware/samsung/exynos-acpm-protocol.h
@@ -34,9 +34,27 @@ struct acpm_pmic_ops {
u8 type, u8 reg, u8 chan, u8 value, u8 mask);
};
+struct acpm_tmu_ops {
+ int (*init)(struct acpm_handle *handle, unsigned int acpm_chan_id);
+ int (*read_temp)(struct acpm_handle *handle, unsigned int acpm_chan_id,
+ u8 tz, int *temp);
+ int (*set_threshold)(struct acpm_handle *handle,
+ unsigned int acpm_chan_id, u8 tz,
+ const u8 temperature[8], size_t tlen);
+ int (*set_interrupt_enable)(struct acpm_handle *handle,
+ unsigned int acpm_chan_id, u8 tz, u8 inten);
+ int (*tz_control)(struct acpm_handle *handle, unsigned int acpm_chan_id,
+ u8 tz, bool enable);
+ int (*clear_tz_irq)(struct acpm_handle *handle,
+ unsigned int acpm_chan_id, u8 tz);
+ int (*suspend)(struct acpm_handle *handle, unsigned int acpm_chan_id);
+ int (*resume)(struct acpm_handle *handle, unsigned int acpm_chan_id);
+};
+
struct acpm_ops {
struct acpm_dvfs_ops dvfs;
struct acpm_pmic_ops pmic;
+ struct acpm_tmu_ops tmu;
};
/**
--
2.54.0.rc1.555.g9c883467ad-goog
^ permalink raw reply related
* [PATCH v3 08/10] MAINTAINERS: Add entry for Samsung Exynos ACPM thermal driver
From: Tudor Ambarus @ 2026-04-20 17:39 UTC (permalink / raw)
To: Rafael J. Wysocki, Zhang Rui, Lukasz Luba, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Krzysztof Kozlowski,
Alim Akhtar, Bartlomiej Zolnierkiewicz, Kees Cook,
Gustavo A. R. Silva, Peter Griffin, André Draszik,
Daniel Lezcano, Sylwester Nawrocki, Chanwoo Choi,
Michael Turquette, Stephen Boyd, Lee Jones
Cc: willmcvicker, jyescas, shin.son, linux-samsung-soc, linux-kernel,
linux-pm, devicetree, linux-arm-kernel, linux-hardening,
linux-clk, Tudor Ambarus, Krzysztof Kozlowski
In-Reply-To: <20260420-acpm-tmu-v3-0-3dc8e93f0b26@linaro.org>
Add a MAINTAINERS entry for the Samsung Exynos ACPM thermal driver.
Signed-off-by: Tudor Ambarus <tudor.ambarus@linaro.org>
Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
---
MAINTAINERS | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 76d8291237be..fa67f6f449a7 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -23676,6 +23676,14 @@ F: drivers/clk/samsung/clk-acpm.c
F: drivers/firmware/samsung/exynos-acpm*
F: include/linux/firmware/samsung/exynos-acpm-protocol.h
+SAMSUNG EXYNOS ACPM THERMAL DRIVER
+M: Tudor Ambarus <tudor.ambarus@linaro.org>
+L: linux-kernel@vger.kernel.org
+L: linux-samsung-soc@vger.kernel.org
+S: Supported
+F: Documentation/devicetree/bindings/thermal/google,gs101-tmu-top.yaml
+F: drivers/thermal/samsung/acpm-tmu.c
+
SAMSUNG EXYNOS MAILBOX DRIVER
M: Tudor Ambarus <tudor.ambarus@linaro.org>
L: linux-kernel@vger.kernel.org
--
2.54.0.rc1.555.g9c883467ad-goog
^ permalink raw reply related
* [PATCH v3 09/10] arm64: dts: exynos: gs101: Add thermal management unit
From: Tudor Ambarus @ 2026-04-20 17:39 UTC (permalink / raw)
To: Rafael J. Wysocki, Zhang Rui, Lukasz Luba, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Krzysztof Kozlowski,
Alim Akhtar, Bartlomiej Zolnierkiewicz, Kees Cook,
Gustavo A. R. Silva, Peter Griffin, André Draszik,
Daniel Lezcano, Sylwester Nawrocki, Chanwoo Choi,
Michael Turquette, Stephen Boyd, Lee Jones
Cc: willmcvicker, jyescas, shin.son, linux-samsung-soc, linux-kernel,
linux-pm, devicetree, linux-arm-kernel, linux-hardening,
linux-clk, Tudor Ambarus
In-Reply-To: <20260420-acpm-tmu-v3-0-3dc8e93f0b26@linaro.org>
Add the Thermal Management Unit (TMU) support for the Google GS101 SoC.
Describe the TMU using a consolidated SoC node that includes memory
resources for interrupt identification and a phandle to the ACPM IPC
interface for functional control.
Define thermal zones for the little, mid, and big CPU clusters, including
associated trip points and cooling-device maps to enable thermal
mitigation.
Signed-off-by: Tudor Ambarus <tudor.ambarus@linaro.org>
---
arch/arm64/boot/dts/exynos/google/gs101-tmu.dtsi | 136 +++++++++++++++++++++++
arch/arm64/boot/dts/exynos/google/gs101.dtsi | 18 +++
2 files changed, 154 insertions(+)
diff --git a/arch/arm64/boot/dts/exynos/google/gs101-tmu.dtsi b/arch/arm64/boot/dts/exynos/google/gs101-tmu.dtsi
new file mode 100644
index 000000000000..b27d1a539ec2
--- /dev/null
+++ b/arch/arm64/boot/dts/exynos/google/gs101-tmu.dtsi
@@ -0,0 +1,136 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Google GS101 TMU configurations device tree source
+ *
+ * Copyright 2020 Samsung Electronics Co., Ltd.
+ * Copyright 2020 Google LLC.
+ * Copyright 2026 Linaro Ltd.
+ */
+
+#include <dt-bindings/thermal/thermal.h>
+
+/ {
+ thermal-zones {
+ cpucl2-thermal {
+ polling-delay-passive = <0>;
+ polling-delay = <0>;
+ thermal-sensors = <&tmu_top 0>;
+
+ trips {
+ big_switch_on: big-switch-on {
+ temperature = <80000>;
+ hysteresis = <2000>;
+ type = "passive";
+ };
+
+ big_mitigate: big-mitigate {
+ temperature = <90000>;
+ hysteresis = <5000>;
+ type = "passive";
+ };
+
+ big_hot: big-hot {
+ temperature = <100000>;
+ hysteresis = <5000>;
+ type = "hot";
+ };
+
+ big_critical: big-critical {
+ temperature = <105000>;
+ hysteresis = <2000>;
+ type = "critical";
+ };
+ };
+
+ cooling-maps {
+ map0 {
+ trip = <&big_mitigate>;
+ cooling-device = <&cpu6 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>,
+ <&cpu7 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>;
+ };
+ };
+ };
+
+ cpucl1-thermal {
+ polling-delay-passive = <0>;
+ polling-delay = <0>;
+ thermal-sensors = <&tmu_top 1>;
+
+ trips {
+ mid_switch_on: mid-switch-on {
+ temperature = <80000>;
+ hysteresis = <2000>;
+ type = "passive";
+ };
+
+ mid_mitigate: mid-mitigate {
+ temperature = <90000>;
+ hysteresis = <5000>;
+ type = "passive";
+ };
+
+ mid_hot: mid-hot {
+ temperature = <100000>;
+ hysteresis = <5000>;
+ type = "hot";
+ };
+
+ mid_critical: mid-critical {
+ temperature = <105000>;
+ hysteresis = <2000>;
+ type = "critical";
+ };
+ };
+
+ cooling-maps {
+ map0 {
+ trip = <&mid_mitigate>;
+ cooling-device = <&cpu4 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>,
+ <&cpu5 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>;
+ };
+ };
+ };
+
+ cpucl0-thermal {
+ polling-delay-passive = <0>;
+ polling-delay = <0>;
+ thermal-sensors = <&tmu_top 2>;
+
+ trips {
+ little_switch_on: little-switch-on {
+ temperature = <80000>;
+ hysteresis = <2000>;
+ type = "passive";
+ };
+
+ little_mitigate: little-mitigate {
+ temperature = <90000>;
+ hysteresis = <5000>;
+ type = "passive";
+ };
+
+ little_hot: little-hot {
+ temperature = <100000>;
+ hysteresis = <5000>;
+ type = "hot";
+ };
+
+ little_critical: little-critical {
+ temperature = <105000>;
+ hysteresis = <2000>;
+ type = "critical";
+ };
+ };
+
+ cooling-maps {
+ map0 {
+ trip = <&little_mitigate>;
+ cooling-device = <&cpu0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>,
+ <&cpu1 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>,
+ <&cpu2 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>,
+ <&cpu3 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>;
+ };
+ };
+ };
+ };
+};
diff --git a/arch/arm64/boot/dts/exynos/google/gs101.dtsi b/arch/arm64/boot/dts/exynos/google/gs101.dtsi
index d085f9fb0f62..4b8c7edaddb6 100644
--- a/arch/arm64/boot/dts/exynos/google/gs101.dtsi
+++ b/arch/arm64/boot/dts/exynos/google/gs101.dtsi
@@ -74,6 +74,7 @@ cpu0: cpu@0 {
compatible = "arm,cortex-a55";
reg = <0x0000>;
clocks = <&acpm_ipc GS101_CLK_ACPM_DVFS_CPUCL0>;
+ #cooling-cells = <2>;
enable-method = "psci";
cpu-idle-states = <&ananke_cpu_sleep>;
capacity-dmips-mhz = <250>;
@@ -86,6 +87,7 @@ cpu1: cpu@100 {
compatible = "arm,cortex-a55";
reg = <0x0100>;
clocks = <&acpm_ipc GS101_CLK_ACPM_DVFS_CPUCL0>;
+ #cooling-cells = <2>;
enable-method = "psci";
cpu-idle-states = <&ananke_cpu_sleep>;
capacity-dmips-mhz = <250>;
@@ -98,6 +100,7 @@ cpu2: cpu@200 {
compatible = "arm,cortex-a55";
reg = <0x0200>;
clocks = <&acpm_ipc GS101_CLK_ACPM_DVFS_CPUCL0>;
+ #cooling-cells = <2>;
enable-method = "psci";
cpu-idle-states = <&ananke_cpu_sleep>;
capacity-dmips-mhz = <250>;
@@ -110,6 +113,7 @@ cpu3: cpu@300 {
compatible = "arm,cortex-a55";
reg = <0x0300>;
clocks = <&acpm_ipc GS101_CLK_ACPM_DVFS_CPUCL0>;
+ #cooling-cells = <2>;
enable-method = "psci";
cpu-idle-states = <&ananke_cpu_sleep>;
capacity-dmips-mhz = <250>;
@@ -122,6 +126,7 @@ cpu4: cpu@400 {
compatible = "arm,cortex-a76";
reg = <0x0400>;
clocks = <&acpm_ipc GS101_CLK_ACPM_DVFS_CPUCL1>;
+ #cooling-cells = <2>;
enable-method = "psci";
cpu-idle-states = <&enyo_cpu_sleep>;
capacity-dmips-mhz = <620>;
@@ -134,6 +139,7 @@ cpu5: cpu@500 {
compatible = "arm,cortex-a76";
reg = <0x0500>;
clocks = <&acpm_ipc GS101_CLK_ACPM_DVFS_CPUCL1>;
+ #cooling-cells = <2>;
enable-method = "psci";
cpu-idle-states = <&enyo_cpu_sleep>;
capacity-dmips-mhz = <620>;
@@ -146,6 +152,7 @@ cpu6: cpu@600 {
compatible = "arm,cortex-x1";
reg = <0x0600>;
clocks = <&acpm_ipc GS101_CLK_ACPM_DVFS_CPUCL2>;
+ #cooling-cells = <2>;
enable-method = "psci";
cpu-idle-states = <&hera_cpu_sleep>;
capacity-dmips-mhz = <1024>;
@@ -158,6 +165,7 @@ cpu7: cpu@700 {
compatible = "arm,cortex-x1";
reg = <0x0700>;
clocks = <&acpm_ipc GS101_CLK_ACPM_DVFS_CPUCL2>;
+ #cooling-cells = <2>;
enable-method = "psci";
cpu-idle-states = <&hera_cpu_sleep>;
capacity-dmips-mhz = <1024>;
@@ -639,6 +647,15 @@ watchdog_cl1: watchdog@10070000 {
status = "disabled";
};
+ tmu_top: thermal-sensor@100a0000 {
+ compatible = "google,gs101-tmu-top";
+ reg = <0x100a0000 0x800>;
+ clocks = <&cmu_misc CLK_GOUT_MISC_TMU_TOP_PCLK>;
+ interrupts = <GIC_SPI 769 IRQ_TYPE_LEVEL_HIGH 0>;
+ samsung,acpm-ipc = <&acpm_ipc>;
+ #thermal-sensor-cells = <1>;
+ };
+
trng: rng@10141400 {
compatible = "google,gs101-trng",
"samsung,exynos850-trng";
@@ -1861,3 +1878,4 @@ timer {
};
#include "gs101-pinctrl.dtsi"
+#include "gs101-tmu.dtsi"
--
2.54.0.rc1.555.g9c883467ad-goog
^ permalink raw reply related
* [PATCH v3 07/10] thermal: samsung: Add Exynos ACPM TMU driver GS101
From: Tudor Ambarus @ 2026-04-20 17:39 UTC (permalink / raw)
To: Rafael J. Wysocki, Zhang Rui, Lukasz Luba, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Krzysztof Kozlowski,
Alim Akhtar, Bartlomiej Zolnierkiewicz, Kees Cook,
Gustavo A. R. Silva, Peter Griffin, André Draszik,
Daniel Lezcano, Sylwester Nawrocki, Chanwoo Choi,
Michael Turquette, Stephen Boyd, Lee Jones
Cc: willmcvicker, jyescas, shin.son, linux-samsung-soc, linux-kernel,
linux-pm, devicetree, linux-arm-kernel, linux-hardening,
linux-clk, Tudor Ambarus, Krzysztof Kozlowski
In-Reply-To: <20260420-acpm-tmu-v3-0-3dc8e93f0b26@linaro.org>
Add driver for the Thermal Management Unit (TMU) managed via the Alive
Clock and Power Manager (ACPM), found on Samsung Exynos SoCs such as
Google GS101 (and Exynos850, autov920, etc.).
The TMU on utilizes a hybrid management model shared between the
Application Processor (AP) and the ACPM firmware. The driver maintains
direct memory-mapped access to the TMU interrupt pending registers to
identify thermal events, while delegating functional tasks - such as
sensor initialization, threshold configuration, and temperature
acquisition - to the ACPM firmware via the ACPM IPC protocol.
Signed-off-by: Tudor Ambarus <tudor.ambarus@linaro.org>
Acked-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
---
drivers/thermal/samsung/Kconfig | 17 ++
drivers/thermal/samsung/Makefile | 2 +
drivers/thermal/samsung/acpm-tmu.c | 539 +++++++++++++++++++++++++++++++++++++
3 files changed, 558 insertions(+)
diff --git a/drivers/thermal/samsung/Kconfig b/drivers/thermal/samsung/Kconfig
index f4eff5a41a84..0d3ffbdc66f0 100644
--- a/drivers/thermal/samsung/Kconfig
+++ b/drivers/thermal/samsung/Kconfig
@@ -9,3 +9,20 @@ config EXYNOS_THERMAL
the TMU, reports temperature and handles cooling action if defined.
This driver uses the Exynos core thermal APIs and TMU configuration
data from the supported SoCs.
+
+config EXYNOS_ACPM_THERMAL
+ tristate "Exynos ACPM thermal management unit driver"
+ depends on THERMAL_OF
+ depends on EXYNOS_ACPM_PROTOCOL || (COMPILE_TEST && !EXYNOS_ACPM_PROTOCOL)
+ help
+ Support for the Thermal Management Unit (TMU) on Samsung Exynos SoCs
+ (such as Google GS101 and Exynos850).
+
+ The TMU on these platforms is managed through a hybrid architecture.
+ This driver handles direct register access for thermal interrupt status
+ monitoring and communicates with the Alive Clock and Power Manager
+ (ACPM) firmware via the ACPM IPC protocol for functional sensor control
+ and configuration.
+
+ Select this if you want to monitor device temperature and enable
+ thermal mitigation on Samsung Exynos ACPM based devices.
diff --git a/drivers/thermal/samsung/Makefile b/drivers/thermal/samsung/Makefile
index f139407150d2..daed80647c34 100644
--- a/drivers/thermal/samsung/Makefile
+++ b/drivers/thermal/samsung/Makefile
@@ -4,3 +4,5 @@
#
obj-$(CONFIG_EXYNOS_THERMAL) += exynos_thermal.o
exynos_thermal-y := exynos_tmu.o
+obj-$(CONFIG_EXYNOS_ACPM_THERMAL) += exynos_acpm_thermal.o
+exynos_acpm_thermal-y := acpm-tmu.o
diff --git a/drivers/thermal/samsung/acpm-tmu.c b/drivers/thermal/samsung/acpm-tmu.c
new file mode 100644
index 000000000000..942d8caa78f5
--- /dev/null
+++ b/drivers/thermal/samsung/acpm-tmu.c
@@ -0,0 +1,539 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright 2019 Samsung Electronics Co., Ltd.
+ * Copyright 2025 Google LLC.
+ * Copyright 2026 Linaro Ltd.
+ */
+
+#include <linux/cleanup.h>
+#include <linux/clk.h>
+#include <linux/device/devres.h>
+#include <linux/err.h>
+#include <linux/firmware/samsung/exynos-acpm-protocol.h>
+#include <linux/interrupt.h>
+#include <linux/minmax.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/thermal.h>
+#include <linux/units.h>
+
+#include "../thermal_hwmon.h"
+
+#define EXYNOS_TMU_SENSOR(i) BIT(i)
+#define EXYNOS_TMU_SENSORS_MAX_COUNT 16
+
+#define GS101_CPUCL2_SENSOR_MASK (EXYNOS_TMU_SENSOR(0) | \
+ EXYNOS_TMU_SENSOR(6) | \
+ EXYNOS_TMU_SENSOR(7) | \
+ EXYNOS_TMU_SENSOR(8) | \
+ EXYNOS_TMU_SENSOR(9))
+#define GS101_CPUCL1_SENSOR_MASK (EXYNOS_TMU_SENSOR(4) | \
+ EXYNOS_TMU_SENSOR(5))
+#define GS101_CPUCL0_SENSOR_MASK (EXYNOS_TMU_SENSOR(1) | \
+ EXYNOS_TMU_SENSOR(2))
+
+#define GS101_REG_INTPEND(i) ((i) * 0x50 + 0xf8)
+
+enum {
+ P0_INTPEND,
+ P1_INTPEND,
+ P2_INTPEND,
+ P3_INTPEND,
+ P4_INTPEND,
+ P5_INTPEND,
+ P6_INTPEND,
+ P7_INTPEND,
+ P8_INTPEND,
+ P9_INTPEND,
+ P10_INTPEND,
+ P11_INTPEND,
+ P12_INTPEND,
+ P13_INTPEND,
+ P14_INTPEND,
+ P15_INTPEND,
+ REG_INTPEND_COUNT,
+};
+
+struct acpm_tmu_sensor_group {
+ u16 mask;
+ u8 id;
+};
+
+struct acpm_tmu_sensor {
+ const struct acpm_tmu_sensor_group *group;
+ struct thermal_zone_device *tzd;
+ struct acpm_tmu_priv *priv;
+ struct mutex lock; /* protects sensor state */
+ bool enabled;
+};
+
+struct acpm_tmu_priv {
+ struct regmap_field *regmap_fields[REG_INTPEND_COUNT];
+ struct acpm_handle *handle;
+ struct device *dev;
+ struct clk *clk;
+ unsigned int mbox_chan_id;
+ unsigned int num_sensors;
+ int irq;
+ struct acpm_tmu_sensor sensors[] __counted_by(num_sensors);
+};
+
+struct acpm_tmu_driver_data {
+ const struct reg_field *reg_fields;
+ const struct acpm_tmu_sensor_group *sensor_groups;
+ unsigned int num_sensor_groups;
+ unsigned int mbox_chan_id;
+};
+
+#define ACPM_TMU_SENSOR_GROUP(_mask, _id) \
+ { \
+ .mask = _mask, \
+ .id = _id, \
+ }
+
+static const struct acpm_tmu_sensor_group gs101_sensor_groups[] = {
+ ACPM_TMU_SENSOR_GROUP(GS101_CPUCL2_SENSOR_MASK, 0),
+ ACPM_TMU_SENSOR_GROUP(GS101_CPUCL1_SENSOR_MASK, 1),
+ ACPM_TMU_SENSOR_GROUP(GS101_CPUCL0_SENSOR_MASK, 2),
+};
+
+static const struct reg_field gs101_reg_fields[REG_INTPEND_COUNT] = {
+ [P0_INTPEND] = REG_FIELD(GS101_REG_INTPEND(0), 0, 31),
+ [P1_INTPEND] = REG_FIELD(GS101_REG_INTPEND(1), 0, 31),
+ [P2_INTPEND] = REG_FIELD(GS101_REG_INTPEND(2), 0, 31),
+ [P3_INTPEND] = REG_FIELD(GS101_REG_INTPEND(3), 0, 31),
+ [P4_INTPEND] = REG_FIELD(GS101_REG_INTPEND(4), 0, 31),
+ [P5_INTPEND] = REG_FIELD(GS101_REG_INTPEND(5), 0, 31),
+ [P6_INTPEND] = REG_FIELD(GS101_REG_INTPEND(6), 0, 31),
+ [P7_INTPEND] = REG_FIELD(GS101_REG_INTPEND(7), 0, 31),
+ [P8_INTPEND] = REG_FIELD(GS101_REG_INTPEND(8), 0, 31),
+ [P9_INTPEND] = REG_FIELD(GS101_REG_INTPEND(9), 0, 31),
+ [P10_INTPEND] = REG_FIELD(GS101_REG_INTPEND(10), 0, 31),
+ [P11_INTPEND] = REG_FIELD(GS101_REG_INTPEND(11), 0, 31),
+ [P12_INTPEND] = REG_FIELD(GS101_REG_INTPEND(12), 0, 31),
+ [P13_INTPEND] = REG_FIELD(GS101_REG_INTPEND(13), 0, 31),
+ [P14_INTPEND] = REG_FIELD(GS101_REG_INTPEND(14), 0, 31),
+ [P15_INTPEND] = REG_FIELD(GS101_REG_INTPEND(15), 0, 31),
+};
+
+static const struct regmap_config gs101_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .use_relaxed_mmio = true,
+ .max_register = GS101_REG_INTPEND(15),
+};
+
+static const struct acpm_tmu_driver_data acpm_tmu_gs101 = {
+ .reg_fields = gs101_reg_fields,
+ .sensor_groups = gs101_sensor_groups,
+ .num_sensor_groups = ARRAY_SIZE(gs101_sensor_groups),
+ .mbox_chan_id = 9,
+};
+
+static int acpm_tmu_op_tz_control(struct acpm_tmu_sensor *sensor, bool on)
+{
+ struct acpm_tmu_priv *priv = sensor->priv;
+ struct acpm_handle *handle = priv->handle;
+ const struct acpm_tmu_ops *ops = &handle->ops->tmu;
+ int ret;
+
+ ret = ops->tz_control(handle, priv->mbox_chan_id, sensor->group->id,
+ on);
+ if (ret)
+ return ret;
+
+ sensor->enabled = on;
+
+ return 0;
+}
+
+static int acpm_tmu_control(struct acpm_tmu_priv *priv, bool on)
+{
+ struct device *dev = priv->dev;
+ int i, ret;
+
+ ret = pm_runtime_resume_and_get(dev);
+ if (ret < 0)
+ return ret;
+
+ for (i = 0; i < priv->num_sensors; i++) {
+ struct acpm_tmu_sensor *sensor = &priv->sensors[i];
+
+ /* Skip sensors that weren't found in DT */
+ if (!sensor->tzd)
+ continue;
+
+ scoped_guard(mutex, &sensor->lock) {
+ ret = acpm_tmu_op_tz_control(sensor, on);
+ }
+
+ if (ret)
+ goto out;
+ }
+
+out:
+ pm_runtime_put_autosuspend(dev);
+ return ret;
+}
+
+static int acpm_tmu_get_temp(struct thermal_zone_device *tz, int *temp)
+{
+ struct acpm_tmu_sensor *sensor = thermal_zone_device_priv(tz);
+ struct acpm_tmu_priv *priv = sensor->priv;
+ struct acpm_handle *handle = priv->handle;
+ const struct acpm_tmu_ops *ops = &handle->ops->tmu;
+ struct device *dev = priv->dev;
+ int acpm_temp, ret;
+
+ if (!sensor->enabled)
+ return -EAGAIN;
+
+ ret = pm_runtime_resume_and_get(dev);
+ if (ret < 0)
+ return ret;
+
+ scoped_guard(mutex, &sensor->lock) {
+ ret = ops->read_temp(handle, priv->mbox_chan_id,
+ sensor->group->id, &acpm_temp);
+ }
+
+ pm_runtime_put_autosuspend(dev);
+
+ if (ret)
+ return ret;
+
+ *temp = acpm_temp * MILLIDEGREE_PER_DEGREE;
+
+ return 0;
+}
+
+static int acpm_tmu_set_trips(struct thermal_zone_device *tz, int low, int high)
+{
+ struct acpm_tmu_sensor *sensor = thermal_zone_device_priv(tz);
+ struct acpm_tmu_priv *priv = sensor->priv;
+ struct acpm_handle *handle = priv->handle;
+ const struct acpm_tmu_ops *ops = &handle->ops->tmu;
+ struct device *dev = priv->dev;
+ unsigned int mbox_chan_id = priv->mbox_chan_id;
+ u8 acpm_sensor_id = sensor->group->id;
+ u8 thresholds[2] = {};
+ u8 inten = 0;
+ int ret;
+
+ /* If a valid lower bound exists, set the threshold and enable its interrupt */
+ if (low > -INT_MAX) {
+ thresholds[0] = clamp_val(low / MILLIDEGREE_PER_DEGREE, 0, 255);
+ inten |= BIT(0);
+ }
+
+ /* If a valid upper bound exists, set the threshold and enable its interrupt */
+ if (high < INT_MAX) {
+ thresholds[1] = clamp_val(high / MILLIDEGREE_PER_DEGREE, 0, 255);
+ inten |= BIT(1);
+ }
+
+ ret = pm_runtime_resume_and_get(dev);
+ if (ret)
+ return ret;
+
+ scoped_guard(mutex, &sensor->lock) {
+ bool was_enabled = sensor->enabled;
+
+ if (was_enabled) {
+ ret = acpm_tmu_op_tz_control(sensor, false);
+ if (ret)
+ goto out;
+ }
+
+ ret = ops->set_threshold(handle, mbox_chan_id, acpm_sensor_id,
+ thresholds, 2);
+ if (ret)
+ goto out;
+
+ ret = ops->set_interrupt_enable(handle, mbox_chan_id,
+ acpm_sensor_id, inten);
+ if (ret)
+ goto out;
+
+ /* Restore based on cached state. */
+ if (was_enabled)
+ ret = acpm_tmu_op_tz_control(sensor, true);
+ }
+
+out:
+ pm_runtime_put_autosuspend(dev);
+ return ret;
+}
+
+static const struct thermal_zone_device_ops acpm_tmu_sensor_ops = {
+ .get_temp = acpm_tmu_get_temp,
+ .set_trips = acpm_tmu_set_trips,
+};
+
+static int acpm_tmu_has_pending_irq(struct acpm_tmu_sensor *sensor,
+ bool *pending_irq)
+{
+ struct acpm_tmu_priv *priv = sensor->priv;
+ unsigned long mask = sensor->group->mask;
+ int i, ret;
+ u32 val;
+
+ guard(mutex)(&sensor->lock);
+
+ for_each_set_bit(i, &mask, EXYNOS_TMU_SENSORS_MAX_COUNT) {
+ ret = regmap_field_read(priv->regmap_fields[i], &val);
+ if (ret)
+ return ret;
+
+ if (val) {
+ *pending_irq = true;
+ break;
+ }
+ }
+
+ return 0;
+}
+
+static irqreturn_t acpm_tmu_thread_fn(int irq, void *id)
+{
+ struct acpm_tmu_priv *priv = id;
+ struct acpm_handle *handle = priv->handle;
+ const struct acpm_tmu_ops *ops = &handle->ops->tmu;
+ struct device *dev = priv->dev;
+ int i, ret;
+
+ ret = pm_runtime_resume_and_get(dev);
+ if (ret) {
+ dev_err(dev, "Failed to resume: %d\n", ret);
+ return IRQ_NONE;
+ }
+
+ for (i = 0; i < priv->num_sensors; i++) {
+ struct acpm_tmu_sensor *sensor = &priv->sensors[i];
+ bool pending_irq = false;
+
+ if (!sensor->tzd)
+ continue;
+
+ ret = acpm_tmu_has_pending_irq(sensor, &pending_irq);
+ if (ret || !pending_irq)
+ continue;
+
+ thermal_zone_device_update(sensor->tzd,
+ THERMAL_EVENT_UNSPECIFIED);
+
+ scoped_guard(mutex, &sensor->lock) {
+ ret = ops->clear_tz_irq(handle, priv->mbox_chan_id,
+ sensor->group->id);
+ if (ret)
+ dev_err(priv->dev, "Sensor %d: failed to clear IRQ (%d)\n",
+ i, ret);
+ }
+ }
+
+ pm_runtime_put_autosuspend(dev);
+
+ return IRQ_HANDLED;
+}
+
+static const struct of_device_id acpm_tmu_match[] = {
+ { .compatible = "google,gs101-tmu-top" },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, acpm_tmu_match);
+
+static int acpm_tmu_probe(struct platform_device *pdev)
+{
+ const struct acpm_tmu_driver_data *data = &acpm_tmu_gs101;
+ struct acpm_handle *acpm_handle;
+ struct device *dev = &pdev->dev;
+ struct acpm_tmu_priv *priv;
+ struct regmap *regmap;
+ void __iomem *base;
+ int i, ret;
+
+ acpm_handle = devm_acpm_get_by_phandle(dev);
+ if (IS_ERR(acpm_handle))
+ return dev_err_probe(dev, PTR_ERR(acpm_handle),
+ "Failed to get ACPM handle\n");
+
+ priv = devm_kzalloc(dev,
+ struct_size(priv, sensors, data->num_sensor_groups),
+ GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->dev = dev;
+ priv->handle = acpm_handle;
+ priv->mbox_chan_id = data->mbox_chan_id;
+ priv->num_sensors = data->num_sensor_groups;
+
+ platform_set_drvdata(pdev, priv);
+
+ base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(base))
+ return dev_err_probe(dev, PTR_ERR(base), "Failed to ioremap resource\n");
+
+ regmap = devm_regmap_init_mmio(dev, base, &gs101_regmap_config);
+ if (IS_ERR(regmap))
+ return dev_err_probe(dev, PTR_ERR(regmap), "Failed to init regmap\n");
+
+ ret = devm_regmap_field_bulk_alloc(dev, regmap, priv->regmap_fields,
+ data->reg_fields, REG_INTPEND_COUNT);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Unable to map syscon registers\n");
+
+ priv->clk = devm_clk_get(dev, NULL);
+ if (IS_ERR(priv->clk))
+ return dev_err_probe(dev, PTR_ERR(priv->clk),
+ "Failed to get the clock\n");
+
+ priv->irq = platform_get_irq(pdev, 0);
+ if (priv->irq < 0)
+ return dev_err_probe(dev, priv->irq, "Failed to get irq\n");
+
+ ret = devm_request_threaded_irq(dev, priv->irq, NULL,
+ acpm_tmu_thread_fn, IRQF_ONESHOT,
+ dev_name(dev), priv);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to request irq\n");
+
+ pm_runtime_set_autosuspend_delay(dev, 100);
+ pm_runtime_use_autosuspend(dev);
+
+ ret = devm_pm_runtime_enable(dev);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to enable runtime PM\n");
+
+ ret = pm_runtime_resume_and_get(dev);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "Failed to resume device\n");
+
+ ret = acpm_handle->ops->tmu.init(acpm_handle, priv->mbox_chan_id);
+ if (ret) {
+ ret = dev_err_probe(dev, ret, "Failed to init TMU\n");
+ goto err_pm_put;
+ }
+
+ for (i = 0; i < priv->num_sensors; i++) {
+ struct acpm_tmu_sensor *sensor = &priv->sensors[i];
+
+ mutex_init(&sensor->lock);
+ sensor->group = &data->sensor_groups[i];
+ sensor->priv = priv;
+
+ sensor->tzd = devm_thermal_of_zone_register(dev, i, sensor,
+ &acpm_tmu_sensor_ops);
+ if (IS_ERR(sensor->tzd)) {
+ ret = PTR_ERR(sensor->tzd);
+ if (ret == -ENODEV) {
+ sensor->tzd = NULL;
+ dev_dbg(dev, "Sensor %d not used in DT, skipping\n", i);
+ continue;
+ }
+
+ ret = dev_err_probe(dev, ret, "Failed to register sensor %d\n", i);
+ goto err_pm_put;
+ }
+
+ ret = devm_thermal_add_hwmon_sysfs(dev, sensor->tzd);
+ if (ret)
+ dev_warn(dev, "Failed to add hwmon sysfs!\n");
+ }
+
+ ret = acpm_tmu_control(priv, true);
+ if (ret) {
+ ret = dev_err_probe(dev, ret, "Failed to enable TMU\n");
+ goto err_pm_put;
+ }
+
+ pm_runtime_put_autosuspend(dev);
+
+ return 0;
+
+err_pm_put:
+ pm_runtime_put_sync(dev);
+ return ret;
+}
+
+static void acpm_tmu_remove(struct platform_device *pdev)
+{
+ struct acpm_tmu_priv *priv = platform_get_drvdata(pdev);
+
+ /* Stop IRQ first to prevent race with thread_fn */
+ disable_irq(priv->irq);
+
+ acpm_tmu_control(priv, false);
+}
+
+static int acpm_tmu_suspend(struct device *dev)
+{
+ struct acpm_tmu_priv *priv = dev_get_drvdata(dev);
+ struct acpm_handle *handle = priv->handle;
+ const struct acpm_tmu_ops *ops = &handle->ops->tmu;
+ int ret;
+
+ ret = acpm_tmu_control(priv, false);
+ if (ret)
+ return ret;
+
+ /* APB clock not required for this specific msg */
+ return ops->suspend(handle, priv->mbox_chan_id);
+}
+
+static int acpm_tmu_resume(struct device *dev)
+{
+ struct acpm_tmu_priv *priv = dev_get_drvdata(dev);
+ struct acpm_handle *handle = priv->handle;
+ const struct acpm_tmu_ops *ops = &handle->ops->tmu;
+ int ret;
+
+ /* APB clock not required for this specific msg */
+ ret = ops->resume(handle, priv->mbox_chan_id);
+ if (ret)
+ return ret;
+
+ return acpm_tmu_control(priv, true);
+}
+
+static int acpm_tmu_runtime_suspend(struct device *dev)
+{
+ struct acpm_tmu_priv *priv = dev_get_drvdata(dev);
+
+ clk_disable_unprepare(priv->clk);
+
+ return 0;
+}
+
+static int acpm_tmu_runtime_resume(struct device *dev)
+{
+ struct acpm_tmu_priv *priv = dev_get_drvdata(dev);
+
+ return clk_prepare_enable(priv->clk);
+}
+
+static const struct dev_pm_ops acpm_tmu_pm_ops = {
+ SYSTEM_SLEEP_PM_OPS(acpm_tmu_suspend, acpm_tmu_resume)
+ RUNTIME_PM_OPS(acpm_tmu_runtime_suspend, acpm_tmu_runtime_resume, NULL)
+};
+
+static struct platform_driver acpm_tmu_driver = {
+ .driver = {
+ .name = "gs-tmu",
+ .pm = pm_ptr(&acpm_tmu_pm_ops),
+ .of_match_table = acpm_tmu_match,
+ },
+ .probe = acpm_tmu_probe,
+ .remove = acpm_tmu_remove,
+};
+module_platform_driver(acpm_tmu_driver);
+
+MODULE_AUTHOR("Tudor Ambarus <tudor.ambarus@linaro.org>");
+MODULE_DESCRIPTION("Samsung Exynos ACPM TMU Driver");
+MODULE_LICENSE("GPL");
--
2.54.0.rc1.555.g9c883467ad-goog
^ permalink raw reply related
* [PATCH v3 10/10] arm64: defconfig: enable Exynos ACPM thermal support
From: Tudor Ambarus @ 2026-04-20 17:39 UTC (permalink / raw)
To: Rafael J. Wysocki, Zhang Rui, Lukasz Luba, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Krzysztof Kozlowski,
Alim Akhtar, Bartlomiej Zolnierkiewicz, Kees Cook,
Gustavo A. R. Silva, Peter Griffin, André Draszik,
Daniel Lezcano, Sylwester Nawrocki, Chanwoo Choi,
Michael Turquette, Stephen Boyd, Lee Jones
Cc: willmcvicker, jyescas, shin.son, linux-samsung-soc, linux-kernel,
linux-pm, devicetree, linux-arm-kernel, linux-hardening,
linux-clk, Tudor Ambarus
In-Reply-To: <20260420-acpm-tmu-v3-0-3dc8e93f0b26@linaro.org>
Enable the Exynos ACPM thermal driver (CONFIG_EXYNOS_ACPM_THERMAL)
to allow temperature monitoring and thermal management on Samsung
Exynos SoCs that use the Alive Clock and Power Manager (ACPM)
protocol.
Signed-off-by: Tudor Ambarus <tudor.ambarus@linaro.org>
---
arch/arm64/configs/defconfig | 1 +
1 file changed, 1 insertion(+)
diff --git a/arch/arm64/configs/defconfig b/arch/arm64/configs/defconfig
index d905a0777f93..3fe76a4c2633 100644
--- a/arch/arm64/configs/defconfig
+++ b/arch/arm64/configs/defconfig
@@ -793,6 +793,7 @@ CONFIG_BCM2711_THERMAL=m
CONFIG_BCM2835_THERMAL=m
CONFIG_BRCMSTB_THERMAL=m
CONFIG_EXYNOS_THERMAL=y
+CONFIG_EXYNOS_ACPM_THERMAL=m
CONFIG_TEGRA_SOCTHERM=m
CONFIG_TEGRA_BPMP_THERMAL=m
CONFIG_GENERIC_ADC_THERMAL=m
--
2.54.0.rc1.555.g9c883467ad-goog
^ permalink raw reply related
* Re: [PATCH v11 12/14] cpuidle/poll_state: Wait for need-resched via tif_need_resched_relaxed_wait()
From: Ankur Arora @ 2026-04-20 17:50 UTC (permalink / raw)
To: Okanovic, Haris
Cc: ankur.a.arora@oracle.com, joao.m.martins@oracle.com,
xueshuai@linux.alibaba.com, david.laight.linux@gmail.com,
boris.ostrovsky@oracle.com, memxor@gmail.com, ashok.bhat@arm.com,
zhenglifeng1@huawei.com, konrad.wilk@oracle.com, cl@gentwo.org,
akpm@linux-foundation.org, linux-kernel@vger.kernel.org,
catalin.marinas@arm.com, ast@kernel.org, rdunlap@infradead.org,
daniel.lezcano@linaro.org, arnd@arndb.de,
linux-arch@vger.kernel.org, will@kernel.org, mark.rutland@arm.com,
peterz@infradead.org, bpf@vger.kernel.org,
linux-arm-kernel@lists.infradead.org, rafael@kernel.org,
linux-pm@vger.kernel.org
In-Reply-To: <a374b23f8b03f850a874d46bc78411fb99483ca2.camel@amazon.com>
Okanovic, Haris <harisokn@amazon.com> writes:
> On Wed, 2026-04-08 at 17:55 +0530, Ankur Arora wrote:
>> CAUTION: This email originated from outside of the organization. Do not click links or open attachments unless you can confirm the sender and know the content is safe.
>>
>>
>>
>> The inner loop in poll_idle() polls over the thread_info flags,
>> waiting to see if the thread has TIF_NEED_RESCHED set. The loop
>> exits once the condition is met, or if the poll time limit has
>> been exceeded.
>>
>> To minimize the number of instructions executed in each iteration,
>> the time check is rate-limited. In addition, each loop iteration
>> executes cpu_relax() which on certain platforms provides a hint to
>> the pipeline that the loop busy-waits, allowing the processor to
>> reduce power consumption.
>>
>> Switch over to tif_need_resched_relaxed_wait() instead, since that
>> provides exactly that.
>>
>> However, since we want to minimize power consumption in idle, building
>> of cpuidle/poll_state.c continues to depend on CONFIG_ARCH_HAS_CPU_RELAX
>> as that serves as an indicator that the platform supports an optimized
>> version of tif_need_resched_relaxed_wait() (via
>> smp_cond_load_acquire_timeout()).
>>
>> Cc: Rafael J. Wysocki <rafael@kernel.org>
>> Cc: Daniel Lezcano <daniel.lezcano@linaro.org>
>> Cc: linux-pm@vger.kernel.org
>> Suggested-by: Rafael J. Wysocki <rafael@kernel.org>
>> Acked-by: Rafael J. Wysocki (Intel) <rafael@kernel.org>
>> Signed-off-by: Ankur Arora <ankur.a.arora@oracle.com>
>> ---
>> drivers/cpuidle/poll_state.c | 21 +--------------------
>> 1 file changed, 1 insertion(+), 20 deletions(-)
>>
>> diff --git a/drivers/cpuidle/poll_state.c b/drivers/cpuidle/poll_state.c
>> index c7524e4c522a..7443b3e971ba 100644
>> --- a/drivers/cpuidle/poll_state.c
>> +++ b/drivers/cpuidle/poll_state.c
>> @@ -6,41 +6,22 @@
>> #include <linux/cpuidle.h>
>> #include <linux/export.h>
>> #include <linux/irqflags.h>
>> -#include <linux/sched.h>
>> -#include <linux/sched/clock.h>
>> #include <linux/sched/idle.h>
>> #include <linux/sprintf.h>
>> #include <linux/types.h>
>>
>> -#define POLL_IDLE_RELAX_COUNT 200
>> -
>> static int __cpuidle poll_idle(struct cpuidle_device *dev,
>> struct cpuidle_driver *drv, int index)
>> {
>> - u64 time_start;
>> -
>> - time_start = local_clock_noinstr();
>> -
>> dev->poll_time_limit = false;
>>
>> raw_local_irq_enable();
>> if (!current_set_polling_and_test()) {
>> - unsigned int loop_count = 0;
>> u64 limit;
>>
>> limit = cpuidle_poll_time(drv, dev);
>>
>> - while (!need_resched()) {
>> - cpu_relax();
>> - if (loop_count++ < POLL_IDLE_RELAX_COUNT)
>> - continue;
>> -
>> - loop_count = 0;
>> - if (local_clock_noinstr() - time_start > limit) {
>> - dev->poll_time_limit = true;
>> - break;
>> - }
>> - }
>> + dev->poll_time_limit = !tif_need_resched_relaxed_wait(limit);
>> }
>> raw_local_irq_disable();
>>
>> --
>> 2.31.1
>>
>
> Hi Ankur,
>
> Tested atop latest mainline d60bc1401 with the rest of your haltpoll
> changes from separate thread:
> ~10% improvement in `perf sched bench pipe` micro and ~4-6% throughput
> improvements in mysql,
> postgresql, cassandra, and memcached in under-loaded configurations.
> Tested on AWS Graviton3 and
> Graviton4, ARM Neoverse V1 and V2 cores respectively.
>
> I hope this series can merge soon. It's been stuck in review for more
> than 2 years.
>
> Tested-by: Haris Okanovic <harisokn@amazon.com>
Thanks Haris. Yeah, I don't think there are any open issues left on
this.
--
ankur
^ permalink raw reply
* Re: [PATCH v2] dt-bindings: thermal: idle: Complete the example code
From: Daniel Lezcano @ 2026-04-20 18:06 UTC (permalink / raw)
To: Rob Herring (Arm), Krzysztof Kozlowski
Cc: Conor Dooley, linux-pm, Krzysztof Kozlowski, linux-kernel,
Daniel Lezcano, devicetree, Lukasz Luba, Zhang Rui,
Rafael J. Wysocki
In-Reply-To: <177559016610.3536304.190890909066170434.robh@kernel.org>
On 4/7/26 21:29, Rob Herring (Arm) wrote:
>
> On Tue, 07 Apr 2026 07:39:58 +0200, Krzysztof Kozlowski wrote:
>> Thermal bindings expect the node name with all the zones to be named
>> 'thermal-zones' (hyphen instead of underscore) and thermal zones to end
>> with '-thermal'. Also DTS coding style is not to use underscores for
>> node names. After using correct names, bindings point warnings for
>> missing properties, so add also thermal-sensors. Drop fake top
>> compatible as it is not useful in this context.
>>
>> Signed-off-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
>>
>> ---
>>
>> Changes in v2:
>> 1. Drop top level compatible and other properties
>> 2. Add thermal-sensors
>> 3. Rename also trips and cpu-thermal
>> ---
>> .../bindings/thermal/thermal-idle.yaml | 18 +++++++-----------
>> 1 file changed, 7 insertions(+), 11 deletions(-)
>>
>
> My bot found errors running 'make dt_binding_check' on your patch:
Hi Krzysztof,
what about this error ?
> yamllint warnings/errors:
>
> dtschema/dtc warnings/errors:
> /builds/robherring/dt-review-ci/linux/Documentation/devicetree/bindings/thermal/thermal-idle.example.dtb: /: 'compatible' is a required property
> from schema $id: http://devicetree.org/schemas/root-node.yaml
> /builds/robherring/dt-review-ci/linux/Documentation/devicetree/bindings/thermal/thermal-idle.example.dtb: /: 'model' is a required property
> from schema $id: http://devicetree.org/schemas/root-node.yaml
> /builds/robherring/dt-review-ci/linux/Documentation/devicetree/bindings/thermal/thermal-idle.example.dtb: /: '#address-cells' is a required property
> from schema $id: http://devicetree.org/schemas/root-node.yaml
> /builds/robherring/dt-review-ci/linux/Documentation/devicetree/bindings/thermal/thermal-idle.example.dtb: /: '#size-cells' is a required property
> from schema $id: http://devicetree.org/schemas/root-node.yaml
>
> doc reference errors (make refcheckdocs):
>
> See https://patchwork.kernel.org/project/devicetree/patch/20260407053957.10508-2-krzysztof.kozlowski@oss.qualcomm.com
>
> The base for the series is generally the latest rc1. A different dependency
> should be noted in *this* patch.
>
> If you already ran 'make dt_binding_check' and didn't see the above
> error(s), then make sure 'yamllint' is installed and dt-schema is up to
> date:
>
> pip3 install dtschema --upgrade
>
> Please check and re-submit after running the above command yourself. Note
> that DT_SCHEMA_FILES can be set to your schema file to speed up checking
> your schema. However, it must be unset to test all examples with your schema.
>
^ permalink raw reply
* [PATCH v5 0/4] rust: add basic serial device bus abstractions
From: Markus Probst @ 2026-04-20 20:07 UTC (permalink / raw)
To: Rob Herring, Greg Kroah-Hartman, Jiri Slaby, Miguel Ojeda,
Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg,
Alice Ryhl, Trevor Gross, Danilo Krummrich, Kari Argillander,
Rafael J. Wysocki, Viresh Kumar, Boqun Feng, David Airlie,
Simona Vetter, Boqun Feng
Cc: linux-serial, linux-kernel, rust-for-linux, linux-pm, driver-core,
dri-devel, Markus Probst
This patch series adds the serdev device bus rust abstraction into the
kernel.
This abstraction will be used by a driver,
which targets the MCU devices in Synology devices.
Kari Argillander also messaged me, stating that he wants to write a
watchdog driver with this abstraction (needing initial device data).
@Rob: Are you willing to maintain these rust abstractions yourself,
as you are the expert on this subsystem, otherwise I would take care of
it with a "SERIAL DEVICE BUS [RUST]" section in the MAINTAINERS file. In
the second case, I assume you are going to pick those patches as-is into
your tree, after they have been reviewed?
Signed-off-by: Markus Probst <markus.probst@posteo.de>
---
Changes in v5:
- fix typo in documentation
- Link to v4: https://lore.kernel.org/r/20260411-rust_serdev-v4-0-845e960c6627@posteo.de
Changes in v4:
- fixed not selecting rust serdev abstraction in sample
- Link to v3: https://lore.kernel.org/r/20260313-rust_serdev-v3-0-c9a3af214f7f@posteo.de
Changes in v3:
- fix vertical import style
- add Kconfig entry for the rust abstraction
- fix documentation in include/linux/serdev.h
- rename private_data to rust_private_data
- fix `complete_all` <-> `wait_for_completion` typo
- move drvdata_borrow call after the completion
- Link to v2: https://lore.kernel.org/r/20260306-rust_serdev-v2-0-e9b23b42b255@posteo.de
Changes in v2:
- fix documentation in `serdev::Driver::write` and
`serdev::Driver::write_all`
- remove use of `dev_info` in probe from the sample
- remove `properties_parse` from the sample
- add optional `baudrate` property to the sample
- remove 1. patch
- remove `TryFrom<&device::Device<Ctx>> for &serdev::Device<Ctx>`
implementation
- fix import style
- add patch to return reference in `devres::register` to fix safety
issue
- add patch to add private data to serdev_device, to fix
`Device.drvdata()` from failing
- simplify abstraction by removing ability to receive the initial
transmission. It may be added later in a separate patch series if
needed.
- Link to v1: https://lore.kernel.org/r/20251220-rust_serdev-v1-0-e44645767621@posteo.de
---
Markus Probst (4):
rust: devres: return reference in `devres::register`
serdev: add rust private data to serdev_device
rust: add basic serial device bus abstractions
samples: rust: add Rust serial device bus sample device driver
drivers/tty/serdev/Kconfig | 7 +
include/linux/serdev.h | 15 +-
rust/bindings/bindings_helper.h | 1 +
rust/helpers/helpers.c | 1 +
rust/helpers/serdev.c | 22 ++
rust/kernel/cpufreq.rs | 3 +-
rust/kernel/devres.rs | 15 +-
rust/kernel/drm/driver.rs | 3 +-
rust/kernel/lib.rs | 2 +
rust/kernel/serdev.rs | 536 +++++++++++++++++++++++++++++++++++++
samples/rust/Kconfig | 11 +
samples/rust/Makefile | 1 +
samples/rust/rust_driver_serdev.rs | 86 ++++++
13 files changed, 693 insertions(+), 10 deletions(-)
---
base-commit: 3131ff5a117498bb4b9db3a238bb311cbf8383ce
change-id: 20251217-rust_serdev-ee5481e9085c
^ permalink raw reply
* [PATCH v5 1/4] rust: devres: return reference in `devres::register`
From: Markus Probst @ 2026-04-20 20:07 UTC (permalink / raw)
To: Rob Herring, Greg Kroah-Hartman, Jiri Slaby, Miguel Ojeda,
Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg,
Alice Ryhl, Trevor Gross, Danilo Krummrich, Kari Argillander,
Rafael J. Wysocki, Viresh Kumar, Boqun Feng, David Airlie,
Simona Vetter, Boqun Feng
Cc: linux-serial, linux-kernel, rust-for-linux, linux-pm, driver-core,
dri-devel, Markus Probst
In-Reply-To: <20260420-rust_serdev-v5-0-57e8ba0519f3@posteo.de>
Return the reference to the initialized data in the `devres::register`
function.
This is needed in a following commit (rust: add basic serial device bus
abstractions).
Acked-by: Viresh Kumar <viresh.kumar@linaro.org>
Signed-off-by: Markus Probst <markus.probst@posteo.de>
---
rust/kernel/cpufreq.rs | 3 ++-
rust/kernel/devres.rs | 15 +++++++++++++--
rust/kernel/drm/driver.rs | 3 ++-
3 files changed, 17 insertions(+), 4 deletions(-)
diff --git a/rust/kernel/cpufreq.rs b/rust/kernel/cpufreq.rs
index f5adee48d40c..31bf7e685097 100644
--- a/rust/kernel/cpufreq.rs
+++ b/rust/kernel/cpufreq.rs
@@ -1052,7 +1052,8 @@ pub fn new_foreign_owned(dev: &Device<Bound>) -> Result
where
T: 'static,
{
- devres::register(dev, Self::new()?, GFP_KERNEL)
+ devres::register(dev, Self::new()?, GFP_KERNEL)?;
+ Ok(())
}
}
diff --git a/rust/kernel/devres.rs b/rust/kernel/devres.rs
index 6afe196be42c..f882bace8601 100644
--- a/rust/kernel/devres.rs
+++ b/rust/kernel/devres.rs
@@ -326,15 +326,26 @@ fn register_foreign<P>(dev: &Device<Bound>, data: P) -> Result
/// }
///
/// fn from_bound_context(dev: &Device<Bound>) -> Result {
-/// devres::register(dev, Registration::new(), GFP_KERNEL)
+/// devres::register(dev, Registration::new(), GFP_KERNEL)?;
+/// Ok(())
/// }
/// ```
-pub fn register<T, E>(dev: &Device<Bound>, data: impl PinInit<T, E>, flags: Flags) -> Result
+pub fn register<'a, T, E>(
+ dev: &'a Device<Bound>,
+ data: impl PinInit<T, E>,
+ flags: Flags,
+) -> Result<&'a T>
where
T: Send + 'static,
Error: From<E>,
{
let data = KBox::pin_init(data, flags)?;
+ let data_ptr = &raw const *data;
+
register_foreign(dev, data)
+ // SAFETY: `dev` is valid for the lifetime of 'a. As long as there is a reference to
+ // `Device<Bound>`, it is guaranteed that the device is not unbound and data has not been
+ // dropped. Thus `data_ptr` is also valid for the lifetime of 'a.
+ .map(|()| unsafe { &*data_ptr })
}
diff --git a/rust/kernel/drm/driver.rs b/rust/kernel/drm/driver.rs
index e09f977b5b51..51e0c7e30cc2 100644
--- a/rust/kernel/drm/driver.rs
+++ b/rust/kernel/drm/driver.rs
@@ -145,7 +145,8 @@ pub fn new_foreign_owned(
let reg = Registration::<T>::new(drm, flags)?;
- devres::register(dev, reg, GFP_KERNEL)
+ devres::register(dev, reg, GFP_KERNEL)?;
+ Ok(())
}
/// Returns a reference to the `Device` instance for this registration.
--
2.52.0
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox