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

348 lines
10 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 "sunxi-budget-cooling.h"
#include "sunxi-budget-interface.h"
#define SUNXI_BUDGET_COOLING_NAME "sunxi-budget"
#define SUNXI_BUDGET_DRIVER_NAME "sunxi-budget-cooling"
static struct sunxi_budget_cooling_device *budget_cdev;
#define USERSPACE_ROOMAGE
#ifdef USERSPACE_ROOMAGE
static ssize_t
sunxi_budget_roomage_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
int ret=0,i;
unsigned int roomage_data[8] = {0};
struct sunxi_budget_cooling_device *budget_cdev = dev_get_drvdata(dev);
if(!budget_cdev)
return ret;
for(i = 0; i < budget_cdev->cluster_num; i++){
sunxi_cpufreq_get_roomage(budget_cdev, &roomage_data[2 * i],
&roomage_data[2 * i + 4], i);
sunxi_hotplug_get_roomage(budget_cdev, &roomage_data[2 * i + 1],
&roomage_data[2 * i + 5], i);
}
ret += sprintf(buf, "roomage:%d,%d,%d,%d,%d,%d,%d,%d\n",
roomage_data[0],
roomage_data[1],
roomage_data[2],
roomage_data[3],
roomage_data[4],
roomage_data[5],
roomage_data[6],
roomage_data[7]);
return ret;
}
static ssize_t
sunxi_budget_roomage_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
int i;
unsigned int roomage_data[8] = {0};
unsigned int pre_freq_floor = 0, pre_freq_roof = 0, next_freq_floor = 0, next_freq_roof = 0;
unsigned int pre_num_floor = 0, pre_num_roof = 0, next_num_floor = 0, next_num_roof = 0;
struct sunxi_budget_cooling_device *budget_cdev = dev_get_drvdata(dev);
if(!budget_cdev)
return count;
sscanf(buf,"%u %u %u %u %u %u %u %u\n",
&roomage_data[0],
&roomage_data[1],
&roomage_data[2],
&roomage_data[3],
&roomage_data[4],
&roomage_data[5],
&roomage_data[6],
&roomage_data[7]);
for(i = 0; i < budget_cdev->cluster_num; i++){
sunxi_cpufreq_get_roomage(budget_cdev, &pre_freq_floor, &pre_freq_roof, i);
sunxi_hotplug_get_roomage(budget_cdev, &pre_num_floor, &pre_num_roof, i);
next_freq_floor = roomage_data[2 * i];
next_num_floor = roomage_data[2 * i + 1];
next_freq_roof = roomage_data[2 * i + 4];
next_num_roof = roomage_data[2 *i + 5];
if(next_freq_roof < pre_freq_roof && next_num_roof > pre_num_roof){
sunxi_cpufreq_set_roomage(budget_cdev, next_freq_floor, next_freq_roof, i);
sunxi_hotplug_set_roomage(budget_cdev, next_num_floor, next_num_roof, i);
}else{
sunxi_hotplug_set_roomage(budget_cdev, next_num_floor, next_num_roof, i);
sunxi_cpufreq_set_roomage(budget_cdev, next_freq_floor, next_freq_roof, i);
}
}
return count;
}
static DEVICE_ATTR(roomage, 0644,sunxi_budget_roomage_show, sunxi_budget_roomage_store);
#endif
static int cpu_budget_apply_cooling(struct sunxi_budget_cooling_device *cooling_device,
unsigned long cooling_state)
{
int ret = 0,cluster;
u32 pre_state;
u32 pre_freq = 0, next_freq = 0, pre_num = 0, next_num = 0;
/* Check if the old cooling action is same as new cooling action */
if (cooling_device->cooling_state == cooling_state)
return 0;
if(cooling_state >= cooling_device->state_num)
return -EINVAL;
pre_state = cooling_device->cooling_state;
cooling_device->cooling_state = cooling_state;
for(cluster = 0; cluster < cooling_device->cluster_num; cluster++){
if(cooling_device->cpufreq){
pre_freq = cooling_device->cpufreq->tbl[pre_state].cluster_freq[cluster];
next_freq = cooling_device->cpufreq->tbl[cooling_state].cluster_freq[cluster];
}
if(cooling_device->hotplug){
pre_num = cooling_device->hotplug->tbl[pre_state].cluster_num[cluster];
next_num = cooling_device->hotplug->tbl[cooling_state].cluster_num[cluster];
}
if((pre_freq < next_freq) && (pre_num > next_num)){
ret = sunxi_hotplug_update_state(cooling_device, cluster);
if(ret)
return ret;
ret = sunxi_cpufreq_update_state(cooling_device, cluster);
if(ret)
return ret;
}else{
ret = sunxi_cpufreq_update_state(cooling_device, cluster);
if(ret)
return ret;
ret = sunxi_hotplug_update_state(cooling_device, cluster);
if(ret)
return ret;
}
}
return 0;
}
/**
* 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_budget_get_max_state(struct thermal_cooling_device *cdev,
unsigned long *state)
{
struct sunxi_budget_cooling_device *cooling_device = cdev->devdata;
*state = cooling_device->state_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_budget_get_cur_state(struct thermal_cooling_device *cdev,
unsigned long *state)
{
struct sunxi_budget_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_budget_set_cur_state(struct thermal_cooling_device *cdev,
unsigned long state)
{
struct sunxi_budget_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_budget_get_max_state,
.get_cur_state = cpu_budget_get_cur_state,
.set_cur_state = cpu_budget_set_cur_state,
};
/**
* sunxi_budget_cooling_get_cpumask - get the cpumask of system.
*/
static int sunxi_budget_cooling_get_cpumask(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(&budget_cdev->cluster_cpus[1],&budget_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, &budget_cdev->cluster_cpus[0])) {
pr_err("Failed to parse cluster0 cpu mask!\n");
return -EBUSY;
}
if (cpulist_parse(CONFIG_CLUSTER1_CPU_MASK, &budget_cdev->cluster_cpus[1])) {
pr_err("Failed to parse cluster1 cpu mask!\n");
return -EBUSY;
}
}
#else
cpumask_copy(&budget_cdev->cluster_cpus[0], cpu_possible_mask);
budget_cdev->cluster_num = 1;
#endif
for(cluster = 0; cluster < budget_cdev->cluster_num; min = 0, max = 0, cluster ++){
for_each_cpu(i, &budget_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_budget_cooling_parse(struct platform_device *pdev)
{
struct device_node *np = NULL;
int ret = 0;
np = pdev->dev.of_node;
if (!of_device_is_available(np)) {
pr_err("%s: budget cooling is disable", __func__);
return -EPERM;
}
budget_cdev = kzalloc(sizeof(*budget_cdev), GFP_KERNEL);
if (IS_ERR_OR_NULL(budget_cdev)) {
pr_err("cooling_dev: not enough memory for budget cooling data\n");
return -ENOMEM;
}
if (of_property_read_u32(np, "cluster_num", &budget_cdev->cluster_num)) {
pr_err("%s: get cluster_num failed", __func__);
ret = -EBUSY;
goto parse_fail;
}
if (of_property_read_u32(np, "state_cnt", &budget_cdev->state_num)) {
pr_err("%s: get state_cnt failed", __func__);
ret = -EBUSY;
goto parse_fail;
}
ret = sunxi_budget_cooling_get_cpumask();
if(ret)
goto parse_fail;
return 0;
parse_fail:
kfree(budget_cdev);
return ret;
}
static int sunxi_budget_cooling_probe(struct platform_device *pdev)
{
s32 err = 0;
struct thermal_cooling_device *cool_dev;
pr_info("sunxi budget cooling probe start !\n");
if (pdev->dev.of_node) {
/* get dt and sysconfig */
err = sunxi_budget_cooling_parse(pdev);
}else{
pr_err("sunxi budget cooling device tree err!\n");
return -EBUSY;
}
if(err){
pr_err("sunxi budget cooling device tree parse err!\n");
return -EBUSY;
}
budget_cdev->dev = &pdev->dev;
budget_cdev->cpufreq = sunxi_cpufreq_cooling_register(budget_cdev);
budget_cdev->hotplug = sunxi_hotplug_cooling_register(budget_cdev);
cool_dev = thermal_of_cooling_device_register(pdev->dev.of_node, SUNXI_BUDGET_COOLING_NAME,
budget_cdev, &sunxi_cpu_cooling_ops);
if (!cool_dev)
goto fail;
budget_cdev->cool_dev = cool_dev;
budget_cdev->cooling_state = 0;
dev_set_drvdata(&pdev->dev, budget_cdev);
#ifdef USERSPACE_ROOMAGE
device_create_file(&pdev->dev, &dev_attr_roomage);
#endif
pr_info("CPU budget cooling register Success\n");
return 0;
fail:
sunxi_cpufreq_cooling_unregister(budget_cdev);
sunxi_hotplug_cooling_unregister(budget_cdev);
kfree(budget_cdev);
return -EBUSY;
}
static int sunxi_budget_cooling_remove(struct platform_device *pdev)
{
thermal_cooling_device_unregister(budget_cdev->cool_dev);
sunxi_cpufreq_cooling_unregister(budget_cdev);
sunxi_hotplug_cooling_unregister(budget_cdev);;
kfree(budget_cdev);
pr_info("CPU budget cooling unregister Success\n");
return 0;
}
#ifdef CONFIG_OF
/* Translate OpenFirmware node properties into platform_data */
static struct of_device_id sunxi_budget_cooling_of_match[] = {
{ .compatible = "allwinner,budget_cooling", },
{ },
};
MODULE_DEVICE_TABLE(of, sunxi_budget_cooling_of_match);
#else /* !CONFIG_OF */
#endif
static struct platform_driver sunxi_budget_cooling_driver = {
.probe = sunxi_budget_cooling_probe,
.remove = sunxi_budget_cooling_remove,
.driver = {
.name = SUNXI_BUDGET_DRIVER_NAME,
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(sunxi_budget_cooling_of_match),
}
};
module_platform_driver(sunxi_budget_cooling_driver);
MODULE_DESCRIPTION("SUNXI budget cooling driver");
MODULE_AUTHOR("QIn");
MODULE_LICENSE("GPL v2");