mirror of
https://github.com/AetherDroid/android_kernel_samsung_on5xelte.git
synced 2025-09-08 09:08:05 -04:00
Fixed MTP to work with TWRP
This commit is contained in:
commit
f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions
19
drivers/thermal/samsung/Kconfig
Normal file
19
drivers/thermal/samsung/Kconfig
Normal file
|
@ -0,0 +1,19 @@
|
|||
config EXYNOS_THERMAL
|
||||
tristate "Exynos thermal management unit driver"
|
||||
depends on ARCH_HAS_BANDGAP && OF
|
||||
depends on CPU_THERMAL
|
||||
help
|
||||
If you say yes here you get support for the TMU (Thermal Management
|
||||
Unit) driver for SAMSUNG EXYNOS series of SoCs. This driver initialises
|
||||
the TMU, reports temperature and handles cooling action if defined.
|
||||
This driver uses the Exynos core thermal APIs and TMU configuration
|
||||
data from the supported SoCs.
|
||||
|
||||
config EXYNOS_THERMAL_CORE
|
||||
bool "Core thermal framework support for EXYNOS SOCs"
|
||||
depends on EXYNOS_THERMAL
|
||||
help
|
||||
If you say yes here you get support for EXYNOS TMU
|
||||
(Thermal Management Unit) common registration/unregistration
|
||||
functions to the core thermal layer and also to use the generic
|
||||
CPU cooling APIs.
|
7
drivers/thermal/samsung/Makefile
Normal file
7
drivers/thermal/samsung/Makefile
Normal file
|
@ -0,0 +1,7 @@
|
|||
#
|
||||
# Samsung thermal specific Makefile
|
||||
#
|
||||
obj-$(CONFIG_EXYNOS_THERMAL) += exynos_thermal.o
|
||||
exynos_thermal-y := exynos_tmu.o
|
||||
exynos_thermal-y += exynos_tmu_data.o
|
||||
exynos_thermal-$(CONFIG_EXYNOS_THERMAL_CORE) += exynos_thermal_common.o
|
590
drivers/thermal/samsung/cpu_cooling.c
Executable file
590
drivers/thermal/samsung/cpu_cooling.c
Executable file
|
@ -0,0 +1,590 @@
|
|||
/*
|
||||
* linux/drivers/thermal/cpu_cooling.c
|
||||
*
|
||||
* Copyright (C) 2012 Samsung Electronics Co., Ltd(http://www.samsung.com)
|
||||
* Copyright (C) 2012 Amit Daniel <amit.kachhap@linaro.org>
|
||||
*
|
||||
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; version 2 of the License.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
*
|
||||
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
*/
|
||||
#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/cpu_cooling.h>
|
||||
|
||||
#include <soc/samsung/tmu.h>
|
||||
|
||||
#include "exynos_tmu_data.h"
|
||||
#include <trace/events/exynos.h>
|
||||
|
||||
static DEFINE_IDR(cpufreq_idr);
|
||||
static DEFINE_MUTEX(cooling_cpufreq_lock);
|
||||
static BLOCKING_NOTIFIER_HEAD(cpu_notifier);
|
||||
|
||||
static unsigned int cpufreq_dev_count;
|
||||
|
||||
static LIST_HEAD(cpufreq_dev_list);
|
||||
|
||||
/**
|
||||
* get_idr - function to get a unique id.
|
||||
* @idr: struct idr * handle used to create a id.
|
||||
* @id: int * value generated by this function.
|
||||
*
|
||||
* This function will populate @id with an unique
|
||||
* id, using the idr API.
|
||||
*
|
||||
* Return: 0 on success, an error code on failure.
|
||||
*/
|
||||
static int get_idr(struct idr *idr, int *id)
|
||||
{
|
||||
int ret;
|
||||
|
||||
mutex_lock(&cooling_cpufreq_lock);
|
||||
ret = idr_alloc(idr, NULL, 0, 0, GFP_KERNEL);
|
||||
mutex_unlock(&cooling_cpufreq_lock);
|
||||
if (unlikely(ret < 0))
|
||||
return ret;
|
||||
*id = ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* release_idr - function to free the unique id.
|
||||
* @idr: struct idr * handle used for creating the id.
|
||||
* @id: int value representing the unique id.
|
||||
*/
|
||||
static void release_idr(struct idr *idr, int id)
|
||||
{
|
||||
mutex_lock(&cooling_cpufreq_lock);
|
||||
idr_remove(idr, id);
|
||||
mutex_unlock(&cooling_cpufreq_lock);
|
||||
}
|
||||
|
||||
/* Below code defines functions to be used for cpufreq as cooling device */
|
||||
|
||||
/**
|
||||
* is_cpufreq_valid - function to check frequency transitioning capability.
|
||||
* @cpu: cpu for which check is needed.
|
||||
*
|
||||
* This function will check the current state of the system if
|
||||
* it is capable of changing the frequency for a given @cpu.
|
||||
*
|
||||
* Return: 0 if the system is not currently capable of changing
|
||||
* the frequency of given cpu. !0 in case the frequency is changeable.
|
||||
*/
|
||||
static int is_cpufreq_valid(int cpu)
|
||||
{
|
||||
struct cpufreq_policy policy;
|
||||
|
||||
return (!cpufreq_get_policy(&policy, cpu) && policy.user_policy.governor);
|
||||
}
|
||||
|
||||
enum cpufreq_cooling_property {
|
||||
GET_LEVEL,
|
||||
GET_FREQ,
|
||||
GET_MAXL,
|
||||
};
|
||||
|
||||
/**
|
||||
* get_property - fetch a property of interest for a give cpu.
|
||||
* @cpu: cpu for which the property is required
|
||||
* @input: query parameter
|
||||
* @output: query return
|
||||
* @property: type of query (frequency, level, max level)
|
||||
*
|
||||
* This is the common function to
|
||||
* 1. get maximum cpu cooling states
|
||||
* 2. translate frequency to cooling state
|
||||
* 3. translate cooling state to frequency
|
||||
* Note that the code may be not in good shape
|
||||
* but it is written in this way in order to:
|
||||
* a) reduce duplicate code as most of the code can be shared.
|
||||
* b) make sure the logic is consistent when translating between
|
||||
* cooling states and frequencies.
|
||||
*
|
||||
* Return: 0 on success, -EINVAL when invalid parameters are passed.
|
||||
*/
|
||||
static int get_property(unsigned int cpu, unsigned long input,
|
||||
unsigned int *output,
|
||||
enum cpufreq_cooling_property property)
|
||||
{
|
||||
int i;
|
||||
unsigned long max_level = 0, level = 0;
|
||||
unsigned int freq = CPUFREQ_ENTRY_INVALID;
|
||||
int descend = -1;
|
||||
struct cpufreq_frequency_table *pos, *table =
|
||||
cpufreq_frequency_get_table(cpu);
|
||||
|
||||
if (!output)
|
||||
return -EINVAL;
|
||||
|
||||
if (!table)
|
||||
return -EINVAL;
|
||||
|
||||
cpufreq_for_each_valid_entry(pos, table) {
|
||||
/* ignore duplicate entry */
|
||||
if (freq == pos->frequency)
|
||||
continue;
|
||||
|
||||
/* get the frequency order */
|
||||
if (freq != CPUFREQ_ENTRY_INVALID && descend == -1)
|
||||
descend = freq > pos->frequency;
|
||||
|
||||
freq = pos->frequency;
|
||||
max_level++;
|
||||
}
|
||||
|
||||
/* No valid cpu frequency entry */
|
||||
if (max_level == 0)
|
||||
return -EINVAL;
|
||||
|
||||
/* max_level is an index, not a counter */
|
||||
max_level--;
|
||||
|
||||
/* get max level */
|
||||
if (property == GET_MAXL) {
|
||||
*output = (unsigned int)max_level;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (property == GET_FREQ)
|
||||
level = descend ? input : (max_level - input);
|
||||
|
||||
i = 0;
|
||||
cpufreq_for_each_valid_entry(pos, table) {
|
||||
/* ignore duplicate entry */
|
||||
if (freq == pos->frequency)
|
||||
continue;
|
||||
|
||||
/* now we have a valid frequency entry */
|
||||
freq = pos->frequency;
|
||||
|
||||
if (property == GET_LEVEL && (unsigned int)input == freq) {
|
||||
/* get level by frequency */
|
||||
*output = descend ? i : (max_level - i);
|
||||
return 0;
|
||||
}
|
||||
if (property == GET_FREQ && level == i) {
|
||||
/* get frequency by level */
|
||||
*output = freq;
|
||||
return 0;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/**
|
||||
* cpufreq_cooling_get_level - for a give cpu, return the cooling level.
|
||||
* @cpu: cpu for which the level is required
|
||||
* @freq: the frequency of interest
|
||||
*
|
||||
* This function will match the cooling level corresponding to the
|
||||
* requested @freq and return it.
|
||||
*
|
||||
* Return: The matched cooling level on success or THERMAL_CSTATE_INVALID
|
||||
* otherwise.
|
||||
*/
|
||||
unsigned long cpufreq_cooling_get_level(unsigned int cpu, unsigned int freq)
|
||||
{
|
||||
unsigned int val;
|
||||
|
||||
if (get_property(cpu, (unsigned long)freq, &val, GET_LEVEL))
|
||||
return THERMAL_CSTATE_INVALID;
|
||||
|
||||
return (unsigned long)val;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(cpufreq_cooling_get_level);
|
||||
|
||||
/**
|
||||
* get_cpu_frequency - get the absolute value of frequency from level.
|
||||
* @cpu: cpu for which frequency is fetched.
|
||||
* @level: cooling level
|
||||
*
|
||||
* This function matches cooling level with frequency. Based on a cooling level
|
||||
* of frequency, equals cooling state of cpu cooling device, it will return
|
||||
* the corresponding frequency.
|
||||
* e.g level=0 --> 1st MAX FREQ, level=1 ---> 2nd MAX FREQ, .... etc
|
||||
*
|
||||
* Return: 0 on error, the corresponding frequency otherwise.
|
||||
*/
|
||||
static unsigned int get_cpu_frequency(unsigned int cpu, unsigned long level)
|
||||
{
|
||||
int ret = 0;
|
||||
unsigned int freq;
|
||||
|
||||
ret = get_property(cpu, level, &freq, GET_FREQ);
|
||||
if (ret)
|
||||
return 0;
|
||||
|
||||
return freq;
|
||||
}
|
||||
|
||||
/**
|
||||
* cpufreq_apply_cooling - function to apply frequency clipping.
|
||||
* @cpufreq_device: cpufreq_cooling_device pointer containing frequency
|
||||
* clipping data.
|
||||
* @cooling_state: value of the cooling state.
|
||||
*
|
||||
* Function used to make sure the cpufreq layer is aware of current thermal
|
||||
* limits. The limits are applied by updating the cpufreq policy.
|
||||
*
|
||||
* Return: 0 on success, an error code otherwise (-EINVAL in case wrong
|
||||
* cooling state).
|
||||
*/
|
||||
static int cpufreq_apply_cooling(struct cpufreq_cooling_device *cpufreq_device,
|
||||
unsigned long cooling_state)
|
||||
{
|
||||
unsigned int cpuid, clip_freq;
|
||||
struct cpumask *mask = &cpufreq_device->allowed_cpus;
|
||||
unsigned int cpu = cpumask_any(mask);
|
||||
|
||||
|
||||
/* Check if the old cooling action is same as new cooling action */
|
||||
if (cpufreq_device->cpufreq_state == cooling_state)
|
||||
return 0;
|
||||
|
||||
clip_freq = get_cpu_frequency(cpu, cooling_state);
|
||||
if (!clip_freq)
|
||||
return -EINVAL;
|
||||
|
||||
cpufreq_device->cpufreq_state = cooling_state;
|
||||
cpufreq_device->cpufreq_val = clip_freq;
|
||||
|
||||
for_each_cpu(cpuid, mask) {
|
||||
if (is_cpufreq_valid(cpuid))
|
||||
cpufreq_update_policy(cpuid);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* cpufreq_thermal_notifier - notifier callback for cpufreq policy change.
|
||||
* @nb: struct notifier_block * with callback info.
|
||||
* @event: value showing cpufreq event for which this function invoked.
|
||||
* @data: callback-specific data
|
||||
*
|
||||
* Callback to hijack the notification on cpufreq policy transition.
|
||||
* Every time there is a change in policy, we will intercept and
|
||||
* update the cpufreq policy with thermal constraints.
|
||||
*
|
||||
* Return: 0 (success)
|
||||
*/
|
||||
static int cpufreq_thermal_notifier(struct notifier_block *nb,
|
||||
unsigned long event, void *data)
|
||||
{
|
||||
struct cpufreq_policy *policy = data;
|
||||
unsigned long max_freq = 0;
|
||||
struct cpufreq_cooling_device *cpufreq_dev;
|
||||
char *cooling_device_name;
|
||||
|
||||
if (event != CPUFREQ_ADJUST)
|
||||
return 0;
|
||||
|
||||
mutex_lock(&cooling_cpufreq_lock);
|
||||
list_for_each_entry(cpufreq_dev, &cpufreq_dev_list, node) {
|
||||
if (!cpumask_test_cpu(policy->cpu,
|
||||
&cpufreq_dev->allowed_cpus))
|
||||
continue;
|
||||
|
||||
if (!cpufreq_dev->cpufreq_val)
|
||||
cpufreq_dev->cpufreq_val = get_cpu_frequency(
|
||||
cpumask_any(&cpufreq_dev->allowed_cpus),
|
||||
cpufreq_dev->cpufreq_state);
|
||||
|
||||
max_freq = cpufreq_dev->cpufreq_val;
|
||||
|
||||
if (policy->max != max_freq) {
|
||||
if (policy->cpu)
|
||||
cooling_device_name = "cluster1";
|
||||
else
|
||||
cooling_device_name = "cluster0";
|
||||
|
||||
cpufreq_verify_within_limits(policy, 0, max_freq);
|
||||
exynos_ss_thermal(NULL, 0, cooling_device_name, max_freq);
|
||||
trace_exynos_thermal(NULL, 0, cooling_device_name, max_freq);
|
||||
}
|
||||
}
|
||||
mutex_unlock(&cooling_cpufreq_lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* cpufreq cooling device callback functions are defined below */
|
||||
|
||||
/**
|
||||
* cpufreq_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.
|
||||
*
|
||||
* Callback for the thermal cooling device to return the cpufreq
|
||||
* max cooling state.
|
||||
*
|
||||
* Return: 0 on success, an error code otherwise.
|
||||
*/
|
||||
static int cpufreq_get_max_state(struct thermal_cooling_device *cdev,
|
||||
unsigned long *state)
|
||||
{
|
||||
struct cpufreq_cooling_device *cpufreq_device = cdev->devdata;
|
||||
struct cpumask *mask = &cpufreq_device->allowed_cpus;
|
||||
unsigned int cpu;
|
||||
unsigned int count = 0;
|
||||
int ret;
|
||||
|
||||
cpu = cpumask_any(mask);
|
||||
|
||||
ret = get_property(cpu, 0, &count, GET_MAXL);
|
||||
|
||||
if (count > 0)
|
||||
*state = count;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* cpufreq_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.
|
||||
*
|
||||
* Callback for the thermal cooling device to return the cpufreq
|
||||
* current cooling state.
|
||||
*
|
||||
* Return: 0 on success, an error code otherwise.
|
||||
*/
|
||||
static int cpufreq_get_cur_state(struct thermal_cooling_device *cdev,
|
||||
unsigned long *state)
|
||||
{
|
||||
struct cpufreq_cooling_device *cpufreq_device = cdev->devdata;
|
||||
|
||||
*state = cpufreq_device->cpufreq_state;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* cpufreq_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.
|
||||
*
|
||||
* Callback for the thermal cooling device to change the cpufreq
|
||||
* current cooling state.
|
||||
*
|
||||
* Return: 0 on success, an error code otherwise.
|
||||
*/
|
||||
static int cpufreq_set_cur_state(struct thermal_cooling_device *cdev,
|
||||
unsigned long state)
|
||||
{
|
||||
struct cpufreq_cooling_device *cpufreq_device = cdev->devdata;
|
||||
|
||||
return cpufreq_apply_cooling(cpufreq_device, state);
|
||||
}
|
||||
|
||||
static enum tmu_noti_state_t cpu_tstate = TMU_NORMAL;
|
||||
|
||||
int cpufreq_set_cur_temp(bool suspended, unsigned long temp)
|
||||
{
|
||||
enum tmu_noti_state_t tstate;
|
||||
unsigned int on;
|
||||
|
||||
if (suspended || temp < EXYNOS_COLD_TEMP) {
|
||||
tstate = TMU_COLD;
|
||||
on = 1;
|
||||
} else {
|
||||
tstate = TMU_NORMAL;
|
||||
on = 0;
|
||||
}
|
||||
|
||||
if (cpu_tstate == tstate)
|
||||
return 0;
|
||||
|
||||
cpu_tstate = tstate;
|
||||
|
||||
blocking_notifier_call_chain(&cpu_notifier, TMU_COLD, &on);
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
/* Notifier for cpufreq policy change */
|
||||
static struct notifier_block thermal_cpufreq_notifier_block = {
|
||||
.notifier_call = cpufreq_thermal_notifier,
|
||||
};
|
||||
|
||||
int exynos_tmu_add_notifier(struct notifier_block *n)
|
||||
{
|
||||
return blocking_notifier_chain_register(&cpu_notifier, n);
|
||||
}
|
||||
|
||||
/**
|
||||
* __cpufreq_cooling_register - helper function to create cpufreq cooling device
|
||||
* @np: a valid struct device_node to the cooling device device tree node
|
||||
* @clip_cpus: cpumask of cpus where the frequency constraints will happen.
|
||||
*
|
||||
* This interface function registers the cpufreq cooling device with the name
|
||||
* "thermal-cpufreq-%x". This api can support multiple instances of cpufreq
|
||||
* cooling devices. It also gives the opportunity to link the cooling device
|
||||
* with a device tree node, in order to bind it via the thermal DT code.
|
||||
*
|
||||
* Return: a valid struct thermal_cooling_device pointer on success,
|
||||
* on failure, it returns a corresponding ERR_PTR().
|
||||
*/
|
||||
static struct thermal_cooling_device *
|
||||
__cpufreq_cooling_register(struct device_node *np,
|
||||
const struct cpumask *clip_cpus)
|
||||
{
|
||||
struct thermal_cooling_device *cool_dev;
|
||||
struct cpufreq_cooling_device *cpufreq_dev = NULL;
|
||||
unsigned int min = 0, max = 0;
|
||||
char dev_name[THERMAL_NAME_LENGTH];
|
||||
int ret = 0, i;
|
||||
struct cpufreq_policy policy;
|
||||
|
||||
/* Verify that all the clip cpus have same freq_min, freq_max limit */
|
||||
for_each_cpu(i, clip_cpus) {
|
||||
/* 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)
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
}
|
||||
cpufreq_dev = kzalloc(sizeof(struct cpufreq_cooling_device),
|
||||
GFP_KERNEL);
|
||||
if (!cpufreq_dev)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
cpumask_copy(&cpufreq_dev->allowed_cpus, clip_cpus);
|
||||
|
||||
ret = get_idr(&cpufreq_idr, &cpufreq_dev->id);
|
||||
if (ret) {
|
||||
kfree(cpufreq_dev);
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
snprintf(dev_name, sizeof(dev_name), "thermal-cpufreq-%d",
|
||||
cpufreq_dev->id);
|
||||
|
||||
cool_dev = thermal_of_cooling_device_register(np, dev_name, cpufreq_dev,
|
||||
&cpufreq_cooling_ops);
|
||||
if (IS_ERR(cool_dev)) {
|
||||
release_idr(&cpufreq_idr, cpufreq_dev->id);
|
||||
kfree(cpufreq_dev);
|
||||
return cool_dev;
|
||||
}
|
||||
cpufreq_dev->cool_dev = cool_dev;
|
||||
cpufreq_dev->cpufreq_state = 0;
|
||||
mutex_lock(&cooling_cpufreq_lock);
|
||||
|
||||
/* Register the notifier for first cpufreq cooling device */
|
||||
if (cpufreq_dev_count == 0)
|
||||
cpufreq_register_notifier(&thermal_cpufreq_notifier_block,
|
||||
CPUFREQ_POLICY_NOTIFIER);
|
||||
cpufreq_dev_count++;
|
||||
list_add(&cpufreq_dev->node, &cpufreq_dev_list);
|
||||
|
||||
mutex_unlock(&cooling_cpufreq_lock);
|
||||
|
||||
return cool_dev;
|
||||
}
|
||||
|
||||
/**
|
||||
* cpufreq_cooling_register - function to create cpufreq cooling device.
|
||||
* @clip_cpus: cpumask of cpus where the frequency constraints will happen.
|
||||
*
|
||||
* This interface function registers the cpufreq cooling device with the name
|
||||
* "thermal-cpufreq-%x". This api can support multiple instances of cpufreq
|
||||
* cooling devices.
|
||||
*
|
||||
* Return: a valid struct thermal_cooling_device pointer on success,
|
||||
* on failure, it returns a corresponding ERR_PTR().
|
||||
*/
|
||||
struct thermal_cooling_device *
|
||||
cpufreq_cooling_register(const struct cpumask *clip_cpus)
|
||||
{
|
||||
return __cpufreq_cooling_register(NULL, clip_cpus);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(cpufreq_cooling_register);
|
||||
|
||||
/**
|
||||
* of_cpufreq_cooling_register - function to create cpufreq cooling device.
|
||||
* @np: a valid struct device_node to the cooling device device tree node
|
||||
* @clip_cpus: cpumask of cpus where the frequency constraints will happen.
|
||||
*
|
||||
* This interface function registers the cpufreq cooling device with the name
|
||||
* "thermal-cpufreq-%x". This api can support multiple instances of cpufreq
|
||||
* cooling devices. Using this API, the cpufreq cooling device will be
|
||||
* linked to the device tree node provided.
|
||||
*
|
||||
* Return: a valid struct thermal_cooling_device pointer on success,
|
||||
* on failure, it returns a corresponding ERR_PTR().
|
||||
*/
|
||||
struct thermal_cooling_device *
|
||||
of_cpufreq_cooling_register(struct device_node *np,
|
||||
const struct cpumask *clip_cpus)
|
||||
{
|
||||
if (!np)
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
return __cpufreq_cooling_register(np, clip_cpus);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(of_cpufreq_cooling_register);
|
||||
|
||||
/**
|
||||
* cpufreq_cooling_unregister - function to remove cpufreq cooling device.
|
||||
* @cdev: thermal cooling device pointer.
|
||||
*
|
||||
* This interface function unregisters the "thermal-cpufreq-%x" cooling device.
|
||||
*/
|
||||
void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
|
||||
{
|
||||
struct cpufreq_cooling_device *cpufreq_dev;
|
||||
|
||||
if (!cdev)
|
||||
return;
|
||||
|
||||
cpufreq_dev = cdev->devdata;
|
||||
mutex_lock(&cooling_cpufreq_lock);
|
||||
list_del(&cpufreq_dev->node);
|
||||
cpufreq_dev_count--;
|
||||
|
||||
/* Unregister the notifier for the last cpufreq cooling device */
|
||||
if (cpufreq_dev_count == 0)
|
||||
cpufreq_unregister_notifier(&thermal_cpufreq_notifier_block,
|
||||
CPUFREQ_POLICY_NOTIFIER);
|
||||
mutex_unlock(&cooling_cpufreq_lock);
|
||||
|
||||
thermal_cooling_device_unregister(cpufreq_dev->cool_dev);
|
||||
release_idr(&cpufreq_idr, cpufreq_dev->id);
|
||||
kfree(cpufreq_dev);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(cpufreq_cooling_unregister);
|
641
drivers/thermal/samsung/exynos_thermal_common.c
Normal file
641
drivers/thermal/samsung/exynos_thermal_common.c
Normal file
|
@ -0,0 +1,641 @@
|
|||
/*
|
||||
* exynos_thermal_common.c - Samsung EXYNOS common thermal file
|
||||
*
|
||||
* Copyright (C) 2013 Samsung Electronics
|
||||
* Amit Daniel Kachhap <amit.daniel@samsung.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/cpu_cooling.h>
|
||||
#include <linux/gpu_cooling.h>
|
||||
#include <linux/isp_cooling.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/suspend.h>
|
||||
#include <linux/thermal.h>
|
||||
#include <linux/pm_qos.h>
|
||||
#include <linux/cpufreq.h>
|
||||
#include <soc/samsung/cpufreq.h>
|
||||
|
||||
#include "exynos_thermal_common.h"
|
||||
|
||||
unsigned long cpu_max_temp[2];
|
||||
|
||||
struct exynos_thermal_zone {
|
||||
enum thermal_device_mode mode;
|
||||
struct thermal_zone_device *therm_dev;
|
||||
struct thermal_cooling_device *cool_dev[MAX_COOLING_DEVICE];
|
||||
unsigned int cool_dev_size;
|
||||
struct platform_device *exynos4_dev;
|
||||
struct thermal_sensor_conf *sensor_conf;
|
||||
bool bind;
|
||||
};
|
||||
|
||||
static DEFINE_MUTEX (thermal_suspend_lock);
|
||||
static bool suspended;
|
||||
static bool is_cpu_hotplugged_out;
|
||||
|
||||
/* Get mode callback functions for thermal zone */
|
||||
static int exynos_get_mode(struct thermal_zone_device *thermal,
|
||||
enum thermal_device_mode *mode)
|
||||
{
|
||||
struct exynos_thermal_zone *th_zone = thermal->devdata;
|
||||
if (th_zone)
|
||||
*mode = th_zone->mode;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Set mode callback functions for thermal zone */
|
||||
static int exynos_set_mode(struct thermal_zone_device *thermal,
|
||||
enum thermal_device_mode mode)
|
||||
{
|
||||
struct exynos_thermal_zone *th_zone = thermal->devdata;
|
||||
if (!th_zone) {
|
||||
dev_err(&thermal->device,
|
||||
"thermal zone not registered\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
mutex_lock(&thermal->lock);
|
||||
|
||||
if (mode == THERMAL_DEVICE_ENABLED &&
|
||||
!th_zone->sensor_conf->trip_data.trigger_falling)
|
||||
thermal->polling_delay = IDLE_INTERVAL;
|
||||
else
|
||||
thermal->polling_delay = 0;
|
||||
|
||||
mutex_unlock(&thermal->lock);
|
||||
|
||||
th_zone->mode = mode;
|
||||
thermal_zone_device_update(thermal);
|
||||
dev_dbg(th_zone->sensor_conf->dev,
|
||||
"thermal polling set for duration=%d msec\n",
|
||||
thermal->polling_delay);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* Get trip type callback functions for thermal zone */
|
||||
static int exynos_get_trip_type(struct thermal_zone_device *thermal, int trip,
|
||||
enum thermal_trip_type *type)
|
||||
{
|
||||
struct exynos_thermal_zone *th_zone = thermal->devdata;
|
||||
int max_trip = th_zone->sensor_conf->trip_data.trip_count;
|
||||
int trip_type;
|
||||
|
||||
if (trip < 0 || trip >= max_trip)
|
||||
return -EINVAL;
|
||||
|
||||
trip_type = th_zone->sensor_conf->trip_data.trip_type[trip];
|
||||
|
||||
if (trip_type == SW_TRIP)
|
||||
*type = THERMAL_TRIP_CRITICAL;
|
||||
else if (trip_type == THROTTLE_ACTIVE)
|
||||
*type = THERMAL_TRIP_ACTIVE;
|
||||
else if (trip_type == THROTTLE_PASSIVE)
|
||||
*type = THERMAL_TRIP_PASSIVE;
|
||||
else
|
||||
return -EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Get trip temperature callback functions for thermal zone */
|
||||
static int exynos_get_trip_temp(struct thermal_zone_device *thermal, int trip,
|
||||
unsigned long *temp)
|
||||
{
|
||||
struct exynos_thermal_zone *th_zone = thermal->devdata;
|
||||
int max_trip = th_zone->sensor_conf->trip_data.trip_count;
|
||||
|
||||
if (trip < 0 || trip >= max_trip)
|
||||
return -EINVAL;
|
||||
|
||||
*temp = th_zone->sensor_conf->trip_data.trip_val[trip];
|
||||
/* convert the temperature into millicelsius */
|
||||
*temp = *temp * MCELSIUS;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Get critical temperature callback functions for thermal zone */
|
||||
static int exynos_get_crit_temp(struct thermal_zone_device *thermal,
|
||||
unsigned long *temp)
|
||||
{
|
||||
struct exynos_thermal_zone *th_zone = thermal->devdata;
|
||||
int max_trip = th_zone->sensor_conf->trip_data.trip_count;
|
||||
/* Get the temp of highest trip*/
|
||||
return exynos_get_trip_temp(thermal, max_trip - 1, temp);
|
||||
}
|
||||
|
||||
/* Bind callback functions for thermal zone */
|
||||
static int exynos_bind(struct thermal_zone_device *thermal,
|
||||
struct thermal_cooling_device *cdev)
|
||||
{
|
||||
int ret = 0, i, tab_size, level;
|
||||
struct freq_clip_table *tab_ptr, *clip_data;
|
||||
struct exynos_thermal_zone *th_zone = thermal->devdata;
|
||||
struct thermal_sensor_conf *data = th_zone->sensor_conf;
|
||||
enum thermal_trip_type type;
|
||||
struct cpufreq_policy policy;
|
||||
#ifdef CONFIG_GPU_THERMAL
|
||||
int gpu_max_freq, gpu_min_freq;
|
||||
#endif
|
||||
|
||||
tab_ptr = (struct freq_clip_table *)data->cooling_data.freq_data;
|
||||
tab_size = data->cooling_data.freq_clip_count;
|
||||
|
||||
if (tab_ptr == NULL || tab_size == 0)
|
||||
return 0;
|
||||
|
||||
/* find the cooling device registered*/
|
||||
for (i = 0; i < th_zone->cool_dev_size; i++)
|
||||
if (cdev == th_zone->cool_dev[i])
|
||||
break;
|
||||
|
||||
/* No matching cooling device */
|
||||
if (i == th_zone->cool_dev_size)
|
||||
return 0;
|
||||
|
||||
/* Bind the thermal zone to the cpufreq cooling device */
|
||||
for (i = 0; i < tab_size; i++) {
|
||||
clip_data = (struct freq_clip_table *)&(tab_ptr[i]);
|
||||
|
||||
if(data->d_type == CLUSTER0 || data->d_type == CLUSTER1) {
|
||||
ret = cpufreq_get_policy(&policy, data->d_type);
|
||||
if (ret)
|
||||
return -EINVAL;
|
||||
|
||||
if (clip_data->freq_clip_max > policy.max) {
|
||||
pr_warn("%s: Throttling freq(%d) is greater than policy max(%d)\n", __func__, clip_data->freq_clip_max, policy.max);
|
||||
clip_data->freq_clip_max = policy.max;
|
||||
} else if (clip_data->freq_clip_max < policy.min){
|
||||
pr_warn("%s: Throttling freq(%d) is lower than policy min(%d)\n", __func__, clip_data->freq_clip_max, policy.min);
|
||||
clip_data->freq_clip_max = policy.min;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef CONFIG_GPU_THERMAL
|
||||
if(data->d_type == GPU) {
|
||||
gpu_max_freq = gpu_dvfs_get_max_freq() * 1000;
|
||||
gpu_min_freq = gpu_dvfs_get_min_freq() * 1000;
|
||||
|
||||
if(gpu_max_freq > 0 && gpu_min_freq > 0) {
|
||||
if (clip_data->freq_clip_max > gpu_max_freq) {
|
||||
pr_warn("%s: GPU Throttling freq(%d) is greater than max(%d)\n", __func__, clip_data->freq_clip_max, gpu_max_freq);
|
||||
clip_data->freq_clip_max = gpu_max_freq;
|
||||
} else if (clip_data->freq_clip_max < gpu_min_freq){
|
||||
pr_warn("%s: GPU Throttling freq(%d) is lower than min(%d)\n", __func__, clip_data->freq_clip_max, gpu_min_freq);
|
||||
clip_data->freq_clip_max = gpu_min_freq;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (data->d_type == CLUSTER0)
|
||||
level = cpufreq_cooling_get_level(0, clip_data->freq_clip_max);
|
||||
else if (data->d_type == CLUSTER1)
|
||||
level = cpufreq_cooling_get_level(4, clip_data->freq_clip_max);
|
||||
else if (data->d_type == GPU)
|
||||
level = gpufreq_cooling_get_level(0, clip_data->freq_clip_max);
|
||||
else if (data->d_type == ISP)
|
||||
level = isp_cooling_get_fps(0, clip_data->freq_clip_max);
|
||||
else
|
||||
level = (int)THERMAL_CSTATE_INVALID;
|
||||
|
||||
if (level == THERMAL_CSTATE_INVALID) {
|
||||
pr_warn("Bind fail is occurred because level is invalid.\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
exynos_get_trip_type(thermal, i, &type);
|
||||
|
||||
switch (GET_ZONE(type)) {
|
||||
case MONITOR_ZONE:
|
||||
case WARN_ZONE:
|
||||
if (thermal_zone_bind_cooling_device(thermal, i, cdev,
|
||||
level, 0)) {
|
||||
dev_err(data->dev,
|
||||
"error unbinding cdev inst=%d\n", i);
|
||||
ret = -EINVAL;
|
||||
}
|
||||
th_zone->bind = true;
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Unbind callback functions for thermal zone */
|
||||
static int exynos_unbind(struct thermal_zone_device *thermal,
|
||||
struct thermal_cooling_device *cdev)
|
||||
{
|
||||
int ret = 0, i, tab_size;
|
||||
struct exynos_thermal_zone *th_zone = thermal->devdata;
|
||||
struct thermal_sensor_conf *data = th_zone->sensor_conf;
|
||||
enum thermal_trip_type type;
|
||||
|
||||
if (th_zone->bind == false)
|
||||
return 0;
|
||||
|
||||
tab_size = data->cooling_data.freq_clip_count;
|
||||
|
||||
if (tab_size == 0)
|
||||
return 0;
|
||||
|
||||
/* find the cooling device registered*/
|
||||
for (i = 0; i < th_zone->cool_dev_size; i++)
|
||||
if (cdev == th_zone->cool_dev[i])
|
||||
break;
|
||||
|
||||
/* No matching cooling device */
|
||||
if (i == th_zone->cool_dev_size)
|
||||
return 0;
|
||||
|
||||
/* Bind the thermal zone to the cpufreq cooling device */
|
||||
for (i = 0; i < tab_size; i++) {
|
||||
exynos_get_trip_type(thermal, i, &type);
|
||||
|
||||
switch (GET_ZONE(type)) {
|
||||
case MONITOR_ZONE:
|
||||
case WARN_ZONE:
|
||||
if (thermal_zone_unbind_cooling_device(thermal, i,
|
||||
cdev)) {
|
||||
dev_err(data->dev,
|
||||
"error unbinding cdev inst=%d\n", i);
|
||||
ret = -EINVAL;
|
||||
}
|
||||
th_zone->bind = false;
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_CPU_THERMAL
|
||||
extern int cpufreq_set_cur_temp(bool suspended, unsigned long temp);
|
||||
#else
|
||||
static inline int cpufreq_set_cur_temp(bool suspended, unsigned long temp) { return 0; }
|
||||
#endif
|
||||
#ifdef CONFIG_GPU_THERMAL
|
||||
extern int gpufreq_set_cur_temp(bool suspended, unsigned long temp);
|
||||
#else
|
||||
static inline int gpufreq_set_cur_temp(bool suspended, unsigned long temp) { return 0; }
|
||||
#endif
|
||||
#ifdef CONFIG_ISP_THERMAL
|
||||
extern int isp_set_cur_temp(bool suspended, unsigned long temp);
|
||||
#else
|
||||
static inline int isp_set_cur_temp(bool suspended, unsigned long temp) { return 0; }
|
||||
#endif
|
||||
|
||||
/* Get temperature callback functions for thermal zone */
|
||||
static int exynos_get_temp(struct thermal_zone_device *thermal,
|
||||
unsigned long *temp)
|
||||
{
|
||||
struct exynos_thermal_zone *th_zone = thermal->devdata;
|
||||
void *data;
|
||||
unsigned long max_temp;
|
||||
|
||||
if (!th_zone->sensor_conf) {
|
||||
dev_err(&thermal->device,
|
||||
"Temperature sensor not initialised\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
data = th_zone->sensor_conf->driver_data;
|
||||
*temp = th_zone->sensor_conf->read_temperature(data);
|
||||
|
||||
/* convert the temperature into millicelsius */
|
||||
*temp = *temp * MCELSIUS;
|
||||
|
||||
mutex_lock(&thermal_suspend_lock);
|
||||
if (th_zone->sensor_conf->d_type == CLUSTER0 || th_zone->sensor_conf->d_type == CLUSTER1) {
|
||||
cpu_max_temp[th_zone->sensor_conf->d_type] = *temp;
|
||||
max_temp = max(cpu_max_temp[CLUSTER0], cpu_max_temp[CLUSTER1]);
|
||||
cpufreq_set_cur_temp(suspended, max_temp / 1000);
|
||||
} else if (th_zone->sensor_conf->d_type == GPU)
|
||||
gpufreq_set_cur_temp(suspended, *temp / 1000);
|
||||
else if (th_zone->sensor_conf->d_type == ISP)
|
||||
isp_set_cur_temp(suspended, *temp / 1000);
|
||||
mutex_unlock(&thermal_suspend_lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Get temperature callback functions for thermal zone */
|
||||
static int exynos_set_emul_temp(struct thermal_zone_device *thermal,
|
||||
unsigned long temp)
|
||||
{
|
||||
void *data;
|
||||
int ret = -EINVAL;
|
||||
struct exynos_thermal_zone *th_zone = thermal->devdata;
|
||||
|
||||
if (!th_zone->sensor_conf) {
|
||||
dev_err(&thermal->device,
|
||||
"Temperature sensor not initialised\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
data = th_zone->sensor_conf->driver_data;
|
||||
if (th_zone->sensor_conf->write_emul_temp)
|
||||
ret = th_zone->sensor_conf->write_emul_temp(data, temp);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Get the temperature trend */
|
||||
static int exynos_get_trend(struct thermal_zone_device *thermal,
|
||||
int trip, enum thermal_trend *trend)
|
||||
{
|
||||
int ret;
|
||||
unsigned long trip_temp;
|
||||
|
||||
ret = exynos_get_trip_temp(thermal, trip, &trip_temp);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (thermal->temperature >= trip_temp)
|
||||
*trend = THERMAL_TREND_RAISE_FULL;
|
||||
else
|
||||
*trend = THERMAL_TREND_DROP_FULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct pm_qos_request thermal_cpu_hotplug_request;
|
||||
static int exynos_throttle_cpu_hotplug(struct thermal_zone_device *thermal)
|
||||
{
|
||||
struct exynos_thermal_zone *th_zone = thermal->devdata;
|
||||
struct thermal_sensor_conf *data = th_zone->sensor_conf;
|
||||
struct cpufreq_cooling_device *cpufreq_device = (struct cpufreq_cooling_device *)th_zone->cool_dev[0]->devdata;
|
||||
int ret = 0;
|
||||
int cur_temp = 0;
|
||||
|
||||
if (!thermal->temperature)
|
||||
return -EINVAL;
|
||||
|
||||
cur_temp = thermal->temperature / MCELSIUS;
|
||||
|
||||
if (is_cpu_hotplugged_out) {
|
||||
if (cur_temp < data->hotplug_in_threshold) {
|
||||
/*
|
||||
* If current temperature is lower than low threshold,
|
||||
* call cluster1_cores_hotplug(false) for hotplugged out cpus.
|
||||
*/
|
||||
pm_qos_update_request(&thermal_cpu_hotplug_request, NR_CPUS);
|
||||
is_cpu_hotplugged_out = false;
|
||||
cpufreq_device->cpufreq_state = 0;
|
||||
}
|
||||
} else {
|
||||
if (cur_temp >= data->hotplug_out_threshold) {
|
||||
/*
|
||||
* If current temperature is higher than high threshold,
|
||||
* call cluster1_cores_hotplug(true) to hold temperature down.
|
||||
*/
|
||||
pm_qos_update_request(&thermal_cpu_hotplug_request, NR_CLUST1_CPUS);
|
||||
is_cpu_hotplugged_out = true;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Operation callback functions for thermal zone */
|
||||
static struct thermal_zone_device_ops exynos_dev_ops = {
|
||||
.bind = exynos_bind,
|
||||
.unbind = exynos_unbind,
|
||||
.get_temp = exynos_get_temp,
|
||||
.set_emul_temp = exynos_set_emul_temp,
|
||||
.get_trend = exynos_get_trend,
|
||||
.get_mode = exynos_get_mode,
|
||||
.set_mode = exynos_set_mode,
|
||||
.get_trip_type = exynos_get_trip_type,
|
||||
.get_trip_temp = exynos_get_trip_temp,
|
||||
.get_crit_temp = exynos_get_crit_temp,
|
||||
};
|
||||
|
||||
/* Operation callback functions for thermal zone */
|
||||
static struct thermal_zone_device_ops exynos_dev_hotplug_ops = {
|
||||
.bind = exynos_bind,
|
||||
.unbind = exynos_unbind,
|
||||
.get_temp = exynos_get_temp,
|
||||
.set_emul_temp = exynos_set_emul_temp,
|
||||
.get_trend = exynos_get_trend,
|
||||
.get_mode = exynos_get_mode,
|
||||
.set_mode = exynos_set_mode,
|
||||
.get_trip_type = exynos_get_trip_type,
|
||||
.get_trip_temp = exynos_get_trip_temp,
|
||||
.get_crit_temp = exynos_get_crit_temp,
|
||||
.throttle_cpu_hotplug = exynos_throttle_cpu_hotplug,
|
||||
};
|
||||
|
||||
/*
|
||||
* This function may be called from interrupt based temperature sensor
|
||||
* when threshold is changed.
|
||||
*/
|
||||
void exynos_report_trigger(struct thermal_sensor_conf *conf)
|
||||
{
|
||||
unsigned int i;
|
||||
char data[10];
|
||||
char *envp[] = { data, NULL };
|
||||
struct exynos_thermal_zone *th_zone;
|
||||
|
||||
if (!conf || !conf->pzone_data) {
|
||||
pr_err("Invalid temperature sensor configuration data\n");
|
||||
return;
|
||||
}
|
||||
|
||||
th_zone = conf->pzone_data;
|
||||
|
||||
if (th_zone->bind == false) {
|
||||
for (i = 0; i < th_zone->cool_dev_size; i++) {
|
||||
if (!th_zone->cool_dev[i])
|
||||
continue;
|
||||
exynos_bind(th_zone->therm_dev,
|
||||
th_zone->cool_dev[i]);
|
||||
}
|
||||
}
|
||||
|
||||
thermal_zone_device_update(th_zone->therm_dev);
|
||||
|
||||
mutex_lock(&th_zone->therm_dev->lock);
|
||||
/* Find the level for which trip happened */
|
||||
for (i = 0; i < th_zone->sensor_conf->trip_data.trip_count; i++) {
|
||||
if (th_zone->therm_dev->last_temperature <
|
||||
th_zone->sensor_conf->trip_data.trip_val[i] * MCELSIUS)
|
||||
break;
|
||||
}
|
||||
|
||||
if (th_zone->mode == THERMAL_DEVICE_ENABLED &&
|
||||
!th_zone->sensor_conf->trip_data.trigger_falling) {
|
||||
if (i > 0)
|
||||
th_zone->therm_dev->polling_delay = ACTIVE_INTERVAL;
|
||||
else
|
||||
th_zone->therm_dev->polling_delay = IDLE_INTERVAL;
|
||||
}
|
||||
|
||||
snprintf(data, sizeof(data), "%u", i);
|
||||
kobject_uevent_env(&th_zone->therm_dev->device.kobj, KOBJ_CHANGE, envp);
|
||||
mutex_unlock(&th_zone->therm_dev->lock);
|
||||
}
|
||||
|
||||
static int exynos_pm_notifier(struct notifier_block *notifier,
|
||||
unsigned long event, void *v)
|
||||
{
|
||||
switch (event) {
|
||||
case PM_SUSPEND_PREPARE:
|
||||
mutex_lock(&thermal_suspend_lock);
|
||||
suspended = true;
|
||||
cpufreq_set_cur_temp(suspended, 0);
|
||||
gpufreq_set_cur_temp(suspended, 0);
|
||||
isp_set_cur_temp(suspended, 0);
|
||||
mutex_unlock(&thermal_suspend_lock);
|
||||
break;
|
||||
case PM_POST_SUSPEND:
|
||||
mutex_lock(&thermal_suspend_lock);
|
||||
suspended = false;
|
||||
mutex_unlock(&thermal_suspend_lock);
|
||||
break;
|
||||
}
|
||||
|
||||
return NOTIFY_OK;
|
||||
}
|
||||
|
||||
static struct notifier_block exynos_tmu_pm_notifier = {
|
||||
.notifier_call = exynos_pm_notifier,
|
||||
};
|
||||
|
||||
/* Register with the in-kernel thermal management */
|
||||
int exynos_register_thermal(struct thermal_sensor_conf *sensor_conf)
|
||||
{
|
||||
int ret, cpu;
|
||||
struct cpumask mask_val;
|
||||
struct exynos_thermal_zone *th_zone;
|
||||
struct thermal_zone_device_ops *dev_ops;
|
||||
|
||||
if (!sensor_conf || !sensor_conf->read_temperature) {
|
||||
pr_err("Temperature sensor not initialised\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
th_zone = devm_kzalloc(sensor_conf->dev,
|
||||
sizeof(struct exynos_thermal_zone), GFP_KERNEL);
|
||||
if (!th_zone)
|
||||
return -ENOMEM;
|
||||
|
||||
th_zone->sensor_conf = sensor_conf;
|
||||
cpumask_clear(&mask_val);
|
||||
|
||||
for_each_possible_cpu(cpu) {
|
||||
if (cpu_topology[cpu].cluster_id == sensor_conf->id) {
|
||||
cpumask_copy(&mask_val, topology_core_cpumask(cpu));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO: 1) Handle multiple cooling devices in a thermal zone
|
||||
* 2) Add a flag/name in cooling info to map to specific
|
||||
* sensor
|
||||
*/
|
||||
if (sensor_conf->cooling_data.freq_clip_count > 0) {
|
||||
if (sensor_conf->d_type == CLUSTER0 || sensor_conf->d_type == CLUSTER1) {
|
||||
th_zone->cool_dev[th_zone->cool_dev_size] =
|
||||
cpufreq_cooling_register(&mask_val);
|
||||
} else if (sensor_conf->d_type == GPU) {
|
||||
th_zone->cool_dev[th_zone->cool_dev_size] =
|
||||
gpufreq_cooling_register(&mask_val);
|
||||
} else if (sensor_conf->d_type == ISP) {
|
||||
th_zone->cool_dev[th_zone->cool_dev_size] =
|
||||
isp_cooling_register(&mask_val);
|
||||
}
|
||||
if (IS_ERR(th_zone->cool_dev[th_zone->cool_dev_size])) {
|
||||
dev_err(sensor_conf->dev,
|
||||
"Failed to register cpufreq cooling device\n");
|
||||
ret = -EINVAL;
|
||||
goto err_unregister;
|
||||
}
|
||||
th_zone->cool_dev_size++;
|
||||
}
|
||||
|
||||
/* Add hotplug function ops */
|
||||
if (sensor_conf->hotplug_enable) {
|
||||
dev_ops = &exynos_dev_hotplug_ops;
|
||||
pm_qos_add_request(&thermal_cpu_hotplug_request, PM_QOS_CPU_ONLINE_MAX,
|
||||
PM_QOS_CPU_ONLINE_MAX_DEFAULT_VALUE);
|
||||
} else
|
||||
dev_ops = &exynos_dev_ops;
|
||||
|
||||
th_zone->therm_dev = thermal_zone_device_register(
|
||||
sensor_conf->name, sensor_conf->trip_data.trip_count,
|
||||
0, th_zone, dev_ops, NULL, 0,
|
||||
sensor_conf->trip_data.trigger_falling ? 0 :
|
||||
IDLE_INTERVAL);
|
||||
|
||||
if (IS_ERR(th_zone->therm_dev)) {
|
||||
dev_err(sensor_conf->dev,
|
||||
"Failed to register thermal zone device\n");
|
||||
ret = PTR_ERR(th_zone->therm_dev);
|
||||
goto err_unregister;
|
||||
}
|
||||
th_zone->mode = THERMAL_DEVICE_ENABLED;
|
||||
sensor_conf->pzone_data = th_zone;
|
||||
|
||||
if (sensor_conf->id == 0)
|
||||
register_pm_notifier(&exynos_tmu_pm_notifier);
|
||||
|
||||
dev_info(sensor_conf->dev,
|
||||
"Exynos: Thermal zone(%s) registered\n", sensor_conf->name);
|
||||
|
||||
return 0;
|
||||
|
||||
err_unregister:
|
||||
exynos_unregister_thermal(sensor_conf);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Un-Register with the in-kernel thermal management */
|
||||
void exynos_unregister_thermal(struct thermal_sensor_conf *sensor_conf)
|
||||
{
|
||||
int i;
|
||||
struct exynos_thermal_zone *th_zone;
|
||||
|
||||
if (!sensor_conf || !sensor_conf->pzone_data) {
|
||||
pr_err("Invalid temperature sensor configuration data\n");
|
||||
return;
|
||||
}
|
||||
|
||||
th_zone = sensor_conf->pzone_data;
|
||||
|
||||
thermal_zone_device_unregister(th_zone->therm_dev);
|
||||
|
||||
for (i = 0; i < th_zone->cool_dev_size; i++) {
|
||||
if (sensor_conf->d_type == CLUSTER0 || sensor_conf->d_type == CLUSTER1)
|
||||
cpufreq_cooling_unregister(th_zone->cool_dev[i]);
|
||||
else if (sensor_conf->d_type == GPU)
|
||||
gpufreq_cooling_unregister(th_zone->cool_dev[i]);
|
||||
else if (sensor_conf->d_type == ISP)
|
||||
isp_cooling_unregister(th_zone->cool_dev[i]);
|
||||
}
|
||||
|
||||
if (sensor_conf->id == 0)
|
||||
unregister_pm_notifier(&exynos_tmu_pm_notifier);
|
||||
|
||||
dev_info(sensor_conf->dev,
|
||||
"Exynos: Kernel Thermal management unregistered\n");
|
||||
}
|
134
drivers/thermal/samsung/exynos_thermal_common.h
Normal file
134
drivers/thermal/samsung/exynos_thermal_common.h
Normal file
|
@ -0,0 +1,134 @@
|
|||
/*
|
||||
* exynos_thermal_common.h - Samsung EXYNOS common header file
|
||||
*
|
||||
* Copyright (C) 2013 Samsung Electronics
|
||||
* Amit Daniel Kachhap <amit.daniel@samsung.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _EXYNOS_THERMAL_COMMON_H
|
||||
#define _EXYNOS_THERMAL_COMMON_H
|
||||
|
||||
#include <linux/types.h>
|
||||
|
||||
/* In-kernel thermal framework related macros & definations */
|
||||
#define SENSOR_NAME_LEN 16
|
||||
#define MAX_TRIP_COUNT 8
|
||||
#define MAX_COOLING_DEVICE 4
|
||||
#define MAX_TRIMINFO_CTRL_REG 2
|
||||
|
||||
#define ACTIVE_INTERVAL 300
|
||||
#define IDLE_INTERVAL 500
|
||||
#define MCELSIUS 1000
|
||||
|
||||
/* CPU Zone information */
|
||||
#define PANIC_ZONE 4
|
||||
#define WARN_ZONE 3
|
||||
#define MONITOR_ZONE 2
|
||||
#define SAFE_ZONE 1
|
||||
|
||||
#define GET_ZONE(trip) (trip + 2)
|
||||
#define GET_TRIP(zone) (zone - 2)
|
||||
|
||||
/* Bit type */
|
||||
#define TYPE_8BIT_MASK (0xFF)
|
||||
#define TYPE_9BIT_MASK (0x1FF)
|
||||
|
||||
enum trigger_type {
|
||||
THROTTLE_ACTIVE = 1,
|
||||
THROTTLE_PASSIVE,
|
||||
SW_TRIP,
|
||||
HW_TRIP,
|
||||
};
|
||||
|
||||
enum dev_type {
|
||||
CLUSTER0,
|
||||
CLUSTER1,
|
||||
GPU,
|
||||
ISP,
|
||||
};
|
||||
|
||||
/**
|
||||
* struct freq_clip_table
|
||||
* @freq_clip_max: maximum frequency allowed for this cooling state.
|
||||
* @temp_level: Temperature level at which the temperature clipping will
|
||||
* happen.
|
||||
* @mask_val: cpumask of the allowed cpu's where the clipping will take place.
|
||||
*
|
||||
* This structure is required to be filled and passed to the
|
||||
* cpufreq_cooling_unregister function.
|
||||
*/
|
||||
struct freq_clip_table {
|
||||
unsigned int freq_clip_max;
|
||||
unsigned int temp_level;
|
||||
const struct cpumask *mask_val;
|
||||
};
|
||||
|
||||
struct thermal_trip_point_conf {
|
||||
int trip_val[MAX_TRIP_COUNT];
|
||||
int trip_type[MAX_TRIP_COUNT];
|
||||
int trip_count;
|
||||
unsigned char trigger_falling;
|
||||
};
|
||||
|
||||
struct thermal_cooling_conf {
|
||||
struct freq_clip_table freq_data[MAX_TRIP_COUNT];
|
||||
int freq_clip_count;
|
||||
};
|
||||
|
||||
struct thermal_sensor_conf {
|
||||
char name[SENSOR_NAME_LEN];
|
||||
int (*read_temperature)(void *data);
|
||||
int (*write_emul_temp)(void *drv_data, unsigned long temp);
|
||||
struct thermal_trip_point_conf trip_data;
|
||||
struct thermal_cooling_conf cooling_data;
|
||||
void *driver_data;
|
||||
void *pzone_data;
|
||||
struct device *dev;
|
||||
enum dev_type d_type;
|
||||
int id;
|
||||
bool hotplug_enable;
|
||||
int count;
|
||||
int hotplug_in_threshold;
|
||||
int hotplug_out_threshold;
|
||||
};
|
||||
|
||||
/*Functions used exynos based thermal sensor driver*/
|
||||
#ifdef CONFIG_EXYNOS_THERMAL_CORE
|
||||
void exynos_unregister_thermal(struct thermal_sensor_conf *sensor_conf);
|
||||
int exynos_register_thermal(struct thermal_sensor_conf *sensor_conf);
|
||||
void exynos_report_trigger(struct thermal_sensor_conf *sensor_conf);
|
||||
#else
|
||||
static inline void
|
||||
exynos_unregister_thermal(struct thermal_sensor_conf *sensor_conf) { return; }
|
||||
|
||||
static inline int
|
||||
exynos_register_thermal(struct thermal_sensor_conf *sensor_conf) { return 0; }
|
||||
|
||||
static inline void
|
||||
exynos_report_trigger(struct thermal_sensor_conf *sensor_conf) { return; }
|
||||
#endif /* CONFIG_EXYNOS_THERMAL_CORE */
|
||||
|
||||
#if defined(CONFIG_GPU_THERMAL) && defined(CONFIG_MALI_DVFS)
|
||||
extern int gpu_dvfs_get_max_freq(void);
|
||||
extern int gpu_dvfs_get_min_freq(void);
|
||||
#else
|
||||
static inline int gpu_dvfs_get_max_freq(void) { return -1; };
|
||||
static inline int gpu_dvfs_get_min_freq(void) { return -1; };
|
||||
#endif
|
||||
|
||||
#endif /* _EXYNOS_THERMAL_COMMON_H */
|
1411
drivers/thermal/samsung/exynos_tmu.c
Normal file
1411
drivers/thermal/samsung/exynos_tmu.c
Normal file
File diff suppressed because it is too large
Load diff
323
drivers/thermal/samsung/exynos_tmu.h
Normal file
323
drivers/thermal/samsung/exynos_tmu.h
Normal file
|
@ -0,0 +1,323 @@
|
|||
/*
|
||||
* exynos_tmu.h - Samsung EXYNOS TMU (Thermal Management Unit)
|
||||
*
|
||||
* Copyright (C) 2011 Samsung Electronics
|
||||
* Donggeun Kim <dg77.kim@samsung.com>
|
||||
* Amit Daniel Kachhap <amit.daniel@samsung.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#ifndef _EXYNOS_TMU_H
|
||||
#define _EXYNOS_TMU_H
|
||||
#include <linux/cpu_cooling.h>
|
||||
|
||||
#include "exynos_thermal_common.h"
|
||||
|
||||
#define FIRST_SENSOR 0
|
||||
|
||||
enum calibration_type {
|
||||
TYPE_ONE_POINT_TRIMMING,
|
||||
TYPE_ONE_POINT_TRIMMING_25,
|
||||
TYPE_ONE_POINT_TRIMMING_85,
|
||||
TYPE_TWO_POINT_TRIMMING,
|
||||
TYPE_NONE,
|
||||
};
|
||||
|
||||
enum calibration_mode {
|
||||
SW_MODE,
|
||||
HW_MODE,
|
||||
};
|
||||
|
||||
enum soc_type {
|
||||
SOC_ARCH_EXYNOS3250 = 1,
|
||||
SOC_ARCH_EXYNOS4210,
|
||||
SOC_ARCH_EXYNOS4412,
|
||||
SOC_ARCH_EXYNOS5250,
|
||||
SOC_ARCH_EXYNOS5260,
|
||||
SOC_ARCH_EXYNOS5420_TRIMINFO,
|
||||
SOC_ARCH_EXYNOS5440,
|
||||
SOC_ARCH_EXYNOS7580,
|
||||
SOC_ARCH_EXYNOS7870,
|
||||
SOC_ARCH_EXYNOS7570,
|
||||
SOC_ARCH_EXYNOS8890,
|
||||
};
|
||||
|
||||
/**
|
||||
* EXYNOS TMU supported features.
|
||||
* TMU_SUPPORT_EMULATION - This features is used to set user defined
|
||||
* temperature to the TMU controller.
|
||||
* TMU_SUPPORT_MULTI_INST - This features denotes that the soc
|
||||
* has many instances of TMU.
|
||||
* TMU_SUPPORT_TRIM_RELOAD - This features shows that trimming can
|
||||
* be reloaded.
|
||||
* TMU_SUPPORT_FALLING_TRIP - This features shows that interrupt can
|
||||
* be registered for falling trips also.
|
||||
* TMU_SUPPORT_READY_STATUS - This feature tells that the TMU current
|
||||
* state(active/idle) can be checked.
|
||||
* TMU_SUPPORT_EMUL_TIME - This features allows to set next temp emulation
|
||||
* sample time.
|
||||
* TMU_SUPPORT_ADDRESS_MULTIPLE - This feature tells that the different TMU
|
||||
* sensors shares some common registers.
|
||||
* TMU_SUPPORT - macro to compare the above features with the supplied.
|
||||
*/
|
||||
#define TMU_SUPPORT_EMULATION BIT(0)
|
||||
#define TMU_SUPPORT_MULTI_INST BIT(1)
|
||||
#define TMU_SUPPORT_TRIM_RELOAD BIT(2)
|
||||
#define TMU_SUPPORT_FALLING_TRIP BIT(3)
|
||||
#define TMU_SUPPORT_READY_STATUS BIT(4)
|
||||
#define TMU_SUPPORT_EMUL_TIME BIT(5)
|
||||
#define TMU_SUPPORT_ADDRESS_MULTIPLE BIT(6)
|
||||
|
||||
#define TMU_SUPPORTS(a, b) (a->features & TMU_SUPPORT_ ## b)
|
||||
|
||||
/**
|
||||
* struct exynos_tmu_register - register descriptors to access registers and
|
||||
* bitfields. The register validity, offsets and bitfield values may vary
|
||||
* slightly across different exynos SOC's.
|
||||
* @triminfo_data: register containing 2 pont trimming data
|
||||
* @triminfo_ctrl: trim info controller register.
|
||||
* @triminfo_ctrl_count: the number of trim info controller register.
|
||||
* @triminfo_85_shift: shift bit of the 85 C trim value in triminfo_data reg.
|
||||
* @tmu_ctrl: TMU main controller register.
|
||||
* @tmu_ctrl1: TMU main controller register.
|
||||
* @test_mux_addr_shift: shift bits of test mux address.
|
||||
* @therm_trip_mode_shift: shift bits of tripping mode in tmu_ctrl register.
|
||||
* @therm_trip_mode_mask: mask bits of tripping mode in tmu_ctrl register.
|
||||
* @therm_trip_en_shift: shift bits of tripping enable in tmu_ctrl register.
|
||||
* @lpi0_mode_en_shift: shift bits of LIPI0 mode enable bit in tmu_ctrl1 register.
|
||||
* @tmu_status: register drescribing the TMU status.
|
||||
* @tmu_cur_temp: register containing the current temperature of the TMU.
|
||||
* @threshold_temp: register containing the base threshold level.
|
||||
* @threshold_th0: Register containing first set of rising levels.
|
||||
* @threshold_th1: Register containing second set of rising levels.
|
||||
* @threshold_th2: Register containing third set of rising levels.
|
||||
* @threshold_th3: Register containing fourth set of rising levels.
|
||||
* @threshold_th3_l0_shift: shift bits of level0 threshold temperature.
|
||||
* @tmu_inten: register containing the different threshold interrupt
|
||||
enable bits.
|
||||
* @inten_rise0_shift: shift bits of rising 0 interrupt bits.
|
||||
* @inten_rise1_shift: shift bits of rising 1 interrupt bits.
|
||||
* @inten_rise2_shift: shift bits of rising 2 interrupt bits.
|
||||
* @inten_rise3_shift: shift bits of rising 3 interrupt bits.
|
||||
* @inten_rise4_shift: shift bits of rising 4 interrupt bits.
|
||||
* @inten_rise5_shift: shift bits of rising 5 interrupt bits.
|
||||
* @inten_rise6_shift: shift bits of rising 6 interrupt bits.
|
||||
* @inten_rise7_shift: shift bits of rising 7 interrupt bits.
|
||||
* @inten_fall0_shift: shift bits of falling 0 interrupt bits.
|
||||
* @inten_fall1_shift: shift bits of falling 1 interrupt bits.
|
||||
* @inten_fall2_shift: shift bits of falling 2 interrupt bits.
|
||||
* @inten_fall3_shift: shift bits of falling 3 interrupt bits.
|
||||
* @inten_fall4_shift: shift bits of falling 4 interrupt bits.
|
||||
* @inten_fall5_shift: shift bits of falling 5 interrupt bits.
|
||||
* @inten_fall6_shift: shift bits of falling 6 interrupt bits.
|
||||
* @inten_fall7_shift: shift bits of falling 7 interrupt bits.
|
||||
* @tmu_intstat: Register containing the interrupt status values.
|
||||
* @tmu_intclear: Register for clearing the raised interrupt status.
|
||||
* @emul_con: TMU emulation controller register.
|
||||
* @emul_temp_shift: shift bits of emulation temperature.
|
||||
* @emul_time_shift: shift bits of emulation time.
|
||||
* @tmu_irqstatus: register to find which TMU generated interrupts.
|
||||
* @tmu_pmin: register to get/set the Pmin value.
|
||||
*/
|
||||
struct exynos_tmu_registers {
|
||||
u32 triminfo_data;
|
||||
u32 calib_sel_shift;
|
||||
u32 calib_sel_mask;
|
||||
|
||||
u32 triminfo_ctrl[MAX_TRIMINFO_CTRL_REG];
|
||||
u32 triminfo_ctrl_count;
|
||||
|
||||
u32 triminfo_85_shift;
|
||||
|
||||
u32 tmu_ctrl;
|
||||
u32 tmu_ctrl1;
|
||||
u32 test_mux_addr_shift;
|
||||
/* vref and slope otp value */
|
||||
u32 buf_vref_otp_reg;
|
||||
u32 buf_vref_otp_shift;
|
||||
u32 buf_vref_otp_mask;
|
||||
u32 buf_slope_otp_reg;
|
||||
u32 buf_slope_otp_shift;
|
||||
u32 buf_slope_otp_mask;
|
||||
u32 therm_trip_mode_shift;
|
||||
u32 therm_trip_mode_mask;
|
||||
u32 therm_trip_en_shift;
|
||||
u32 lpi0_mode_en_shift;
|
||||
|
||||
u32 tmu_status;
|
||||
u32 valid_mask;
|
||||
u32 valid_p0_shift;
|
||||
u32 valid_p1_shift;
|
||||
u32 valid_p2_shift;
|
||||
u32 valid_p3_shift;
|
||||
u32 valid_p4_shift;
|
||||
|
||||
u32 tmu_cur_temp;
|
||||
|
||||
u32 threshold_temp;
|
||||
|
||||
u32 threshold_th0;
|
||||
u32 threshold_th1;
|
||||
u32 threshold_th2;
|
||||
u32 threshold_th3;
|
||||
u32 threshold_th3_l0_shift;
|
||||
|
||||
u32 threshold_th4;
|
||||
u32 threshold_th5;
|
||||
u32 threshold_th6;
|
||||
u32 threshold_th7;
|
||||
|
||||
u32 tmu_inten;
|
||||
|
||||
u32 tmu_intstat;
|
||||
u32 inten_rise0_shift;
|
||||
u32 inten_rise1_shift;
|
||||
u32 inten_rise2_shift;
|
||||
u32 inten_rise3_shift;
|
||||
u32 inten_rise4_shift;
|
||||
u32 inten_rise5_shift;
|
||||
u32 inten_rise6_shift;
|
||||
u32 inten_rise7_shift;
|
||||
u32 inten_fall0_shift;
|
||||
u32 inten_fall1_shift;
|
||||
u32 inten_fall2_shift;
|
||||
u32 inten_fall3_shift;
|
||||
u32 inten_fall4_shift;
|
||||
u32 inten_fall5_shift;
|
||||
u32 inten_fall6_shift;
|
||||
u32 inten_fall7_shift;
|
||||
|
||||
u32 tmu_intclear;
|
||||
|
||||
u32 emul_con;
|
||||
u32 emul_temp_shift;
|
||||
u32 emul_time_shift;
|
||||
|
||||
u32 tmu_irqstatus;
|
||||
u32 tmu_pmin;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct exynos_tmu_platform_data
|
||||
* @threshold: basic temperature for generating interrupt
|
||||
* 25 <= threshold <= 125 [unit: degree Celsius]
|
||||
* @threshold_falling: differntial value for setting threshold
|
||||
* of temperature falling interrupt.
|
||||
* @trigger_levels: array for each interrupt levels
|
||||
* [unit: degree Celsius]
|
||||
* 0: temperature for trigger_level0 interrupt
|
||||
* condition for trigger_level0 interrupt:
|
||||
* current temperature > threshold + trigger_levels[0]
|
||||
* 1: temperature for trigger_level1 interrupt
|
||||
* condition for trigger_level1 interrupt:
|
||||
* current temperature > threshold + trigger_levels[1]
|
||||
* 2: temperature for trigger_level2 interrupt
|
||||
* condition for trigger_level2 interrupt:
|
||||
* current temperature > threshold + trigger_levels[2]
|
||||
* 3: temperature for trigger_level3 interrupt
|
||||
* condition for trigger_level3 interrupt:
|
||||
* current temperature > threshold + trigger_levels[3]
|
||||
* @trigger_type: defines the type of trigger. Possible values are,
|
||||
* THROTTLE_ACTIVE trigger type
|
||||
* THROTTLE_PASSIVE trigger type
|
||||
* SW_TRIP trigger type
|
||||
* HW_TRIP
|
||||
* @trigger_enable[]: array to denote which trigger levels are enabled.
|
||||
* 1 = enable trigger_level[] interrupt,
|
||||
* 0 = disable trigger_level[] interrupt
|
||||
* @max_trigger_level: max trigger level supported by the TMU
|
||||
* @non_hw_trigger_levels: number of defined non-hardware trigger levels
|
||||
* @gain: gain of amplifier in the positive-TC generator block
|
||||
* 0 < gain <= 15
|
||||
* @reference_voltage: reference voltage of amplifier
|
||||
* in the positive-TC generator block
|
||||
* 0 < reference_voltage <= 31
|
||||
* @noise_cancel_mode: noise cancellation mode
|
||||
* 000, 100, 101, 110 and 111 can be different modes
|
||||
* @type: determines the type of SOC
|
||||
* @efuse_value: platform defined fuse value
|
||||
* @min_efuse_value: minimum valid trimming data
|
||||
* @max_efuse_value: maximum valid trimming data
|
||||
* @first_point_trim: temp value of the first point trimming
|
||||
* @second_point_trim: temp value of the second point trimming
|
||||
* @default_temp_offset: default temperature offset in case of no trimming
|
||||
* @test_mux; information if SoC supports test MUX
|
||||
* @triminfo_reload: reload value to read TRIMINFO register
|
||||
* @cal_type: calibration type for temperature
|
||||
* @freq_clip_table: Table representing frequency reduction percentage.
|
||||
* @freq_tab_count: Count of the above table as frequency reduction may
|
||||
* applicable to only some of the trigger levels.
|
||||
* @registers: Pointer to structure containing all the TMU controller registers
|
||||
* and bitfields shifts and masks.
|
||||
* @features: a bitfield value indicating the features supported in SOC like
|
||||
* emulation, multi instance etc
|
||||
*
|
||||
* This structure is required for configuration of exynos_tmu driver.
|
||||
*/
|
||||
struct exynos_tmu_platform_data {
|
||||
char *tmu_name;
|
||||
int ect_hotplug_flag;
|
||||
int ect_hotplug_interval;
|
||||
|
||||
u32 temp_mask;
|
||||
u8 threshold;
|
||||
u8 threshold_falling;
|
||||
u8 trigger_levels[MAX_TRIP_COUNT];
|
||||
enum trigger_type trigger_type[MAX_TRIP_COUNT];
|
||||
bool trigger_enable[MAX_TRIP_COUNT];
|
||||
u8 max_trigger_level;
|
||||
u8 non_hw_trigger_levels;
|
||||
u8 default_ptat_cont;
|
||||
u8 gain;
|
||||
u8 reference_voltage;
|
||||
u8 noise_cancel_mode;
|
||||
|
||||
u32 efuse_value;
|
||||
u32 min_efuse_value;
|
||||
u32 max_efuse_value;
|
||||
u8 first_point_trim;
|
||||
u8 second_point_trim;
|
||||
u8 default_temp_offset;
|
||||
u8 test_mux;
|
||||
u8 triminfo_reload[MAX_TRIMINFO_CTRL_REG];
|
||||
bool hotplug_enable;
|
||||
u32 hotplug_in_threshold;
|
||||
u32 hotplug_out_threshold;
|
||||
|
||||
enum calibration_type cal_type;
|
||||
enum soc_type type;
|
||||
enum dev_type d_type;
|
||||
struct freq_clip_table freq_tab[MAX_TRIP_COUNT];
|
||||
unsigned int freq_tab_count;
|
||||
const struct exynos_tmu_registers *registers;
|
||||
unsigned int features;
|
||||
|
||||
u8 sensor_type;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct exynos_tmu_init_data
|
||||
* @tmu_count: number of TMU instances.
|
||||
* @tmu_data: platform data of all TMU instances.
|
||||
* This structure is required to store data for multi-instance exynos tmu
|
||||
* driver.
|
||||
*/
|
||||
struct exynos_tmu_init_data {
|
||||
int tmu_count;
|
||||
struct exynos_tmu_platform_data tmu_data[];
|
||||
};
|
||||
|
||||
#endif /* _EXYNOS_TMU_H */
|
1042
drivers/thermal/samsung/exynos_tmu_data.c
Executable file
1042
drivers/thermal/samsung/exynos_tmu_data.c
Executable file
File diff suppressed because it is too large
Load diff
361
drivers/thermal/samsung/exynos_tmu_data.h
Executable file
361
drivers/thermal/samsung/exynos_tmu_data.h
Executable file
|
@ -0,0 +1,361 @@
|
|||
/*
|
||||
* exynos_tmu_data.h - Samsung EXYNOS tmu data header file
|
||||
*
|
||||
* Copyright (C) 2013 Samsung Electronics
|
||||
* Amit Daniel Kachhap <amit.daniel@samsung.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _EXYNOS_TMU_DATA_H
|
||||
#define _EXYNOS_TMU_DATA_H
|
||||
|
||||
/* Exynos generic registers */
|
||||
#define EXYNOS_TMU_REG_TRIMINFO 0x0
|
||||
#define EXYNOS_TMU_REG_TRIMINFO1 0x4
|
||||
#define EXYNOS_TMU_REG_CONTROL 0x20
|
||||
#define EXYNOS_TMU_REG_CONTROL1 0x24
|
||||
#define EXYNOS_TMU_REG_STATUS 0x28
|
||||
#define EXYNOS_TMU_REG_CURRENT_TEMP 0x40
|
||||
#define EXYNOS_TMU_REG_INTEN 0x70
|
||||
#define EXYNOS_TMU_REG_INTSTAT 0x74
|
||||
#define EXYNOS_TMU_REG_INTCLEAR 0x78
|
||||
|
||||
#define EXYNOS_TMU_REF_VOLTAGE_OTP_SHIFT 18
|
||||
#define EXYNOS_TMU_REF_VOLTAGE_OTP_MASK 0x1F
|
||||
#define EXYNOS_TMU_REF_VOLTAGE_OTP_MASK_3BIT 0x7
|
||||
#define EXYNOS_TMU_REF_VOLTAGE_SHIFT 24
|
||||
#define EXYNOS_TMU_REF_VOLTAGE_MASK 0x1f
|
||||
#define EXYNOS_TMU_BUF_SLOPE_SEL_OTP_MASK 0xf
|
||||
#define EXYNOS_TMU_BUF_SLOPE_SEL_OTP_MASK_3BIT 0x7
|
||||
#define EXYNOS_TMU_BUF_SLOPE_SEL_OTP_SHIFT 18
|
||||
#define EXYNOS_TMU_BUF_SLOPE_SEL_MASK 0xf
|
||||
#define EXYNOS_TMU_BUF_SLOPE_SEL_SHIFT 8
|
||||
#define EXYNOS_TMU_CORE_EN_SHIFT 0
|
||||
#define EXYNOS_TMU_MUX_ADDR_SHIFT 20
|
||||
#define EXYNOS_TMU_MUX_ADDR_MASK 0x5
|
||||
#define EXYNOS_TMU_PTAT_CON_SHIFT 20
|
||||
#define EXYNOS_TMU_PTAT_CON_MASK 0x7
|
||||
#define EXYNOS_TMU_BUF_CONT_SHIFT 12
|
||||
#define EXYNOS_TMU_BUF_CONT_MASK 0xf
|
||||
|
||||
/* Exynos3250 specific registers */
|
||||
#define EXYNOS_TMU_TRIMINFO_CON1 0x10
|
||||
|
||||
/* Exynos4210 specific registers */
|
||||
#define EXYNOS4210_TMU_REG_THRESHOLD_TEMP 0x44
|
||||
#define EXYNOS4210_TMU_REG_TRIG_LEVEL0 0x50
|
||||
|
||||
/* Exynos5250, Exynos4412, Exynos3250 specific registers */
|
||||
#define EXYNOS_TMU_TRIMINFO_CON2 0x14
|
||||
#define EXYNOS_THD_TEMP_RISE 0x50
|
||||
#define EXYNOS_THD_TEMP_FALL 0x54
|
||||
#define EXYNOS_EMUL_CON 0x80
|
||||
|
||||
#define EXYNOS_TRIMINFO_25_SHIFT 0
|
||||
#define EXYNOS_TRIMINFO_85_SHIFT 8
|
||||
#define EXYNOS_TMU_TRIP_MODE_SHIFT 13
|
||||
#define EXYNOS_TMU_TRIP_MODE_MASK 0x7
|
||||
#define EXYNOS_TMU_THERM_TRIP_EN_SHIFT 12
|
||||
|
||||
#define EXYNOS_TMU_INTEN_RISE0_SHIFT 0
|
||||
#define EXYNOS_TMU_INTEN_RISE1_SHIFT 4
|
||||
#define EXYNOS_TMU_INTEN_RISE2_SHIFT 8
|
||||
#define EXYNOS_TMU_INTEN_RISE3_SHIFT 12
|
||||
#define EXYNOS_TMU_INTEN_FALL0_SHIFT 16
|
||||
|
||||
#define EXYNOS_EMUL_TIME 0x1
|
||||
#define EXYNOS_EMUL_TIME_MASK 0xffff
|
||||
#define EXYNOS_EMUL_TIME_SHIFT 16
|
||||
#define EXYNOS_EMUL_DATA_SHIFT 8
|
||||
#define EXYNOS_EMUL_DATA_MASK 0xFF
|
||||
#define EXYNOS_EMUL_ENABLE 0x1
|
||||
|
||||
#define EXYNOS_MAX_TRIGGER_PER_REG 8
|
||||
|
||||
/* Exynos5260 specific */
|
||||
#define EXYNOS5260_TMU_REG_INTEN 0xC0
|
||||
#define EXYNOS5260_TMU_REG_INTSTAT 0xC4
|
||||
#define EXYNOS5260_TMU_REG_INTCLEAR 0xC8
|
||||
#define EXYNOS5260_EMUL_CON 0x100
|
||||
|
||||
/* Exynos4412 specific */
|
||||
#define EXYNOS4412_MUX_ADDR_VALUE 6
|
||||
#define EXYNOS4412_MUX_ADDR_SHIFT 20
|
||||
|
||||
/*exynos5440 specific registers*/
|
||||
#define EXYNOS5440_TMU_S0_7_TRIM 0x000
|
||||
#define EXYNOS5440_TMU_S0_7_CTRL 0x020
|
||||
#define EXYNOS5440_TMU_S0_7_DEBUG 0x040
|
||||
#define EXYNOS5440_TMU_S0_7_STATUS 0x060
|
||||
#define EXYNOS5440_TMU_S0_7_TEMP 0x0f0
|
||||
#define EXYNOS5440_TMU_S0_7_TH0 0x110
|
||||
#define EXYNOS5440_TMU_S0_7_TH1 0x130
|
||||
#define EXYNOS5440_TMU_S0_7_TH2 0x150
|
||||
#define EXYNOS5440_TMU_S0_7_IRQEN 0x210
|
||||
#define EXYNOS5440_TMU_S0_7_IRQ 0x230
|
||||
/* exynos5440 common registers */
|
||||
#define EXYNOS5440_TMU_IRQ_STATUS 0x000
|
||||
#define EXYNOS5440_TMU_PMIN 0x004
|
||||
|
||||
#define EXYNOS5440_TMU_INTEN_RISE0_SHIFT 0
|
||||
#define EXYNOS5440_TMU_INTEN_RISE1_SHIFT 1
|
||||
#define EXYNOS5440_TMU_INTEN_RISE2_SHIFT 2
|
||||
#define EXYNOS5440_TMU_INTEN_RISE3_SHIFT 3
|
||||
#define EXYNOS5440_TMU_INTEN_FALL0_SHIFT 4
|
||||
#define EXYNOS5440_TMU_TH_RISE4_SHIFT 24
|
||||
#define EXYNOS5440_EFUSE_SWAP_OFFSET 8
|
||||
|
||||
#if defined(CONFIG_SOC_EXYNOS3250)
|
||||
extern struct exynos_tmu_init_data const exynos3250_default_tmu_data;
|
||||
#define EXYNOS3250_TMU_DRV_DATA (&exynos3250_default_tmu_data)
|
||||
#else
|
||||
#define EXYNOS3250_TMU_DRV_DATA (NULL)
|
||||
#endif
|
||||
|
||||
/*exynos7580 specific registers*/
|
||||
#define EXYNOS7580_TMU_RISE3_0 0x50
|
||||
#define EXYNOS7580_TMU_RISE7_4 0x54
|
||||
#define EXYNOS7580_TMU_TH_HW_TRIP_SHIFT 24
|
||||
|
||||
#define EXYNOS7580_TMU_FALL3_0 0x60
|
||||
#define EXYNOS7580_TMU_FALL7_4 0x64
|
||||
|
||||
#define EXYNOS7580_TMU_REG_INTEN 0xC0
|
||||
#define EXYNOS7580_TMU_REG_INTCLEAR 0xC8
|
||||
#define EXYNOS7580_EMUL_CON 0x110
|
||||
|
||||
#define EXYNOS7580_TMU_LPI0_MODE_EN_SHIFT 10
|
||||
|
||||
#define EXYNOS7580_TMU_VALID_P0 15
|
||||
|
||||
#define EXYNOS7580_TMU_INTEN_RISE0_SHIFT 0
|
||||
#define EXYNOS7580_TMU_INTEN_RISE1_SHIFT 1
|
||||
#define EXYNOS7580_TMU_INTEN_RISE2_SHIFT 2
|
||||
#define EXYNOS7580_TMU_INTEN_RISE3_SHIFT 3
|
||||
#define EXYNOS7580_TMU_INTEN_RISE4_SHIFT 4
|
||||
#define EXYNOS7580_TMU_INTEN_RISE5_SHIFT 5
|
||||
#define EXYNOS7580_TMU_INTEN_RISE6_SHIFT 6
|
||||
#define EXYNOS7580_TMU_INTEN_RISE7_SHIFT 7
|
||||
#define EXYNOS7580_TMU_INTEN_FALL0_SHIFT 16
|
||||
#define EXYNOS7580_TMU_INTEN_FALL1_SHIFT 17
|
||||
#define EXYNOS7580_TMU_INTEN_FALL2_SHIFT 18
|
||||
#define EXYNOS7580_TMU_INTEN_FALL3_SHIFT 19
|
||||
#define EXYNOS7580_TMU_INTEN_FALL4_SHIFT 20
|
||||
#define EXYNOS7580_TMU_INTEN_FALL5_SHIFT 21
|
||||
#define EXYNOS7580_TMU_INTEN_FALL6_SHIFT 22
|
||||
#define EXYNOS7580_TMU_INTEN_FALL7_SHIFT 23
|
||||
|
||||
#define EXYNOS7580_TMU_RISE_INT_MASK 0xff
|
||||
#define EXYNOS7580_TMU_RISE_INT_SHIFT 0
|
||||
#define EXYNOS7580_TMU_FALL_INT_MASK 0xff
|
||||
#define EXYNOS7580_TMU_FALL_INT_SHIFT 16
|
||||
|
||||
/* Define sensor type */
|
||||
#define EXYNOS_TEM1456X 0x1
|
||||
#define EXYNOS_TEM1455X 0x2
|
||||
|
||||
/*TEM1456X specific registers*/
|
||||
#define EXYNOS_TEM1456X_TMU_TEMP_MASK 0x1ff
|
||||
#define EXYNOS_TEM1456X_TRIMINFO_25_SHIFT 9
|
||||
#define EXYNOS_TEM1456X_TRIMINFO_85_SHIFT 9
|
||||
#define EXYNOS_TEM1456X_CALIB_SEL_SHIFT 23
|
||||
#define EXYNOS_TEM1456X_CALIB_SEL_MASK 1
|
||||
#define EXYNOS_TEM1456X_TMU_LPI0_MODE_EN_SHIFT 10
|
||||
|
||||
#define EXYNOS_TEM1456X_TMU_RISE6_7 0x50
|
||||
#define EXYNOS_TEM1456X_TMU_RISE4_5 0x54
|
||||
#define EXYNOS_TEM1456X_TMU_RISE2_3 0x58
|
||||
#define EXYNOS_TEM1456X_TMU_RISE0_1 0x5C
|
||||
#define EXYNOS_TEM1456X_TMU_TH_HW_TRIP_SHIFT 24
|
||||
|
||||
#define EXYNOS_TEM1456X_TMU_FALL6_7 0x60
|
||||
#define EXYNOS_TEM1456X_TMU_FALL4_5 0x64
|
||||
#define EXYNOS_TEM1456X_TMU_FALL2_3 0x68
|
||||
#define EXYNOS_TEM1456X_TMU_FALL0_1 0x6C
|
||||
|
||||
#define EXYNOS_TEM1456X_TMU_REG_INTEN 0x110
|
||||
#define EXYNOS_TEM1456X_TMU_REG_INTCLEAR 0x118
|
||||
|
||||
#define EXYNOS_TEM1456X_EMUL_CON 0x160
|
||||
#define EXYNOS_TEM1456X_EMUL_DATA_SHIFT 7
|
||||
#define EXYNOS_TEM1456X_EMUL_DATA_MASK 0x1FF
|
||||
|
||||
#define EXYNOS_TEM1456X_TMU_LPI0_MODE_EN_SHIFT 10
|
||||
#define EXYNOS_TEM1456X_TMU_VALID_P0 15
|
||||
|
||||
#define EXYNOS_TEM1456X_TMU_INTEN_RISE0_SHIFT 0
|
||||
#define EXYNOS_TEM1456X_TMU_INTEN_RISE1_SHIFT 1
|
||||
#define EXYNOS_TEM1456X_TMU_INTEN_RISE2_SHIFT 2
|
||||
#define EXYNOS_TEM1456X_TMU_INTEN_RISE3_SHIFT 3
|
||||
#define EXYNOS_TEM1456X_TMU_INTEN_RISE4_SHIFT 4
|
||||
#define EXYNOS_TEM1456X_TMU_INTEN_RISE5_SHIFT 5
|
||||
#define EXYNOS_TEM1456X_TMU_INTEN_RISE6_SHIFT 6
|
||||
#define EXYNOS_TEM1456X_TMU_INTEN_RISE7_SHIFT 7
|
||||
#define EXYNOS_TEM1456X_TMU_INTEN_FALL0_SHIFT 16
|
||||
#define EXYNOS_TEM1456X_TMU_INTEN_FALL1_SHIFT 17
|
||||
#define EXYNOS_TEM1456X_TMU_INTEN_FALL2_SHIFT 18
|
||||
#define EXYNOS_TEM1456X_TMU_INTEN_FALL3_SHIFT 19
|
||||
#define EXYNOS_TEM1456X_TMU_INTEN_FALL4_SHIFT 20
|
||||
#define EXYNOS_TEM1456X_TMU_INTEN_FALL5_SHIFT 21
|
||||
#define EXYNOS_TEM1456X_TMU_INTEN_FALL6_SHIFT 22
|
||||
#define EXYNOS_TEM1456X_TMU_INTEN_FALL7_SHIFT 23
|
||||
|
||||
#define EXYNOS_TEM1456X_TMU_RISE_INT_MASK 0xff
|
||||
#define EXYNOS_TEM1456X_TMU_RISE_INT_SHIFT 0
|
||||
#define EXYNOS_TEM1456X_TMU_FALL_INT_MASK 0xff
|
||||
#define EXYNOS_TEM1456X_TMU_FALL_INT_SHIFT 16
|
||||
#define EXYNOS_TEM1456X_VALID_MASK 0x1
|
||||
#define EXYNOS_TEM1456X_VALID_P0_SHIFT 12
|
||||
#define EXYNOS_TEM1456X_VALID_P1_SHIFT 13
|
||||
#define EXYNOS_TEM1456X_VALID_P2_SHIFT 14
|
||||
#define EXYNOS_TEM1456X_VALID_P3_SHIFT 15
|
||||
#define EXYNOS_TEM1456X_VALID_P4_SHIFT 16
|
||||
|
||||
/*TEM1455X specific registers*/
|
||||
#define EXYNOS_TEM1455X_MUX_ADDR_VALUE 0x0
|
||||
#define EXYNOS_TEM1455X_TMU_TEMP_MASK 0x1ff
|
||||
#define EXYNOS_TEM1455X_TRIMINFO_25_SHIFT 0
|
||||
#define EXYNOS_TEM1455X_TRIMINFO_85_SHIFT 9
|
||||
#define EXYNOS_TEM1455X_CALIB_SEL_SHIFT 23
|
||||
#define EXYNOS_TEM1455X_CALIB_SEL_MASK 1
|
||||
#define EXYNOS_TEM1455X_TMU_LPI0_MODE_EN_SHIFT 10
|
||||
|
||||
#define EXYNOS_TEM1455X_TMU_RISE6_7 0x50
|
||||
#define EXYNOS_TEM1455X_TMU_RISE4_5 0x54
|
||||
#define EXYNOS_TEM1455X_TMU_RISE2_3 0x58
|
||||
#define EXYNOS_TEM1455X_TMU_RISE0_1 0x5C
|
||||
|
||||
#define EXYNOS_TEM1455X_TMU_FALL6_7 0x60
|
||||
#define EXYNOS_TEM1455X_TMU_FALL4_5 0x64
|
||||
#define EXYNOS_TEM1455X_TMU_FALL2_3 0x68
|
||||
#define EXYNOS_TEM1455X_TMU_FALL0_1 0x6C
|
||||
|
||||
#define EXYNOS_TEM1455X_TMU_REG_INTEN 0x110
|
||||
#define EXYNOS_TEM1455X_TMU_REG_INTPEND 0x118
|
||||
#define EXYNOS_TEM1455X_TMU_REG_INTCLEAR 0x118
|
||||
|
||||
#define EXYNOS_TEM1455X_EMUL_CON 0x160
|
||||
#define EXYNOS_TEM1455X_EMUL_DATA_SHIFT 7
|
||||
#define EXYNOS_TEM1455X_EMUL_DATA_MASK 0x1FF
|
||||
|
||||
#define EXYNOS_TEM1455X_TMU_LPI0_MODE_EN_SHIFT 10
|
||||
#define EXYNOS_TEM1455X_TMU_VALID_P0 15
|
||||
|
||||
#define EXYNOS_TEM1455X_TMU_INTEN_RISE0_SHIFT 0
|
||||
#define EXYNOS_TEM1455X_TMU_INTEN_RISE1_SHIFT 1
|
||||
#define EXYNOS_TEM1455X_TMU_INTEN_RISE2_SHIFT 2
|
||||
#define EXYNOS_TEM1455X_TMU_INTEN_RISE3_SHIFT 3
|
||||
#define EXYNOS_TEM1455X_TMU_INTEN_RISE4_SHIFT 4
|
||||
#define EXYNOS_TEM1455X_TMU_INTEN_RISE5_SHIFT 5
|
||||
#define EXYNOS_TEM1455X_TMU_INTEN_RISE6_SHIFT 6
|
||||
#define EXYNOS_TEM1455X_TMU_INTEN_RISE7_SHIFT 7
|
||||
#define EXYNOS_TEM1455X_TMU_INTEN_FALL0_SHIFT 16
|
||||
#define EXYNOS_TEM1455X_TMU_INTEN_FALL1_SHIFT 17
|
||||
#define EXYNOS_TEM1455X_TMU_INTEN_FALL2_SHIFT 18
|
||||
#define EXYNOS_TEM1455X_TMU_INTEN_FALL3_SHIFT 19
|
||||
#define EXYNOS_TEM1455X_TMU_INTEN_FALL4_SHIFT 20
|
||||
#define EXYNOS_TEM1455X_TMU_INTEN_FALL5_SHIFT 21
|
||||
#define EXYNOS_TEM1455X_TMU_INTEN_FALL6_SHIFT 22
|
||||
#define EXYNOS_TEM1455X_TMU_INTEN_FALL7_SHIFT 23
|
||||
|
||||
#define EXYNOS_TEM1455X_TMU_RISE_INT_MASK 0xff
|
||||
#define EXYNOS_TEM1455X_TMU_RISE_INT_SHIFT 0
|
||||
#define EXYNOS_TEM1455X_TMU_FALL_INT_MASK 0xff
|
||||
#define EXYNOS_TEM1455X_TMU_FALL_INT_SHIFT 16
|
||||
#define EXYNOS_TEM1455X_VALID_MASK 0x1
|
||||
#define EXYNOS_TEM1455X_VALID_P0_SHIFT 12
|
||||
#define EXYNOS_TEM1455X_VALID_P1_SHIFT 13
|
||||
#define EXYNOS_TEM1455X_VALID_P2_SHIFT 14
|
||||
#define EXYNOS_TEM1455X_VALID_P3_SHIFT 15
|
||||
#define EXYNOS_TEM1455X_VALID_P4_SHIFT 16
|
||||
|
||||
/* Define Exynos Global constant value */
|
||||
#define EXYNOS_MAX_TEMP 125
|
||||
#define EXYNOS_MIN_TEMP 10
|
||||
#define EXYNOS_COLD_TEMP 15
|
||||
|
||||
|
||||
#if defined(CONFIG_CPU_EXYNOS4210)
|
||||
extern struct exynos_tmu_init_data const exynos4210_default_tmu_data;
|
||||
#define EXYNOS4210_TMU_DRV_DATA (&exynos4210_default_tmu_data)
|
||||
#else
|
||||
#define EXYNOS4210_TMU_DRV_DATA (NULL)
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_SOC_EXYNOS4412)
|
||||
extern struct exynos_tmu_init_data const exynos4412_default_tmu_data;
|
||||
#define EXYNOS4412_TMU_DRV_DATA (&exynos4412_default_tmu_data)
|
||||
#else
|
||||
#define EXYNOS4412_TMU_DRV_DATA (NULL)
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_SOC_EXYNOS5250)
|
||||
extern struct exynos_tmu_init_data const exynos5250_default_tmu_data;
|
||||
#define EXYNOS5250_TMU_DRV_DATA (&exynos5250_default_tmu_data)
|
||||
#else
|
||||
#define EXYNOS5250_TMU_DRV_DATA (NULL)
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_SOC_EXYNOS5260)
|
||||
extern struct exynos_tmu_init_data const exynos5260_default_tmu_data;
|
||||
#define EXYNOS5260_TMU_DRV_DATA (&exynos5260_default_tmu_data)
|
||||
#else
|
||||
#define EXYNOS5260_TMU_DRV_DATA (NULL)
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_SOC_EXYNOS5420)
|
||||
extern struct exynos_tmu_init_data const exynos5420_default_tmu_data;
|
||||
#define EXYNOS5420_TMU_DRV_DATA (&exynos5420_default_tmu_data)
|
||||
#else
|
||||
#define EXYNOS5420_TMU_DRV_DATA (NULL)
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_SOC_EXYNOS5440)
|
||||
extern struct exynos_tmu_init_data const exynos5440_default_tmu_data;
|
||||
#define EXYNOS5440_TMU_DRV_DATA (&exynos5440_default_tmu_data)
|
||||
#else
|
||||
#define EXYNOS5440_TMU_DRV_DATA (NULL)
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_SOC_EXYNOS7580)
|
||||
extern struct exynos_tmu_init_data const exynos7580_default_tmu_data;
|
||||
#define EXYNOS7580_TMU_DRV_DATA (&exynos7580_default_tmu_data)
|
||||
#else
|
||||
#define EXYNOS7580_TMU_DRV_DATA (NULL)
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_SOC_EXYNOS8890)
|
||||
extern struct exynos_tmu_init_data exynos8890_default_tmu_data;
|
||||
#define EXYNOS8890_TMU_DRV_DATA (&exynos8890_default_tmu_data)
|
||||
#else
|
||||
#define EXYNOS8890_TMU_DRV_DATA (NULL)
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_SOC_EXYNOS7870)
|
||||
extern struct exynos_tmu_init_data exynos7870_default_tmu_data;
|
||||
#define EXYNOS7870_TMU_DRV_DATA (&exynos7870_default_tmu_data)
|
||||
#else
|
||||
#define EXYNOS7870_TMU_DRV_DATA (NULL)
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_SOC_EXYNOS7570)
|
||||
extern struct exynos_tmu_init_data exynos7570_default_tmu_data;
|
||||
#define EXYNOS7570_TMU_DRV_DATA (&exynos7570_default_tmu_data)
|
||||
#else
|
||||
#define EXYNOS7570_TMU_DRV_DATA (NULL)
|
||||
#endif
|
||||
#endif /*_EXYNOS_TMU_DATA_H*/
|
460
drivers/thermal/samsung/gpu_cooling.c
Normal file
460
drivers/thermal/samsung/gpu_cooling.c
Normal file
|
@ -0,0 +1,460 @@
|
|||
/*
|
||||
* linux/drivers/thermal/gpu_cooling.c
|
||||
*
|
||||
* Copyright (C) 2012 Samsung Electronics Co., Ltd(http://www.samsung.com)
|
||||
* Copyright (C) 2012 Amit Daniel <amit.kachhap@linaro.org>
|
||||
*
|
||||
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; version 2 of the License.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
*
|
||||
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
*/
|
||||
#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/gpu_cooling.h>
|
||||
|
||||
#include <soc/samsung/tmu.h>
|
||||
|
||||
#include "exynos_tmu_data.h"
|
||||
|
||||
/**
|
||||
* struct gpufreq_cooling_device - data for cooling device with gpufreq
|
||||
* @id: unique integer value corresponding to each gpufreq_cooling_device
|
||||
* registered.
|
||||
* @cool_dev: thermal_cooling_device pointer to keep track of the
|
||||
* registered cooling device.
|
||||
* @gpufreq_state: integer value representing the current state of gpufreq
|
||||
* cooling devices.
|
||||
* @gpufreq_val: integer value representing the absolute value of the clipped
|
||||
* frequency.
|
||||
* @allowed_gpus: all the gpus involved for this gpufreq_cooling_device.
|
||||
*
|
||||
* This structure is required for keeping information of each
|
||||
* gpufreq_cooling_device registered. In order to prevent corruption of this a
|
||||
* mutex lock cooling_gpu_lock is used.
|
||||
*/
|
||||
struct gpufreq_cooling_device {
|
||||
int id;
|
||||
struct thermal_cooling_device *cool_dev;
|
||||
unsigned int gpufreq_state;
|
||||
unsigned int gpufreq_val;
|
||||
};
|
||||
static DEFINE_IDR(gpufreq_idr);
|
||||
static DEFINE_MUTEX(cooling_gpu_lock);
|
||||
static BLOCKING_NOTIFIER_HEAD(gpu_notifier);
|
||||
|
||||
static unsigned int gpufreq_dev_count;
|
||||
|
||||
extern struct cpufreq_frequency_table gpu_freq_table[];
|
||||
|
||||
/**
|
||||
* get_idr - function to get a unique id.
|
||||
* @idr: struct idr * handle used to create a id.
|
||||
* @id: int * value generated by this function.
|
||||
*
|
||||
* This function will populate @id with an unique
|
||||
* id, using the idr API.
|
||||
*
|
||||
* Return: 0 on success, an error code on failure.
|
||||
*/
|
||||
static int get_idr(struct idr *idr, int *id)
|
||||
{
|
||||
int ret;
|
||||
|
||||
mutex_lock(&cooling_gpu_lock);
|
||||
ret = idr_alloc(idr, NULL, 0, 0, GFP_KERNEL);
|
||||
mutex_unlock(&cooling_gpu_lock);
|
||||
if (unlikely(ret < 0))
|
||||
return ret;
|
||||
*id = ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* release_idr - function to free the unique id.
|
||||
* @idr: struct idr * handle used for creating the id.
|
||||
* @id: int value representing the unique id.
|
||||
*/
|
||||
static void release_idr(struct idr *idr, int id)
|
||||
{
|
||||
mutex_lock(&cooling_gpu_lock);
|
||||
idr_remove(idr, id);
|
||||
mutex_unlock(&cooling_gpu_lock);
|
||||
}
|
||||
|
||||
/* Below code defines functions to be used for gpufreq as cooling device */
|
||||
|
||||
enum gpufreq_cooling_property {
|
||||
GET_LEVEL,
|
||||
GET_FREQ,
|
||||
GET_MAXL,
|
||||
};
|
||||
|
||||
/**
|
||||
* get_property - fetch a property of interest for a give gpu.
|
||||
* @gpu: gpu for which the property is required
|
||||
* @input: query parameter
|
||||
* @output: query return
|
||||
* @property: type of query (frequency, level, max level)
|
||||
*
|
||||
* This is the common function to
|
||||
* 1. get maximum gpu cooling states
|
||||
* 2. translate frequency to cooling state
|
||||
* 3. translate cooling state to frequency
|
||||
* Note that the code may be not in good shape
|
||||
* but it is written in this way in order to:
|
||||
* a) reduce duplicate code as most of the code can be shared.
|
||||
* b) make sure the logic is consistent when translating between
|
||||
* cooling states and frequencies.
|
||||
*
|
||||
* Return: 0 on success, -EINVAL when invalid parameters are passed.
|
||||
*/
|
||||
static int get_property(unsigned int gpu, unsigned long input,
|
||||
unsigned int *output,
|
||||
enum gpufreq_cooling_property property)
|
||||
{
|
||||
int i;
|
||||
unsigned long max_level = 0, level = 0;
|
||||
unsigned int freq = CPUFREQ_ENTRY_INVALID;
|
||||
int descend = -1;
|
||||
struct cpufreq_frequency_table *pos, *table =
|
||||
gpu_freq_table;
|
||||
|
||||
if (!output)
|
||||
return -EINVAL;
|
||||
|
||||
cpufreq_for_each_valid_entry(pos, table) {
|
||||
/* ignore duplicate entry */
|
||||
if (freq == pos->frequency)
|
||||
continue;
|
||||
|
||||
/* get the frequency order */
|
||||
if (freq != CPUFREQ_ENTRY_INVALID && descend == -1)
|
||||
descend = freq > pos->frequency;
|
||||
|
||||
freq = pos->frequency;
|
||||
max_level++;
|
||||
}
|
||||
|
||||
/* No valid cpu frequency entry */
|
||||
if (max_level == 0)
|
||||
return -EINVAL;
|
||||
|
||||
/* max_level is an index, not a counter */
|
||||
max_level--;
|
||||
|
||||
/* get max level */
|
||||
if (property == GET_MAXL) {
|
||||
*output = (unsigned int)max_level;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (property == GET_FREQ)
|
||||
level = descend ? input : (max_level - input);
|
||||
|
||||
i = 0;
|
||||
cpufreq_for_each_valid_entry(pos, table) {
|
||||
/* ignore duplicate entry */
|
||||
if (freq == pos->frequency)
|
||||
continue;
|
||||
|
||||
/* now we have a valid frequency entry */
|
||||
freq = pos->frequency;
|
||||
|
||||
if (property == GET_LEVEL && (unsigned int)input == freq) {
|
||||
/* get level by frequency */
|
||||
*output = descend ? i : (max_level - i);
|
||||
return 0;
|
||||
}
|
||||
if (property == GET_FREQ && level == i) {
|
||||
/* get frequency by level */
|
||||
*output = freq;
|
||||
return 0;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/**
|
||||
* gpufreq_cooling_get_level - for a give gpu, return the cooling level.
|
||||
* @gpu: gpu for which the level is required
|
||||
* @freq: the frequency of interest
|
||||
*
|
||||
* This function will match the cooling level corresponding to the
|
||||
* requested @freq and return it.
|
||||
*
|
||||
* Return: The matched cooling level on success or THERMAL_CSTATE_INVALID
|
||||
* otherwise.
|
||||
*/
|
||||
unsigned long gpufreq_cooling_get_level(unsigned int gpu, unsigned int freq)
|
||||
{
|
||||
unsigned int val;
|
||||
|
||||
if (get_property(gpu, (unsigned long)freq, &val, GET_LEVEL))
|
||||
return THERMAL_CSTATE_INVALID;
|
||||
|
||||
return (unsigned long)val;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(gpufreq_cooling_get_level);
|
||||
|
||||
/**
|
||||
* gpufreq_apply_cooling - function to apply frequency clipping.
|
||||
* @gpufreq_device: gpufreq_cooling_device pointer containing frequency
|
||||
* clipping data.
|
||||
* @cooling_state: value of the cooling state.
|
||||
*
|
||||
* Function used to make sure the gpufreq layer is aware of current thermal
|
||||
* limits. The limits are applied by updating the gpufreq policy.
|
||||
*
|
||||
* Return: 0 on success, an error code otherwise (-EINVAL in case wrong
|
||||
* cooling state).
|
||||
*/
|
||||
static int gpufreq_apply_cooling(struct gpufreq_cooling_device *gpufreq_device,
|
||||
unsigned long cooling_state)
|
||||
{
|
||||
/* Check if the old cooling action is same as new cooling action */
|
||||
if (gpufreq_device->gpufreq_state == cooling_state)
|
||||
return 0;
|
||||
|
||||
gpufreq_device->gpufreq_state = cooling_state;
|
||||
|
||||
blocking_notifier_call_chain(&gpu_notifier, GPU_THROTTLING, &cooling_state);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* gpufreq cooling device callback functions are defined below */
|
||||
|
||||
/**
|
||||
* gpufreq_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.
|
||||
*
|
||||
* Callback for the thermal cooling device to return the gpufreq
|
||||
* max cooling state.
|
||||
*
|
||||
* Return: 0 on success, an error code otherwise.
|
||||
*/
|
||||
static int gpufreq_get_max_state(struct thermal_cooling_device *cdev,
|
||||
unsigned long *state)
|
||||
{
|
||||
unsigned int count = 0;
|
||||
int ret;
|
||||
|
||||
ret = get_property(0, 0, &count, GET_MAXL);
|
||||
|
||||
if (count > 0)
|
||||
*state = count;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* gpufreq_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.
|
||||
*
|
||||
* Callback for the thermal cooling device to return the gpufreq
|
||||
* current cooling state.
|
||||
*
|
||||
* Return: 0 on success, an error code otherwise.
|
||||
*/
|
||||
static int gpufreq_get_cur_state(struct thermal_cooling_device *cdev,
|
||||
unsigned long *state)
|
||||
{
|
||||
struct gpufreq_cooling_device *gpufreq_device = cdev->devdata;
|
||||
|
||||
*state = gpufreq_device->gpufreq_state;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* gpufreq_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.
|
||||
*
|
||||
* Callback for the thermal cooling device to change the gpufreq
|
||||
* current cooling state.
|
||||
*
|
||||
* Return: 0 on success, an error code otherwise.
|
||||
*/
|
||||
static int gpufreq_set_cur_state(struct thermal_cooling_device *cdev,
|
||||
unsigned long state)
|
||||
{
|
||||
struct gpufreq_cooling_device *gpufreq_device = cdev->devdata;
|
||||
|
||||
return gpufreq_apply_cooling(gpufreq_device, state);
|
||||
}
|
||||
|
||||
static enum tmu_noti_state_t gpu_tstate = GPU_COLD;
|
||||
|
||||
int gpufreq_set_cur_temp(bool suspended, unsigned long temp)
|
||||
{
|
||||
enum tmu_noti_state_t tstate;
|
||||
|
||||
if (suspended || temp < EXYNOS_COLD_TEMP)
|
||||
tstate = GPU_COLD;
|
||||
else
|
||||
tstate = GPU_NORMAL;
|
||||
|
||||
if (gpu_tstate == tstate)
|
||||
return 0;
|
||||
|
||||
gpu_tstate = tstate;
|
||||
|
||||
blocking_notifier_call_chain(&gpu_notifier, tstate, &tstate);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Bind gpufreq callbacks to thermal cooling device ops */
|
||||
static struct thermal_cooling_device_ops const gpufreq_cooling_ops = {
|
||||
.get_max_state = gpufreq_get_max_state,
|
||||
.get_cur_state = gpufreq_get_cur_state,
|
||||
.set_cur_state = gpufreq_set_cur_state,
|
||||
};
|
||||
|
||||
|
||||
int exynos_gpu_add_notifier(struct notifier_block *n)
|
||||
{
|
||||
return blocking_notifier_chain_register(&gpu_notifier, n);
|
||||
}
|
||||
|
||||
/**
|
||||
* __gpufreq_cooling_register - helper function to create gpufreq cooling device
|
||||
* @np: a valid struct device_node to the cooling device device tree node
|
||||
* @clip_gpus: gpumask of gpus where the frequency constraints will happen.
|
||||
*
|
||||
* This interface function registers the gpufreq cooling device with the name
|
||||
* "thermal-gpufreq-%x". This api can support multiple instances of gpufreq
|
||||
* cooling devices. It also gives the opportunity to link the cooling device
|
||||
* with a device tree node, in order to bind it via the thermal DT code.
|
||||
*
|
||||
* Return: a valid struct thermal_cooling_device pointer on success,
|
||||
* on failure, it returns a corresponding ERR_PTR().
|
||||
*/
|
||||
static struct thermal_cooling_device *
|
||||
__gpufreq_cooling_register(struct device_node *np,
|
||||
const struct cpumask *clip_gpus)
|
||||
{
|
||||
struct thermal_cooling_device *cool_dev;
|
||||
struct gpufreq_cooling_device *gpufreq_dev = NULL;
|
||||
char dev_name[THERMAL_NAME_LENGTH];
|
||||
int ret = 0;
|
||||
|
||||
gpufreq_dev = kzalloc(sizeof(struct gpufreq_cooling_device),
|
||||
GFP_KERNEL);
|
||||
if (!gpufreq_dev)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
ret = get_idr(&gpufreq_idr, &gpufreq_dev->id);
|
||||
if (ret) {
|
||||
kfree(gpufreq_dev);
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
snprintf(dev_name, sizeof(dev_name), "thermal-gpufreq-%d",
|
||||
gpufreq_dev->id);
|
||||
|
||||
cool_dev = thermal_of_cooling_device_register(np, dev_name, gpufreq_dev,
|
||||
&gpufreq_cooling_ops);
|
||||
if (IS_ERR(cool_dev)) {
|
||||
release_idr(&gpufreq_idr, gpufreq_dev->id);
|
||||
kfree(gpufreq_dev);
|
||||
return cool_dev;
|
||||
}
|
||||
gpufreq_dev->cool_dev = cool_dev;
|
||||
gpufreq_dev->gpufreq_state = 0;
|
||||
mutex_lock(&cooling_gpu_lock);
|
||||
|
||||
gpufreq_dev_count++;
|
||||
|
||||
mutex_unlock(&cooling_gpu_lock);
|
||||
|
||||
return cool_dev;
|
||||
}
|
||||
|
||||
/**
|
||||
* gpufreq_cooling_register - function to create gpufreq cooling device.
|
||||
* @clip_gpus: cpumask of gpus where the frequency constraints will happen.
|
||||
*
|
||||
* This interface function registers the gpufreq cooling device with the name
|
||||
* "thermal-gpufreq-%x". This api can support multiple instances of gpufreq
|
||||
* cooling devices.
|
||||
*
|
||||
* Return: a valid struct thermal_cooling_device pointer on success,
|
||||
* on failure, it returns a corresponding ERR_PTR().
|
||||
*/
|
||||
struct thermal_cooling_device *
|
||||
gpufreq_cooling_register(const struct cpumask *clip_gpus)
|
||||
{
|
||||
return __gpufreq_cooling_register(NULL, clip_gpus);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(gpufreq_cooling_register);
|
||||
|
||||
/**
|
||||
* of_gpufreq_cooling_register - function to create gpufreq cooling device.
|
||||
* @np: a valid struct device_node to the cooling device device tree node
|
||||
* @clip_gpus: cpumask of gpus where the frequency constraints will happen.
|
||||
*
|
||||
* This interface function registers the gpufreq cooling device with the name
|
||||
* "thermal-gpufreq-%x". This api can support multiple instances of gpufreq
|
||||
* cooling devices. Using this API, the gpufreq cooling device will be
|
||||
* linked to the device tree node provided.
|
||||
*
|
||||
* Return: a valid struct thermal_cooling_device pointer on success,
|
||||
* on failure, it returns a corresponding ERR_PTR().
|
||||
*/
|
||||
struct thermal_cooling_device *
|
||||
of_gpufreq_cooling_register(struct device_node *np,
|
||||
const struct cpumask *clip_gpus)
|
||||
{
|
||||
if (!np)
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
return __gpufreq_cooling_register(np, clip_gpus);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(of_gpufreq_cooling_register);
|
||||
|
||||
/**
|
||||
* gpufreq_cooling_unregister - function to remove gpufreq cooling device.
|
||||
* @cdev: thermal cooling device pointer.
|
||||
*
|
||||
* This interface function unregisters the "thermal-gpufreq-%x" cooling device.
|
||||
*/
|
||||
void gpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
|
||||
{
|
||||
struct gpufreq_cooling_device *gpufreq_dev;
|
||||
|
||||
if (!cdev)
|
||||
return;
|
||||
|
||||
gpufreq_dev = cdev->devdata;
|
||||
mutex_lock(&cooling_gpu_lock);
|
||||
gpufreq_dev_count--;
|
||||
mutex_unlock(&cooling_gpu_lock);
|
||||
|
||||
thermal_cooling_device_unregister(gpufreq_dev->cool_dev);
|
||||
release_idr(&gpufreq_idr, gpufreq_dev->id);
|
||||
kfree(gpufreq_dev);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(gpufreq_cooling_unregister);
|
459
drivers/thermal/samsung/isp_cooling.c
Normal file
459
drivers/thermal/samsung/isp_cooling.c
Normal file
|
@ -0,0 +1,459 @@
|
|||
/*
|
||||
* linux/drivers/thermal/isp_cooling.c
|
||||
*
|
||||
* Copyright (C) 2012 Samsung Electronics Co., Ltd(http://www.samsung.com)
|
||||
* Copyright (C) 2012 Amit Daniel <amit.kachhap@linaro.org>
|
||||
*
|
||||
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; version 2 of the License.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
*
|
||||
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
*/
|
||||
#include <linux/module.h>
|
||||
#include <linux/thermal.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/cpu.h>
|
||||
#include <linux/isp_cooling.h>
|
||||
|
||||
#include <soc/samsung/tmu.h>
|
||||
|
||||
#include "exynos_tmu_data.h"
|
||||
|
||||
/**
|
||||
* struct isp_cooling_device - data for cooling device with isp
|
||||
* @id: unique integer value corresponding to each isp_cooling_device
|
||||
* registered.
|
||||
* @cool_dev: thermal_cooling_device pointer to keep track of the
|
||||
* registered cooling device.
|
||||
* @isp_state: integer value representing the current state of isp
|
||||
* cooling devices.
|
||||
* @isp_val: integer value representing the absolute value of the clipped
|
||||
* fps.
|
||||
* @allowed_isp: all the isp involved for this isp_cooling_device.
|
||||
*
|
||||
* This structure is required for keeping information of each
|
||||
* isp_cooling_device registered. In order to prevent corruption of this a
|
||||
* mutex lock cooling_isp_lock is used.
|
||||
*/
|
||||
struct isp_cooling_device {
|
||||
int id;
|
||||
struct thermal_cooling_device *cool_dev;
|
||||
unsigned int isp_state;
|
||||
unsigned int isp_val;
|
||||
};
|
||||
static DEFINE_IDR(isp_idr);
|
||||
static DEFINE_MUTEX(cooling_isp_lock);
|
||||
static BLOCKING_NOTIFIER_HEAD(isp_notifier);
|
||||
|
||||
static unsigned int isp_dev_count;
|
||||
|
||||
extern struct isp_fps_table isp_fps_table[];
|
||||
|
||||
/**
|
||||
* get_idr - function to get a unique id.
|
||||
* @idr: struct idr * handle used to create a id.
|
||||
* @id: int * value generated by this function.
|
||||
*
|
||||
* This function will populate @id with an unique
|
||||
* id, using the idr API.
|
||||
*
|
||||
* Return: 0 on success, an error code on failure.
|
||||
*/
|
||||
static int get_idr(struct idr *idr, int *id)
|
||||
{
|
||||
int ret;
|
||||
|
||||
mutex_lock(&cooling_isp_lock);
|
||||
ret = idr_alloc(idr, NULL, 0, 0, GFP_KERNEL);
|
||||
mutex_unlock(&cooling_isp_lock);
|
||||
if (unlikely(ret < 0))
|
||||
return ret;
|
||||
*id = ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* release_idr - function to free the unique id.
|
||||
* @idr: struct idr * handle used for creating the id.
|
||||
* @id: int value representing the unique id.
|
||||
*/
|
||||
static void release_idr(struct idr *idr, int id)
|
||||
{
|
||||
mutex_lock(&cooling_isp_lock);
|
||||
idr_remove(idr, id);
|
||||
mutex_unlock(&cooling_isp_lock);
|
||||
}
|
||||
|
||||
/* Below code defines functions to be used for isp as cooling device */
|
||||
|
||||
enum isp_cooling_property {
|
||||
GET_LEVEL,
|
||||
GET_FREQ,
|
||||
GET_MAXL,
|
||||
};
|
||||
|
||||
/**
|
||||
* get_property - fetch a property of interest for a give isp.
|
||||
* @isp: isp for which the property is required
|
||||
* @input: query parameter
|
||||
* @output: query return
|
||||
* @property: type of query (fps, level, max level)
|
||||
*
|
||||
* This is the common function to
|
||||
* 1. get maximum isp cooling states
|
||||
* 2. translate fps to cooling state
|
||||
* 3. translate cooling state to fps
|
||||
* Note that the code may be not in good shape
|
||||
* but it is written in this way in order to:
|
||||
* a) reduce duplicate code as most of the code can be shared.
|
||||
* b) make sure the logic is consistent when translating between
|
||||
* cooling states and fps.
|
||||
*
|
||||
* Return: 0 on success, -EINVAL when invalid parameters are passed.
|
||||
*/
|
||||
static int get_property(unsigned int isp, unsigned long input,
|
||||
unsigned int *output,
|
||||
enum isp_cooling_property property)
|
||||
{
|
||||
int i;
|
||||
unsigned long max_level = 0, level = 0;
|
||||
unsigned int fps = ISP_FPS_ENTRY_INVALID;
|
||||
int descend = -1;
|
||||
struct isp_fps_table *pos, *table =
|
||||
isp_fps_table;
|
||||
|
||||
if (!output)
|
||||
return -EINVAL;
|
||||
|
||||
if (!table)
|
||||
return -EINVAL;
|
||||
|
||||
isp_fps_for_each_valid_entry(pos, table) {
|
||||
/* ignore duplicate entry */
|
||||
if (fps == pos->fps)
|
||||
continue;
|
||||
|
||||
/* get the fps order */
|
||||
if (fps != ISP_FPS_ENTRY_INVALID && descend == -1)
|
||||
descend = fps > pos->fps;
|
||||
|
||||
fps = pos->fps;
|
||||
max_level++;
|
||||
}
|
||||
|
||||
/* No valid cpu fps entry */
|
||||
if (max_level == 0)
|
||||
return -EINVAL;
|
||||
|
||||
/* max_level is an index, not a counter */
|
||||
max_level--;
|
||||
|
||||
/* get max level */
|
||||
if (property == GET_MAXL) {
|
||||
*output = (unsigned int)max_level;
|
||||
return 0;
|
||||
}
|
||||
|
||||
i = 0;
|
||||
isp_fps_for_each_valid_entry(pos, table) {
|
||||
/* ignore duplicate entry */
|
||||
if (fps == pos->fps)
|
||||
continue;
|
||||
|
||||
/* now we have a valid fps entry */
|
||||
fps = pos->fps;
|
||||
|
||||
if (property == GET_LEVEL && (unsigned int)input == fps) {
|
||||
/* get level by fps */
|
||||
*output = descend ? i : (max_level - i);
|
||||
return 0;
|
||||
}
|
||||
if (property == GET_FREQ && level == i) {
|
||||
/* get fps by level */
|
||||
*output = fps;
|
||||
return 0;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/**
|
||||
* isp_cooling_get_level - for a give isp, return the cooling level.
|
||||
* @isp: isp for which the level is required
|
||||
* @fps: the fps of interest
|
||||
*
|
||||
* This function will match the cooling level corresponding to the
|
||||
* requested @fps and return it.
|
||||
*
|
||||
* Return: The matched cooling level on success or THERMAL_CSTATE_INVALID
|
||||
* otherwise.
|
||||
*/
|
||||
unsigned long isp_cooling_get_fps(unsigned int isp, unsigned int fps)
|
||||
{
|
||||
unsigned int val;
|
||||
|
||||
if (get_property(isp, (unsigned long)fps, &val, GET_LEVEL))
|
||||
return THERMAL_CSTATE_INVALID;
|
||||
|
||||
return (unsigned long)val;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(isp_cooling_get_fps);
|
||||
|
||||
/**
|
||||
* isp_apply_cooling - function to apply fps clipping.
|
||||
* @isp_device: isp_cooling_device pointer containing fps
|
||||
* clipping data.
|
||||
* @cooling_state: value of the cooling state.
|
||||
*
|
||||
* Function used to make sure the isp layer is aware of current thermal
|
||||
* limits. The limits are applied by updating the isp policy.
|
||||
*
|
||||
* Return: 0 on success, an error code otherwise (-EINVAL in case wrong
|
||||
* cooling state).
|
||||
*/
|
||||
static int isp_apply_cooling(struct isp_cooling_device *isp_device,
|
||||
unsigned long cooling_state)
|
||||
{
|
||||
/* Check if the old cooling action is same as new cooling action */
|
||||
if (isp_device->isp_state == cooling_state)
|
||||
return 0;
|
||||
|
||||
isp_device->isp_state = cooling_state;
|
||||
|
||||
blocking_notifier_call_chain(&isp_notifier, cooling_state, &cooling_state);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* isp cooling device callback functions are defined below */
|
||||
|
||||
/**
|
||||
* isp_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.
|
||||
*
|
||||
* Callback for the thermal cooling device to return the isp
|
||||
* max cooling state.
|
||||
*
|
||||
* Return: 0 on success, an error code otherwise.
|
||||
*/
|
||||
static int isp_get_max_state(struct thermal_cooling_device *cdev,
|
||||
unsigned long *state)
|
||||
{
|
||||
unsigned int count = 0;
|
||||
int ret;
|
||||
|
||||
ret = get_property(0, 0, &count, GET_MAXL);
|
||||
|
||||
if (count > 0)
|
||||
*state = count;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* isp_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.
|
||||
*
|
||||
* Callback for the thermal cooling device to return the isp
|
||||
* current cooling state.
|
||||
*
|
||||
* Return: 0 on success, an error code otherwise.
|
||||
*/
|
||||
static int isp_get_cur_state(struct thermal_cooling_device *cdev,
|
||||
unsigned long *state)
|
||||
{
|
||||
struct isp_cooling_device *isp_device = cdev->devdata;
|
||||
|
||||
*state = isp_device->isp_state;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* isp_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.
|
||||
*
|
||||
* Callback for the thermal cooling device to change the isp
|
||||
* current cooling state.
|
||||
*
|
||||
* Return: 0 on success, an error code otherwise.
|
||||
*/
|
||||
static int isp_set_cur_state(struct thermal_cooling_device *cdev,
|
||||
unsigned long state)
|
||||
{
|
||||
struct isp_cooling_device *isp_device = cdev->devdata;
|
||||
|
||||
return isp_apply_cooling(isp_device, state);
|
||||
}
|
||||
|
||||
static enum tmu_noti_state_t isp_tstate = ISP_COLD;
|
||||
|
||||
int isp_set_cur_temp(bool suspended, unsigned long temp)
|
||||
{
|
||||
enum tmu_noti_state_t tstate;
|
||||
|
||||
if (suspended || temp < EXYNOS_COLD_TEMP)
|
||||
tstate = ISP_COLD;
|
||||
else
|
||||
tstate = ISP_NORMAL;
|
||||
|
||||
if (isp_tstate == tstate)
|
||||
return 0;
|
||||
|
||||
isp_tstate = tstate;
|
||||
|
||||
blocking_notifier_call_chain(&isp_notifier, tstate, &tstate);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Bind isp callbacks to thermal cooling device ops */
|
||||
static struct thermal_cooling_device_ops const isp_cooling_ops = {
|
||||
.get_max_state = isp_get_max_state,
|
||||
.get_cur_state = isp_get_cur_state,
|
||||
.set_cur_state = isp_set_cur_state,
|
||||
};
|
||||
|
||||
|
||||
int exynos_tmu_isp_add_notifier(struct notifier_block *n)
|
||||
{
|
||||
return blocking_notifier_chain_register(&isp_notifier, n);
|
||||
}
|
||||
|
||||
/**
|
||||
* __isp_cooling_register - helper function to create isp cooling device
|
||||
* @np: a valid struct device_node to the cooling device device tree node
|
||||
* @clip_isp: ispmask of isp where the fps constraints will happen.
|
||||
*
|
||||
* This interface function registers the isp cooling device with the name
|
||||
* "thermal-isp-%x". This api can support multiple instances of isp
|
||||
* cooling devices. It also gives the opportunity to link the cooling device
|
||||
* with a device tree node, in order to bind it via the thermal DT code.
|
||||
*
|
||||
* Return: a valid struct thermal_cooling_device pointer on success,
|
||||
* on failure, it returns a corresponding ERR_PTR().
|
||||
*/
|
||||
static struct thermal_cooling_device *
|
||||
__isp_cooling_register(struct device_node *np,
|
||||
const struct cpumask *clip_isp)
|
||||
{
|
||||
struct thermal_cooling_device *cool_dev;
|
||||
struct isp_cooling_device *isp_dev = NULL;
|
||||
char dev_name[THERMAL_NAME_LENGTH];
|
||||
int ret = 0;
|
||||
|
||||
isp_dev = kzalloc(sizeof(struct isp_cooling_device),
|
||||
GFP_KERNEL);
|
||||
if (!isp_dev)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
ret = get_idr(&isp_idr, &isp_dev->id);
|
||||
if (ret) {
|
||||
kfree(isp_dev);
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
snprintf(dev_name, sizeof(dev_name), "thermal-isp-%d",
|
||||
isp_dev->id);
|
||||
|
||||
cool_dev = thermal_of_cooling_device_register(np, dev_name, isp_dev,
|
||||
&isp_cooling_ops);
|
||||
if (IS_ERR(cool_dev)) {
|
||||
release_idr(&isp_idr, isp_dev->id);
|
||||
kfree(isp_dev);
|
||||
return cool_dev;
|
||||
}
|
||||
isp_dev->cool_dev = cool_dev;
|
||||
isp_dev->isp_state = 0;
|
||||
mutex_lock(&cooling_isp_lock);
|
||||
|
||||
isp_dev_count++;
|
||||
|
||||
mutex_unlock(&cooling_isp_lock);
|
||||
|
||||
return cool_dev;
|
||||
}
|
||||
|
||||
/**
|
||||
* isp_cooling_register - function to create isp cooling device.
|
||||
* @clip_isp: cpumask of gpus where the fps constraints will happen.
|
||||
*
|
||||
* This interface function registers the isp cooling device with the name
|
||||
* "thermal-isp-%x". This api can support multiple instances of isp
|
||||
* cooling devices.
|
||||
*
|
||||
* Return: a valid struct thermal_cooling_device pointer on success,
|
||||
* on failure, it returns a corresponding ERR_PTR().
|
||||
*/
|
||||
struct thermal_cooling_device *
|
||||
isp_cooling_register(const struct cpumask *clip_isp)
|
||||
{
|
||||
return __isp_cooling_register(NULL, clip_isp);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(isp_cooling_register);
|
||||
|
||||
/**
|
||||
* of_isp_cooling_register - function to create isp cooling device.
|
||||
* @np: a valid struct device_node to the cooling device device tree node
|
||||
* @clip_isp: cpumask of gpus where the fps constraints will happen.
|
||||
*
|
||||
* This interface function registers the isp cooling device with the name
|
||||
* "thermal-isp-%x". This api can support multiple instances of isp
|
||||
* cooling devices. Using this API, the isp cooling device will be
|
||||
* linked to the device tree node provided.
|
||||
*
|
||||
* Return: a valid struct thermal_cooling_device pointer on success,
|
||||
* on failure, it returns a corresponding ERR_PTR().
|
||||
*/
|
||||
struct thermal_cooling_device *
|
||||
of_isp_cooling_register(struct device_node *np,
|
||||
const struct cpumask *clip_isp)
|
||||
{
|
||||
if (!np)
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
return __isp_cooling_register(np, clip_isp);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(of_isp_cooling_register);
|
||||
|
||||
/**
|
||||
* isp_cooling_unregister - function to remove isp cooling device.
|
||||
* @cdev: thermal cooling device pointer.
|
||||
*
|
||||
* This interface function unregisters the "thermal-isp-%x" cooling device.
|
||||
*/
|
||||
void isp_cooling_unregister(struct thermal_cooling_device *cdev)
|
||||
{
|
||||
struct isp_cooling_device *isp_dev;
|
||||
|
||||
if (!cdev)
|
||||
return;
|
||||
|
||||
isp_dev = cdev->devdata;
|
||||
mutex_lock(&cooling_isp_lock);
|
||||
isp_dev_count--;
|
||||
mutex_unlock(&cooling_isp_lock);
|
||||
|
||||
thermal_cooling_device_unregister(isp_dev->cool_dev);
|
||||
release_idr(&isp_idr, isp_dev->id);
|
||||
kfree(isp_dev);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(isp_cooling_unregister);
|
Loading…
Add table
Add a link
Reference in a new issue