thermal: add thermal trip event cooling device

This change adds a cooling device driver to notify the user space of the
thermal trip event. To avoid having user space process poll the
cooling state, a sysfs node is exposed that supports blocking reads.
The driver also supports a timeout (in milliseconds) for blocking reads
which can be done by writing the value to thermal_trip_event_block node
before reading. The blocked user space process will be woken up when the
cooling device becomes active or times out.

Bug 4261645
Bug 1688327

Signed-off-by: Yi-Wei Wang <yiweiw@nvidia.com>
Change-Id: Ic89406ba2713e5bc8f3806d6cfeb462601c73a7d
Reviewed-on: https://git-master.nvidia.com/r/c/linux-nv-oot/+/3015652
(cherry picked from commit 856471d64f)
Reviewed-on: https://git-master.nvidia.com/r/c/linux-nv-oot/+/3019439
Reviewed-by: Laxman Dewangan <ldewangan@nvidia.com>
GVS: Gerrit_Virtual_Submit <buildbot_gerritrpt@nvidia.com>
This commit is contained in:
Yi-Wei Wang
2023-11-14 14:00:33 +08:00
committed by mobile promotions
parent 5615d9f984
commit 1c865fdba3
2 changed files with 258 additions and 0 deletions

View File

@@ -4,5 +4,6 @@
ifeq ($(findstring ack_src,$(NV_BUILD_KERNEL_OPTIONS)),)
obj-m += pex9749-thermal.o
obj-m += tegra234-oc-event.o
obj-m += thermal-trip-event.o
endif
obj-m += max77851_thermal.o

View File

