]>
Commit | Line | Data |
---|---|---|
52d15dd2 TR |
1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* | |
3 | * Copyright (C) 2019 NVIDIA CORPORATION. All rights reserved. | |
4 | */ | |
5 | ||
6 | #include <linux/clk.h> | |
7 | #include <linux/debugfs.h> | |
8 | #include <linux/module.h> | |
9 | #include <linux/mod_devicetable.h> | |
9a38cb27 | 10 | #include <linux/of_platform.h> |
52d15dd2 TR |
11 | #include <linux/platform_device.h> |
12 | ||
13 | #include <soc/tegra/bpmp.h> | |
9a38cb27 | 14 | #include "mc.h" |
52d15dd2 TR |
15 | |
16 | struct tegra186_emc_dvfs { | |
17 | unsigned long latency; | |
18 | unsigned long rate; | |
19 | }; | |
20 | ||
21 | struct tegra186_emc { | |
22 | struct tegra_bpmp *bpmp; | |
23 | struct device *dev; | |
24 | struct clk *clk; | |
25 | ||
26 | struct tegra186_emc_dvfs *dvfs; | |
27 | unsigned int num_dvfs; | |
28 | ||
29 | struct { | |
30 | struct dentry *root; | |
31 | unsigned long min_rate; | |
32 | unsigned long max_rate; | |
33 | } debugfs; | |
9a38cb27 SG |
34 | |
35 | struct icc_provider provider; | |
52d15dd2 TR |
36 | }; |
37 | ||
9a38cb27 SG |
38 | static inline struct tegra186_emc *to_tegra186_emc(struct icc_provider *provider) |
39 | { | |
40 | return container_of(provider, struct tegra186_emc, provider); | |
41 | } | |
42 | ||
52d15dd2 TR |
43 | /* |
44 | * debugfs interface | |
45 | * | |
46 | * The memory controller driver exposes some files in debugfs that can be used | |
47 | * to control the EMC frequency. The top-level directory can be found here: | |
48 | * | |
49 | * /sys/kernel/debug/emc | |
50 | * | |
51 | * It contains the following files: | |
52 | * | |
53 | * - available_rates: This file contains a list of valid, space-separated | |
54 | * EMC frequencies. | |
55 | * | |
56 | * - min_rate: Writing a value to this file sets the given frequency as the | |
57 | * floor of the permitted range. If this is higher than the currently | |
58 | * configured EMC frequency, this will cause the frequency to be | |
59 | * increased so that it stays within the valid range. | |
60 | * | |
61 | * - max_rate: Similarily to the min_rate file, writing a value to this file | |
62 | * sets the given frequency as the ceiling of the permitted range. If | |
63 | * the value is lower than the currently configured EMC frequency, this | |
64 | * will cause the frequency to be decreased so that it stays within the | |
65 | * valid range. | |
66 | */ | |
67 | ||
68 | static bool tegra186_emc_validate_rate(struct tegra186_emc *emc, | |
69 | unsigned long rate) | |
70 | { | |
71 | unsigned int i; | |
72 | ||
73 | for (i = 0; i < emc->num_dvfs; i++) | |
74 | if (rate == emc->dvfs[i].rate) | |
75 | return true; | |
76 | ||
77 | return false; | |
78 | } | |
79 | ||
80 | static int tegra186_emc_debug_available_rates_show(struct seq_file *s, | |
81 | void *data) | |
82 | { | |
83 | struct tegra186_emc *emc = s->private; | |
84 | const char *prefix = ""; | |
85 | unsigned int i; | |
86 | ||
87 | for (i = 0; i < emc->num_dvfs; i++) { | |
88 | seq_printf(s, "%s%lu", prefix, emc->dvfs[i].rate); | |
89 | prefix = " "; | |
90 | } | |
91 | ||
92 | seq_puts(s, "\n"); | |
93 | ||
94 | return 0; | |
95 | } | |
58504acd | 96 | DEFINE_SHOW_ATTRIBUTE(tegra186_emc_debug_available_rates); |
52d15dd2 TR |
97 | |
98 | static int tegra186_emc_debug_min_rate_get(void *data, u64 *rate) | |
99 | { | |
100 | struct tegra186_emc *emc = data; | |
101 | ||
102 | *rate = emc->debugfs.min_rate; | |
103 | ||
104 | return 0; | |
105 | } | |
106 | ||
107 | static int tegra186_emc_debug_min_rate_set(void *data, u64 rate) | |
108 | { | |
109 | struct tegra186_emc *emc = data; | |
110 | int err; | |
111 | ||
112 | if (!tegra186_emc_validate_rate(emc, rate)) | |
113 | return -EINVAL; | |
114 | ||
115 | err = clk_set_min_rate(emc->clk, rate); | |
116 | if (err < 0) | |
117 | return err; | |
118 | ||
119 | emc->debugfs.min_rate = rate; | |
120 | ||
121 | return 0; | |
122 | } | |
123 | ||
321b36c7 JC |
124 | DEFINE_DEBUGFS_ATTRIBUTE(tegra186_emc_debug_min_rate_fops, |
125 | tegra186_emc_debug_min_rate_get, | |
126 | tegra186_emc_debug_min_rate_set, "%llu\n"); | |
52d15dd2 TR |
127 | |
128 | static int tegra186_emc_debug_max_rate_get(void *data, u64 *rate) | |
129 | { | |
130 | struct tegra186_emc *emc = data; | |
131 | ||
132 | *rate = emc->debugfs.max_rate; | |
133 | ||
134 | return 0; | |
135 | } | |
136 | ||
137 | static int tegra186_emc_debug_max_rate_set(void *data, u64 rate) | |
138 | { | |
139 | struct tegra186_emc *emc = data; | |
140 | int err; | |
141 | ||
142 | if (!tegra186_emc_validate_rate(emc, rate)) | |
143 | return -EINVAL; | |
144 | ||
145 | err = clk_set_max_rate(emc->clk, rate); | |
146 | if (err < 0) | |
147 | return err; | |
148 | ||
149 | emc->debugfs.max_rate = rate; | |
150 | ||
151 | return 0; | |
152 | } | |
153 | ||
321b36c7 JC |
154 | DEFINE_DEBUGFS_ATTRIBUTE(tegra186_emc_debug_max_rate_fops, |
155 | tegra186_emc_debug_max_rate_get, | |
156 | tegra186_emc_debug_max_rate_set, "%llu\n"); | |
52d15dd2 | 157 | |
0a7e4578 SG |
158 | static int tegra186_emc_get_emc_dvfs_latency(struct tegra186_emc *emc) |
159 | { | |
160 | struct mrq_emc_dvfs_latency_response response; | |
161 | struct tegra_bpmp_message msg; | |
162 | unsigned int i; | |
163 | int err; | |
164 | ||
165 | memset(&msg, 0, sizeof(msg)); | |
166 | msg.mrq = MRQ_EMC_DVFS_LATENCY; | |
167 | msg.tx.data = NULL; | |
168 | msg.tx.size = 0; | |
169 | msg.rx.data = &response; | |
170 | msg.rx.size = sizeof(response); | |
171 | ||
172 | err = tegra_bpmp_transfer(emc->bpmp, &msg); | |
173 | if (err < 0) { | |
174 | dev_err(emc->dev, "failed to EMC DVFS pairs: %d\n", err); | |
175 | return err; | |
176 | } | |
177 | if (msg.rx.ret < 0) { | |
178 | dev_err(emc->dev, "EMC DVFS MRQ failed: %d (BPMP error code)\n", msg.rx.ret); | |
179 | return -EINVAL; | |
180 | } | |
181 | ||
182 | emc->debugfs.min_rate = ULONG_MAX; | |
183 | emc->debugfs.max_rate = 0; | |
184 | ||
185 | emc->num_dvfs = response.num_pairs; | |
186 | ||
187 | emc->dvfs = devm_kmalloc_array(emc->dev, emc->num_dvfs, sizeof(*emc->dvfs), GFP_KERNEL); | |
188 | if (!emc->dvfs) | |
189 | return -ENOMEM; | |
190 | ||
191 | dev_dbg(emc->dev, "%u DVFS pairs:\n", emc->num_dvfs); | |
192 | ||
193 | for (i = 0; i < emc->num_dvfs; i++) { | |
194 | emc->dvfs[i].rate = response.pairs[i].freq * 1000; | |
195 | emc->dvfs[i].latency = response.pairs[i].latency; | |
196 | ||
197 | if (emc->dvfs[i].rate < emc->debugfs.min_rate) | |
198 | emc->debugfs.min_rate = emc->dvfs[i].rate; | |
199 | ||
200 | if (emc->dvfs[i].rate > emc->debugfs.max_rate) | |
201 | emc->debugfs.max_rate = emc->dvfs[i].rate; | |
202 | ||
203 | dev_dbg(emc->dev, " %2u: %lu Hz -> %lu us\n", i, | |
204 | emc->dvfs[i].rate, emc->dvfs[i].latency); | |
205 | } | |
206 | ||
207 | err = clk_set_rate_range(emc->clk, emc->debugfs.min_rate, emc->debugfs.max_rate); | |
208 | if (err < 0) { | |
209 | dev_err(emc->dev, "failed to set rate range [%lu-%lu] for %pC\n", | |
210 | emc->debugfs.min_rate, emc->debugfs.max_rate, emc->clk); | |
211 | return err; | |
212 | } | |
213 | ||
214 | emc->debugfs.root = debugfs_create_dir("emc", NULL); | |
6e1547f9 TR |
215 | debugfs_create_file("available_rates", 0444, emc->debugfs.root, emc, |
216 | &tegra186_emc_debug_available_rates_fops); | |
217 | debugfs_create_file("min_rate", 0644, emc->debugfs.root, emc, | |
218 | &tegra186_emc_debug_min_rate_fops); | |
219 | debugfs_create_file("max_rate", 0644, emc->debugfs.root, emc, | |
220 | &tegra186_emc_debug_max_rate_fops); | |
0a7e4578 SG |
221 | |
222 | return 0; | |
223 | } | |
224 | ||
9a38cb27 SG |
225 | /* |
226 | * tegra_emc_icc_set_bw() - Set BW api for EMC provider | |
227 | * @src: ICC node for External Memory Controller (EMC) | |
228 | * @dst: ICC node for External Memory (DRAM) | |
229 | * | |
230 | * Do nothing here as info to BPMP-FW is now passed in the BW set function | |
231 | * of the MC driver. BPMP-FW sets the final Freq based on the passed values. | |
232 | */ | |
233 | static int tegra_emc_icc_set_bw(struct icc_node *src, struct icc_node *dst) | |
234 | { | |
235 | return 0; | |
236 | } | |
237 | ||
238 | static struct icc_node * | |
239 | tegra_emc_of_icc_xlate(struct of_phandle_args *spec, void *data) | |
240 | { | |
241 | struct icc_provider *provider = data; | |
242 | struct icc_node *node; | |
243 | ||
244 | /* External Memory is the only possible ICC route */ | |
245 | list_for_each_entry(node, &provider->nodes, node_list) { | |
246 | if (node->id != TEGRA_ICC_EMEM) | |
247 | continue; | |
248 | ||
249 | return node; | |
250 | } | |
251 | ||
252 | return ERR_PTR(-EPROBE_DEFER); | |
253 | } | |
254 | ||
255 | static int tegra_emc_icc_get_init_bw(struct icc_node *node, u32 *avg, u32 *peak) | |
256 | { | |
257 | *avg = 0; | |
258 | *peak = 0; | |
259 | ||
260 | return 0; | |
261 | } | |
262 | ||
263 | static int tegra_emc_interconnect_init(struct tegra186_emc *emc) | |
264 | { | |
265 | struct tegra_mc *mc = dev_get_drvdata(emc->dev->parent); | |
266 | const struct tegra_mc_soc *soc = mc->soc; | |
267 | struct icc_node *node; | |
268 | int err; | |
269 | ||
270 | emc->provider.dev = emc->dev; | |
271 | emc->provider.set = tegra_emc_icc_set_bw; | |
272 | emc->provider.data = &emc->provider; | |
273 | emc->provider.aggregate = soc->icc_ops->aggregate; | |
274 | emc->provider.xlate = tegra_emc_of_icc_xlate; | |
275 | emc->provider.get_bw = tegra_emc_icc_get_init_bw; | |
276 | ||
277 | icc_provider_init(&emc->provider); | |
278 | ||
279 | /* create External Memory Controller node */ | |
280 | node = icc_node_create(TEGRA_ICC_EMC); | |
281 | if (IS_ERR(node)) { | |
282 | err = PTR_ERR(node); | |
283 | goto err_msg; | |
284 | } | |
285 | ||
286 | node->name = "External Memory Controller"; | |
287 | icc_node_add(node, &emc->provider); | |
288 | ||
289 | /* link External Memory Controller to External Memory (DRAM) */ | |
290 | err = icc_link_create(node, TEGRA_ICC_EMEM); | |
291 | if (err) | |
292 | goto remove_nodes; | |
293 | ||
294 | /* create External Memory node */ | |
295 | node = icc_node_create(TEGRA_ICC_EMEM); | |
296 | if (IS_ERR(node)) { | |
297 | err = PTR_ERR(node); | |
298 | goto remove_nodes; | |
299 | } | |
300 | ||
301 | node->name = "External Memory (DRAM)"; | |
302 | icc_node_add(node, &emc->provider); | |
303 | ||
304 | err = icc_provider_register(&emc->provider); | |
305 | if (err) | |
306 | goto remove_nodes; | |
307 | ||
308 | return 0; | |
309 | ||
310 | remove_nodes: | |
311 | icc_nodes_remove(&emc->provider); | |
312 | err_msg: | |
313 | dev_err(emc->dev, "failed to initialize ICC: %d\n", err); | |
314 | ||
315 | return err; | |
316 | } | |
317 | ||
52d15dd2 TR |
318 | static int tegra186_emc_probe(struct platform_device *pdev) |
319 | { | |
9a38cb27 | 320 | struct tegra_mc *mc = dev_get_drvdata(pdev->dev.parent); |
52d15dd2 | 321 | struct tegra186_emc *emc; |
52d15dd2 TR |
322 | int err; |
323 | ||
324 | emc = devm_kzalloc(&pdev->dev, sizeof(*emc), GFP_KERNEL); | |
325 | if (!emc) | |
326 | return -ENOMEM; | |
327 | ||
328 | emc->bpmp = tegra_bpmp_get(&pdev->dev); | |
25f2f5e5 KK |
329 | if (IS_ERR(emc->bpmp)) |
330 | return dev_err_probe(&pdev->dev, PTR_ERR(emc->bpmp), "failed to get BPMP\n"); | |
52d15dd2 TR |
331 | |
332 | emc->clk = devm_clk_get(&pdev->dev, "emc"); | |
333 | if (IS_ERR(emc->clk)) { | |
334 | err = PTR_ERR(emc->clk); | |
335 | dev_err(&pdev->dev, "failed to get EMC clock: %d\n", err); | |
c3d4eb3b | 336 | goto put_bpmp; |
52d15dd2 TR |
337 | } |
338 | ||
339 | platform_set_drvdata(pdev, emc); | |
340 | emc->dev = &pdev->dev; | |
341 | ||
0a7e4578 SG |
342 | if (tegra_bpmp_mrq_is_supported(emc->bpmp, MRQ_EMC_DVFS_LATENCY)) { |
343 | err = tegra186_emc_get_emc_dvfs_latency(emc); | |
344 | if (err) | |
345 | goto put_bpmp; | |
52d15dd2 TR |
346 | } |
347 | ||
9a38cb27 SG |
348 | if (mc && mc->soc->icc_ops) { |
349 | if (tegra_bpmp_mrq_is_supported(emc->bpmp, MRQ_BWMGR_INT)) { | |
350 | mc->bwmgr_mrq_supported = true; | |
351 | ||
352 | /* | |
353 | * MC driver probe can't get BPMP reference as it gets probed | |
354 | * earlier than BPMP. So, save the BPMP ref got from the EMC | |
355 | * DT node in the mc->bpmp and use it in MC's icc_set hook. | |
356 | */ | |
357 | mc->bpmp = emc->bpmp; | |
358 | barrier(); | |
359 | } | |
360 | ||
361 | /* | |
362 | * Initialize the ICC even if BPMP-FW doesn't support 'MRQ_BWMGR_INT'. | |
363 | * Use the flag 'mc->bwmgr_mrq_supported' within MC driver and return | |
364 | * EINVAL instead of passing the request to BPMP-FW later when the BW | |
365 | * request is made by client with 'icc_set_bw()' call. | |
366 | */ | |
367 | err = tegra_emc_interconnect_init(emc); | |
368 | if (err) { | |
369 | mc->bpmp = NULL; | |
370 | goto put_bpmp; | |
371 | } | |
372 | } | |
373 | ||
52d15dd2 | 374 | return 0; |
c3d4eb3b CJ |
375 | |
376 | put_bpmp: | |
377 | tegra_bpmp_put(emc->bpmp); | |
378 | return err; | |
52d15dd2 TR |
379 | } |
380 | ||
381 | static int tegra186_emc_remove(struct platform_device *pdev) | |
382 | { | |
9a38cb27 | 383 | struct tegra_mc *mc = dev_get_drvdata(pdev->dev.parent); |
52d15dd2 TR |
384 | struct tegra186_emc *emc = platform_get_drvdata(pdev); |
385 | ||
386 | debugfs_remove_recursive(emc->debugfs.root); | |
9a38cb27 SG |
387 | |
388 | mc->bpmp = NULL; | |
52d15dd2 TR |
389 | tegra_bpmp_put(emc->bpmp); |
390 | ||
391 | return 0; | |
392 | } | |
393 | ||
394 | static const struct of_device_id tegra186_emc_of_match[] = { | |
0454efbc | 395 | #if defined(CONFIG_ARCH_TEGRA_186_SOC) |
52d15dd2 | 396 | { .compatible = "nvidia,tegra186-emc" }, |
a127e690 | 397 | #endif |
0454efbc | 398 | #if defined(CONFIG_ARCH_TEGRA_194_SOC) |
a127e690 | 399 | { .compatible = "nvidia,tegra194-emc" }, |
72c81bb6 TR |
400 | #endif |
401 | #if defined(CONFIG_ARCH_TEGRA_234_SOC) | |
402 | { .compatible = "nvidia,tegra234-emc" }, | |
4e04b886 | 403 | #endif |
52d15dd2 TR |
404 | { /* sentinel */ } |
405 | }; | |
406 | MODULE_DEVICE_TABLE(of, tegra186_emc_of_match); | |
407 | ||
408 | static struct platform_driver tegra186_emc_driver = { | |
409 | .driver = { | |
410 | .name = "tegra186-emc", | |
411 | .of_match_table = tegra186_emc_of_match, | |
412 | .suppress_bind_attrs = true, | |
9a38cb27 | 413 | .sync_state = icc_sync_state, |
52d15dd2 TR |
414 | }, |
415 | .probe = tegra186_emc_probe, | |
416 | .remove = tegra186_emc_remove, | |
417 | }; | |
418 | module_platform_driver(tegra186_emc_driver); | |
419 | ||
420 | MODULE_AUTHOR("Thierry Reding <treding@nvidia.com>"); | |
421 | MODULE_DESCRIPTION("NVIDIA Tegra186 External Memory Controller driver"); |