diff options
Diffstat (limited to 'drivers/gpu/nvgpu/common/linux/platform_gp10b_tegra.c')
-rw-r--r-- | drivers/gpu/nvgpu/common/linux/platform_gp10b_tegra.c | 777 |
1 files changed, 777 insertions, 0 deletions
diff --git a/drivers/gpu/nvgpu/common/linux/platform_gp10b_tegra.c b/drivers/gpu/nvgpu/common/linux/platform_gp10b_tegra.c new file mode 100644 index 00000000..b944844e --- /dev/null +++ b/drivers/gpu/nvgpu/common/linux/platform_gp10b_tegra.c | |||
@@ -0,0 +1,777 @@ | |||
1 | /* | ||
2 | * GP10B Tegra Platform Interface | ||
3 | * | ||
4 | * Copyright (c) 2014-2017, NVIDIA CORPORATION. All rights reserved. | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or modify it | ||
7 | * under the terms and conditions of the GNU General Public License, | ||
8 | * version 2, as published by the Free Software Foundation. | ||
9 | * | ||
10 | * This program is distributed in the hope it will be useful, but WITHOUT | ||
11 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
12 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
13 | * more details. | ||
14 | */ | ||
15 | |||
16 | #include <linux/of_platform.h> | ||
17 | #include <linux/debugfs.h> | ||
18 | #include <linux/dma-buf.h> | ||
19 | #include <linux/nvmap.h> | ||
20 | #include <linux/reset.h> | ||
21 | #include <linux/platform/tegra/emc_bwmgr.h> | ||
22 | |||
23 | #include <uapi/linux/nvgpu.h> | ||
24 | |||
25 | #include <soc/tegra/tegra_bpmp.h> | ||
26 | #include <soc/tegra/tegra_powergate.h> | ||
27 | |||
28 | #include <nvgpu/kmem.h> | ||
29 | #include <nvgpu/bug.h> | ||
30 | #include <nvgpu/enabled.h> | ||
31 | #include <nvgpu/hashtable.h> | ||
32 | #include <nvgpu/nvhost.h> | ||
33 | |||
34 | #include "os_linux.h" | ||
35 | |||
36 | #include "clk.h" | ||
37 | |||
38 | #include "gk20a/gk20a.h" | ||
39 | |||
40 | #include "platform_gk20a.h" | ||
41 | #include "platform_gk20a_tegra.h" | ||
42 | #include "gp10b/platform_gp10b.h" | ||
43 | #include "platform_gp10b_tegra.h" | ||
44 | #include "scale.h" | ||
45 | |||
46 | /* Select every GP10B_FREQ_SELECT_STEP'th frequency from h/w table */ | ||
47 | #define GP10B_FREQ_SELECT_STEP 8 | ||
48 | /* Max number of freq supported in h/w */ | ||
49 | #define GP10B_MAX_SUPPORTED_FREQS 120 | ||
50 | static unsigned long | ||
51 | gp10b_freq_table[GP10B_MAX_SUPPORTED_FREQS / GP10B_FREQ_SELECT_STEP]; | ||
52 | |||
53 | #define TEGRA_GP10B_BW_PER_FREQ 64 | ||
54 | #define TEGRA_DDR4_BW_PER_FREQ 16 | ||
55 | |||
56 | #define EMC_BW_RATIO (TEGRA_GP10B_BW_PER_FREQ / TEGRA_DDR4_BW_PER_FREQ) | ||
57 | |||
58 | static struct { | ||
59 | char *name; | ||
60 | unsigned long default_rate; | ||
61 | } tegra_gp10b_clocks[] = { | ||
62 | {"gpu", 1000000000}, | ||
63 | {"gpu_sys", 204000000} }; | ||
64 | |||
65 | static void gr_gp10b_remove_sysfs(struct device *dev); | ||
66 | |||
67 | /* | ||
68 | * gp10b_tegra_get_clocks() | ||
69 | * | ||
70 | * This function finds clocks in tegra platform and populates | ||
71 | * the clock information to gp10b platform data. | ||
72 | */ | ||
73 | |||
74 | int gp10b_tegra_get_clocks(struct device *dev) | ||
75 | { | ||
76 | struct gk20a_platform *platform = dev_get_drvdata(dev); | ||
77 | unsigned int i; | ||
78 | |||
79 | platform->num_clks = 0; | ||
80 | for (i = 0; i < ARRAY_SIZE(tegra_gp10b_clocks); i++) { | ||
81 | long rate = tegra_gp10b_clocks[i].default_rate; | ||
82 | struct clk *c; | ||
83 | |||
84 | c = clk_get(dev, tegra_gp10b_clocks[i].name); | ||
85 | if (IS_ERR(c)) { | ||
86 | nvgpu_err(platform->g, "cannot get clock %s", | ||
87 | tegra_gp10b_clocks[i].name); | ||
88 | } else { | ||
89 | clk_set_rate(c, rate); | ||
90 | platform->clk[i] = c; | ||
91 | } | ||
92 | } | ||
93 | platform->num_clks = i; | ||
94 | |||
95 | return 0; | ||
96 | } | ||
97 | |||
98 | static void gp10b_tegra_scale_init(struct device *dev) | ||
99 | { | ||
100 | struct gk20a_platform *platform = gk20a_get_platform(dev); | ||
101 | struct gk20a_scale_profile *profile = platform->g->scale_profile; | ||
102 | struct tegra_bwmgr_client *bwmgr_handle; | ||
103 | |||
104 | if (!profile) | ||
105 | return; | ||
106 | |||
107 | bwmgr_handle = tegra_bwmgr_register(TEGRA_BWMGR_CLIENT_GPU); | ||
108 | if (!bwmgr_handle) | ||
109 | return; | ||
110 | |||
111 | profile->private_data = (void *)bwmgr_handle; | ||
112 | } | ||
113 | |||
114 | static void gp10b_tegra_scale_exit(struct device *dev) | ||
115 | { | ||
116 | struct gk20a_platform *platform = gk20a_get_platform(dev); | ||
117 | struct gk20a_scale_profile *profile = platform->g->scale_profile; | ||
118 | |||
119 | if (profile) | ||
120 | tegra_bwmgr_unregister( | ||
121 | (struct tegra_bwmgr_client *)profile->private_data); | ||
122 | } | ||
123 | |||
124 | static int gp10b_tegra_probe(struct device *dev) | ||
125 | { | ||
126 | struct gk20a_platform *platform = dev_get_drvdata(dev); | ||
127 | #ifdef CONFIG_TEGRA_GK20A_NVHOST | ||
128 | int ret; | ||
129 | |||
130 | ret = nvgpu_get_nvhost_dev(platform->g); | ||
131 | if (ret) | ||
132 | return ret; | ||
133 | #endif | ||
134 | |||
135 | platform->bypass_smmu = !device_is_iommuable(dev); | ||
136 | platform->disable_bigpage = platform->bypass_smmu; | ||
137 | |||
138 | platform->g->gr.t18x.ctx_vars.dump_ctxsw_stats_on_channel_close | ||
139 | = false; | ||
140 | platform->g->gr.t18x.ctx_vars.dump_ctxsw_stats_on_channel_close | ||
141 | = false; | ||
142 | |||
143 | platform->g->gr.t18x.ctx_vars.force_preemption_gfxp = false; | ||
144 | platform->g->gr.t18x.ctx_vars.force_preemption_cilp = false; | ||
145 | |||
146 | gp10b_tegra_get_clocks(dev); | ||
147 | nvgpu_linux_init_clk_support(platform->g); | ||
148 | gk20a_tegra_init_secure_alloc(platform->g); | ||
149 | |||
150 | return 0; | ||
151 | } | ||
152 | |||
153 | static int gp10b_tegra_late_probe(struct device *dev) | ||
154 | { | ||
155 | /* Cause early VPR resize */ | ||
156 | gk20a_tegra_secure_page_alloc(dev); | ||
157 | |||
158 | /* Initialise tegra specific scaling quirks */ | ||
159 | gp10b_tegra_scale_init(dev); | ||
160 | return 0; | ||
161 | } | ||
162 | |||
163 | int gp10b_tegra_remove(struct device *dev) | ||
164 | { | ||
165 | gr_gp10b_remove_sysfs(dev); | ||
166 | |||
167 | /* deinitialise tegra specific scaling quirks */ | ||
168 | gp10b_tegra_scale_exit(dev); | ||
169 | |||
170 | #ifdef CONFIG_TEGRA_GK20A_NVHOST | ||
171 | nvgpu_free_nvhost_dev(get_gk20a(dev)); | ||
172 | #endif | ||
173 | |||
174 | return 0; | ||
175 | } | ||
176 | |||
177 | static bool gp10b_tegra_is_railgated(struct device *dev) | ||
178 | { | ||
179 | bool ret = false; | ||
180 | |||
181 | if (tegra_bpmp_running()) | ||
182 | ret = !tegra_powergate_is_powered(TEGRA186_POWER_DOMAIN_GPU); | ||
183 | |||
184 | return ret; | ||
185 | } | ||
186 | |||
187 | static int gp10b_tegra_railgate(struct device *dev) | ||
188 | { | ||
189 | struct gk20a_platform *platform = gk20a_get_platform(dev); | ||
190 | struct gk20a_scale_profile *profile = platform->g->scale_profile; | ||
191 | |||
192 | /* remove emc frequency floor */ | ||
193 | if (profile) | ||
194 | tegra_bwmgr_set_emc( | ||
195 | (struct tegra_bwmgr_client *)profile->private_data, | ||
196 | 0, TEGRA_BWMGR_SET_EMC_FLOOR); | ||
197 | |||
198 | if (tegra_bpmp_running() && | ||
199 | tegra_powergate_is_powered(TEGRA186_POWER_DOMAIN_GPU)) { | ||
200 | int i; | ||
201 | for (i = 0; i < platform->num_clks; i++) { | ||
202 | if (platform->clk[i]) | ||
203 | clk_disable_unprepare(platform->clk[i]); | ||
204 | } | ||
205 | tegra_powergate_partition(TEGRA186_POWER_DOMAIN_GPU); | ||
206 | } | ||
207 | return 0; | ||
208 | } | ||
209 | |||
210 | static int gp10b_tegra_unrailgate(struct device *dev) | ||
211 | { | ||
212 | int ret = 0; | ||
213 | struct gk20a_platform *platform = gk20a_get_platform(dev); | ||
214 | struct gk20a_scale_profile *profile = platform->g->scale_profile; | ||
215 | |||
216 | if (tegra_bpmp_running()) { | ||
217 | int i; | ||
218 | ret = tegra_unpowergate_partition(TEGRA186_POWER_DOMAIN_GPU); | ||
219 | for (i = 0; i < platform->num_clks; i++) { | ||
220 | if (platform->clk[i]) | ||
221 | clk_prepare_enable(platform->clk[i]); | ||
222 | } | ||
223 | } | ||
224 | |||
225 | /* to start with set emc frequency floor to max rate*/ | ||
226 | if (profile) | ||
227 | tegra_bwmgr_set_emc( | ||
228 | (struct tegra_bwmgr_client *)profile->private_data, | ||
229 | tegra_bwmgr_get_max_emc_rate(), | ||
230 | TEGRA_BWMGR_SET_EMC_FLOOR); | ||
231 | return ret; | ||
232 | } | ||
233 | |||
234 | static int gp10b_tegra_suspend(struct device *dev) | ||
235 | { | ||
236 | return 0; | ||
237 | } | ||
238 | |||
239 | int gp10b_tegra_reset_assert(struct device *dev) | ||
240 | { | ||
241 | struct gk20a_platform *platform = gk20a_get_platform(dev); | ||
242 | int ret = 0; | ||
243 | |||
244 | if (!platform->reset_control) | ||
245 | return -EINVAL; | ||
246 | |||
247 | ret = reset_control_assert(platform->reset_control); | ||
248 | |||
249 | return ret; | ||
250 | } | ||
251 | |||
252 | int gp10b_tegra_reset_deassert(struct device *dev) | ||
253 | { | ||
254 | struct gk20a_platform *platform = gk20a_get_platform(dev); | ||
255 | int ret = 0; | ||
256 | |||
257 | if (!platform->reset_control) | ||
258 | return -EINVAL; | ||
259 | |||
260 | ret = reset_control_deassert(platform->reset_control); | ||
261 | |||
262 | return ret; | ||
263 | } | ||
264 | |||
265 | static void gp10b_tegra_prescale(struct device *dev) | ||
266 | { | ||
267 | struct gk20a *g = get_gk20a(dev); | ||
268 | u32 avg = 0; | ||
269 | |||
270 | gk20a_dbg_fn(""); | ||
271 | |||
272 | nvgpu_pmu_load_norm(g, &avg); | ||
273 | |||
274 | gk20a_dbg_fn("done"); | ||
275 | } | ||
276 | |||
277 | static void gp10b_tegra_postscale(struct device *pdev, | ||
278 | unsigned long freq) | ||
279 | { | ||
280 | struct gk20a_platform *platform = gk20a_get_platform(pdev); | ||
281 | struct gk20a_scale_profile *profile = platform->g->scale_profile; | ||
282 | struct gk20a *g = get_gk20a(pdev); | ||
283 | unsigned long emc_rate; | ||
284 | |||
285 | gk20a_dbg_fn(""); | ||
286 | if (profile && !gp10b_tegra_is_railgated(pdev)) { | ||
287 | unsigned long emc_scale; | ||
288 | |||
289 | if (freq <= gp10b_freq_table[0]) | ||
290 | emc_scale = 0; | ||
291 | else | ||
292 | emc_scale = g->emc3d_ratio; | ||
293 | |||
294 | emc_rate = (freq * EMC_BW_RATIO * emc_scale) / 1000; | ||
295 | |||
296 | if (emc_rate > tegra_bwmgr_get_max_emc_rate()) | ||
297 | emc_rate = tegra_bwmgr_get_max_emc_rate(); | ||
298 | |||
299 | tegra_bwmgr_set_emc( | ||
300 | (struct tegra_bwmgr_client *)profile->private_data, | ||
301 | emc_rate, TEGRA_BWMGR_SET_EMC_FLOOR); | ||
302 | } | ||
303 | gk20a_dbg_fn("done"); | ||
304 | } | ||
305 | |||
306 | static long gp10b_round_clk_rate(struct device *dev, unsigned long rate) | ||
307 | { | ||
308 | struct gk20a *g = get_gk20a(dev); | ||
309 | struct gk20a_scale_profile *profile = g->scale_profile; | ||
310 | unsigned long *freq_table = profile->devfreq_profile.freq_table; | ||
311 | int max_states = profile->devfreq_profile.max_state; | ||
312 | int i; | ||
313 | |||
314 | for (i = 0; i < max_states; ++i) | ||
315 | if (freq_table[i] >= rate) | ||
316 | return freq_table[i]; | ||
317 | |||
318 | return freq_table[max_states - 1]; | ||
319 | } | ||
320 | |||
321 | static int gp10b_clk_get_freqs(struct device *dev, | ||
322 | unsigned long **freqs, int *num_freqs) | ||
323 | { | ||
324 | struct gk20a_platform *platform = gk20a_get_platform(dev); | ||
325 | unsigned long max_rate; | ||
326 | unsigned long new_rate = 0, prev_rate = 0; | ||
327 | int i = 0, freq_counter = 0; | ||
328 | |||
329 | max_rate = clk_round_rate(platform->clk[0], (UINT_MAX - 1)); | ||
330 | |||
331 | /* | ||
332 | * Walk the h/w frequency table and only select | ||
333 | * GP10B_FREQ_SELECT_STEP'th frequencies and | ||
334 | * add MAX freq to last | ||
335 | */ | ||
336 | for (; i < GP10B_MAX_SUPPORTED_FREQS; ++i) { | ||
337 | prev_rate = new_rate; | ||
338 | new_rate = clk_round_rate(platform->clk[0], prev_rate + 1); | ||
339 | |||
340 | if (i % GP10B_FREQ_SELECT_STEP == 0 || | ||
341 | new_rate == max_rate) { | ||
342 | gp10b_freq_table[freq_counter++] = new_rate; | ||
343 | |||
344 | if (new_rate == max_rate) | ||
345 | break; | ||
346 | } | ||
347 | } | ||
348 | |||
349 | WARN_ON(i == GP10B_MAX_SUPPORTED_FREQS); | ||
350 | |||
351 | /* Fill freq table */ | ||
352 | *freqs = gp10b_freq_table; | ||
353 | *num_freqs = freq_counter; | ||
354 | |||
355 | gk20a_dbg_info("min rate: %ld max rate: %ld num_of_freq %d\n", | ||
356 | gp10b_freq_table[0], max_rate, *num_freqs); | ||
357 | |||
358 | return 0; | ||
359 | } | ||
360 | |||
361 | struct gk20a_platform gp10b_tegra_platform = { | ||
362 | .has_syncpoints = true, | ||
363 | |||
364 | /* power management configuration */ | ||
365 | .railgate_delay_init = 500, | ||
366 | |||
367 | /* power management configuration */ | ||
368 | .can_railgate_init = true, | ||
369 | .enable_elpg = true, | ||
370 | .can_elpg_init = true, | ||
371 | .enable_blcg = true, | ||
372 | .enable_slcg = true, | ||
373 | .enable_elcg = true, | ||
374 | .can_slcg = true, | ||
375 | .can_blcg = true, | ||
376 | .can_elcg = true, | ||
377 | .enable_aelpg = true, | ||
378 | .enable_perfmon = true, | ||
379 | |||
380 | /* ptimer src frequency in hz*/ | ||
381 | .ptimer_src_freq = 31250000, | ||
382 | |||
383 | .ch_wdt_timeout_ms = 5000, | ||
384 | |||
385 | .probe = gp10b_tegra_probe, | ||
386 | .late_probe = gp10b_tegra_late_probe, | ||
387 | .remove = gp10b_tegra_remove, | ||
388 | |||
389 | /* power management callbacks */ | ||
390 | .suspend = gp10b_tegra_suspend, | ||
391 | .railgate = gp10b_tegra_railgate, | ||
392 | .unrailgate = gp10b_tegra_unrailgate, | ||
393 | .is_railgated = gp10b_tegra_is_railgated, | ||
394 | |||
395 | .busy = gk20a_tegra_busy, | ||
396 | .idle = gk20a_tegra_idle, | ||
397 | |||
398 | .dump_platform_dependencies = gk20a_tegra_debug_dump, | ||
399 | |||
400 | .has_cde = true, | ||
401 | |||
402 | .clk_round_rate = gp10b_round_clk_rate, | ||
403 | .get_clk_freqs = gp10b_clk_get_freqs, | ||
404 | |||
405 | /* frequency scaling configuration */ | ||
406 | .prescale = gp10b_tegra_prescale, | ||
407 | .postscale = gp10b_tegra_postscale, | ||
408 | .devfreq_governor = "nvhost_podgov", | ||
409 | |||
410 | .qos_notify = gk20a_scale_qos_notify, | ||
411 | |||
412 | .reset_assert = gp10b_tegra_reset_assert, | ||
413 | .reset_deassert = gp10b_tegra_reset_deassert, | ||
414 | |||
415 | .force_reset_in_do_idle = false, | ||
416 | |||
417 | .soc_name = "tegra18x", | ||
418 | |||
419 | .unified_memory = true, | ||
420 | }; | ||
421 | |||
422 | |||
423 | #define ECC_STAT_NAME_MAX_SIZE 100 | ||
424 | |||
425 | |||
426 | static DEFINE_HASHTABLE(ecc_hash_table, 5); | ||
427 | |||
428 | static struct device_attribute *dev_attr_sm_lrf_ecc_single_err_count_array; | ||
429 | static struct device_attribute *dev_attr_sm_lrf_ecc_double_err_count_array; | ||
430 | |||
431 | static struct device_attribute *dev_attr_sm_shm_ecc_sec_count_array; | ||
432 | static struct device_attribute *dev_attr_sm_shm_ecc_sed_count_array; | ||
433 | static struct device_attribute *dev_attr_sm_shm_ecc_ded_count_array; | ||
434 | |||
435 | static struct device_attribute *dev_attr_tex_ecc_total_sec_pipe0_count_array; | ||
436 | static struct device_attribute *dev_attr_tex_ecc_total_ded_pipe0_count_array; | ||
437 | static struct device_attribute *dev_attr_tex_ecc_unique_sec_pipe0_count_array; | ||
438 | static struct device_attribute *dev_attr_tex_ecc_unique_ded_pipe0_count_array; | ||
439 | static struct device_attribute *dev_attr_tex_ecc_total_sec_pipe1_count_array; | ||
440 | static struct device_attribute *dev_attr_tex_ecc_total_ded_pipe1_count_array; | ||
441 | static struct device_attribute *dev_attr_tex_ecc_unique_sec_pipe1_count_array; | ||
442 | static struct device_attribute *dev_attr_tex_ecc_unique_ded_pipe1_count_array; | ||
443 | |||
444 | static struct device_attribute *dev_attr_l2_ecc_sec_count_array; | ||
445 | static struct device_attribute *dev_attr_l2_ecc_ded_count_array; | ||
446 | |||
447 | |||
448 | static u32 gen_ecc_hash_key(char *str) | ||
449 | { | ||
450 | int i = 0; | ||
451 | u32 hash_key = 0; | ||
452 | |||
453 | while (str[i]) { | ||
454 | hash_key += (u32)(str[i]); | ||
455 | i++; | ||
456 | }; | ||
457 | |||
458 | return hash_key; | ||
459 | } | ||
460 | |||
461 | static ssize_t ecc_stat_show(struct device *dev, | ||
462 | struct device_attribute *attr, | ||
463 | char *buf) | ||
464 | { | ||
465 | const char *ecc_stat_full_name = attr->attr.name; | ||
466 | const char *ecc_stat_base_name; | ||
467 | unsigned int hw_unit; | ||
468 | struct gk20a_ecc_stat *ecc_stat; | ||
469 | u32 hash_key; | ||
470 | |||
471 | if (sscanf(ecc_stat_full_name, "ltc%u", &hw_unit) == 1) { | ||
472 | ecc_stat_base_name = &(ecc_stat_full_name[strlen("ltc0_")]); | ||
473 | } else if (sscanf(ecc_stat_full_name, "gpc0_tpc%u", &hw_unit) == 1) { | ||
474 | ecc_stat_base_name = &(ecc_stat_full_name[strlen("gpc0_tpc0_")]); | ||
475 | } else if (sscanf(ecc_stat_full_name, "gpc%u", &hw_unit) == 1) { | ||
476 | ecc_stat_base_name = &(ecc_stat_full_name[strlen("gpc0_")]); | ||
477 | } else if (sscanf(ecc_stat_full_name, "eng%u", &hw_unit) == 1) { | ||
478 | ecc_stat_base_name = &(ecc_stat_full_name[strlen("eng0_")]); | ||
479 | } else { | ||
480 | return snprintf(buf, | ||
481 | PAGE_SIZE, | ||
482 | "Error: Invalid ECC stat name!\n"); | ||
483 | } | ||
484 | |||
485 | hash_key = gen_ecc_hash_key((char *)ecc_stat_base_name); | ||
486 | hash_for_each_possible(ecc_hash_table, | ||
487 | ecc_stat, | ||
488 | hash_node, | ||
489 | hash_key) { | ||
490 | if (!strcmp(ecc_stat_full_name, ecc_stat->names[hw_unit])) | ||
491 | return snprintf(buf, PAGE_SIZE, "%u\n", ecc_stat->counters[hw_unit]); | ||
492 | } | ||
493 | |||
494 | return snprintf(buf, PAGE_SIZE, "Error: No ECC stat found!\n"); | ||
495 | } | ||
496 | |||
497 | int gr_gp10b_ecc_stat_create(struct device *dev, | ||
498 | int is_l2, | ||
499 | char *ecc_stat_name, | ||
500 | struct gk20a_ecc_stat *ecc_stat, | ||
501 | struct device_attribute **dev_attr_array) | ||
502 | { | ||
503 | struct gk20a *g = get_gk20a(dev); | ||
504 | char *ltc_unit_name = "ltc"; | ||
505 | char *gr_unit_name = "gpc0_tpc"; | ||
506 | int num_hw_units = 0; | ||
507 | |||
508 | if (is_l2) | ||
509 | num_hw_units = g->ltc_count; | ||
510 | else | ||
511 | num_hw_units = g->gr.tpc_count; | ||
512 | |||
513 | |||
514 | return gp10b_ecc_stat_create(dev, num_hw_units, | ||
515 | is_l2 ? ltc_unit_name : gr_unit_name, | ||
516 | ecc_stat_name, | ||
517 | ecc_stat, | ||
518 | dev_attr_array); | ||
519 | } | ||
520 | |||
521 | int gp10b_ecc_stat_create(struct device *dev, | ||
522 | int num_hw_units, | ||
523 | char *ecc_unit_name, | ||
524 | char *ecc_stat_name, | ||
525 | struct gk20a_ecc_stat *ecc_stat, | ||
526 | struct device_attribute **__dev_attr_array) | ||
527 | { | ||
528 | int error = 0; | ||
529 | struct gk20a *g = get_gk20a(dev); | ||
530 | int hw_unit = 0; | ||
531 | u32 hash_key = 0; | ||
532 | struct device_attribute *dev_attr_array; | ||
533 | |||
534 | /* Allocate arrays */ | ||
535 | dev_attr_array = nvgpu_kzalloc(g, sizeof(struct device_attribute) * | ||
536 | num_hw_units); | ||
537 | ecc_stat->counters = nvgpu_kzalloc(g, sizeof(u32) * num_hw_units); | ||
538 | ecc_stat->names = nvgpu_kzalloc(g, sizeof(char *) * num_hw_units); | ||
539 | for (hw_unit = 0; hw_unit < num_hw_units; hw_unit++) { | ||
540 | ecc_stat->names[hw_unit] = nvgpu_kzalloc(g, sizeof(char) * | ||
541 | ECC_STAT_NAME_MAX_SIZE); | ||
542 | } | ||
543 | |||
544 | for (hw_unit = 0; hw_unit < num_hw_units; hw_unit++) { | ||
545 | /* Fill in struct device_attribute members */ | ||
546 | snprintf(ecc_stat->names[hw_unit], | ||
547 | ECC_STAT_NAME_MAX_SIZE, | ||
548 | "%s%d_%s", | ||
549 | ecc_unit_name, | ||
550 | hw_unit, | ||
551 | ecc_stat_name); | ||
552 | |||
553 | sysfs_attr_init(&dev_attr_array[hw_unit].attr); | ||
554 | dev_attr_array[hw_unit].attr.name = ecc_stat->names[hw_unit]; | ||
555 | dev_attr_array[hw_unit].attr.mode = VERIFY_OCTAL_PERMISSIONS(S_IRUGO); | ||
556 | dev_attr_array[hw_unit].show = ecc_stat_show; | ||
557 | dev_attr_array[hw_unit].store = NULL; | ||
558 | |||
559 | /* Create sysfs file */ | ||
560 | error |= device_create_file(dev, &dev_attr_array[hw_unit]); | ||
561 | } | ||
562 | |||
563 | /* Add hash table entry */ | ||
564 | hash_key = gen_ecc_hash_key(ecc_stat_name); | ||
565 | hash_add(ecc_hash_table, | ||
566 | &ecc_stat->hash_node, | ||
567 | hash_key); | ||
568 | |||
569 | *__dev_attr_array = dev_attr_array; | ||
570 | |||
571 | return error; | ||
572 | } | ||
573 | |||
574 | void gr_gp10b_ecc_stat_remove(struct device *dev, | ||
575 | int is_l2, | ||
576 | struct gk20a_ecc_stat *ecc_stat, | ||
577 | struct device_attribute *dev_attr_array) | ||
578 | { | ||
579 | struct gk20a *g = get_gk20a(dev); | ||
580 | int num_hw_units = 0; | ||
581 | |||
582 | if (is_l2) | ||
583 | num_hw_units = g->ltc_count; | ||
584 | else | ||
585 | num_hw_units = g->gr.tpc_count; | ||
586 | |||
587 | gp10b_ecc_stat_remove(dev, num_hw_units, ecc_stat, dev_attr_array); | ||
588 | } | ||
589 | |||
590 | void gp10b_ecc_stat_remove(struct device *dev, | ||
591 | int num_hw_units, | ||
592 | struct gk20a_ecc_stat *ecc_stat, | ||
593 | struct device_attribute *dev_attr_array) | ||
594 | { | ||
595 | struct gk20a *g = get_gk20a(dev); | ||
596 | int hw_unit = 0; | ||
597 | |||
598 | /* Remove sysfs files */ | ||
599 | for (hw_unit = 0; hw_unit < num_hw_units; hw_unit++) { | ||
600 | device_remove_file(dev, &dev_attr_array[hw_unit]); | ||
601 | } | ||
602 | |||
603 | /* Remove hash table entry */ | ||
604 | hash_del(&ecc_stat->hash_node); | ||
605 | |||
606 | /* Free arrays */ | ||
607 | nvgpu_kfree(g, ecc_stat->counters); | ||
608 | for (hw_unit = 0; hw_unit < num_hw_units; hw_unit++) { | ||
609 | nvgpu_kfree(g, ecc_stat->names[hw_unit]); | ||
610 | } | ||
611 | nvgpu_kfree(g, ecc_stat->names); | ||
612 | nvgpu_kfree(g, dev_attr_array); | ||
613 | } | ||
614 | |||
615 | void gr_gp10b_create_sysfs(struct gk20a *g) | ||
616 | { | ||
617 | int error = 0; | ||
618 | struct device *dev = dev_from_gk20a(g); | ||
619 | |||
620 | /* This stat creation function is called on GR init. GR can get | ||
621 | initialized multiple times but we only need to create the ECC | ||
622 | stats once. Therefore, add the following check to avoid | ||
623 | creating duplicate stat sysfs nodes. */ | ||
624 | if (g->ecc.gr.t18x.sm_lrf_single_err_count.counters != NULL) | ||
625 | return; | ||
626 | |||
627 | error |= gr_gp10b_ecc_stat_create(dev, | ||
628 | 0, | ||
629 | "sm_lrf_ecc_single_err_count", | ||
630 | &g->ecc.gr.t18x.sm_lrf_single_err_count, | ||
631 | &dev_attr_sm_lrf_ecc_single_err_count_array); | ||
632 | error |= gr_gp10b_ecc_stat_create(dev, | ||
633 | 0, | ||
634 | "sm_lrf_ecc_double_err_count", | ||
635 | &g->ecc.gr.t18x.sm_lrf_double_err_count, | ||
636 | &dev_attr_sm_lrf_ecc_double_err_count_array); | ||
637 | |||
638 | error |= gr_gp10b_ecc_stat_create(dev, | ||
639 | 0, | ||
640 | "sm_shm_ecc_sec_count", | ||
641 | &g->ecc.gr.t18x.sm_shm_sec_count, | ||
642 | &dev_attr_sm_shm_ecc_sec_count_array); | ||
643 | error |= gr_gp10b_ecc_stat_create(dev, | ||
644 | 0, | ||
645 | "sm_shm_ecc_sed_count", | ||
646 | &g->ecc.gr.t18x.sm_shm_sed_count, | ||
647 | &dev_attr_sm_shm_ecc_sed_count_array); | ||
648 | error |= gr_gp10b_ecc_stat_create(dev, | ||
649 | 0, | ||
650 | "sm_shm_ecc_ded_count", | ||
651 | &g->ecc.gr.t18x.sm_shm_ded_count, | ||
652 | &dev_attr_sm_shm_ecc_ded_count_array); | ||
653 | |||
654 | error |= gr_gp10b_ecc_stat_create(dev, | ||
655 | 0, | ||
656 | "tex_ecc_total_sec_pipe0_count", | ||
657 | &g->ecc.gr.t18x.tex_total_sec_pipe0_count, | ||
658 | &dev_attr_tex_ecc_total_sec_pipe0_count_array); | ||
659 | error |= gr_gp10b_ecc_stat_create(dev, | ||
660 | 0, | ||
661 | "tex_ecc_total_ded_pipe0_count", | ||
662 | &g->ecc.gr.t18x.tex_total_ded_pipe0_count, | ||
663 | &dev_attr_tex_ecc_total_ded_pipe0_count_array); | ||
664 | error |= gr_gp10b_ecc_stat_create(dev, | ||
665 | 0, | ||
666 | "tex_ecc_unique_sec_pipe0_count", | ||
667 | &g->ecc.gr.t18x.tex_unique_sec_pipe0_count, | ||
668 | &dev_attr_tex_ecc_unique_sec_pipe0_count_array); | ||
669 | error |= gr_gp10b_ecc_stat_create(dev, | ||
670 | 0, | ||
671 | "tex_ecc_unique_ded_pipe0_count", | ||
672 | &g->ecc.gr.t18x.tex_unique_ded_pipe0_count, | ||
673 | &dev_attr_tex_ecc_unique_ded_pipe0_count_array); | ||
674 | error |= gr_gp10b_ecc_stat_create(dev, | ||
675 | 0, | ||
676 | "tex_ecc_total_sec_pipe1_count", | ||
677 | &g->ecc.gr.t18x.tex_total_sec_pipe1_count, | ||
678 | &dev_attr_tex_ecc_total_sec_pipe1_count_array); | ||
679 | error |= gr_gp10b_ecc_stat_create(dev, | ||
680 | 0, | ||
681 | "tex_ecc_total_ded_pipe1_count", | ||
682 | &g->ecc.gr.t18x.tex_total_ded_pipe1_count, | ||
683 | &dev_attr_tex_ecc_total_ded_pipe1_count_array); | ||
684 | error |= gr_gp10b_ecc_stat_create(dev, | ||
685 | 0, | ||
686 | "tex_ecc_unique_sec_pipe1_count", | ||
687 | &g->ecc.gr.t18x.tex_unique_sec_pipe1_count, | ||
688 | &dev_attr_tex_ecc_unique_sec_pipe1_count_array); | ||
689 | error |= gr_gp10b_ecc_stat_create(dev, | ||
690 | 0, | ||
691 | "tex_ecc_unique_ded_pipe1_count", | ||
692 | &g->ecc.gr.t18x.tex_unique_ded_pipe1_count, | ||
693 | &dev_attr_tex_ecc_unique_ded_pipe1_count_array); | ||
694 | |||
695 | error |= gr_gp10b_ecc_stat_create(dev, | ||
696 | 1, | ||
697 | "lts0_ecc_sec_count", | ||
698 | &g->ecc.gr.t18x.l2_sec_count, | ||
699 | &dev_attr_l2_ecc_sec_count_array); | ||
700 | error |= gr_gp10b_ecc_stat_create(dev, | ||
701 | 1, | ||
702 | "lts0_ecc_ded_count", | ||
703 | &g->ecc.gr.t18x.l2_ded_count, | ||
704 | &dev_attr_l2_ecc_ded_count_array); | ||
705 | |||
706 | if (error) | ||
707 | dev_err(dev, "Failed to create sysfs attributes!\n"); | ||
708 | } | ||
709 | |||
710 | static void gr_gp10b_remove_sysfs(struct device *dev) | ||
711 | { | ||
712 | struct gk20a *g = get_gk20a(dev); | ||
713 | |||
714 | gr_gp10b_ecc_stat_remove(dev, | ||
715 | 0, | ||
716 | &g->ecc.gr.t18x.sm_lrf_single_err_count, | ||
717 | dev_attr_sm_lrf_ecc_single_err_count_array); | ||
718 | gr_gp10b_ecc_stat_remove(dev, | ||
719 | 0, | ||
720 | &g->ecc.gr.t18x.sm_lrf_double_err_count, | ||
721 | dev_attr_sm_lrf_ecc_double_err_count_array); | ||
722 | |||
723 | gr_gp10b_ecc_stat_remove(dev, | ||
724 | 0, | ||
725 | &g->ecc.gr.t18x.sm_shm_sec_count, | ||
726 | dev_attr_sm_shm_ecc_sec_count_array); | ||
727 | gr_gp10b_ecc_stat_remove(dev, | ||
728 | 0, | ||
729 | &g->ecc.gr.t18x.sm_shm_sed_count, | ||
730 | dev_attr_sm_shm_ecc_sed_count_array); | ||
731 | gr_gp10b_ecc_stat_remove(dev, | ||
732 | 0, | ||
733 | &g->ecc.gr.t18x.sm_shm_ded_count, | ||
734 | dev_attr_sm_shm_ecc_ded_count_array); | ||
735 | |||
736 | gr_gp10b_ecc_stat_remove(dev, | ||
737 | 0, | ||
738 | &g->ecc.gr.t18x.tex_total_sec_pipe0_count, | ||
739 | dev_attr_tex_ecc_total_sec_pipe0_count_array); | ||
740 | gr_gp10b_ecc_stat_remove(dev, | ||
741 | 0, | ||
742 | &g->ecc.gr.t18x.tex_total_ded_pipe0_count, | ||
743 | dev_attr_tex_ecc_total_ded_pipe0_count_array); | ||
744 | gr_gp10b_ecc_stat_remove(dev, | ||
745 | 0, | ||
746 | &g->ecc.gr.t18x.tex_unique_sec_pipe0_count, | ||
747 | dev_attr_tex_ecc_unique_sec_pipe0_count_array); | ||
748 | gr_gp10b_ecc_stat_remove(dev, | ||
749 | 0, | ||
750 | &g->ecc.gr.t18x.tex_unique_ded_pipe0_count, | ||
751 | dev_attr_tex_ecc_unique_ded_pipe0_count_array); | ||
752 | gr_gp10b_ecc_stat_remove(dev, | ||
753 | 0, | ||
754 | &g->ecc.gr.t18x.tex_total_sec_pipe1_count, | ||
755 | dev_attr_tex_ecc_total_sec_pipe1_count_array); | ||
756 | gr_gp10b_ecc_stat_remove(dev, | ||
757 | 0, | ||
758 | &g->ecc.gr.t18x.tex_total_ded_pipe1_count, | ||
759 | dev_attr_tex_ecc_total_ded_pipe1_count_array); | ||
760 | gr_gp10b_ecc_stat_remove(dev, | ||
761 | 0, | ||
762 | &g->ecc.gr.t18x.tex_unique_sec_pipe1_count, | ||
763 | dev_attr_tex_ecc_unique_sec_pipe1_count_array); | ||
764 | gr_gp10b_ecc_stat_remove(dev, | ||
765 | 0, | ||
766 | &g->ecc.gr.t18x.tex_unique_ded_pipe1_count, | ||
767 | dev_attr_tex_ecc_unique_ded_pipe1_count_array); | ||
768 | |||
769 | gr_gp10b_ecc_stat_remove(dev, | ||
770 | 1, | ||
771 | &g->ecc.gr.t18x.l2_sec_count, | ||
772 | dev_attr_l2_ecc_sec_count_array); | ||
773 | gr_gp10b_ecc_stat_remove(dev, | ||
774 | 1, | ||
775 | &g->ecc.gr.t18x.l2_ded_count, | ||
776 | dev_attr_l2_ecc_ded_count_array); | ||
777 | } | ||