/* * 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 . */ #include #include #include #include #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);