* [PATCH 0/9] cpufreq: tegra194: add Tegra264 support
@ 2026-11-04 18:15 Sumit Gupta
2026-11-04 18:15 ` [PATCH 1/9] cpufreq: tegra194: move MPIDR read to CPU hotplug callback Sumit Gupta
` (8 more replies)
0 siblings, 9 replies; 13+ messages in thread
From: Sumit Gupta @ 2026-11-04 18:15 UTC (permalink / raw)
To: rafael, viresh.kumar, robh, krzk+dt, conor+dt, treding, jonathanh,
linux-pm, linux-tegra, linux-kernel, devicetree
Cc: bbasu, sumitg
This series adds cpufreq support for Tegra264, which joins the SoCs
already supported by the tegra194-cpufreq driver.
Tegra264 keeps the existing model, where BPMP provides the NDIV limits.
The driver derives the current frequency from the core clock and
reference counters.
The main differences are:
- Frequency read: through system registers, as on Tegra194, but from
the 64-bit architected AMU counters. The reference counter ticks at
the system counter rate (CNTFRQ).
- Frequency request: through MMIO, as on Tegra234. The driver writes
NDIV to a core_freq_req register in the CCPLEX register space. Each
register controls a cluster-clock unit shared by two single-CPU
clusters, so both CPUs form one cpufreq policy.
The patches fall into four groups:
- Fix (patch 1): read each CPU's MPIDR from a CPU-hotplug callback
instead of at probe. A CPU that is offline at probe then also gets
valid cluster and core IDs.
- Preparation (patches 2 to 5): cleanups, mostly of the counter code,
to make room for a 64-bit counter and for an MMIO mapping without
ACTMON counters.
- Improvement (patches 6 and 7): group CPUs by the generic
"performance-domains" property, which cluster-based grouping cannot
express on Tegra264. Device trees without it keep the existing
grouping.
- Tegra264 support (patches 8 and 9): the driver support, followed by
the device tree changes it relies on.
Patch 9 depends on "arm64: tegra: Fix address translation on Tegra264"
from Thierry's series [1]. Without it, the ccplex@30000000 node does not
translate to the CCPLEX registers.
Sumit Gupta (9):
cpufreq: tegra194: move MPIDR read to CPU hotplug callback
cpufreq: tegra194: remove duplicate counter sampling loops
cpufreq: tegra194: add tegra_counter_delta helper
cpufreq: tegra194: add per-SoC counter mask
cpufreq: tegra194: split MMIO mapping from ACTMON offset
dt-bindings: arm: nvidia: Document the Tegra264 CCPLEX cluster
cpufreq: tegra194: derive frequency domain from device tree
cpufreq: tegra194: Add support for Tegra264
arm64: tegra: Add CCPLEX cluster node for Tegra264
.../tegra/nvidia,tegra-ccplex-cluster.yaml | 27 +-
arch/arm64/boot/dts/nvidia/tegra264.dtsi | 21 +
drivers/cpufreq/Kconfig.arm | 4 +-
drivers/cpufreq/tegra194-cpufreq.c | 422 +++++++++++++-----
4 files changed, 357 insertions(+), 117 deletions(-)
[1] https://lore.kernel.org/linux-tegra/20261030141101.845044-1-thierry.reding@kernel.org/
--
2.34.1
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH 1/9] cpufreq: tegra194: move MPIDR read to CPU hotplug callback
2026-11-04 18:15 [PATCH 0/9] cpufreq: tegra194: add Tegra264 support Sumit Gupta
@ 2026-11-04 18:15 ` Sumit Gupta
2026-11-04 18:29 ` sashiko-bot
2026-11-04 18:15 ` [PATCH 2/9] cpufreq: tegra194: remove duplicate counter sampling loops Sumit Gupta
` (7 subsequent siblings)
8 siblings, 1 reply; 13+ messages in thread
From: Sumit Gupta @ 2026-11-04 18:15 UTC (permalink / raw)
To: rafael, viresh.kumar, robh, krzk+dt, conor+dt, treding, jonathanh,
linux-pm, linux-tegra, linux-kernel, devicetree
Cc: bbasu, sumitg
At probe the driver reads the MPIDR of every possible CPU with
smp_call_function_single(). For a CPU that is offline at boot, the call
fails and the error is ignored, so the CPU's core and cluster IDs come
from an uninitialised variable.
Read MPIDR from a CPU-hotplug online callback instead, which runs on the
target CPU and caches its core and cluster IDs and MMIO scratch address.
This covers CPUs online at probe and CPUs onlined later.
The check that the SoC data covers all CPUs moves to probe, against
nr_cpu_ids. A CPU whose MPIDR does not fit the SoC data gets no scratch
address, and its NDIV access is skipped.
Fixes: 6b121b4cf7e1 ("cpufreq: tegra194: save CPU data to avoid repeated SMP calls")
Signed-off-by: Sumit Gupta <sumitg@nvidia.com>
---
drivers/cpufreq/tegra194-cpufreq.c | 114 ++++++++++++++++++++---------
1 file changed, 79 insertions(+), 35 deletions(-)
diff --git a/drivers/cpufreq/tegra194-cpufreq.c b/drivers/cpufreq/tegra194-cpufreq.c
index 1eb96e9f3310..be81ffd23edd 100644
--- a/drivers/cpufreq/tegra194-cpufreq.c
+++ b/drivers/cpufreq/tegra194-cpufreq.c
@@ -5,6 +5,7 @@
#include <linux/cpu.h>
#include <linux/cpufreq.h>
+#include <linux/cpuhotplug.h>
#include <linux/dma-mapping.h>
#include <linux/module.h>
#include <linux/of.h>
@@ -13,7 +14,7 @@
#include <linux/slab.h>
#include <linux/units.h>
-#include <asm/smp_plat.h>
+#include <asm/cputype.h>
#include <soc/tegra/bpmp.h>
#include <soc/tegra/bpmp-abi.h>
@@ -77,6 +78,8 @@ struct tegra194_cpufreq_data {
const struct tegra_cpufreq_soc *soc;
bool icc_dram_bw_scaling;
struct tegra_cpu_data *cpu_data;
+ enum cpuhp_state cpuhp_state;
+ struct hlist_node cpuhp_node;
};
static struct workqueue_struct *read_counters_wq;
@@ -113,7 +116,7 @@ static void tegra234_get_cpu_cluster_id(u32 cpu, u32 *cpuid, u32 *clusterid)
{
u64 mpidr;
- smp_call_function_single(cpu, tegra_get_cpu_mpidr, &mpidr, true);
+ tegra_get_cpu_mpidr(&mpidr);
if (cpuid)
*cpuid = MPIDR_AFFINITY_LEVEL(mpidr, 1);
@@ -121,11 +124,44 @@ static void tegra234_get_cpu_cluster_id(u32 cpu, u32 *cpuid, u32 *clusterid)
*clusterid = MPIDR_AFFINITY_LEVEL(mpidr, 2);
}
+static int tegra_cpufreq_cpu_online(unsigned int cpu, struct hlist_node *node)
+{
+ struct tegra194_cpufreq_data *data;
+ u32 cpuid, clusterid;
+
+ data = hlist_entry(node, struct tegra194_cpufreq_data, cpuhp_node);
+
+ data->soc->ops->get_cpu_cluster_id(cpu, &cpuid, &clusterid);
+
+ data->cpu_data[cpu].cpuid = cpuid;
+ data->cpu_data[cpu].clusterid = clusterid;
+
+ /* Derive per-core register address only when ccplex MMIO is mapped. */
+ if (data->regs) {
+ if (clusterid >= data->soc->num_clusters ||
+ cpuid >= data->soc->maxcpus_per_cluster) {
+ pr_warn_once("cpufreq: cpu%u: bad MPIDR (cluster %u, core %u)\n",
+ cpu, clusterid, cpuid);
+ } else {
+ u32 index = clusterid * data->soc->maxcpus_per_cluster + cpuid;
+
+ data->cpu_data[cpu].freq_core_reg =
+ SCRATCH_FREQ_CORE_REG(data, index);
+ }
+ }
+
+ return 0;
+}
+
static int tegra234_get_cpu_ndiv(u32 cpu, u32 cpuid, u32 clusterid, u64 *ndiv)
{
struct tegra194_cpufreq_data *data = cpufreq_get_driver_data();
+ void __iomem *reg = data->cpu_data[cpu].freq_core_reg;
+
+ if (!reg)
+ return -EINVAL;
- *ndiv = readl(data->cpu_data[cpu].freq_core_reg) & NDIV_MASK;
+ *ndiv = readl(reg) & NDIV_MASK;
return 0;
}
@@ -135,8 +171,12 @@ static void tegra234_set_cpu_ndiv(struct cpufreq_policy *policy, u64 ndiv)
struct tegra194_cpufreq_data *data = cpufreq_get_driver_data();
u32 cpu;
- for_each_cpu(cpu, policy->cpus)
- writel(ndiv, data->cpu_data[cpu].freq_core_reg);
+ for_each_cpu(cpu, policy->cpus) {
+ void __iomem *reg = data->cpu_data[cpu].freq_core_reg;
+
+ if (reg)
+ writel(ndiv, reg);
+ }
}
/*
@@ -208,7 +248,7 @@ static void tegra194_get_cpu_cluster_id(u32 cpu, u32 *cpuid, u32 *clusterid)
{
u64 mpidr;
- smp_call_function_single(cpu, tegra_get_cpu_mpidr, &mpidr, true);
+ tegra_get_cpu_mpidr(&mpidr);
if (cpuid)
*cpuid = MPIDR_AFFINITY_LEVEL(mpidr, 0);
@@ -682,28 +722,6 @@ tegra_cpufreq_bpmp_read_lut(struct platform_device *pdev, struct tegra_bpmp *bpm
return freq_table;
}
-static int tegra194_cpufreq_store_physids(unsigned int cpu, struct tegra194_cpufreq_data *data)
-{
- int num_cpus = data->soc->maxcpus_per_cluster * data->soc->num_clusters;
- u32 cpuid, clusterid;
- u64 mpidr_id;
-
- if (cpu > (num_cpus - 1)) {
- pr_err("cpufreq: wrong num of cpus or clusters in soc data\n");
- return -EINVAL;
- }
-
- data->soc->ops->get_cpu_cluster_id(cpu, &cpuid, &clusterid);
-
- mpidr_id = (clusterid * data->soc->maxcpus_per_cluster) + cpuid;
-
- data->cpu_data[cpu].cpuid = cpuid;
- data->cpu_data[cpu].clusterid = clusterid;
- data->cpu_data[cpu].freq_core_reg = SCRATCH_FREQ_CORE_REG(data, mpidr_id);
-
- return 0;
-}
-
static int tegra194_cpufreq_probe(struct platform_device *pdev)
{
const struct tegra_cpufreq_soc *soc;
@@ -711,7 +729,6 @@ static int tegra194_cpufreq_probe(struct platform_device *pdev)
struct tegra_bpmp *bpmp;
struct device *cpu_dev;
int err, i;
- u32 cpu;
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
if (!data)
@@ -738,6 +755,12 @@ static int tegra194_cpufreq_probe(struct platform_device *pdev)
return PTR_ERR(data->regs);
}
+ if (nr_cpu_ids > data->soc->num_clusters *
+ data->soc->maxcpus_per_cluster) {
+ dev_err(&pdev->dev, "wrong num of cpus or clusters in soc data\n");
+ return -EINVAL;
+ }
+
data->cpu_data = devm_kcalloc(&pdev->dev, data->soc->num_clusters *
data->soc->maxcpus_per_cluster,
sizeof(*data->cpu_data), GFP_KERNEL);
@@ -766,11 +789,24 @@ static int tegra194_cpufreq_probe(struct platform_device *pdev)
}
}
- for_each_possible_cpu(cpu) {
- err = tegra194_cpufreq_store_physids(cpu, data);
- if (err)
- goto err_free_res;
- }
+ /*
+ * Install the hotplug state before cpufreq_register_driver(), so that
+ * cpu_data is filled in before cpufreq uses a CPU. This covers the
+ * CPUs online now and those onlined later.
+ *
+ * cpufreq_get_driver_data() returns NULL until the driver is
+ * registered. The _multi variant passes the callback its data
+ * through cpuhp_node instead.
+ */
+ err = cpuhp_setup_state_multi(CPUHP_AP_ONLINE_DYN, "cpufreq/tegra194:online",
+ tegra_cpufreq_cpu_online, NULL);
+ if (err < 0)
+ goto err_free_res;
+ data->cpuhp_state = err;
+
+ err = cpuhp_state_add_instance(data->cpuhp_state, &data->cpuhp_node);
+ if (err)
+ goto remove_cpuhp_state;
tegra194_cpufreq_driver.driver_data = data;
@@ -778,7 +814,7 @@ static int tegra194_cpufreq_probe(struct platform_device *pdev)
cpu_dev = get_cpu_device(0);
if (!cpu_dev) {
err = -EPROBE_DEFER;
- goto err_free_res;
+ goto remove_cpuhp_instance;
}
if (dev_pm_opp_of_get_opp_desc_node(cpu_dev)) {
@@ -791,6 +827,10 @@ static int tegra194_cpufreq_probe(struct platform_device *pdev)
if (!err)
goto put_bpmp;
+remove_cpuhp_instance:
+ cpuhp_state_remove_instance(data->cpuhp_state, &data->cpuhp_node);
+remove_cpuhp_state:
+ cpuhp_remove_multi_state(data->cpuhp_state);
err_free_res:
tegra194_cpufreq_free_resources();
put_bpmp:
@@ -800,7 +840,11 @@ static int tegra194_cpufreq_probe(struct platform_device *pdev)
static void tegra194_cpufreq_remove(struct platform_device *pdev)
{
+ struct tegra194_cpufreq_data *data = platform_get_drvdata(pdev);
+
cpufreq_unregister_driver(&tegra194_cpufreq_driver);
+ cpuhp_state_remove_instance(data->cpuhp_state, &data->cpuhp_node);
+ cpuhp_remove_multi_state(data->cpuhp_state);
tegra194_cpufreq_free_resources();
}
--
2.34.1
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH 2/9] cpufreq: tegra194: remove duplicate counter sampling loops
2026-11-04 18:15 [PATCH 0/9] cpufreq: tegra194: add Tegra264 support Sumit Gupta
2026-11-04 18:15 ` [PATCH 1/9] cpufreq: tegra194: move MPIDR read to CPU hotplug callback Sumit Gupta
@ 2026-11-04 18:15 ` Sumit Gupta
2026-11-04 18:15 ` [PATCH 3/9] cpufreq: tegra194: add tegra_counter_delta helper Sumit Gupta
` (6 subsequent siblings)
8 siblings, 0 replies; 13+ messages in thread
From: Sumit Gupta @ 2026-11-04 18:15 UTC (permalink / raw)
To: rafael, viresh.kumar, robh, krzk+dt, conor+dt, treding, jonathanh,
linux-pm, linux-tegra, linux-kernel, devicetree
Cc: bbasu, sumitg
Tegra194 and Tegra234 read their counters differently. Tegra194 reads
a system register and Tegra234 reads its ACTMON registers through MMIO.
Each SoC therefore has its own read_counters op. Both ops open code the
same sampling loop.
Move the loop to common code and replace read_counters with a
sample_counters op that takes a single sample. Keeping the read per-SoC
also prepares for adding support for Tegra264, which reads its counters
from the AMU.
No functional change.
Signed-off-by: Sumit Gupta <sumitg@nvidia.com>
---
drivers/cpufreq/tegra194-cpufreq.c | 93 ++++++++++++------------------
1 file changed, 37 insertions(+), 56 deletions(-)
diff --git a/drivers/cpufreq/tegra194-cpufreq.c b/drivers/cpufreq/tegra194-cpufreq.c
index be81ffd23edd..0f350b4db6cd 100644
--- a/drivers/cpufreq/tegra194-cpufreq.c
+++ b/drivers/cpufreq/tegra194-cpufreq.c
@@ -57,8 +57,11 @@ struct read_counters_work {
struct tegra_cpu_ctr c;
};
+struct tegra194_cpufreq_data;
+
struct tegra_cpufreq_ops {
- void (*read_counters)(struct tegra_cpu_ctr *c);
+ void (*sample_counters)(struct tegra194_cpufreq_data *data,
+ struct tegra_cpu_ctr *c);
void (*set_cpu_ndiv)(struct cpufreq_policy *policy, u64 ndiv);
void (*get_cpu_cluster_id)(u32 cpu, u32 *cpuid, u32 *clusterid);
int (*get_cpu_ndiv)(u32 cpu, u32 cpuid, u32 clusterid, u64 *ndiv);
@@ -186,43 +189,22 @@ static void tegra234_set_cpu_ndiv(struct cpufreq_policy *policy, u64 ndiv)
* [63:32] PLLP counter: Counts at fixed frequency (408 MHz)
* [31:0] Core clock counter: Counts on every core clock cycle
*/
-static void tegra234_read_counters(struct tegra_cpu_ctr *c)
+static void tegra234_sample_counters(struct tegra194_cpufreq_data *data,
+ struct tegra_cpu_ctr *c)
{
- struct tegra194_cpufreq_data *data = cpufreq_get_driver_data();
void __iomem *actmon_reg;
- u32 delta_refcnt;
- int cnt = 0;
u64 val;
actmon_reg = CORE_ACTMON_CNTR_REG(data, data->cpu_data[c->cpu].clusterid,
data->cpu_data[c->cpu].cpuid);
val = readq(actmon_reg);
- c->last_refclk_cnt = upper_32_bits(val);
- c->last_coreclk_cnt = lower_32_bits(val);
-
- /*
- * The sampling window is based on the minimum number of reference
- * clock cycles which is known to give a stable value of CPU frequency.
- */
- do {
- val = readq(actmon_reg);
- c->refclk_cnt = upper_32_bits(val);
- c->coreclk_cnt = lower_32_bits(val);
- if (c->refclk_cnt < c->last_refclk_cnt)
- delta_refcnt = c->refclk_cnt + (MAX_CNT - c->last_refclk_cnt);
- else
- delta_refcnt = c->refclk_cnt - c->last_refclk_cnt;
- if (++cnt >= 0xFFFF) {
- pr_warn("cpufreq: problem with refclk on cpu:%d, delta_refcnt:%u, cnt:%d\n",
- c->cpu, delta_refcnt, cnt);
- break;
- }
- } while (delta_refcnt < data->soc->refclk_delta_min);
+ c->refclk_cnt = upper_32_bits(val);
+ c->coreclk_cnt = lower_32_bits(val);
}
static struct tegra_cpufreq_ops tegra234_cpufreq_ops = {
- .read_counters = tegra234_read_counters,
+ .sample_counters = tegra234_sample_counters,
.get_cpu_cluster_id = tegra234_get_cpu_cluster_id,
.get_cpu_ndiv = tegra234_get_cpu_ndiv,
.set_cpu_ndiv = tegra234_set_cpu_ndiv,
@@ -280,35 +262,13 @@ static inline u32 map_ndiv_to_freq(struct mrq_cpu_ndiv_limits_response
return nltbl->ref_clk_hz / KHZ * ndiv / (nltbl->pdiv * nltbl->mdiv);
}
-static void tegra194_read_counters(struct tegra_cpu_ctr *c)
+static void tegra194_sample_counters(struct tegra194_cpufreq_data *data,
+ struct tegra_cpu_ctr *c)
{
- struct tegra194_cpufreq_data *data = cpufreq_get_driver_data();
- u32 delta_refcnt;
- int cnt = 0;
- u64 val;
+ u64 val = read_freq_feedback();
- val = read_freq_feedback();
- c->last_refclk_cnt = lower_32_bits(val);
- c->last_coreclk_cnt = upper_32_bits(val);
-
- /*
- * The sampling window is based on the minimum number of reference
- * clock cycles which is known to give a stable value of CPU frequency.
- */
- do {
- val = read_freq_feedback();
- c->refclk_cnt = lower_32_bits(val);
- c->coreclk_cnt = upper_32_bits(val);
- if (c->refclk_cnt < c->last_refclk_cnt)
- delta_refcnt = c->refclk_cnt + (MAX_CNT - c->last_refclk_cnt);
- else
- delta_refcnt = c->refclk_cnt - c->last_refclk_cnt;
- if (++cnt >= 0xFFFF) {
- pr_warn("cpufreq: problem with refclk on cpu:%d, delta_refcnt:%u, cnt:%d\n",
- c->cpu, delta_refcnt, cnt);
- break;
- }
- } while (delta_refcnt < data->soc->refclk_delta_min);
+ c->refclk_cnt = lower_32_bits(val);
+ c->coreclk_cnt = upper_32_bits(val);
}
static void tegra_read_counters(struct work_struct *work)
@@ -316,6 +276,8 @@ static void tegra_read_counters(struct work_struct *work)
struct tegra194_cpufreq_data *data = cpufreq_get_driver_data();
struct read_counters_work *read_counters_work;
struct tegra_cpu_ctr *c;
+ u32 delta_refcnt;
+ int cnt = 0;
/*
* ref_clk_counter(32 bit counter) runs on constant clk,
@@ -333,7 +295,26 @@ static void tegra_read_counters(struct work_struct *work)
work);
c = &read_counters_work->c;
- data->soc->ops->read_counters(c);
+ data->soc->ops->sample_counters(data, c);
+ c->last_refclk_cnt = c->refclk_cnt;
+ c->last_coreclk_cnt = c->coreclk_cnt;
+
+ /*
+ * The sampling window is based on the minimum number of reference
+ * clock cycles which is known to give a stable value of CPU frequency.
+ */
+ do {
+ data->soc->ops->sample_counters(data, c);
+ if (c->refclk_cnt < c->last_refclk_cnt)
+ delta_refcnt = c->refclk_cnt + (MAX_CNT - c->last_refclk_cnt);
+ else
+ delta_refcnt = c->refclk_cnt - c->last_refclk_cnt;
+ if (++cnt >= 0xFFFF) {
+ pr_warn("cpufreq: problem with refclk on cpu:%d, delta_refcnt:%u, cnt:%d\n",
+ c->cpu, delta_refcnt, cnt);
+ break;
+ }
+ } while (delta_refcnt < data->soc->refclk_delta_min);
}
/*
@@ -632,7 +613,7 @@ static struct cpufreq_driver tegra194_cpufreq_driver = {
};
static struct tegra_cpufreq_ops tegra194_cpufreq_ops = {
- .read_counters = tegra194_read_counters,
+ .sample_counters = tegra194_sample_counters,
.get_cpu_cluster_id = tegra194_get_cpu_cluster_id,
.get_cpu_ndiv = tegra194_get_cpu_ndiv,
.set_cpu_ndiv = tegra194_set_cpu_ndiv,
--
2.34.1
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH 3/9] cpufreq: tegra194: add tegra_counter_delta helper
2026-11-04 18:15 [PATCH 0/9] cpufreq: tegra194: add Tegra264 support Sumit Gupta
2026-11-04 18:15 ` [PATCH 1/9] cpufreq: tegra194: move MPIDR read to CPU hotplug callback Sumit Gupta
2026-11-04 18:15 ` [PATCH 2/9] cpufreq: tegra194: remove duplicate counter sampling loops Sumit Gupta
@ 2026-11-04 18:15 ` Sumit Gupta
2026-11-04 18:15 ` [PATCH 4/9] cpufreq: tegra194: add per-SoC counter mask Sumit Gupta
` (5 subsequent siblings)
8 siblings, 0 replies; 13+ messages in thread
From: Sumit Gupta @ 2026-11-04 18:15 UTC (permalink / raw)
To: rafael, viresh.kumar, robh, krzk+dt, conor+dt, treding, jonathanh,
linux-pm, linux-tegra, linux-kernel, devicetree
Cc: bbasu, sumitg
Frequency readback open-codes the same counter wrap-around subtraction in
three places. Move it into a single helper.
The helper works on u64 values, so the frequency multiply now runs in
64 bits. The old (unsigned long) cast applied to the result of the 32-bit
multiply, not to its operands, so it had no effect. Drop it.
Signed-off-by: Sumit Gupta <sumitg@nvidia.com>
---
drivers/cpufreq/tegra194-cpufreq.c | 37 +++++++++++++++---------------
1 file changed, 18 insertions(+), 19 deletions(-)
diff --git a/drivers/cpufreq/tegra194-cpufreq.c b/drivers/cpufreq/tegra194-cpufreq.c
index 0f350b4db6cd..afc10c0b874e 100644
--- a/drivers/cpufreq/tegra194-cpufreq.c
+++ b/drivers/cpufreq/tegra194-cpufreq.c
@@ -110,6 +110,14 @@ static int tegra_cpufreq_set_bw(struct cpufreq_policy *policy, unsigned long fre
return ret;
}
+static u64 tegra_counter_delta(u64 after, u64 before, u64 mask)
+{
+ if (after < before)
+ return after + (mask - before);
+
+ return after - before;
+}
+
static void tegra_get_cpu_mpidr(void *mpidr)
{
*((u64 *)mpidr) = read_cpuid_mpidr() & MPIDR_HWID_BITMASK;
@@ -276,7 +284,7 @@ static void tegra_read_counters(struct work_struct *work)
struct tegra194_cpufreq_data *data = cpufreq_get_driver_data();
struct read_counters_work *read_counters_work;
struct tegra_cpu_ctr *c;
- u32 delta_refcnt;
+ u64 delta_refcnt;
int cnt = 0;
/*
@@ -305,12 +313,10 @@ static void tegra_read_counters(struct work_struct *work)
*/
do {
data->soc->ops->sample_counters(data, c);
- if (c->refclk_cnt < c->last_refclk_cnt)
- delta_refcnt = c->refclk_cnt + (MAX_CNT - c->last_refclk_cnt);
- else
- delta_refcnt = c->refclk_cnt - c->last_refclk_cnt;
+ delta_refcnt = tegra_counter_delta(c->refclk_cnt, c->last_refclk_cnt,
+ MAX_CNT);
if (++cnt >= 0xFFFF) {
- pr_warn("cpufreq: problem with refclk on cpu:%d, delta_refcnt:%u, cnt:%d\n",
+ pr_warn("cpufreq: problem with refclk on cpu:%d, delta_refcnt:%llu, cnt:%d\n",
c->cpu, delta_refcnt, cnt);
break;
}
@@ -342,9 +348,9 @@ static unsigned int tegra194_calculate_speed(u32 cpu)
{
struct read_counters_work read_counters_work;
struct tegra_cpu_ctr c;
- u32 delta_refcnt;
- u32 delta_ccnt;
- u32 rate_mhz;
+ u64 delta_refcnt;
+ u64 delta_ccnt;
+ u64 rate_mhz;
/*
* Reconstruct cpu frequency over an observation/sampling window.
@@ -356,23 +362,16 @@ static unsigned int tegra194_calculate_speed(u32 cpu)
flush_work(&read_counters_work.work);
c = read_counters_work.c;
- if (c.coreclk_cnt < c.last_coreclk_cnt)
- delta_ccnt = c.coreclk_cnt + (MAX_CNT - c.last_coreclk_cnt);
- else
- delta_ccnt = c.coreclk_cnt - c.last_coreclk_cnt;
+ delta_ccnt = tegra_counter_delta(c.coreclk_cnt, c.last_coreclk_cnt, MAX_CNT);
if (!delta_ccnt)
return 0;
- /* ref clock is 32 bits */
- if (c.refclk_cnt < c.last_refclk_cnt)
- delta_refcnt = c.refclk_cnt + (MAX_CNT - c.last_refclk_cnt);
- else
- delta_refcnt = c.refclk_cnt - c.last_refclk_cnt;
+ delta_refcnt = tegra_counter_delta(c.refclk_cnt, c.last_refclk_cnt, MAX_CNT);
if (!delta_refcnt) {
pr_debug("cpufreq: %d is idle, delta_refcnt: 0\n", cpu);
return 0;
}
- rate_mhz = ((unsigned long)(delta_ccnt * REF_CLK_MHZ)) / delta_refcnt;
+ rate_mhz = delta_ccnt * REF_CLK_MHZ / delta_refcnt;
return (rate_mhz * KHZ); /* in KHz */
}
--
2.34.1
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH 4/9] cpufreq: tegra194: add per-SoC counter mask
2026-11-04 18:15 [PATCH 0/9] cpufreq: tegra194: add Tegra264 support Sumit Gupta
` (2 preceding siblings ...)
2026-11-04 18:15 ` [PATCH 3/9] cpufreq: tegra194: add tegra_counter_delta helper Sumit Gupta
@ 2026-11-04 18:15 ` Sumit Gupta
2026-11-04 18:15 ` [PATCH 5/9] cpufreq: tegra194: split MMIO mapping from ACTMON offset Sumit Gupta
` (4 subsequent siblings)
8 siblings, 0 replies; 13+ messages in thread
From: Sumit Gupta @ 2026-11-04 18:15 UTC (permalink / raw)
To: rafael, viresh.kumar, robh, krzk+dt, conor+dt, treding, jonathanh,
linux-pm, linux-tegra, linux-kernel, devicetree
Cc: bbasu, sumitg
Frequency readback handles counter wrap-around with MAX_CNT, which is
hardcoded to 32 bits. Move the counter's maximum value into the SoC
data, in preparation for adding support for Tegra264, whose AMU counters
are 64-bit.
Add cnt_mask to tegra_cpufreq_soc and set it to U32_MAX on
Tegra194/234/238, which matches MAX_CNT. Also widen tegra_cpu_ctr to
u64, so it can hold counters of either width.
No functional change.
Signed-off-by: Sumit Gupta <sumitg@nvidia.com>
---
drivers/cpufreq/tegra194-cpufreq.c | 20 +++++++++++++-------
1 file changed, 13 insertions(+), 7 deletions(-)
diff --git a/drivers/cpufreq/tegra194-cpufreq.c b/drivers/cpufreq/tegra194-cpufreq.c
index afc10c0b874e..a3aaebde41cc 100644
--- a/drivers/cpufreq/tegra194-cpufreq.c
+++ b/drivers/cpufreq/tegra194-cpufreq.c
@@ -22,7 +22,6 @@
#define KHZ 1000
#define REF_CLK_MHZ 408 /* 408 MHz */
#define CPUFREQ_TBL_STEP_HZ (50 * KHZ * KHZ)
-#define MAX_CNT ~0U
#define MAX_DELTA_KHZ 115200
@@ -48,8 +47,8 @@ struct tegra_cpu_data {
struct tegra_cpu_ctr {
u32 cpu;
- u32 coreclk_cnt, last_coreclk_cnt;
- u32 refclk_cnt, last_refclk_cnt;
+ u64 coreclk_cnt, last_coreclk_cnt;
+ u64 refclk_cnt, last_refclk_cnt;
};
struct read_counters_work {
@@ -69,6 +68,7 @@ struct tegra_cpufreq_ops {
struct tegra_cpufreq_soc {
struct tegra_cpufreq_ops *ops;
+ u64 cnt_mask;
int maxcpus_per_cluster;
unsigned int num_clusters;
phys_addr_t actmon_cntr_base;
@@ -220,6 +220,7 @@ static struct tegra_cpufreq_ops tegra234_cpufreq_ops = {
static const struct tegra_cpufreq_soc tegra234_cpufreq_soc = {
.ops = &tegra234_cpufreq_ops,
+ .cnt_mask = U32_MAX,
.actmon_cntr_base = 0x9000,
.maxcpus_per_cluster = 4,
.num_clusters = 3,
@@ -228,6 +229,7 @@ static const struct tegra_cpufreq_soc tegra234_cpufreq_soc = {
static const struct tegra_cpufreq_soc tegra238_cpufreq_soc = {
.ops = &tegra234_cpufreq_ops,
+ .cnt_mask = U32_MAX,
.actmon_cntr_base = 0x4000,
.maxcpus_per_cluster = 8,
.num_clusters = 1,
@@ -314,7 +316,7 @@ static void tegra_read_counters(struct work_struct *work)
do {
data->soc->ops->sample_counters(data, c);
delta_refcnt = tegra_counter_delta(c->refclk_cnt, c->last_refclk_cnt,
- MAX_CNT);
+ data->soc->cnt_mask);
if (++cnt >= 0xFFFF) {
pr_warn("cpufreq: problem with refclk on cpu:%d, delta_refcnt:%llu, cnt:%d\n",
c->cpu, delta_refcnt, cnt);
@@ -346,8 +348,10 @@ static void tegra_read_counters(struct work_struct *work)
*/
static unsigned int tegra194_calculate_speed(u32 cpu)
{
+ struct tegra194_cpufreq_data *data = cpufreq_get_driver_data();
struct read_counters_work read_counters_work;
struct tegra_cpu_ctr c;
+ u64 cnt_mask = data->soc->cnt_mask;
u64 delta_refcnt;
u64 delta_ccnt;
u64 rate_mhz;
@@ -362,11 +366,11 @@ static unsigned int tegra194_calculate_speed(u32 cpu)
flush_work(&read_counters_work.work);
c = read_counters_work.c;
- delta_ccnt = tegra_counter_delta(c.coreclk_cnt, c.last_coreclk_cnt, MAX_CNT);
+ delta_ccnt = tegra_counter_delta(c.coreclk_cnt, c.last_coreclk_cnt, cnt_mask);
if (!delta_ccnt)
return 0;
- delta_refcnt = tegra_counter_delta(c.refclk_cnt, c.last_refclk_cnt, MAX_CNT);
+ delta_refcnt = tegra_counter_delta(c.refclk_cnt, c.last_refclk_cnt, cnt_mask);
if (!delta_refcnt) {
pr_debug("cpufreq: %d is idle, delta_refcnt: 0\n", cpu);
return 0;
@@ -620,6 +624,7 @@ static struct tegra_cpufreq_ops tegra194_cpufreq_ops = {
static const struct tegra_cpufreq_soc tegra194_cpufreq_soc = {
.ops = &tegra194_cpufreq_ops,
+ .cnt_mask = U32_MAX,
.maxcpus_per_cluster = 2,
.num_clusters = 4,
.refclk_delta_min = 16000,
@@ -716,7 +721,8 @@ static int tegra194_cpufreq_probe(struct platform_device *pdev)
soc = of_device_get_match_data(&pdev->dev);
- if (soc->ops && soc->maxcpus_per_cluster && soc->num_clusters && soc->refclk_delta_min) {
+ if (soc->ops && soc->maxcpus_per_cluster && soc->num_clusters &&
+ soc->refclk_delta_min && soc->cnt_mask) {
data->soc = soc;
} else {
dev_err(&pdev->dev, "soc data missing\n");
--
2.34.1
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH 5/9] cpufreq: tegra194: split MMIO mapping from ACTMON offset
2026-11-04 18:15 [PATCH 0/9] cpufreq: tegra194: add Tegra264 support Sumit Gupta
` (3 preceding siblings ...)
2026-11-04 18:15 ` [PATCH 4/9] cpufreq: tegra194: add per-SoC counter mask Sumit Gupta
@ 2026-11-04 18:15 ` Sumit Gupta
2026-11-04 18:15 ` [PATCH 6/9] dt-bindings: arm: nvidia: Document the Tegra264 CCPLEX cluster Sumit Gupta
` (3 subsequent siblings)
8 siblings, 0 replies; 13+ messages in thread
From: Sumit Gupta @ 2026-11-04 18:15 UTC (permalink / raw)
To: rafael, viresh.kumar, robh, krzk+dt, conor+dt, treding, jonathanh,
linux-pm, linux-tegra, linux-kernel, devicetree
Cc: bbasu, sumitg
Probe maps the ccplex-cluster registers only when actmon_cntr_base is
set. This has worked so far because each SoC uses one interface for
both requesting and reading frequency. Tegra234/238 use MMIO, and
Tegra194 uses system registers.
Tegra264 requests frequency through MMIO but reads its counters from
system registers. It needs the mapping but has no ACTMON counters.
Add a needs_mmio flag so the mapping no longer depends on
actmon_cntr_base.
No functional change.
Signed-off-by: Sumit Gupta <sumitg@nvidia.com>
---
drivers/cpufreq/tegra194-cpufreq.c | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/drivers/cpufreq/tegra194-cpufreq.c b/drivers/cpufreq/tegra194-cpufreq.c
index a3aaebde41cc..047166463efe 100644
--- a/drivers/cpufreq/tegra194-cpufreq.c
+++ b/drivers/cpufreq/tegra194-cpufreq.c
@@ -71,6 +71,7 @@ struct tegra_cpufreq_soc {
u64 cnt_mask;
int maxcpus_per_cluster;
unsigned int num_clusters;
+ bool needs_mmio;
phys_addr_t actmon_cntr_base;
u32 refclk_delta_min;
};
@@ -221,6 +222,7 @@ static struct tegra_cpufreq_ops tegra234_cpufreq_ops = {
static const struct tegra_cpufreq_soc tegra234_cpufreq_soc = {
.ops = &tegra234_cpufreq_ops,
.cnt_mask = U32_MAX,
+ .needs_mmio = true,
.actmon_cntr_base = 0x9000,
.maxcpus_per_cluster = 4,
.num_clusters = 3,
@@ -230,6 +232,7 @@ static const struct tegra_cpufreq_soc tegra234_cpufreq_soc = {
static const struct tegra_cpufreq_soc tegra238_cpufreq_soc = {
.ops = &tegra234_cpufreq_ops,
.cnt_mask = U32_MAX,
+ .needs_mmio = true,
.actmon_cntr_base = 0x4000,
.maxcpus_per_cluster = 8,
.num_clusters = 1,
@@ -734,8 +737,7 @@ static int tegra194_cpufreq_probe(struct platform_device *pdev)
if (!data->bpmp_luts)
return -ENOMEM;
- if (soc->actmon_cntr_base) {
- /* mmio registers are used for frequency request and re-construction */
+ if (soc->needs_mmio) {
data->regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(data->regs))
return PTR_ERR(data->regs);
--
2.34.1
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH 6/9] dt-bindings: arm: nvidia: Document the Tegra264 CCPLEX cluster
2026-11-04 18:15 [PATCH 0/9] cpufreq: tegra194: add Tegra264 support Sumit Gupta
` (4 preceding siblings ...)
2026-11-04 18:15 ` [PATCH 5/9] cpufreq: tegra194: split MMIO mapping from ACTMON offset Sumit Gupta
@ 2026-11-04 18:15 ` Sumit Gupta
2026-11-04 18:15 ` [PATCH 7/9] cpufreq: tegra194: derive frequency domain from device tree Sumit Gupta
` (2 subsequent siblings)
8 siblings, 0 replies; 13+ messages in thread
From: Sumit Gupta @ 2026-11-04 18:15 UTC (permalink / raw)
To: rafael, viresh.kumar, robh, krzk+dt, conor+dt, treding, jonathanh,
linux-pm, linux-tegra, linux-kernel, devicetree
Cc: bbasu, sumitg
Add a compatible string for the CCPLEX cluster on Tegra264.
On Tegra264, two CPU clusters share one clock. Add the generic
"#performance-domain-cells" provider property so a CPU node can name
its domain with "performance-domains = <&ccplex N>", where N is the
index of the domain that clocks the CPU. The consumer side is already
described in bindings/arm/cpus.yaml.
The property is required on Tegra264, where a domain spans two clusters.
Signed-off-by: Sumit Gupta <sumitg@nvidia.com>
---
.../tegra/nvidia,tegra-ccplex-cluster.yaml | 27 ++++++++++++++++++-
1 file changed, 26 insertions(+), 1 deletion(-)
diff --git a/Documentation/devicetree/bindings/arm/tegra/nvidia,tegra-ccplex-cluster.yaml b/Documentation/devicetree/bindings/arm/tegra/nvidia,tegra-ccplex-cluster.yaml
index fe9c8791f227..2bdb512fb677 100644
--- a/Documentation/devicetree/bindings/arm/tegra/nvidia,tegra-ccplex-cluster.yaml
+++ b/Documentation/devicetree/bindings/arm/tegra/nvidia,tegra-ccplex-cluster.yaml
@@ -25,6 +25,7 @@ properties:
- nvidia,tegra186-ccplex-cluster
- nvidia,tegra234-ccplex-cluster
- nvidia,tegra238-ccplex-cluster
+ - nvidia,tegra264-ccplex-cluster
reg:
maxItems: 1
@@ -35,13 +36,29 @@ properties:
Specifies the BPMP node that needs to be queried to get
operating point data for all CPUs.
-additionalProperties: false
+ '#performance-domain-cells':
+ const: 1
+ description:
+ The cell is the index of the performance domain that clocks a CPU.
+ When present, every CPU names its domain with "performance-domains".
required:
- compatible
- reg
- nvidia,bpmp
+allOf:
+ - if:
+ properties:
+ compatible:
+ contains:
+ const: nvidia,tegra264-ccplex-cluster
+ then:
+ required:
+ - '#performance-domain-cells'
+
+additionalProperties: false
+
examples:
- |
ccplex@e000000 {
@@ -49,3 +66,11 @@ examples:
reg = <0x0e000000 0x5ffff>;
nvidia,bpmp = <&bpmp>;
};
+
+ - |
+ ccplex@30000000 {
+ compatible = "nvidia,tegra264-ccplex-cluster";
+ reg = <0x30000000 0xcf0000>;
+ #performance-domain-cells = <1>;
+ nvidia,bpmp = <&bpmp>;
+ };
--
2.34.1
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH 7/9] cpufreq: tegra194: derive frequency domain from device tree
2026-11-04 18:15 [PATCH 0/9] cpufreq: tegra194: add Tegra264 support Sumit Gupta
` (5 preceding siblings ...)
2026-11-04 18:15 ` [PATCH 6/9] dt-bindings: arm: nvidia: Document the Tegra264 CCPLEX cluster Sumit Gupta
@ 2026-11-04 18:15 ` Sumit Gupta
2026-11-04 18:36 ` sashiko-bot
2026-11-04 18:15 ` [PATCH 8/9] cpufreq: tegra194: Add support for Tegra264 Sumit Gupta
2026-11-04 18:15 ` [PATCH 9/9] arm64: tegra: Add CCPLEX cluster node " Sumit Gupta
8 siblings, 1 reply; 13+ messages in thread
From: Sumit Gupta @ 2026-11-04 18:15 UTC (permalink / raw)
To: rafael, viresh.kumar, robh, krzk+dt, conor+dt, treding, jonathanh,
linux-pm, linux-tegra, linux-kernel, devicetree
Cc: bbasu, sumitg
The driver currently creates one policy per cluster, so a frequency
domain that spans clusters cannot form a single policy.
Group CPUs by the generic "performance-domains" property instead.
of_perf_domain_get_sharing_cpumask() adds to the policy each CPU that
names the same domain, including offline ones. Without the property,
CPUs keep the cluster grouping, so current Tegra194/234/238 device
trees are unaffected.
A policy can now span clusters. It still uses one cluster's BPMP
frequency table. Clusters in a domain share a clock and report the same
NDIV limits, so that table fits the whole domain.
Store the domain index as clkid for every CPU in the policy. It can
then be used to select the domain's frequency register, whichever CPU
manages the policy.
Signed-off-by: Sumit Gupta <sumitg@nvidia.com>
---
drivers/cpufreq/tegra194-cpufreq.c | 41 ++++++++++++++++++++++++------
1 file changed, 33 insertions(+), 8 deletions(-)
diff --git a/drivers/cpufreq/tegra194-cpufreq.c b/drivers/cpufreq/tegra194-cpufreq.c
index 047166463efe..1328aa0a1bd1 100644
--- a/drivers/cpufreq/tegra194-cpufreq.c
+++ b/drivers/cpufreq/tegra194-cpufreq.c
@@ -42,6 +42,7 @@
struct tegra_cpu_data {
u32 cpuid;
u32 clusterid;
+ u32 clkid; /* performance-domain id */
void __iomem *freq_core_reg;
};
@@ -528,21 +529,45 @@ static int tegra194_cpufreq_init(struct cpufreq_policy *policy)
{
struct tegra194_cpufreq_data *data = cpufreq_get_driver_data();
int maxcpus_per_cluster = data->soc->maxcpus_per_cluster;
- u32 clusterid = data->cpu_data[policy->cpu].clusterid;
struct cpufreq_frequency_table *freq_table;
struct cpufreq_frequency_table *bpmp_lut;
- u32 start_cpu, cpu;
+ struct of_phandle_args args;
+ u32 start_cpu, cpu, clusterid;
int ret;
+ /* Covers all possible CPUs, so offline siblings are grouped too. */
+ ret = of_perf_domain_get_sharing_cpumask(policy->cpu, "performance-domains",
+ "#performance-domain-cells",
+ policy->cpus, &args);
+ if (!ret) {
+ if (args.args_count != 1) {
+ pr_err("cpufreq: cpu%u: invalid #performance-domain-cells\n",
+ policy->cpu);
+ of_node_put(args.np);
+ return -EINVAL;
+ }
+
+ for_each_cpu(cpu, policy->cpus)
+ data->cpu_data[cpu].clkid = args.args[0];
+
+ of_node_put(args.np);
+ } else if (ret == -ENOENT) {
+ /* Legacy grouping for DTs without performance-domains. */
+ start_cpu = rounddown(policy->cpu, maxcpus_per_cluster);
+ for (cpu = start_cpu; cpu < (start_cpu + maxcpus_per_cluster); cpu++)
+ if (cpu_possible(cpu))
+ cpumask_set_cpu(cpu, policy->cpus);
+ } else {
+ pr_err("cpufreq: cpu%u: failed to get performance domain: %d\n",
+ policy->cpu, ret);
+ return ret;
+ }
+
+ /* The BPMP LUT is indexed by the MPIDR cluster id, not clkid. */
+ clusterid = data->cpu_data[policy->cpu].clusterid;
if (clusterid >= data->soc->num_clusters || !data->bpmp_luts[clusterid])
return -EINVAL;
- start_cpu = rounddown(policy->cpu, maxcpus_per_cluster);
- /* set same policy for all cpus in a cluster */
- for (cpu = start_cpu; cpu < (start_cpu + maxcpus_per_cluster); cpu++) {
- if (cpu_possible(cpu))
- cpumask_set_cpu(cpu, policy->cpus);
- }
policy->cpuinfo.transition_latency = TEGRA_CPUFREQ_TRANSITION_LATENCY;
bpmp_lut = data->bpmp_luts[clusterid];
--
2.34.1
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH 8/9] cpufreq: tegra194: Add support for Tegra264
2026-11-04 18:15 [PATCH 0/9] cpufreq: tegra194: add Tegra264 support Sumit Gupta
` (6 preceding siblings ...)
2026-11-04 18:15 ` [PATCH 7/9] cpufreq: tegra194: derive frequency domain from device tree Sumit Gupta
@ 2026-11-04 18:15 ` Sumit Gupta
2026-11-04 18:15 ` [PATCH 9/9] arm64: tegra: Add CCPLEX cluster node " Sumit Gupta
8 siblings, 0 replies; 13+ messages in thread
From: Sumit Gupta @ 2026-11-04 18:15 UTC (permalink / raw)
To: rafael, viresh.kumar, robh, krzk+dt, conor+dt, treding, jonathanh,
linux-pm, linux-tegra, linux-kernel, devicetree
Cc: bbasu, sumitg
Add cpufreq support for Tegra264. It requests and reads the CPU
frequency differently from earlier SoCs.
To request a frequency, the driver writes NDIV to a core_freq_req
register in the CCPLEX register space. Two clusters share a
cluster-clock unit and its register, so they always run at the same
frequency. The driver exposes each pair as one performance domain and
one cpufreq policy. Each CPU's "performance-domains" property gives the
domain index, which selects the register. If the CCPLEX node declares
"#performance-domain-cells", probe fails if any CPU lacks the property.
To read the frequency, the driver samples the per-CPU 64-bit AMU
counters through system registers. The AMU constant counter is the
reference. It ticks at the system counter rate, so the driver takes the
reference rate from CNTFRQ instead of the fixed 408 MHz clock.
Signed-off-by: Sumit Gupta <sumitg@nvidia.com>
---
drivers/cpufreq/Kconfig.arm | 4 +-
drivers/cpufreq/tegra194-cpufreq.c | 147 ++++++++++++++++++++++++++++-
2 files changed, 144 insertions(+), 7 deletions(-)
diff --git a/drivers/cpufreq/Kconfig.arm b/drivers/cpufreq/Kconfig.arm
index a441668f9e0c..a5482b12be57 100644
--- a/drivers/cpufreq/Kconfig.arm
+++ b/drivers/cpufreq/Kconfig.arm
@@ -248,9 +248,9 @@ config ARM_TEGRA186_CPUFREQ
config ARM_TEGRA194_CPUFREQ
tristate "Tegra194 CPUFreq support"
- depends on ARCH_TEGRA_194_SOC || ARCH_TEGRA_234_SOC
+ depends on ARCH_TEGRA_194_SOC || ARCH_TEGRA_234_SOC || ARCH_TEGRA_264_SOC
depends on TEGRA_BPMP
- default ARCH_TEGRA_194_SOC || ARCH_TEGRA_234_SOC
+ default ARCH_TEGRA_194_SOC || ARCH_TEGRA_234_SOC || ARCH_TEGRA_264_SOC
help
This adds CPU frequency driver support for Tegra194 SOCs.
diff --git a/drivers/cpufreq/tegra194-cpufreq.c b/drivers/cpufreq/tegra194-cpufreq.c
index 1328aa0a1bd1..0767a6fd6bdc 100644
--- a/drivers/cpufreq/tegra194-cpufreq.c
+++ b/drivers/cpufreq/tegra194-cpufreq.c
@@ -14,7 +14,9 @@
#include <linux/slab.h>
#include <linux/units.h>
+#include <asm/arch_timer.h>
#include <asm/cputype.h>
+#include <asm/sysreg.h>
#include <soc/tegra/bpmp.h>
#include <soc/tegra/bpmp-abi.h>
@@ -36,6 +38,18 @@
(data->regs + (MMCRAB_CLUSTER_BASE(cl) + data->soc->actmon_cntr_base))
#define CORE_ACTMON_CNTR_REG(data, cl, cpu) (CLUSTER_ACTMON_BASE(data, cl) + CORE_OFFSET(cpu))
+/*
+ * Tegra264 requests a frequency by writing the NDIV to a core_freq_req
+ * register in the CCPLEX register space.
+ *
+ * Two clusters that share a cluster-clock unit form a performance domain.
+ * Each domain has one core_freq_req register. The registers are grouped
+ * in blocks, two domains per block.
+ */
+#define T264_CORE_FREQ_REQ_BLOCK_SIZE 0x400000
+#define T264_CORE_FREQ_REQ_BLOCK_DOMAIN0_OFFSET 0x40000
+#define T264_CORE_FREQ_REQ_BLOCK_DOMAIN1_OFFSET 0x50000
+
/* cpufreq transisition latency */
#define TEGRA_CPUFREQ_TRANSITION_LATENCY (300 * 1000) /* unit in nanoseconds */
@@ -70,6 +84,8 @@ struct tegra_cpufreq_ops {
struct tegra_cpufreq_soc {
struct tegra_cpufreq_ops *ops;
u64 cnt_mask;
+ /* AMU's constant counter ticks at the system counter rate. */
+ bool ref_clk_from_cntfrq;
int maxcpus_per_cluster;
unsigned int num_clusters;
bool needs_mmio;
@@ -79,8 +95,10 @@ struct tegra_cpufreq_soc {
struct tegra194_cpufreq_data {
void __iomem *regs;
+ resource_size_t regs_size;
struct cpufreq_frequency_table **bpmp_luts;
const struct tegra_cpufreq_soc *soc;
+ u32 ref_clk_mhz;
bool icc_dram_bw_scaling;
struct tegra_cpu_data *cpu_data;
enum cpuhp_state cpuhp_state;
@@ -240,6 +258,78 @@ static const struct tegra_cpufreq_soc tegra238_cpufreq_soc = {
.refclk_delta_min = 16000,
};
+static void __iomem *tegra264_core_freq_req_reg(struct tegra194_cpufreq_data *data,
+ u32 clkid)
+{
+ u64 offset = (u64)(clkid / 2) * T264_CORE_FREQ_REQ_BLOCK_SIZE;
+
+ offset += (clkid % 2) ? T264_CORE_FREQ_REQ_BLOCK_DOMAIN1_OFFSET :
+ T264_CORE_FREQ_REQ_BLOCK_DOMAIN0_OFFSET;
+
+ /*
+ * clkid comes from the device tree. Reject an index whose 32-bit
+ * register does not fit in the mapped region.
+ */
+ if (offset + sizeof(u32) > data->regs_size)
+ return NULL;
+
+ return data->regs + offset;
+}
+
+static int tegra264_get_cpu_ndiv(u32 cpu, u32 cpuid, u32 clusterid, u64 *ndiv)
+{
+ struct tegra194_cpufreq_data *data = cpufreq_get_driver_data();
+ u32 clkid = data->cpu_data[cpu].clkid;
+ void __iomem *reg;
+
+ reg = tegra264_core_freq_req_reg(data, clkid);
+ if (!reg)
+ return -EINVAL;
+
+ *ndiv = readl(reg) & NDIV_MASK;
+
+ return 0;
+}
+
+static void tegra264_set_cpu_ndiv(struct cpufreq_policy *policy, u64 ndiv)
+{
+ struct tegra194_cpufreq_data *data = cpufreq_get_driver_data();
+ u32 clkid = data->cpu_data[policy->cpu].clkid;
+ void __iomem *reg;
+
+ reg = tegra264_core_freq_req_reg(data, clkid);
+ if (reg)
+ writel(ndiv, reg);
+}
+
+/*
+ * The AMU counters are per-CPU system registers, so this must run on the CPU
+ * it samples. tegra194_calculate_speed() queues it there with queue_work_on().
+ */
+static void tegra264_sample_counters(struct tegra194_cpufreq_data *data,
+ struct tegra_cpu_ctr *c)
+{
+ c->coreclk_cnt = read_sysreg_s(SYS_AMEVCNTR0_CORE_EL0);
+ c->refclk_cnt = read_sysreg_s(SYS_AMEVCNTR0_CONST_EL0);
+}
+
+static struct tegra_cpufreq_ops tegra264_cpufreq_ops = {
+ .sample_counters = tegra264_sample_counters,
+ .get_cpu_cluster_id = tegra234_get_cpu_cluster_id,
+ .get_cpu_ndiv = tegra264_get_cpu_ndiv,
+ .set_cpu_ndiv = tegra264_set_cpu_ndiv,
+};
+
+static const struct tegra_cpufreq_soc tegra264_cpufreq_soc = {
+ .ops = &tegra264_cpufreq_ops,
+ .cnt_mask = U64_MAX,
+ .ref_clk_from_cntfrq = true,
+ .needs_mmio = true,
+ .maxcpus_per_cluster = 1,
+ .num_clusters = 14,
+ .refclk_delta_min = 16000,
+};
+
static void tegra194_get_cpu_cluster_id(u32 cpu, u32 *cpuid, u32 *clusterid)
{
u64 mpidr;
@@ -341,11 +431,11 @@ static void tegra_read_counters(struct work_struct *work)
* - Return Kcycles/second, freq in KHz
*
* delta time period = x sec
- * = delta ref_clk_counter / (408 * 10^6) sec
+ * = delta ref_clk_counter / (ref_clk_mhz * 10^6) sec
* freq in Hz = cycles/sec
* = (delta cycles / x sec
- * = (delta cycles * 408 * 10^6) / delta ref_clk_counter
- * in KHz = (delta cycles * 408 * 10^3) / delta ref_clk_counter
+ * = (delta cycles * ref_clk_mhz * 10^6) / delta ref_clk_counter
+ * in KHz = (delta cycles * ref_clk_mhz * 10^3) / delta ref_clk_counter
*
* @cpu - logical cpu whose freq to be updated
* Returns freq in KHz on success, 0 if cpu is offline
@@ -379,7 +469,7 @@ static unsigned int tegra194_calculate_speed(u32 cpu)
pr_debug("cpufreq: %d is idle, delta_refcnt: 0\n", cpu);
return 0;
}
- rate_mhz = delta_ccnt * REF_CLK_MHZ / delta_refcnt;
+ rate_mhz = delta_ccnt * data->ref_clk_mhz / delta_refcnt;
return (rate_mhz * KHZ); /* in KHz */
}
@@ -735,6 +825,34 @@ tegra_cpufreq_bpmp_read_lut(struct platform_device *pdev, struct tegra_bpmp *bpm
return freq_table;
}
+/*
+ * When the CCPLEX provides performance domains, the domain index selects the
+ * frequency request register. A CPU without "performance-domains" would fall
+ * back to domain 0, so require the property on every CPU.
+ */
+static int tegra_cpufreq_check_perf_domains(struct device *dev)
+{
+ struct device_node *np;
+ unsigned int cpu;
+ bool found;
+
+ if (!of_property_present(dev->of_node, "#performance-domain-cells"))
+ return 0;
+
+ for_each_possible_cpu(cpu) {
+ np = of_cpu_device_node_get(cpu);
+ found = of_property_present(np, "performance-domains");
+ of_node_put(np);
+
+ if (!found) {
+ dev_err(dev, "cpu%u: missing performance-domains\n", cpu);
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
static int tegra194_cpufreq_probe(struct platform_device *pdev)
{
const struct tegra_cpufreq_soc *soc;
@@ -757,15 +875,33 @@ static int tegra194_cpufreq_probe(struct platform_device *pdev)
return -EINVAL;
}
+ if (soc->ref_clk_from_cntfrq)
+ data->ref_clk_mhz = arch_timer_get_cntfrq() / HZ_PER_MHZ;
+ else
+ data->ref_clk_mhz = REF_CLK_MHZ;
+
+ if (!data->ref_clk_mhz) {
+ dev_err(&pdev->dev, "invalid reference clock rate\n");
+ return -EINVAL;
+ }
+
+ err = tegra_cpufreq_check_perf_domains(&pdev->dev);
+ if (err)
+ return err;
+
data->bpmp_luts = devm_kcalloc(&pdev->dev, data->soc->num_clusters,
sizeof(*data->bpmp_luts), GFP_KERNEL);
if (!data->bpmp_luts)
return -ENOMEM;
if (soc->needs_mmio) {
- data->regs = devm_platform_ioremap_resource(pdev, 0);
+ struct resource *res;
+
+ data->regs = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
if (IS_ERR(data->regs))
return PTR_ERR(data->regs);
+
+ data->regs_size = resource_size(res);
}
if (nr_cpu_ids > data->soc->num_clusters *
@@ -865,6 +1001,7 @@ static const struct of_device_id tegra194_cpufreq_of_match[] = {
{ .compatible = "nvidia,tegra194-ccplex", .data = &tegra194_cpufreq_soc },
{ .compatible = "nvidia,tegra234-ccplex-cluster", .data = &tegra234_cpufreq_soc },
{ .compatible = "nvidia,tegra238-ccplex-cluster", .data = &tegra238_cpufreq_soc },
+ { .compatible = "nvidia,tegra264-ccplex-cluster", .data = &tegra264_cpufreq_soc },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, tegra194_cpufreq_of_match);
--
2.34.1
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH 9/9] arm64: tegra: Add CCPLEX cluster node for Tegra264
2026-11-04 18:15 [PATCH 0/9] cpufreq: tegra194: add Tegra264 support Sumit Gupta
` (7 preceding siblings ...)
2026-11-04 18:15 ` [PATCH 8/9] cpufreq: tegra194: Add support for Tegra264 Sumit Gupta
@ 2026-11-04 18:15 ` Sumit Gupta
2026-11-04 18:41 ` sashiko-bot
8 siblings, 1 reply; 13+ messages in thread
From: Sumit Gupta @ 2026-11-04 18:15 UTC (permalink / raw)
To: rafael, viresh.kumar, robh, krzk+dt, conor+dt, treding, jonathanh,
linux-pm, linux-tegra, linux-kernel, devicetree
Cc: bbasu, sumitg
Add the CCPLEX cluster node under bus@8100000000. The cpufreq driver
uses it to request CPU frequencies.
Add the generic "performance-domains" property to each CPU, referencing
this node. The driver uses the domain index to group CPUs that share a
cluster-clock unit and to select their core_freq_req register. Each
cluster has one CPU, and two clusters share a unit, so each domain
holds two CPUs.
Signed-off-by: Sumit Gupta <sumitg@nvidia.com>
---
arch/arm64/boot/dts/nvidia/tegra264.dtsi | 21 +++++++++++++++++++++
1 file changed, 21 insertions(+)
diff --git a/arch/arm64/boot/dts/nvidia/tegra264.dtsi b/arch/arm64/boot/dts/nvidia/tegra264.dtsi
index 5ab34cd09a3d..10ea51802e8c 100644
--- a/arch/arm64/boot/dts/nvidia/tegra264.dtsi
+++ b/arch/arm64/boot/dts/nvidia/tegra264.dtsi
@@ -3964,6 +3964,13 @@ i2c12: i2c@c6d0000 {
status = "disabled";
};
+ ccplex: ccplex@30000000 {
+ compatible = "nvidia,tegra264-ccplex-cluster";
+ reg = <0x00 0x30000000 0x0 0x00cf0000>;
+ #performance-domain-cells = <1>;
+ nvidia,bpmp = <&bpmp>;
+ };
+
gic: interrupt-controller@46000000 {
compatible = "arm,gic-v3";
reg = <0x00 0x46000000 0x0 0x010000>, /* GICD */
@@ -4384,6 +4391,7 @@ cpu0: cpu@0 {
reg = <0x00000>;
enable-method = "psci";
+ performance-domains = <&ccplex 0>;
i-cache-size = <65536>;
i-cache-line-size = <64>;
@@ -4400,6 +4408,7 @@ cpu1: cpu@10000 {
reg = <0x10000>;
enable-method = "psci";
+ performance-domains = <&ccplex 0>;
i-cache-size = <65536>;
i-cache-line-size = <64>;
@@ -4416,6 +4425,7 @@ cpu2: cpu@20000 {
reg = <0x20000>;
enable-method = "psci";
+ performance-domains = <&ccplex 1>;
i-cache-size = <65536>;
i-cache-line-size = <64>;
@@ -4432,6 +4442,7 @@ cpu3: cpu@30000 {
reg = <0x30000>;
enable-method = "psci";
+ performance-domains = <&ccplex 1>;
i-cache-size = <65536>;
i-cache-line-size = <64>;
@@ -4448,6 +4459,7 @@ cpu4: cpu@40000 {
reg = <0x40000>;
enable-method = "psci";
+ performance-domains = <&ccplex 2>;
i-cache-size = <65536>;
i-cache-line-size = <64>;
@@ -4464,6 +4476,7 @@ cpu5: cpu@50000 {
reg = <0x50000>;
enable-method = "psci";
+ performance-domains = <&ccplex 2>;
i-cache-size = <65536>;
i-cache-line-size = <64>;
@@ -4480,6 +4493,7 @@ cpu6: cpu@60000 {
reg = <0x60000>;
enable-method = "psci";
+ performance-domains = <&ccplex 3>;
i-cache-size = <65536>;
i-cache-line-size = <64>;
@@ -4496,6 +4510,7 @@ cpu7: cpu@70000 {
reg = <0x70000>;
enable-method = "psci";
+ performance-domains = <&ccplex 3>;
i-cache-size = <65536>;
i-cache-line-size = <64>;
@@ -4512,6 +4527,7 @@ cpu8: cpu@80000 {
reg = <0x80000>;
enable-method = "psci";
+ performance-domains = <&ccplex 4>;
i-cache-size = <65536>;
i-cache-line-size = <64>;
@@ -4528,6 +4544,7 @@ cpu9: cpu@90000 {
reg = <0x90000>;
enable-method = "psci";
+ performance-domains = <&ccplex 4>;
i-cache-size = <65536>;
i-cache-line-size = <64>;
@@ -4544,6 +4561,7 @@ cpu10: cpu@a0000 {
reg = <0xa0000>;
enable-method = "psci";
+ performance-domains = <&ccplex 5>;
i-cache-size = <65536>;
i-cache-line-size = <64>;
@@ -4560,6 +4578,7 @@ cpu11: cpu@b0000 {
reg = <0xb0000>;
enable-method = "psci";
+ performance-domains = <&ccplex 5>;
i-cache-size = <65536>;
i-cache-line-size = <64>;
@@ -4576,6 +4595,7 @@ cpu12: cpu@c0000 {
reg = <0xc0000>;
enable-method = "psci";
+ performance-domains = <&ccplex 6>;
i-cache-size = <65536>;
i-cache-line-size = <64>;
@@ -4592,6 +4612,7 @@ cpu13: cpu@d0000 {
reg = <0xd0000>;
enable-method = "psci";
+ performance-domains = <&ccplex 6>;
i-cache-size = <65536>;
i-cache-line-size = <64>;
--
2.34.1
^ permalink raw reply related [flat|nested] 13+ messages in thread
* Re: [PATCH 1/9] cpufreq: tegra194: move MPIDR read to CPU hotplug callback
2026-11-04 18:15 ` [PATCH 1/9] cpufreq: tegra194: move MPIDR read to CPU hotplug callback Sumit Gupta
@ 2026-11-04 18:29 ` sashiko-bot
0 siblings, 0 replies; 13+ messages in thread
From: sashiko-bot @ 2026-11-04 18:29 UTC (permalink / raw)
To: Sumit Gupta, devicetree; +Cc: robh, conor+dt
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] cpufreq: tegra194: out-of-bounds MMIO and LUT read on bad MPIDR
--
commit 13d8e373c1e546a0ef275842259e4854d9497025
Author: Sumit Gupta <sumitg@nvidia.com>
cpufreq: tegra194: move MPIDR read to CPU hotplug callback
This commit moves the per-CPU MPIDR read from probe-time SMP calls to a CPU
hotplug online callback so that CPUs offline at boot do not use uninitialized
core and cluster IDs. It also validates the CPU count against the SoC data at
probe and skips populating the MMIO scratch register address when a CPU's
MPIDR exceeds the SoC bounds.
> diff --git a/drivers/cpufreq/tegra194-cpufreq.c b/drivers/cpufreq/tegra194-cpufreq.c
> index 1eb96e9f3310d..be81ffd23edd5 100644
> --- a/drivers/cpufreq/tegra194-cpufreq.c
> +++ b/drivers/cpufreq/tegra194-cpufreq.c
[ ... ]
> @@ -121,11 +124,44 @@ static void tegra234_get_cpu_cluster_id(u32 cpu, u32 *cpuid, u32 *clusterid)
> *clusterid = MPIDR_AFFINITY_LEVEL(mpidr, 2);
> }
>
> +static int tegra_cpufreq_cpu_online(unsigned int cpu, struct hlist_node *node)
> +{
> + struct tegra194_cpufreq_data *data;
> + u32 cpuid, clusterid;
> +
> + data = hlist_entry(node, struct tegra194_cpufreq_data, cpuhp_node);
> +
> + data->soc->ops->get_cpu_cluster_id(cpu, &cpuid, &clusterid);
> +
> + data->cpu_data[cpu].cpuid = cpuid;
> + data->cpu_data[cpu].clusterid = clusterid;
> +
> + /* Derive per-core register address only when ccplex MMIO is mapped. */
> + if (data->regs) {
> + if (clusterid >= data->soc->num_clusters ||
> + cpuid >= data->soc->maxcpus_per_cluster) {
> + pr_warn_once("cpufreq: cpu%u: bad MPIDR (cluster %u, core %u)\n",
> + cpu, clusterid, cpuid);
> + } else {
> + u32 index = clusterid * data->soc->maxcpus_per_cluster + cpuid;
> +
> + data->cpu_data[cpu].freq_core_reg =
> + SCRATCH_FREQ_CORE_REG(data, index);
> + }
> + }
> +
> + return 0;
> +}
[Severity: High]
When a CPU has an out-of-bounds MPIDR (clusterid >= data->soc->num_clusters or
cpuid >= data->soc->maxcpus_per_cluster), tegra_cpufreq_cpu_online() leaves
data->cpu_data[cpu].freq_core_reg as NULL so NDIV accesses are skipped, but
still stores the out-of-bounds cpuid and clusterid into data->cpu_data[cpu]
and returns 0.
In tegra194_cpufreq_init(), only clusterid >= data->soc->num_clusters is
checked on policy->cpu:
if (clusterid >= data->soc->num_clusters || !data->bpmp_luts[clusterid])
return -EINVAL;
Neither cpuid >= data->soc->maxcpus_per_cluster on policy->cpu nor the MPIDR
values of sibling CPUs added to policy->cpus are checked there.
When cpufreq later queries the frequency for that CPU via
tegra194_get_speed(), it calls tegra194_calculate_speed() before checking
get_cpu_ndiv():
rate = tegra194_calculate_speed(cpu);
/* get last written ndiv value */
ret = data->soc->ops->get_cpu_ndiv(cpu, data->cpu_data[cpu].cpuid, clusterid, &ndiv);
if (WARN_ON_ONCE(ret))
return rate;
cpufreq_for_each_valid_entry(pos, data->bpmp_luts[clusterid]) {
On Tegra234, tegra194_calculate_speed() queues tegra_read_counters() ->
tegra234_read_counters(), which computes the counter address directly from the
stored clusterid and cpuid without checking freq_core_reg or bounds:
actmon_reg = CORE_ACTMON_CNTR_REG(data, data->cpu_data[c->cpu].clusterid,
data->cpu_data[c->cpu].cpuid);
val = readq(actmon_reg);
And after tegra194_calculate_speed() returns, tegra234_get_cpu_ndiv() returns
-EINVAL for the NULL freq_core_reg, triggering WARN_ON_ONCE(ret) in
tegra194_get_speed().
In addition, on Tegra194 (where data->regs is NULL),
tegra_cpufreq_cpu_online() skips the bounds check altogether,
tegra194_get_cpu_ndiv() succeeds via system register read, and
tegra194_get_speed() indexes data->bpmp_luts[clusterid] without bounds
validation if a sibling CPU has clusterid >= data->soc->num_clusters.
Can this cause an out-of-bounds MMIO read in tegra234_read_counters(), a
WARN_ON_ONCE() splat, or an out-of-bounds read of data->bpmp_luts[clusterid]
in tegra194_get_speed() when a CPU reports a bad MPIDR?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261104181521.3003578-1-sumitg@nvidia.com?part=1
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH 7/9] cpufreq: tegra194: derive frequency domain from device tree
2026-11-04 18:15 ` [PATCH 7/9] cpufreq: tegra194: derive frequency domain from device tree Sumit Gupta
@ 2026-11-04 18:36 ` sashiko-bot
0 siblings, 0 replies; 13+ messages in thread
From: sashiko-bot @ 2026-11-04 18:36 UTC (permalink / raw)
To: Sumit Gupta, devicetree; +Cc: robh, conor+dt
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] cpufreq: tegra194: unvalidated cluster LUT for multi-cluster policy
- [High] cpufreq: tegra194: unvalidated cpuid and clusterid in tegra194_get_speed()
--
commit 92b7184ee71717b6f0c29371b05878084c1e552d
Author: Sumit Gupta <sumitg@nvidia.com>
cpufreq: tegra194: derive frequency domain from device tree
This commit updates tegra194_cpufreq_init() to group CPUs into policies using
the device tree performance-domains property when present, allowing a single
policy to span multiple clusters and storing the domain index in clkid.
> diff --git a/drivers/cpufreq/tegra194-cpufreq.c b/drivers/cpufreq/tegra194-cpufreq.c
> index 047166463efef..1328aa0a1bd1a 100644
> --- a/drivers/cpufreq/tegra194-cpufreq.c
> +++ b/drivers/cpufreq/tegra194-cpufreq.c
[ ... ]
> @@ -528,21 +529,45 @@ static int tegra194_cpufreq_init(struct cpufreq_policy *policy)
> {
> struct tegra194_cpufreq_data *data = cpufreq_get_driver_data();
> int maxcpus_per_cluster = data->soc->maxcpus_per_cluster;
> - u32 clusterid = data->cpu_data[policy->cpu].clusterid;
> struct cpufreq_frequency_table *freq_table;
> struct cpufreq_frequency_table *bpmp_lut;
> - u32 start_cpu, cpu;
> + struct of_phandle_args args;
> + u32 start_cpu, cpu, clusterid;
> int ret;
>
> + /* Covers all possible CPUs, so offline siblings are grouped too. */
> + ret = of_perf_domain_get_sharing_cpumask(policy->cpu, "performance-domains",
> + "#performance-domain-cells",
> + policy->cpus, &args);
> + if (!ret) {
> + if (args.args_count != 1) {
> + pr_err("cpufreq: cpu%u: invalid #performance-domain-cells\n",
> + policy->cpu);
> + of_node_put(args.np);
> + return -EINVAL;
> + }
> +
> + for_each_cpu(cpu, policy->cpus)
> + data->cpu_data[cpu].clkid = args.args[0];
> +
> + of_node_put(args.np);
> + } else if (ret == -ENOENT) {
> + /* Legacy grouping for DTs without performance-domains. */
> + start_cpu = rounddown(policy->cpu, maxcpus_per_cluster);
> + for (cpu = start_cpu; cpu < (start_cpu + maxcpus_per_cluster); cpu++)
> + if (cpu_possible(cpu))
> + cpumask_set_cpu(cpu, policy->cpus);
> + } else {
> + pr_err("cpufreq: cpu%u: failed to get performance domain: %d\n",
> + policy->cpu, ret);
> + return ret;
> + }
> +
> + /* The BPMP LUT is indexed by the MPIDR cluster id, not clkid. */
> + clusterid = data->cpu_data[policy->cpu].clusterid;
> if (clusterid >= data->soc->num_clusters || !data->bpmp_luts[clusterid])
> return -EINVAL;
[Severity: High]
Since policy->cpus can now span multiple clusters, what happens when
tegra194_get_speed() is called for a CPU in another cluster of the same
policy (for example via cpufreq_get(), or after the initial policy->cpu goes
offline and cpufreq_offline() migrates policy->cpu to a sibling in a second
cluster without re-running tegra194_cpufreq_init())?
tegra194_cpufreq_init() only validates clusterid and
data->bpmp_luts[clusterid] for the initial policy->cpu, but
tegra194_get_speed() indexes data->bpmp_luts[] using the queried CPU's own
clusterid:
drivers/cpufreq/tegra194-cpufreq.c:tegra194_get_speed() {
...
u32 clusterid = data->cpu_data[cpu].clusterid;
...
cpufreq_for_each_valid_entry(pos, data->bpmp_luts[clusterid]) {
}
If tegra_cpufreq_bpmp_read_lut() returned -BPMP_EINVAL for that secondary
cluster (leaving data->bpmp_luts[clusterid] as NULL), or if that CPU's
clusterid is >= data->soc->num_clusters on an SoC where get_cpu_ndiv() does
not depend on freq_core_reg (such as Tegra194 or Tegra264), can
tegra194_get_speed() dereference a NULL pointer or read out of bounds in
data->bpmp_luts[]?
[Severity: High]
Can tegra194_get_speed() also trigger an out-of-bounds MMIO read in
tegra234_sample_counters() when cpuid or clusterid is out of bounds?
In tegra_cpufreq_cpu_online(), cpuid and clusterid from MPIDR are stored in
data->cpu_data[cpu] unconditionally, and if either is out of bounds, it logs
a warning, leaves freq_core_reg as NULL, and still returns 0:
drivers/cpufreq/tegra194-cpufreq.c:tegra_cpufreq_cpu_online() {
...
data->cpu_data[cpu].cpuid = cpuid;
data->cpu_data[cpu].clusterid = clusterid;
/* Derive per-core register address only when ccplex MMIO is mapped. */
if (data->regs) {
if (clusterid >= data->soc->num_clusters ||
cpuid >= data->soc->maxcpus_per_cluster) {
pr_warn_once("cpufreq: cpu%u: bad MPIDR (cluster %u, core %u)\n",
cpu, clusterid, cpuid);
}
tegra194_cpufreq_init() does not check cpuid >= data->soc->maxcpus_per_cluster
for policy->cpu, nor does it validate clusterid or cpuid for cross-cluster or
initially offline sibling CPUs in policy->cpus (which may later come online
via tegra194_cpufreq_online() without re-running tegra194_cpufreq_init()).
When tegra194_get_speed() runs on such a CPU, it calls
tegra194_calculate_speed() before get_cpu_ndiv() checks freq_core_reg:
drivers/cpufreq/tegra194-cpufreq.c:tegra194_get_speed() {
...
/* reconstruct actual cpu freq using counters */
rate = tegra194_calculate_speed(cpu);
/* get last written ndiv value */
ret = data->soc->ops->get_cpu_ndiv(cpu, data->cpu_data[cpu].cpuid, clusterid, &ndiv);
}
On Tegra234 and Tegra238, tegra194_calculate_speed()->tegra_read_counters()->
tegra234_sample_counters() computes actmon_reg directly from the unvalidated
clusterid and cpuid without checking freq_core_reg:
drivers/cpufreq/tegra194-cpufreq.c:tegra234_sample_counters() {
...
actmon_reg = CORE_ACTMON_CNTR_REG(data, data->cpu_data[c->cpu].clusterid,
data->cpu_data[c->cpu].cpuid);
val = readq(actmon_reg);
}
Could this read past the mapped MMIO region in data->regs?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261104181521.3003578-1-sumitg@nvidia.com?part=7
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH 9/9] arm64: tegra: Add CCPLEX cluster node for Tegra264
2026-11-04 18:15 ` [PATCH 9/9] arm64: tegra: Add CCPLEX cluster node " Sumit Gupta
@ 2026-11-04 18:41 ` sashiko-bot
0 siblings, 0 replies; 13+ messages in thread
From: sashiko-bot @ 2026-11-04 18:41 UTC (permalink / raw)
To: Sumit Gupta, devicetree; +Cc: robh, conor+dt
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] arm64: tegra: ccplex@30000000 translates to PCIe window 0x30000000 instead of 0x8130000000
--
commit 26a0214ddb9808bcd10d28a93f2871f0b1488885
Author: Sumit Gupta <sumitg@nvidia.com>
arm64: tegra: Add CCPLEX cluster node for Tegra264
This commit adds the CCPLEX cluster node under bus@8100000000 in
tegra264.dtsi and attaches performance-domains properties to the CPU nodes
for CPU frequency scaling.
> diff --git a/arch/arm64/boot/dts/nvidia/tegra264.dtsi b/arch/arm64/boot/dts/nvidia/tegra264.dtsi
> index 5ab34cd09a3d0..10ea51802e8c8 100644
> --- a/arch/arm64/boot/dts/nvidia/tegra264.dtsi
> +++ b/arch/arm64/boot/dts/nvidia/tegra264.dtsi
> @@ -3964,6 +3964,13 @@ i2c12: i2c@c6d0000 {
> status = "disabled";
> };
>
> + ccplex: ccplex@30000000 {
> + compatible = "nvidia,tegra264-ccplex-cluster";
> + reg = <0x00 0x30000000 0x0 0x00cf0000>;
> + #performance-domain-cells = <1>;
> + nvidia,bpmp = <&bpmp>;
> + };
[Severity: High]
Does ccplex@30000000 translate to CPU physical address 0x00_30000000 instead
of 0x81_30000000 here?
In arch/arm64/boot/dts/nvidia/tegra264.dtsi, the parent bus@8100000000 node
defines its ranges property as:
bus@8100000000 {
...
ranges = <0x00 0x00000000 0x81 0x00000000 0x00 0x20000000>, /* MMIO (512 MiB) */
<0x00 0x20000000 0x00 0x20000000 0x00 0x20000000>, /* non-prefetchable memory (32-bit, 512 MiB) */
<0x00 0x40000000 0x81 0x40000000 0x00 0x20000000>, /* MMIO (512 MiB) */
<0xa8 0x80000000 0xa8 0x80000000 0x57 0x80000000>; /* I/O, ECAM, prefetchable memory (64-bit) */
Because child address 0x00 0x30000000 falls within the second entry
(0x00 0x20000000..0x00 0x3fffffff), OF address translation maps it to parent
CPU address 0x00 0x30000000..0x00 0x30ceffff rather than 0x81 0x30000000.
When tegra194_cpufreq_probe() calls devm_platform_get_and_ioremap_resource()
on this node, tegra264_get_cpu_ndiv() and tegra264_set_cpu_ndiv() will read
and write the 32-bit PCIe non-prefetchable memory window at 0x00 0x30000000,
which also collides with the non-prefetchable memory aperture of pci@8420000:
pci@8420000 {
...
ranges = <0x81000000 0x00 0x84000000 0xb0 0x84000000 0x00 0x00200000>, /* I/O */
<0x82000000 0x00 0x30000000 0x00 0x30000000 0x00 0x08000000>, /* non-prefetchable memory */
<0xc3000000 0xb0 0xc0000000 0xb0 0xc0000000 0x07 0xc0000000>; /* prefetchable memory */
Since pci@c000000 under bus@8100000000 only uses 0x00 0x20000000..0x00 0x27ffffff
for its 32-bit non-prefetchable memory window, should the ranges property on
bus@8100000000 be updated so that 0x00 0x30000000 translates to
0x81 0x30000000?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261104181521.3003578-1-sumitg@nvidia.com?part=9
^ permalink raw reply [flat|nested] 13+ messages in thread
end of thread, other threads:[~2026-11-04 18:41 UTC | newest]
Thread overview: 13+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-04 18:15 [PATCH 0/9] cpufreq: tegra194: add Tegra264 support Sumit Gupta
2026-11-04 18:15 ` [PATCH 1/9] cpufreq: tegra194: move MPIDR read to CPU hotplug callback Sumit Gupta
2026-11-04 18:29 ` sashiko-bot
2026-11-04 18:15 ` [PATCH 2/9] cpufreq: tegra194: remove duplicate counter sampling loops Sumit Gupta
2026-11-04 18:15 ` [PATCH 3/9] cpufreq: tegra194: add tegra_counter_delta helper Sumit Gupta
2026-11-04 18:15 ` [PATCH 4/9] cpufreq: tegra194: add per-SoC counter mask Sumit Gupta
2026-11-04 18:15 ` [PATCH 5/9] cpufreq: tegra194: split MMIO mapping from ACTMON offset Sumit Gupta
2026-11-04 18:15 ` [PATCH 6/9] dt-bindings: arm: nvidia: Document the Tegra264 CCPLEX cluster Sumit Gupta
2026-11-04 18:15 ` [PATCH 7/9] cpufreq: tegra194: derive frequency domain from device tree Sumit Gupta
2026-11-04 18:36 ` sashiko-bot
2026-11-04 18:15 ` [PATCH 8/9] cpufreq: tegra194: Add support for Tegra264 Sumit Gupta
2026-11-04 18:15 ` [PATCH 9/9] arm64: tegra: Add CCPLEX cluster node " Sumit Gupta
2026-11-04 18:41 ` sashiko-bot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox