blob: ed59aeeca4ce248c4e155d0e9abea15208ed72c6 [file] [log] [blame]
#include <linux/module.h>
#include <linux/thermal.h>
#include <linux/cpufreq.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/cpu.h>
#include <linux/pm_qos.h>
#include <linux/devfreq.h>
#include <linux/cooling_dev_asr.h>
#include <linux/cputype.h>
struct freq_cool_device {
struct thermal_cooling_device *cool_dev;
unsigned int *freqs;
unsigned int max_state;
unsigned int cur_state;
struct pm_qos_request qos_max;
};
struct cpuhotplug_cool_device {
struct thermal_cooling_device *cool_dev;
unsigned int max_state;
unsigned int cur_state;
struct pm_qos_request cpu_num_max;
};
#ifdef CONFIG_HOTPLUG_CPU
/* cpuhotplug cooling device callback functions are defined below */
static int cpuhotplug_get_max_state(struct thermal_cooling_device *cdev,
unsigned long *state)
{
struct cpuhotplug_cool_device *cpuhotplug_device = cdev->devdata;
*state = cpuhotplug_device->max_state;
return 0;
}
static int cpuhotplug_get_cur_state(struct thermal_cooling_device *cdev,
unsigned long *state)
{
struct cpuhotplug_cool_device *cpuhotplug_device = cdev->devdata;
*state = cpuhotplug_device->cur_state;
return 0;
}
static int cpuhotplug_set_cur_state(struct thermal_cooling_device *cdev,
unsigned long state)
{
struct cpuhotplug_cool_device *cpuhotplug_device = cdev->devdata;
int cpu_num = num_possible_cpus();
if (state > cpuhotplug_device->max_state)
return -EINVAL;
cpuhotplug_device->cur_state = state;
pm_qos_update_request(&cpuhotplug_device->cpu_num_max, cpu_num - state);
return 0;
}
/* Bind cpufreq callbacks to thermal cooling device ops */
static struct thermal_cooling_device_ops const cpuhotplug_cooling_ops = {
.get_max_state = cpuhotplug_get_max_state,
.get_cur_state = cpuhotplug_get_cur_state,
.set_cur_state = cpuhotplug_set_cur_state,
};
struct thermal_cooling_device *cpuhotplug_cool_register(void)
{
struct thermal_cooling_device *cool_dev;
struct cpuhotplug_cool_device *cpuhotplug_dev = NULL;
cpuhotplug_dev = kzalloc(sizeof(struct cpuhotplug_cool_device),
GFP_KERNEL);
if (!cpuhotplug_dev)
return ERR_PTR(-ENOMEM);
cool_dev = thermal_cooling_device_register("cpu-hotplug-cool",
cpuhotplug_dev, &cpuhotplug_cooling_ops);
if (!cool_dev) {
kfree(cpuhotplug_dev);
return ERR_PTR(-EINVAL);
}
cpuhotplug_dev->max_state = num_possible_cpus() - 1;
cpuhotplug_dev->cool_dev = cool_dev;
cpuhotplug_dev->cur_state = 0;
cpuhotplug_dev->cpu_num_max.name = "req_thermal";
pm_qos_add_request(&cpuhotplug_dev->cpu_num_max,
PM_QOS_CPU_NUM_MAX, num_possible_cpus());
return cool_dev;
}
EXPORT_SYMBOL_GPL(cpuhotplug_cool_register);
void cpuhotplug_cool_unregister(struct thermal_cooling_device *cdev)
{
struct cpuhotplug_cool_device *cpuhotplug_dev = cdev->devdata;
pm_qos_remove_request(&cpuhotplug_dev->cpu_num_max);
thermal_cooling_device_unregister(cpuhotplug_dev->cool_dev);
kfree(cpuhotplug_dev);
}
EXPORT_SYMBOL_GPL(cpuhotplug_cool_unregister);
#endif
#ifdef CONFIG_CPU_FREQ
/* cpufreq cooling device callback functions are defined below */
static int cpufreq_get_max_state(struct thermal_cooling_device *cdev,
unsigned long *state)
{
struct freq_cool_device *cpufreq_device = cdev->devdata;
*state = cpufreq_device->max_state;
return 0;
}
static int cpufreq_get_cur_state(struct thermal_cooling_device *cdev,
unsigned long *state)
{
struct freq_cool_device *cpufreq_device = cdev->devdata;
*state = cpufreq_device->cur_state;
return 0;
}
static int cpufreq_set_cur_state(struct thermal_cooling_device *cdev,
unsigned long state)
{
struct freq_cool_device *cpufreq_device = cdev->devdata;
if (state > cpufreq_device->max_state)
return -EINVAL;
cpufreq_device->cur_state = state;
pm_qos_update_request(&cpufreq_device->qos_max,
cpufreq_device->freqs[state]);
return 0;
}
/* Bind cpufreq callbacks to thermal cooling device ops */
static struct thermal_cooling_device_ops const cpufreq_cooling_ops = {
.get_max_state = cpufreq_get_max_state,
.get_cur_state = cpufreq_get_cur_state,
.set_cur_state = cpufreq_set_cur_state,
};
struct thermal_cooling_device *cpufreq_cool_register(void)
{
struct thermal_cooling_device *cool_dev;
struct freq_cool_device *cpufreq_dev = NULL;
int i, index = 0, descend = -1;
unsigned int max_level = 0;
unsigned int freq = CPUFREQ_ENTRY_INVALID;
struct cpufreq_frequency_table *table =
cpufreq_frequency_get_table(0);
cpufreq_dev = kzalloc(sizeof(struct freq_cool_device),
GFP_KERNEL);
if (!cpufreq_dev)
return ERR_PTR(-ENOMEM);
cool_dev = thermal_cooling_device_register("cpu-freq-cool", cpufreq_dev,
&cpufreq_cooling_ops);
if (!cool_dev) {
kfree(cpufreq_dev);
return ERR_PTR(-EINVAL);
}
/* get frequency number and order*/
for (i = 0; table[i].frequency != CPUFREQ_TABLE_END; i++) {
if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
continue;
if (freq != CPUFREQ_ENTRY_INVALID && descend == -1)
descend = !!(freq > table[i].frequency);
freq = table[i].frequency;
max_level++;
}
cpufreq_dev->max_state = max_level - 1;
/* init freqs */
cpufreq_dev->freqs = kzalloc(sizeof(unsigned int) * max_level,
GFP_KERNEL);
if (!cpufreq_dev->freqs) {
thermal_cooling_device_unregister(cool_dev);
kfree(cpufreq_dev);
return ERR_PTR(-ENOMEM);
}
for (i = 0; table[i].frequency != CPUFREQ_TABLE_END; i++) {
if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
continue;
index = descend ? i : (max_level - i - 1);
cpufreq_dev->freqs[index] = table[i].frequency;
}
cpufreq_dev->cool_dev = cool_dev;
cpufreq_dev->cur_state = 0;
cpufreq_dev->qos_max.name = "req_thermal";
pm_qos_add_request(&cpufreq_dev->qos_max,
PM_QOS_CPUFREQ_MAX, PM_QOS_DEFAULT_VALUE);
return cool_dev;
}
EXPORT_SYMBOL_GPL(cpufreq_cool_register);
void cpufreq_cool_unregister(struct thermal_cooling_device *cdev)
{
struct freq_cool_device *cpufreq_dev = cdev->devdata;
pm_qos_remove_request(&cpufreq_dev->qos_max);
thermal_cooling_device_unregister(cpufreq_dev->cool_dev);
kfree(cpufreq_dev->freqs);
kfree(cpufreq_dev);
}
EXPORT_SYMBOL_GPL(cpufreq_cool_unregister);
#endif
static int freq_get_max_state(struct thermal_cooling_device *cdev,
unsigned long *state)
{
struct freq_cool_device *freq_device = cdev->devdata;
*state = freq_device->max_state;
return 0;
}
static int freq_get_cur_state(struct thermal_cooling_device *cdev,
unsigned long *state)
{
struct freq_cool_device *freq_device = cdev->devdata;
*state = freq_device->cur_state;
return 0;
}
static int freq_set_cur_state(struct thermal_cooling_device *cdev,
unsigned long state)
{
struct freq_cool_device *freq_device = cdev->devdata;
if (state > freq_device->max_state)
return -EINVAL;
freq_device->cur_state = state;
pm_qos_update_request(&freq_device->qos_max,
freq_device->freqs[state]);
return 0;
}
/* Bind cpufreq callbacks to thermal cooling device ops */
static struct thermal_cooling_device_ops const freq_cooling_ops = {
.get_max_state = freq_get_max_state,
.get_cur_state = freq_get_cur_state,
.set_cur_state = freq_set_cur_state,
};
#ifdef CONFIG_DDR_DEVFREQ
struct thermal_cooling_device *ddrfreq_cool_register(void)
{
struct thermal_cooling_device *cool_dev;
struct freq_cool_device *freq_dev = NULL;
int i, descend = -1, index = 0;
unsigned int max_level = 0, freq = 0;
struct devfreq_frequency_table *table =
devfreq_frequency_get_table(DEVFREQ_DDR);
freq_dev = kzalloc(sizeof(struct freq_cool_device),
GFP_KERNEL);
if (!freq_dev)
return ERR_PTR(-ENOMEM);
cool_dev = thermal_cooling_device_register("ddr-freq-cool", freq_dev,
&freq_cooling_ops);
if (!cool_dev) {
kfree(freq_dev);
return ERR_PTR(-EINVAL);
}
freq_dev->cool_dev = cool_dev;
freq_dev->cur_state = 0;
/* get frequency number */
for (i = 0; table[i].frequency != DEVFREQ_TABLE_END; i++) {
if (freq != 0 && descend == -1)
descend = !!(freq > table[i].frequency);
freq = table[i].frequency;
max_level++;
}
freq_dev->max_state = max_level - 1;
/* init freqs */
freq_dev->freqs = kzalloc(sizeof(unsigned int) * max_level,
GFP_KERNEL);
if (!freq_dev->freqs) {
thermal_cooling_device_unregister(cool_dev);
kfree(freq_dev);
return ERR_PTR(-ENOMEM);
}
for (i = 0; table[i].frequency != DEVFREQ_TABLE_END; i++) {
index = descend ? i : (max_level - i - 1);
freq_dev->freqs[index] = table[i].frequency;
}
freq_dev->qos_max.name = "req_thermal";
pm_qos_add_request(&freq_dev->qos_max,
PM_QOS_DDR_DEVFREQ_MAX, PM_QOS_DEFAULT_VALUE);
return cool_dev;
}
EXPORT_SYMBOL_GPL(ddrfreq_cool_register);
void ddrfreq_cool_unregister(struct thermal_cooling_device *cdev)
{
struct freq_cool_device *freq_dev = cdev->devdata;
pm_qos_remove_request(&freq_dev->qos_max);
thermal_cooling_device_unregister(freq_dev->cool_dev);
kfree(freq_dev->freqs);
kfree(freq_dev);
}
EXPORT_SYMBOL_GPL(ddrfreq_cool_unregister);
#endif
#ifdef CONFIG_VPU_DEVFREQ
struct thermal_cooling_device *vpufreq_cool_register(unsigned int dev_id)
{
struct thermal_cooling_device *cool_dev;
struct freq_cool_device *freq_dev = NULL;
int i, descend = -1, index = 0;
unsigned int max_level = 0, freq = 0;
struct devfreq_frequency_table *table =
devfreq_frequency_get_table(dev_id);
freq_dev = kzalloc(sizeof(struct freq_cool_device),
GFP_KERNEL);
if (!freq_dev)
return ERR_PTR(-ENOMEM);
cool_dev = thermal_cooling_device_register("vpu-freq-cool", freq_dev,
&freq_cooling_ops);
if (!cool_dev) {
kfree(freq_dev);
return ERR_PTR(-EINVAL);
}
freq_dev->cool_dev = cool_dev;
freq_dev->cur_state = 0;
/* get frequency number */
for (i = 0; table[i].frequency != DEVFREQ_TABLE_END; i++) {
if (freq != 0 && descend == -1)
descend = !!(freq > table[i].frequency);
freq = table[i].frequency;
max_level++;
}
freq_dev->max_state = max_level - 1;
/* init freqs */
freq_dev->freqs = kzalloc(sizeof(unsigned int) * max_level,
GFP_KERNEL);
if (!freq_dev->freqs) {
thermal_cooling_device_unregister(cool_dev);
kfree(freq_dev);
return ERR_PTR(-ENOMEM);
}
for (i = 0; table[i].frequency != DEVFREQ_TABLE_END; i++) {
index = descend ? i : (max_level - i - 1);
freq_dev->freqs[index] = table[i].frequency;
}
freq_dev->qos_max.name = "req_thermal";
pm_qos_add_request(&freq_dev->qos_max,
PM_QOS_VPU_DEVFREQ_MAX, PM_QOS_DEFAULT_VALUE);
return cool_dev;
}
EXPORT_SYMBOL_GPL(vpufreq_cool_register);
void vpufreq_cool_unregister(struct thermal_cooling_device *cdev)
{
struct freq_cool_device *freq_dev = cdev->devdata;
pm_qos_remove_request(&freq_dev->qos_max);
thermal_cooling_device_unregister(freq_dev->cool_dev);
kfree(freq_dev->freqs);
kfree(freq_dev);
}
EXPORT_SYMBOL_GPL(vpufreq_cool_unregister);
#endif
#ifdef CONFIG_COMMON_CLK
#include <linux/clk/mmp.h>
#if !defined(CONFIG_CPU_ASR18XX) && !defined(CONFIG_CPU_ASR1901)
struct thermal_cooling_device *gpufreq_cool_register(const char *gc_name)
{
struct thermal_cooling_device *cool_dev;
struct freq_cool_device *freq_dev = NULL;
int i, descend = -1, index = 0;
unsigned int max_level = 0, freq = 0;
char cool_name[THERMAL_NAME_LENGTH];
unsigned long dev_freqs[THERMAL_MAX_TRIPS] = {0};
freq_dev = kzalloc(sizeof(struct freq_cool_device),
GFP_KERNEL);
if (!freq_dev)
return ERR_PTR(-ENOMEM);
snprintf(cool_name, sizeof(cool_name), "%s-freq-cool", gc_name);
cool_dev = thermal_cooling_device_register(cool_name, freq_dev,
&freq_cooling_ops);
if (!cool_dev) {
kfree(freq_dev);
return ERR_PTR(-EINVAL);
}
freq_dev->cool_dev = cool_dev;
freq_dev->cur_state = 0;
if (cpu_is_pxa1928()) {
if (!strcmp("gc3d", gc_name))
get_gc3d_freqs_table(dev_freqs, &max_level, THERMAL_MAX_TRIPS);
else if (!strcmp("gc2d", gc_name))
get_gc2d_freqs_table(dev_freqs, &max_level, THERMAL_MAX_TRIPS);
else
get_gc3d_sh_freqs_table(dev_freqs, &max_level, THERMAL_MAX_TRIPS);
} else {
/* get frequency number */
if (!strcmp("gc3d", gc_name))
get_gcu_freqs_table(dev_freqs, &max_level, THERMAL_MAX_TRIPS);
else if (!strcmp("gc2d", gc_name))
get_gcu2d_freqs_table(dev_freqs, &max_level, THERMAL_MAX_TRIPS);
else
get_gc_shader_freqs_table(dev_freqs, &max_level, THERMAL_MAX_TRIPS);
}
for (i = 0; i < max_level; i++) {
if (freq != 0 && descend == -1)
descend = !!(freq > dev_freqs[i]);
freq = dev_freqs[i];
}
freq_dev->max_state = max_level - 1;
/* init freqs */
freq_dev->freqs = kzalloc(sizeof(unsigned int) * max_level,
GFP_KERNEL);
if (!freq_dev->freqs) {
thermal_cooling_device_unregister(cool_dev);
kfree(freq_dev);
return ERR_PTR(-ENOMEM);
}
for (i = 0; i < max_level; i++) {
index = descend ? i : (max_level - i - 1);
/* Hz to kHz */
freq_dev->freqs[index] = dev_freqs[i] / 1000;
}
freq_dev->qos_max.name = "req_thermal";
if (!strcmp("gc3d", gc_name))
pm_qos_add_request(&freq_dev->qos_max,
PM_QOS_GPUFREQ_3D_MAX, INT_MAX);
else if (!strcmp("gc2d", gc_name))
pm_qos_add_request(&freq_dev->qos_max,
PM_QOS_GPUFREQ_2D_MAX, INT_MAX);
else
pm_qos_add_request(&freq_dev->qos_max,
PM_QOS_GPUFREQ_SH_MAX, INT_MAX);
return cool_dev;
}
EXPORT_SYMBOL_GPL(gpufreq_cool_register);
void gpufreq_cool_unregister(struct thermal_cooling_device *cdev)
{
struct freq_cool_device *freq_dev = cdev->devdata;
pm_qos_remove_request(&freq_dev->qos_max);
thermal_cooling_device_unregister(freq_dev->cool_dev);
kfree(freq_dev->freqs);
kfree(freq_dev);
}
EXPORT_SYMBOL_GPL(gpufreq_cool_unregister);
#endif
#endif