From: Aaron Kling webgeek1234@gmail.com
[ Upstream commit ba6018929165fc914c665f071f8e8cdbac844a49 ]
During initialization, the EDVD_COREx_VOLT_FREQ registers for some cores are still at reset values and not reflecting the actual frequency. This causes get calls to fail. Set all cores to their respective max frequency during probe to initialize the registers to working values.
Suggested-by: Mikko Perttunen mperttunen@nvidia.com Signed-off-by: Aaron Kling webgeek1234@gmail.com Reviewed-by: Mikko Perttunen mperttunen@nvidia.com Signed-off-by: Viresh Kumar viresh.kumar@linaro.org Signed-off-by: Sasha Levin sashal@kernel.org ---
LLM Generated explanations, may be completely bogus:
YES - `tegra186_cpufreq_get()` reads the EDVD register for a policy’s lead CPU and returns 0 when the register is still at its reset value (`drivers/cpufreq/tegra186-cpufreq.c:120-126`); the cpufreq core treats a zero return as fatal and tears down the policy (`drivers/cpufreq/cpufreq.c:1486-1492`), so the driver currently fails to probe on systems where some cores never had their EDVD register programmed by firmware. - The patch teaches `init_vhint_table()` to hand the caller the number of valid operating points and asserts that at least one exists (`drivers/cpufreq/tegra186-cpufreq.c:178-193` together with the new check at `259-266`), so we know which table entry corresponds to the highest valid frequency. - During probe the driver now programs every CPU in each cluster with the highest frequency/voltage tuple from the freshly built table (`drivers/cpufreq/tegra186-cpufreq.c:268-273`). This guarantees those EDVD registers hold a non-zero, valid state before cpufreq asks for the current rate, unblocking registration while staying in-spec because the value comes directly from the board’s own V/F table. - The change is tightly scoped to the Tegra186 cpufreq driver, relies only on data already returned by BPMP, and doesn’t alter core interfaces; once cpufreq is up, the existing `set_target` path continues to broadcast every new selection to all CPUs in the policy (`drivers/cpufreq/tegra186-cpufreq.c:100-103`), so there’s no new long-term behaviour difference beyond the one-time initialization write. - Risk is low: the only observable effect is a brief switch to a table- defined maximum frequency during probe, which is within the validated OPP set and quickly superseded by the governor, whereas the unfixed bug leaves the entire cpufreq subsystem unusable on affected Tegra186 systems.
drivers/cpufreq/tegra186-cpufreq.c | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-)
diff --git a/drivers/cpufreq/tegra186-cpufreq.c b/drivers/cpufreq/tegra186-cpufreq.c index 6c394b429b618..bd94beebc4cc2 100644 --- a/drivers/cpufreq/tegra186-cpufreq.c +++ b/drivers/cpufreq/tegra186-cpufreq.c @@ -138,13 +138,14 @@ static struct cpufreq_driver tegra186_cpufreq_driver = {
static struct cpufreq_frequency_table *init_vhint_table( struct platform_device *pdev, struct tegra_bpmp *bpmp, - struct tegra186_cpufreq_cluster *cluster, unsigned int cluster_id) + struct tegra186_cpufreq_cluster *cluster, unsigned int cluster_id, + int *num_rates) { struct cpufreq_frequency_table *table; struct mrq_cpu_vhint_request req; struct tegra_bpmp_message msg; struct cpu_vhint_data *data; - int err, i, j, num_rates = 0; + int err, i, j; dma_addr_t phys; void *virt;
@@ -174,6 +175,7 @@ static struct cpufreq_frequency_table *init_vhint_table( goto free; }
+ *num_rates = 0; for (i = data->vfloor; i <= data->vceil; i++) { u16 ndiv = data->ndiv[i];
@@ -184,10 +186,10 @@ static struct cpufreq_frequency_table *init_vhint_table( if (i > 0 && ndiv == data->ndiv[i - 1]) continue;
- num_rates++; + (*num_rates)++; }
- table = devm_kcalloc(&pdev->dev, num_rates + 1, sizeof(*table), + table = devm_kcalloc(&pdev->dev, *num_rates + 1, sizeof(*table), GFP_KERNEL); if (!table) { table = ERR_PTR(-ENOMEM); @@ -229,7 +231,9 @@ static int tegra186_cpufreq_probe(struct platform_device *pdev) { struct tegra186_cpufreq_data *data; struct tegra_bpmp *bpmp; - unsigned int i = 0, err; + unsigned int i = 0, err, edvd_offset; + int num_rates = 0; + u32 edvd_val, cpu;
data = devm_kzalloc(&pdev->dev, struct_size(data, clusters, TEGRA186_NUM_CLUSTERS), @@ -252,10 +256,21 @@ static int tegra186_cpufreq_probe(struct platform_device *pdev) for (i = 0; i < TEGRA186_NUM_CLUSTERS; i++) { struct tegra186_cpufreq_cluster *cluster = &data->clusters[i];
- cluster->table = init_vhint_table(pdev, bpmp, cluster, i); + cluster->table = init_vhint_table(pdev, bpmp, cluster, i, &num_rates); if (IS_ERR(cluster->table)) { err = PTR_ERR(cluster->table); goto put_bpmp; + } else if (!num_rates) { + err = -EINVAL; + goto put_bpmp; + } + + for (cpu = 0; cpu < ARRAY_SIZE(tegra186_cpus); cpu++) { + if (data->cpus[cpu].bpmp_cluster_id == i) { + edvd_val = cluster->table[num_rates - 1].driver_data; + edvd_offset = data->cpus[cpu].edvd_offset; + writel(edvd_val, data->regs + edvd_offset); + } } }