blob: 579e403d0f6cb220773a7b6b2b1525e40d6229b9 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2018 Google, Inc.
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* 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.
*/
#include "airbrush-cooling.h"
#include <linux/debugfs.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/thermal.h>
static struct dentry *ab_cooling_droot;
struct ab_cooling {
struct thermal_cooling_device *cdev;
struct ab_cooling_ops ops;
void *op_data;
struct mutex lock;
bool enable;
unsigned long state;
struct dentry *droot;
};
static unsigned long ab_cooling_get_state_nolock(struct ab_cooling *cooling)
{
return cooling->enable ? cooling->state : 0;
}
static int ab_cooling_op_get_max_state(struct thermal_cooling_device *cdev,
unsigned long *state)
{
*state = AIRBRUSH_COOLING_STATE_MAX;
return 0;
}
static int ab_cooling_op_get_cur_state(struct thermal_cooling_device *cdev,
unsigned long *state)
{
struct ab_cooling *cooling = cdev->devdata;
mutex_lock(&cooling->lock);
*state = ab_cooling_get_state_nolock(cooling);
mutex_unlock(&cooling->lock);
return 0;
}
static int ab_cooling_op_set_cur_state(struct thermal_cooling_device *cdev,
unsigned long state)
{
struct ab_cooling *cooling = cdev->devdata;
unsigned long old_state, new_state;
mutex_lock(&cooling->lock);
old_state = ab_cooling_get_state_nolock(cooling);
cooling->state = state;
new_state = ab_cooling_get_state_nolock(cooling);
if (new_state != old_state) {
cooling->ops.state_updated(cooling, old_state, state,
cooling->op_data);
}
mutex_unlock(&cooling->lock);
return 0;
}
static const struct thermal_cooling_device_ops ab_cooling_ops = {
.get_max_state = ab_cooling_op_get_max_state,
.get_cur_state = ab_cooling_op_get_cur_state,
.set_cur_state = ab_cooling_op_set_cur_state,
};
static int ab_cooling_enable_get(void *data, u64 *val)
{
struct ab_cooling *cooling = data;
mutex_lock(&cooling->lock);
*val = cooling->enable;
mutex_unlock(&cooling->lock);
return 0;
}
static int ab_cooling_enable_set(void *data, u64 val)
{
struct ab_cooling *cooling = data;
unsigned long old_state, new_state;
mutex_lock(&cooling->lock);
old_state = ab_cooling_get_state_nolock(cooling);
cooling->enable = val;
new_state = ab_cooling_get_state_nolock(cooling);
if (new_state != old_state) {
cooling->ops.state_updated(cooling, old_state, new_state,
cooling->op_data);
}
mutex_unlock(&cooling->lock);
return 0;
}
DEFINE_DEBUGFS_ATTRIBUTE(fops_ab_cooling_enable, ab_cooling_enable_get,
ab_cooling_enable_set, "%llu\n");
static void ab_cooling_create_debugfs(struct ab_cooling *cooling)
{
struct dentry *d;
if (!ab_cooling_droot) {
ab_cooling_droot = debugfs_create_dir("airbrush_cooling", NULL);
if (!ab_cooling_droot)
ab_cooling_droot = ERR_PTR(-ENOENT);
}
if (IS_ERR(ab_cooling_droot))
goto err_out;
cooling->droot = debugfs_create_dir(cooling->cdev->type,
ab_cooling_droot);
if (!cooling->droot)
goto err_out;
d = debugfs_create_file("enable", 0666, cooling->droot, cooling,
&fops_ab_cooling_enable);
if (!d)
goto err_out;
return;
err_out:
dev_warn(&cooling->cdev->device,
"Some error occurred while creating debugfs entry for airbrush cooling %s",
cooling->cdev->type);
}
static void ab_cooling_remove_debugfs(struct ab_cooling *cooling)
{
debugfs_remove_recursive(cooling->droot);
}
struct ab_cooling *ab_cooling_register(struct device_node *np, char *type,
const struct ab_cooling_ops *ops, void *op_data, bool enable)
{
struct ab_cooling *cooling;
int err;
cooling = kzalloc(sizeof(struct ab_cooling), GFP_KERNEL);
if (!cooling)
return ERR_PTR(-ENOMEM);
cooling->ops = *ops;
cooling->op_data = op_data;
mutex_init(&cooling->lock);
cooling->enable = enable;
cooling->state = 0;
cooling->cdev = thermal_of_cooling_device_register(np, type,
cooling, &ab_cooling_ops);
if (IS_ERR(cooling->cdev)) {
err = PTR_ERR(cooling->cdev);
kfree(cooling);
return ERR_PTR(err);
}
ab_cooling_create_debugfs(cooling);
return cooling;
}
void ab_cooling_unregister(struct ab_cooling *cooling)
{
ab_cooling_remove_debugfs(cooling);
thermal_cooling_device_unregister(cooling->cdev);
kfree(cooling);
}