blob: 046eb7fdbfa4ba4cf2870e2391c6c7167c23b4a1 [file] [log] [blame]
/*
* 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);