+ }
+}
+
+static void check_source(struct power_clk_source *s)
+{
+ mutex_lock(&s->lock);
+
+ if (!is_data_changed(s)) {
+ mutex_unlock(&s->lock);
+ return;
+ }
+
+ pr_debug("cpu: %lu/%lu/%lu/%lu\n",
+ power_ctx.cpu.data[0].value,
+ power_ctx.cpu.data[1].value,
+ power_ctx.cpu.data[2].value,
+ power_ctx.cpu.data[3].value);
+
+ update_data(s);
+ mutex_unlock(&s->lock);
+
+ make_sample();
+}
+
+static void
+read_source(struct power_clk_source *s, int cpu)
+{
+ mutex_lock(&s->lock);
+
+ switch (s->type) {
+ case QUADD_POWER_CLK_CPU:
+ /* update cpu frequency */
+ if (cpu < 0 || cpu >= POWER_CLK_MAX_VALUES) {
+ pr_err_once("error: cpu id: %d\n", cpu);
+ break;
+ }
+
+ s->data[cpu].value = cpufreq_get(cpu);
+ pr_debug("QUADD_POWER_CLK_CPU, cpu: %d, value: %lu\n",
+ cpu, s->data[cpu].value);
+ break;
+
+ case QUADD_POWER_CLK_GPU:
+ /* update gpu frequency */
+ s->clkp = clk_get_sys("3d", NULL);
+ if (!IS_ERR_OR_NULL(s->clkp)) {
+ s->data[0].value =
+ clk_get_rate(s->clkp) / 1000;
+ clk_put(s->clkp);
+ }
+ pr_debug("QUADD_POWER_CLK_GPU, value: %lu\n",
+ s->data[0].value);
+ break;
+
+ case QUADD_POWER_CLK_EMC:
+ /* update emc frequency */
+ s->clkp = clk_get_sys("cpu", "emc");
+ if (!IS_ERR_OR_NULL(s->clkp)) {
+ s->data[0].value =
+ clk_get_rate(s->clkp) / 1000;
+ clk_put(s->clkp);
+ }
+ pr_debug("QUADD_POWER_CLK_EMC, value: %lu\n",
+ s->data[0].value);
+ break;
+
+ default:
+ pr_err_once("error: invalid power_clk type\n");
+ return;
+ }
+
+ s->counter++;
+ mutex_unlock(&s->lock);
+}
+
+static int
+gpu_notifier_call(struct notifier_block *nb,
+ unsigned long action, void *data)
+{
+ read_source(&power_ctx.gpu, -1);
+ check_clks();
+
+ return NOTIFY_DONE;
+}
+
+static int
+emc_notifier_call(struct notifier_block *nb,
+ unsigned long action, void *data)
+{
+ read_source(&power_ctx.emc, -1);
+ check_clks();
+
+ return NOTIFY_DONE;
+}
+
+static void
+read_cpufreq(struct power_clk_source *s, struct cpufreq_freqs *freq)
+{
+ int cpu, cpufreq;
+
+ mutex_lock(&s->lock);
+
+ if (!atomic_read(&s->active)) {
+ mutex_unlock(&s->lock);
+ return;
+ }
+
+ cpu = freq->cpu;
+ cpufreq = freq->new;
+
+ if (cpu >= POWER_CLK_MAX_VALUES) {
+ pr_err_once("error: cpu id: %d\n", cpu);
+ mutex_unlock(&s->lock);
+ return;
+ }
+
+ s->data[cpu].value = cpufreq;
+
+ pr_debug("[%d] cpufreq: %u --> %u\n",
+ cpu, freq->old, cpufreq);
+
+ mutex_unlock(&s->lock);
+
+ check_source(s);
+}
+
+static int
+cpufreq_notifier_call(struct notifier_block *nb,
+ unsigned long action, void *hcpu)
+{
+ struct cpufreq_freqs *freq;
+ struct power_clk_source *s = &power_ctx.cpu;
+
+ if (!atomic_read(&s->active))
+ return 0;
+
+ if (action == CPUFREQ_POSTCHANGE ||
+ action == CPUFREQ_RESUMECHANGE) {
+ freq = hcpu;
+ read_cpufreq(s, freq);
+ }
+
+ return 0;