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