aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMikko Perttunen <mperttunen@nvidia.com>2017-04-11 04:09:15 -0400
committerRafael J. Wysocki <rafael.j.wysocki@intel.com>2017-04-19 17:23:08 -0400
commit939dc6f51e90c95a7d88034da48b747f01873bce (patch)
tree1af2dd00ee28636ee28f99583980a3a8b8b4178c
parenteafca851639038a7863910e7fac869f5c8bdfb9d (diff)
cpufreq: Add Tegra186 cpufreq driver
Add a new cpufreq driver for Tegra186 (and likely later). The CPUs are organized into two clusters, Denver and A57, with two and four cores respectively. CPU frequency can be adjusted by writing the desired rate divisor and a voltage hint to a special per-core register. The frequency of each core can be set individually; however, this is just a hint as all CPUs in a cluster will run at the maximum rate of non-idle CPUs in the cluster. Signed-off-by: Mikko Perttunen <mperttunen@nvidia.com> Acked-by: Viresh Kumar <viresh.kumar@linaro.org> Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
-rw-r--r--drivers/cpufreq/Kconfig.arm6
-rw-r--r--drivers/cpufreq/Makefile1
-rw-r--r--drivers/cpufreq/tegra186-cpufreq.c275
3 files changed, 282 insertions, 0 deletions
diff --git a/drivers/cpufreq/Kconfig.arm b/drivers/cpufreq/Kconfig.arm
index 74fa5c5904d3..74ed7e9a7f27 100644
--- a/drivers/cpufreq/Kconfig.arm
+++ b/drivers/cpufreq/Kconfig.arm
@@ -247,6 +247,12 @@ config ARM_TEGRA124_CPUFREQ
247 help 247 help
248 This adds the CPUFreq driver support for Tegra124 SOCs. 248 This adds the CPUFreq driver support for Tegra124 SOCs.
249 249
250config ARM_TEGRA186_CPUFREQ
251 tristate "Tegra186 CPUFreq support"
252 depends on ARCH_TEGRA && TEGRA_BPMP
253 help
254 This adds the CPUFreq driver support for Tegra186 SOCs.
255
250config ARM_TI_CPUFREQ 256config ARM_TI_CPUFREQ
251 bool "Texas Instruments CPUFreq support" 257 bool "Texas Instruments CPUFreq support"
252 depends on ARCH_OMAP2PLUS 258 depends on ARCH_OMAP2PLUS
diff --git a/drivers/cpufreq/Makefile b/drivers/cpufreq/Makefile
index 9f5a8045f36d..b7e78f063c4f 100644
--- a/drivers/cpufreq/Makefile
+++ b/drivers/cpufreq/Makefile
@@ -77,6 +77,7 @@ obj-$(CONFIG_ARM_SPEAR_CPUFREQ) += spear-cpufreq.o
77obj-$(CONFIG_ARM_STI_CPUFREQ) += sti-cpufreq.o 77obj-$(CONFIG_ARM_STI_CPUFREQ) += sti-cpufreq.o
78obj-$(CONFIG_ARM_TEGRA20_CPUFREQ) += tegra20-cpufreq.o 78obj-$(CONFIG_ARM_TEGRA20_CPUFREQ) += tegra20-cpufreq.o
79obj-$(CONFIG_ARM_TEGRA124_CPUFREQ) += tegra124-cpufreq.o 79obj-$(CONFIG_ARM_TEGRA124_CPUFREQ) += tegra124-cpufreq.o
80obj-$(CONFIG_ARM_TEGRA186_CPUFREQ) += tegra186-cpufreq.o
80obj-$(CONFIG_ARM_TI_CPUFREQ) += ti-cpufreq.o 81obj-$(CONFIG_ARM_TI_CPUFREQ) += ti-cpufreq.o
81obj-$(CONFIG_ARM_VEXPRESS_SPC_CPUFREQ) += vexpress-spc-cpufreq.o 82obj-$(CONFIG_ARM_VEXPRESS_SPC_CPUFREQ) += vexpress-spc-cpufreq.o
82obj-$(CONFIG_ACPI_CPPC_CPUFREQ) += cppc_cpufreq.o 83obj-$(CONFIG_ACPI_CPPC_CPUFREQ) += cppc_cpufreq.o
diff --git a/drivers/cpufreq/tegra186-cpufreq.c b/drivers/cpufreq/tegra186-cpufreq.c
new file mode 100644
index 000000000000..fe7875311d62
--- /dev/null
+++ b/drivers/cpufreq/tegra186-cpufreq.c
@@ -0,0 +1,275 @@
1/*
2 * Copyright (c) 2017, NVIDIA CORPORATION. All rights reserved
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms and conditions of the GNU General Public License,
6 * version 2, as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope it will be useful, but WITHOUT
9 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
10 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
11 * more details.
12 */
13
14#include <linux/cpufreq.h>
15#include <linux/dma-mapping.h>
16#include <linux/module.h>
17#include <linux/of.h>
18#include <linux/platform_device.h>
19
20#include <soc/tegra/bpmp.h>
21#include <soc/tegra/bpmp-abi.h>
22
23#define EDVD_CORE_VOLT_FREQ(core) (0x20 + (core) * 0x4)
24#define EDVD_CORE_VOLT_FREQ_F_SHIFT 0
25#define EDVD_CORE_VOLT_FREQ_V_SHIFT 16
26
27struct tegra186_cpufreq_cluster_info {
28 unsigned long offset;
29 int cpus[4];
30 unsigned int bpmp_cluster_id;
31};
32
33#define NO_CPU -1
34static const struct tegra186_cpufreq_cluster_info tegra186_clusters[] = {
35 /* Denver cluster */
36 {
37 .offset = SZ_64K * 7,
38 .cpus = { 1, 2, NO_CPU, NO_CPU },
39 .bpmp_cluster_id = 0,
40 },
41 /* A57 cluster */
42 {
43 .offset = SZ_64K * 6,
44 .cpus = { 0, 3, 4, 5 },
45 .bpmp_cluster_id = 1,
46 },
47};
48
49struct tegra186_cpufreq_cluster {
50 const struct tegra186_cpufreq_cluster_info *info;
51 struct cpufreq_frequency_table *table;
52};
53
54struct tegra186_cpufreq_data {
55 void __iomem *regs;
56
57 size_t num_clusters;
58 struct tegra186_cpufreq_cluster *clusters;
59};
60
61static int tegra186_cpufreq_init(struct cpufreq_policy *policy)
62{
63 struct tegra186_cpufreq_data *data = cpufreq_get_driver_data();
64 unsigned int i;
65
66 for (i = 0; i < data->num_clusters; i++) {
67 struct tegra186_cpufreq_cluster *cluster = &data->clusters[i];
68 const struct tegra186_cpufreq_cluster_info *info =
69 cluster->info;
70 int core;
71
72 for (core = 0; core < ARRAY_SIZE(info->cpus); core++) {
73 if (info->cpus[core] == policy->cpu)
74 break;
75 }
76 if (core == ARRAY_SIZE(info->cpus))
77 continue;
78
79 policy->driver_data =
80 data->regs + info->offset + EDVD_CORE_VOLT_FREQ(core);
81 cpufreq_table_validate_and_show(policy, cluster->table);
82 }
83
84 policy->cpuinfo.transition_latency = 300 * 1000;
85
86 return 0;
87}
88
89static int tegra186_cpufreq_set_target(struct cpufreq_policy *policy,
90 unsigned int index)
91{
92 struct cpufreq_frequency_table *tbl = policy->freq_table + index;
93 void __iomem *edvd_reg = policy->driver_data;
94 u32 edvd_val = tbl->driver_data;
95
96 writel(edvd_val, edvd_reg);
97
98 return 0;
99}
100
101static struct cpufreq_driver tegra186_cpufreq_driver = {
102 .name = "tegra186",
103 .flags = CPUFREQ_STICKY | CPUFREQ_HAVE_GOVERNOR_PER_POLICY,
104 .verify = cpufreq_generic_frequency_table_verify,
105 .target_index = tegra186_cpufreq_set_target,
106 .init = tegra186_cpufreq_init,
107 .attr = cpufreq_generic_attr,
108};
109
110static struct cpufreq_frequency_table *init_vhint_table(
111 struct platform_device *pdev, struct tegra_bpmp *bpmp,
112 unsigned int cluster_id)
113{
114 struct cpufreq_frequency_table *table;
115 struct mrq_cpu_vhint_request req;
116 struct tegra_bpmp_message msg;
117 struct cpu_vhint_data *data;
118 int err, i, j, num_rates = 0;
119 dma_addr_t phys;
120 void *virt;
121
122 virt = dma_alloc_coherent(bpmp->dev, sizeof(*data), &phys,
123 GFP_KERNEL | GFP_DMA32);
124 if (!virt)
125 return ERR_PTR(-ENOMEM);
126
127 data = (struct cpu_vhint_data *)virt;
128
129 memset(&req, 0, sizeof(req));
130 req.addr = phys;
131 req.cluster_id = cluster_id;
132
133 memset(&msg, 0, sizeof(msg));
134 msg.mrq = MRQ_CPU_VHINT;
135 msg.tx.data = &req;
136 msg.tx.size = sizeof(req);
137
138 err = tegra_bpmp_transfer(bpmp, &msg);
139 if (err) {
140 table = ERR_PTR(err);
141 goto free;
142 }
143
144 for (i = data->vfloor; i <= data->vceil; i++) {
145 u16 ndiv = data->ndiv[i];
146
147 if (ndiv < data->ndiv_min || ndiv > data->ndiv_max)
148 continue;
149
150 /* Only store lowest voltage index for each rate */
151 if (i > 0 && ndiv == data->ndiv[i - 1])
152 continue;
153
154 num_rates++;
155 }
156
157 table = devm_kcalloc(&pdev->dev, num_rates + 1, sizeof(*table),
158 GFP_KERNEL);
159 if (!table) {
160 table = ERR_PTR(-ENOMEM);
161 goto free;
162 }
163
164 for (i = data->vfloor, j = 0; i <= data->vceil; i++) {
165 struct cpufreq_frequency_table *point;
166 u16 ndiv = data->ndiv[i];
167 u32 edvd_val = 0;
168
169 if (ndiv < data->ndiv_min || ndiv > data->ndiv_max)
170 continue;
171
172 /* Only store lowest voltage index for each rate */
173 if (i > 0 && ndiv == data->ndiv[i - 1])
174 continue;
175
176 edvd_val |= i << EDVD_CORE_VOLT_FREQ_V_SHIFT;
177 edvd_val |= ndiv << EDVD_CORE_VOLT_FREQ_F_SHIFT;
178
179 point = &table[j++];
180 point->driver_data = edvd_val;
181 point->frequency = data->ref_clk_hz * ndiv / data->pdiv /
182 data->mdiv / 1000;
183 }
184
185 table[j].frequency = CPUFREQ_TABLE_END;
186
187free:
188 dma_free_coherent(bpmp->dev, sizeof(*data), virt, phys);
189
190 return table;
191}
192
193static int tegra186_cpufreq_probe(struct platform_device *pdev)
194{
195 struct tegra186_cpufreq_data *data;
196 struct tegra_bpmp *bpmp;
197 struct resource *res;
198 unsigned int i = 0, err;
199
200 data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
201 if (!data)
202 return -ENOMEM;
203
204 data->clusters = devm_kcalloc(&pdev->dev, ARRAY_SIZE(tegra186_clusters),
205 sizeof(*data->clusters), GFP_KERNEL);
206 if (!data->clusters)
207 return -ENOMEM;
208
209 data->num_clusters = ARRAY_SIZE(tegra186_clusters);
210
211 bpmp = tegra_bpmp_get(&pdev->dev);
212 if (IS_ERR(bpmp))
213 return PTR_ERR(bpmp);
214
215 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
216 data->regs = devm_ioremap_resource(&pdev->dev, res);
217 if (IS_ERR(data->regs)) {
218 err = PTR_ERR(data->regs);
219 goto put_bpmp;
220 }
221
222 for (i = 0; i < data->num_clusters; i++) {
223 struct tegra186_cpufreq_cluster *cluster = &data->clusters[i];
224
225 cluster->info = &tegra186_clusters[i];
226 cluster->table = init_vhint_table(
227 pdev, bpmp, cluster->info->bpmp_cluster_id);
228 if (IS_ERR(cluster->table)) {
229 err = PTR_ERR(cluster->table);
230 goto put_bpmp;
231 }
232 }
233
234 tegra_bpmp_put(bpmp);
235
236 tegra186_cpufreq_driver.driver_data = data;
237
238 err = cpufreq_register_driver(&tegra186_cpufreq_driver);
239 if (err)
240 return err;
241
242 return 0;
243
244put_bpmp:
245 tegra_bpmp_put(bpmp);
246
247 return err;
248}
249
250static int tegra186_cpufreq_remove(struct platform_device *pdev)
251{
252 cpufreq_unregister_driver(&tegra186_cpufreq_driver);
253
254 return 0;
255}
256
257static const struct of_device_id tegra186_cpufreq_of_match[] = {
258 { .compatible = "nvidia,tegra186-ccplex-cluster", },
259 { }
260};
261MODULE_DEVICE_TABLE(of, tegra186_cpufreq_of_match);
262
263static struct platform_driver tegra186_cpufreq_platform_driver = {
264 .driver = {
265 .name = "tegra186-cpufreq",
266 .of_match_table = tegra186_cpufreq_of_match,
267 },
268 .probe = tegra186_cpufreq_probe,
269 .remove = tegra186_cpufreq_remove,
270};
271module_platform_driver(tegra186_cpufreq_platform_driver);
272
273MODULE_AUTHOR("Mikko Perttunen <mperttunen@nvidia.com>");
274MODULE_DESCRIPTION("NVIDIA Tegra186 cpufreq driver");
275MODULE_LICENSE("GPL v2");