348 lines
10 KiB
C
Executable File
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");
|
|
|