diff options
author | Jonathan Herman <hermanjl@cs.unc.edu> | 2013-01-22 10:38:37 -0500 |
---|---|---|
committer | Jonathan Herman <hermanjl@cs.unc.edu> | 2013-01-22 10:38:37 -0500 |
commit | fcc9d2e5a6c89d22b8b773a64fb4ad21ac318446 (patch) | |
tree | a57612d1888735a2ec7972891b68c1ac5ec8faea /arch/arm/mach-tegra/tegra2_mc.c | |
parent | 8dea78da5cee153b8af9c07a2745f6c55057fe12 (diff) |
Diffstat (limited to 'arch/arm/mach-tegra/tegra2_mc.c')
-rw-r--r-- | arch/arm/mach-tegra/tegra2_mc.c | 1017 |
1 files changed, 1017 insertions, 0 deletions
diff --git a/arch/arm/mach-tegra/tegra2_mc.c b/arch/arm/mach-tegra/tegra2_mc.c new file mode 100644 index 00000000000..6df9c232c02 --- /dev/null +++ b/arch/arm/mach-tegra/tegra2_mc.c | |||
@@ -0,0 +1,1017 @@ | |||
1 | /* | ||
2 | * arch/arm/mach-tegra/tegra2_mc.c | ||
3 | * | ||
4 | * Memory controller bandwidth profiling interface | ||
5 | * | ||
6 | * Copyright (c) 2009-2011, NVIDIA Corporation. | ||
7 | * | ||
8 | * This program is free software; you can redistribute it and/or modify | ||
9 | * it under the terms of the GNU General Public License as published by | ||
10 | * the Free Software Foundation; either version 2 of the License, or | ||
11 | * (at your option) any later version. | ||
12 | * | ||
13 | * This program is distributed in the hope that it will be useful, but WITHOUT | ||
14 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
15 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
16 | * more details. | ||
17 | * | ||
18 | * You should have received a copy of the GNU General Public License along | ||
19 | * with this program; if not, write to the Free Software Foundation, Inc., | ||
20 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||
21 | */ | ||
22 | |||
23 | #include <linux/slab.h> | ||
24 | #include <linux/kobject.h> | ||
25 | #include <linux/string.h> | ||
26 | #include <linux/sysfs.h> | ||
27 | #include <linux/sysdev.h> | ||
28 | #include <linux/ktime.h> | ||
29 | #include <linux/hrtimer.h> | ||
30 | #include <linux/parser.h> | ||
31 | #include <linux/io.h> | ||
32 | #include <linux/module.h> | ||
33 | #include <linux/init.h> | ||
34 | #include <linux/clk.h> | ||
35 | |||
36 | #include <mach/iomap.h> | ||
37 | |||
38 | #include <asm/uaccess.h> | ||
39 | |||
40 | #include "clock.h" | ||
41 | #include "tegra2_mc.h" | ||
42 | |||
43 | static void stat_start(void); | ||
44 | static void stat_stop(void); | ||
45 | static void stat_log(void); | ||
46 | |||
47 | static struct hrtimer sample_timer; | ||
48 | |||
49 | #define MC_COUNTER_INITIALIZER() \ | ||
50 | { \ | ||
51 | .enabled = false, \ | ||
52 | .period = 10, \ | ||
53 | .mode = FILTER_CLIENT, \ | ||
54 | .address_low = 0, \ | ||
55 | .address_length = 0xfffffffful, \ | ||
56 | .sample_data = { \ | ||
57 | .signature = 0xdeadbeef, \ | ||
58 | } \ | ||
59 | } | ||
60 | |||
61 | static struct tegra_mc_counter mc_counter0 = MC_COUNTER_INITIALIZER(); | ||
62 | static struct tegra_mc_counter mc_counter1 = MC_COUNTER_INITIALIZER(); | ||
63 | static struct tegra_mc_counter emc_llp_counter = MC_COUNTER_INITIALIZER(); | ||
64 | |||
65 | /* /sys/devices/system/tegra_mc */ | ||
66 | static bool sample_enable = SAMPLE_ENABLE_DEFAULT; | ||
67 | static u16 sample_quantum = SAMPLE_QUANTUM_DEFAULT; | ||
68 | static u8 sample_log[SAMPLE_LOG_SIZE]; | ||
69 | |||
70 | static DEFINE_SPINLOCK(sample_enable_lock); | ||
71 | static DEFINE_SPINLOCK(sample_log_lock); | ||
72 | |||
73 | static u8 *sample_log_wptr = sample_log, *sample_log_rptr = sample_log; | ||
74 | static int sample_log_size = SAMPLE_LOG_SIZE - 1; | ||
75 | static struct clk *emc_clock = NULL; | ||
76 | |||
77 | static bool sampling(void) | ||
78 | { | ||
79 | bool ret; | ||
80 | |||
81 | spin_lock_bh(&sample_enable_lock); | ||
82 | ret = (sample_enable == true)? true : false; | ||
83 | spin_unlock_bh(&sample_enable_lock); | ||
84 | |||
85 | return ret; | ||
86 | } | ||
87 | |||
88 | static struct sysdev_class tegra_mc_sysclass = { | ||
89 | .name = "tegra_mc", | ||
90 | }; | ||
91 | |||
92 | static ssize_t tegra_mc_enable_show(struct sysdev_class *class, | ||
93 | struct sysdev_class_attribute *attr, char *buf) | ||
94 | { | ||
95 | return sprintf(buf, "%d\n", sample_enable); | ||
96 | } | ||
97 | |||
98 | static ssize_t tegra_mc_enable_store(struct sysdev_class *class, | ||
99 | struct sysdev_class_attribute *attr, | ||
100 | const char *buf, size_t count) | ||
101 | { | ||
102 | int value, i; | ||
103 | struct tegra_mc_counter *counters[] = { | ||
104 | &mc_counter0, | ||
105 | &mc_counter1, | ||
106 | &emc_llp_counter | ||
107 | }; | ||
108 | |||
109 | sscanf(buf, "%d", &value); | ||
110 | |||
111 | if (value == 0 || value == 1) | ||
112 | sample_enable = value; | ||
113 | else | ||
114 | return -EINVAL; | ||
115 | |||
116 | if (!sample_enable) { | ||
117 | stat_stop(); | ||
118 | hrtimer_cancel(&sample_timer); | ||
119 | return count; | ||
120 | } | ||
121 | |||
122 | hrtimer_cancel(&sample_timer); | ||
123 | |||
124 | /* we need to initialize variables that change during sampling */ | ||
125 | sample_log_wptr = sample_log_rptr = sample_log; | ||
126 | sample_log_size = SAMPLE_LOG_SIZE - 1; | ||
127 | |||
128 | for (i = 0; i < ARRAY_SIZE(counters); i++) { | ||
129 | struct tegra_mc_counter *c = counters[i]; | ||
130 | |||
131 | if (!c->enabled) | ||
132 | continue; | ||
133 | |||
134 | c->current_client_index = 0; | ||
135 | } | ||
136 | |||
137 | stat_start(); | ||
138 | |||
139 | hrtimer_start(&sample_timer, | ||
140 | ktime_add_ns(ktime_get(), (u64)sample_quantum * 1000000), | ||
141 | HRTIMER_MODE_ABS); | ||
142 | |||
143 | return count; | ||
144 | } | ||
145 | |||
146 | static ssize_t tegra_mc_log_show(struct sysdev_class *class, | ||
147 | struct sysdev_class_attribute *attr, char *buf) | ||
148 | { | ||
149 | int index = 0, count = 0; | ||
150 | unsigned long flags; | ||
151 | |||
152 | spin_lock_irqsave(&sample_log_lock, flags); | ||
153 | |||
154 | while (sample_log_rptr != sample_log_wptr) { | ||
155 | if (sample_log_rptr < sample_log_wptr) { | ||
156 | count = sample_log_wptr - sample_log_rptr; | ||
157 | memcpy(buf + index, sample_log_rptr, count); | ||
158 | sample_log_rptr = sample_log_wptr; | ||
159 | sample_log_size += count; | ||
160 | } else { | ||
161 | count = SAMPLE_LOG_SIZE - | ||
162 | (sample_log_rptr - sample_log); | ||
163 | memcpy(buf + index, sample_log_rptr, count); | ||
164 | sample_log_rptr = sample_log; | ||
165 | sample_log_size += count; | ||
166 | } | ||
167 | index += count; | ||
168 | } | ||
169 | |||
170 | spin_unlock_irqrestore(&sample_log_lock, flags); | ||
171 | |||
172 | return index; | ||
173 | } | ||
174 | |||
175 | static ssize_t tegra_mc_log_store(struct sysdev_class *class, | ||
176 | struct sysdev_class_attribute *attr, | ||
177 | const char *buf, size_t count) | ||
178 | { | ||
179 | return -EPERM; | ||
180 | } | ||
181 | |||
182 | static ssize_t tegra_mc_quantum_show(struct sysdev_class *class, | ||
183 | struct sysdev_class_attribute *attr, char *buf) | ||
184 | { | ||
185 | return sprintf(buf, "%d\n", sample_quantum); | ||
186 | } | ||
187 | |||
188 | static ssize_t tegra_mc_quantum_store(struct sysdev_class *class, | ||
189 | struct sysdev_class_attribute *attr, | ||
190 | const char *buf, size_t count) | ||
191 | { | ||
192 | int value; | ||
193 | |||
194 | if (sampling()) | ||
195 | return -EINVAL; | ||
196 | |||
197 | sscanf(buf, "%d", &value); | ||
198 | sample_quantum = value; | ||
199 | |||
200 | return count; | ||
201 | } | ||
202 | |||
203 | #define TEGRA_MC_EXPAND(_attr,_mode) \ | ||
204 | static SYSDEV_CLASS_ATTR( \ | ||
205 | _attr, _mode, tegra_mc_##_attr##_show, tegra_mc_##_attr##_store); | ||
206 | |||
207 | #define TEGRA_MC_ATTRIBUTES(_attr1,_mode1,_attr2,_mode2,_attr3,_mode3) \ | ||
208 | TEGRA_MC_EXPAND(_attr1,_mode1) \ | ||
209 | TEGRA_MC_EXPAND(_attr2,_mode2) \ | ||
210 | TEGRA_MC_EXPAND(_attr3,_mode3) | ||
211 | |||
212 | TEGRA_MC_ATTRIBUTES(enable,0666,log,0444,quantum,0666) | ||
213 | |||
214 | #undef TEGRA_MC_EXPAND | ||
215 | |||
216 | #define TEGRA_MC_EXPAND(_attr,_mode) \ | ||
217 | &attr_##_attr, | ||
218 | |||
219 | static struct sysdev_class_attribute *tegra_mc_attrs[] = { | ||
220 | TEGRA_MC_ATTRIBUTES(enable,0666,log,0444,quantum,0666) | ||
221 | NULL | ||
222 | }; | ||
223 | |||
224 | /* /sys/devices/system/tegra_mc/client */ | ||
225 | static bool tegra_mc_client_0_enabled = CLIENT_ENABLED_DEFAULT; | ||
226 | static u8 tegra_mc_client_0_on_schedule_buffer[CLIENT_ON_SCHEDULE_LENGTH]; | ||
227 | static struct kobject *tegra_mc_client_kobj, *tegra_mc_client_0_kobj; | ||
228 | |||
229 | struct match_mode { | ||
230 | const char *name; | ||
231 | int mode; | ||
232 | }; | ||
233 | |||
234 | static const struct match_mode mode_list[] = { | ||
235 | [0] = { | ||
236 | .name = "none", | ||
237 | .mode = FILTER_NONE, | ||
238 | }, | ||
239 | [1] = { | ||
240 | .name = "address", | ||
241 | .mode = FILTER_ADDR, | ||
242 | }, | ||
243 | [2] = { | ||
244 | .name = "client", | ||
245 | .mode = FILTER_CLIENT, | ||
246 | }, | ||
247 | }; | ||
248 | |||
249 | static int tegra_mc_parse_mode(const char* str) { | ||
250 | int i; | ||
251 | |||
252 | for (i = 0; i < ARRAY_SIZE(mode_list); i++) { | ||
253 | if (!strncmp(str, mode_list[i].name, strlen(mode_list[i].name))) | ||
254 | return mode_list[i].mode; | ||
255 | } | ||
256 | return -EINVAL; | ||
257 | } | ||
258 | |||
259 | static int tegra_mc_client_parse(const char *buf, size_t count, | ||
260 | tegra_mc_counter_t *counter0, tegra_mc_counter_t *counter1, | ||
261 | tegra_mc_counter_t *llp) | ||
262 | { | ||
263 | char *options, *p, *ptr; | ||
264 | tegra_mc_counter_t *counter; | ||
265 | substring_t args[MAX_OPT_ARGS]; | ||
266 | enum { | ||
267 | opt_period, | ||
268 | opt_mode, | ||
269 | opt_client, | ||
270 | opt_address_low, | ||
271 | opt_address_length, | ||
272 | opt_err, | ||
273 | }; | ||
274 | const match_table_t tokens = { | ||
275 | {opt_period, "period=%s"}, | ||
276 | {opt_mode, "mode=%s"}, | ||
277 | {opt_client, "client=%s"}, | ||
278 | {opt_address_low, "address_low=%s"}, | ||
279 | {opt_address_length, "address_length=%s"}, | ||
280 | {opt_err, NULL}, | ||
281 | }; | ||
282 | int ret = 0, i, token, index = 0; | ||
283 | bool aggregate = false; | ||
284 | int period, *client_ids, mode; | ||
285 | u64 address_low = 0; | ||
286 | u64 address_length = 1ull << 32; | ||
287 | |||
288 | client_ids = kmalloc(sizeof(int) * (MC_COUNTER_CLIENT_SIZE + 1), | ||
289 | GFP_KERNEL); | ||
290 | if (!client_ids) | ||
291 | return -ENOMEM; | ||
292 | |||
293 | memset(client_ids, -1, (sizeof(int) * (MC_COUNTER_CLIENT_SIZE + 1))); | ||
294 | |||
295 | options = kstrdup(buf, GFP_KERNEL); | ||
296 | if (!options) { | ||
297 | ret = -ENOMEM; | ||
298 | goto end; | ||
299 | } | ||
300 | |||
301 | while ((p = strsep(&options, " ")) != NULL) { | ||
302 | if (!*p) | ||
303 | continue; | ||
304 | |||
305 | pr_debug("\t %s\n", p); | ||
306 | |||
307 | token = match_token(p, tokens, args); | ||
308 | switch (token) { | ||
309 | case opt_period: | ||
310 | if (match_int(&args[0], &period) || period <= 0) { | ||
311 | ret = -EINVAL; | ||
312 | goto end; | ||
313 | } | ||
314 | break; | ||
315 | |||
316 | case opt_mode: | ||
317 | mode = tegra_mc_parse_mode(args[0].from); | ||
318 | if (mode < 0) { | ||
319 | ret = mode; | ||
320 | goto end; | ||
321 | } | ||
322 | break; | ||
323 | |||
324 | case opt_client: | ||
325 | ptr = get_options(args[0].from, | ||
326 | MC_COUNTER_CLIENT_SIZE + 1, client_ids); | ||
327 | |||
328 | if (client_ids[1] == MC_STAT_AGGREGATE) { | ||
329 | aggregate = true; | ||
330 | break; | ||
331 | } | ||
332 | break; | ||
333 | |||
334 | case opt_address_low: | ||
335 | address_low = simple_strtoull(args[0].from, NULL, 0); | ||
336 | break; | ||
337 | |||
338 | case opt_address_length: | ||
339 | address_length = simple_strtoull(args[0].from, NULL, 0); | ||
340 | break; | ||
341 | |||
342 | default: | ||
343 | ret = -EINVAL; | ||
344 | goto end; | ||
345 | } | ||
346 | } | ||
347 | |||
348 | address_low &= PAGE_MASK; | ||
349 | address_length += PAGE_SIZE - 1; | ||
350 | address_length &= ~((1ull << PAGE_SHIFT) - 1ull); | ||
351 | |||
352 | if (mode == FILTER_CLIENT) { | ||
353 | counter = counter0; | ||
354 | llp->enabled = false; | ||
355 | counter1->enabled = false; | ||
356 | } else if (mode == FILTER_ADDR || mode == FILTER_NONE) { | ||
357 | if (aggregate) { | ||
358 | counter = counter1; | ||
359 | llp->enabled = false; | ||
360 | counter0->enabled = false; | ||
361 | } else { | ||
362 | counter = counter0; | ||
363 | counter1->enabled = false; | ||
364 | llp->enabled = false; | ||
365 | } | ||
366 | } else { | ||
367 | ret = -EINVAL; | ||
368 | goto end; | ||
369 | } | ||
370 | |||
371 | counter->mode = mode; | ||
372 | counter->enabled = true; | ||
373 | counter->address_low = (u32)address_low; | ||
374 | counter->address_length = (u32)(address_length - 1); | ||
375 | |||
376 | for (i = 1; i < MC_COUNTER_CLIENT_SIZE; i++) { | ||
377 | if (client_ids[i] != -1) | ||
378 | counter->clients[index++] = client_ids[i]; | ||
379 | } | ||
380 | |||
381 | counter->total_clients = index; | ||
382 | |||
383 | if (llp->enabled) { | ||
384 | llp->mode = counter->mode; | ||
385 | llp->period = counter->period; | ||
386 | llp->address_low = counter->address_low; | ||
387 | llp->address_length = counter->address_length; | ||
388 | } | ||
389 | |||
390 | end: | ||
391 | if (options) | ||
392 | kfree(options); | ||
393 | if (client_ids) | ||
394 | kfree(client_ids); | ||
395 | |||
396 | return ret; | ||
397 | } | ||
398 | |||
399 | static ssize_t tegra_mc_client_0_show(struct kobject *kobj, | ||
400 | struct kobj_attribute *attr, char *buf) | ||
401 | { | ||
402 | if (strcmp(attr->attr.name, "enable") == 0) | ||
403 | return sprintf(buf, "%d\n", tegra_mc_client_0_enabled); | ||
404 | else if (strcmp(attr->attr.name, "on_schedule") == 0) | ||
405 | return sprintf(buf, "%s", tegra_mc_client_0_on_schedule_buffer); | ||
406 | else | ||
407 | return -EINVAL; | ||
408 | } | ||
409 | |||
410 | static ssize_t tegra_mc_client_0_store(struct kobject *kobj, | ||
411 | struct kobj_attribute *attr, const char *buf, size_t count) | ||
412 | { | ||
413 | int value; | ||
414 | |||
415 | if (sampling()) | ||
416 | return -EINVAL; | ||
417 | |||
418 | if (strcmp(attr->attr.name, "enable") == 0) { | ||
419 | sscanf(buf, "%d\n", &value); | ||
420 | if (value == 0 || value == 1) | ||
421 | tegra_mc_client_0_enabled = value; | ||
422 | else | ||
423 | return -EINVAL; | ||
424 | |||
425 | return count; | ||
426 | } else if (strcmp(attr->attr.name, "on_schedule") == 0) { | ||
427 | if (tegra_mc_client_parse(buf, count, | ||
428 | &mc_counter0, &mc_counter1, | ||
429 | &emc_llp_counter) == 0) { | ||
430 | |||
431 | strncpy(tegra_mc_client_0_on_schedule_buffer, | ||
432 | buf, count); | ||
433 | |||
434 | return count; | ||
435 | } else | ||
436 | return -EINVAL; | ||
437 | } else | ||
438 | return -EINVAL; | ||
439 | } | ||
440 | |||
441 | static struct kobj_attribute tegra_mc_client_0_enable = | ||
442 | __ATTR(enable, 0660, tegra_mc_client_0_show, tegra_mc_client_0_store); | ||
443 | |||
444 | static struct kobj_attribute tegra_mc_client_0_on_schedule = | ||
445 | __ATTR(on_schedule, 0660, tegra_mc_client_0_show, tegra_mc_client_0_store); | ||
446 | |||
447 | static struct attribute *tegra_mc_client_0_attrs[] = { | ||
448 | &tegra_mc_client_0_enable.attr, | ||
449 | &tegra_mc_client_0_on_schedule.attr, | ||
450 | NULL, | ||
451 | }; | ||
452 | |||
453 | static struct attribute_group tegra_mc_client_0_attr_group = { | ||
454 | .attrs = tegra_mc_client_0_attrs | ||
455 | }; | ||
456 | |||
457 | /* /sys/devices/system/tegra_mc/dram */ | ||
458 | #define dram_counters(_x) \ | ||
459 | _x(activate_cnt, ACTIVATE_CNT) \ | ||
460 | _x(read_cnt, READ_CNT) \ | ||
461 | _x(write_cnt, WRITE_CNT) \ | ||
462 | _x(ref_cnt, REF_CNT) \ | ||
463 | _x(cumm_banks_active_cke_eq1, CUMM_BANKS_ACTIVE_CKE_EQ1) \ | ||
464 | _x(cumm_banks_active_cke_eq0, CUMM_BANKS_ACTIVE_CKE_EQ0) \ | ||
465 | _x(cke_eq1_clks, CKE_EQ1_CLKS) \ | ||
466 | _x(extclks_cke_eq1, EXTCLKS_CKE_EQ1) \ | ||
467 | _x(extclks_cke_eq0, EXTCLKS_CKE_EQ0) \ | ||
468 | _x(no_banks_active_cke_eq1, NO_BANKS_ACTIVE_CKE_EQ1) \ | ||
469 | _x(no_banks_active_cke_eq0, NO_BANKS_ACTIVE_CKE_EQ0) | ||
470 | |||
471 | #define DEFINE_COUNTER(_name, _val) { .enabled = false, .device_mask = 0, }, | ||
472 | |||
473 | static tegra_emc_dram_counter_t dram_counters[] = { | ||
474 | dram_counters(DEFINE_COUNTER) | ||
475 | }; | ||
476 | |||
477 | #define DEFINE_SYSFS(_name, _val) \ | ||
478 | \ | ||
479 | static struct kobject *tegra_mc_dram_##_name##_kobj; \ | ||
480 | \ | ||
481 | static ssize_t tegra_mc_dram_##_name##_show(struct kobject *kobj, \ | ||
482 | struct kobj_attribute *attr, char *buf) \ | ||
483 | { \ | ||
484 | return tegra_mc_dram_show(kobj, attr, buf, \ | ||
485 | _val - EMC_DRAM_STAT_BEGIN); \ | ||
486 | } \ | ||
487 | \ | ||
488 | static ssize_t tegra_mc_dram_##_name##_store(struct kobject *kobj, \ | ||
489 | struct kobj_attribute *attr, const char *buf, size_t count) \ | ||
490 | { \ | ||
491 | if (sampling()) \ | ||
492 | return 0; \ | ||
493 | \ | ||
494 | return tegra_mc_dram_store(kobj, attr, buf, count, \ | ||
495 | _val - EMC_DRAM_STAT_BEGIN); \ | ||
496 | } \ | ||
497 | \ | ||
498 | \ | ||
499 | static struct kobj_attribute tegra_mc_dram_##_name##_enable = \ | ||
500 | __ATTR(enable, 0660, tegra_mc_dram_##_name##_show, \ | ||
501 | tegra_mc_dram_##_name##_store); \ | ||
502 | \ | ||
503 | static struct kobj_attribute tegra_mc_dram_##_name##_device_mask = \ | ||
504 | __ATTR(device_mask, 0660, tegra_mc_dram_##_name##_show, \ | ||
505 | tegra_mc_dram_##_name##_store); \ | ||
506 | \ | ||
507 | static struct attribute *tegra_mc_dram_##_name##_attrs[] = { \ | ||
508 | &tegra_mc_dram_##_name##_enable.attr, \ | ||
509 | &tegra_mc_dram_##_name##_device_mask.attr, \ | ||
510 | NULL, \ | ||
511 | }; \ | ||
512 | \ | ||
513 | static struct attribute_group tegra_mc_dram_##_name##_attr_group = { \ | ||
514 | .attrs = tegra_mc_dram_##_name##_attrs, \ | ||
515 | }; | ||
516 | |||
517 | static struct kobject *tegra_mc_dram_kobj; | ||
518 | |||
519 | static ssize_t tegra_mc_dram_show(struct kobject *kobj, | ||
520 | struct kobj_attribute *attr, char *buf, int index) | ||
521 | { | ||
522 | if (index >= EMC_DRAM_STAT_END - EMC_DRAM_STAT_BEGIN) | ||
523 | return -EINVAL; | ||
524 | |||
525 | if (strcmp(attr->attr.name, "enable") == 0) | ||
526 | return sprintf(buf, "%d\n", dram_counters[index].enabled); | ||
527 | else if (strcmp(attr->attr.name, "device_mask") == 0) | ||
528 | return sprintf(buf, "%d\n", dram_counters[index].device_mask); | ||
529 | else | ||
530 | return -EINVAL; | ||
531 | } | ||
532 | static ssize_t tegra_mc_dram_store(struct kobject *kobj, | ||
533 | struct kobj_attribute *attr, const char *buf, size_t count, int index) | ||
534 | { | ||
535 | int value; | ||
536 | |||
537 | if (index >= EMC_DRAM_STAT_END - EMC_DRAM_STAT_BEGIN) | ||
538 | return -EINVAL; | ||
539 | |||
540 | if (strcmp(attr->attr.name, "enable") == 0) { | ||
541 | sscanf(buf, "%d\n", &value); | ||
542 | if (value == 0 || value == 1) | ||
543 | dram_counters[index].enabled = value; | ||
544 | else | ||
545 | return -EINVAL; | ||
546 | |||
547 | return count; | ||
548 | } else if (strcmp(attr->attr.name, "device_mask") == 0) { | ||
549 | sscanf(buf, "%d\n", &value); | ||
550 | dram_counters[index].device_mask = (u8)value; | ||
551 | |||
552 | return count; | ||
553 | } else | ||
554 | return -EINVAL; | ||
555 | } | ||
556 | |||
557 | dram_counters(DEFINE_SYSFS) | ||
558 | |||
559 | /* Tegra Statistics */ | ||
560 | typedef struct { | ||
561 | void __iomem *mmio; | ||
562 | } tegra_device_t; | ||
563 | |||
564 | static tegra_device_t mc = { | ||
565 | .mmio = IO_ADDRESS(TEGRA_MC_BASE), | ||
566 | }; | ||
567 | |||
568 | static tegra_device_t emc = { | ||
569 | .mmio = IO_ADDRESS(TEGRA_EMC_BASE), | ||
570 | }; | ||
571 | |||
572 | void mc_stat_start(tegra_mc_counter_t *counter0, tegra_mc_counter_t *counter1) | ||
573 | { | ||
574 | struct tegra_mc_counter *c; | ||
575 | u32 filter_client = ARMC_STAT_CONTROL_FILTER_CLIENT_DISABLE; | ||
576 | u32 filter_addr = ARMC_STAT_CONTROL_FILTER_ADDR_DISABLE; | ||
577 | |||
578 | if (!tegra_mc_client_0_enabled) | ||
579 | return; | ||
580 | |||
581 | c = (counter0->enabled) ? counter0 : counter1; | ||
582 | |||
583 | /* disable statistics */ | ||
584 | writel((MC_STAT_CONTROL_0_EMC_GATHER_DISABLE << MC_STAT_CONTROL_0_EMC_GATHER_SHIFT), | ||
585 | mc.mmio + MC_STAT_CONTROL_0); | ||
586 | |||
587 | if (c->enabled && c->mode == FILTER_ADDR) | ||
588 | filter_addr = ARMC_STAT_CONTROL_FILTER_ADDR_ENABLE; | ||
589 | else if (c->enabled && c->mode == FILTER_CLIENT) | ||
590 | filter_client = ARMC_STAT_CONTROL_FILTER_CLIENT_ENABLE; | ||
591 | |||
592 | filter_addr <<= ARMC_STAT_CONTROL_FILTER_ADDR_SHIFT; | ||
593 | filter_client <<= ARMC_STAT_CONTROL_FILTER_CLIENT_SHIFT; | ||
594 | |||
595 | if (c->enabled) { | ||
596 | u32 reg = 0; | ||
597 | reg |= (ARMC_STAT_CONTROL_MODE_BANDWIDTH << | ||
598 | ARMC_STAT_CONTROL_MODE_SHIFT); | ||
599 | reg |= (ARMC_STAT_CONTROL_EVENT_QUALIFIED << | ||
600 | ARMC_STAT_CONTROL_EVENT_SHIFT); | ||
601 | reg |= (ARMC_STAT_CONTROL_FILTER_PRI_DISABLE << | ||
602 | ARMC_STAT_CONTROL_FILTER_PRI_SHIFT); | ||
603 | reg |= (ARMC_STAT_CONTROL_FILTER_COALESCED_DISABLE << | ||
604 | ARMC_STAT_CONTROL_FILTER_COALESCED_SHIFT); | ||
605 | reg |= filter_client; | ||
606 | reg |= filter_addr; | ||
607 | reg |= (c->clients[c->current_client_index] << | ||
608 | ARMC_STAT_CONTROL_CLIENT_ID_SHIFT); | ||
609 | |||
610 | /* note these registers are shared */ | ||
611 | writel(c->address_low, | ||
612 | mc.mmio + MC_STAT_EMC_ADDR_LOW_0); | ||
613 | writel((c->address_low + c->address_length), | ||
614 | mc.mmio + MC_STAT_EMC_ADDR_HIGH_0); | ||
615 | writel(0xFFFFFFFF, mc.mmio + MC_STAT_EMC_CLOCK_LIMIT_0); | ||
616 | |||
617 | writel(reg, mc.mmio + MC_STAT_EMC_CONTROL_0_0); | ||
618 | } | ||
619 | |||
620 | /* reset then enable statistics */ | ||
621 | writel((MC_STAT_CONTROL_0_EMC_GATHER_CLEAR << MC_STAT_CONTROL_0_EMC_GATHER_SHIFT), | ||
622 | mc.mmio + MC_STAT_CONTROL_0); | ||
623 | |||
624 | writel((MC_STAT_CONTROL_0_EMC_GATHER_ENABLE << MC_STAT_CONTROL_0_EMC_GATHER_SHIFT), | ||
625 | mc.mmio + MC_STAT_CONTROL_0); | ||
626 | } | ||
627 | |||
628 | void mc_stat_stop(tegra_mc_counter_t *counter0, | ||
629 | tegra_mc_counter_t *counter1) | ||
630 | { | ||
631 | u32 total_counts = readl(mc.mmio + MC_STAT_EMC_CLOCKS_0); | ||
632 | |||
633 | /* Disable statistics */ | ||
634 | writel((MC_STAT_CONTROL_0_EMC_GATHER_DISABLE << MC_STAT_CONTROL_0_EMC_GATHER_SHIFT), | ||
635 | mc.mmio + MC_STAT_CONTROL_0); | ||
636 | |||
637 | if (counter0->enabled) { | ||
638 | counter0->sample_data.client_counts = readl(mc.mmio + MC_STAT_EMC_COUNT_0_0); | ||
639 | counter0->sample_data.total_counts = total_counts; | ||
640 | counter0->sample_data.emc_clock_rate = clk_get_rate(emc_clock); | ||
641 | } | ||
642 | else { | ||
643 | counter1->sample_data.client_counts = readl(mc.mmio + MC_STAT_EMC_COUNT_1_0); | ||
644 | counter1->sample_data.total_counts = total_counts; | ||
645 | counter1->sample_data.emc_clock_rate = clk_get_rate(emc_clock); | ||
646 | } | ||
647 | } | ||
648 | |||
649 | void emc_stat_start(tegra_mc_counter_t *llp_counter, | ||
650 | tegra_emc_dram_counter_t *dram_counter) | ||
651 | { | ||
652 | u32 llmc_stat = 0; | ||
653 | u32 llmc_ctrl = | ||
654 | (AREMC_STAT_CONTROL_MODE_BANDWIDTH << | ||
655 | AREMC_STAT_CONTROL_MODE_SHIFT) | | ||
656 | (AREMC_STAT_CONTROL_CLIENT_TYPE_MPCORER << | ||
657 | AREMC_STAT_CONTROL_CLIENT_TYPE_SHIFT) | | ||
658 | (AREMC_STAT_CONTROL_EVENT_QUALIFIED << | ||
659 | AREMC_STAT_CONTROL_EVENT_SHIFT); | ||
660 | |||
661 | /* disable statistics */ | ||
662 | llmc_stat |= (EMC_STAT_CONTROL_0_LLMC_GATHER_DISABLE << | ||
663 | EMC_STAT_CONTROL_0_LLMC_GATHER_SHIFT); | ||
664 | llmc_stat |= (EMC_STAT_CONTROL_0_DRAM_GATHER_DISABLE << | ||
665 | EMC_STAT_CONTROL_0_DRAM_GATHER_SHIFT); | ||
666 | writel(llmc_stat, emc.mmio + EMC_STAT_CONTROL_0); | ||
667 | |||
668 | if (tegra_mc_client_0_enabled && llp_counter->enabled) { | ||
669 | if (llp_counter->mode == FILTER_ADDR) { | ||
670 | llmc_ctrl |= | ||
671 | (AREMC_STAT_CONTROL_FILTER_ADDR_ENABLE << | ||
672 | AREMC_STAT_CONTROL_FILTER_ADDR_SHIFT); | ||
673 | llmc_ctrl |= | ||
674 | (AREMC_STAT_CONTROL_FILTER_CLIENT_DISABLE << | ||
675 | AREMC_STAT_CONTROL_FILTER_CLIENT_SHIFT); | ||
676 | } else if (llp_counter->mode == FILTER_CLIENT) { | ||
677 | /* not allow aggregate client in client mode */ | ||
678 | llmc_ctrl |= | ||
679 | (AREMC_STAT_CONTROL_FILTER_ADDR_DISABLE << | ||
680 | AREMC_STAT_CONTROL_FILTER_ADDR_SHIFT); | ||
681 | llmc_ctrl |= | ||
682 | (AREMC_STAT_CONTROL_FILTER_CLIENT_DISABLE << | ||
683 | AREMC_STAT_CONTROL_FILTER_CLIENT_SHIFT); | ||
684 | } else if (llp_counter->mode == FILTER_NONE) { | ||
685 | llmc_ctrl |= | ||
686 | (AREMC_STAT_CONTROL_FILTER_ADDR_DISABLE << | ||
687 | AREMC_STAT_CONTROL_FILTER_ADDR_SHIFT); | ||
688 | llmc_ctrl |= | ||
689 | (AREMC_STAT_CONTROL_FILTER_CLIENT_DISABLE << | ||
690 | AREMC_STAT_CONTROL_FILTER_CLIENT_SHIFT); | ||
691 | } | ||
692 | |||
693 | writel(llp_counter->address_low, | ||
694 | emc.mmio + EMC_STAT_LLMC_ADDR_LOW_0); | ||
695 | writel( (llp_counter->address_low + llp_counter->address_length), | ||
696 | emc.mmio + EMC_STAT_LLMC_ADDR_HIGH_0); | ||
697 | writel(0xFFFFFFFF, emc.mmio + EMC_STAT_LLMC_CLOCK_LIMIT_0); | ||
698 | writel(llmc_ctrl, emc.mmio + EMC_STAT_LLMC_CONTROL_0_0); | ||
699 | } | ||
700 | |||
701 | writel(0xFFFFFFFF, emc.mmio + EMC_STAT_DRAM_CLOCK_LIMIT_LO_0); | ||
702 | writel(0xFF, emc.mmio + EMC_STAT_DRAM_CLOCK_LIMIT_HI_0); | ||
703 | |||
704 | llmc_stat = 0; | ||
705 | /* Reset then enable statistics */ | ||
706 | llmc_stat |= (EMC_STAT_CONTROL_0_LLMC_GATHER_CLEAR << | ||
707 | EMC_STAT_CONTROL_0_LLMC_GATHER_SHIFT); | ||
708 | llmc_stat |= (EMC_STAT_CONTROL_0_DRAM_GATHER_CLEAR << | ||
709 | EMC_STAT_CONTROL_0_DRAM_GATHER_SHIFT); | ||
710 | writel(llmc_stat, emc.mmio + EMC_STAT_CONTROL_0); | ||
711 | |||
712 | llmc_stat = 0; | ||
713 | llmc_stat |= (EMC_STAT_CONTROL_0_LLMC_GATHER_ENABLE << | ||
714 | EMC_STAT_CONTROL_0_LLMC_GATHER_SHIFT); | ||
715 | llmc_stat |= (EMC_STAT_CONTROL_0_DRAM_GATHER_ENABLE << | ||
716 | EMC_STAT_CONTROL_0_DRAM_GATHER_SHIFT); | ||
717 | writel(llmc_stat, emc.mmio + EMC_STAT_CONTROL_0); | ||
718 | } | ||
719 | |||
720 | void emc_stat_stop(tegra_mc_counter_t *llp_counter, | ||
721 | tegra_emc_dram_counter_t *dram_counter) | ||
722 | { | ||
723 | u32 llmc_stat = 0; | ||
724 | int i; | ||
725 | int dev0_offsets_lo[] = { | ||
726 | EMC_STAT_DRAM_DEV0_ACTIVATE_CNT_LO_0, | ||
727 | EMC_STAT_DRAM_DEV0_READ_CNT_LO_0, | ||
728 | EMC_STAT_DRAM_DEV0_WRITE_CNT_LO_0, | ||
729 | EMC_STAT_DRAM_DEV0_REF_CNT_LO_0, | ||
730 | EMC_STAT_DRAM_DEV0_CUMM_BANKS_ACTIVE_CKE_EQ1_LO_0, | ||
731 | EMC_STAT_DRAM_DEV0_CUMM_BANKS_ACTIVE_CKE_EQ0_LO_0, | ||
732 | EMC_STAT_DRAM_DEV0_CKE_EQ1_CLKS_LO_0, | ||
733 | EMC_STAT_DRAM_DEV0_EXTCLKS_CKE_EQ1_LO_0, | ||
734 | EMC_STAT_DRAM_DEV0_EXTCLKS_CKE_EQ0_LO_0, | ||
735 | EMC_STAT_DRAM_DEV0_NO_BANKS_ACTIVE_CKE_EQ1_LO_0, | ||
736 | EMC_STAT_DRAM_DEV0_NO_BANKS_ACTIVE_CKE_EQ0_LO_0, | ||
737 | }; | ||
738 | int dev0_offsets_hi[] = { | ||
739 | EMC_STAT_DRAM_DEV0_ACTIVATE_CNT_HI_0, | ||
740 | EMC_STAT_DRAM_DEV0_READ_CNT_HI_0, | ||
741 | EMC_STAT_DRAM_DEV0_WRITE_CNT_HI_0, | ||
742 | EMC_STAT_DRAM_DEV0_REF_CNT_HI_0, | ||
743 | EMC_STAT_DRAM_DEV0_CUMM_BANKS_ACTIVE_CKE_EQ1_HI_0, | ||
744 | EMC_STAT_DRAM_DEV0_CUMM_BANKS_ACTIVE_CKE_EQ0_HI_0, | ||
745 | EMC_STAT_DRAM_DEV0_CKE_EQ1_CLKS_HI_0, | ||
746 | EMC_STAT_DRAM_DEV0_EXTCLKS_CKE_EQ1_HI_0, | ||
747 | EMC_STAT_DRAM_DEV0_EXTCLKS_CKE_EQ0_HI_0, | ||
748 | EMC_STAT_DRAM_DEV0_NO_BANKS_ACTIVE_CKE_EQ1_HI_0, | ||
749 | EMC_STAT_DRAM_DEV0_NO_BANKS_ACTIVE_CKE_EQ0_HI_0, | ||
750 | }; | ||
751 | int dev1_offsets_lo[] = { | ||
752 | EMC_STAT_DRAM_DEV1_ACTIVATE_CNT_LO_0, | ||
753 | EMC_STAT_DRAM_DEV1_READ_CNT_LO_0, | ||
754 | EMC_STAT_DRAM_DEV1_WRITE_CNT_LO_0, | ||
755 | EMC_STAT_DRAM_DEV1_REF_CNT_LO_0, | ||
756 | EMC_STAT_DRAM_DEV1_CUMM_BANKS_ACTIVE_CKE_EQ1_LO_0, | ||
757 | EMC_STAT_DRAM_DEV1_CUMM_BANKS_ACTIVE_CKE_EQ0_LO_0, | ||
758 | EMC_STAT_DRAM_DEV1_CKE_EQ1_CLKS_LO_0, | ||
759 | EMC_STAT_DRAM_DEV1_EXTCLKS_CKE_EQ1_LO_0, | ||
760 | EMC_STAT_DRAM_DEV1_EXTCLKS_CKE_EQ0_LO_0, | ||
761 | EMC_STAT_DRAM_DEV1_NO_BANKS_ACTIVE_CKE_EQ1_LO_0, | ||
762 | EMC_STAT_DRAM_DEV1_NO_BANKS_ACTIVE_CKE_EQ0_LO_0, | ||
763 | }; | ||
764 | int dev1_offsets_hi[] = { | ||
765 | EMC_STAT_DRAM_DEV1_ACTIVATE_CNT_HI_0, | ||
766 | EMC_STAT_DRAM_DEV1_READ_CNT_HI_0, | ||
767 | EMC_STAT_DRAM_DEV1_WRITE_CNT_HI_0, | ||
768 | EMC_STAT_DRAM_DEV1_REF_CNT_HI_0, | ||
769 | EMC_STAT_DRAM_DEV1_CUMM_BANKS_ACTIVE_CKE_EQ1_HI_0, | ||
770 | EMC_STAT_DRAM_DEV1_CUMM_BANKS_ACTIVE_CKE_EQ0_HI_0, | ||
771 | EMC_STAT_DRAM_DEV1_CKE_EQ1_CLKS_HI_0, | ||
772 | EMC_STAT_DRAM_DEV1_EXTCLKS_CKE_EQ1_HI_0, | ||
773 | EMC_STAT_DRAM_DEV1_EXTCLKS_CKE_EQ0_HI_0, | ||
774 | EMC_STAT_DRAM_DEV1_NO_BANKS_ACTIVE_CKE_EQ1_HI_0, | ||
775 | EMC_STAT_DRAM_DEV1_NO_BANKS_ACTIVE_CKE_EQ0_HI_0, | ||
776 | }; | ||
777 | |||
778 | /* Disable statistics */ | ||
779 | llmc_stat |= (EMC_STAT_CONTROL_0_LLMC_GATHER_DISABLE << | ||
780 | EMC_STAT_CONTROL_0_LLMC_GATHER_SHIFT); | ||
781 | llmc_stat |= (EMC_STAT_CONTROL_0_DRAM_GATHER_DISABLE << | ||
782 | EMC_STAT_CONTROL_0_DRAM_GATHER_SHIFT); | ||
783 | writel(llmc_stat, emc.mmio + EMC_STAT_CONTROL_0); | ||
784 | |||
785 | if (tegra_mc_client_0_enabled == true && llp_counter->enabled) { | ||
786 | u32 total_counts = readl(mc.mmio + MC_STAT_EMC_CLOCKS_0); | ||
787 | llp_counter->sample_data.client_counts = readl(emc.mmio + EMC_STAT_LLMC_COUNT_0_0); | ||
788 | llp_counter->sample_data.total_counts = total_counts; | ||
789 | llp_counter->sample_data.emc_clock_rate = clk_get_rate(emc_clock); | ||
790 | } | ||
791 | |||
792 | for (i = 0; i < EMC_DRAM_STAT_END - EMC_DRAM_STAT_BEGIN; i++) { | ||
793 | if (dram_counter[i].enabled) { | ||
794 | |||
795 | dram_counter[i].sample_data.client_counts = 0; | ||
796 | dram_counter[i].sample_data.emc_clock_rate = clk_get_rate(emc_clock); | ||
797 | |||
798 | if (!(dram_counter[i].device_mask & 0x1)) { | ||
799 | if (readl(emc.mmio + dev0_offsets_hi[i]) != 0) { | ||
800 | dram_counter[i].sample_data.client_counts = 0xFFFFFFFF; | ||
801 | continue; | ||
802 | } | ||
803 | dram_counter[i].sample_data.client_counts += | ||
804 | readl(emc.mmio + dev0_offsets_lo[i]); | ||
805 | } | ||
806 | |||
807 | if (!(dram_counter[i].device_mask & 0x2)) { | ||
808 | if (readl(emc.mmio + dev1_offsets_hi[i]) != 0) { | ||
809 | dram_counter[i].sample_data.client_counts = 0xFFFFFFFF; | ||
810 | continue; | ||
811 | } | ||
812 | dram_counter[i].sample_data.client_counts += | ||
813 | readl(emc.mmio + dev1_offsets_lo[i]); | ||
814 | } | ||
815 | } | ||
816 | } | ||
817 | } | ||
818 | |||
819 | static void stat_start(void) | ||
820 | { | ||
821 | mc_stat_start(&mc_counter0, &mc_counter1); | ||
822 | emc_stat_start(&emc_llp_counter, dram_counters); | ||
823 | } | ||
824 | |||
825 | static void stat_stop(void) | ||
826 | { | ||
827 | mc_stat_stop(&mc_counter0, &mc_counter1); | ||
828 | emc_stat_stop(&emc_llp_counter, dram_counters); | ||
829 | } | ||
830 | |||
831 | #define statcpy(_buf, _bufstart, _buflen, _elem) \ | ||
832 | do { \ | ||
833 | size_t s = sizeof(_elem); \ | ||
834 | memcpy(_buf, &_elem, s); \ | ||
835 | _buf += s; \ | ||
836 | if (_buf >= _bufstart + _buflen) \ | ||
837 | _buf = _bufstart; \ | ||
838 | } while (0); | ||
839 | |||
840 | static void stat_log(void) | ||
841 | { | ||
842 | int i; | ||
843 | unsigned long flags; | ||
844 | |||
845 | struct tegra_mc_counter *counters[] = { | ||
846 | &mc_counter0, | ||
847 | &mc_counter1, | ||
848 | &emc_llp_counter | ||
849 | }; | ||
850 | |||
851 | spin_lock_irqsave(&sample_log_lock, flags); | ||
852 | |||
853 | if (tegra_mc_client_0_enabled) { | ||
854 | for (i = 0; i < ARRAY_SIZE(counters); i++) { | ||
855 | struct tegra_mc_counter *c = counters[i]; | ||
856 | |||
857 | if (!c->enabled) | ||
858 | continue; | ||
859 | |||
860 | c->sample_data.client_number = c->clients[c->current_client_index]; | ||
861 | |||
862 | c->current_client_index++; | ||
863 | if (c->current_client_index == c->total_clients) | ||
864 | c->current_client_index = 0; | ||
865 | |||
866 | statcpy(sample_log_wptr, sample_log, | ||
867 | SAMPLE_LOG_SIZE, c->sample_data); | ||
868 | } | ||
869 | } | ||
870 | |||
871 | for (i = 0; i < EMC_DRAM_STAT_END - EMC_DRAM_STAT_BEGIN; i++) { | ||
872 | if (dram_counters[i].enabled) { | ||
873 | statcpy(sample_log_wptr, sample_log, | ||
874 | SAMPLE_LOG_SIZE, dram_counters[i].sample_data); | ||
875 | } | ||
876 | } | ||
877 | |||
878 | spin_unlock_irqrestore(&sample_log_lock, flags); | ||
879 | } | ||
880 | |||
881 | static enum hrtimer_restart sample_timer_function(struct hrtimer *handle) | ||
882 | { | ||
883 | stat_stop(); | ||
884 | stat_log(); | ||
885 | |||
886 | if (!sample_enable) | ||
887 | return HRTIMER_NORESTART; | ||
888 | |||
889 | stat_start(); | ||
890 | |||
891 | hrtimer_add_expires_ns(&sample_timer, (u64)sample_quantum * 1000000); | ||
892 | return HRTIMER_RESTART; | ||
893 | } | ||
894 | |||
895 | /* module init */ | ||
896 | #define REGISTER_SYSFS(_name, _val) \ | ||
897 | tegra_mc_dram_##_name##_kobj = \ | ||
898 | kobject_create_and_add(#_name, tegra_mc_dram_kobj); \ | ||
899 | sysfs_create_group(tegra_mc_dram_##_name##_kobj, \ | ||
900 | &tegra_mc_dram_##_name##_attr_group); | ||
901 | |||
902 | static int tegra_mc_init(void) | ||
903 | { | ||
904 | int i; | ||
905 | int rc; | ||
906 | |||
907 | /* /sys/devices/system/tegra_mc */ | ||
908 | rc = sysdev_class_register(&tegra_mc_sysclass); | ||
909 | if(rc) | ||
910 | goto out; | ||
911 | |||
912 | for (i = 0; i < ARRAY_SIZE(tegra_mc_attrs)-1; i++) { | ||
913 | rc = sysdev_class_create_file(&tegra_mc_sysclass, | ||
914 | tegra_mc_attrs[i]); | ||
915 | if(rc) { | ||
916 | printk("\n sysdev_class_create_file : failed \n"); | ||
917 | goto out_unreg_class; | ||
918 | } | ||
919 | } | ||
920 | |||
921 | /* /sys/devices/system/tegra_mc/client */ | ||
922 | tegra_mc_client_kobj = kobject_create_and_add("client", | ||
923 | &tegra_mc_sysclass.kset.kobj); | ||
924 | if(!tegra_mc_client_kobj) | ||
925 | goto out_remove_sysdev_files; | ||
926 | |||
927 | tegra_mc_client_0_kobj = kobject_create_and_add("0", | ||
928 | tegra_mc_client_kobj); | ||
929 | if(!tegra_mc_client_0_kobj) | ||
930 | goto out_put_kobject_client; | ||
931 | |||
932 | rc = sysfs_create_group(tegra_mc_client_0_kobj, | ||
933 | &tegra_mc_client_0_attr_group); | ||
934 | if(rc) | ||
935 | goto out_put_kobject_client_0; | ||
936 | |||
937 | /* /sys/devices/system/tegra_mc/dram */ | ||
938 | tegra_mc_dram_kobj = kobject_create_and_add("dram", | ||
939 | &tegra_mc_sysclass.kset.kobj); | ||
940 | if(!tegra_mc_dram_kobj) | ||
941 | goto out_remove_group_client_0; | ||
942 | |||
943 | dram_counters(REGISTER_SYSFS) | ||
944 | |||
945 | /* hrtimer */ | ||
946 | hrtimer_init(&sample_timer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS); | ||
947 | sample_timer.function = sample_timer_function; | ||
948 | |||
949 | for (i = 0; i < EMC_DRAM_STAT_END - EMC_DRAM_STAT_BEGIN; i++) { | ||
950 | dram_counters[i].sample_data.client_number = EMC_DRAM_STAT_BEGIN + i; | ||
951 | dram_counters[i].sample_data.signature = 0xdeadbeef; | ||
952 | } | ||
953 | |||
954 | emc_clock = clk_get_sys(NULL, "emc"); | ||
955 | if (!emc_clock) { | ||
956 | pr_err("Could not get EMC clock\n"); | ||
957 | goto out_remove_group_client_0; | ||
958 | } | ||
959 | |||
960 | return 0; | ||
961 | |||
962 | out_remove_group_client_0: | ||
963 | sysfs_remove_group(tegra_mc_client_0_kobj, &tegra_mc_client_0_attr_group); | ||
964 | |||
965 | out_put_kobject_client_0: | ||
966 | kobject_put(tegra_mc_client_0_kobj); | ||
967 | |||
968 | out_put_kobject_client: | ||
969 | kobject_put(tegra_mc_client_kobj); | ||
970 | |||
971 | out_remove_sysdev_files: | ||
972 | for (i = 0; i < ARRAY_SIZE(tegra_mc_attrs)-1; i++) { | ||
973 | sysdev_class_remove_file(&tegra_mc_sysclass, tegra_mc_attrs[i]); | ||
974 | } | ||
975 | |||
976 | out_unreg_class: | ||
977 | sysdev_class_unregister(&tegra_mc_sysclass); | ||
978 | |||
979 | out: | ||
980 | return rc; | ||
981 | } | ||
982 | |||
983 | /* module deinit */ | ||
984 | #define REMOVE_SYSFS(_name, _val) \ | ||
985 | sysfs_remove_group(tegra_mc_dram_##_name##_kobj, \ | ||
986 | &tegra_mc_dram_##_name##_attr_group); \ | ||
987 | kobject_put(tegra_mc_dram_##_name##_kobj); | ||
988 | |||
989 | static void tegra_mc_exit(void) | ||
990 | { | ||
991 | int i; | ||
992 | |||
993 | stat_stop(); | ||
994 | |||
995 | /* hrtimer */ | ||
996 | hrtimer_cancel(&sample_timer); | ||
997 | |||
998 | /* /sys/devices/system/tegra_mc/client */ | ||
999 | sysfs_remove_group(tegra_mc_client_0_kobj, | ||
1000 | &tegra_mc_client_0_attr_group); | ||
1001 | kobject_put(tegra_mc_client_0_kobj); | ||
1002 | kobject_put(tegra_mc_client_kobj); | ||
1003 | |||
1004 | /* /sys/devices/system/tegra_mc/dram */ | ||
1005 | dram_counters(REMOVE_SYSFS) | ||
1006 | kobject_put(tegra_mc_dram_kobj); | ||
1007 | |||
1008 | /* /sys/devices/system/tegra_mc */ | ||
1009 | for (i = 0; i < ARRAY_SIZE(tegra_mc_attrs)-1; i++) { | ||
1010 | sysdev_class_remove_file(&tegra_mc_sysclass, tegra_mc_attrs[i]); | ||
1011 | } | ||
1012 | sysdev_class_unregister(&tegra_mc_sysclass); | ||
1013 | } | ||
1014 | |||
1015 | module_init(tegra_mc_init); | ||
1016 | module_exit(tegra_mc_exit); | ||
1017 | MODULE_LICENSE("Dual BSD/GPL"); | ||