1
2
3
4
5
6#include <linux/cpufreq.h>
7#include <linux/dma-mapping.h>
8#include <linux/module.h>
9#include <linux/of.h>
10#include <linux/platform_device.h>
11
12#include <soc/tegra/bpmp.h>
13#include <soc/tegra/bpmp-abi.h>
14
15#define EDVD_CORE_VOLT_FREQ(core) (0x20 + (core) * 0x4)
16#define EDVD_CORE_VOLT_FREQ_F_SHIFT 0
17#define EDVD_CORE_VOLT_FREQ_F_MASK 0xffff
18#define EDVD_CORE_VOLT_FREQ_V_SHIFT 16
19
20struct tegra186_cpufreq_cluster_info {
21 unsigned long offset;
22 int cpus[4];
23 unsigned int bpmp_cluster_id;
24};
25
26#define NO_CPU -1
27static const struct tegra186_cpufreq_cluster_info tegra186_clusters[] = {
28
29 {
30 .offset = SZ_64K * 7,
31 .cpus = { 1, 2, NO_CPU, NO_CPU },
32 .bpmp_cluster_id = 0,
33 },
34
35 {
36 .offset = SZ_64K * 6,
37 .cpus = { 0, 3, 4, 5 },
38 .bpmp_cluster_id = 1,
39 },
40};
41
42struct tegra186_cpufreq_cluster {
43 const struct tegra186_cpufreq_cluster_info *info;
44 struct cpufreq_frequency_table *table;
45 u32 ref_clk_khz;
46 u32 div;
47};
48
49struct tegra186_cpufreq_data {
50 void __iomem *regs;
51
52 size_t num_clusters;
53 struct tegra186_cpufreq_cluster *clusters;
54};
55
56static int tegra186_cpufreq_init(struct cpufreq_policy *policy)
57{
58 struct tegra186_cpufreq_data *data = cpufreq_get_driver_data();
59 unsigned int i;
60
61 for (i = 0; i < data->num_clusters; i++) {
62 struct tegra186_cpufreq_cluster *cluster = &data->clusters[i];
63 const struct tegra186_cpufreq_cluster_info *info =
64 cluster->info;
65 int core;
66
67 for (core = 0; core < ARRAY_SIZE(info->cpus); core++) {
68 if (info->cpus[core] == policy->cpu)
69 break;
70 }
71 if (core == ARRAY_SIZE(info->cpus))
72 continue;
73
74 policy->driver_data =
75 data->regs + info->offset + EDVD_CORE_VOLT_FREQ(core);
76 policy->freq_table = cluster->table;
77 break;
78 }
79
80 policy->cpuinfo.transition_latency = 300 * 1000;
81
82 return 0;
83}
84
85static int tegra186_cpufreq_set_target(struct cpufreq_policy *policy,
86 unsigned int index)
87{
88 struct cpufreq_frequency_table *tbl = policy->freq_table + index;
89 void __iomem *edvd_reg = policy->driver_data;
90 u32 edvd_val = tbl->driver_data;
91
92 writel(edvd_val, edvd_reg);
93
94 return 0;
95}
96
97static unsigned int tegra186_cpufreq_get(unsigned int cpu)
98{
99 struct tegra186_cpufreq_data *data = cpufreq_get_driver_data();
100 struct cpufreq_policy *policy;
101 void __iomem *edvd_reg;
102 unsigned int i, freq = 0;
103 u32 ndiv;
104
105 policy = cpufreq_cpu_get(cpu);
106 if (!policy)
107 return 0;
108
109 edvd_reg = policy->driver_data;
110 ndiv = readl(edvd_reg) & EDVD_CORE_VOLT_FREQ_F_MASK;
111
112 for (i = 0; i < data->num_clusters; i++) {
113 struct tegra186_cpufreq_cluster *cluster = &data->clusters[i];
114 int core;
115
116 for (core = 0; core < ARRAY_SIZE(cluster->info->cpus); core++) {
117 if (cluster->info->cpus[core] != policy->cpu)
118 continue;
119
120 freq = (cluster->ref_clk_khz * ndiv) / cluster->div;
121 goto out;
122 }
123 }
124
125out:
126 cpufreq_cpu_put(policy);
127
128 return freq;
129}
130
131static struct cpufreq_driver tegra186_cpufreq_driver = {
132 .name = "tegra186",
133 .flags = CPUFREQ_STICKY | CPUFREQ_HAVE_GOVERNOR_PER_POLICY |
134 CPUFREQ_NEED_INITIAL_FREQ_CHECK,
135 .get = tegra186_cpufreq_get,
136 .verify = cpufreq_generic_frequency_table_verify,
137 .target_index = tegra186_cpufreq_set_target,
138 .init = tegra186_cpufreq_init,
139 .attr = cpufreq_generic_attr,
140};
141
142static struct cpufreq_frequency_table *init_vhint_table(
143 struct platform_device *pdev, struct tegra_bpmp *bpmp,
144 struct tegra186_cpufreq_cluster *cluster)
145{
146 struct cpufreq_frequency_table *table;
147 struct mrq_cpu_vhint_request req;
148 struct tegra_bpmp_message msg;
149 struct cpu_vhint_data *data;
150 int err, i, j, num_rates = 0;
151 dma_addr_t phys;
152 void *virt;
153
154 virt = dma_alloc_coherent(bpmp->dev, sizeof(*data), &phys,
155 GFP_KERNEL);
156 if (!virt)
157 return ERR_PTR(-ENOMEM);
158
159 data = (struct cpu_vhint_data *)virt;
160
161 memset(&req, 0, sizeof(req));
162 req.addr = phys;
163 req.cluster_id = cluster->info->bpmp_cluster_id;
164
165 memset(&msg, 0, sizeof(msg));
166 msg.mrq = MRQ_CPU_VHINT;
167 msg.tx.data = &req;
168 msg.tx.size = sizeof(req);
169
170 err = tegra_bpmp_transfer(bpmp, &msg);
171 if (err) {
172 table = ERR_PTR(err);
173 goto free;
174 }
175
176 for (i = data->vfloor; i <= data->vceil; i++) {
177 u16 ndiv = data->ndiv[i];
178
179 if (ndiv < data->ndiv_min || ndiv > data->ndiv_max)
180 continue;
181
182
183 if (i > 0 && ndiv == data->ndiv[i - 1])
184 continue;
185
186 num_rates++;
187 }
188
189 table = devm_kcalloc(&pdev->dev, num_rates + 1, sizeof(*table),
190 GFP_KERNEL);
191 if (!table) {
192 table = ERR_PTR(-ENOMEM);
193 goto free;
194 }
195
196 cluster->ref_clk_khz = data->ref_clk_hz / 1000;
197 cluster->div = data->pdiv * data->mdiv;
198
199 for (i = data->vfloor, j = 0; i <= data->vceil; i++) {
200 struct cpufreq_frequency_table *point;
201 u16 ndiv = data->ndiv[i];
202 u32 edvd_val = 0;
203
204 if (ndiv < data->ndiv_min || ndiv > data->ndiv_max)
205 continue;
206
207
208 if (i > 0 && ndiv == data->ndiv[i - 1])
209 continue;
210
211 edvd_val |= i << EDVD_CORE_VOLT_FREQ_V_SHIFT;
212 edvd_val |= ndiv << EDVD_CORE_VOLT_FREQ_F_SHIFT;
213
214 point = &table[j++];
215 point->driver_data = edvd_val;
216 point->frequency = (cluster->ref_clk_khz * ndiv) / cluster->div;
217 }
218
219 table[j].frequency = CPUFREQ_TABLE_END;
220
221free:
222 dma_free_coherent(bpmp->dev, sizeof(*data), virt, phys);
223
224 return table;
225}
226
227static int tegra186_cpufreq_probe(struct platform_device *pdev)
228{
229 struct tegra186_cpufreq_data *data;
230 struct tegra_bpmp *bpmp;
231 unsigned int i = 0, err;
232
233 data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
234 if (!data)
235 return -ENOMEM;
236
237 data->clusters = devm_kcalloc(&pdev->dev, ARRAY_SIZE(tegra186_clusters),
238 sizeof(*data->clusters), GFP_KERNEL);
239 if (!data->clusters)
240 return -ENOMEM;
241
242 data->num_clusters = ARRAY_SIZE(tegra186_clusters);
243
244 bpmp = tegra_bpmp_get(&pdev->dev);
245 if (IS_ERR(bpmp))
246 return PTR_ERR(bpmp);
247
248 data->regs = devm_platform_ioremap_resource(pdev, 0);
249 if (IS_ERR(data->regs)) {
250 err = PTR_ERR(data->regs);
251 goto put_bpmp;
252 }
253
254 for (i = 0; i < data->num_clusters; i++) {
255 struct tegra186_cpufreq_cluster *cluster = &data->clusters[i];
256
257 cluster->info = &tegra186_clusters[i];
258 cluster->table = init_vhint_table(pdev, bpmp, cluster);
259 if (IS_ERR(cluster->table)) {
260 err = PTR_ERR(cluster->table);
261 goto put_bpmp;
262 }
263 }
264
265 tegra186_cpufreq_driver.driver_data = data;
266
267 err = cpufreq_register_driver(&tegra186_cpufreq_driver);
268
269put_bpmp:
270 tegra_bpmp_put(bpmp);
271
272 return err;
273}
274
275static int tegra186_cpufreq_remove(struct platform_device *pdev)
276{
277 cpufreq_unregister_driver(&tegra186_cpufreq_driver);
278
279 return 0;
280}
281
282static const struct of_device_id tegra186_cpufreq_of_match[] = {
283 { .compatible = "nvidia,tegra186-ccplex-cluster", },
284 { }
285};
286MODULE_DEVICE_TABLE(of, tegra186_cpufreq_of_match);
287
288static struct platform_driver tegra186_cpufreq_platform_driver = {
289 .driver = {
290 .name = "tegra186-cpufreq",
291 .of_match_table = tegra186_cpufreq_of_match,
292 },
293 .probe = tegra186_cpufreq_probe,
294 .remove = tegra186_cpufreq_remove,
295};
296module_platform_driver(tegra186_cpufreq_platform_driver);
297
298MODULE_AUTHOR("Mikko Perttunen <mperttunen@nvidia.com>");
299MODULE_DESCRIPTION("NVIDIA Tegra186 cpufreq driver");
300MODULE_LICENSE("GPL v2");
301