blob: bd01483b48e1a42231db038fd3289bd372fe37d4 [file] [log] [blame]
/*
* drivers/cpufreq/mmp-bL-cpufreq.c
*
* Copyright (C) 2014 Marvell Semiconductors Inc.
*
* Author: Bill Zhou <zhoub@marvell.com>
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* 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.
*
*/
#include <linux/clk.h>
#include <linux/cpufreq.h>
#include <linux/cpumask.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/sched.h>
#include <linux/suspend.h>
#include <linux/pm_qos.h>
#include <linux/clk-private.h>
#ifdef CONFIG_DEBUG_FS
#include <linux/debugfs.h>
#include <linux/debugfs-pxa.h>
#endif
#include "mmp-cpufreq-comm.h"
/* Unit of various frequencies:
* clock driver: Hz
* cpufreq framework/driver & QoS framework/interface: KHz
*/
/* FIXME: too simple and may be conflicted with other clock names. */
#define CLUSTER_CLOCK_NAME "clst"
/*
* clients that requests cpufreq Qos min:
* cpufreq target callback, input boost, wifi/BT/..., policy->cpuinfo.min
* clients that requests cpufreq Qos max:
* cpufreq Qos min, thermal driver, policy->cpuinfo.max
*/
/* used to generate names to request cpufreq Qos min from profiler */
#define PROFILER_REQ_MIN "PROFILER_REQ_MIN_"
/* used to generate names to request cpufreq Qos max from cpufreq Qos min notifier */
#define QOSMIN_REQ_MAX "QOSMIN_REQ_MAX_"
/* used to generate names to request cpufreq Qos min/max from policy->cpuinfo.min/max */
#define CPUFREQ_POLICY_REQ "CPUFREQ_POLICY_REQ_"
/* used to generate names to request voltage level Qos min */
#define VL_REQ_MIN "VL_REQ_MIN_"
/* NOTE:
* Here, we don't assume the cluster order in system as below, but just
* define what types of clusters we need handle.
*/
enum cluster_type {
LITTLE_CLST = 0,
BIG_CLST,
NR_CLST_TYPE,
};
static int cpufreq_qos_min_id[NR_CLST_TYPE] = {
PM_QOS_CPUFREQ_L_MIN,
PM_QOS_CPUFREQ_B_MIN,
};
static int cpufreq_qos_max_id[NR_CLST_TYPE] = {
PM_QOS_CPUFREQ_L_MAX,
PM_QOS_CPUFREQ_B_MAX,
};
struct cpufreq_cluster {
int initialized; /* whether this struct has been initialized */
int clst_index;
int is_big; /* all cores in this cluster are big or not */
int nr_cores; /* core number in this cluster */
int first_cpu_id; /* the first cpu id in this cluster */
char *clk_name;
struct clk *clk;
#ifdef CONFIG_DEVFREQ_GOV_THROUGHPUT
struct cpufreq_ddr_upthrd *cdu;
#endif
struct cpufreq_policy *policy;
struct cpufreq_frequency_table *freq_table;
int freq_table_size;
int cpufreq_qos_max_id;
int cpufreq_qos_min_id;
int vl_qos_min_id;
struct pm_qos_request profiler_req_min;
struct pm_qos_request qosmin_req_max;
struct pm_qos_request cpufreq_policy_qos_req_min;
struct pm_qos_request cpufreq_policy_qos_req_max;
struct pm_qos_request vl_qos_req_min;
struct notifier_block cpufreq_min_notifier;
struct notifier_block cpufreq_max_notifier;
struct notifier_block cpufreq_policy_notifier;
struct notifier_block vl_min_notifier;
struct list_head node;
};
static LIST_HEAD(clst_list);
#define min_nb_to_clst(ptr) (container_of(ptr, struct cpufreq_cluster, cpufreq_min_notifier))
#define max_nb_to_clst(ptr) (container_of(ptr, struct cpufreq_cluster, cpufreq_max_notifier))
#define policy_nb_to_clst(ptr) (container_of(ptr, struct cpufreq_cluster, cpufreq_policy_notifier))
#define vl_nb_to_clst(ptr) (container_of(ptr, struct cpufreq_cluster, vl_min_notifier))
/* Locks used to serialize cpufreq target request. */
static DEFINE_MUTEX(mmp_cpufreq_lock);
/*
* We have two modes to handle cpufreq on dual clusters:
* 1. When there are no limitations from policy, thermal, etc, frequencies
* of dual clusters are always aligned to the same voltage level.
* 2. Each cluster can change its frequency freely.
*
* We can
* append "vl_cpufreq=1 (or 0)" to uboot cmdline
* or "echo 1 (or 0) > /sys/kernel/debug/pxa/vl_cpufreq"
* to enable or disable voltage based cpufreq.
*/
static unsigned int vl_cpufreq_enable = 0;
static int __init __cpufreq_mode_setup(char *str)
{
unsigned int n;
if (!get_option(&str, &n))
return 0;
if (n < 2)
vl_cpufreq_enable = n;
return 1;
}
__setup("vl_cpufreq=", __cpufreq_mode_setup);
/*
* This function comes from cpufreq_frequency_table_target, but remove
* if ((freq < policy->min) || (freq > policy->max))
* continue;
* to make sure critical setrate request will not be skipped.
* For example, thermal wants to set max qos to 312MHz, but
* policy->min is 624MHz. Then it fails to set rate to 312MHz.
*/
static int __cpufreq_frequency_table_target(
struct cpufreq_cluster *clst,
unsigned int target_freq,
unsigned int relation,
unsigned int *index)
{
struct cpufreq_frequency_table *table = clst->freq_table;
struct cpufreq_frequency_table optimal = {
.driver_data = ~0,
.frequency = 0,
};
struct cpufreq_frequency_table suboptimal = {
.driver_data = ~0,
.frequency = 0,
};
unsigned int i;
switch (relation) {
case CPUFREQ_RELATION_H:
suboptimal.frequency = ~0;
break;
case CPUFREQ_RELATION_L:
optimal.frequency = ~0;
break;
}
for (i = 0; (table[i].frequency != CPUFREQ_TABLE_END); i++) {
unsigned int freq = table[i].frequency;
if (freq == CPUFREQ_ENTRY_INVALID)
continue;
/*
if ((freq < policy->min) || (freq > policy->max))
continue;
*/
switch (relation) {
case CPUFREQ_RELATION_H:
if (freq <= target_freq) {
if (freq >= optimal.frequency) {
optimal.frequency = freq;
optimal.driver_data = i;
}
} else {
if (freq <= suboptimal.frequency) {
suboptimal.frequency = freq;
suboptimal.driver_data = i;
}
}
break;
case CPUFREQ_RELATION_L:
if (freq >= target_freq) {
if (freq <= optimal.frequency) {
optimal.frequency = freq;
optimal.driver_data = i;
}
} else {
if (freq >= suboptimal.frequency) {
suboptimal.frequency = freq;
suboptimal.driver_data = i;
}
}
break;
}
}
if (optimal.driver_data > i) {
if (suboptimal.driver_data > i)
return -EINVAL;
*index = suboptimal.driver_data;
} else {
*index = optimal.driver_data;
}
return 0;
}
/* check whether all cores in a cluster are offline. */
static int __cpufreq_clst_allcores_off(struct cpufreq_cluster *clst)
{
int i;
for (i = clst->first_cpu_id; i < (clst->first_cpu_id + clst->nr_cores); i++) {
if (cpu_online(i))
return 0;
}
return 1;
}
static void __cpufreq_update_clst_topology(struct cpufreq_cluster *clst)
{
/* FIXME:
* Don't assume the cluster order and core number in each cluster.
* For example, Versatile Express TC2 has 2 Cortex-A15 and 3 Cortex-A7.
* Now we just add hardcode here, but we need retrieve the information
* from device tree or GTS configuration.
*/
if (clst->clst_index == 1) {
clst->is_big = 1;
clst->nr_cores = 4;
clst->first_cpu_id = 4;
} else {
clst->is_big = 0;
clst->nr_cores = 4;
clst->first_cpu_id = 0;
}
}
/* map a target freq to its voltage level */
static unsigned int __cpufreq_freq_2_vl(
struct cpufreq_cluster *clst,
unsigned int freq)
{
int i;
if (freq >= clst->freq_table[clst->freq_table_size - 1].frequency)
return clst->freq_table[clst->freq_table_size - 1].driver_data;
for (i = clst->freq_table_size - 1; i >= 1; i--)
if ((clst->freq_table[i - 1].frequency < freq) && (freq <= clst->freq_table[i].frequency))
return clst->freq_table[i].driver_data;
/*
* When target freq < all other freqs, or any other condition,
* return the lowest volt level.
*/
return clst->freq_table[0].driver_data;
}
/* map a voltage level to the max freq it could support */
static unsigned int __cpufreq_vl_2_max_freq(
struct cpufreq_cluster *clst,
unsigned int vl)
{
int i;
for (i = clst->freq_table_size - 1; i >= 0; i--)
if (clst->freq_table[i].driver_data <= vl)
return clst->freq_table[i].frequency;
return clst->freq_table[0].frequency;
}
static int __cpufreq_freq_min_notify(
struct notifier_block *nb,
unsigned long min,
void *v)
{
int ret, index;
unsigned int volt_level;
struct cpufreq_cluster *clst;
if (!nb)
return NOTIFY_BAD;
clst = min_nb_to_clst(nb);
/* CPUFREQ_RELATION_L means "at or above target":
* i.e:
* If target freq is 400MHz, we round up to the closest 416MHz in freq table.
*/
ret = __cpufreq_frequency_table_target(clst, min, CPUFREQ_RELATION_L, &index);
if (ret != 0) {
pr_err("%s: cannot get a valid index from freq_table\n", __func__);
return NOTIFY_BAD;
}
if (vl_cpufreq_enable) {
volt_level = __cpufreq_freq_2_vl(clst, clst->freq_table[index].frequency);
if (pm_qos_request_active(&clst->vl_qos_req_min))
pm_qos_update_request(&clst->vl_qos_req_min, volt_level);
} else {
if (pm_qos_request_active(&clst->qosmin_req_max))
pm_qos_update_request(&clst->qosmin_req_max, clst->freq_table[index].frequency);
}
return NOTIFY_OK;
}
static int __cpufreq_vl_min_notify(
struct notifier_block *nb,
unsigned long vl_min,
void *v)
{
unsigned long freq = 0;
struct cpufreq_cluster *clst;
if (!nb)
return NOTIFY_BAD;
if (!list_empty(&clst_list)) {
list_for_each_entry(clst, &clst_list, node) {
freq = __cpufreq_vl_2_max_freq(clst, vl_min);
if (pm_qos_request_active(&clst->qosmin_req_max))
pm_qos_update_request(&clst->qosmin_req_max, freq);
}
}
return NOTIFY_OK;
}
int cpufreq_lfreq_index;
int cpufreq_lfreq_table_size;
static int __cpufreq_freq_max_notify(
struct notifier_block *nb,
unsigned long max,
void *v)
{
int ret, index;
struct cpufreq_cluster *clst;
struct cpufreq_freqs freqs;
struct cpufreq_policy *policy;
if (!nb)
return NOTIFY_BAD;
clst = max_nb_to_clst(nb);
if(__cpufreq_clst_allcores_off(clst))
return NOTIFY_BAD;
/* CPUFREQ_RELATION_H means "below or at target":
* i.e:
* If target freq is 400MHz, we round down to the closest 312MHz in freq table.
*/
ret = __cpufreq_frequency_table_target(clst, max, CPUFREQ_RELATION_H, &index);
if (ret != 0) {
pr_err("%s: cannot get a valid index from freq_table\n", __func__);
return NOTIFY_BAD;
}
policy = clst->policy;
freqs.old = policy->cur;
freqs.new = clst->freq_table[index].frequency;
cpufreq_notify_transition(policy, &freqs, CPUFREQ_PRECHANGE);
if (!clk_set_rate(policy->clk, freqs.new * 1000)) {
ret = NOTIFY_OK;
if (clst->clst_index == LITTLE_CLST)
cpufreq_lfreq_index = index;
} else {
freqs.new = freqs.old;
ret = NOTIFY_BAD;
}
cpufreq_notify_transition(policy, &freqs, CPUFREQ_POSTCHANGE);
#ifdef CONFIG_DEVFREQ_GOV_THROUGHPUT
clst->cdu->new_cpu_rate = freqs.new;
cpufreq_ddr_upthrd_send_req(clst->cdu);
#endif
return ret;
}
static int __cpufreq_policy_notify(
struct notifier_block *nb,
unsigned long val,
void *data)
{
struct cpufreq_policy *policy = data;
struct cpufreq_cluster *clst;
if (!nb)
return NOTIFY_BAD;
clst = policy_nb_to_clst(nb);
if ((policy->clk == clst->clk) && (val == CPUFREQ_ADJUST)) {
pm_qos_update_request(&clst->cpufreq_policy_qos_req_min, policy->min);
pm_qos_update_request(&clst->cpufreq_policy_qos_req_max, policy->max);
}
return NOTIFY_OK;
}
static unsigned int mmp_bL_cpufreq_get(unsigned int cpu)
{
int clst_index = topology_physical_package_id(cpu);
struct cpufreq_cluster *clst;
list_for_each_entry(clst, &clst_list, node)
if (clst->clst_index == clst_index)
break;
BUG_ON(!clst);
return clk_get_rate(clst->clk) / 1000;
}
/*
* We don't handle param "relation" here, but use "CPUFREQ_RELATION_L" in
* cpufreq qos min notifier to round up and use "CPUFREQ_RELATION_H" in
* cpufreq qos max notifier to round down. This method can meet our
* requirement.
*/
static int mmp_bL_cpufreq_target(
struct cpufreq_policy *policy,
unsigned int target_freq,
unsigned int relation)
{
int ret = 0;
struct cpufreq_cluster *clst;
list_for_each_entry(clst, &clst_list, node)
if (clst->policy == policy)
break;
BUG_ON(!clst);
mutex_lock(&mmp_cpufreq_lock);
pm_qos_update_request(&clst->profiler_req_min, target_freq);
mutex_unlock(&mmp_cpufreq_lock);
return ret;
}
static int mmp_bL_cpufreq_init(struct cpufreq_policy *policy)
{
int i = 0, ret, clst_index, found = 0;
unsigned long cur_freq;
struct cpufreq_cluster *clst;
if (policy->cpu >= num_possible_cpus())
return -EINVAL;
clst_index = topology_physical_package_id(policy->cpu);
pr_debug("%s: start initialization on cluster-%d\n", __func__, clst_index);
if (!list_empty(&clst_list))
list_for_each_entry(clst, &clst_list, node)
if (clst->clst_index == clst_index) {
found = 1;
break;
}
if (!found) {
clst = kzalloc(sizeof(struct cpufreq_cluster), GFP_KERNEL);
if (!clst) {
pr_err("%s: fail to allocate memory for clst.\n", __func__);
return -ENOMEM;
}
}
if (!clst->initialized) {
char clst_id[2];
clst->clst_index = clst_index;
__cpufreq_update_clst_topology(clst);
clst->clk_name = __cpufreq_printf("%s%d", CLUSTER_CLOCK_NAME, clst_index);
if (!clst->clk_name) {
pr_err("%s: fail to allocate memory for clock name.\n", __func__);
goto err_out;
}
clst->clk = __clk_lookup(clst->clk_name);
if (!clst->clk) {
pr_err("%s: fail to get clock for cluster-%d\n", __func__, clst_index);
goto err_out;
}
clst->freq_table = cpufreq_frequency_get_table(policy->cpu);
/* get freq table size from cpufreq_frequency_table structure. */
while (clst->freq_table[i++].frequency != CPUFREQ_TABLE_END)
;
/* driver_data in last entry save freq table size, but that of other entries store voltage level. */
clst->freq_table_size = clst->freq_table[i - 1].driver_data;
if (clst->is_big) {
strcpy(clst_id, "B");
clst->cpufreq_qos_min_id = cpufreq_qos_min_id[BIG_CLST];
clst->cpufreq_qos_max_id = cpufreq_qos_max_id[BIG_CLST];
} else {
strcpy(clst_id, "L");
clst->cpufreq_qos_min_id = cpufreq_qos_min_id[LITTLE_CLST];
clst->cpufreq_qos_max_id = cpufreq_qos_max_id[LITTLE_CLST];
cpufreq_lfreq_table_size = clst->freq_table_size;
}
clst->vl_qos_min_id = PM_QOS_CLST_VL_MIN;
clst->profiler_req_min.name = __cpufreq_printf("%s%s", PROFILER_REQ_MIN, clst_id);
if (!clst->profiler_req_min.name) {
pr_err("%s: no mem for profiler_req_min.name.\n", __func__);
goto err_out;
}
clst->qosmin_req_max.name = __cpufreq_printf("%s%s", QOSMIN_REQ_MAX, clst_id);
if (!clst->qosmin_req_max.name) {
pr_err("%s: no mem for qosmin_req_max.name.\n", __func__);
goto err_out;
}
if (vl_cpufreq_enable) {
clst->vl_qos_req_min.name = __cpufreq_printf("%s%s", VL_REQ_MIN, clst_id);
if (!clst->vl_qos_req_min.name) {
pr_err("%s: no mem for vl_qos_req_min.name.\n", __func__);
goto err_out;
}
}
clst->cpufreq_policy_qos_req_min.name = __cpufreq_printf("%s%s%s", CPUFREQ_POLICY_REQ, "MIN_", clst_id);
if (!clst->cpufreq_policy_qos_req_min.name) {
pr_err("%s: no mem for cpufreq_policy_qos_req_min.name.\n", __func__);
goto err_out;
}
clst->cpufreq_policy_qos_req_max.name = __cpufreq_printf("%s%s%s", CPUFREQ_POLICY_REQ, "MAX_", clst_id);
if (!clst->cpufreq_policy_qos_req_max.name) {
pr_err("%s: no mem for cpufreq_policy_qos_req_max.name.\n", __func__);
goto err_out;
}
clst->cpufreq_min_notifier.notifier_call = __cpufreq_freq_min_notify;
clst->cpufreq_max_notifier.notifier_call = __cpufreq_freq_max_notify;
if (vl_cpufreq_enable)
clst->vl_min_notifier.notifier_call = __cpufreq_vl_min_notify;
clst->cpufreq_policy_notifier.notifier_call = __cpufreq_policy_notify;
pm_qos_add_notifier(clst->cpufreq_qos_min_id, &clst->cpufreq_min_notifier);
pm_qos_add_notifier(clst->cpufreq_qos_max_id, &clst->cpufreq_max_notifier);
if (vl_cpufreq_enable)
pm_qos_add_notifier(clst->vl_qos_min_id, &clst->vl_min_notifier);
cpufreq_register_notifier(&clst->cpufreq_policy_notifier, CPUFREQ_POLICY_NOTIFIER);
#ifdef CONFIG_DEVFREQ_GOV_THROUGHPUT
clst->cdu = cpufreq_ddr_upthrd_init(clst->clk);
if (!clst->cdu) {
pr_err("%s: fail to get a valid cdu for ddr_thrd.\n", __func__);
goto err_out;
}
#endif
}
ret = cpufreq_table_validate_and_show(policy, clst->freq_table);
if (ret) {
pr_err("%s: invalid frequency table for cluster-%d\n", __func__, clst_index);
goto err_out;
}
cpumask_copy(policy->cpus, topology_core_cpumask(policy->cpu));
cur_freq = clk_get_rate(clst->clk) / 1000;
policy->clk = clst->clk;
policy->cpuinfo.transition_latency = 10 * 1000;
policy->cur = cur_freq;
clst->policy = policy;
if (!clst->initialized) {
pm_qos_add_request(&clst->profiler_req_min, clst->cpufreq_qos_min_id, policy->cur);
pm_qos_add_request(&clst->qosmin_req_max, clst->cpufreq_qos_max_id, pm_qos_request(clst->cpufreq_qos_min_id));
if (vl_cpufreq_enable)
pm_qos_add_request(&clst->vl_qos_req_min, clst->vl_qos_min_id, __cpufreq_freq_2_vl(clst, cur_freq));
pm_qos_add_request(&clst->cpufreq_policy_qos_req_min, clst->cpufreq_qos_min_id, policy->min);
pm_qos_add_request(&clst->cpufreq_policy_qos_req_max, clst->cpufreq_qos_max_id, policy->max);
clst->initialized = 1;
list_add_tail(&clst->node, &clst_list);
pr_info("vl_cpufreq %s\n", (vl_cpufreq_enable == 1) ? "enabled" : "disabled");
}
pr_debug("%s: finish initialization on cluster-%d\n", __func__, clst_index);
return 0;
err_out:
if (clst) {
kfree(clst->clk_name);
kfree(clst->profiler_req_min.name);
kfree(clst->qosmin_req_max.name);
if (vl_cpufreq_enable)
kfree(clst->vl_qos_req_min.name);
kfree(clst->cpufreq_policy_qos_req_min.name);
kfree(clst->cpufreq_policy_qos_req_max.name);
kfree(clst);
}
return -EINVAL;
}
static int mmp_bL_cpufreq_exit(struct cpufreq_policy *policy)
{
return 0;
}
static struct cpufreq_driver mmp_bL_cpufreq_driver = {
.name = "mmp-bL-cpufreq",
.flags = CPUFREQ_HAVE_GOVERNOR_PER_POLICY,
.verify = cpufreq_generic_frequency_table_verify,
.get = mmp_bL_cpufreq_get,
.target = mmp_bL_cpufreq_target,
.init = mmp_bL_cpufreq_init,
.exit = mmp_bL_cpufreq_exit,
.attr = cpufreq_generic_attr,
};
static struct dentry *vl_cpufreq;
static int __init mmp_bL_cpufreq_register(void)
{
#ifdef CONFIG_DEBUG_FS
vl_cpufreq = debugfs_create_u32("vl_cpufreq", 0664, pxa, &vl_cpufreq_enable);
if (vl_cpufreq == NULL)
pr_err("%s: Error to create file node vl_cpufreq.\n", __func__);
#endif
return cpufreq_register_driver(&mmp_bL_cpufreq_driver);
}
static void __exit mmp_bL_cpufreq_unregister(void)
{
#ifdef CONFIG_DEBUG_FS
if (vl_cpufreq)
debugfs_remove(vl_cpufreq);
#endif
cpufreq_unregister_driver(&mmp_bL_cpufreq_driver);
}
MODULE_DESCRIPTION("cpufreq driver for Marvell MMP SoC w/ bL arch");
MODULE_LICENSE("GPL");
module_init(mmp_bL_cpufreq_register);
module_exit(mmp_bL_cpufreq_unregister);