| /* | |
| * Gem5 Multi-cluster CPUFreq Interface driver | |
| * (adapted from vexpress_big_little.c) | |
| * | |
| * It provides necessary opp's to arm_gem5_mc.c cpufreq driver and gets | |
| * frequency information from gem5 energy controller device. | |
| * | |
| * Copyright (C) 2013 - 2014 ARM Ltd. | |
| * Authors: Akash Bagdia <Akash.bagdia@arm.com> | |
| * Vasileios Spiliopoulos <vasileios.spiliopoulos@arm.com> | |
| * | |
| * This program is free software; you can redistribute it and/or modify | |
| * it under the terms of the GNU General Public License version 2 as | |
| * published by the Free Software Foundation. | |
| * | |
| * This program is distributed "as is" WITHOUT ANY WARRANTY of any | |
| * kind, whether express or implied; without even the implied warranty | |
| * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| * GNU General Public License for more details. | |
| */ | |
| #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt | |
| #include <linux/cpufreq.h> | |
| #include <linux/export.h> | |
| #include <linux/pm_opp.h> | |
| #include <linux/module.h> | |
| #include <linux/types.h> | |
| #include <linux/gem5_energy_ctrl.h> | |
| #include "arm_gem5_mc.h" | |
| static int gem5_init_opp_table(struct device *cpu_dev) | |
| { | |
| int i = -1, count, cluster = cpu_to_cluster(cpu_dev->id); | |
| u32 *freq_table; | |
| u32 *volt_table; /* In micro volts */ | |
| int ret; | |
| count = gem5_energy_ctrl_get_opp_table(cluster, &freq_table, &volt_table); | |
| if (!freq_table || !count) { | |
| pr_err("gem5 energy controller returned invalid freq table"); | |
| return -EINVAL; | |
| } | |
| if (!volt_table || !count) { | |
| pr_err("gem5 energy controller returned invalid voltage table"); | |
| return -EINVAL; | |
| } | |
| while (++i < count) { | |
| ret = dev_pm_opp_add(cpu_dev, freq_table[i] * 1000, | |
| volt_table[i]); | |
| if (ret) { | |
| dev_warn(cpu_dev, | |
| "%s: Failed to add OPP freq %d, u-voltage %d,\ | |
| err: %d\n", | |
| __func__, freq_table[i] * 1000, | |
| volt_table[i], ret); | |
| return ret; | |
| } | |
| } | |
| return 0; | |
| } | |
| static int gem5_get_transition_latency(struct device *cpu_dev) | |
| { | |
| return gem5_energy_ctrl_get_trans_latency(); | |
| } | |
| static struct cpufreq_arm_mc_ops gem5_mc_ops = { | |
| .name = "gem5-mc", | |
| .get_transition_latency = gem5_get_transition_latency, | |
| .init_opp_table = gem5_init_opp_table, | |
| }; | |
| static int gem5_mc_init(void) | |
| { | |
| if (!gem5_energy_ctrl_check_loaded()) { | |
| pr_info("%s: No energy controller found\n", __func__); | |
| return -ENOENT; | |
| } | |
| if (!gem5_energy_ctrl_dvfs_enabled()) { | |
| pr_info("%s: DVFS handler in energy controller is disabled, \ | |
| ARM gem5 multi-cluster cpufreq driver \ | |
| will not be registered\n", | |
| __func__); | |
| return -ENOENT; | |
| } | |
| return mc_cpufreq_register(&gem5_mc_ops); | |
| } | |
| module_init(gem5_mc_init); | |
| static void gem5_mc_exit(void) | |
| { | |
| return mc_cpufreq_unregister(&gem5_mc_ops); | |
| } | |
| module_exit(gem5_mc_exit); | |
| MODULE_DESCRIPTION("ARM gem5 multi-cluster cpufreq driver"); | |
| MODULE_LICENSE("GPL"); |