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