blob: f5f6ca4f104b7d6c876651f79e8ad52236dbe912 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0-only
/*
* Thermal management support for GCIP devices.
*
* Copyright (C) 2023 Google LLC
*/
#include <linux/debugfs.h>
#include <linux/device.h>
#include <linux/minmax.h>
#include <linux/mutex.h>
#include <linux/notifier.h>
#include <linux/thermal.h>
#include <linux/version.h>
#include <gcip/gcip-config.h>
#include <gcip/gcip-pm.h>
#include <gcip/gcip-thermal.h>
#define OF_DATA_NUM_MAX (GCIP_THERMAL_MAX_NUM_STATES * 2)
#define to_cdev(dev) container_of(dev, struct thermal_cooling_device, device)
#define to_gcip_thermal(dev) ((struct gcip_thermal *)to_cdev(dev)->devdata)
/* Struct for state to rate and state to power mappings. */
struct gcip_rate_pwr {
unsigned long rate;
u32 power;
};
static struct gcip_rate_pwr state_map[GCIP_THERMAL_MAX_NUM_STATES] = { 0 };
static int gcip_thermal_get_max_state(struct thermal_cooling_device *cdev, unsigned long *state)
{
struct gcip_thermal *thermal = cdev->devdata;
if (!thermal->num_states)
return -ENODEV;
*state = thermal->num_states - 1;
return 0;
}
static int gcip_thermal_get_cur_state(struct thermal_cooling_device *cdev, unsigned long *state)
{
struct gcip_thermal *thermal = cdev->devdata;
mutex_lock(&thermal->lock);
*state = thermal->state;
mutex_unlock(&thermal->lock);
return 0;
}
static int gcip_thermal_set_cur_state(struct thermal_cooling_device *cdev, unsigned long state)
{
struct gcip_thermal *thermal = cdev->devdata;
int i, ret = 0;
if (state >= thermal->num_states) {
dev_err(thermal->dev, "Invalid thermal cooling state %lu\n", state);
return -EINVAL;
}
mutex_lock(&thermal->lock);
thermal->vote[GCIP_THERMAL_COOLING_DEVICE] = state;
for (i = 0; i < GCIP_THERMAL_MAX_NUM_VOTERS; i++)
state = max(state, thermal->vote[i]);
if (state == thermal->state)
goto out;
if (!gcip_pm_get_if_powered(thermal->pm, false)) {
ret = thermal->set_rate(thermal->data, state_map[state].rate);
gcip_pm_put(thermal->pm);
}
if (ret)
dev_err(thermal->dev, "Failed to set thermal cooling state: %d\n", ret);
else
thermal->state = state;
out:
mutex_unlock(&thermal->lock);
return ret;
}
static int gcip_thermal_rate2power_internal(struct gcip_thermal *thermal, unsigned long rate,
u32 *power)
{
int i;
for (i = 0; i < thermal->num_states; i++) {
if (rate == state_map[i].rate) {
*power = state_map[i].power;
return 0;
}
}
dev_err(thermal->dev, "Unknown rate for: %lu\n", rate);
*power = 0;
return -EINVAL;
}
static int gcip_thermal_get_requested_power(struct thermal_cooling_device *cdev, u32 *power)
{
struct gcip_thermal *thermal = cdev->devdata;
unsigned long rate;
int ret;
if (gcip_pm_get_if_powered(thermal->pm, false)) {
*power = 0;
return 0;
}
mutex_lock(&thermal->lock);
ret = thermal->get_rate(thermal->data, &rate);
mutex_unlock(&thermal->lock);
gcip_pm_put(thermal->pm);
if (ret)
return ret;
return gcip_thermal_rate2power_internal(thermal, rate, power);
}
static int gcip_thermal_state2power(struct thermal_cooling_device *cdev, unsigned long state,
u32 *power)
{
struct gcip_thermal *thermal = cdev->devdata;
if (state >= thermal->num_states) {
dev_err(thermal->dev, "Invalid state: %lu\n", state);
return -EINVAL;
}
return gcip_thermal_rate2power_internal(thermal, state_map[state].rate, power);
}
static int gcip_thermal_power2state(struct thermal_cooling_device *cdev, u32 power,
unsigned long *state)
{
struct gcip_thermal *thermal = cdev->devdata;
if (!thermal->num_states)
return -ENODEV;
/*
* Argument "power" is the maximum allowed power consumption in mW as defined by the PID
* control loop. Checks for the first state that is less than or equal to the current
* allowed power. state_map is descending, so lowest power consumption is last value in the
* array. Returns lowest state even if it consumes more power than allowed as not all
* platforms can handle throttling below an active state.
*/
for (*state = 0; *state < thermal->num_states; (*state)++)
if (power >= state_map[*state].power)
return 0;
*state = thermal->num_states - 1;
return 0;
}
static const struct thermal_cooling_device_ops gcip_thermal_ops = {
.get_max_state = gcip_thermal_get_max_state,
.get_cur_state = gcip_thermal_get_cur_state,
.set_cur_state = gcip_thermal_set_cur_state,
.get_requested_power = gcip_thermal_get_requested_power,
.state2power = gcip_thermal_state2power,
.power2state = gcip_thermal_power2state,
};
/* This API was removed, but Android still uses it to update thermal request. */
#if GCIP_IS_GKI
void thermal_cdev_update(struct thermal_cooling_device *cdev);
#endif
static void gcip_thermal_update(struct gcip_thermal *thermal)
{
struct thermal_cooling_device *cdev = thermal->cdev;
cdev->updated = false;
#if GCIP_IS_GKI
thermal_cdev_update(cdev);
#elif IS_ENABLED(CONFIG_THERMAL)
dev_err_once(thermal->dev, "Thermal update not implemented");
#endif
}
static ssize_t user_vote_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct gcip_thermal *thermal = to_gcip_thermal(dev);
ssize_t ret;
if (!thermal)
return -ENODEV;
mutex_lock(&thermal->lock);
ret = sysfs_emit(buf, "%lu\n", thermal->vote[GCIP_THERMAL_SYSFS]);
mutex_unlock(&thermal->lock);
return ret;
}
static ssize_t user_vote_store(struct device *dev, struct device_attribute *attr, const char *buf,
size_t count)
{
struct gcip_thermal *thermal = to_gcip_thermal(dev);
unsigned long state;
int ret;
if (!thermal)
return -ENODEV;
ret = kstrtoul(buf, 0, &state);
if (ret)
return ret;
if (state >= thermal->num_states)
return -EINVAL;
mutex_lock(&thermal->lock);
thermal->vote[GCIP_THERMAL_SYSFS] = state;
mutex_unlock(&thermal->lock);
gcip_thermal_update(thermal);
return count;
}
static DEVICE_ATTR_RW(user_vote);
static int gcip_thermal_rate2state(struct gcip_thermal *thermal, unsigned long rate)
{
int i;
for (i = 0; i < thermal->num_states; i++) {
if (state_map[i].rate <= rate)
return i;
}
/* Returns lowest state on an invalid input. */
return thermal->num_states - 1;
}
static int gcip_thermal_notifier(struct notifier_block *nb, unsigned long rate, void *nb_data)
{
struct gcip_thermal *thermal = container_of(nb, struct gcip_thermal, nb);
unsigned long state = gcip_thermal_rate2state(thermal, rate);
dev_dbg(thermal->dev, "Thermal notifier req original: %lu, state: %lu\n", rate, state);
mutex_lock(&thermal->lock);
thermal->vote[GCIP_THERMAL_NOTIFIER_BLOCK] = state;
mutex_unlock(&thermal->lock);
gcip_thermal_update(thermal);
return NOTIFY_OK;
}
struct notifier_block *gcip_thermal_get_notifier_block(struct gcip_thermal *thermal)
{
if (IS_ERR_OR_NULL(thermal))
return NULL;
return &thermal->nb;
}
static ssize_t state2power_table_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct gcip_thermal *thermal = to_gcip_thermal(dev);
ssize_t count = 0;
int i;
u32 power;
for (i = 0; i < thermal->num_states; i++) {
gcip_thermal_state2power(thermal->cdev, i, &power);
count += sysfs_emit_at(buf, count, "%u ", power);
}
count += sysfs_emit_at(buf, count, "\n");
return count;
}
static DEVICE_ATTR_RO(state2power_table);
void gcip_thermal_destroy(struct gcip_thermal *thermal)
{
if (IS_ERR_OR_NULL(thermal))
return;
debugfs_remove_recursive(thermal->dentry);
thermal_cooling_device_unregister(thermal->cdev);
devm_kfree(thermal->dev, thermal);
}
static int gcip_thermal_enable_get(void *data, u64 *val)
{
struct gcip_thermal *thermal = (struct gcip_thermal *)data;
mutex_lock(&thermal->lock);
*val = thermal->enabled;
mutex_unlock(&thermal->lock);
return 0;
}
static int gcip_thermal_enable_set(void *data, u64 val)
{
struct gcip_thermal *thermal = (struct gcip_thermal *)data;
int ret = 0;
mutex_lock(&thermal->lock);
if (thermal->enabled != (bool)val) {
/*
* If the device is not powered, the value will be restored by
* gcip_thermal_restore_on_powering in next fw boot.
*/
if (!gcip_pm_get_if_powered(thermal->pm, false)) {
ret = thermal->control(thermal->data, val);
gcip_pm_put(thermal->pm);
}
if (!ret) {
thermal->enabled = val;
dev_info_ratelimited(thermal->dev, "%s thermal control",
thermal->enabled ? "Enable" : "Disable");
} else {
dev_err(thermal->dev, "Failed to %s thermal control: %d ",
val ? "enable" : "disable", ret);
}
}
mutex_unlock(&thermal->lock);
return ret;
}
DEFINE_DEBUGFS_ATTRIBUTE(fops_gcip_thermal_enable, gcip_thermal_enable_get, gcip_thermal_enable_set,
"%llu\n");
static int gcip_thermal_parse_dvfs_table(struct gcip_thermal *thermal)
{
int row_size, col_size, tbl_size, i;
int of_data_int_array[OF_DATA_NUM_MAX];
if (of_property_read_u32_array(thermal->dev->of_node, GCIP_THERMAL_TABLE_SIZE_NAME,
of_data_int_array, 2))
goto error;
row_size = of_data_int_array[0];
col_size = of_data_int_array[1];
tbl_size = row_size * col_size;
if (row_size > GCIP_THERMAL_MAX_NUM_STATES) {
dev_err(thermal->dev, "Too many states\n");
goto error;
}
if (tbl_size > OF_DATA_NUM_MAX)
goto error;
if (of_property_read_u32_array(thermal->dev->of_node, GCIP_THERMAL_TABLE_NAME,
of_data_int_array, tbl_size))
goto error;
thermal->num_states = row_size;
for (i = 0; i < row_size; ++i) {
int idx = col_size * i;
state_map[i].rate = of_data_int_array[idx];
state_map[i].power = of_data_int_array[idx + 1];
}
return 0;
error:
dev_err(thermal->dev, "Failed to parse DVFS table\n");
return -EINVAL;
}
static int gcip_thermal_cooling_register(struct gcip_thermal *thermal, const char *type,
const char *node_name)
{
struct device_node *node = NULL;
int ret;
ret = gcip_thermal_parse_dvfs_table(thermal);
if (ret)
return ret;
if (node_name)
node = of_find_node_by_name(NULL, node_name);
if (!node)
dev_warn(thermal->dev, "Failed to find thermal cooling node\n");
thermal->cdev = thermal_of_cooling_device_register(node, type, thermal, &gcip_thermal_ops);
if (IS_ERR(thermal->cdev))
return PTR_ERR(thermal->cdev);
ret = device_create_file(&thermal->cdev->device, &dev_attr_user_vote);
if (ret)
thermal_cooling_device_unregister(thermal->cdev);
ret = device_create_file(&thermal->cdev->device, &dev_attr_state2power_table);
if (ret)
thermal_cooling_device_unregister(thermal->cdev);
return ret;
}
struct gcip_thermal *gcip_thermal_create(const struct gcip_thermal_args *args)
{
struct gcip_thermal *thermal;
int ret;
if (!args->dev || !args->get_rate || !args->set_rate || !args->control)
return ERR_PTR(-EINVAL);
thermal = devm_kzalloc(args->dev, sizeof(*thermal), GFP_KERNEL);
if (!thermal)
return ERR_PTR(-ENOMEM);
thermal->dev = args->dev;
thermal->nb.notifier_call = gcip_thermal_notifier;
thermal->pm = args->pm;
thermal->enabled = true;
thermal->data = args->data;
thermal->get_rate = args->get_rate;
thermal->set_rate = args->set_rate;
thermal->control = args->control;
mutex_init(&thermal->lock);
ret = gcip_thermal_cooling_register(thermal, args->type, args->node_name);
if (ret) {
dev_err(args->dev, "Failed to initialize external thermal cooling\n");
devm_kfree(args->dev, thermal);
return ERR_PTR(ret);
}
thermal->dentry = debugfs_create_dir("cooling", args->dentry);
/* Don't let debugfs creation failure abort the init procedure. */
if (IS_ERR_OR_NULL(thermal->dentry))
dev_warn(args->dev, "Failed to create debugfs for thermal cooling");
else
debugfs_create_file("enable", 0660, thermal->dentry, thermal,
&fops_gcip_thermal_enable);
return thermal;
}
int gcip_thermal_suspend_device(struct gcip_thermal *thermal)
{
int ret = 0;
if (IS_ERR_OR_NULL(thermal))
return 0;
mutex_lock(&thermal->lock);
/*
* Always sets as suspended even when the request cannot be handled for unknown reasons
* because we still want to prevent the client from using device.
*/
thermal->device_suspended = true;
if (!gcip_pm_get_if_powered(thermal->pm, false)) {
ret = thermal->set_rate(thermal->data, 0);
gcip_pm_put(thermal->pm);
}
mutex_unlock(&thermal->lock);
return ret;
}
int gcip_thermal_resume_device(struct gcip_thermal *thermal)
{
int ret = 0;
if (IS_ERR_OR_NULL(thermal))
return 0;
mutex_lock(&thermal->lock);
if (!gcip_pm_get_if_powered(thermal->pm, false)) {
ret = thermal->set_rate(thermal->data, state_map[thermal->state].rate);
gcip_pm_put(thermal->pm);
}
/*
* Unlike gcip_thermal_suspend_device(), only sets the device as resumed if the request is
* fulfilled.
*/
if (!ret)
thermal->device_suspended = false;
mutex_unlock(&thermal->lock);
return ret;
}
bool gcip_thermal_is_device_suspended(struct gcip_thermal *thermal)
{
if (IS_ERR_OR_NULL(thermal))
return false;
return thermal->device_suspended;
}
int gcip_thermal_restore_on_powering(struct gcip_thermal *thermal)
{
int ret = 0;
if (IS_ERR_OR_NULL(thermal))
return 0;
mutex_lock(&thermal->lock);
if (!thermal->enabled)
ret = thermal->control(thermal->data, thermal->enabled);
else if (thermal->device_suspended)
ret = thermal->set_rate(thermal->data, 0);
else if (thermal->state)
/* Skips state 0 since it's the default thermal state. */
ret = thermal->set_rate(thermal->data, state_map[thermal->state].rate);
mutex_unlock(&thermal->lock);
return ret;
}