162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (c) 2017, NVIDIA CORPORATION. All rights reserved
462306a36Sopenharmony_ci */
562306a36Sopenharmony_ci
662306a36Sopenharmony_ci#include <linux/cpufreq.h>
762306a36Sopenharmony_ci#include <linux/dma-mapping.h>
862306a36Sopenharmony_ci#include <linux/module.h>
962306a36Sopenharmony_ci#include <linux/of.h>
1062306a36Sopenharmony_ci#include <linux/platform_device.h>
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_ci#include <soc/tegra/bpmp.h>
1362306a36Sopenharmony_ci#include <soc/tegra/bpmp-abi.h>
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_ci#define TEGRA186_NUM_CLUSTERS		2
1662306a36Sopenharmony_ci#define EDVD_OFFSET_A57(core)		((SZ_64K * 6) + (0x20 + (core) * 0x4))
1762306a36Sopenharmony_ci#define EDVD_OFFSET_DENVER(core)	((SZ_64K * 7) + (0x20 + (core) * 0x4))
1862306a36Sopenharmony_ci#define EDVD_CORE_VOLT_FREQ_F_SHIFT	0
1962306a36Sopenharmony_ci#define EDVD_CORE_VOLT_FREQ_F_MASK	0xffff
2062306a36Sopenharmony_ci#define EDVD_CORE_VOLT_FREQ_V_SHIFT	16
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_cistruct tegra186_cpufreq_cpu {
2362306a36Sopenharmony_ci	unsigned int bpmp_cluster_id;
2462306a36Sopenharmony_ci	unsigned int edvd_offset;
2562306a36Sopenharmony_ci};
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_cistatic const struct tegra186_cpufreq_cpu tegra186_cpus[] = {
2862306a36Sopenharmony_ci	/* CPU0 - A57 Cluster */
2962306a36Sopenharmony_ci	{
3062306a36Sopenharmony_ci		.bpmp_cluster_id = 1,
3162306a36Sopenharmony_ci		.edvd_offset = EDVD_OFFSET_A57(0)
3262306a36Sopenharmony_ci	},
3362306a36Sopenharmony_ci	/* CPU1 - Denver Cluster */
3462306a36Sopenharmony_ci	{
3562306a36Sopenharmony_ci		.bpmp_cluster_id = 0,
3662306a36Sopenharmony_ci		.edvd_offset = EDVD_OFFSET_DENVER(0)
3762306a36Sopenharmony_ci	},
3862306a36Sopenharmony_ci	/* CPU2 - Denver Cluster */
3962306a36Sopenharmony_ci	{
4062306a36Sopenharmony_ci		.bpmp_cluster_id = 0,
4162306a36Sopenharmony_ci		.edvd_offset = EDVD_OFFSET_DENVER(1)
4262306a36Sopenharmony_ci	},
4362306a36Sopenharmony_ci	/* CPU3 - A57 Cluster */
4462306a36Sopenharmony_ci	{
4562306a36Sopenharmony_ci		.bpmp_cluster_id = 1,
4662306a36Sopenharmony_ci		.edvd_offset = EDVD_OFFSET_A57(1)
4762306a36Sopenharmony_ci	},
4862306a36Sopenharmony_ci	/* CPU4 - A57 Cluster */
4962306a36Sopenharmony_ci	{
5062306a36Sopenharmony_ci		.bpmp_cluster_id = 1,
5162306a36Sopenharmony_ci		.edvd_offset = EDVD_OFFSET_A57(2)
5262306a36Sopenharmony_ci	},
5362306a36Sopenharmony_ci	/* CPU5 - A57 Cluster */
5462306a36Sopenharmony_ci	{
5562306a36Sopenharmony_ci		.bpmp_cluster_id = 1,
5662306a36Sopenharmony_ci		.edvd_offset = EDVD_OFFSET_A57(3)
5762306a36Sopenharmony_ci	},
5862306a36Sopenharmony_ci};
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_cistruct tegra186_cpufreq_cluster {
6162306a36Sopenharmony_ci	struct cpufreq_frequency_table *table;
6262306a36Sopenharmony_ci	u32 ref_clk_khz;
6362306a36Sopenharmony_ci	u32 div;
6462306a36Sopenharmony_ci};
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_cistruct tegra186_cpufreq_data {
6762306a36Sopenharmony_ci	void __iomem *regs;
6862306a36Sopenharmony_ci	const struct tegra186_cpufreq_cpu *cpus;
6962306a36Sopenharmony_ci	struct tegra186_cpufreq_cluster clusters[];
7062306a36Sopenharmony_ci};
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_cistatic int tegra186_cpufreq_init(struct cpufreq_policy *policy)
7362306a36Sopenharmony_ci{
7462306a36Sopenharmony_ci	struct tegra186_cpufreq_data *data = cpufreq_get_driver_data();
7562306a36Sopenharmony_ci	unsigned int cluster = data->cpus[policy->cpu].bpmp_cluster_id;
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_ci	policy->freq_table = data->clusters[cluster].table;
7862306a36Sopenharmony_ci	policy->cpuinfo.transition_latency = 300 * 1000;
7962306a36Sopenharmony_ci	policy->driver_data = NULL;
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_ci	return 0;
8262306a36Sopenharmony_ci}
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_cistatic int tegra186_cpufreq_set_target(struct cpufreq_policy *policy,
8562306a36Sopenharmony_ci				       unsigned int index)
8662306a36Sopenharmony_ci{
8762306a36Sopenharmony_ci	struct tegra186_cpufreq_data *data = cpufreq_get_driver_data();
8862306a36Sopenharmony_ci	struct cpufreq_frequency_table *tbl = policy->freq_table + index;
8962306a36Sopenharmony_ci	unsigned int edvd_offset = data->cpus[policy->cpu].edvd_offset;
9062306a36Sopenharmony_ci	u32 edvd_val = tbl->driver_data;
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_ci	writel(edvd_val, data->regs + edvd_offset);
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_ci	return 0;
9562306a36Sopenharmony_ci}
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_cistatic unsigned int tegra186_cpufreq_get(unsigned int cpu)
9862306a36Sopenharmony_ci{
9962306a36Sopenharmony_ci	struct tegra186_cpufreq_data *data = cpufreq_get_driver_data();
10062306a36Sopenharmony_ci	struct tegra186_cpufreq_cluster *cluster;
10162306a36Sopenharmony_ci	struct cpufreq_policy *policy;
10262306a36Sopenharmony_ci	unsigned int edvd_offset, cluster_id;
10362306a36Sopenharmony_ci	u32 ndiv;
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_ci	policy = cpufreq_cpu_get(cpu);
10662306a36Sopenharmony_ci	if (!policy)
10762306a36Sopenharmony_ci		return 0;
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_ci	edvd_offset = data->cpus[policy->cpu].edvd_offset;
11062306a36Sopenharmony_ci	ndiv = readl(data->regs + edvd_offset) & EDVD_CORE_VOLT_FREQ_F_MASK;
11162306a36Sopenharmony_ci	cluster_id = data->cpus[policy->cpu].bpmp_cluster_id;
11262306a36Sopenharmony_ci	cluster = &data->clusters[cluster_id];
11362306a36Sopenharmony_ci	cpufreq_cpu_put(policy);
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_ci	return (cluster->ref_clk_khz * ndiv) / cluster->div;
11662306a36Sopenharmony_ci}
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_cistatic struct cpufreq_driver tegra186_cpufreq_driver = {
11962306a36Sopenharmony_ci	.name = "tegra186",
12062306a36Sopenharmony_ci	.flags = CPUFREQ_HAVE_GOVERNOR_PER_POLICY |
12162306a36Sopenharmony_ci			CPUFREQ_NEED_INITIAL_FREQ_CHECK,
12262306a36Sopenharmony_ci	.get = tegra186_cpufreq_get,
12362306a36Sopenharmony_ci	.verify = cpufreq_generic_frequency_table_verify,
12462306a36Sopenharmony_ci	.target_index = tegra186_cpufreq_set_target,
12562306a36Sopenharmony_ci	.init = tegra186_cpufreq_init,
12662306a36Sopenharmony_ci	.attr = cpufreq_generic_attr,
12762306a36Sopenharmony_ci};
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_cistatic struct cpufreq_frequency_table *init_vhint_table(
13062306a36Sopenharmony_ci	struct platform_device *pdev, struct tegra_bpmp *bpmp,
13162306a36Sopenharmony_ci	struct tegra186_cpufreq_cluster *cluster, unsigned int cluster_id)
13262306a36Sopenharmony_ci{
13362306a36Sopenharmony_ci	struct cpufreq_frequency_table *table;
13462306a36Sopenharmony_ci	struct mrq_cpu_vhint_request req;
13562306a36Sopenharmony_ci	struct tegra_bpmp_message msg;
13662306a36Sopenharmony_ci	struct cpu_vhint_data *data;
13762306a36Sopenharmony_ci	int err, i, j, num_rates = 0;
13862306a36Sopenharmony_ci	dma_addr_t phys;
13962306a36Sopenharmony_ci	void *virt;
14062306a36Sopenharmony_ci
14162306a36Sopenharmony_ci	virt = dma_alloc_coherent(bpmp->dev, sizeof(*data), &phys,
14262306a36Sopenharmony_ci				  GFP_KERNEL);
14362306a36Sopenharmony_ci	if (!virt)
14462306a36Sopenharmony_ci		return ERR_PTR(-ENOMEM);
14562306a36Sopenharmony_ci
14662306a36Sopenharmony_ci	data = (struct cpu_vhint_data *)virt;
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_ci	memset(&req, 0, sizeof(req));
14962306a36Sopenharmony_ci	req.addr = phys;
15062306a36Sopenharmony_ci	req.cluster_id = cluster_id;
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_ci	memset(&msg, 0, sizeof(msg));
15362306a36Sopenharmony_ci	msg.mrq = MRQ_CPU_VHINT;
15462306a36Sopenharmony_ci	msg.tx.data = &req;
15562306a36Sopenharmony_ci	msg.tx.size = sizeof(req);
15662306a36Sopenharmony_ci
15762306a36Sopenharmony_ci	err = tegra_bpmp_transfer(bpmp, &msg);
15862306a36Sopenharmony_ci	if (err) {
15962306a36Sopenharmony_ci		table = ERR_PTR(err);
16062306a36Sopenharmony_ci		goto free;
16162306a36Sopenharmony_ci	}
16262306a36Sopenharmony_ci	if (msg.rx.ret) {
16362306a36Sopenharmony_ci		table = ERR_PTR(-EINVAL);
16462306a36Sopenharmony_ci		goto free;
16562306a36Sopenharmony_ci	}
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_ci	for (i = data->vfloor; i <= data->vceil; i++) {
16862306a36Sopenharmony_ci		u16 ndiv = data->ndiv[i];
16962306a36Sopenharmony_ci
17062306a36Sopenharmony_ci		if (ndiv < data->ndiv_min || ndiv > data->ndiv_max)
17162306a36Sopenharmony_ci			continue;
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_ci		/* Only store lowest voltage index for each rate */
17462306a36Sopenharmony_ci		if (i > 0 && ndiv == data->ndiv[i - 1])
17562306a36Sopenharmony_ci			continue;
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_ci		num_rates++;
17862306a36Sopenharmony_ci	}
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_ci	table = devm_kcalloc(&pdev->dev, num_rates + 1, sizeof(*table),
18162306a36Sopenharmony_ci			     GFP_KERNEL);
18262306a36Sopenharmony_ci	if (!table) {
18362306a36Sopenharmony_ci		table = ERR_PTR(-ENOMEM);
18462306a36Sopenharmony_ci		goto free;
18562306a36Sopenharmony_ci	}
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_ci	cluster->ref_clk_khz = data->ref_clk_hz / 1000;
18862306a36Sopenharmony_ci	cluster->div = data->pdiv * data->mdiv;
18962306a36Sopenharmony_ci
19062306a36Sopenharmony_ci	for (i = data->vfloor, j = 0; i <= data->vceil; i++) {
19162306a36Sopenharmony_ci		struct cpufreq_frequency_table *point;
19262306a36Sopenharmony_ci		u16 ndiv = data->ndiv[i];
19362306a36Sopenharmony_ci		u32 edvd_val = 0;
19462306a36Sopenharmony_ci
19562306a36Sopenharmony_ci		if (ndiv < data->ndiv_min || ndiv > data->ndiv_max)
19662306a36Sopenharmony_ci			continue;
19762306a36Sopenharmony_ci
19862306a36Sopenharmony_ci		/* Only store lowest voltage index for each rate */
19962306a36Sopenharmony_ci		if (i > 0 && ndiv == data->ndiv[i - 1])
20062306a36Sopenharmony_ci			continue;
20162306a36Sopenharmony_ci
20262306a36Sopenharmony_ci		edvd_val |= i << EDVD_CORE_VOLT_FREQ_V_SHIFT;
20362306a36Sopenharmony_ci		edvd_val |= ndiv << EDVD_CORE_VOLT_FREQ_F_SHIFT;
20462306a36Sopenharmony_ci
20562306a36Sopenharmony_ci		point = &table[j++];
20662306a36Sopenharmony_ci		point->driver_data = edvd_val;
20762306a36Sopenharmony_ci		point->frequency = (cluster->ref_clk_khz * ndiv) / cluster->div;
20862306a36Sopenharmony_ci	}
20962306a36Sopenharmony_ci
21062306a36Sopenharmony_ci	table[j].frequency = CPUFREQ_TABLE_END;
21162306a36Sopenharmony_ci
21262306a36Sopenharmony_cifree:
21362306a36Sopenharmony_ci	dma_free_coherent(bpmp->dev, sizeof(*data), virt, phys);
21462306a36Sopenharmony_ci
21562306a36Sopenharmony_ci	return table;
21662306a36Sopenharmony_ci}
21762306a36Sopenharmony_ci
21862306a36Sopenharmony_cistatic int tegra186_cpufreq_probe(struct platform_device *pdev)
21962306a36Sopenharmony_ci{
22062306a36Sopenharmony_ci	struct tegra186_cpufreq_data *data;
22162306a36Sopenharmony_ci	struct tegra_bpmp *bpmp;
22262306a36Sopenharmony_ci	unsigned int i = 0, err;
22362306a36Sopenharmony_ci
22462306a36Sopenharmony_ci	data = devm_kzalloc(&pdev->dev,
22562306a36Sopenharmony_ci			    struct_size(data, clusters, TEGRA186_NUM_CLUSTERS),
22662306a36Sopenharmony_ci			    GFP_KERNEL);
22762306a36Sopenharmony_ci	if (!data)
22862306a36Sopenharmony_ci		return -ENOMEM;
22962306a36Sopenharmony_ci
23062306a36Sopenharmony_ci	data->cpus = tegra186_cpus;
23162306a36Sopenharmony_ci
23262306a36Sopenharmony_ci	bpmp = tegra_bpmp_get(&pdev->dev);
23362306a36Sopenharmony_ci	if (IS_ERR(bpmp))
23462306a36Sopenharmony_ci		return PTR_ERR(bpmp);
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_ci	data->regs = devm_platform_ioremap_resource(pdev, 0);
23762306a36Sopenharmony_ci	if (IS_ERR(data->regs)) {
23862306a36Sopenharmony_ci		err = PTR_ERR(data->regs);
23962306a36Sopenharmony_ci		goto put_bpmp;
24062306a36Sopenharmony_ci	}
24162306a36Sopenharmony_ci
24262306a36Sopenharmony_ci	for (i = 0; i < TEGRA186_NUM_CLUSTERS; i++) {
24362306a36Sopenharmony_ci		struct tegra186_cpufreq_cluster *cluster = &data->clusters[i];
24462306a36Sopenharmony_ci
24562306a36Sopenharmony_ci		cluster->table = init_vhint_table(pdev, bpmp, cluster, i);
24662306a36Sopenharmony_ci		if (IS_ERR(cluster->table)) {
24762306a36Sopenharmony_ci			err = PTR_ERR(cluster->table);
24862306a36Sopenharmony_ci			goto put_bpmp;
24962306a36Sopenharmony_ci		}
25062306a36Sopenharmony_ci	}
25162306a36Sopenharmony_ci
25262306a36Sopenharmony_ci	tegra186_cpufreq_driver.driver_data = data;
25362306a36Sopenharmony_ci
25462306a36Sopenharmony_ci	err = cpufreq_register_driver(&tegra186_cpufreq_driver);
25562306a36Sopenharmony_ci
25662306a36Sopenharmony_ciput_bpmp:
25762306a36Sopenharmony_ci	tegra_bpmp_put(bpmp);
25862306a36Sopenharmony_ci
25962306a36Sopenharmony_ci	return err;
26062306a36Sopenharmony_ci}
26162306a36Sopenharmony_ci
26262306a36Sopenharmony_cistatic void tegra186_cpufreq_remove(struct platform_device *pdev)
26362306a36Sopenharmony_ci{
26462306a36Sopenharmony_ci	cpufreq_unregister_driver(&tegra186_cpufreq_driver);
26562306a36Sopenharmony_ci}
26662306a36Sopenharmony_ci
26762306a36Sopenharmony_cistatic const struct of_device_id tegra186_cpufreq_of_match[] = {
26862306a36Sopenharmony_ci	{ .compatible = "nvidia,tegra186-ccplex-cluster", },
26962306a36Sopenharmony_ci	{ }
27062306a36Sopenharmony_ci};
27162306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, tegra186_cpufreq_of_match);
27262306a36Sopenharmony_ci
27362306a36Sopenharmony_cistatic struct platform_driver tegra186_cpufreq_platform_driver = {
27462306a36Sopenharmony_ci	.driver = {
27562306a36Sopenharmony_ci		.name = "tegra186-cpufreq",
27662306a36Sopenharmony_ci		.of_match_table = tegra186_cpufreq_of_match,
27762306a36Sopenharmony_ci	},
27862306a36Sopenharmony_ci	.probe = tegra186_cpufreq_probe,
27962306a36Sopenharmony_ci	.remove_new = tegra186_cpufreq_remove,
28062306a36Sopenharmony_ci};
28162306a36Sopenharmony_cimodule_platform_driver(tegra186_cpufreq_platform_driver);
28262306a36Sopenharmony_ci
28362306a36Sopenharmony_ciMODULE_AUTHOR("Mikko Perttunen <mperttunen@nvidia.com>");
28462306a36Sopenharmony_ciMODULE_DESCRIPTION("NVIDIA Tegra186 cpufreq driver");
28562306a36Sopenharmony_ciMODULE_LICENSE("GPL v2");
286