| From 97abcfc5219149ff7d4883b295c80257f0315b5e Mon Sep 17 00:00:00 2001 |
| From: Anson Huang <Anson.Huang@nxp.com> |
| Date: Wed, 7 Aug 2019 08:40:59 +0800 |
| Subject: [PATCH] thermal: Add generic device cooling support |
| |
| To compatible with previous implementation, add generic device |
| cooling support, each thermal zone will register a cooling |
| device, and when temperature exceed passive trip, the device |
| cooling driver will send out a system wide notification, each |
| device supporting cooling will need to register device cooling |
| and takes action when passive trip is exceeded; |
| |
| Signed-off-by: Anson Huang <Anson.Huang@nxp.com> |
| [rebase] |
| Signed-off-by: Yangbo Lu <yangbo.lu@nxp.com> |
| --- |
| drivers/thermal/Kconfig | 7 ++ |
| drivers/thermal/Makefile | 1 + |
| drivers/thermal/device_cooling.c | 152 +++++++++++++++++++++++++++++++++++++++ |
| include/linux/device_cooling.h | 45 ++++++++++++ |
| 4 files changed, 205 insertions(+) |
| create mode 100644 drivers/thermal/device_cooling.c |
| create mode 100644 include/linux/device_cooling.h |
| |
| --- a/drivers/thermal/Kconfig |
| +++ b/drivers/thermal/Kconfig |
| @@ -233,6 +233,13 @@ config IMX_THERMAL |
| cpufreq is used as the cooling device to throttle CPUs when the |
| passive trip is crossed. |
| |
| +config DEVICE_THERMAL |
| + tristate "generic device cooling support" |
| + help |
| + Support for device cooling. |
| + It supports notification of crossing passive trip for devices, |
| + devices need to do their own actions to cool down the SOC. |
| + |
| config MAX77620_THERMAL |
| tristate "Temperature sensor driver for Maxim MAX77620 PMIC" |
| depends on MFD_MAX77620 |
| --- a/drivers/thermal/Makefile |
| +++ b/drivers/thermal/Makefile |
| @@ -41,6 +41,7 @@ obj-$(CONFIG_DB8500_THERMAL) += db8500_t |
| obj-$(CONFIG_ARMADA_THERMAL) += armada_thermal.o |
| obj-$(CONFIG_TANGO_THERMAL) += tango_thermal.o |
| obj-$(CONFIG_IMX_THERMAL) += imx_thermal.o |
| +obj-$(CONFIG_DEVICE_THERMAL) += device_cooling.o |
| obj-$(CONFIG_MAX77620_THERMAL) += max77620_thermal.o |
| obj-$(CONFIG_QORIQ_THERMAL) += qoriq_thermal.o |
| obj-$(CONFIG_DA9062_THERMAL) += da9062-thermal.o |
| --- /dev/null |
| +++ b/drivers/thermal/device_cooling.c |
| @@ -0,0 +1,152 @@ |
| +/* |
| + * Copyright (C) 2013-2015 Freescale Semiconductor, Inc. |
| + * |
| + * This program is free software; you can redistribute it and/or modify |
| + * it under the terms of the GNU General Public License version 2 as |
| + * published by the Free Software Foundation. |
| + * |
| + */ |
| + |
| +#include <linux/module.h> |
| +#include <linux/thermal.h> |
| +#include <linux/err.h> |
| +#include <linux/slab.h> |
| + |
| +struct devfreq_cooling_device { |
| + int id; |
| + struct thermal_cooling_device *cool_dev; |
| + unsigned int devfreq_state; |
| +}; |
| + |
| +static DEFINE_IDR(devfreq_idr); |
| +static DEFINE_MUTEX(devfreq_cooling_lock); |
| + |
| +#define MAX_STATE 1 |
| + |
| +static BLOCKING_NOTIFIER_HEAD(devfreq_cooling_chain_head); |
| + |
| +int register_devfreq_cooling_notifier(struct notifier_block *nb) |
| +{ |
| + return blocking_notifier_chain_register( |
| + &devfreq_cooling_chain_head, nb); |
| +} |
| +EXPORT_SYMBOL_GPL(register_devfreq_cooling_notifier); |
| + |
| +int unregister_devfreq_cooling_notifier(struct notifier_block *nb) |
| +{ |
| + return blocking_notifier_chain_unregister( |
| + &devfreq_cooling_chain_head, nb); |
| +} |
| +EXPORT_SYMBOL_GPL(unregister_devfreq_cooling_notifier); |
| + |
| +static int devfreq_cooling_notifier_call_chain(unsigned long val) |
| +{ |
| + return (blocking_notifier_call_chain( |
| + &devfreq_cooling_chain_head, val, NULL) |
| + == NOTIFY_BAD) ? -EINVAL : 0; |
| +} |
| + |
| +static int devfreq_set_cur_state(struct thermal_cooling_device *cdev, |
| + unsigned long state) |
| +{ |
| + struct devfreq_cooling_device *devfreq_device = cdev->devdata; |
| + int ret; |
| + |
| + ret = devfreq_cooling_notifier_call_chain(state); |
| + if (ret) |
| + return -EINVAL; |
| + |
| + devfreq_device->devfreq_state = state; |
| + |
| + return 0; |
| +} |
| + |
| +static int devfreq_get_max_state(struct thermal_cooling_device *cdev, |
| + unsigned long *state) |
| +{ |
| + *state = MAX_STATE; |
| + |
| + return 0; |
| +} |
| + |
| +static int devfreq_get_cur_state(struct thermal_cooling_device *cdev, |
| + unsigned long *state) |
| +{ |
| + struct devfreq_cooling_device *devfreq_device = cdev->devdata; |
| + |
| + *state = devfreq_device->devfreq_state; |
| + |
| + return 0; |
| +} |
| + |
| +static struct thermal_cooling_device_ops const devfreq_cooling_ops = { |
| + .get_max_state = devfreq_get_max_state, |
| + .get_cur_state = devfreq_get_cur_state, |
| + .set_cur_state = devfreq_set_cur_state, |
| +}; |
| + |
| +static int get_idr(struct idr *idr, int *id) |
| +{ |
| + int ret; |
| + |
| + mutex_lock(&devfreq_cooling_lock); |
| + ret = idr_alloc(idr, NULL, 0, 0, GFP_KERNEL); |
| + mutex_unlock(&devfreq_cooling_lock); |
| + if (unlikely(ret < 0)) |
| + return ret; |
| + *id = ret; |
| + |
| + return 0; |
| +} |
| + |
| +static void release_idr(struct idr *idr, int id) |
| +{ |
| + mutex_lock(&devfreq_cooling_lock); |
| + idr_remove(idr, id); |
| + mutex_unlock(&devfreq_cooling_lock); |
| +} |
| + |
| +struct thermal_cooling_device *devfreq_cooling_register(void) |
| +{ |
| + struct thermal_cooling_device *cool_dev; |
| + struct devfreq_cooling_device *devfreq_dev = NULL; |
| + char dev_name[THERMAL_NAME_LENGTH]; |
| + int ret = 0; |
| + |
| + devfreq_dev = kzalloc(sizeof(struct devfreq_cooling_device), |
| + GFP_KERNEL); |
| + if (!devfreq_dev) |
| + return ERR_PTR(-ENOMEM); |
| + |
| + ret = get_idr(&devfreq_idr, &devfreq_dev->id); |
| + if (ret) { |
| + kfree(devfreq_dev); |
| + return ERR_PTR(-EINVAL); |
| + } |
| + |
| + snprintf(dev_name, sizeof(dev_name), "thermal-devfreq-%d", |
| + devfreq_dev->id); |
| + |
| + cool_dev = thermal_cooling_device_register(dev_name, devfreq_dev, |
| + &devfreq_cooling_ops); |
| + if (!cool_dev) { |
| + release_idr(&devfreq_idr, devfreq_dev->id); |
| + kfree(devfreq_dev); |
| + return ERR_PTR(-EINVAL); |
| + } |
| + devfreq_dev->cool_dev = cool_dev; |
| + devfreq_dev->devfreq_state = 0; |
| + |
| + return cool_dev; |
| +} |
| +EXPORT_SYMBOL_GPL(devfreq_cooling_register); |
| + |
| +void devfreq_cooling_unregister(struct thermal_cooling_device *cdev) |
| +{ |
| + struct devfreq_cooling_device *devfreq_dev = cdev->devdata; |
| + |
| + thermal_cooling_device_unregister(devfreq_dev->cool_dev); |
| + release_idr(&devfreq_idr, devfreq_dev->id); |
| + kfree(devfreq_dev); |
| +} |
| +EXPORT_SYMBOL_GPL(devfreq_cooling_unregister); |
| --- /dev/null |
| +++ b/include/linux/device_cooling.h |
| @@ -0,0 +1,45 @@ |
| +/* |
| + * Copyright (C) 2013-2015 Freescale Semiconductor, Inc. |
| + * |
| + * This program is free software; you can redistribute it and/or modify |
| + * it under the terms of the GNU General Public License version 2 as |
| + * published by the Free Software Foundation. |
| + * |
| + */ |
| + |
| +#ifndef __DEVICE_THERMAL_H__ |
| +#define __DEVICE_THERMAL_H__ |
| + |
| +#include <linux/thermal.h> |
| + |
| +#ifdef CONFIG_DEVICE_THERMAL |
| +int register_devfreq_cooling_notifier(struct notifier_block *nb); |
| +int unregister_devfreq_cooling_notifier(struct notifier_block *nb); |
| +struct thermal_cooling_device *devfreq_cooling_register(void); |
| +void devfreq_cooling_unregister(struct thermal_cooling_device *cdev); |
| +#else |
| +static inline |
| +int register_devfreq_cooling_notifier(struct notifier_block *nb) |
| +{ |
| + return 0; |
| +} |
| + |
| +static inline |
| +int unregister_devfreq_cooling_notifier(struct notifier_block *nb) |
| +{ |
| + return 0; |
| +} |
| + |
| +static inline |
| +struct thermal_cooling_device *devfreq_cooling_register(void) |
| +{ |
| + return NULL; |
| +} |
| + |
| +static inline |
| +void devfreq_cooling_unregister(struct thermal_cooling_device *cdev) |
| +{ |
| + return; |
| +} |
| +#endif |
| +#endif /* __DEVICE_THERMAL_H__ */ |