blob: bb9a0d521cbd72cf05cc41c28341cc7a55fc52a6 [file] [log] [blame]
/*
* arch/arm/mach-tegra/tegra_volt_cap.c
*
* Copyright (c) 2013, NVIDIA CORPORATION. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope 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.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <linux/kernel.h>
#include <linux/hrtimer.h>
#include <linux/module.h>
#include <mach/edp.h>
#include "cpu-tegra.h"
#define WATCHDOG_TIMER_RATE 3900000 /* 1hr5min in msecs */
#define CPU_THERMAL_ZONE_TYPE "CPU-therm"
#define ALTERNATE_CPU_TZ_TYPE "Tdiode_tegra"
/* If the watch dog times out, use this as the voltage cap */
#define DEFAULT_CPU_VMAX_CAP 1180
static struct timer_list watchdog_timer;
static struct work_struct reset_cap_work;
static struct thermal_zone_device *cpu_tz;
static int cpu_vc_temperatures[] = {
30, 40, 50, 60, 70, 80, 90, 100, 110, 120
};
static struct tegra_cooling_device cpu_vc_cdev = {
.cdev_type = "tegra_cpu_vc",
.trip_temperatures = cpu_vc_temperatures,
.trip_temperatures_num = ARRAY_SIZE(cpu_vc_temperatures)
};
static int cpu_voltage_cap;
struct volt_cap_data {
struct tegra_cooling_device *cd;
int thermal_idx;
};
static struct volt_cap_data cpuv_capping_data = {
.cd = &cpu_vc_cdev,
.thermal_idx = 0,
};
/* Protects cpu_voltage_cap and serializes calls to update the voltage cap */
DEFINE_MUTEX(cpu_volt_cap_lock);
static struct kobject *volt_cap_kobj;
static ssize_t tegra_cpu_volt_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
return scnprintf(buf, PAGE_SIZE, "%d\n", cpu_voltage_cap);
}
static ssize_t tegra_cpu_volt_store(struct kobject *kobj,
struct kobj_attribute *attr,
const char *buf, size_t count)
{
unsigned int val, freq;
if (kstrtou32(buf, 10, &val))
return -EINVAL;
mutex_lock(&cpu_volt_cap_lock);
freq = 0;
if (cpu_voltage_cap != val) {
if (val)
freq = tegra_edp_find_maxf(val) / 1000;
else
freq = tegra_edp_find_maxf(DEFAULT_CPU_VMAX_CAP) / 1000;
tegra_cpu_set_volt_cap(freq);
}
cpu_voltage_cap = val;
mod_timer(&watchdog_timer, jiffies +
msecs_to_jiffies(WATCHDOG_TIMER_RATE));
mutex_unlock(&cpu_volt_cap_lock);
return count;
}
static struct kobj_attribute tegra_cpu_volt =
__ATTR(cpu_volt, 0644, tegra_cpu_volt_show, tegra_cpu_volt_store);
static ssize_t watchdog_rate_sec_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
return scnprintf(buf, PAGE_SIZE, "%d\n", WATCHDOG_TIMER_RATE / 1000);
}
static struct kobj_attribute watchdog_rate =
__ATTR_RO(watchdog_rate_sec);
const struct attribute *tegra_volt_cap_attrs[] = {
&tegra_cpu_volt.attr,
&watchdog_rate.attr,
NULL,
};
static int volt_cap_sysfs_init(void)
{
volt_cap_kobj = kobject_create_and_add("tegra_volt_cap", kernel_kobj);
if (!volt_cap_kobj) {
pr_info("Tegra volt cap kobject create failed\n");
return -1;
}
if (sysfs_create_files(volt_cap_kobj, tegra_volt_cap_attrs)) {
pr_err("tegra_volt_cap: failed to create sysfs interface\n");
return -1;
}
return 0;
}
static struct thermal_zone_device *get_cpu_tz(void)
{
struct thermal_zone_device *tz;
tz = thermal_zone_device_find_by_name(CPU_THERMAL_ZONE_TYPE);
if (!tz)
tz = thermal_zone_device_find_by_name(ALTERNATE_CPU_TZ_TYPE);
return tz;
}
/* Cooling device limits minimum rail voltage at cold temperature in pll mode */
static int tegra_vc_get_max_state(struct thermal_cooling_device *cdev,
unsigned long *max_state)
{
struct volt_cap_data *vcd = (struct volt_cap_data *)cdev->devdata;
*max_state = vcd->cd->trip_temperatures_num;
return 0;
}
static int tegra_vc_get_cur_state(struct thermal_cooling_device *cdev,
unsigned long *cur_state)
{
struct volt_cap_data *vcd = (struct volt_cap_data *)cdev->devdata;
*cur_state = vcd->thermal_idx;
return 0;
}
static int tegra_vc_set_cur_state(struct thermal_cooling_device *cdev,
unsigned long cur_state)
{
unsigned long prev_state;
unsigned long idx;
struct volt_cap_data *vcd = (struct volt_cap_data *)cdev->devdata;
prev_state = vcd->thermal_idx;
vcd->thermal_idx = cur_state;
if (prev_state != cur_state && cpu_voltage_cap != 0) {
if (!cpu_tz)
cpu_tz = get_cpu_tz();
if (!cpu_tz) {
pr_err("tegra_volt_cap: Couldn't find cpu tz\n");
return 0;
}
idx = cur_state;
/* Actual trip point being crossed */
if (idx)
idx = idx - 1;
thermal_generate_netlink_event(cpu_tz,
prev_state < cur_state ? THERMAL_AUX0 :
THERMAL_AUX1,
vcd->cd->trip_temperatures[idx]);
}
return 0;
}
static struct thermal_cooling_device_ops tegra_vc_notify_cooling_ops = {
.get_max_state = tegra_vc_get_max_state,
.get_cur_state = tegra_vc_get_cur_state,
.set_cur_state = tegra_vc_set_cur_state,
};
struct tegra_cooling_device *tegra_vc_get_cdev(void)
{
return &cpu_vc_cdev;
}
static void reset_cpu_volt_cap(struct work_struct *work)
{
unsigned int freq;
mutex_lock(&cpu_volt_cap_lock);
cpu_voltage_cap = 0;
freq = tegra_edp_find_maxf(DEFAULT_CPU_VMAX_CAP) / 1000;
tegra_cpu_set_volt_cap(freq);
mutex_unlock(&cpu_volt_cap_lock);
pr_warn("tegra_volt_cap:Timeout. Setting default Vmax-vdd_cpu to: %d\n",
DEFAULT_CPU_VMAX_CAP);
}
static void watchdog_timeout(unsigned long data)
{
schedule_work(&reset_cap_work);
}
static int __init tegra_volt_cap_init(void)
{
struct thermal_cooling_device *tcd;
volt_cap_sysfs_init();
/* Register cpu voltage capping related cooling device */
tcd = thermal_cooling_device_register("tegra_cpu_vc",
&cpuv_capping_data,
&tegra_vc_notify_cooling_ops);
if (IS_ERR_OR_NULL(tcd))
pr_err("tegra cooling device %s failed to register\n",
"tegra-vc");
cpu_tz = get_cpu_tz();
INIT_WORK(&reset_cap_work, reset_cpu_volt_cap);
init_timer(&watchdog_timer);
watchdog_timer.function = watchdog_timeout;
return 0;
}
MODULE_DESCRIPTION("Tegra voltage capping driver");
MODULE_LICENSE("GPL");
module_init(tegra_volt_cap_init);