OSHW-DEIMOS/SOFTWARE/A64-TERES/linux-a64/drivers/thermal/sunxi-cpufreq-cooling.c
Dimitar Gamishev f9b0e7a283 linux
2017-10-13 14:07:04 +03:00

373 lines
11 KiB
C
Executable File

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/cpufreq.h>
#include <linux/thermal.h>
#include <linux/platform_device.h>
#include <linux/of_platform.h>
#include <linux/cpufreq.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/cpu.h>
#include <linux/cpumask.h>
#include "thermal_core.h"
#include "sunxi-cpufreq-cooling.h"
#define BOOT_CPU 0
#define SUNXI_CPUFREQ_COOLING_NAME "sunxi_cpufreq_cool"
static struct sunxi_cpufreq_cooling_device *cpufreq_cdev;
static int is_cpufreq_valid(unsigned int cpu)
{
struct cpufreq_policy policy;
return !cpufreq_get_policy(&policy, cpu);
}
int sunxi_cpufreq_update_state(struct sunxi_cpufreq_cooling_device *cooling_device)
{
int ret = 0,cluster;
unsigned int cpuid;
struct cpufreq_policy policy;
for(cluster = 0; cluster < cpufreq_cdev->cluster_num; cluster++){
for_each_cpu(cpuid, &cooling_device->cluster_cpus[cluster]) {
if (is_cpufreq_valid(cpuid))
{
if((cpufreq_get_policy(&policy, cpuid) == 0) &&
policy.user_policy.governor){
cpufreq_update_policy(cpuid);
break;
}
}
}
}
return ret;
}
EXPORT_SYMBOL(sunxi_cpufreq_update_state);
static int cpu_budget_apply_cooling(struct sunxi_cpufreq_cooling_device *cooling_device,
unsigned long cooling_state)
{
unsigned long flags;
int cluster;
/* struct thermal_instance *instance; */
/* int temperature = 0; */
/* Check if the old cooling action is same as new cooling action */
if (cooling_device->cooling_state == cooling_state)
return 0;
cooling_device->cooling_state = cooling_state;
if(cooling_state >= cooling_device->tbl_num)
return -EINVAL;
spin_lock_irqsave(&cooling_device->lock, flags);
for(cluster = 0; cluster < cooling_device->cluster_num; cluster++)
cooling_device->cluster_freq_limit[cluster] = cooling_device->tbl[cooling_state].cluster_freq[cluster];
spin_unlock_irqrestore(&cooling_device->lock, flags);
/*
trace_cpu_budget_throttle(cooling_state,
cooling_device->cluster0_freq_limit,
cooling_device->cluster0_num_limit ,
cooling_device->cluster1_freq_limit,
cooling_device->cluster1_num_limit,
cooling_device->gpu_throttle);
list_for_each_entry(instance, &(cooling_device->cool_dev->thermal_instances), cdev_node) {
if(instance->tz->temperature > temperature)
temperature = instance->tz->temperature;
}
pr_info("CPU Budget: Temperature: %u Limit state:%lu item[%d,%d,%d,%d %d]\n",temperature,cooling_state,
cooling_device->cluster0_freq_limit,
cooling_device->cluster0_num_limit ,
cooling_device->cluster1_freq_limit ,
cooling_device->cluster1_num_limit,
cooling_device->gpu_throttle);
*/
return sunxi_cpufreq_update_state(cooling_device);
}
static int cpufreq_thermal_notifier(struct notifier_block *nb,
unsigned long event, void *data)
{
struct cpufreq_policy *policy = data;
int cluster = 0;
unsigned long limit_freq=0,base_freq=0,head_freq=0;
unsigned long max_freq=0,min_freq=0;
if (event != CPUFREQ_ADJUST || cpufreq_cdev == NOTIFY_INVALID)
return 0;
while(cluster < cpufreq_cdev->cluster_num){
if (cpumask_test_cpu(policy->cpu, &cpufreq_cdev->cluster_cpus[cluster])){
limit_freq = cpufreq_cdev->cluster_freq_limit[cluster];
base_freq = cpufreq_cdev->cluster_freq_floor[cluster];
head_freq = cpufreq_cdev->cluster_freq_roof[cluster];
break;
}else
cluster ++;
}
if(cluster == cpufreq_cdev->cluster_num)
return 0;
if(limit_freq && limit_freq != INVALID_FREQ)
{
max_freq =(head_freq >= limit_freq)?limit_freq:head_freq;
min_freq = base_freq;
/* Never exceed policy.max*/
if (max_freq > policy->max)
max_freq = policy->max;
if (min_freq < policy->min)
{
min_freq = policy->min;
}
min_freq = (min_freq < max_freq)?min_freq:max_freq;
if (policy->max != max_freq || policy->min != min_freq )
{
cpufreq_verify_within_limits(policy, min_freq, max_freq);
policy->user_policy.max = policy->max;
pr_info("CPU Budget:update CPU %d cpufreq max to %lu min to %lu\n",policy->cpu,max_freq, min_freq);
}
}
return 0;
}
/*
* cpufreq cooling device callback functions are defined below
*/
/**
* cpu_budget_get_max_state - callback function to get the max cooling state.
* @cdev: thermal cooling device pointer.
* @state: fill this variable with the max cooling state.
*/
static int cpu_freq_get_max_state(struct thermal_cooling_device *cdev,
unsigned long *state)
{
struct sunxi_cpufreq_cooling_device *cooling_device = cdev->devdata;
*state = cooling_device->tbl_num-1;
return 0;
}
/**
* cpu_budget_get_cur_state - callback function to get the current cooling state.
* @cdev: thermal cooling device pointer.
* @state: fill this variable with the current cooling state.
*/
static int cpu_freq_get_cur_state(struct thermal_cooling_device *cdev,
unsigned long *state)
{
struct sunxi_cpufreq_cooling_device *cooling_device = cdev->devdata;
*state = cooling_device->cooling_state;
return 0;
}
/**
* cpu_budget_set_cur_state - callback function to set the current cooling state.
* @cdev: thermal cooling device pointer.
* @state: set this variable to the current cooling state.
*/
static int cpu_freq_set_cur_state(struct thermal_cooling_device *cdev,
unsigned long state)
{
struct sunxi_cpufreq_cooling_device *cooling_device = cdev->devdata;
return cpu_budget_apply_cooling(cooling_device, state);
}
/* Bind cpufreq callbacks to thermal cooling device ops */
static struct thermal_cooling_device_ops const sunxi_cpu_cooling_ops = {
.get_max_state = cpu_freq_get_max_state,
.get_cur_state = cpu_freq_get_cur_state,
.set_cur_state = cpu_freq_set_cur_state,
};
static int sunxi_cpufreq_cooling_get_cpu(void)
{
int cluster,i;
unsigned int min = 0, max = 0;
struct cpufreq_policy policy;
#if defined(CONFIG_SCHED_HMP)
arch_get_fast_and_slow_cpus(&cpufreq_cdev->cluster_cpus[1],&cpufreq_cdev->cluster_cpus[0]);
#elif defined(CONFIG_SCHED_SMP_DCMP)
if (strlen(CONFIG_CLUSTER0_CPU_MASK) && strlen(CONFIG_CLUSTER1_CPU_MASK)) {
if (cpulist_parse(CONFIG_CLUSTER0_CPU_MASK, &cpufreq_cdev->cluster_cpus[0])) {
pr_err("Failed to parse cluster0 cpu mask!\n");
return -EBUSY;
}
if (cpulist_parse(CONFIG_CLUSTER1_CPU_MASK, &cpufreq_cdev->cluster_cpus[1])) {
pr_err("Failed to parse cluster1 cpu mask!\n");
return -EBUSY;
}
}
#else
cpumask_copy(&cpufreq_cdev->cluster_cpus[0], cpu_possible_mask);
cpufreq_cdev->cluster_num = 1;
#endif
for(cluster = 0; cluster < cpufreq_cdev->cluster_num; min = 0, max = 0, cluster ++){
for_each_cpu(i, &cpufreq_cdev->cluster_cpus[cluster]) {
/*continue if cpufreq policy not found and not return error*/
if (!cpufreq_get_policy(&policy, i))
continue;
if (min == 0 && max == 0) {
min = policy.cpuinfo.min_freq;
max = policy.cpuinfo.max_freq;
} else {
if (min != policy.cpuinfo.min_freq ||
max != policy.cpuinfo.max_freq){
pr_err("different freq, return.\n");
return -EBUSY;
}
}
}
}
return 0;
}
static int sunxi_cpufreq_cooling_parse(struct platform_device *pdev)
{
struct device_node *np = NULL;
struct sunxi_cpufreq_cooling_table *ctbl = NULL;
int i,j,cluster;
int ret = 0;
char name[32];
np = pdev->dev.of_node;
if (!of_device_is_available(np)) {
pr_err("%s: cpufreq cooling is disable", __func__);
return -EPERM;
}
cpufreq_cdev = kzalloc(sizeof(*cpufreq_cdev), GFP_KERNEL);
if (IS_ERR_OR_NULL(cpufreq_cdev)) {
pr_err("cooling_dev: not enough memory for cpufreq cooling data\n");
return -ENOMEM;
}
if (of_property_read_u32(np, "cluster_num", &cpufreq_cdev->cluster_num)) {
pr_err("%s: get cluster_num failed", __func__);
ret = -EBUSY;
goto parse_fail_1;
}
if (of_property_read_u32(np, "state_cnt", &cpufreq_cdev->tbl_num)) {
pr_err("%s: get state_cnt failed", __func__);
ret = -EBUSY;
goto parse_fail_1;
}
ret = sunxi_cpufreq_cooling_get_cpu();
if(ret)
goto parse_fail_1;
ctbl = kzalloc(cpufreq_cdev->tbl_num * sizeof(*ctbl), GFP_KERNEL);
if (!ctbl) {
ret = -ENOMEM;
goto parse_fail_1;
}
for(i = 0; i < cpufreq_cdev->tbl_num; i++){
sprintf(name, "state%d", i);
for(j = 0; j < cpufreq_cdev->cluster_num; j++){
if (of_property_read_u32_index(np, (const char *)&name,
j, &(ctbl[i].cluster_freq[j]))) {
pr_err("node %s get failed!\n", name);
ret = -EBUSY;
goto parse_fail_0;
}
}
}
cpufreq_cdev->tbl = ctbl;
for(cluster = 0; cluster < cpufreq_cdev->cluster_num; cluster ++){
cpufreq_cdev->cluster_freq_limit[cluster] = cpufreq_cdev->tbl[0].cluster_freq[cluster];
cpufreq_cdev->cluster_freq_roof[cluster] = cpufreq_cdev->cluster_freq_limit[cluster];
}
return 0;
parse_fail_0:
kfree(ctbl);
parse_fail_1:
kfree(cpufreq_cdev);
return ret;
}
static int sunxi_cpufreq_cooling_probe(struct platform_device *pdev)
{
s32 err = 0;
struct thermal_cooling_device *cool_dev;
pr_info("sunxi cpufreq cooling probe start !\n");
if (pdev->dev.of_node) {
/* get dt and sysconfig */
err = sunxi_cpufreq_cooling_parse(pdev);
}else{
pr_err("sunxi cpufreq cooling device tree err!\n");
return -EBUSY;
}
if(err){
pr_err("sunxi cpufreq cooling device tree parse err!\n");
return -EBUSY;
}
spin_lock_init(&cpufreq_cdev->lock);
cool_dev = thermal_of_cooling_device_register(pdev->dev.of_node, SUNXI_CPUFREQ_COOLING_NAME,
cpufreq_cdev, &sunxi_cpu_cooling_ops);
if (!cool_dev)
goto fail;
cpufreq_cdev->cool_dev = cool_dev;
cpufreq_cdev->cooling_state = 0;
cpufreq_cdev->cpufreq_notifer.notifier_call=cpufreq_thermal_notifier;
cpufreq_register_notifier(&(cpufreq_cdev->cpufreq_notifer),
CPUFREQ_POLICY_NOTIFIER);
pr_info("CPU freq cooling register Success\n");
return 0;
fail:
kfree(cpufreq_cdev->tbl);
kfree(cpufreq_cdev);
return -EBUSY;
}
static int sunxi_cpufreq_cooling_remove(struct platform_device *pdev)
{
cpufreq_unregister_notifier(&(cpufreq_cdev->cpufreq_notifer),
CPUFREQ_POLICY_NOTIFIER);
pr_info("CPU freq notifer unregister Success\n");
thermal_cooling_device_unregister(cpufreq_cdev->cool_dev);
kfree(cpufreq_cdev->tbl);
kfree(cpufreq_cdev);
pr_info("CPU freq cooling unregister Success\n");
return 0;
}
#ifdef CONFIG_OF
/* Translate OpenFirmware node properties into platform_data */
static struct of_device_id sunxi_cpufreq_cooling_of_match[] = {
{ .compatible = "allwinner,cpufreq_cooling", },
{ },
};
MODULE_DEVICE_TABLE(of, sunxi_cpufreq_cooling_of_match);
#else /* !CONFIG_OF */
#endif
static struct platform_driver sunxi_cpufreq_cooling_driver = {
.probe = sunxi_cpufreq_cooling_probe,
.remove = sunxi_cpufreq_cooling_remove,
.driver = {
.name = SUNXI_CPUFREQ_COOLING_NAME,
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(sunxi_cpufreq_cooling_of_match),
}
};
module_platform_driver(sunxi_cpufreq_cooling_driver);
MODULE_DESCRIPTION("SUNXI cpufreq cooling driver");
MODULE_AUTHOR("QIn");
MODULE_LICENSE("GPL v2");