kernel_samsung_a53x/drivers/thermal/samsung/dev_cooling.c
2024-06-15 16:02:09 -03:00

308 lines
9 KiB
C
Executable file

/*
* Copyright (C) 2019 Samsung Electronics Co., Ltd(http://www.samsung.com)
* Copyright (C) 2019 Hanjun Shin <hanjun.shin@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; 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 <soc/samsung/dev_cooling.h>
#include <soc/samsung/debug-snapshot.h>
#include <soc/samsung/exynos_pm_qos.h>
#include <soc/samsung/ect_parser.h>
#include "../thermal_core.h"
/**
* struct dev_cooling_device - data for cooling device with dev
* @id: unique integer value corresponding to each dev_cooling_device
* registered.
* @cool_dev: thermal_cooling_device pointer to keep track of the
* registered cooling device.
* @dev_state: integer value representing the current state of dev
* cooling devices.
* @dev_val: integer value representing the absolute value of the clipped
* freq.
* @freq_table: frequency table to convert level to freq
* @thermal_pm_qos_max: pm qos node to throttle max freq
*
* This structure is required for keeping information of each
* dev_cooling_device registered. In order to prevent corruption of this a
* mutex lock cooling_dev_lock is used.
*/
struct dev_cooling_device {
int id;
struct thermal_cooling_device *cool_dev;
unsigned int dev_state;
unsigned int dev_val;
u32 max_state;
struct exynos_devfreq_opp_table *freq_table;
struct exynos_pm_qos_request thermal_pm_qos_max;
};
static DEFINE_MUTEX(cooling_dev_lock);
static unsigned int dev_count;
/* dev cooling device callback functions are defined below */
/**
* dev_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 dev
* max cooling state.
*
* Return: 0 on success, an error code otherwise.
*/
static int dev_get_max_state(struct thermal_cooling_device *cdev,
unsigned long *state)
{
struct thermal_cooling_device *cdev_tmp =
(struct thermal_cooling_device *)((unsigned long long)cdev & ~0x1ull);
struct dev_cooling_device *dev = cdev_tmp->devdata;
*state = dev->max_state;
return 0;
}
/**
* dev_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 dev
* current cooling state.
*
* Return: 0 on success, an error code otherwise.
*/
static int dev_get_cur_state(struct thermal_cooling_device *cdev,
unsigned long *state)
{
struct dev_cooling_device *dev = cdev->devdata;
*state = dev->dev_state;
return 0;
}
/**
* dev_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 dev
* current cooling state.
*
* Return: 0 on success, an error code otherwise.
*/
static int dev_set_cur_state(struct thermal_cooling_device *cdev,
unsigned long state)
{
struct dev_cooling_device *dev = cdev->devdata;
if (dev->dev_state == state)
return 0;
dev->dev_state = (unsigned int)state;
dev->dev_val = dev->freq_table[state].freq;
dbg_snapshot_thermal(NULL, 0, cdev->type, dev->dev_val);
exynos_pm_qos_update_request(&dev->thermal_pm_qos_max, dev->dev_val);
return 0;
}
/* Bind dev callbacks to thermal cooling device ops */
static struct thermal_cooling_device_ops const dev_cooling_ops = {
.get_max_state = dev_get_max_state,
.get_cur_state = dev_get_cur_state,
.set_cur_state = dev_set_cur_state,
};
static int dev_cooling_get_level(struct thermal_cooling_device *cdev,
unsigned long value)
{
struct dev_cooling_device *dev = cdev->devdata;
int i, level = dev->max_state - 1;
for (i = 0; i < dev->max_state; i++) {
if (dev->freq_table[i].freq <= value) {
level = i;
break;
}
}
return level;
}
static int parse_ect_cooling_level(struct thermal_cooling_device *cdev,
char *tz_name)
{
struct thermal_instance *instance;
struct thermal_zone_device *tz;
bool foundtz = false;
void *thermal_block;
struct ect_ap_thermal_function *function;
int i, temperature;
unsigned int freq;
mutex_lock(&cdev->lock);
list_for_each_entry(instance, &cdev->thermal_instances, cdev_node) {
tz = instance->tz;
if (!strncasecmp(tz_name, tz->type, THERMAL_NAME_LENGTH)) {
foundtz = true;
break;
}
}
mutex_unlock(&cdev->lock);
if (!foundtz)
goto skip_ect;
thermal_block = ect_get_block(BLOCK_AP_THERMAL);
if (!thermal_block)
goto skip_ect;
function = ect_ap_thermal_get_function(thermal_block, tz_name);
if (!function)
goto skip_ect;
for (i = 0; i < function->num_of_range; ++i) {
unsigned long max_level = 0;
int level;
temperature = function->range_list[i].lower_bound_temperature;
freq = function->range_list[i].max_frequency;
instance = get_thermal_instance(tz, cdev, i);
if (!instance) {
pr_err("%s: (%s, %d)instance isn't valid\n", __func__, tz_name, i);
goto skip_ect;
}
cdev->ops->get_max_state(cdev, &max_level);
level = dev_cooling_get_level(cdev, freq);
if (level == THERMAL_CSTATE_INVALID)
level = max_level;
instance->upper = level;
pr_info("Parsed From ECT : %s: [%d] Temperature : %d, frequency : %u, level: %d\n",
tz_name, i, temperature, freq, level);
}
skip_ect:
return 0;
}
/**
* __dev_cooling_register - helper function to create dev cooling device
* @np: a valid struct device_node to the cooling device device tree node
* @data: exynos_devfreq_data structure for using devfreq
*
* This interface function registers the dev cooling device with the name
* "thermal-dev-%x". This api can support multiple instances of dev
* 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 *
__dev_cooling_register(struct device_node *np, struct exynos_devfreq_data *data)
{
struct thermal_cooling_device *cool_dev;
struct dev_cooling_device *dev = NULL;
char dev_name[THERMAL_NAME_LENGTH];
char cooling_name[THERMAL_NAME_LENGTH];
const char *name;
dev = kzalloc(sizeof(struct dev_cooling_device), GFP_KERNEL);
if (!dev)
return ERR_PTR(-ENOMEM);
if (of_property_read_string(np, "tz-cooling-name", &name)) {
pr_err("%s: could not find tz-cooling-name\n", __func__);
kfree(dev);
return ERR_PTR(-EINVAL);
}
strncpy(cooling_name, name, sizeof(cooling_name));
mutex_lock(&cooling_dev_lock);
dev->id = dev_count;
dev_count++;
mutex_unlock(&cooling_dev_lock);
snprintf(dev_name, sizeof(dev_name), "thermal-dev-%d", dev->id);
dev->dev_state = 0;
dev->freq_table = data->opp_list;
dev->max_state = data->max_state;
exynos_pm_qos_add_request(&dev->thermal_pm_qos_max, (int)data->pm_qos_class_max, data->max_freq);
cool_dev = thermal_of_cooling_device_register(np, dev_name, dev,
&dev_cooling_ops);
if (IS_ERR(cool_dev)) {
kfree(dev);
return cool_dev;
}
parse_ect_cooling_level(cool_dev, cooling_name);
dev->cool_dev = cool_dev;
return cool_dev;
}
/**
* dev_cooling_unregister - function to remove dev cooling device.
* @cdev: thermal cooling device pointer.
*
* This interface function unregisters the "thermal-dev-%x" cooling device.
*/
void dev_cooling_unregister(struct thermal_cooling_device *cdev)
{
struct dev_cooling_device *dev;
if (!cdev)
return;
dev = cdev->devdata;
mutex_lock(&cooling_dev_lock);
dev_count--;
mutex_unlock(&cooling_dev_lock);
thermal_cooling_device_unregister(dev->cool_dev);
kfree(dev);
}
EXPORT_SYMBOL_GPL(dev_cooling_unregister);
/**
* exynos_dev_cooling_register - function to remove dev cooling device.
* @np: a valid struct device_node to the cooling device device tree node
* @data: exynos_devfreq_data structure for using devfreq
*
* This interface function registers the exynos devfreq cooling device.
*/
struct thermal_cooling_device *exynos_dev_cooling_register(struct device_node *np, struct exynos_devfreq_data *data)
{
return __dev_cooling_register(np, data);
}
EXPORT_SYMBOL_GPL(exynos_dev_cooling_register);