2 * drivers/misc/tegra-profiler/power_clk.c
4 * Copyright (c) 2013-2017, NVIDIA CORPORATION. All rights reserved.
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.
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
17 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
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>
27 #include <linux/tegra_profiler.h>
29 #include "power_clk.h"
35 #define PCLK_MAX_VALUES 32
37 struct power_clk_data {
51 #define PCLK_NB_MAX PCLK_NB_CPU_MAX
53 struct power_clk_source {
57 struct notifier_block nb[PCLK_NB_MAX];
60 struct power_clk_data data[PCLK_MAX_VALUES];
66 struct power_clk_context_s {
67 struct power_clk_source cpu;
68 struct power_clk_source gpu;
69 struct power_clk_source emc;
71 struct timer_list timer;
74 struct quadd_ctx *quadd_ctx;
78 QUADD_POWER_CLK_CPU = 1,
83 static struct power_clk_context_s power_ctx;
85 static void make_sample(void)
88 u32 extra_cpus[NR_CPUS];
89 struct power_clk_source *s;
90 struct quadd_iovec vec;
92 struct quadd_record_data record;
93 struct quadd_power_rate_data *power_rate = &record.power_rate;
95 record.record_type = QUADD_RECORD_TYPE_POWER_RATE;
97 power_rate->time = quadd_get_time();
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;
106 power_rate->nr_cpus = 0;
108 mutex_unlock(&s->lock);
111 mutex_lock(&s->lock);
112 if (atomic_read(&s->active))
113 power_rate->gpu = s->data[0].value;
117 mutex_unlock(&s->lock);
120 mutex_lock(&s->lock);
121 if (atomic_read(&s->active))
122 power_rate->emc = s->data[0].value;
126 mutex_unlock(&s->lock);
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);
132 vec.base = extra_cpus;
133 vec.len = power_rate->nr_cpus * sizeof(extra_cpus[0]);
135 quadd_put_sample(&record, &vec, 1);
139 make_sample_hotplug(int cpu, int is_online)
141 struct quadd_record_data record;
142 struct quadd_hotplug_data *s = &record.hotplug;
144 record.record_type = QUADD_RECORD_TYPE_HOTPLUG;
147 s->is_online = is_online ? 1 : 0;
148 s->time = quadd_get_time();
151 quadd_put_sample(&record, NULL, 0);
155 is_data_changed(struct power_clk_source *s)
159 for (i = 0; i < s->nr; i++) {
160 if (s->data[i].value != s->data[i].prev)
168 update_data(struct power_clk_source *s)
172 for (i = 0; i < s->nr; i++)
173 s->data[i].prev = s->data[i].value;
176 static void check_clks(void)
178 struct power_clk_source *s;
182 mutex_lock(&s->lock);
183 if (is_data_changed(s)) {
187 mutex_unlock(&s->lock);
190 mutex_lock(&s->lock);
191 if (is_data_changed(s)) {
195 mutex_unlock(&s->lock);
198 pr_debug("gpu: %lu, emc: %lu\n",
199 power_ctx.gpu.data[0].value,
200 power_ctx.emc.data[0].value);
206 static void check_source(struct power_clk_source *s)
208 mutex_lock(&s->lock);
210 if (!is_data_changed(s))
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);
220 mutex_unlock(&s->lock);
226 mutex_unlock(&s->lock);
230 read_source(struct power_clk_source *s, int cpu)
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);
242 value = cpufreq_get(cpu);
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);
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)) {
257 clk_get_rate(s->clkp) / 1000;
260 pr_debug("QUADD_POWER_CLK_GPU, value: %lu\n",
262 mutex_unlock(&s->lock);
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)) {
271 clk_get_rate(s->clkp) / 1000;
274 pr_debug("QUADD_POWER_CLK_EMC, value: %lu\n",
276 mutex_unlock(&s->lock);
280 pr_err_once("error: invalid power_clk type\n");
286 gpu_notifier_call(struct notifier_block *nb,
287 unsigned long action, void *data)
289 read_source(&power_ctx.gpu, -1);
296 emc_notifier_call(struct notifier_block *nb,
297 unsigned long action, void *data)
299 read_source(&power_ctx.emc, -1);
306 read_cpufreq(struct power_clk_source *s, struct cpufreq_freqs *freq)
310 mutex_lock(&s->lock);
312 if (!atomic_read(&s->active))
318 pr_debug("cpu: %d, cpufreq: %d\n", cpu, cpufreq);
321 pr_err_once("error: cpu id: %d\n", cpu);
325 s->data[cpu].value = cpufreq;
327 pr_debug("[%d] cpufreq: %u --> %u\n",
328 cpu, freq->old, cpufreq);
330 mutex_unlock(&s->lock);
335 mutex_unlock(&s->lock);
339 cpufreq_notifier_call(struct notifier_block *nb,
340 unsigned long action, void *hcpu)
342 struct cpufreq_freqs *freq;
343 struct power_clk_source *s = &power_ctx.cpu;
345 if (!atomic_read(&s->active))
348 pr_debug("action: %lu\n", action);
350 if (action == CPUFREQ_POSTCHANGE) {
352 read_cpufreq(s, freq);
359 cpu_hotplug_notifier_call(struct notifier_block *nb,
360 unsigned long action, void *hcpu)
363 struct power_clk_source *s = &power_ctx.cpu;
365 if (!atomic_read(&s->active))
370 pr_debug("cpu: %d, action: %lu\n", cpu, action);
373 pr_err_once("error: cpu id: %d\n", cpu);
379 case CPU_ONLINE_FROZEN:
380 make_sample_hotplug(cpu, 1);
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);
390 make_sample_hotplug(cpu, 0);
400 static void reset_data(struct power_clk_source *s)
404 mutex_lock(&s->lock);
405 for (i = 0; i < s->nr; i++) {
406 s->data[i].value = 0;
409 mutex_unlock(&s->lock);
412 static void init_source(struct power_clk_source *s,
417 s->nr = min_t(int, nr_values, PCLK_MAX_VALUES);
418 atomic_set(&s->active, 0);
419 mutex_init(&s->lock);
425 power_clk_work_func(struct work_struct *work)
427 read_source(&power_ctx.gpu, -1);
428 read_source(&power_ctx.emc, -1);
433 static DECLARE_WORK(power_clk_work, power_clk_work_func);
435 static void power_clk_timer(unsigned long data)
437 struct timer_list *timer = &power_ctx.timer;
439 schedule_work(&power_clk_work);
440 timer->expires = jiffies + msecs_to_jiffies(power_ctx.period);
445 read_all_sources_work_func(struct work_struct *work)
448 struct power_clk_source *s = &power_ctx.cpu;
450 for_each_possible_cpu(cpu_id)
451 read_source(s, cpu_id);
453 read_source(&power_ctx.gpu, -1);
454 read_source(&power_ctx.emc, -1);
460 static DECLARE_WORK(read_all_sources_work, read_all_sources_work_func);
462 int quadd_power_clk_start(void)
464 struct power_clk_source *s;
465 struct timer_list *timer = &power_ctx.timer;
466 struct quadd_parameters *param = &power_ctx.quadd_ctx->param;
468 if (param->power_rate_freq == 0) {
469 pr_info("power_clk is not started\n");
473 #ifdef CONFIG_COMMON_CLK
474 power_ctx.period = 0;
476 power_ctx.period = MSEC_PER_SEC / param->power_rate_freq;
477 pr_info("clk: use timer\n");
480 pr_info("power_clk: start, freq: %d\n",
481 param->power_rate_freq);
483 /* setup gpu frequency */
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);
491 pr_err("error: could not setup gpu freq\n");
498 atomic_set(&s->active, 1);
500 pr_warn("warning: could not setup gpu freq\n");
501 atomic_set(&s->active, 0);
504 /* setup emc frequency */
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);
512 pr_err("error: could not setup emc freq\n");
519 atomic_set(&s->active, 1);
521 pr_warn("warning: could not setup emc freq\n");
522 atomic_set(&s->active, 0);
525 /* setup cpu frequency notifier */
528 atomic_set(&s->active, 1);
530 if (power_ctx.period > 0) {
532 timer->function = power_clk_timer;
533 timer->expires = jiffies + msecs_to_jiffies(power_ctx.period);
538 schedule_work(&read_all_sources_work);
543 void quadd_power_clk_stop(void)
545 struct power_clk_source *s;
546 struct quadd_parameters *param = &power_ctx.quadd_ctx->param;
548 if (param->power_rate_freq == 0)
551 if (power_ctx.period > 0)
552 del_timer_sync(&power_ctx.timer);
555 mutex_lock(&s->lock);
556 if (atomic_cmpxchg(&s->active, 1, 0)) {
557 #ifdef CONFIG_COMMON_CLK
559 clk_notifier_unregister(s->clkp, s->nb);
562 mutex_unlock(&s->lock);
565 mutex_lock(&s->lock);
566 if (atomic_cmpxchg(&s->active, 1, 0)) {
567 #ifdef CONFIG_COMMON_CLK
569 clk_notifier_unregister(s->clkp, s->nb);
572 mutex_unlock(&s->lock);
575 mutex_lock(&s->lock);
576 atomic_set(&s->active, 0);
577 mutex_unlock(&s->lock);
579 pr_info("power_clk: stop\n");
582 int quadd_power_clk_init(struct quadd_ctx *quadd_ctx)
584 struct power_clk_source *s;
587 s->nb[PCLK_NB_GPU].notifier_call = gpu_notifier_call;
588 init_source(s, 1, QUADD_POWER_CLK_GPU);
591 s->nb[PCLK_NB_EMC].notifier_call = emc_notifier_call;
592 init_source(s, 1, QUADD_POWER_CLK_EMC);
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);
599 power_ctx.quadd_ctx = quadd_ctx;
601 cpufreq_register_notifier(&s->nb[PCLK_NB_CPU_FREQ],
602 CPUFREQ_TRANSITION_NOTIFIER);
603 register_cpu_notifier(&s->nb[PCLK_NB_CPU_HOTPLUG]);
608 void quadd_power_clk_deinit(void)
610 struct power_clk_source *s = &power_ctx.cpu;
612 quadd_power_clk_stop();
614 cpufreq_unregister_notifier(&s->nb[PCLK_NB_CPU_FREQ],
615 CPUFREQ_TRANSITION_NOTIFIER);
616 unregister_cpu_notifier(&s->nb[PCLK_NB_CPU_HOTPLUG]);