blob: 88890a649c8855855e32c4c6c5a9b42db3af9c90 [file] [edit]
// SPDX-License-Identifier: GPL-2.0-only
/*
* GS101 SoC Exynos ACME (A Cpufreq that Meets Every chipset) driver
*
* Copyright (c) 2019 Samsung Electronics Co., Ltd.
* http://www.samsung.com
*
* GS101 based board files should include this file.
*
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/init.h>
#include <linux/of.h>
#include <linux/slab.h>
#include <linux/cpu.h>
#include <linux/cpumask.h>
#include <linux/cpufreq.h>
#include <linux/tick.h>
#include <linux/pm_opp.h>
#include <linux/suspend.h>
#include <linux/platform_device.h>
#include <soc/google/cal-if.h>
#include <soc/google/ect_parser.h>
#include <soc/google/exynos-cpupm.h>
#include <soc/google/exynos_cpu_cooling.h>
#include <soc/google/debug-snapshot.h>
#include <trace/hooks/systrace.h>
#include <trace/events/power.h>
#include "exynos-acme.h"
#if IS_ENABLED(CONFIG_SOC_ZUMA)
#include <soc/google/gs_tmu_v3.h>
#endif
/*
* list head of cpufreq domain
*/
static LIST_HEAD(domains);
/*********************************************************************
* HELPER FUNCTION *
*********************************************************************/
static struct exynos_cpufreq_domain *find_domain(unsigned int cpu)
{
struct exynos_cpufreq_domain *domain;
list_for_each_entry(domain, &domains, list)
if (cpumask_test_cpu(cpu, &domain->cpus))
return domain;
pr_err("cannot find cpufreq domain by cpu\n");
return NULL;
}
static void enable_domain(struct exynos_cpufreq_domain *domain)
{
mutex_lock(&domain->lock);
domain->enabled = true;
mutex_unlock(&domain->lock);
}
static void disable_domain(struct exynos_cpufreq_domain *domain)
{
mutex_lock(&domain->lock);
domain->enabled = false;
mutex_unlock(&domain->lock);
}
/*********************************************************************
* PRE/POST HANDLING FOR SCALING *
*********************************************************************/
static int pre_scale(struct cpufreq_freqs *freqs)
{
return 0;
}
static int post_scale(struct cpufreq_freqs *freqs)
{
return 0;
}
/*********************************************************************
* FREQUENCY SCALING *
*********************************************************************/
/*
* Depending on cluster structure, it cannot be possible to get/set
* cpu frequency while cluster is off. For this, disable cluster-wide
* power mode while getting/setting frequency.
*/
static unsigned int get_freq(struct exynos_cpufreq_domain *domain)
{
int wakeup_flag = 0;
unsigned int freq;
struct cpumask temp;
cpumask_and(&temp, &domain->cpus, cpu_online_mask);
if (cpumask_empty(&temp))
return domain->old;
if (domain->need_awake) {
if (likely(domain->old))
return domain->old;
wakeup_flag = 1;
disable_power_mode(cpumask_any(&domain->cpus), POWERMODE_TYPE_CLUSTER);
}
freq = (unsigned int)cal_dfs_get_rate(domain->cal_id);
if (!freq) {
/* On changing state, CAL returns 0 */
freq = domain->old;
}
if (unlikely(wakeup_flag))
enable_power_mode(cpumask_any(&domain->cpus), POWERMODE_TYPE_CLUSTER);
return freq;
}
static int set_freq(struct exynos_cpufreq_domain *domain,
unsigned int target_freq)
{
int err;
dbg_snapshot_printk("ID %d: %d -> %d (%d)\n",
domain->id, domain->old, target_freq, DSS_FLAG_IN);
if (domain->need_awake)
disable_power_mode(cpumask_any(&domain->cpus), POWERMODE_TYPE_CLUSTER);
err = cal_dfs_set_rate(domain->cal_id, target_freq);
if (err < 0) {
pr_err("failed to scale frequency of domain%d (%d -> %d)\n",
domain->id, domain->old, target_freq);
} else {
trace_clock_set_rate(domain->dn->full_name, target_freq,
raw_smp_processor_id());
}
if (domain->need_awake)
enable_power_mode(cpumask_any(&domain->cpus), POWERMODE_TYPE_CLUSTER);
dbg_snapshot_printk("ID %d: %d -> %d (%d)\n", domain->id, domain->old,
target_freq, DSS_FLAG_OUT);
return err;
}
static int scale(struct exynos_cpufreq_domain *domain,
struct cpufreq_policy *policy,
unsigned int target_freq)
{
int ret;
struct cpufreq_freqs freqs = {
.policy = policy,
.old = domain->old,
.new = target_freq,
.flags = 0,
};
cpufreq_freq_transition_begin(policy, &freqs);
dbg_snapshot_freq(domain->id, domain->old, target_freq, DSS_FLAG_IN);
ret = pre_scale(&freqs);
if (ret)
goto fail_scale;
/* Scale frequency by hooked function, set_freq() */
ret = set_freq(domain, target_freq);
if (ret)
goto fail_scale;
ret = post_scale(&freqs);
if (ret)
goto fail_scale;
fail_scale:
/* In scaling failure case, logs -1 to exynos snapshot */
dbg_snapshot_freq(domain->id, domain->old, target_freq,
ret < 0 ? ret : DSS_FLAG_OUT);
cpufreq_freq_transition_end(policy, &freqs, ret);
return ret;
}
/*********************************************************************
* THERMAL PRESSURE *
*********************************************************************/
/*
* TJ and TSKIN are the two actors that could apply thermal pressure independently
*/
static void apply_thermal_pressure(struct exynos_cpufreq_domain *domain,
unsigned long capped_freq, int thermal_actor)
{
cpumask_t *maskp;
unsigned long arch_capped_freq;
if (!domain || (domain->capped_freq[thermal_actor] == capped_freq))
return;
maskp = &domain->cpus;
spin_lock(&domain->thermal_update_lock);
domain->capped_freq[thermal_actor] = capped_freq;
arch_capped_freq = min(domain->capped_freq[TJ], domain->capped_freq[TSKIN]);
arch_update_thermal_pressure(maskp, arch_capped_freq);
spin_unlock(&domain->thermal_update_lock);
if (unlikely(trace_clock_set_rate_enabled()))
trace_clock_set_rate(domain->capped_freq_name[thermal_actor],
domain->capped_freq[thermal_actor],
raw_smp_processor_id());
}
#if IS_ENABLED(CONFIG_EXYNOS_CPU_THERMAL) && IS_ENABLED(CONFIG_SOC_ZUMA)
/*
* ECT table is in ascending order of frequencies
* domain->freq_table is in descending order of frequencies
* domain->freq_table may have fewer number of frequencies compared to ECT table
*/
static int get_freq_table_index(struct exynos_cpufreq_domain *domain, int ECT_index)
{
int freq_index_max = (int)(domain->table_size - 1);
int freq_index = (int)(freq_index_max - ECT_index + domain->ect_table_offset);
return max(0, min(freq_index, freq_index_max));
}
/*
* When device thermals throttle the CPUs, we notify the scheduler of
* capacity change using this callback function
*/
static void exynos_cpufreq_set_tj_pressure_cb(struct cpumask *maskp, int cdev_index)
{
int first_cpu = cpumask_first(maskp);
struct exynos_cpufreq_domain *domain = find_domain(first_cpu);
unsigned int tj_freq;
int freq_index;
if (!domain)
return;
freq_index = get_freq_table_index(domain, cdev_index);
tj_freq = domain->freq_table[freq_index].frequency;
apply_thermal_pressure(domain, tj_freq, TJ);
}
/*********************************************************************
* EXYNOS CPUFREQ ECT POWER TABLE OFFSET *
*********************************************************************/
static int exynos_cpufreq_get_power_table_ect_offset(struct cpumask *maskp, int *offset)
{
struct exynos_cpufreq_domain *domain;
list_for_each_entry (domain, &domains, list)
if (cpumask_subset(&domain->cpus, maskp)) {
*offset = domain->ect_table_offset;
return 0;
}
return -ENODEV;
}
#endif
/*********************************************************************
* EXYNOS CPUFREQ DRIVER INTERFACE *
*********************************************************************/
static int exynos_cpufreq_init(struct cpufreq_policy *policy)
{
struct exynos_cpufreq_domain *domain = find_domain(policy->cpu);
if (!domain)
return -EINVAL;
policy->freq_table = domain->freq_table;
policy->cur = get_freq(domain);
policy->cpuinfo.transition_latency = TRANSITION_LATENCY;
policy->dvfs_possible_from_any_cpu = true;
cpumask_copy(policy->cpus, &domain->cpus);
pr_info("CPUFREQ domain%d registered\n", domain->id);
return 0;
}
static int exynos_cpufreq_online(struct cpufreq_policy *policy)
{
struct exynos_cpufreq_domain *domain;
/*
* CPU frequency is not changed before cpufreq_resume() is called.
* Therefore, if it is called by enable_nonboot_cpus(),
* it is ignored.
*/
if (cpuhp_tasks_frozen)
return 0;
domain = find_domain(policy->cpu);
if (!domain)
return 0;
enable_domain(domain);
freq_qos_update_request(&domain->max_qos_req, domain->max_freq);
return 0;
}
static int exynos_cpufreq_offline(struct cpufreq_policy *policy)
{
struct exynos_cpufreq_domain *domain;
/*
* CPU frequency is not changed after cpufreq_suspend() is called.
* Therefore, if it is called by disable_nonboot_cpus(),
* it is ignored.
*/
if (cpuhp_tasks_frozen)
return 0;
domain = find_domain(policy->cpu);
if (!domain)
return 0;
freq_qos_update_request(&domain->max_qos_req, domain->min_freq);
disable_domain(domain);
return 0;
}
static int exynos_cpufreq_verify(struct cpufreq_policy_data *new_policy)
{
struct exynos_cpufreq_domain *domain = find_domain(new_policy->cpu);
struct cpufreq_policy policy;
unsigned int min_freq, max_freq;
int index, ret;
if (!domain)
return -EINVAL;
policy.freq_table = new_policy->freq_table;
index = cpufreq_table_find_index_ah(&policy, new_policy->max, false);
if (index == -1) {
pr_err("%s : failed to find a proper max frequency\n", __func__);
return -EINVAL;
}
max_freq = policy.freq_table[index].frequency;
index = cpufreq_table_find_index_al(&policy, new_policy->min, false);
if (index == -1) {
pr_err("%s : failed to find a proper min frequency\n", __func__);
return -EINVAL;
}
min_freq = policy.freq_table[index].frequency;
domain->max_freq_qos = max_freq;
domain->min_freq_qos = min_freq;
new_policy->max = domain->max_freq_qos;
new_policy->min = domain->min_freq_qos;
policy_update_call_to_DM(domain->dm_type,
domain->min_freq_qos,
domain->max_freq_qos);
ret = cpufreq_frequency_table_verify(new_policy, domain->freq_table);
if (!ret)
apply_thermal_pressure(domain, new_policy->max, TSKIN);
return ret;
}
static int __exynos_cpufreq_target(struct cpufreq_policy *policy,
unsigned int target_freq)
{
struct exynos_cpufreq_domain *domain = find_domain(policy->cpu);
int ret = 0;
if (!domain)
return -EINVAL;
ATRACE_BEGIN(__func__);
mutex_lock(&domain->lock);
if (!domain->enabled) {
ret = -EINVAL;
goto out;
}
#if IS_ENABLED(CONFIG_EXYNOS_CPU_THERMAL) && IS_ENABLED(CONFIG_SOC_ZUMA)
if (!cpumask_subset(&domain->cpus, &tmu_enabled_mask)) {
ret = -EINVAL;
goto out;
}
#endif
if (target_freq > domain->user_max_qos_req.pnode.prio) {
ret = -EINVAL;
goto out;
}
/* Target is same as current, skip scaling */
if (domain->old == target_freq)
goto out;
ret = scale(domain, policy, target_freq);
if (ret)
goto out;
pr_debug("CPUFREQ domain%d frequency change %u kHz -> %u kHz\n",
domain->id, domain->old, target_freq);
domain->old = target_freq;
out:
mutex_unlock(&domain->lock);
ATRACE_END();
return ret;
}
static int exynos_cpufreq_target_index(struct cpufreq_policy *policy,
unsigned int index)
{
struct exynos_cpufreq_domain *domain = find_domain(policy->cpu);
unsigned long target_freq;
int ret = 0;
ATRACE_BEGIN(__func__);
if (!domain || !domain->enabled) {
ret = -EINVAL;
goto out;
}
target_freq = policy->freq_table[index].frequency;
// Always go to DM_CALL even with `domain->old == freq` to update dm->governor_freq
if (!target_freq) {
goto out;
}
if (list_empty(&domain->dm_list)) {
ret = __exynos_cpufreq_target(policy, target_freq);
goto out;
}
ret = DM_CALL(domain->dm_type, &target_freq);
out:
ATRACE_END();
return ret;
}
static unsigned int exynos_cpufreq_get(unsigned int cpu)
{
struct exynos_cpufreq_domain *domain = find_domain(cpu);
if (!domain)
return 0;
return get_freq(domain);
}
static int __exynos_cpufreq_suspend(struct cpufreq_policy *policy,
struct exynos_cpufreq_domain *domain)
{
unsigned int freq;
struct work_struct *update_work = &policy->update;
if (!domain)
return 0;
freq = domain->resume_freq;
freq_qos_update_request(policy->min_freq_req, freq);
freq_qos_update_request(policy->max_freq_req, freq);
flush_work(update_work);
return 0;
}
static int exynos_cpufreq_suspend(struct cpufreq_policy *policy)
{
return __exynos_cpufreq_suspend(policy, find_domain(policy->cpu));
}
static int __exynos_cpufreq_resume(struct cpufreq_policy *policy,
struct exynos_cpufreq_domain *domain)
{
if (!domain)
return -EINVAL;
freq_qos_update_request(policy->max_freq_req, domain->max_freq);
freq_qos_update_request(policy->min_freq_req, domain->min_freq);
return 0;
}
static int exynos_cpufreq_resume(struct cpufreq_policy *policy)
{
return __exynos_cpufreq_resume(policy, find_domain(policy->cpu));
}
static int exynos_cpufreq_pm_notifier(struct notifier_block *notifier,
unsigned long pm_event, void *v)
{
struct exynos_cpufreq_domain *domain;
struct cpufreq_policy *policy;
switch (pm_event) {
case PM_SUSPEND_PREPARE:
list_for_each_entry_reverse(domain, &domains, list) {
policy = cpufreq_cpu_get(cpumask_any(&domain->cpus));
if (!policy)
continue;
if (__exynos_cpufreq_suspend(policy, domain))
return NOTIFY_BAD;
}
break;
case PM_POST_SUSPEND:
list_for_each_entry(domain, &domains, list) {
policy = cpufreq_cpu_get(cpumask_any(&domain->cpus));
if (!policy)
continue;
if (__exynos_cpufreq_resume(policy, domain))
return NOTIFY_BAD;
}
break;
}
return NOTIFY_OK;
}
static struct notifier_block exynos_cpufreq_pm = {
.notifier_call = exynos_cpufreq_pm_notifier,
.priority = INT_MAX,
};
static struct cpufreq_driver exynos_driver = {
.name = "exynos_cpufreq",
.flags = CPUFREQ_HAVE_GOVERNOR_PER_POLICY | CPUFREQ_ASYNC_NOTIFICATION |
CPUFREQ_NEED_UPDATE_LIMITS, // force update dm->governor_freq
.init = exynos_cpufreq_init,
.verify = exynos_cpufreq_verify,
.target_index = exynos_cpufreq_target_index,
.get = exynos_cpufreq_get,
.online = exynos_cpufreq_online,
.offline = exynos_cpufreq_offline,
.suspend = exynos_cpufreq_suspend,
.resume = exynos_cpufreq_resume,
.attr = cpufreq_generic_attr,
};
/*********************************************************************
* CPUFREQ SYSFS *
*********************************************************************/
static ssize_t resume_freq_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
ssize_t count = 0;
struct exynos_cpufreq_domain *domain;
list_for_each_entry(domain, &domains, list)
count += sysfs_emit_at(buf, count, "cpu%d: resume_freq: %d\n",
cpumask_first(&domain->cpus),
domain->resume_freq);
return count;
}
static ssize_t resume_freq_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
int freq, cpu, ret;
struct exynos_cpufreq_domain *domain;
ret = sscanf(buf, "%d %8d", &cpu, &freq);
if (ret!=2)
return -EINVAL;
if (cpu < 0 || cpu >= num_possible_cpus() || freq < 0)
return -EINVAL;
domain = find_domain(cpu);
if (!domain)
return -EINVAL;
domain->resume_freq = freq;
return count;
}
static ssize_t freq_qos_min_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
ssize_t count = 0;
struct exynos_cpufreq_domain *domain;
list_for_each_entry(domain, &domains, list)
count += snprintf(buf + count, 30, "cpu%d: qos_min: %d\n",
cpumask_first(&domain->cpus),
domain->user_min_qos_req.pnode.prio);
return count;
}
static ssize_t freq_qos_min_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
int freq, cpu, ret;
struct exynos_cpufreq_domain *domain;
ret = sscanf(buf, "%d %8d", &cpu, &freq);
if (ret!=2)
return -EINVAL;
if (cpu < 0 || cpu >= num_possible_cpus() || freq < 0)
return -EINVAL;
domain = find_domain(cpu);
if (!domain)
return -EINVAL;
freq_qos_update_request(&domain->user_min_qos_req, freq);
return count;
}
static ssize_t freq_qos_max_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
ssize_t count = 0;
struct exynos_cpufreq_domain *domain;
list_for_each_entry(domain, &domains, list)
count += snprintf(buf + count, 30, "cpu%d: qos_max: %d\n",
cpumask_first(&domain->cpus),
domain->user_max_qos_req.pnode.prio);
return count;
}
static ssize_t freq_qos_max_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
int freq, cpu, ret;
struct exynos_cpufreq_domain *domain;
ret = sscanf(buf, "%d %8d", &cpu, &freq);
if (ret!=2)
return -EINVAL;
if (cpu < 0 || cpu >= num_possible_cpus() || freq < 0)
return -EINVAL;
domain = find_domain(cpu);
if (!domain)
return -EINVAL;
freq_qos_update_request(&domain->user_max_qos_req, freq);
return count;
}
static ssize_t min_freq_qos_list_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
ssize_t len = 0;
int total_requests = 0;
struct exynos_cpufreq_domain *domain;
struct plist_node *min_freq_pos;
list_for_each_entry(domain, &domains, list) {
total_requests = 0;
list_for_each_entry(min_freq_pos,
&domain->min_qos_req.qos->min_freq.list.node_list, node_list) {
total_requests += 1;
len += sysfs_emit_at(buf, len, "cpu%d: total_requests: %d,"
" min_freq_qos: %d\n",
cpumask_first(&domain->cpus),
total_requests, min_freq_pos->prio);
}
}
return len;
}
static ssize_t max_freq_qos_list_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
ssize_t len = 0;
int total_requests = 0;
struct exynos_cpufreq_domain *domain;
struct plist_node *max_freq_pos;
list_for_each_entry(domain, &domains, list) {
total_requests = 0;
list_for_each_entry(max_freq_pos,
&domain->max_qos_req.qos->max_freq.list.node_list, node_list) {
total_requests += 1;
len += sysfs_emit_at(buf, len, "cpu%d: total_requests: %d,"
" max_freq_qos: %d\n",
cpumask_first(&domain->cpus),
total_requests, max_freq_pos->prio);
}
}
return len;
}
static ssize_t fw_freq_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct exynos_cpufreq_domain *domain;
long freq;
ssize_t len = 0;
list_for_each_entry(domain, &domains, list) {
freq = cal_dfs_get_rate_acpm(domain->cal_id);
len += sysfs_emit_at(buf, len, "cpu%d: freq: %ld\n",
cpumask_first(&domain->cpus), freq);
}
return len;
}
static DEVICE_ATTR_RW(resume_freq);
static DEVICE_ATTR_RW(freq_qos_max);
static DEVICE_ATTR_RW(freq_qos_min);
static DEVICE_ATTR_RO(min_freq_qos_list);
static DEVICE_ATTR_RO(max_freq_qos_list);
static DEVICE_ATTR_RO(fw_freq);
/*********************************************************************
* CPUFREQ DEV FOPS *
*********************************************************************/
static ssize_t cpufreq_fops_write(struct file *filp, const char __user *buf,
size_t count, loff_t *f_pos)
{
s32 value;
struct freq_qos_request *req = filp->private_data;
if (count == sizeof(s32)) {
if (copy_from_user(&value, buf, sizeof(s32)))
return -EFAULT;
} else {
int ret;
ret = kstrtos32_from_user(buf, count, 16, &value);
if (ret)
return ret;
}
freq_qos_update_request(req, value);
return count;
}
static ssize_t cpufreq_fops_read(struct file *filp, char __user *buf,
size_t count, loff_t *f_pos)
{
s32 value = 0;
return simple_read_from_buffer(buf, count, f_pos, &value, sizeof(s32));
}
static int cpufreq_fops_open(struct inode *inode, struct file *filp)
{
int ret;
struct exynos_cpufreq_file_operations *fops;
struct freq_qos_request *req;
fops = container_of(filp->f_op, struct exynos_cpufreq_file_operations, fops);
req = kzalloc(sizeof(*req), GFP_KERNEL);
if (!req)
return -ENOMEM;
filp->private_data = req;
ret = freq_qos_add_request(fops->freq_constraints, req,
fops->req_type,
fops->default_value);
if (ret)
return ret;
return 0;
}
static int cpufreq_fops_release(struct inode *inode, struct file *filp)
{
struct freq_qos_request *req = filp->private_data;
freq_qos_remove_request(req);
kfree(req);
return 0;
}
/*********************************************************************
* SUPPORT for DVFS MANAGER *
*********************************************************************/
static void
validate_dm_constraint_table(struct exynos_dm_freq *table, int table_size,
int driver_cal_id, int constraint_cal_id)
{
unsigned long *ect_table;
int ect_size, index, ect_index;
if (!driver_cal_id)
goto validate_constraint;
/* validate driver frequency */
ect_size = cal_dfs_get_lv_num(driver_cal_id);
ect_table = kcalloc(ect_size, sizeof(unsigned long), GFP_KERNEL);
if (!ect_table)
return;
cal_dfs_get_rate_table(driver_cal_id, ect_table);
for (index = 0; index < table_size; index++) {
unsigned int freq = table[index].driver_freq;
ect_index = ect_size;
while (--ect_index >= 0) {
if (freq <= ect_table[ect_index]) {
table[index].driver_freq = ect_table[ect_index];
break;
}
}
}
kfree(ect_table);
validate_constraint:
if (!constraint_cal_id)
return;
/* validate constraint frequency */
ect_size = cal_dfs_get_lv_num(constraint_cal_id);
ect_table = kcalloc(ect_size, sizeof(unsigned long), GFP_KERNEL);
if (!ect_table)
return;
cal_dfs_get_rate_table(constraint_cal_id, ect_table);
for (index = 0; index < table_size; index++) {
unsigned int freq = table[index].constraint_freq;
ect_index = ect_size;
while (--ect_index >= 0) {
if (freq <= ect_table[ect_index]) {
table[index].constraint_freq = ect_table[ect_index];
break;
}
}
}
kfree(ect_table);
}
static int init_constraint_table_dt(struct exynos_cpufreq_dm *dm,
struct device_node *dn)
{
struct exynos_dm_freq *table;
int size, table_size, index, c_index;
/*
* A DM constraint table row consists of driver and constraint frequency
* value, the size of a row is 64bytes. Divide size in half when
* table is allocated.
*/
size = of_property_count_u32_elems(dn, "table");
if (size < 0)
return size;
table_size = size / 2;
table = kcalloc(table_size, sizeof(*table), GFP_KERNEL);
if (!table)
return -ENOMEM;
of_property_read_u32_array(dn, "table", (unsigned int *)table, size);
validate_dm_constraint_table(table, table_size,
dm->driver_cal_id, dm->constraint_cal_id);
for (index = 0; index < dm->c.table_length; index++) {
unsigned int freq = dm->c.freq_table[index].driver_freq;
if (freq > table[0].driver_freq)
continue;
for (c_index = 0; c_index < table_size; c_index++) {
if (freq >= table[c_index].driver_freq) {
dm->c.freq_table[index].constraint_freq =
table[c_index].constraint_freq;
break;
}
if (c_index == table_size - 1)
dm->c.freq_table[index].constraint_freq =
table[c_index].constraint_freq;
}
}
kfree(table);
return 0;
}
static int dm_scaler(int dm_type, void *devdata, unsigned int target_freq,
unsigned int relation)
{
struct exynos_cpufreq_domain *domain = devdata;
struct cpufreq_policy *policy;
struct cpumask mask;
int ret, target_index;
/* Skip scaling if all cpus of domain are hotplugged out */
cpumask_and(&mask, &domain->cpus, cpu_online_mask);
if (cpumask_empty(&mask))
return -ENODEV;
policy = cpufreq_cpu_get(cpumask_first(&mask));
if (!policy) {
pr_err("%s: failed get cpufreq policy\n", __func__);
return -ENODEV;
}
target_index = cpufreq_frequency_table_target(policy, target_freq, relation);
target_freq = policy->freq_table[target_index].frequency;
ret = __exynos_cpufreq_target(policy, target_freq);
cpufreq_cpu_put(policy);
return ret;
}
static int init_dm(struct exynos_cpufreq_domain *domain,
struct device_node *dn)
{
struct exynos_cpufreq_dm *dm;
struct device_node *root;
struct of_phandle_iterator iter;
int ret, err;
ret = of_property_read_u32(dn, "dm-type", &domain->dm_type);
if (ret)
return ret;
ret = exynos_dm_data_init(domain->dm_type, domain, domain->min_freq,
domain->max_freq, domain->old);
if (ret)
return ret;
/* Initialize list head of DVFS Manager constraints */
INIT_LIST_HEAD(&domain->dm_list);
/*
* Initialize DVFS Manager constraints
* - constraint_type : minimum or maximum constraint
* - constraint_dm_type : cpu/mif/int/.. etc
* - guidance : constraint from chipset characteristic
* - freq_table : constraint table
*/
root = of_get_child_by_name(dn, "dm-constraints");
of_for_each_phandle(&iter, err, root, "list", NULL, 0) {
int index, r_index;
/* allocate DM constraint */
dm = kzalloc(sizeof(*dm), GFP_KERNEL);
if (!dm)
goto init_fail;
list_add_tail(&dm->list, &domain->dm_list);
of_property_read_u32(iter.node, "const-type", &dm->c.constraint_type);
of_property_read_u32(iter.node, "dm-constraint", &dm->c.dm_constraint);
of_property_read_u32(iter.node, "driver-cal-id", &dm->driver_cal_id);
of_property_read_u32(iter.node, "constraint-cal-id", &dm->constraint_cal_id);
/* allocate DM constraint table */
dm->c.freq_table = kcalloc(domain->table_size,
sizeof(*dm->c.freq_table),
GFP_KERNEL);
if (!dm->c.freq_table)
goto init_fail;
dm->c.table_length = domain->table_size;
/*
* fill driver freq, domain frequency table is in ascending
* order, but DM constraint table must be in descending
* order.
*/
index = 0;
r_index = domain->table_size - 1;
while (r_index >= 0) {
dm->c.freq_table[index].driver_freq =
domain->freq_table[r_index].frequency;
index++;
r_index--;
}
/* fill constraint freq */
if (init_constraint_table_dt(dm, iter.node))
continue;
/* register DM constraint */
ret = register_exynos_dm_constraint_table(domain->dm_type, &dm->c);
if (ret)
goto init_fail;
}
return register_exynos_dm_freq_scaler(domain->dm_type, dm_scaler);
init_fail:
while (!list_empty(&domain->dm_list)) {
dm = list_last_entry(&domain->dm_list, struct exynos_cpufreq_dm, list);
list_del(&dm->list);
kfree(dm->c.freq_table);
kfree(dm);
}
return 0;
}
/*********************************************************************
* INITIALIZE EXYNOS CPUFREQ DRIVER *
*********************************************************************/
static void print_domain_info(struct exynos_cpufreq_domain *domain)
{
int i;
char buf[10];
pr_info("CPUFREQ of domain%d cal-id : %#x\n", domain->id, domain->cal_id);
scnprintf(buf, sizeof(buf), "%*pbl", cpumask_pr_args(&domain->cpus));
pr_info("CPUFREQ of domain%d sibling cpus : %s\n", domain->id, buf);
pr_info("CPUFREQ of domain%d boot freq = %d kHz, resume freq = %d kHz\n",
domain->id, domain->boot_freq, domain->resume_freq);
pr_info("CPUFREQ of domain%d max freq : %d kHz, min freq : %d kHz\n",
domain->id,
domain->max_freq, domain->min_freq);
pr_info("CPUFREQ of domain%d table size = %d\n",
domain->id, domain->table_size);
for (i = 0; i < domain->table_size; i++) {
if (domain->freq_table[i].frequency == CPUFREQ_ENTRY_INVALID)
continue;
pr_info("CPUFREQ of domain%d : L%-2d %7d kHz\n",
domain->id,
domain->freq_table[i].driver_data,
domain->freq_table[i].frequency);
}
}
static void freq_qos_release(struct work_struct *work)
{
struct exynos_cpufreq_domain *domain = container_of(to_delayed_work(work),
struct exynos_cpufreq_domain,
work);
freq_qos_update_request(&domain->min_qos_req, domain->min_freq);
freq_qos_update_request(&domain->max_qos_req, domain->max_freq);
}
static int
init_user_freq_qos(struct exynos_cpufreq_domain *domain, struct cpufreq_policy *policy)
{
int ret = freq_qos_add_request(&policy->constraints, &domain->user_min_qos_req,
FREQ_QOS_MIN, domain->min_freq);
if (ret < 0)
return ret;
ret = freq_qos_add_request(&policy->constraints, &domain->user_max_qos_req,
FREQ_QOS_MAX, domain->soft_max_freq);
return ret;
}
static int
init_freq_qos(struct exynos_cpufreq_domain *domain, struct cpufreq_policy *policy)
{
unsigned int boot_qos, val;
struct device_node *dn = domain->dn;
int ret;
ret = freq_qos_add_request(&policy->constraints, &domain->min_qos_req,
FREQ_QOS_MIN, domain->min_freq);
if (ret < 0)
return ret;
ret = freq_qos_add_request(&policy->constraints, &domain->max_qos_req,
FREQ_QOS_MAX, domain->max_freq);
if (ret < 0)
return ret;
/* Skip pm_qos if setting exists in device tree */
if (of_property_read_bool(dn, "skip-boot-pmqos")) {
pr_info("Skipping boot pm_qos domain%d\n", domain->id);
return 0;
}
/*
* Basically booting pm_qos is set to max frequency of domain.
* But if pm_qos-booting exists in device tree,
* booting pm_qos is selected to smaller one
* between max frequency of domain and the value defined in device tree.
*/
boot_qos = domain->max_freq;
if (!of_property_read_u32(dn, "pm_qos-booting", &val))
boot_qos = min(boot_qos, val);
freq_qos_update_request(&domain->min_qos_req, boot_qos);
freq_qos_update_request(&domain->max_qos_req, boot_qos);
/* booting boost, it is expired after 40s */
INIT_DELAYED_WORK(&domain->work, freq_qos_release);
schedule_delayed_work(&domain->work, msecs_to_jiffies(40000));
pr_info("Set boot pm_qos domain%d to %d for %ld\n", domain->id,
boot_qos, 40 * USEC_PER_SEC);
return 0;
}
static int
init_fops(struct exynos_cpufreq_domain *domain, struct cpufreq_policy *policy)
{
char *node_name_buffer;
int ret, buffer_size;
buffer_size = sizeof(char [64]);
node_name_buffer = kzalloc(buffer_size, GFP_KERNEL);
if (!node_name_buffer)
return -ENOMEM;
snprintf(node_name_buffer, buffer_size,
"cluster%d_freq_min", domain->id);
domain->min_qos_fops.fops.write = cpufreq_fops_write;
domain->min_qos_fops.fops.read = cpufreq_fops_read;
domain->min_qos_fops.fops.open = cpufreq_fops_open;
domain->min_qos_fops.fops.release = cpufreq_fops_release;
domain->min_qos_fops.fops.llseek = noop_llseek;
domain->min_qos_fops.miscdev.minor = MISC_DYNAMIC_MINOR;
domain->min_qos_fops.miscdev.name = node_name_buffer;
domain->min_qos_fops.miscdev.fops = &domain->min_qos_fops.fops;
domain->min_qos_fops.freq_constraints = &policy->constraints;
domain->min_qos_fops.default_value = FREQ_QOS_MIN_DEFAULT_VALUE;
domain->min_qos_fops.req_type = FREQ_QOS_MIN;
ret = misc_register(&domain->min_qos_fops.miscdev);
if (ret) {
pr_err("CPUFREQ couldn't register misc device min for domain %d", domain->id);
kfree(node_name_buffer);
return ret;
}
node_name_buffer = kzalloc(buffer_size, GFP_KERNEL);
if (!node_name_buffer)
return -ENOMEM;
snprintf(node_name_buffer, buffer_size,
"cluster%d_freq_max", domain->id);
domain->max_qos_fops.fops.write = cpufreq_fops_write;
domain->max_qos_fops.fops.read = cpufreq_fops_read;
domain->max_qos_fops.fops.open = cpufreq_fops_open;
domain->max_qos_fops.fops.release = cpufreq_fops_release;
domain->max_qos_fops.fops.llseek = noop_llseek;
domain->max_qos_fops.miscdev.minor = MISC_DYNAMIC_MINOR;
domain->max_qos_fops.miscdev.name = node_name_buffer;
domain->max_qos_fops.miscdev.fops = &domain->max_qos_fops.fops;
domain->max_qos_fops.freq_constraints = &policy->constraints;
domain->max_qos_fops.default_value = FREQ_QOS_MAX_DEFAULT_VALUE;
domain->max_qos_fops.req_type = FREQ_QOS_MAX;
ret = misc_register(&domain->max_qos_fops.miscdev);
if (ret) {
pr_err("CPUFREQ couldn't register misc device max for domain %d", domain->id);
kfree(node_name_buffer);
return ret;
}
return 0;
}
static int init_domain(struct exynos_cpufreq_domain *domain,
struct device_node *dn)
{
unsigned int val, orig_table_size;
int index, r_index;
unsigned long *freq_table;
unsigned int *volt_table;
const char *buf;
struct device *cpu_dev;
int ret;
unsigned int resume_freq = 0;
/* Get CAL ID */
ret = of_property_read_u32(dn, "cal-id", &domain->cal_id);
if (ret)
return ret;
/*
* Set min/max frequency.
* If max-freq property exists in device tree, max frequency is
* selected to smaller one between the value defined in device
* tree and CAL. In case of min-freq, min frequency is selected
* to bigger one.
*/
domain->max_freq = cal_dfs_get_max_freq(domain->cal_id);
domain->soft_max_freq = domain->max_freq;
domain->min_freq = cal_dfs_get_min_freq(domain->cal_id);
if (!of_property_read_u32(dn, "max-freq", &val))
domain->max_freq = val;
if (!of_property_read_u32(dn, "min-freq", &val))
domain->min_freq = max(domain->min_freq, val);
if (!of_property_read_u32(dn, "resume-freq", &val))
resume_freq = max(domain->min_freq, val);
if (!of_property_read_u32(dn, "soft-max-freq", &val))
domain->soft_max_freq = min(domain->max_freq, val);
domain->soft_max_freq = max(domain->soft_max_freq, domain->min_freq);
domain->max_freq_qos = domain->max_freq;
domain->min_freq_qos = domain->min_freq;
/*
* Allocate temporary frequency and voltage tables
* to get DVFS table from CAL.
*/
orig_table_size = cal_dfs_get_lv_num(domain->cal_id);
freq_table = kcalloc(orig_table_size, sizeof(unsigned long),
GFP_KERNEL);
if (!freq_table)
return -ENOMEM;
cal_dfs_get_rate_table(domain->cal_id, freq_table);
volt_table = kzalloc(sizeof(unsigned int) * orig_table_size,
GFP_KERNEL);
if (!volt_table) {
kfree(freq_table);
return -ENOMEM;
}
cal_dfs_get_asv_table(domain->cal_id, volt_table);
/*
* A voltage-based cap can be used to find the max frequency
* from the given ASV table.
*/
if (!of_property_read_u32(dn, "max-volt", &val)) {
for (index = 0; index < orig_table_size; index++) {
if (volt_table[index] <= val)
break;
}
domain->max_freq = min(domain->max_freq, (unsigned int)freq_table[index]);
domain->max_freq_qos = domain->max_freq;
}
resume_freq = min(resume_freq, domain->max_freq);
/*
* Set frequency table size.
*/
domain->table_size = 0;
domain->ect_table_offset = 0;
for (index = 0; index < orig_table_size; index++) {
if (freq_table[index] > domain->max_freq) {
domain->ect_table_offset++;
continue;
}
if (freq_table[index] < domain->min_freq)
continue;
domain->table_size++;
}
/*
* Allocate frequency table.
* Last row of frequency table must be set to CPUFREQ_TABLE_END.
* Table size should be one larger than real table size.
*/
domain->freq_table = kcalloc(domain->table_size + 1,
sizeof(*domain->freq_table),
GFP_KERNEL);
if (!domain->freq_table) {
kfree(freq_table);
return -ENOMEM;
}
/*
* Initialize frequency table.
* The frequency table obtained from ECT is in descending order, but
* the frequency table of domain is organized in ascending order for
* Android BatteryStat service.
*/
index = 0;
r_index = orig_table_size;
while (--r_index >= 0) {
if (freq_table[r_index] > domain->max_freq)
continue;
if (freq_table[r_index] < domain->min_freq)
continue;
domain->freq_table[index].driver_data = index;
domain->freq_table[index].frequency = freq_table[r_index];
index++;
}
domain->freq_table[index].driver_data = index;
domain->freq_table[index].frequency = CPUFREQ_TABLE_END;
/*
* Get cpumask which belongs to domain.
*/
ret = of_property_read_string(dn, "sibling-cpus", &buf);
if (ret) {
kfree(freq_table);
return ret;
}
cpulist_parse(buf, &domain->cpus);
cpumask_and(&domain->cpus, &domain->cpus, cpu_possible_mask);
if (cpumask_weight(&domain->cpus) == 0) {
kfree(freq_table);
return -ENODEV;
}
/*
* Add OPP table for thermal.
* Thermal CPU cooling is based on the OPP table.
*/
for (index = 0; index < orig_table_size; index++) {
int cpu;
if (freq_table[index] > domain->max_freq)
continue;
if (freq_table[index] < domain->min_freq)
continue;
for_each_cpu_and(cpu, &domain->cpus, cpu_possible_mask)
dev_pm_opp_add(get_cpu_device(cpu),
freq_table[index] * 1000,
volt_table[index]);
}
kfree(freq_table);
kfree(volt_table);
/*
* Initialize other items.
*/
if (of_property_read_bool(dn, "need-awake"))
domain->need_awake = true;
if (domain->need_awake)
disable_power_mode(cpumask_any(&domain->cpus), POWERMODE_TYPE_CLUSTER);
domain->boot_freq = cal_dfs_get_boot_freq(domain->cal_id);
domain->resume_freq = resume_freq ? resume_freq :
cal_dfs_get_resume_freq(domain->cal_id);
domain->old = get_freq(domain);
if (domain->old < domain->min_freq || domain->max_freq < domain->old) {
WARN(1, "Out-of-range freq(%dkhz) returned for domain%d in init time\n",
domain->old, domain->id);
domain->old = domain->boot_freq;
}
if (domain->need_awake)
enable_power_mode(cpumask_any(&domain->cpus), POWERMODE_TYPE_CLUSTER);
mutex_init(&domain->lock);
spin_lock_init(&domain->thermal_update_lock);
domain->capped_freq[TJ] = domain->max_freq;
domain->capped_freq[TSKIN] = domain->max_freq;
scnprintf(domain->capped_freq_name[TJ], (THERMAL_PRESSURE_STR_LEN), "TJ_CAPPED_FREQUENCY_%d",
domain->id);
scnprintf(domain->capped_freq_name[TSKIN], (THERMAL_PRESSURE_STR_LEN), "TSKIN_CAPPED_FREQUENCY_%d",
domain->id);
/*
* Initialize CPUFreq DVFS Manager
* DVFS Manager is the optional function, it does not check return value
*/
init_dm(domain, dn);
cpu_dev = get_cpu_device(cpumask_first(&domain->cpus));
dev_pm_opp_of_register_em(cpu_dev, &domain->cpus);
pr_info("Complete to initialize cpufreq-domain%d\n", domain->id);
return 0;
}
static int exynos_cpufreq_probe(struct platform_device *pdev)
{
struct device_node *dn;
struct exynos_cpufreq_domain *domain;
unsigned int domain_id = 0;
int ret = 0;
/*
* Pre-initialization.
*
* allocate and initialize cpufreq domain
*/
for_each_child_of_node(pdev->dev.of_node, dn) {
domain = kzalloc(sizeof(*domain), GFP_KERNEL);
if (!domain)
return -ENOMEM;
domain->id = domain_id++;
if (init_domain(domain, dn)) {
pr_err("failed to initialize cpufreq domain%d\n", domain->id);
kfree(domain->freq_table);
kfree(domain);
continue;
}
domain->dn = dn;
list_add_tail(&domain->list, &domains);
print_domain_info(domain);
}
#if IS_ENABLED(CONFIG_EXYNOS_CPU_THERMAL) && IS_ENABLED(CONFIG_SOC_ZUMA)
register_get_cpu_power_table_ect_offset(exynos_cpufreq_get_power_table_ect_offset);
#endif
if (!domain_id) {
pr_err("Failed to initialize cpufreq driver\n");
return -ENOMEM;
}
/* Register cpufreq driver */
ret = cpufreq_register_driver(&exynos_driver);
if (ret) {
pr_err("failed to register cpufreq driver\n");
return ret;
}
/*
* Post-initialization
*
* 1. create sysfs to control frequency min/max
* 2. enable frequency scaling of each domain
* 3. initialize freq qos of each domain
* 4. register notifier bloack
*/
ret = sysfs_create_file(&pdev->dev.kobj, &dev_attr_resume_freq.attr);
if (ret) {
pr_err("failed to create resume_freq node\n");
return ret;
}
ret = sysfs_create_file(&pdev->dev.kobj, &dev_attr_freq_qos_max.attr);
if (ret) {
pr_err("failed to create user_max node\n");
return ret;
}
ret = sysfs_create_file(&pdev->dev.kobj, &dev_attr_freq_qos_min.attr);
if (ret) {
pr_err("failed to create user_min node\n");
return ret;
}
ret = sysfs_create_file(&pdev->dev.kobj, &dev_attr_min_freq_qos_list.attr);
if (ret) {
pr_err("failed to create min_freq_qos_list node\n");
return ret;
}
ret = sysfs_create_file(&pdev->dev.kobj, &dev_attr_max_freq_qos_list.attr);
if (ret) {
pr_err("failed to create max_freq_qos_list node\n");
return ret;
}
ret = sysfs_create_file(&pdev->dev.kobj, &dev_attr_fw_freq.attr);
if (ret) {
pr_err("failed to create fw_freq node\n");
return ret;
}
list_for_each_entry(domain, &domains, list) {
struct cpufreq_policy *policy;
policy = cpufreq_cpu_get_raw(cpumask_first(&domain->cpus));
if (!policy) {
pr_err("failed to find domain policy!\n");
return -ENODEV;
}
ret = init_user_freq_qos(domain, policy);
if (ret < 0) {
pr_err("Failed to set max user qos vote!\n");
return ret;
}
enable_domain(domain);
#if IS_ENABLED(CONFIG_EXYNOS_CPU_THERMAL)
exynos_cpufreq_cooling_register(domain->dn, policy);
#endif
ret = init_freq_qos(domain, policy);
if (ret) {
pr_info("failed to init pm_qos with err %d\n", ret);
return ret;
}
ret = init_fops(domain, policy);
if (ret) {
pr_info("failed to init fops with err %d\n", ret);
return ret;
}
}
register_pm_notifier(&exynos_cpufreq_pm);
#if IS_ENABLED(CONFIG_EXYNOS_CPU_THERMAL) && IS_ENABLED(CONFIG_SOC_ZUMA)
register_thermal_pressure_cb(exynos_cpufreq_set_tj_pressure_cb);
#endif
pr_info("Initialized Exynos cpufreq driver\n");
return ret;
}
static const struct of_device_id of_exynos_cpufreq_match[] = {
{ .compatible = "samsung,exynos-acme", },
{ },
};
MODULE_DEVICE_TABLE(of, of_exynos_cpufreq_match);
static struct platform_driver exynos_cpufreq_driver = {
.driver = {
.name = "exynos-acme",
.owner = THIS_MODULE,
.of_match_table = of_exynos_cpufreq_match,
},
.probe = exynos_cpufreq_probe,
};
module_platform_driver(exynos_cpufreq_driver);
MODULE_DESCRIPTION("Exynos ACME");
MODULE_LICENSE("GPL");