// SPDX-License-Identifier: GPL-2.0 /* * Legacy Energy Model loading driver * * Copyright (C) 2018, ARM Ltd. * Written by: Quentin Perret, ARM Ltd. */ #define pr_fmt(fmt) "legacy-dt-em: " fmt #include #include #include #include #include #include #include #include #include static cpumask_var_t cpus_to_visit; static DEFINE_PER_CPU(unsigned long, nr_states) = 0; struct em_state { unsigned long frequency; unsigned long power; unsigned long capacity; }; static DEFINE_PER_CPU(struct em_state*, cpu_em) = NULL; static void finish_em_loading_workfn(struct work_struct *work); static DECLARE_WORK(finish_em_loading_work, finish_em_loading_workfn); static DEFINE_MUTEX(em_loading_mutex); /* * Callback given to the EM framework. All this does is browse the table * created by legacy_em_dt(). */ static int get_power(unsigned long *mW, unsigned long *KHz, int cpu) { unsigned long nstates = per_cpu(nr_states, cpu); struct em_state *em = per_cpu(cpu_em, cpu); int i; if (!nstates || !em) return -ENODEV; for (i = 0; i < nstates - 1; i++) { if (em[i].frequency > *KHz) break; } *KHz = em[i].frequency; *mW = em[i].power; return 0; } static int init_em_dt_callback(struct notifier_block *nb, unsigned long val, void *data) { struct em_data_callback em_cb = EM_DATA_CB(get_power); unsigned long nstates, scale_cpu, max_freq; struct cpufreq_policy *policy = data; const struct property *prop; struct device_node *cn, *cp; struct em_state *em; int cpu, i, ret = 0; const __be32 *tmp; if (val != CPUFREQ_NOTIFY) return 0; mutex_lock(&em_loading_mutex); /* Do not register twice an energy model */ for_each_cpu(cpu, policy->cpus) { if (per_cpu(nr_states, cpu) || per_cpu(cpu_em, cpu)) { pr_err("EM of CPU%d already loaded\n", cpu); ret = -EEXIST; goto unlock; } } max_freq = policy->cpuinfo.max_freq; if (!max_freq) { pr_err("No policy->max for CPU%d\n", cpu); ret = -EINVAL; goto unlock; } cpu = cpumask_first(policy->cpus); cn = of_get_cpu_node(cpu, NULL); if (!cn) { pr_err("No device_node for CPU%d\n", cpu); ret = -ENODEV; goto unlock; } cp = of_parse_phandle(cn, "sched-energy-costs", 0); if (!cp) { pr_err("CPU%d node has no sched-energy-costs\n", cpu); ret = -ENODEV; goto unlock; } prop = of_find_property(cp, "busy-cost-data", NULL); if (!prop || !prop->value) { pr_err("No busy-cost-data for CPU%d\n", cpu); ret = -ENODEV; goto unlock; } nstates = (prop->length / sizeof(u32)) / 2; em = kcalloc(nstates, sizeof(struct em_cap_state), GFP_KERNEL); if (!em) { ret = -ENOMEM; goto unlock; } /* Copy the capacity and power cost to the table. */ for (i = 0, tmp = prop->value; i < nstates; i++) { em[i].capacity = be32_to_cpup(tmp++); em[i].power = be32_to_cpup(tmp++); } /* Get the CPU capacity (according to the EM) */ scale_cpu = em[nstates - 1].capacity; if (!scale_cpu) { pr_err("CPU%d: capacity cannot be 0\n", cpu); kfree(em); ret = -EINVAL; goto unlock; } /* Re-compute the intermediate frequencies based on the EM. */ for (i = 0; i < nstates; i++) em[i].frequency = em[i].capacity * max_freq / scale_cpu; /* Assign the table to all CPUs of this policy. */ for_each_cpu(i, policy->cpus) { per_cpu(nr_states, i) = nstates; per_cpu(cpu_em, i) = em; } pr_info("Registering EM of %*pbl\n", cpumask_pr_args(policy->cpus)); em_register_perf_domain(policy->cpus, nstates, &em_cb); /* Finish the work when all possible CPUs have been registered. */ cpumask_andnot(cpus_to_visit, cpus_to_visit, policy->cpus); if (cpumask_empty(cpus_to_visit)) schedule_work(&finish_em_loading_work); unlock: mutex_unlock(&em_loading_mutex); return ret; } static struct notifier_block init_em_dt_notifier = { .notifier_call = init_em_dt_callback, }; static void finish_em_loading_workfn(struct work_struct *work) { cpufreq_unregister_notifier(&init_em_dt_notifier, CPUFREQ_POLICY_NOTIFIER); free_cpumask_var(cpus_to_visit); /* Let the scheduler know the Energy Model is ready. */ rebuild_sched_domains(); } static int __init register_cpufreq_notifier(void) { int ret; if (!alloc_cpumask_var(&cpus_to_visit, GFP_KERNEL)) return -ENOMEM; cpumask_copy(cpus_to_visit, cpu_possible_mask); ret = cpufreq_register_notifier(&init_em_dt_notifier, CPUFREQ_POLICY_NOTIFIER); if (ret) free_cpumask_var(cpus_to_visit); return ret; } core_initcall(register_cpufreq_notifier);