blob: a8d372073bbb76eab8ed408a052a19d73aec131c [file] [log] [blame]
/* Copyright (c) 2012, Code Aurora Forum. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* 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/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/msm_tsens.h>
#include <linux/workqueue.h>
#include <linux/cpu.h>
#include <linux/cpufreq.h>
#include <linux/msm_tsens.h>
#include <linux/msm_thermal.h>
#include <mach/cpufreq.h>
static int enabled;
static struct msm_thermal_data msm_thermal_info;
static uint32_t limited_max_freq = MSM_CPUFREQ_NO_LIMIT;
static struct delayed_work check_temp_work;
static int update_cpu_max_freq(int cpu, uint32_t max_freq)
{
int ret = 0;
ret = msm_cpufreq_set_freq_limits(cpu, MSM_CPUFREQ_NO_LIMIT, max_freq);
if (ret)
return ret;
ret = cpufreq_update_policy(cpu);
if (ret)
return ret;
limited_max_freq = max_freq;
if (max_freq != MSM_CPUFREQ_NO_LIMIT)
pr_info("msm_thermal: Limiting cpu%d max frequency to %d\n",
cpu, max_freq);
else
pr_info("msm_thermal: Max frequency reset for cpu%d\n", cpu);
return ret;
}
static void check_temp(struct work_struct *work)
{
struct tsens_device tsens_dev;
unsigned long temp = 0;
uint32_t max_freq = limited_max_freq;
int cpu = 0;
int ret = 0;
tsens_dev.sensor_num = msm_thermal_info.sensor_id;
ret = tsens_get_temp(&tsens_dev, &temp);
if (ret) {
pr_debug("msm_thermal: Unable to read TSENS sensor %d\n",
tsens_dev.sensor_num);
goto reschedule;
}
if (temp >= msm_thermal_info.limit_temp)
max_freq = msm_thermal_info.limit_freq;
else if (temp <
msm_thermal_info.limit_temp - msm_thermal_info.temp_hysteresis)
max_freq = MSM_CPUFREQ_NO_LIMIT;
if (max_freq == limited_max_freq)
goto reschedule;
/* Update new limits */
for_each_possible_cpu(cpu) {
ret = update_cpu_max_freq(cpu, max_freq);
if (ret)
pr_debug("Unable to limit cpu%d max freq to %d\n",
cpu, max_freq);
}
reschedule:
if (enabled)
schedule_delayed_work(&check_temp_work,
msecs_to_jiffies(msm_thermal_info.poll_ms));
}
static void disable_msm_thermal(void)
{
int cpu = 0;
/* make sure check_temp is no longer running */
cancel_delayed_work(&check_temp_work);
flush_scheduled_work();
if (limited_max_freq == MSM_CPUFREQ_NO_LIMIT)
return;
for_each_possible_cpu(cpu) {
update_cpu_max_freq(cpu, MSM_CPUFREQ_NO_LIMIT);
}
}
static int set_enabled(const char *val, const struct kernel_param *kp)
{
int ret = 0;
ret = param_set_bool(val, kp);
if (!enabled)
disable_msm_thermal();
else
pr_info("msm_thermal: no action for enabled = %d\n", enabled);
pr_info("msm_thermal: enabled = %d\n", enabled);
return ret;
}
static struct kernel_param_ops module_ops = {
.set = set_enabled,
.get = param_get_bool,
};
module_param_cb(enabled, &module_ops, &enabled, 0644);
MODULE_PARM_DESC(enabled, "enforce thermal limit on cpu");
int __init msm_thermal_init(struct msm_thermal_data *pdata)
{
int ret = 0;
BUG_ON(!pdata);
BUG_ON(pdata->sensor_id >= TSENS_MAX_SENSORS);
memcpy(&msm_thermal_info, pdata, sizeof(struct msm_thermal_data));
enabled = 1;
INIT_DELAYED_WORK(&check_temp_work, check_temp);
schedule_delayed_work(&check_temp_work, 0);
return ret;
}