From 801ade22c4fc078ea9b11e1f3fe3aae8b8bef886 Mon Sep 17 00:00:00 2001 From: Daniel Schwierzeck Date: Fri, 21 Aug 2026 10:45:39 +0200 Subject: [PATCH 01/17] cpufreq: airoha: fix probe failure with correct error check dev_pm_domain_attach_list() returns the number of attached PM domains on success (a positive count), or a negative error code on failure. The driver checks "if (ret)" which treats a successful attachment of one or more PM domains as an error, causing the probe to fail with return code 1. Fix by checking "if (ret < 0)" like all other users of this API. Cc: stable@vger.kernel.org Fixes: 84cf9e541ccc ("cpufreq: airoha: Add EN7581 CPUFreq SMCCC driver") Signed-off-by: Daniel Schwierzeck Signed-off-by: Christian Marangi Reviewed-by: Zhongqiu Han Signed-off-by: Viresh Kumar --- drivers/cpufreq/airoha-cpufreq.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/cpufreq/airoha-cpufreq.c b/drivers/cpufreq/airoha-cpufreq.c index 3e7770860d13..8283c528d0c1 100644 --- a/drivers/cpufreq/airoha-cpufreq.c +++ b/drivers/cpufreq/airoha-cpufreq.c @@ -64,7 +64,7 @@ static int airoha_cpufreq_probe(struct platform_device *pdev) /* Attach PM for OPP */ ret = dev_pm_domain_attach_list(cpu_dev, &attach_data, &priv->pd_list); - if (ret) + if (ret < 0) goto clear_opp_config; cpufreq_dt = platform_device_register_simple("cpufreq-dt", -1, NULL, 0); From 02c5d7bfa8724789f6620f7787532666edc891a1 Mon Sep 17 00:00:00 2001 From: Mehmet Koseoglu Date: Thu, 1 Oct 2026 13:19:15 +0300 Subject: [PATCH 02/17] rust: cpufreq: reject NULL from cpufreq_cpu_get() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cpufreq_cpu_get() returns either a referenced policy or NULL. PolicyCpu::from_cpu() passed its return value to from_err_ptr(), which rejects ERR_PTR values but accepts NULL. If the lookup fails, Policy::from_raw_mut() therefore constructs a mutable reference from NULL. Dropping the resulting PolicyCpu then passes the invalid pointer to cpufreq_cpu_put(), causing an oops in kobject_put(). Reject NULL with NonNull before constructing the Policy reference. Return ENODEV instead. A KUnit negative-control run reproduced the oops with the original conversion. The same test passed with this change. The reproducer is available on request. Fixes: 6ebdd7c93177 ("rust: cpufreq: Extend abstractions for policy and driver ops") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Mehmet Koseoglu Reviewed-by: Onur Özkan Reviewed-by: spidermana Signed-off-by: Viresh Kumar --- rust/kernel/cpufreq.rs | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/rust/kernel/cpufreq.rs b/rust/kernel/cpufreq.rs index affa2b9490ef..b75d692846a2 100644 --- a/rust/kernel/cpufreq.rs +++ b/rust/kernel/cpufreq.rs @@ -14,7 +14,13 @@ cpumask, device::{Bound, Device}, devres, - error::{code::*, from_err_ptr, from_result, to_result, Result, VTABLE_DEFAULT_ERROR}, + error::{ + code::*, + from_result, + to_result, + Result, + VTABLE_DEFAULT_ERROR, // + }, ffi::{c_char, c_ulong}, prelude::*, types::ForeignOwnable, @@ -29,7 +35,10 @@ marker::PhantomData, ops::{Deref, DerefMut}, pin::Pin, - ptr, + ptr::{ + self, + NonNull, // + }, }; use macros::vtable; @@ -687,12 +696,13 @@ fn clear_data(&mut self) -> Option { impl<'a> PolicyCpu<'a> { fn from_cpu(cpu: CpuId) -> Result { // SAFETY: It is safe to call `cpufreq_cpu_get` for any valid CPU. - let ptr = from_err_ptr(unsafe { bindings::cpufreq_cpu_get(u32::from(cpu)) })?; + let ptr = + NonNull::new(unsafe { bindings::cpufreq_cpu_get(u32::from(cpu)) }).ok_or(ENODEV)?; Ok(Self( - // SAFETY: The `ptr` is guaranteed to be valid and remains valid for the lifetime of - // the returned reference. - unsafe { Policy::from_raw_mut(ptr) }, + // SAFETY: `ptr` is non-NULL and `cpufreq_cpu_get()` took a reference on it, so it is + // valid for writing and remains valid for the lifetime of the returned reference. + unsafe { Policy::from_raw_mut(ptr.as_ptr()) }, )) } } From e5b33fa47792375ca7fc49ccadee87d8e4b72929 Mon Sep 17 00:00:00 2001 From: Xueqin Luo Date: Mon, 31 Aug 2026 13:44:38 +0800 Subject: [PATCH 03/17] cpufreq: sparc-us2e: fix frequency table index copy-paste error In us2e_freq_cpu_init(), the last two frequency writes both use index [2] instead of [3] and [4] respectively, and the terminator uses [3] instead of [5]. This is a copy-paste error where the index was not incremented, causing the divider-6 and divider-8 entries to overwrite the already-written divider-4 entry. As a result, only three frequency steps (div 1, 2, and 4) are actually available to the cpufreq core, while the intended dividers 6 and 8 are silently lost. The struct us2e_freq_percpu_info::table[6] has room for 5 entries plus a terminator, matching the 5 hardware dividers. Fix the indices so all five frequency steps are correctly populated: table[0]=div1, table[1]=div2, table[2]=div4, table[3]=div6, table[4]=div8, table[5]=TABLE_END. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Signed-off-by: Xueqin Luo Signed-off-by: Viresh Kumar --- drivers/cpufreq/sparc-us2e-cpufreq.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/drivers/cpufreq/sparc-us2e-cpufreq.c b/drivers/cpufreq/sparc-us2e-cpufreq.c index a68706406b88..5cda391ad03b 100644 --- a/drivers/cpufreq/sparc-us2e-cpufreq.c +++ b/drivers/cpufreq/sparc-us2e-cpufreq.c @@ -282,12 +282,11 @@ static int us2e_freq_cpu_init(struct cpufreq_policy *policy) table[1].frequency = clock_tick / 2; table[2].driver_data = 2; table[2].frequency = clock_tick / 4; - table[2].driver_data = 3; - table[2].frequency = clock_tick / 6; - table[2].driver_data = 4; - table[2].frequency = clock_tick / 8; - table[2].driver_data = 5; - table[3].frequency = CPUFREQ_TABLE_END; + table[3].driver_data = 3; + table[3].frequency = clock_tick / 6; + table[4].driver_data = 4; + table[4].frequency = clock_tick / 8; + table[5].frequency = CPUFREQ_TABLE_END; policy->cpuinfo.transition_latency = 0; policy->cur = clock_tick; From f0a750dad7da1c653e85f0698063235453b43831 Mon Sep 17 00:00:00 2001 From: Xueqin Luo Date: Mon, 31 Aug 2026 15:30:08 +0800 Subject: [PATCH 04/17] cpufreq: tegra194: fix double-pointer error in get_cpu_ndiv ndiv is already a u64 pointer, passing &ndiv to smp_call_function_single() results in a u64** being written to instead of the caller's u64 variable, so the caller always reads back an uninitialized ndiv. Drop the spurious '&'. Fixes: 0839ed1fd7ac ("cpufreq: tegra194: add soc data to support multiple soc") Signed-off-by: Xueqin Luo Reviewed-by: Sumit Gupta Signed-off-by: Viresh Kumar --- drivers/cpufreq/tegra194-cpufreq.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/cpufreq/tegra194-cpufreq.c b/drivers/cpufreq/tegra194-cpufreq.c index c6375e14d445..1eb96e9f3310 100644 --- a/drivers/cpufreq/tegra194-cpufreq.c +++ b/drivers/cpufreq/tegra194-cpufreq.c @@ -367,7 +367,7 @@ static void tegra194_get_cpu_ndiv_sysreg(void *ndiv) static int tegra194_get_cpu_ndiv(u32 cpu, u32 cpuid, u32 clusterid, u64 *ndiv) { - return smp_call_function_single(cpu, tegra194_get_cpu_ndiv_sysreg, &ndiv, true); + return smp_call_function_single(cpu, tegra194_get_cpu_ndiv_sysreg, ndiv, true); } static void tegra194_set_cpu_ndiv_sysreg(void *data) From fdbe4cae71c8196cd7681ad47254b5b0560d3c1e Mon Sep 17 00:00:00 2001 From: Xueqin Luo Date: Mon, 31 Aug 2026 17:11:59 +0800 Subject: [PATCH 05/17] cpufreq: sti: avoid NULL dereference in dev_err() ddata.cpu is NULL-checked, then immediately passed to dev_err(). Replace with pr_err() to avoid dereferencing NULL. Fixes: ab0ea257fc58 ("cpufreq: st: Provide runtime initialised driver for ST's platforms") Signed-off-by: Xueqin Luo Signed-off-by: Viresh Kumar --- drivers/cpufreq/sti-cpufreq.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/cpufreq/sti-cpufreq.c b/drivers/cpufreq/sti-cpufreq.c index b15b3142b5fe..144ac0f1d85d 100644 --- a/drivers/cpufreq/sti-cpufreq.c +++ b/drivers/cpufreq/sti-cpufreq.c @@ -263,7 +263,7 @@ static int __init sti_cpufreq_init(void) ddata.cpu = get_cpu_device(0); if (!ddata.cpu) { - dev_err(ddata.cpu, "Failed to get device for CPU0\n"); + pr_err("Failed to get device for CPU0\n"); goto skip_voltage_scaling; } @@ -281,7 +281,7 @@ static int __init sti_cpufreq_init(void) goto register_cpufreq_dt; skip_voltage_scaling: - dev_err(ddata.cpu, "Not doing voltage scaling\n"); + pr_err("Not doing voltage scaling\n"); register_cpufreq_dt: platform_device_register_simple("cpufreq-dt", -1, NULL, 0); From 53732733c7af437b0ed710a1df91a67a0d3dfc07 Mon Sep 17 00:00:00 2001 From: Frank Zhang Date: Fri, 4 Sep 2026 15:56:35 +0800 Subject: [PATCH 06/17] rust: rcpufreq_dt: add module alias Add the missing `platform:cpufreq-dt` module alias so that the driver can be automatically loaded when the platform device is registered. Fixes: 06149d8f2216 ("cpufreq: Add Rust-based cpufreq-dt driver") Cc: stable@vger.kernel.org Signed-off-by: Frank Zhang Signed-off-by: Viresh Kumar --- drivers/cpufreq/rcpufreq_dt.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/cpufreq/rcpufreq_dt.rs b/drivers/cpufreq/rcpufreq_dt.rs index d7ead60bf8c2..2b1f18b7a1e7 100644 --- a/drivers/cpufreq/rcpufreq_dt.rs +++ b/drivers/cpufreq/rcpufreq_dt.rs @@ -225,4 +225,5 @@ fn probe<'bound>( authors: ["Viresh Kumar "], description: "Generic CPUFreq DT driver", license: "GPL v2", + alias: ["platform:cpufreq-dt"], } From 3b585d7fa3a1256c8bc3b9bb3b1dcfc3b2ff5d91 Mon Sep 17 00:00:00 2001 From: Sumeet Pawnikar Date: Sun, 13 Sep 2026 00:41:53 +0530 Subject: [PATCH 07/17] cpufreq: Use %pe to print error pointers symbolically Replace PTR_ERR() and %ld with %pe and pass the original pointer directly to pr_err() and pr_warn(). The %pe format specifier prints a symbolic error name (e.g. -ENOMEM) when CONFIG_SYMBOLIC_ERRNAME is enabled, otherwise it falls back gracefully and prints the raw integer value. This makes messages more readable without any functional change. Signed-off-by: Sumeet Pawnikar Signed-off-by: Viresh Kumar --- drivers/cpufreq/bmips-cpufreq.c | 4 ++-- drivers/cpufreq/cppc_cpufreq.c | 4 ++-- drivers/cpufreq/qoriq-cpufreq.c | 3 +-- drivers/cpufreq/s3c64xx-cpufreq.c | 5 ++--- 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/drivers/cpufreq/bmips-cpufreq.c b/drivers/cpufreq/bmips-cpufreq.c index a8e35bc75fb2..389fff5e6f65 100644 --- a/drivers/cpufreq/bmips-cpufreq.c +++ b/drivers/cpufreq/bmips-cpufreq.c @@ -132,8 +132,8 @@ static int bmips_cpufreq_init(struct cpufreq_policy *policy) freq_table = bmips_cpufreq_get_freq_table(policy); if (IS_ERR(freq_table)) { - pr_err("%s: couldn't determine frequency table (%ld).\n", - BMIPS_CPUFREQ_NAME, PTR_ERR(freq_table)); + pr_err("%s: couldn't determine frequency table (%pe).\n", + BMIPS_CPUFREQ_NAME, freq_table); return PTR_ERR(freq_table); } diff --git a/drivers/cpufreq/cppc_cpufreq.c b/drivers/cpufreq/cppc_cpufreq.c index 80893844353c..f767898ebfb5 100644 --- a/drivers/cpufreq/cppc_cpufreq.c +++ b/drivers/cpufreq/cppc_cpufreq.c @@ -230,8 +230,8 @@ static void cppc_fie_kworker_init(void) kworker_fie = kthread_run_worker(0, "cppc_fie"); if (IS_ERR(kworker_fie)) { - pr_warn("%s: failed to create kworker_fie: %ld\n", __func__, - PTR_ERR(kworker_fie)); + pr_warn("%s: failed to create kworker_fie: %pe\n", __func__, + kworker_fie); fie_disabled = FIE_DISABLED; kworker_fie = NULL; return; diff --git a/drivers/cpufreq/qoriq-cpufreq.c b/drivers/cpufreq/qoriq-cpufreq.c index 42edb41ad459..a21b6f01218a 100644 --- a/drivers/cpufreq/qoriq-cpufreq.c +++ b/drivers/cpufreq/qoriq-cpufreq.c @@ -57,8 +57,7 @@ static u32 get_bus_freq(void) /* get platform freq by its clock name */ pltclk = clk_get(NULL, "cg-pll0-div1"); if (IS_ERR(pltclk)) { - pr_err("%s: can't get bus frequency %ld\n", - __func__, PTR_ERR(pltclk)); + pr_err("%s: can't get bus frequency %pe\n", __func__, pltclk); return PTR_ERR(pltclk); } diff --git a/drivers/cpufreq/s3c64xx-cpufreq.c b/drivers/cpufreq/s3c64xx-cpufreq.c index 9cef71528076..9a01592425ee 100644 --- a/drivers/cpufreq/s3c64xx-cpufreq.c +++ b/drivers/cpufreq/s3c64xx-cpufreq.c @@ -152,15 +152,14 @@ static int s3c64xx_cpufreq_driver_init(struct cpufreq_policy *policy) policy->clk = clk_get(NULL, "armclk"); if (IS_ERR(policy->clk)) { - pr_err("Unable to obtain ARMCLK: %ld\n", - PTR_ERR(policy->clk)); + pr_err("Unable to obtain ARMCLK: %pe\n", policy->clk); return PTR_ERR(policy->clk); } #ifdef CONFIG_REGULATOR vddarm = regulator_get(NULL, "vddarm"); if (IS_ERR(vddarm)) { - pr_err("Failed to obtain VDDARM: %ld\n", PTR_ERR(vddarm)); + pr_err("Failed to obtain VDDARM: %pe\n", vddarm); pr_err("Only frequency scaling available\n"); vddarm = NULL; } else { From cce713d008bdcca5e46541de86eab79b80d394bc Mon Sep 17 00:00:00 2001 From: Alexander Koskovich Date: Mon, 5 Oct 2026 17:47:27 +0000 Subject: [PATCH 08/17] cpufreq: Add Milos to cpufreq-dt-platdev blocklist The Qualcomm Milos platform uses the qcom-cpufreq-hw driver so add it to the cpufreq-dt-platdev driver's blocklist. Signed-off-by: Alexander Koskovich Signed-off-by: Viresh Kumar --- drivers/cpufreq/cpufreq-dt-platdev.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/cpufreq/cpufreq-dt-platdev.c b/drivers/cpufreq/cpufreq-dt-platdev.c index 371ca1fe9020..574c5bec4831 100644 --- a/drivers/cpufreq/cpufreq-dt-platdev.c +++ b/drivers/cpufreq/cpufreq-dt-platdev.c @@ -155,6 +155,7 @@ static const struct of_device_id blocklist[] __initconst = { { .compatible = "nvidia,tegra234", }, { .compatible = "qcom,apq8096", }, + { .compatible = "qcom,milos", }, { .compatible = "qcom,msm8909", }, { .compatible = "qcom,msm8996", }, { .compatible = "qcom,msm8998", }, From 95cc3927ce26cadc325833a16b13c2a3c08353de Mon Sep 17 00:00:00 2001 From: Kathiravan Thirumoorthy Date: Wed, 7 Oct 2026 12:31:56 +0530 Subject: [PATCH 09/17] cpufreq: qcom-nvmem: Add IPQ9650 support Add IPQ9650 support to the Qualcomm NVMEM cpufreq driver so the supported OPP can be selected at runtime using the eFuse value and the opp-supported-hw OPP property. Also block the generic cpufreq-dt platform device for IPQ9650 so the NVMEM-based driver is used. Signed-off-by: Kathiravan Thirumoorthy Reviewed-by: Dmitry Baryshkov Signed-off-by: Viresh Kumar --- drivers/cpufreq/cpufreq-dt-platdev.c | 1 + drivers/cpufreq/qcom-cpufreq-nvmem.c | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/drivers/cpufreq/cpufreq-dt-platdev.c b/drivers/cpufreq/cpufreq-dt-platdev.c index 574c5bec4831..bce4c87573a1 100644 --- a/drivers/cpufreq/cpufreq-dt-platdev.c +++ b/drivers/cpufreq/cpufreq-dt-platdev.c @@ -210,6 +210,7 @@ static const struct of_device_id blocklist[] __initconst = { { .compatible = "qcom,ipq8064", }, { .compatible = "qcom,ipq8074", }, { .compatible = "qcom,ipq9574", }, + { .compatible = "qcom,ipq9650", }, { .compatible = "qcom,apq8064", }, { .compatible = "qcom,msm8974", }, { .compatible = "qcom,msm8960", }, diff --git a/drivers/cpufreq/qcom-cpufreq-nvmem.c b/drivers/cpufreq/qcom-cpufreq-nvmem.c index efa766e98d86..375f12d5d11a 100644 --- a/drivers/cpufreq/qcom-cpufreq-nvmem.c +++ b/drivers/cpufreq/qcom-cpufreq-nvmem.c @@ -211,6 +211,14 @@ static int qcom_cpufreq_kryo_name_version(struct device *cpu_dev, case QCOM_ID_IPQ5404: drv->versions = (*speedbin == 0x3b) ? BIT(1) : BIT(0); break; + case QCOM_ID_IPQ9610: + case QCOM_ID_IPQ9620: + case QCOM_ID_IPQ9630: + case QCOM_ID_IPQ9640: + case QCOM_ID_IPQ9650: + case QCOM_ID_IPQ9670: + drv->versions = (*speedbin == 0x4b) ? BIT(3) : BIT(2); + break; case QCOM_ID_MSM8996SG: case QCOM_ID_APQ8096SG: drv->versions = 1 << ((unsigned int)(*speedbin) + 4); @@ -633,6 +641,7 @@ static const struct of_device_id qcom_cpufreq_match_list[] __initconst __maybe_u { .compatible = "qcom,ipq8074", .data = &match_data_ipq8074 }, { .compatible = "qcom,apq8064", .data = &match_data_krait }, { .compatible = "qcom,ipq9574", .data = &match_data_kryo }, + { .compatible = "qcom,ipq9650", .data = &match_data_kryo }, { .compatible = "qcom,msm8974", .data = &match_data_krait }, { .compatible = "qcom,msm8960", .data = &match_data_krait }, {}, From 6fa50b1988b8dfd2e92a9cecba073fe4347a5e23 Mon Sep 17 00:00:00 2001 From: Haotian Zhang Date: Fri, 9 Oct 2026 01:24:42 +0800 Subject: [PATCH 10/17] cpufreq: qcom-cpufreq-hw: fix device node refcount leak in phandle parsing of_parse_phandle_with_args() transfers a reference to the provider node in out_args->np, which the caller is responsible for releasing with of_node_put(). Neither qcom_get_related_cpus() nor qcom_cpufreq_hw_cpu_init() ever puts args.np: they only put cpu_np, which is a different node obtained from of_cpu_device_node_get(). The reference is therefore leaked on every successful call, and qcom_get_related_cpus() leaks one per present CPU. Release args.np after the parsed index has been read in both call sites. Fixes: 2849dd8bc72b ("cpufreq: qcom-hw: Add support for QCOM cpufreq HW driver") Assisted-by: DeepSeek-V4.1-Flash Signed-off-by: Haotian Zhang Signed-off-by: Viresh Kumar --- drivers/cpufreq/qcom-cpufreq-hw.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/cpufreq/qcom-cpufreq-hw.c b/drivers/cpufreq/qcom-cpufreq-hw.c index 874ff3fb9d97..060a460716f7 100644 --- a/drivers/cpufreq/qcom-cpufreq-hw.c +++ b/drivers/cpufreq/qcom-cpufreq-hw.c @@ -322,6 +322,8 @@ static void qcom_get_related_cpus(int index, struct cpumask *m) if (index == args.args[0]) cpumask_set_cpu(cpu, m); + + of_node_put(args.np); } } @@ -554,6 +556,7 @@ static int qcom_cpufreq_hw_cpu_init(struct cpufreq_policy *policy) return ret; index = args.args[0]; + of_node_put(args.np); data = &qcom_cpufreq.data[index]; /* HW should be in enabled state to proceed */ From a4b90518dd01f85e1e4bf7e9361396b2eded1f7e Mon Sep 17 00:00:00 2001 From: Ryan Brue Date: Fri, 9 Oct 2026 14:44:43 -0500 Subject: [PATCH 11/17] cpufreq: mediatek: Enter system sleep at the suspend OPP cpufreq_suspend() stops the governors at the start of system suspend, and this driver has no .suspend callback, so each cluster sleeps at whatever OPP it was last left at. On an MT8173 tablet (Amazon Fire HD 10, 2017), entering suspend-to-RAM with both clusters at their highest OPP measured 63.5 mA. With this change and the lowest OPP marked, the same entry measured 52.0 mA. Use the generic suspend-frequency support, as cpufreq-dt does. Tables without an opp-suspend entry leave suspend_freq at 0, which makes cpufreq_generic_suspend() a no-op, so they are unaffected. Assisted-by: LLM Signed-off-by: Ryan Brue Signed-off-by: Viresh Kumar --- drivers/cpufreq/mediatek-cpufreq.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/cpufreq/mediatek-cpufreq.c b/drivers/cpufreq/mediatek-cpufreq.c index 052ca7cd2f4f..f9b1eca176eb 100644 --- a/drivers/cpufreq/mediatek-cpufreq.c +++ b/drivers/cpufreq/mediatek-cpufreq.c @@ -603,6 +603,7 @@ static int mtk_cpufreq_init(struct cpufreq_policy *policy) policy->freq_table = freq_table; policy->driver_data = info; policy->clk = info->cpu_clk; + policy->suspend_freq = dev_pm_opp_get_suspend_opp_freq(info->cpu_dev) / 1000; return 0; } @@ -624,6 +625,7 @@ static struct cpufreq_driver mtk_cpufreq_driver = { .init = mtk_cpufreq_init, .exit = mtk_cpufreq_exit, .register_em = cpufreq_register_em_with_opp, + .suspend = cpufreq_generic_suspend, .name = "mtk-cpufreq", }; From 71245acb8c937382fc31806572314d36667b17f6 Mon Sep 17 00:00:00 2001 From: Ryan Brue Date: Fri, 9 Oct 2026 14:44:44 -0500 Subject: [PATCH 12/17] arm64: dts: mediatek: mt8173: Mark the lowest CPU OPPs for suspend Both clusters otherwise enter system sleep at whatever OPP the governor last chose. On the Amazon Fire HD 10 (2017), entering suspend-to-RAM with both clusters at their highest OPP measured 63.5 mA, against 52.0 mA when they suspend at 507 MHz. Mark the 507 MHz OPP of each cluster, the lowest in both tables, as the one to suspend at. mediatek-cpufreq uses it for system sleep, and a kernel without that support ignores it. Assisted-by: LLM Signed-off-by: Ryan Brue Signed-off-by: Viresh Kumar --- arch/arm64/boot/dts/mediatek/mt8173.dtsi | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/arm64/boot/dts/mediatek/mt8173.dtsi b/arch/arm64/boot/dts/mediatek/mt8173.dtsi index 78c2ccd5be13..7f1af15cb934 100644 --- a/arch/arm64/boot/dts/mediatek/mt8173.dtsi +++ b/arch/arm64/boot/dts/mediatek/mt8173.dtsi @@ -56,6 +56,7 @@ cluster0_opp: opp-table-0 { opp-507000000 { opp-hz = /bits/ 64 <507000000>; opp-microvolt = <859000>; + opp-suspend; }; opp-702000000 { opp-hz = /bits/ 64 <702000000>; @@ -93,6 +94,7 @@ cluster1_opp: opp-table-1 { opp-507000000 { opp-hz = /bits/ 64 <507000000>; opp-microvolt = <828000>; + opp-suspend; }; opp-702000000 { opp-hz = /bits/ 64 <702000000>; From 48a82a3f44576a1b227f964fdb150e1cae7455bf Mon Sep 17 00:00:00 2001 From: Huacai Chen Date: Fri, 28 Aug 2026 11:11:57 +0800 Subject: [PATCH 13/17] cpufreq: loongson3: Make this driver depend on MACH_LOONGSON64 32BIT Loongson machines don't have SMC and FreqCtrl registers, so make this driver depend on MACH_LOONGSON64 (only for LoongArch based 64bit machines currently). Cc: stable@vger.kernel.org Fixes: ccf51454145b ("cpufreq: Add Loongson-3 CPUFreq driver support") Signed-off-by: Huacai Chen Reviewed-by: Zhongqiu Han Signed-off-by: Viresh Kumar --- drivers/cpufreq/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/cpufreq/Kconfig b/drivers/cpufreq/Kconfig index db83f3365698..edc1299098d4 100644 --- a/drivers/cpufreq/Kconfig +++ b/drivers/cpufreq/Kconfig @@ -288,6 +288,7 @@ endif if LOONGARCH config LOONGSON3_CPUFREQ tristate "Loongson3 CPUFreq Driver" + depends on MACH_LOONGSON64 help This option adds a CPUFreq driver for Loongson processors which support software configurable cpu frequency. From 290814d3c22b300eb36b74dc76ad36bd7fdc1220 Mon Sep 17 00:00:00 2001 From: Huacai Chen Date: Fri, 28 Aug 2026 11:11:58 +0800 Subject: [PATCH 14/17] cpufreq: loongson3: Adjust the width of id and val in smc_message The id field of smc_message is usually stand for the CPU ID. In the past this driver was only tested for desktop and laptop productions so 4-bits are enough. But now we have servers that can have as many as 256 cores, so we need 8-bits CPU ID. On the other hand, the val field is usually stand for CPU frequency so 12-bits are enough to represent 4GHz, and for higher frequencies there is an extra bit to extend (enough for 8GHz). In theory, this is a incompatible change, but fortunately the old SMC firmwares are not widely shipped and can be updated on the air, thus we can safely adjust the widths. Cc: stable@vger.kernel.org Fixes: ccf51454145b ("cpufreq: Add Loongson-3 CPUFreq driver support") Signed-off-by: Hongliang Wang Signed-off-by: Huacai Chen Reviewed-by: Zhongqiu Han Signed-off-by: Viresh Kumar --- drivers/cpufreq/loongson3_cpufreq.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/cpufreq/loongson3_cpufreq.c b/drivers/cpufreq/loongson3_cpufreq.c index 1e8715ea1b77..630f679aa739 100644 --- a/drivers/cpufreq/loongson3_cpufreq.c +++ b/drivers/cpufreq/loongson3_cpufreq.c @@ -21,9 +21,9 @@ union smc_message { u32 value; struct { - u32 id : 4; + u32 id : 8; u32 info : 4; - u32 val : 16; + u32 val : 12; u32 cmd : 6; u32 extra : 1; u32 complete : 1; From 5d7802ada30b3101f408706b7399b0e170d2febd Mon Sep 17 00:00:00 2001 From: Huacai Chen Date: Fri, 28 Aug 2026 11:11:59 +0800 Subject: [PATCH 15/17] cpufreq: loongson3: Replace per-package mutex with per-node Our server productions (e.g. Loongson-3D6000/3E6000) can have multiple nodes in one package and SMC mailboxes are also per-node. So replace the per-package mutex with per-node one. Cc: stable@vger.kernel.org Fixes: ccf51454145b ("cpufreq: Add Loongson-3 CPUFreq driver support") Signed-off-by: Hongliang Wang Signed-off-by: Huacai Chen Reviewed-by: Zhongqiu Han Signed-off-by: Viresh Kumar --- drivers/cpufreq/loongson3_cpufreq.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/cpufreq/loongson3_cpufreq.c b/drivers/cpufreq/loongson3_cpufreq.c index 630f679aa739..e3cd78a5ab18 100644 --- a/drivers/cpufreq/loongson3_cpufreq.c +++ b/drivers/cpufreq/loongson3_cpufreq.c @@ -169,7 +169,7 @@ struct loongson3_freq_data { struct cpufreq_frequency_table table[]; }; -static struct mutex cpufreq_mutex[MAX_PACKAGES]; +static struct mutex cpufreq_mutex[MAX_NUMNODES]; static struct cpufreq_driver loongson3_cpufreq_driver; static DEFINE_PER_CPU(struct loongson3_freq_data *, freq_data); @@ -177,14 +177,14 @@ static inline int do_service_request(u32 id, u32 info, u32 cmd, u32 val, u32 ext { int retries; unsigned int cpu = raw_smp_processor_id(); - unsigned int package = cpu_data[cpu].package; + unsigned int nid = cpu_to_node(cpu); union smc_message msg, last; - mutex_lock(&cpufreq_mutex[package]); + mutex_lock(&cpufreq_mutex[nid]); last.value = iocsr_read32(LOONGARCH_IOCSR_SMCMBX); if (!last.complete) { - mutex_unlock(&cpufreq_mutex[package]); + mutex_unlock(&cpufreq_mutex[nid]); return -EPERM; } @@ -208,11 +208,11 @@ static inline int do_service_request(u32 id, u32 info, u32 cmd, u32 val, u32 ext } if (!msg.complete || msg.cmd != CMD_OK) { - mutex_unlock(&cpufreq_mutex[package]); + mutex_unlock(&cpufreq_mutex[nid]); return -EPERM; } - mutex_unlock(&cpufreq_mutex[package]); + mutex_unlock(&cpufreq_mutex[nid]); return msg.val; } @@ -337,7 +337,7 @@ static int loongson3_cpufreq_probe(struct platform_device *pdev) { int i, ret; - for (i = 0; i < MAX_PACKAGES; i++) { + for (i = 0; i < MAX_NUMNODES; i++) { ret = devm_mutex_init(&pdev->dev, &cpufreq_mutex[i]); if (ret) return ret; From e38360d7c62c8fa530b58905f99a4557d4d6b352 Mon Sep 17 00:00:00 2001 From: Huacai Chen Date: Fri, 28 Aug 2026 11:12:00 +0800 Subject: [PATCH 16/17] cpufreq: loongson3: Use global physical CPU ID in get/target callbacks Our server productions (e.g. Loongson-3D6000/3E6000) can have discrete global physical CPU IDs while the core ID inside the packages are always continuous. In these cases we should use global physical CPU IDs to get and set frequencies. Cc: stable@vger.kernel.org Fixes: ccf51454145b ("cpufreq: Add Loongson-3 CPUFreq driver support") Signed-off-by: Hongliang Wang Signed-off-by: Huacai Chen Reviewed-by: Zhongqiu Han Signed-off-by: Viresh Kumar --- drivers/cpufreq/loongson3_cpufreq.c | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/drivers/cpufreq/loongson3_cpufreq.c b/drivers/cpufreq/loongson3_cpufreq.c index e3cd78a5ab18..1ae821e265fb 100644 --- a/drivers/cpufreq/loongson3_cpufreq.c +++ b/drivers/cpufreq/loongson3_cpufreq.c @@ -16,6 +16,7 @@ #include #include #include +#include /* Message */ union smc_message { @@ -219,38 +220,40 @@ static inline int do_service_request(u32 id, u32 info, u32 cmd, u32 val, u32 ext static unsigned int loongson3_cpufreq_get(unsigned int cpu) { - int ret; + int ret, core = cpu_logical_map(cpu); - ret = do_service_request(cpu, FREQ_INFO_TYPE_FREQ, CMD_GET_FREQ_INFO, 0, 0); + ret = do_service_request(core, FREQ_INFO_TYPE_FREQ, CMD_GET_FREQ_INFO, 0, 0); - return ret * KILO; + return (ret < 0) ? 0 : ret * KILO; } static int loongson3_cpufreq_target(struct cpufreq_policy *policy, unsigned int index) { - int ret; + int ret, core = cpu_logical_map(policy->cpu); - ret = do_service_request(cpu_data[policy->cpu].core, - FREQ_INFO_TYPE_LEVEL, CMD_SET_FREQ_INFO, index, 0); + ret = do_service_request(core, FREQ_INFO_TYPE_LEVEL, CMD_SET_FREQ_INFO, + index, 0); return (ret >= 0) ? 0 : ret; } static int configure_freq_table(int cpu) { - int i, ret, boost_level, max_level, freq_level; + int i, ret, core, boost_level, max_level, freq_level; struct platform_device *pdev = cpufreq_get_driver_data(); struct loongson3_freq_data *data; if (per_cpu(freq_data, cpu)) return 0; - ret = do_service_request(cpu, 0, CMD_GET_FREQ_LEVEL_NUM, 0, 0); + core = cpu_logical_map(cpu); + + ret = do_service_request(core, 0, CMD_GET_FREQ_LEVEL_NUM, 0, 0); if (ret < 0) return ret; max_level = ret; - ret = do_service_request(cpu, 0, CMD_GET_FREQ_BOOST_LEVEL, 0, 0); + ret = do_service_request(core, 0, CMD_GET_FREQ_BOOST_LEVEL, 0, 0); if (ret < 0) return ret; boost_level = ret; @@ -263,7 +266,7 @@ static int configure_freq_table(int cpu) data->def_freq_level = boost_level - 1; for (i = 0; i < freq_level; i++) { - ret = do_service_request(cpu, FREQ_INFO_TYPE_FREQ, CMD_GET_FREQ_LEVEL_INFO, i, 0); + ret = do_service_request(core, FREQ_INFO_TYPE_FREQ, CMD_GET_FREQ_LEVEL_INFO, i, 0); if (ret < 0) { devm_kfree(&pdev->dev, data); return ret; From 81792daacfb41ca07b04bd3956ea9e672080cc98 Mon Sep 17 00:00:00 2001 From: Huacai Chen Date: Fri, 28 Aug 2026 11:12:01 +0800 Subject: [PATCH 17/17] cpufreq: loongson3: Replace IOCSR read/write with MMIO ones Our server productions (e.g. Loongson-3D6000/3E6000) can have multiple nodes in one package and SMC mailboxes are also per-node. However, IOCSR read/write can only perform on the current node, while sometimes we want to perform on other nodes (e.g. when switch governor, the get and target callbacks are not run on target core). So replace IOCSR read/write with MMIO ones. Cc: stable@vger.kernel.org Fixes: ccf51454145b ("cpufreq: Add Loongson-3 CPUFreq driver support") Tested-by: Xi Ruoyao Signed-off-by: Huacai Chen Reviewed-by: Zhongqiu Han Signed-off-by: Viresh Kumar --- drivers/cpufreq/loongson3_cpufreq.c | 34 +++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/drivers/cpufreq/loongson3_cpufreq.c b/drivers/cpufreq/loongson3_cpufreq.c index 1ae821e265fb..aea7c31e155a 100644 --- a/drivers/cpufreq/loongson3_cpufreq.c +++ b/drivers/cpufreq/loongson3_cpufreq.c @@ -17,6 +17,7 @@ #include #include #include +#include /* Message */ union smc_message { @@ -165,6 +166,12 @@ union smc_message { #define FREQ_MAX_LEVEL 16 +#define MMIO_SMCMBX(node) \ + ((void __iomem *)(nid_to_addrbase(node) | IO_BASE | LOONGSON_REG_BASE | LOONGARCH_IOCSR_SMCMBX)) + +#define MMIO_MISC_FUNC(node) \ + ((void __iomem *)(nid_to_addrbase(node) | IO_BASE | LOONGSON_REG_BASE | LOONGARCH_IOCSR_MISC_FUNC)) + struct loongson3_freq_data { unsigned int def_freq_level; struct cpufreq_frequency_table table[]; @@ -177,13 +184,27 @@ static DEFINE_PER_CPU(struct loongson3_freq_data *, freq_data); static inline int do_service_request(u32 id, u32 info, u32 cmd, u32 val, u32 extra) { int retries; - unsigned int cpu = raw_smp_processor_id(); - unsigned int nid = cpu_to_node(cpu); + unsigned int cpu = 0, nid; union smc_message msg, last; +#ifdef CONFIG_SMP + switch (cmd) { + case CMD_GET_FREQ_INFO: + case CMD_SET_FREQ_INFO: + case CMD_GET_FREQ_LEVEL_NUM: + case CMD_GET_FREQ_LEVEL_INFO: + case CMD_GET_FREQ_BOOST_LEVEL: + cpu = cpu_number_map(id); + break; + default: + cpu = raw_smp_processor_id(); + } +#endif + nid = cpu_to_node(cpu); + mutex_lock(&cpufreq_mutex[nid]); - last.value = iocsr_read32(LOONGARCH_IOCSR_SMCMBX); + last.value = readl(MMIO_SMCMBX(nid)); if (!last.complete) { mutex_unlock(&cpufreq_mutex[nid]); return -EPERM; @@ -196,12 +217,11 @@ static inline int do_service_request(u32 id, u32 info, u32 cmd, u32 val, u32 ext msg.extra = extra; msg.complete = 0; - iocsr_write32(msg.value, LOONGARCH_IOCSR_SMCMBX); - iocsr_write32(iocsr_read32(LOONGARCH_IOCSR_MISC_FUNC) | IOCSR_MISC_FUNC_SOFT_INT, - LOONGARCH_IOCSR_MISC_FUNC); + writel(msg.value, MMIO_SMCMBX(nid)); + writel(readl(MMIO_MISC_FUNC(nid)) | IOCSR_MISC_FUNC_SOFT_INT, MMIO_MISC_FUNC(nid)); for (retries = 0; retries < 10000; retries++) { - msg.value = iocsr_read32(LOONGARCH_IOCSR_SMCMBX); + msg.value = readl(MMIO_SMCMBX(nid)); if (msg.complete) break;