| /* |
| * 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"); |