@@ -0,0 +1,257 @@
// SPDX-License-Identifier: GPL-2.0-only
// SPDX-FileCopyrightText: Copyright (c) 2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/thermal.h>
#include <linux/wait.h>
enum cdev_states {
CDEV_INACTIVE,
CDEV_ACTIVE,
CDEV_DESTROY,
};
struct therm_trip_event {
unsigned int cur_state;
unsigned int max_state;
unsigned int event_timeout_ms;
struct mutex cur_state_lock;
struct mutex event_timeout_lock;
struct thermal_cooling_device *cdev;
wait_queue_head_t waitq_head;
};
static int tte_cdev_get_max_state(struct thermal_cooling_device *tcd,
unsigned long *state)
{
struct therm_trip_event *tte = tcd->devdata;
*state = tte->max_state;
return 0;
}
static int tte_cdev_get_cur_state(struct thermal_cooling_device *tcd,
unsigned long *state)
{
struct therm_trip_event *tte = tcd->devdata;
*state = tte->cur_state;
return 0;
}
static int tte_cdev_set_cur_state(struct thermal_cooling_device *tcd,
unsigned long state)
{
struct therm_trip_event *tte = tcd->devdata;
struct device *dev = &tcd->device;
if (state > tte->max_state)
return -EINVAL;
if (state == tte->cur_state)
return 0;
dev_notice(dev, "%s cooling state: %u -> %lu\n", tcd->type,
tte->cur_state, state);
/*
* Although tcd->lock is already held in the thermal framework, a lock
* is added here to avoid a race condition between set_cur_state() and
* driver removal.
*/
mutex_lock(&tte->cur_state_lock);
tte->cur_state = state;
mutex_unlock(&tte->cur_state_lock);
if (tte->cur_state != CDEV_INACTIVE) {
if (wq_has_sleeper(&tte->waitq_head)) {
wake_up_interruptible_all(&tte->waitq_head);
dev_dbg(dev, "THERMAL_EVENT_TRIPPED!\n");
}
}
return 0;
}
static ssize_t thermal_trip_event_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct therm_trip_event *tte = dev_get_drvdata(dev);
return sprintf(buf, "%d\n", tte->cur_state);
}
static ssize_t thermal_trip_event_block_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct therm_trip_event *tte = dev_get_drvdata(dev);
unsigned int event_timeout_ms = tte->event_timeout_ms;
int ret;
if (event_timeout_ms > 0)
ret = wait_event_interruptible_timeout(
tte->waitq_head, tte->cur_state != CDEV_INACTIVE,
msecs_to_jiffies(event_timeout_ms));
else
ret = wait_event_interruptible(tte->waitq_head,
tte->cur_state != CDEV_INACTIVE);
/*
* -ERESTARTSYS is returned to avoid false alarm and to resume the call.
*/
if (ret < 0)
return ret;
return sprintf(buf, "%d\n", tte->cur_state);
}
static ssize_t thermal_trip_event_block_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
unsigned int val;
struct therm_trip_event *tte = dev_get_drvdata(dev);
if (kstrtouint(buf, 0, &val))
return -EINVAL;
mutex_lock(&tte->event_timeout_lock);
tte->event_timeout_ms = val;
mutex_unlock(&tte->event_timeout_lock);
return count;
}
static struct thermal_cooling_device_ops tte_cdev_ops = {
.get_max_state = tte_cdev_get_max_state,
.get_cur_state = tte_cdev_get_cur_state,
.set_cur_state = tte_cdev_set_cur_state,
};
static DEVICE_ATTR_RO(thermal_trip_event);
static DEVICE_ATTR_RW(thermal_trip_event_block);
static const struct attribute *tte_cdev_attr[] = {
&dev_attr_thermal_trip_event.attr,
&dev_attr_thermal_trip_event_block.attr,
NULL,
};
static int thermal_trip_event_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
struct therm_trip_event *tte;
const char *cdev_type;
int ret;
tte = devm_kzalloc(dev, sizeof(struct therm_trip_event), GFP_KERNEL);
if (!tte)
return -ENOMEM;
if (of_property_read_string(np, "cdev-type", &cdev_type) != 0) {
dev_err(dev, "invalid cdev-type property of %pOFn\n", np);
return -EINVAL;
}
tte->cur_state = CDEV_INACTIVE;
tte->max_state = CDEV_DESTROY;
mutex_init(&tte->cur_state_lock);
mutex_init(&tte->event_timeout_lock);
init_waitqueue_head(&tte->waitq_head);
dev_set_drvdata(dev, tte);
tte->cdev = thermal_of_cooling_device_register(np, cdev_type, tte,
&tte_cdev_ops);
if (IS_ERR(tte->cdev)) {
ret = PTR_ERR(tte->cdev);
goto destroy_lock;
}
ret = sysfs_create_files(&dev->kobj, tte_cdev_attr);
if (ret) {
dev_err(dev, "failed to create sysfs files\n");
goto free_cdev;
}
ret = sysfs_create_link(&tte->cdev->device.kobj, &dev->kobj,
"thermal_trip_event");
if (ret) {
dev_err(dev, "failed to create sysfs symlink\n");
goto free_sysfs_files;
}
dev_info(dev, "cooling device registered.\n");
return 0;
free_sysfs_files:
sysfs_remove_files(&dev->kobj, tte_cdev_attr);
free_cdev:
thermal_cooling_device_unregister(tte->cdev);
destroy_lock:
mutex_destroy(&tte->event_timeout_lock);
mutex_destroy(&tte->cur_state_lock);
return ret;
}
static int thermal_trip_event_remove(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct therm_trip_event *tte = dev_get_drvdata(dev);
struct thermal_cooling_device *cdev = tte->cdev;
/*
* There could be processes waiting for the event to happen while the
* driver is being removed. To have smooth removal, wake them up by
* updating the cur_state. The cooling state change here is not
* perceived by the thermal framework, but it's not a big deal as the
* cooling device is going to be destroyed soon.
*/
mutex_lock(&tte->cur_state_lock);
tte->cur_state = CDEV_DESTROY;
mutex_unlock(&tte->cur_state_lock);
if (wq_has_sleeper(&tte->waitq_head)) {
wake_up_interruptible_all(&tte->waitq_head);
dev_dbg(dev, "THERMAL_EVENT_DESTROYED!\n");
}
sysfs_remove_link(&cdev->device.kobj, "thermal_trip_event");
sysfs_remove_files(&dev->kobj, tte_cdev_attr);
thermal_cooling_device_unregister(cdev);
mutex_destroy(&tte->event_timeout_lock);
mutex_destroy(&tte->cur_state_lock);
return 0;
}
static const struct of_device_id thermal_trip_event_of_match[] = {
{
.compatible = "thermal-trip-event",
},
{},
};
MODULE_DEVICE_TABLE(of, thermal_trip_event_of_match);
static struct platform_driver thermal_trip_event_driver = {
.driver = {
.name = "thermal-trip-event",
.owner = THIS_MODULE,
.of_match_table = thermal_trip_event_of_match,
},
.probe = thermal_trip_event_probe,
.remove = thermal_trip_event_remove,
};
module_platform_driver(thermal_trip_event_driver);
MODULE_AUTHOR("Sreenivasulu Velpula <svelpula@nvidia.com>");
MODULE_AUTHOR("Yi-Wei Wang <yiweiw@nvidia.com>");
MODULE_DESCRIPTION("Thermal Trip Event Driver");
MODULE_LICENSE("GPL v2");