]> rtime.felk.cvut.cz Git - hercules2020/nv-tegra/linux-4.4.git/blob - drivers/misc/tegra-profiler/power_clk.c
843f074273443c1f353b95b8a385e51d3d053d61
[hercules2020/nv-tegra/linux-4.4.git] / drivers / misc / tegra-profiler / power_clk.c
1 /*
2  * drivers/misc/tegra-profiler/power_clk.c
3  *
4  * Copyright (c) 2013-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
17 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
18
19 #include <linux/cpufreq.h>
20 #include <linux/clk.h>
21 #include <linux/notifier.h>
22 #include <linux/workqueue.h>
23 #include <linux/cpu.h>
24 #include <linux/timer.h>
25 #include <linux/err.h>
26
27 #include <linux/tegra_profiler.h>
28
29 #include "power_clk.h"
30 #include "quadd.h"
31 #include "hrt.h"
32 #include "comm.h"
33 #include "debug.h"
34
35 #define PCLK_MAX_VALUES 32
36
37 struct power_clk_data {
38         unsigned long value;
39         unsigned long prev;
40 };
41
42 #define PCLK_NB_GPU     0
43 #define PCLK_NB_EMC     0
44
45 enum {
46         PCLK_NB_CPU_FREQ,
47         PCLK_NB_CPU_HOTPLUG,
48         PCLK_NB_CPU_MAX,
49 };
50
51 #define PCLK_NB_MAX     PCLK_NB_CPU_MAX
52
53 struct power_clk_source {
54         int type;
55
56         struct clk *clkp;
57         struct notifier_block nb[PCLK_NB_MAX];
58
59         int nr;
60         struct power_clk_data data[PCLK_MAX_VALUES];
61
62         atomic_t active;
63         struct mutex lock;
64 };
65
66 struct power_clk_context_s {
67         struct power_clk_source cpu;
68         struct power_clk_source gpu;
69         struct power_clk_source emc;
70
71         struct timer_list timer;
72         unsigned int period;
73
74         struct quadd_ctx *quadd_ctx;
75 };
76
77 enum {
78         QUADD_POWER_CLK_CPU = 1,
79         QUADD_POWER_CLK_GPU,
80         QUADD_POWER_CLK_EMC,
81 };
82
83 static struct power_clk_context_s power_ctx;
84
85 static void make_sample(void)
86 {
87         int i;
88         u32 extra_cpus[NR_CPUS];
89         struct power_clk_source *s;
90         struct quadd_iovec vec;
91
92         struct quadd_record_data record;
93         struct quadd_power_rate_data *power_rate = &record.power_rate;
94
95         record.record_type = QUADD_RECORD_TYPE_POWER_RATE;
96
97         power_rate->time = quadd_get_time();
98
99         s = &power_ctx.cpu;
100         mutex_lock(&s->lock);
101         if (atomic_read(&s->active)) {
102                 power_rate->nr_cpus = s->nr;
103                 for (i = 0; i < s->nr; i++)
104                         extra_cpus[i] = s->data[i].value;
105         } else {
106                 power_rate->nr_cpus = 0;
107         }
108         mutex_unlock(&s->lock);
109
110         s = &power_ctx.gpu;
111         mutex_lock(&s->lock);
112         if (atomic_read(&s->active))
113                 power_rate->gpu = s->data[0].value;
114         else
115                 power_rate->gpu = 0;
116
117         mutex_unlock(&s->lock);
118
119         s = &power_ctx.emc;
120         mutex_lock(&s->lock);
121         if (atomic_read(&s->active))
122                 power_rate->emc = s->data[0].value;
123         else
124                 power_rate->emc = 0;
125
126         mutex_unlock(&s->lock);
127 /*
128  *      pr_debug("make_sample: cpu: %u/%u/%u/%u, gpu: %u, emc: %u\n",
129  *               extra_cpus[0], extra_cpus[1], extra_cpus[2], extra_cpus[3],
130  *               power_rate->gpu, power_rate->emc);
131 */
132         vec.base = extra_cpus;
133         vec.len = power_rate->nr_cpus * sizeof(extra_cpus[0]);
134
135         quadd_put_sample(&record, &vec, 1);
136 }
137
138 static void
139 make_sample_hotplug(int cpu, int is_online)
140 {
141         struct quadd_record_data record;
142         struct quadd_hotplug_data *s = &record.hotplug;
143
144         record.record_type = QUADD_RECORD_TYPE_HOTPLUG;
145
146         s->cpu = cpu;
147         s->is_online = is_online ? 1 : 0;
148         s->time = quadd_get_time();
149         s->reserved = 0;
150
151         quadd_put_sample(&record, NULL, 0);
152 }
153
154 static inline int
155 is_data_changed(struct power_clk_source *s)
156 {
157         int i;
158
159         for (i = 0; i < s->nr; i++) {
160                 if (s->data[i].value != s->data[i].prev)
161                         return 1;
162         }
163
164         return 0;
165 }
166
167 static inline void
168 update_data(struct power_clk_source *s)
169 {
170         int i;
171
172         for (i = 0; i < s->nr; i++)
173                 s->data[i].prev = s->data[i].value;
174 }
175
176 static void check_clks(void)
177 {
178         struct power_clk_source *s;
179         int changed = 0;
180
181         s = &power_ctx.gpu;
182         mutex_lock(&s->lock);
183         if (is_data_changed(s)) {
184                 update_data(s);
185                 changed = 1;
186         }
187         mutex_unlock(&s->lock);
188
189         s = &power_ctx.emc;
190         mutex_lock(&s->lock);
191         if (is_data_changed(s)) {
192                 update_data(s);
193                 changed = 1;
194         }
195         mutex_unlock(&s->lock);
196
197         if (changed) {
198                 pr_debug("gpu: %lu, emc: %lu\n",
199                          power_ctx.gpu.data[0].value,
200                          power_ctx.emc.data[0].value);
201
202                 make_sample();
203         }
204 }
205
206 static void check_source(struct power_clk_source *s)
207 {
208         mutex_lock(&s->lock);
209
210         if (!is_data_changed(s))
211                 goto out_unlock;
212
213         pr_debug("cpu: %lu/%lu/%lu/%lu\n",
214                  power_ctx.cpu.data[0].value,
215                  power_ctx.cpu.data[1].value,
216                  power_ctx.cpu.data[2].value,
217                  power_ctx.cpu.data[3].value);
218
219         update_data(s);
220         mutex_unlock(&s->lock);
221
222         make_sample();
223         return;
224
225 out_unlock:
226         mutex_unlock(&s->lock);
227 }
228
229 static void
230 read_source(struct power_clk_source *s, int cpu)
231 {
232         unsigned int value;
233
234         switch (s->type) {
235         case QUADD_POWER_CLK_CPU:
236                 /* update cpu frequency */
237                 if (cpu < 0 || cpu >= max_t(int, s->nr, nr_cpu_ids)) {
238                         pr_err_once("error: cpu id: %d\n", cpu);
239                         break;
240                 }
241
242                 value = cpufreq_get(cpu);
243
244                 mutex_lock(&s->lock);
245                 s->data[cpu].value = value;
246                 pr_debug("QUADD_POWER_CLK_CPU, cpu: %d, value: %lu\n",
247                          cpu, s->data[cpu].value);
248                 mutex_unlock(&s->lock);
249                 break;
250
251         case QUADD_POWER_CLK_GPU:
252                 /* update gpu frequency */
253                 mutex_lock(&s->lock);
254                 s->clkp = clk_get_sys("3d", NULL);
255                 if (!IS_ERR_OR_NULL(s->clkp)) {
256                         s->data[0].value =
257                                 clk_get_rate(s->clkp) / 1000;
258                         clk_put(s->clkp);
259                 }
260                 pr_debug("QUADD_POWER_CLK_GPU, value: %lu\n",
261                          s->data[0].value);
262                 mutex_unlock(&s->lock);
263                 break;
264
265         case QUADD_POWER_CLK_EMC:
266                 /* update emc frequency */
267                 mutex_lock(&s->lock);
268                 s->clkp = clk_get_sys("cpu", "emc");
269                 if (!IS_ERR_OR_NULL(s->clkp)) {
270                         s->data[0].value =
271                                 clk_get_rate(s->clkp) / 1000;
272                         clk_put(s->clkp);
273                 }
274                 pr_debug("QUADD_POWER_CLK_EMC, value: %lu\n",
275                          s->data[0].value);
276                 mutex_unlock(&s->lock);
277                 break;
278
279         default:
280                 pr_err_once("error: invalid power_clk type\n");
281                 break;
282         }
283 }
284
285 static int
286 gpu_notifier_call(struct notifier_block *nb,
287                   unsigned long action, void *data)
288 {
289         read_source(&power_ctx.gpu, -1);
290         check_clks();
291
292         return NOTIFY_DONE;
293 }
294
295 static int
296 emc_notifier_call(struct notifier_block *nb,
297                   unsigned long action, void *data)
298 {
299         read_source(&power_ctx.emc, -1);
300         check_clks();
301
302         return NOTIFY_DONE;
303 }
304
305 static void
306 read_cpufreq(struct power_clk_source *s, struct cpufreq_freqs *freq)
307 {
308         int cpu, cpufreq;
309
310         mutex_lock(&s->lock);
311
312         if (!atomic_read(&s->active))
313                 goto out_unlock;
314
315         cpu = freq->cpu;
316         cpufreq = freq->new;
317
318         pr_debug("cpu: %d, cpufreq: %d\n", cpu, cpufreq);
319
320         if (cpu >= s->nr) {
321                 pr_err_once("error: cpu id: %d\n", cpu);
322                 goto out_unlock;
323         }
324
325         s->data[cpu].value = cpufreq;
326
327         pr_debug("[%d] cpufreq: %u --> %u\n",
328                  cpu, freq->old, cpufreq);
329
330         mutex_unlock(&s->lock);
331         check_source(s);
332         return;
333
334 out_unlock:
335         mutex_unlock(&s->lock);
336 }
337
338 static int
339 cpufreq_notifier_call(struct notifier_block *nb,
340                       unsigned long action, void *hcpu)
341 {
342         struct cpufreq_freqs *freq;
343         struct power_clk_source *s = &power_ctx.cpu;
344
345         if (!atomic_read(&s->active))
346                 return 0;
347
348         pr_debug("action: %lu\n", action);
349
350         if (action == CPUFREQ_POSTCHANGE) {
351                 freq = hcpu;
352                 read_cpufreq(s, freq);
353         }
354
355         return 0;
356 }
357
358 static int
359 cpu_hotplug_notifier_call(struct notifier_block *nb,
360                           unsigned long action, void *hcpu)
361 {
362         int cpu;
363         struct power_clk_source *s = &power_ctx.cpu;
364
365         if (!atomic_read(&s->active))
366                 return NOTIFY_DONE;
367
368         cpu = (long)hcpu;
369
370         pr_debug("cpu: %d, action: %lu\n", cpu, action);
371
372         if (cpu >= s->nr) {
373                 pr_err_once("error: cpu id: %d\n", cpu);
374                 return NOTIFY_DONE;
375         }
376
377         switch (action) {
378         case CPU_ONLINE:
379         case CPU_ONLINE_FROZEN:
380                 make_sample_hotplug(cpu, 1);
381                 break;
382
383         case CPU_DEAD:
384         case CPU_DEAD_FROZEN:
385                 mutex_lock(&s->lock);
386                 if (atomic_read(&s->active))
387                         s->data[cpu].value = 0;
388                 mutex_unlock(&s->lock);
389
390                 make_sample_hotplug(cpu, 0);
391                 break;
392
393         default:
394                 return NOTIFY_DONE;
395         }
396
397         return NOTIFY_OK;
398 }
399
400 static void reset_data(struct power_clk_source *s)
401 {
402         int i;
403
404         mutex_lock(&s->lock);
405         for (i = 0; i < s->nr; i++) {
406                 s->data[i].value = 0;
407                 s->data[i].prev = 0;
408         }
409         mutex_unlock(&s->lock);
410 }
411
412 static void init_source(struct power_clk_source *s,
413                         int nr_values,
414                         int type)
415 {
416         s->type = type;
417         s->nr = min_t(int, nr_values, PCLK_MAX_VALUES);
418         atomic_set(&s->active, 0);
419         mutex_init(&s->lock);
420
421         reset_data(s);
422 }
423
424 static void
425 power_clk_work_func(struct work_struct *work)
426 {
427         read_source(&power_ctx.gpu, -1);
428         read_source(&power_ctx.emc, -1);
429
430         check_clks();
431 }
432
433 static DECLARE_WORK(power_clk_work, power_clk_work_func);
434
435 static void power_clk_timer(unsigned long data)
436 {
437         struct timer_list *timer = &power_ctx.timer;
438
439         schedule_work(&power_clk_work);
440         timer->expires = jiffies + msecs_to_jiffies(power_ctx.period);
441         add_timer(timer);
442 }
443
444 static void
445 read_all_sources_work_func(struct work_struct *work)
446 {
447         int cpu_id;
448         struct power_clk_source *s = &power_ctx.cpu;
449
450         for_each_possible_cpu(cpu_id)
451                 read_source(s, cpu_id);
452
453         read_source(&power_ctx.gpu, -1);
454         read_source(&power_ctx.emc, -1);
455
456         check_clks();
457         check_source(s);
458 }
459
460 static DECLARE_WORK(read_all_sources_work, read_all_sources_work_func);
461
462 int quadd_power_clk_start(void)
463 {
464         struct power_clk_source *s;
465         struct timer_list *timer = &power_ctx.timer;
466         struct quadd_parameters *param = &power_ctx.quadd_ctx->param;
467
468         if (param->power_rate_freq == 0) {
469                 pr_info("power_clk is not started\n");
470                 return 0;
471         }
472
473 #ifdef CONFIG_COMMON_CLK
474         power_ctx.period = 0;
475 #else
476         power_ctx.period = MSEC_PER_SEC / param->power_rate_freq;
477         pr_info("clk: use timer\n");
478 #endif
479
480         pr_info("power_clk: start, freq: %d\n",
481                 param->power_rate_freq);
482
483         /* setup gpu frequency */
484         s = &power_ctx.gpu;
485         s->clkp = clk_get_sys("3d", NULL);
486         if (!IS_ERR_OR_NULL(s->clkp)) {
487 #ifdef CONFIG_COMMON_CLK
488                 int status = clk_notifier_register(s->clkp, s->nb);
489
490                 if (status < 0) {
491                         pr_err("error: could not setup gpu freq\n");
492                         clk_put(s->clkp);
493                         return status;
494                 }
495 #endif
496                 clk_put(s->clkp);
497                 reset_data(s);
498                 atomic_set(&s->active, 1);
499         } else {
500                 pr_warn("warning: could not setup gpu freq\n");
501                 atomic_set(&s->active, 0);
502         }
503
504         /* setup emc frequency */
505         s = &power_ctx.emc;
506         s->clkp = clk_get_sys("cpu", "emc");
507         if (!IS_ERR_OR_NULL(s->clkp)) {
508 #ifdef CONFIG_COMMON_CLK
509                 int status = clk_notifier_register(s->clkp, s->nb);
510
511                 if (status < 0) {
512                         pr_err("error: could not setup emc freq\n");
513                         clk_put(s->clkp);
514                         return status;
515                 }
516 #endif
517                 clk_put(s->clkp);
518                 reset_data(s);
519                 atomic_set(&s->active, 1);
520         } else {
521                 pr_warn("warning: could not setup emc freq\n");
522                 atomic_set(&s->active, 0);
523         }
524
525         /* setup cpu frequency notifier */
526         s = &power_ctx.cpu;
527         reset_data(s);
528         atomic_set(&s->active, 1);
529
530         if (power_ctx.period > 0) {
531                 init_timer(timer);
532                 timer->function = power_clk_timer;
533                 timer->expires = jiffies + msecs_to_jiffies(power_ctx.period);
534                 timer->data = 0;
535                 add_timer(timer);
536         }
537
538         schedule_work(&read_all_sources_work);
539
540         return 0;
541 }
542
543 void quadd_power_clk_stop(void)
544 {
545         struct power_clk_source *s;
546         struct quadd_parameters *param = &power_ctx.quadd_ctx->param;
547
548         if (param->power_rate_freq == 0)
549                 return;
550
551         if (power_ctx.period > 0)
552                 del_timer_sync(&power_ctx.timer);
553
554         s = &power_ctx.gpu;
555         mutex_lock(&s->lock);
556         if (atomic_cmpxchg(&s->active, 1, 0)) {
557 #ifdef CONFIG_COMMON_CLK
558                 if (s->clkp)
559                         clk_notifier_unregister(s->clkp, s->nb);
560 #endif
561         }
562         mutex_unlock(&s->lock);
563
564         s = &power_ctx.emc;
565         mutex_lock(&s->lock);
566         if (atomic_cmpxchg(&s->active, 1, 0)) {
567 #ifdef CONFIG_COMMON_CLK
568                 if (s->clkp)
569                         clk_notifier_unregister(s->clkp, s->nb);
570 #endif
571         }
572         mutex_unlock(&s->lock);
573
574         s = &power_ctx.cpu;
575         mutex_lock(&s->lock);
576         atomic_set(&s->active, 0);
577         mutex_unlock(&s->lock);
578
579         pr_info("power_clk: stop\n");
580 }
581
582 int quadd_power_clk_init(struct quadd_ctx *quadd_ctx)
583 {
584         struct power_clk_source *s;
585
586         s = &power_ctx.gpu;
587         s->nb[PCLK_NB_GPU].notifier_call = gpu_notifier_call;
588         init_source(s, 1, QUADD_POWER_CLK_GPU);
589
590         s = &power_ctx.emc;
591         s->nb[PCLK_NB_EMC].notifier_call = emc_notifier_call;
592         init_source(s, 1, QUADD_POWER_CLK_EMC);
593
594         s = &power_ctx.cpu;
595         s->nb[PCLK_NB_CPU_FREQ].notifier_call = cpufreq_notifier_call;
596         s->nb[PCLK_NB_CPU_HOTPLUG].notifier_call = cpu_hotplug_notifier_call;
597         init_source(s, nr_cpu_ids, QUADD_POWER_CLK_CPU);
598
599         power_ctx.quadd_ctx = quadd_ctx;
600
601         cpufreq_register_notifier(&s->nb[PCLK_NB_CPU_FREQ],
602                                   CPUFREQ_TRANSITION_NOTIFIER);
603         register_cpu_notifier(&s->nb[PCLK_NB_CPU_HOTPLUG]);
604
605         return 0;
606 }
607
608 void quadd_power_clk_deinit(void)
609 {
610         struct power_clk_source *s = &power_ctx.cpu;
611
612         quadd_power_clk_stop();
613
614         cpufreq_unregister_notifier(&s->nb[PCLK_NB_CPU_FREQ],
615                                     CPUFREQ_TRANSITION_NOTIFIER);
616         unregister_cpu_notifier(&s->nb[PCLK_NB_CPU_HOTPLUG]);
617 }