diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile index 234edf5e..f68515f0 100644 --- a/drivers/thermal/Makefile +++ b/drivers/thermal/Makefile @@ -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 diff --git a/drivers/thermal/thermal-trip-event.c b/drivers/thermal/thermal-trip-event.c new file mode 100644 index 00000000..fd3f5006 --- /dev/null +++ b/drivers/thermal/thermal-trip-event.c @@ -0,0 +1,257 @@ +// SPDX-License-Identifier: GPL-2.0-only +// SPDX-FileCopyrightText: Copyright (c) 2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + +#include +#include +#include +#include +#include + +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 "); +MODULE_AUTHOR("Yi-Wei Wang "); +MODULE_DESCRIPTION("Thermal Trip Event Driver"); +MODULE_LICENSE("GPL v2");