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