gem5: Adding support for the gem5 energy controller

The energy controller enables DVFS (dynamic voltage and frequency
scaling support) of gem5 for up to 32 independent domains (or
clusters).  The changes are modelled somewhat after the VExpress SPC
component, but are specific to gem5.

Change-Id: I161913cb72bd9a8515f4e63686452e94ef222106
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
Reviewed-by: Andreas Sandberg <andreas.sandberg@arm.com>
Reviewed-by: Geoffrey Blake <geoffrey.blake@arm.com>
[Ported to gem5's 4.9 kernel. Squashed fixups in 4.4 branch.]
Signed-off-by: Andreas Sandberg <andreas.sandberg@arm.com>
[Ported to gem5's 4.14 kernel.]
Signed-off-by: Andreas Sandberg <andreas.sandberg@arm.com>
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index 1c4e1aa..fd48fbe 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -226,6 +226,11 @@
 	  This driver supports the IDT VersaClock 5 and VersaClock 6
 	  programmable clock generators.
 
+config GEM5_CPUFREQ_CLK
+	bool "Clk driver for GEM5 Platform"
+	---help---
+	  Enable clk hardware for cpufreq in gem5 platforms
+
 source "drivers/clk/bcm/Kconfig"
 source "drivers/clk/hisilicon/Kconfig"
 source "drivers/clk/imgtec/Kconfig"
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index f7f761b..37556b2 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -98,3 +98,5 @@
 endif
 obj-$(CONFIG_ARCH_ZX)			+= zte/
 obj-$(CONFIG_ARCH_ZYNQ)			+= zynq/
+obj-$(CONFIG_GEM5_CPUFREQ_CLK)		+= gem5/
+
diff --git a/drivers/clk/gem5/Makefile b/drivers/clk/gem5/Makefile
new file mode 100644
index 0000000..0af147b
--- /dev/null
+++ b/drivers/clk/gem5/Makefile
@@ -0,0 +1,2 @@
+# Makefile for Gem5-specific clocks
+obj-y	+= clk-gem5-energy-ctrl.o
diff --git a/drivers/clk/gem5/clk-gem5-energy-ctrl.c b/drivers/clk/gem5/clk-gem5-energy-ctrl.c
new file mode 100644
index 0000000..046eb7f
--- /dev/null
+++ b/drivers/clk/gem5/clk-gem5-energy-ctrl.c
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2013-2014,2016 ARM Limited
+ * Copyright (C) 2013 Linaro
+ *
+ * Authors: Akash Bagdia <Akash.bagdia@arm.com>
+ *          Vasileios Spiliopoulos <vasileios.spiliopoulos@arm.com>
+ *          Sascha Bischoff <sascha.bischoff@arm.com>
+ * (code adapted from clk-vexpress-spc.c)
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2. This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ */
+
+/* PE Controller clock programming interface for Gem5 Platform cpus */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/clkdev.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/gem5_energy_ctrl.h>
+
+struct clk_energy_ctrl {
+	struct clk_hw hw;
+	spinlock_t *lock;
+	int cluster;
+};
+
+#define to_clk_energy_ctrl(ec) container_of(ec, struct clk_energy_ctrl, hw)
+
+static unsigned long energy_ctrl_recalc_rate(struct clk_hw *hw,
+		unsigned long parent_rate)
+{
+	struct clk_energy_ctrl *energy_ctrl = to_clk_energy_ctrl(hw);
+	u32 freq;
+
+	if (gem5_energy_ctrl_get_performance(energy_ctrl->cluster, &freq)) {
+		return -EIO;
+		pr_err("%s: Failed", __func__);
+	}
+
+	return freq * 1000;
+}
+
+static long energy_ctrl_round_rate(struct clk_hw *hw, unsigned long drate,
+		unsigned long *parent_rate)
+{
+	return drate;
+}
+
+static int energy_ctrl_set_rate(struct clk_hw *hw, unsigned long rate,
+		unsigned long parent_rate)
+{
+	struct clk_energy_ctrl *energy_ctrl = to_clk_energy_ctrl(hw);
+
+	return gem5_energy_ctrl_set_performance(energy_ctrl->cluster, rate / 1000);
+}
+
+static struct clk_ops clk_energy_ctrl_ops = {
+	.recalc_rate = energy_ctrl_recalc_rate,
+	.round_rate = energy_ctrl_round_rate,
+	.set_rate = energy_ctrl_set_rate,
+};
+
+struct clk *gem5_clk_register_energy_ctrl(const char *name, int cluster_id)
+{
+	struct clk_init_data init;
+	struct clk_energy_ctrl *energy_ctrl;
+	struct clk *clk;
+
+	if (!name) {
+		pr_err("Invalid name passed");
+		return ERR_PTR(-EINVAL);
+	}
+
+	energy_ctrl = kzalloc(sizeof(*energy_ctrl), GFP_KERNEL);
+	if (!energy_ctrl) {
+		pr_err("could not allocate energy_ctrl clk\n");
+		return ERR_PTR(-ENOMEM);
+	}
+
+	energy_ctrl->hw.init = &init;
+	energy_ctrl->cluster = cluster_id;
+
+	init.name = name;
+	init.ops = &clk_energy_ctrl_ops;
+	init.flags = CLK_GET_RATE_NOCACHE;
+	init.num_parents = 0;
+
+	clk = clk_register(NULL, &energy_ctrl->hw);
+	if (!IS_ERR_OR_NULL(clk))
+		return clk;
+
+	pr_err("clk register failed\n");
+	kfree(energy_ctrl);
+
+	return NULL;
+}
+
+/*
+ * Register clocks for each CPU cluster we can find in the DT.
+ *
+ * Ideally, we parse the information from the cpu-map child of the cpus node.
+ * Failing that, we fall back to the old method using clusters.
+ */
+static void __init gem5_clk_of_register_energy_ctrl(struct device_node *ctrl_node)
+{
+	char name[16];
+	struct device_node *node = NULL, *cn = NULL, *map = NULL;
+	struct clk *clk;
+	const u32 *val;
+	int cluster_id = 0, len, cpu;
+
+	if (!of_find_compatible_node(NULL, NULL, "arm,gem5-energy-ctrl")) {
+		pr_err("%s: No EC found, Exiting!!\n", __func__);
+		return;
+	}
+
+	cn = of_find_node_by_path("/cpus");
+	if (!cn) {
+		pr_err("No CPU information found in DT\n");
+		return;
+	}
+
+	map = of_get_child_by_name(cn, "cpu-map");
+	if (!map) {
+		pr_warn("No cpu-map in DT! Falling back to old detection method!\n");
+
+		goto old_detection;
+	}
+
+	/*
+	 * We loop over the clusters in the cpu-map, and add a clock for each
+	 * cluster present.
+	 */
+	cluster_id = 0;
+	do {
+		snprintf(name, sizeof(name), "cluster%d", cluster_id);
+		node = of_get_child_by_name(map, name);
+		if (node) {
+			snprintf(name, sizeof(name), "cpu-cluster.%d", cluster_id);
+			clk = gem5_clk_register_energy_ctrl(name, cluster_id);
+			if (IS_ERR(clk)) {
+				of_node_put(node);
+				return;
+			}
+
+			pr_debug("Registered clock '%s'\n", name);
+			clk_register_clkdev(clk, NULL, name);
+			of_node_put(node);
+		}
+		cluster_id++;
+	} while (node);
+	return;
+
+old_detection:
+	/*
+	 * Fall back to looking at the non-standard clusters node in the DTB.
+	 */
+	cn = of_find_node_by_path("/clusters");
+	if (!cn) {
+		pr_warn("No clusters in DT! Falling back to using CPU topology!\n");
+		goto cpu_topology;
+	}
+	while ((node = of_find_node_by_name(node, "cluster"))) {
+		val = of_get_property(node, "reg", &len);
+		if (val && len == 4)
+			cluster_id = be32_to_cpup(val);
+
+		snprintf(name, sizeof(name), "cpu-cluster.%d", cluster_id);
+		clk = gem5_clk_register_energy_ctrl(name, cluster_id);
+		if (IS_ERR(clk))
+			return;
+
+		pr_debug("Registered clock '%s'\n", name);
+		clk_register_clkdev(clk, NULL, name);
+	}
+	return;
+
+cpu_topology:
+	/*
+	 * Look at the CPU topology itself. Might not match what we are trying to
+	 * simulate. We need to check if we have already registered the clock as
+	 * there is no guarantee that the CPUs are ordered sensibly.
+	 */
+	for_each_possible_cpu(cpu) {
+		cluster_id = topology_physical_package_id(cpu);
+		snprintf(name, sizeof(name), "cpu-cluster.%d", cluster_id);
+
+		if (IS_ERR(clk_get_sys(name, NULL))) {
+			clk = gem5_clk_register_energy_ctrl(name, cluster_id);
+			if (IS_ERR(clk))
+				return;
+
+			pr_debug("Registered clock '%s'\n", name);
+			clk_register_clkdev(clk, NULL, name);
+		}
+	}
+}
+
+CLK_OF_DECLARE(energy_ctrl, "arm,gem5-energy-ctrl", gem5_clk_of_register_energy_ctrl);
diff --git a/drivers/cpufreq/Kconfig.arm b/drivers/cpufreq/Kconfig.arm
index bdce448..5843766 100644
--- a/drivers/cpufreq/Kconfig.arm
+++ b/drivers/cpufreq/Kconfig.arm
@@ -40,6 +40,27 @@
 	  This enables probing via DT for Generic CPUfreq driver for ARM
 	  big.LITTLE platform. This gets frequency tables from DT.
 
+config ARM_GEM5_MULTI_CLUSTER_CPUFREQ
+	tristate "Generic ARM Multi-Cluster CPUfreq driver"
+	depends on PM_OPP && HAVE_CLK
+	depends on (ARM_CPU_TOPOLOGY && ARM) || ARM64
+	depends on !ARM_BIG_LITTLE_CPUFREQ
+	depends on ARCH_GEM5_ENERGY_CTRL
+	select CPU_FREQ_TABLE
+	select GEM5_CPUFREQ_CLK
+	help
+	  This enables the Generic CPUfreq driver for ARM gem5 multi-cluster
+	  platforms.
+
+config ARCH_GEM5_ENERGY_CTRL
+	bool "Gem5 Energy Controller"
+	depends on (ARM || ARM64)
+	select ARCH_HAS_CPUFREQ
+	select ARCH_HAS_OPP
+	select PM_OPP
+	help
+	  Select options available for the gem5 energy controller related configs
+
 config ARM_VEXPRESS_SPC_CPUFREQ
         tristate "Versatile Express SPC based CPUfreq driver"
 	depends on ARM_BIG_LITTLE_CPUFREQ && ARCH_VEXPRESS_SPC
diff --git a/drivers/cpufreq/Makefile b/drivers/cpufreq/Makefile
index 812f9e0..83fba1e 100644
--- a/drivers/cpufreq/Makefile
+++ b/drivers/cpufreq/Makefile
@@ -53,6 +53,11 @@
 obj-$(CONFIG_ARM_DT_BL_CPUFREQ)		+= arm_big_little_dt.o
 
 obj-$(CONFIG_ARM_BRCMSTB_AVS_CPUFREQ)	+= brcmstb-avs-cpufreq.o
+
+# multi-cluster configuration support for a configurable gem5 platform.
+obj-$(CONFIG_ARM_GEM5_MULTI_CLUSTER_CPUFREQ)	+= arm_gem5_mc.o gem5_energy_ctrl_mc.o
+obj-$(CONFIG_ARCH_GEM5_ENERGY_CTRL)		+= gem5-energy-ctrl.o
+
 obj-$(CONFIG_ARCH_DAVINCI)		+= davinci-cpufreq.o
 obj-$(CONFIG_ARM_EXYNOS5440_CPUFREQ)	+= exynos5440-cpufreq.o
 obj-$(CONFIG_ARM_HIGHBANK_CPUFREQ)	+= highbank-cpufreq.o
diff --git a/drivers/cpufreq/arm_gem5_mc.c b/drivers/cpufreq/arm_gem5_mc.c
new file mode 100644
index 0000000..c36fe73
--- /dev/null
+++ b/drivers/cpufreq/arm_gem5_mc.c
@@ -0,0 +1,351 @@
+/*
+ * ARM Multi-Cluster Platforms CPUFreq support for Gem5
+ *
+ * Copyright (C) 2013-2014,2016 ARM Ltd.
+ *
+ * Akash Bagdia <akash.bagdia@arm.com>
+ * Vasileios Spiliopoulos <vasileios.spiliopoulos@arm.com>
+ * Sascha Bischoff <sascha.bischoff@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/clk.h>
+#include <linux/cpu.h>
+#include <linux/cpufreq.h>
+#include <linux/cpumask.h>
+#include <linux/export.h>
+#include <linux/mutex.h>
+#include <linux/of_platform.h>
+#include <linux/pm_opp.h>
+#include <linux/slab.h>
+#include <linux/topology.h>
+#include <linux/types.h>
+
+#include "arm_gem5_mc.h"
+
+static struct cpufreq_arm_mc_ops *arm_mc_ops;
+static struct clk *clk[MAX_CLUSTERS];
+static struct cpufreq_frequency_table *freq_table[MAX_CLUSTERS + 1];
+static atomic_t cluster_usage[MAX_CLUSTERS + 1] = {ATOMIC_INIT(0),
+	ATOMIC_INIT(0)};
+static struct mutex cluster_lock[MAX_CLUSTERS];
+
+static unsigned int clk_get_cpu_rate(unsigned int cpu)
+{
+	u32 cur_cluster = cpu_to_cluster(cpu);
+	u32 rate = clk_get_rate(clk[cur_cluster]) / 1000;
+
+	pr_debug("%s: cpu: %d, cluster: %d, freq: %u\n", __func__, cpu,
+		 cur_cluster, rate);
+
+	return rate;
+}
+
+static unsigned int mc_cpufreq_get_rate(unsigned int cpu)
+{
+	return clk_get_cpu_rate(cpu);
+}
+
+static unsigned int
+mc_cpufreq_set_rate(u32 cpu, u32 new_cluster, u32 rate)
+{
+	int ret;
+
+	pr_debug("%s: cpu: %d, new cluster: %d, freq: %d\n",
+		 __func__, cpu, new_cluster, rate);
+
+	mutex_lock(&cluster_lock[new_cluster]);
+	ret = clk_set_rate(clk[new_cluster], rate * 1000);
+	mutex_unlock(&cluster_lock[new_cluster]);
+
+	if (WARN_ON(ret)) {
+		pr_err("clk_set_rate failed: %d, new cluster: %d\n", ret,
+		       new_cluster);
+	}
+	return ret;
+}
+
+/* Validate policy frequency range */
+static int mc_cpufreq_verify_policy(struct cpufreq_policy *policy)
+{
+	u32 cur_cluster = cpu_to_cluster(policy->cpu);
+
+	return cpufreq_frequency_table_verify(policy, freq_table[cur_cluster]);
+}
+
+/* Set clock frequency */
+static int mc_cpufreq_set_target(struct cpufreq_policy *policy,
+				 unsigned int target_freq,
+				 unsigned int relation)
+{
+	struct cpufreq_freqs freqs;
+	u32 cpu = policy->cpu, freq_tab_idx, cur_cluster;
+	int ret = 0;
+
+	cur_cluster = cpu_to_cluster(cpu);
+
+	freqs.old = mc_cpufreq_get_rate(cpu);
+
+	/* Determine valid target frequency using freq_table */
+	freq_tab_idx = cpufreq_frequency_table_target(policy, target_freq,
+						      relation);
+	freqs.new = freq_table[cur_cluster][freq_tab_idx].frequency;
+
+	pr_debug("%s: cpu: %d, cluster: %d, oldfreq: %d, target freq: %d, new freq: %d\n",
+		 __func__, cpu, cur_cluster, freqs.old, target_freq,
+		 freqs.new);
+
+	if (freqs.old == freqs.new)
+		return 0;
+
+	cpufreq_freq_transition_begin(policy, &freqs);
+	ret = mc_cpufreq_set_rate(cpu, cur_cluster, freqs.new);
+	cpufreq_freq_transition_end(policy, &freqs, ret);
+
+	if (ret)
+		freqs.new = freqs.old;
+
+	return ret;
+}
+
+static inline u32 get_table_count(struct cpufreq_frequency_table *table)
+{
+	int count;
+
+	for (count = 0; table[count].frequency != CPUFREQ_TABLE_END; count++)
+		;
+
+	return count;
+}
+
+/* get the minimum frequency in the cpufreq_frequency_table */
+static inline u32 get_table_min(struct cpufreq_frequency_table *table)
+{
+	int i;
+	uint32_t min_freq = ~0;
+	for (i = 0; (table[i].frequency != CPUFREQ_TABLE_END); i++)
+		if (table[i].frequency < min_freq)
+			min_freq = table[i].frequency;
+	return min_freq;
+}
+
+/* get the maximum frequency in the cpufreq_frequency_table */
+static inline u32 get_table_max(struct cpufreq_frequency_table *table)
+{
+	int i;
+	uint32_t max_freq = 0;
+	for (i = 0; (table[i].frequency != CPUFREQ_TABLE_END); i++)
+		if (table[i].frequency > max_freq)
+			max_freq = table[i].frequency;
+	return max_freq;
+}
+
+static void _put_cluster_clk_and_freq_table(struct device *cpu_dev)
+{
+	u32 cluster = cpu_to_cluster(cpu_dev->id);
+
+	if (!atomic_dec_return(&cluster_usage[cluster])) {
+		clk_put(clk[cluster]);
+		dev_pm_opp_free_cpufreq_table(cpu_dev, &freq_table[cluster]);
+		dev_dbg(cpu_dev, "%s: cluster: %d\n", __func__, cluster);
+	}
+}
+
+static void put_cluster_clk_and_freq_table(struct device *cpu_dev)
+{
+	u32 cluster = cpu_to_cluster(cpu_dev->id);
+
+	if (cluster < MAX_CLUSTERS)
+		return _put_cluster_clk_and_freq_table(cpu_dev);
+}
+
+static int _get_cluster_clk_and_freq_table(struct device *cpu_dev)
+{
+	u32 cluster = cpu_to_cluster(cpu_dev->id);
+	char name[14] = "cpu-cluster.";
+	int ret;
+
+	if (atomic_inc_return(&cluster_usage[cluster]) != 1)
+		return 0;
+
+	ret = arm_mc_ops->init_opp_table(cpu_dev);
+	if (ret) {
+		dev_err(cpu_dev, "%s: init_opp_table failed, cpu: %d, err: %d\n",
+				__func__, cpu_dev->id, ret);
+		goto atomic_dec;
+	}
+
+	ret = dev_pm_opp_init_cpufreq_table(cpu_dev, &freq_table[cluster]);
+	if (ret) {
+		dev_err(cpu_dev, "%s: failed to init cpufreq table, cpu: %d, err: %d\n",
+				__func__, cpu_dev->id, ret);
+		goto atomic_dec;
+	}
+
+	name[12] = cluster + '0';
+	clk[cluster] = clk_get_sys(name, NULL);
+
+
+	if (!IS_ERR(clk[cluster])) {
+		dev_dbg(cpu_dev, "%s: clk: %p & freq table: %p, cluster: %d\n",
+				__func__, clk[cluster], freq_table[cluster],
+				cluster);
+		return 0;
+	}
+
+	dev_err(cpu_dev, "%s: Failed to get clk for cpu: %d, cluster: %d\n",
+			__func__, cpu_dev->id, cluster);
+	ret = PTR_ERR(clk[cluster]);
+	dev_pm_opp_free_cpufreq_table(cpu_dev, &freq_table[cluster]);
+
+atomic_dec:
+	atomic_dec(&cluster_usage[cluster]);
+	dev_err(cpu_dev, "%s: Failed to get data for cluster: %d\n", __func__,
+			cluster);
+	return ret;
+}
+
+static int get_cluster_clk_and_freq_table(struct device *cpu_dev)
+{
+	u32 cluster = cpu_to_cluster(cpu_dev->id);
+
+	if (cluster < MAX_CLUSTERS)
+		return _get_cluster_clk_and_freq_table(cpu_dev);
+
+	return 0;
+}
+
+/* Per-CPU initialization */
+static int mc_cpufreq_init(struct cpufreq_policy *policy)
+{
+	u32 cur_cluster = cpu_to_cluster(policy->cpu);
+	struct device *cpu_dev;
+	int ret;
+
+	cpu_dev = get_cpu_device(policy->cpu);
+	if (!cpu_dev) {
+		pr_err("%s: failed to get cpu%d device\n", __func__,
+				policy->cpu);
+		return -ENODEV;
+	}
+
+	ret = get_cluster_clk_and_freq_table(cpu_dev);
+	if (ret)
+		return ret;
+
+	ret = cpufreq_table_validate_and_show(policy, freq_table[cur_cluster]);
+	if (ret)
+		return ret;
+
+	ret = cpufreq_frequency_table_verify(policy, freq_table[cur_cluster]);
+	if (ret)
+		return ret;
+
+	if (cur_cluster < MAX_CLUSTERS) {
+		cpumask_copy(policy->cpus, topology_core_cpumask(policy->cpu));
+	} else {
+		pr_err("Invalid current cluster %d\n", cur_cluster);
+		return -ENODEV;
+	}
+
+	if (arm_mc_ops->get_transition_latency)
+		policy->cpuinfo.transition_latency =
+			arm_mc_ops->get_transition_latency(cpu_dev);
+	else
+		policy->cpuinfo.transition_latency = CPUFREQ_ETERNAL;
+
+	policy->cur = clk_get_cpu_rate(policy->cpu);
+
+	dev_info(cpu_dev, "%s: CPU %d initialized\n", __func__, policy->cpu);
+	return 0;
+}
+
+/* Export freq_table to sysfs */
+static struct freq_attr *mc_cpufreq_attr[] = {
+	&cpufreq_freq_attr_scaling_available_freqs,
+	NULL,
+};
+
+static struct cpufreq_driver mc_cpufreq_driver = {
+	.name			= "arm-gem5-mc",
+	.flags			= CPUFREQ_STICKY |
+					CPUFREQ_HAVE_GOVERNOR_PER_POLICY,
+	.verify			= mc_cpufreq_verify_policy,
+	.target			= mc_cpufreq_set_target,
+	.get			= mc_cpufreq_get_rate,
+	.init			= mc_cpufreq_init,
+	.attr			= mc_cpufreq_attr,
+};
+
+int mc_cpufreq_register(struct cpufreq_arm_mc_ops *ops)
+{
+	int ret, i;
+
+	if (arm_mc_ops) {
+		pr_debug("%s: Already registered: %s, exiting\n", __func__,
+				arm_mc_ops->name);
+		return -EBUSY;
+	}
+
+	if (!ops || !strlen(ops->name) || !ops->init_opp_table) {
+		pr_err("%s: Invalid arm_mc_ops, exiting\n", __func__);
+		return -ENODEV;
+	}
+
+	arm_mc_ops = ops;
+
+	for (i = 0; i < MAX_CLUSTERS; i++)
+		mutex_init(&cluster_lock[i]);
+
+	ret = cpufreq_register_driver(&mc_cpufreq_driver);
+	if (ret) {
+		pr_info("%s: Failed registering platform driver: %s, err: %d\n",
+				__func__, ops->name, ret);
+		arm_mc_ops = NULL;
+	} else {
+		pr_info("%s: Registered platform driver: %s\n",
+				__func__, ops->name);
+	}
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(mc_cpufreq_register);
+
+void mc_cpufreq_unregister(struct cpufreq_arm_mc_ops *ops)
+{
+	int i;
+
+	if (arm_mc_ops != ops) {
+		pr_err("%s: Registered with: %s, can't unregister, exiting\n",
+				__func__, arm_mc_ops->name);
+		return;
+	}
+
+	cpufreq_unregister_driver(&mc_cpufreq_driver);
+	pr_info("%s: Un-registered platform driver: %s\n", __func__,
+			arm_mc_ops->name);
+
+	for (i = 0; i < MAX_CLUSTERS; i++) {
+		struct device *cdev = get_cpu_device(i);
+		if (!cdev) {
+			pr_err("%s: failed to get cpu%d device\n",
+					__func__, i);
+			return;
+		}
+
+		put_cluster_clk_and_freq_table(cdev);
+	}
+
+	arm_mc_ops = NULL;
+}
+EXPORT_SYMBOL_GPL(mc_cpufreq_unregister);
diff --git a/drivers/cpufreq/arm_gem5_mc.h b/drivers/cpufreq/arm_gem5_mc.h
new file mode 100644
index 0000000..77fa767
--- /dev/null
+++ b/drivers/cpufreq/arm_gem5_mc.h
@@ -0,0 +1,47 @@
+/*
+ * ARM GEM5 Multi-cluster platform's CPUFreq header file
+ *
+ * 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.
+ */
+#ifndef CPUFREQ_ARM_GEM5_MC_H
+#define CPUFREQ_ARM_GEM5_MC_H
+
+#include <linux/cpufreq.h>
+#include <linux/device.h>
+#include <linux/types.h>
+
+/* Currently we support n=32 clusters */
+#define MAX_CLUSTERS	32
+
+struct cpufreq_arm_mc_ops {
+	char name[CPUFREQ_NAME_LEN];
+	int (*get_transition_latency)(struct device *cpu_dev);
+
+	/*
+	 * This must set opp table for cpu_dev in a similar way as done by
+	 * of_init_opp_table().
+	 */
+	int (*init_opp_table)(struct device *cpu_dev);
+};
+
+static inline int cpu_to_cluster(int cpu)
+{
+	return topology_physical_package_id(cpu);
+}
+
+int mc_cpufreq_register(struct cpufreq_arm_mc_ops *ops);
+void mc_cpufreq_unregister(struct cpufreq_arm_mc_ops *ops);
+
+#endif /* CPUFREQ_ARM_GEM5_MC_H */
diff --git a/drivers/cpufreq/gem5-energy-ctrl.c b/drivers/cpufreq/gem5-energy-ctrl.c
new file mode 100644
index 0000000..7d0cbd7
--- /dev/null
+++ b/drivers/cpufreq/gem5-energy-ctrl.c
@@ -0,0 +1,380 @@
+/*
+ * Gem5 Energy Controller support
+ * (code adapted from vexpress-spc)
+ *
+ * Copyright (C) 2013-2014, 2016 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.
+ */
+
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/gem5_energy_ctrl.h>
+
+// Register addresses
+#define DVFS_HANDLER_STATUS 0x00
+#define DVFS_NUM_DOMAINS 0x04
+#define DVFS_DOMAINID_AT_INDEX 0x08
+#define DVFS_HANDLER_TRANS_LATENCY 0x0C
+#define DOMAIN_ID  0x10
+#define PERF_LEVEL 0x14
+#define PERF_LEVEL_ACK 0x18
+#define NUM_OF_PERF_LEVELS 0x1C
+#define PERF_LEVEL_TO_READ 0x20
+#define FREQ_AT_PERF_LEVEL 0x24
+#define VOLT_AT_PERF_LEVEL 0x28
+
+#define TIME_OUT	100
+#define GEM5_MAX_NUM_DOMAINS	32
+
+struct gem5_energy_ctrl_drvdata {
+	void __iomem *baseaddr;
+	bool dvfs_handler_status;
+	u32 num_gem5_domains;
+	u32 *freqs[GEM5_MAX_NUM_DOMAINS];
+	u32 *voltages[GEM5_MAX_NUM_DOMAINS];
+	int opp_cnt[GEM5_MAX_NUM_DOMAINS];
+	u32 domain_ids[GEM5_MAX_NUM_DOMAINS];
+	spinlock_t lock;
+};
+
+static struct gem5_energy_ctrl_drvdata *info;
+
+static int gem5_energy_ctrl_load_result = -EAGAIN;
+
+static bool gem5_energy_ctrl_initialized(void)
+{
+	return gem5_energy_ctrl_load_result == 0;
+}
+
+bool gem5_energy_ctrl_dvfs_enabled(void)
+{
+	return info->dvfs_handler_status;
+}
+EXPORT_SYMBOL_GPL(gem5_energy_ctrl_dvfs_enabled);
+
+static u32 index_of_domain_id(u32 domain_id)
+{
+	u32 i;
+
+	for(i = 0; i < info->num_gem5_domains; i++)
+		if(domain_id == info->domain_ids[i])
+			return i;
+	return GEM5_MAX_NUM_DOMAINS;
+}
+
+u32 gem5_energy_ctrl_get_trans_latency(void)
+{
+	u32 data;
+
+	if (!gem5_energy_ctrl_initialized() || !info->dvfs_handler_status)
+		return -EINVAL;
+
+	spin_lock(&info->lock);
+	data = readl(info->baseaddr + DVFS_HANDLER_TRANS_LATENCY);
+	spin_unlock(&info->lock);
+
+	return data;
+}
+EXPORT_SYMBOL_GPL(gem5_energy_ctrl_get_trans_latency);
+
+/**
+ * gem5_energy_ctrl_get_performance - get current performance level of domain
+ * @domain_id: mpidr[15:8] bitfield describing domain's affinity level
+ * @freq: pointer to the performance level to be assigned
+ *
+ * Return: 0 on success
+ *         < 0 on read error
+ */
+int gem5_energy_ctrl_get_performance(u32 domain_id, u32 *freq)
+{
+	int perf;
+	u32 domain_index;
+
+	if (!gem5_energy_ctrl_initialized() || !info->dvfs_handler_status)
+		return -EINVAL;
+
+	domain_index = index_of_domain_id(domain_id);
+	if(domain_index >= info->num_gem5_domains)
+		return -EINVAL;
+
+	spin_lock(&info->lock);
+	writel(domain_id, info->baseaddr + DOMAIN_ID);
+	perf = readl(info->baseaddr + PERF_LEVEL);
+	spin_unlock(&info->lock);
+	*freq = info->freqs[domain_index][perf];
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(gem5_energy_ctrl_get_performance);
+
+static int gem5_energy_ctrl_find_perf_index(u32 domain_index, u32 freq)
+{
+	int idx;
+
+	for (idx = 0; idx < info->opp_cnt[domain_index]; idx++)
+		if (info->freqs[domain_index][idx] == freq)
+			return idx;
+
+	return -EINVAL;
+}
+
+
+static inline int read_wait_to(void __iomem *reg, int status, int timeout)
+{
+	while (timeout-- && readl(reg) != status) {
+		cpu_relax();
+		udelay(2);
+	}
+	if (!timeout)
+		return -EAGAIN;
+	else
+		return 0;
+}
+
+/**
+ * gem5_energy_ctrl_set_performance - set current performance level of domain
+ *
+ * @domain_id: mpidr[15:8] bitfield describing domain's affinity level
+ * @freq: performance level to be programmed
+ *
+ * Returns: 0 on success
+ *          < 0 on write error
+ */
+int gem5_energy_ctrl_set_performance(u32 domain_id, u32 freq)
+{
+	int ret, perf;
+	u32 domain_index;
+
+        ret = 0;
+
+	if (!gem5_energy_ctrl_initialized() || !info->dvfs_handler_status)
+		return -EINVAL;
+
+	domain_index = index_of_domain_id(domain_id);
+	if(domain_index >= info->num_gem5_domains)
+		return -EINVAL;
+
+	perf = gem5_energy_ctrl_find_perf_index(domain_index, freq);
+	if (perf < 0)
+		return -EINVAL;
+
+	spin_lock(&info->lock);
+	writel(domain_id, info->baseaddr + DOMAIN_ID);
+	writel(perf, info->baseaddr + PERF_LEVEL);
+	//Some logic to determine successful setting of perf level
+	if (read_wait_to(info->baseaddr + PERF_LEVEL_ACK, 1, TIME_OUT))
+		ret = -EAGAIN;
+	spin_unlock(&info->lock);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(gem5_energy_ctrl_set_performance);
+
+/**
+ * gem5_energy_ctrl_populate_opps() - initialize opp tables from energy ctrl
+ *
+ * @domain_id: mpidr[15:8] bitfield describing domain's affinity level
+ *
+ * Return: 0 on success
+ *         < 0 on error
+ */
+static int gem5_energy_ctrl_populate_opps(u32 domain_id)
+{
+	u32 data = 0, i;
+	u32 domain_index = index_of_domain_id(domain_id);
+
+	if (!info->dvfs_handler_status ||
+		WARN_ON_ONCE(domain_index >= info->num_gem5_domains)) {
+		return -EINVAL;
+	}
+
+	spin_lock(&info->lock);
+
+	writel(domain_id, info->baseaddr + DOMAIN_ID);
+
+	if (readl(info->baseaddr + DOMAIN_ID) != domain_id) {
+		spin_unlock(&info->lock);
+		return -EINVAL;
+	}
+
+	data = readl(info->baseaddr + NUM_OF_PERF_LEVELS);
+	info->opp_cnt[domain_index] = data;
+
+	info->freqs[domain_index] = kzalloc(sizeof(u32) *
+				info->opp_cnt[domain_index], GFP_KERNEL);
+
+	info->voltages[domain_index] = kzalloc(sizeof(u32) *
+				info->opp_cnt[domain_index], GFP_KERNEL);
+
+	for (i = 0; i < info->opp_cnt[domain_index] ; i++) {
+		writel(i, info->baseaddr + PERF_LEVEL_TO_READ);
+		data = readl(info->baseaddr + FREQ_AT_PERF_LEVEL);
+		info->freqs[domain_index][i] = data;
+		data = readl(info->baseaddr + VOLT_AT_PERF_LEVEL);
+		info->voltages[domain_index][i] = data;
+	}
+
+	spin_unlock(&info->lock);
+
+	return 0;
+}
+
+/**
+ * gem5_energy_ctrl_get_opp_table() - Retrieve a pointer to the frequency,
+ *                voltage tables for a given domain
+ *
+ * @domain_id: mpidr[15:8] bitfield describing domain's affinity level
+ * @fptr: pointer to be initialized
+ * Return: operating points count on success
+ *         -EINVAL on pointer error
+ */
+int gem5_energy_ctrl_get_opp_table(u32 domain_id, u32 **fptr, u32 **vptr)
+{
+	u32 domain_index;
+
+	if (!gem5_energy_ctrl_initialized() || !fptr || !vptr || !info ||
+            !info->dvfs_handler_status)
+		return -EINVAL;
+
+	domain_index = index_of_domain_id(domain_id);
+	if(domain_index >= info->num_gem5_domains)
+		return -EINVAL;
+
+	*fptr = info->freqs[domain_index];
+	*vptr = info->voltages[domain_index];
+	return info->opp_cnt[domain_index];
+}
+EXPORT_SYMBOL_GPL(gem5_energy_ctrl_get_opp_table);
+
+static const struct of_device_id gem5_energy_ctrl_ids[] __initconst = {
+	{ .compatible = "arm,gem5-energy-ctrl" },
+	{},
+};
+
+static int __init gem5_energy_ctrl_init(void)
+{
+	int ret;
+	u32 i, data;
+	struct device_node *node = of_find_matching_node(NULL,
+							 gem5_energy_ctrl_ids);
+
+	if (!node)
+		return -ENODEV;
+
+	info = kzalloc(sizeof(*info), GFP_KERNEL);
+	if (!info) {
+		pr_err("%s: unable to allocate mem\n", __func__);
+		return -ENOMEM;
+	}
+
+	info->dvfs_handler_status = 0;
+	info->num_gem5_domains = 0;
+
+	info->baseaddr = of_iomap(node, 0);
+	if (WARN_ON(!info->baseaddr)) {
+		ret = -ENXIO;
+		goto mem_free;
+	}
+
+	spin_lock_init(&info->lock);
+
+	info->dvfs_handler_status = readl(info->baseaddr + DVFS_HANDLER_STATUS);
+
+	if (!info->dvfs_handler_status) {
+		pr_info("gem5 DVFS handler is disabled\n");
+	} else {
+		info->num_gem5_domains = readl(info->baseaddr +
+						DVFS_NUM_DOMAINS);
+		if (info->num_gem5_domains > GEM5_MAX_NUM_DOMAINS) {
+			pr_err("gem5 DVFS handler manages more domains than\
+				supported by the gem5 energy controller driver\n");
+			ret = -ENODEV;
+			goto unmap;
+		}
+		else {
+			/* Get domain ID information
+			* Populate operation table for all the domains(clusters)
+			* managed by the controller
+			*/
+			for(i = 0;i < info->num_gem5_domains; i++) {
+				spin_lock(&info->lock);
+				writel(i, info->baseaddr +
+					DVFS_DOMAINID_AT_INDEX);
+				data = readl(info->baseaddr +
+					DVFS_DOMAINID_AT_INDEX);
+				spin_unlock(&info->lock);
+				info->domain_ids[i] = data;
+				if(gem5_energy_ctrl_populate_opps(info->domain_ids[i]))
+				{
+					pr_err("failed to build OPP table for\
+						%d domain\n",
+					info->domain_ids[i]);
+					ret = -ENODEV;
+					goto unmap;
+				}
+			}
+		}
+	}
+	pr_info("gem5-energy-ctrl loaded at %p\n", info->baseaddr);
+	return 0;
+
+unmap:
+	pr_info("gem5-energy-ctrl unmapped at %p, possible error in syncing with "\
+		"the device\n", info->baseaddr);
+	iounmap(info->baseaddr);
+
+mem_free:
+	kfree(info);
+	return ret;
+}
+
+static bool __init __gem5_energy_ctrl_check_loaded(void);
+/*
+ * Pointer spc_check_loaded is swapped after init hence it is safe
+ * to initialize it to a function in the __init section
+ */
+static bool (*energy_ctrl_check_loaded)(void) __refdata = \
+     &__gem5_energy_ctrl_check_loaded;
+
+static bool __init __gem5_energy_ctrl_check_loaded(void)
+{
+	if (gem5_energy_ctrl_load_result == -EAGAIN)
+		gem5_energy_ctrl_load_result = gem5_energy_ctrl_init();
+	energy_ctrl_check_loaded = &gem5_energy_ctrl_initialized;
+	return gem5_energy_ctrl_initialized();
+}
+
+/*
+ * Function exported to manage early_initcall ordering.
+ * SPC code is needed very early in the boot process
+ * to bring CPUs out of reset and initialize power
+ * management back-end. After boot swap pointers to
+ * make the functionality check available to loadable
+ * modules, when early boot init functions have been
+ * already freed from kernel address space.
+ */
+bool gem5_energy_ctrl_check_loaded(void)
+{
+	return energy_ctrl_check_loaded();
+}
+EXPORT_SYMBOL_GPL(gem5_energy_ctrl_check_loaded);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/cpufreq/gem5_energy_ctrl_mc.c b/drivers/cpufreq/gem5_energy_ctrl_mc.c
new file mode 100644
index 0000000..c64d4fe
--- /dev/null
+++ b/drivers/cpufreq/gem5_energy_ctrl_mc.c
@@ -0,0 +1,103 @@
+/*
+ * 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");
diff --git a/include/linux/gem5_energy_ctrl.h b/include/linux/gem5_energy_ctrl.h
new file mode 100644
index 0000000..eadb854
--- /dev/null
+++ b/include/linux/gem5_energy_ctrl.h
@@ -0,0 +1,28 @@
+/*
+ * 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 in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * Copyright (C) 2013 - 2014 ARM Limited
+ * Authors: Akash Bagdia <Akash.bagdia@arm.com>
+ *          Vasileios Spiliopoulos <vasileios.spiliopoulos@arm.com>
+ */
+
+#ifndef _LINUX_GEM5_ENERGY_CTRL_H
+#define _LINUX_GEM5_ENERGY_CTRL_H
+
+/* Energy Controller */
+
+extern bool gem5_energy_ctrl_check_loaded(void);
+extern bool gem5_energy_ctrl_dvfs_enabled(void);
+extern u32 gem5_energy_ctrl_get_trans_latency(void);
+extern int gem5_energy_ctrl_get_opp_table(u32, u32 **, u32 **);
+extern int gem5_energy_ctrl_get_performance(u32, u32 *);
+extern int gem5_energy_ctrl_set_performance(u32, u32);
+
+#endif