From: "jeson.gao" <jeson.gao@xxxxxxxxxx> IPA need a temperature of the whole CPU zone for thermal-cpufreq-0 and thermal-cpufreq-1 cooling device but the real sensor is placed near per-core on sprd platform,so adding this driver to register a virtual sensor,it will polling the temperature of per-core and find the highest temperature as the current temperature of the whole cpu zone for IPA using. Signed-off-by: jeson.gao <jeson.gao@xxxxxxxxxx> --- drivers/thermal/Kconfig | 9 ++ drivers/thermal/Makefile | 1 + drivers/thermal/sprd_virtual_thermal.c | 213 +++++++++++++++++++++++++ 3 files changed, 223 insertions(+) create mode 100644 drivers/thermal/sprd_virtual_thermal.c diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index 7edc8dc6bbab..b3d392846f69 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig @@ -500,6 +500,15 @@ config SPRD_THERMAL Support for the Spreadtrum thermal sensor driver in the Linux thermal framework. +config SPRD_VIRTUAL_THERMAL + tristate "sprd virtual thermal" + depends on ARCH_SPRD || COMPILE_TEST + depends on SPRD_THERMAL + help + Say Y here to support virtual thermal driver + We can use it to find the highest temperature from 8 per core sensor + as the current temperature of the whole cpu zone. + config KHADAS_MCU_FAN_THERMAL tristate "Khadas MCU controller FAN cooling support" depends on OF || COMPILE_TEST diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile index b64dd50a6629..f4aecfff3703 100644 --- a/drivers/thermal/Makefile +++ b/drivers/thermal/Makefile @@ -61,4 +61,5 @@ obj-$(CONFIG_ZX2967_THERMAL) += zx2967_thermal.o obj-$(CONFIG_UNIPHIER_THERMAL) += uniphier_thermal.o obj-$(CONFIG_AMLOGIC_THERMAL) += amlogic_thermal.o obj-$(CONFIG_SPRD_THERMAL) += sprd_thermal.o +obj-$(CONFIG_SPRD_VIRTUAL_THERMAL) += sprd_virtual_thermal.o obj-$(CONFIG_KHADAS_MCU_FAN_THERMAL) += khadas_mcu_fan.o diff --git a/drivers/thermal/sprd_virtual_thermal.c b/drivers/thermal/sprd_virtual_thermal.c new file mode 100644 index 000000000000..db4d6eca2356 --- /dev/null +++ b/drivers/thermal/sprd_virtual_thermal.c @@ -0,0 +1,213 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2020 Unisoc Inc. + +#include <linux/cpu_cooling.h> +#include <linux/cpufreq.h> +#include <linux/cpumask.h> +#include <linux/debugfs.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/thermal.h> +#include <linux/of.h> +#include <linux/kernel.h> + +struct virtual_thm { + int id; + struct device *dev; + struct thermal_zone_device *thm_dev; +}; + +struct real_tz_list { + int temp; + struct thermal_zone_device *tz_dev; +}; + +struct virtual_thm_data { + int nr_thm; + struct real_tz_list *tz_list; + struct virtual_thm *vir_thm; +}; + +static int virtual_thm_get_temp(void *devdata, int *temp) +{ + int i, ret = 0; + int max_temp = 0; + struct thermal_zone_device *tz = NULL; + struct real_tz_list *tz_list = NULL; + struct virtual_thm_data *thm_data = devdata; + struct device *dev; + + if (!thm_data || !temp) + return -EINVAL; + + dev = thm_data->vir_thm->dev; + for (i = 0; i < thm_data->nr_thm; i++) { + tz_list = &thm_data->tz_list[i]; + tz = tz_list->tz_dev; + if (!tz || IS_ERR(tz) || !tz->ops->get_temp) + return -EINVAL; + ret = tz->ops->get_temp(tz, &tz_list->temp); + if (ret) { + dev_err(dev, "fail to get temp\n"); + return ret; + } + max_temp = max(max_temp, tz_list->temp); + } + *temp = max_temp; + + return ret; +} + +static void virtual_thm_unregister(struct platform_device *pdev) +{ + struct virtual_thm_data *data = platform_get_drvdata(pdev); + struct virtual_thm *vir_thm = data->vir_thm; + + devm_thermal_zone_of_sensor_unregister(&pdev->dev, vir_thm->thm_dev); +} + +static const struct thermal_zone_of_device_ops virtual_thm_ops = { + .get_temp = virtual_thm_get_temp, +}; + +static int virtual_thm_register(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct virtual_thm_data *data = platform_get_drvdata(pdev); + struct virtual_thm *vir_thm = data->vir_thm; + + vir_thm->thm_dev = devm_thermal_zone_of_sensor_register(dev, + vir_thm->id, data, + &virtual_thm_ops); + if (IS_ERR_OR_NULL(vir_thm->thm_dev)) + return -ENODEV; + thermal_zone_device_update(vir_thm->thm_dev, THERMAL_EVENT_UNSPECIFIED); + + return 0; +} + +static int get_thm_zone_counts(struct device *dev) +{ + int count; + struct device_node *np = dev->of_node; + + if (!np) { + dev_err(dev, "device node not found\n"); + return -EINVAL; + } + + count = of_property_count_strings(np, "thmzone-names"); + if (count < 0) { + dev_err(dev, "thmzone-names not found\n"); + return count; + } + + return count; +} + +static int get_thm_zone_device(struct platform_device *pdev) +{ + int i, ret = 0; + const char *name; + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct real_tz_list *tz_list; + struct virtual_thm_data *data = platform_get_drvdata(pdev); + + for (i = 0; i < data->nr_thm; i++) { + ret = of_property_read_string_index(np, "thmzone-names", + i, &name); + if (ret) { + dev_err(dev, "fail to get thmzone-names\n"); + return ret; + } + tz_list = &data->tz_list[i]; + tz_list->tz_dev = thermal_zone_get_zone_by_name(name); + if (IS_ERR(tz_list->tz_dev)) { + dev_err(dev, "failed to get thermal zone by name\n"); + return -EINVAL; + } + } + + return ret; +} + +static int virtual_thm_probe(struct platform_device *pdev) +{ + int count = 0, ret = 0, id; + struct virtual_thm_data *data; + struct device *dev = &pdev->dev; + struct device_node *np = pdev->dev.of_node; + + if (!np) { + dev_err(dev, "device node not found\n"); + return -EINVAL; + } + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + count = get_thm_zone_counts(dev); + if (count < 0) { + dev_err(dev, "failed to get thmzone count\n"); + return -EINVAL; + } + data->nr_thm = count; + data->tz_list = devm_kzalloc(dev, sizeof(*data->tz_list) * data->nr_thm, + GFP_KERNEL); + if (!data->tz_list) + return -ENOMEM; + + data->vir_thm = devm_kzalloc(dev, sizeof(*data->vir_thm), GFP_KERNEL); + if (!data->vir_thm) + return -ENOMEM; + + platform_set_drvdata(pdev, data); + ret = get_thm_zone_device(pdev); + if (ret) { + dev_err(dev, "failed to get thmzone device\n"); + return -EINVAL; + } + id = of_alias_get_id(np, "thm-sensor"); + if (id < 0) { + dev_err(dev, "failed to get id\n"); + return -ENODEV; + } + data->vir_thm->id = id; + data->vir_thm->dev = dev; + ret = virtual_thm_register(pdev); + if (ret < 0) { + dev_err(dev, "failed to register virtual thermal\n"); + return ret; + } + + return 0; +} + +static int virtual_thm_remove(struct platform_device *pdev) +{ + virtual_thm_unregister(pdev); + return 0; +} + +static const struct of_device_id virtual_thermal_of_match[] = { + { .compatible = "sprd,virtual-thermal" }, + {}, +}; + +static struct platform_driver virtual_thermal_driver = { + .probe = virtual_thm_probe, + .remove = virtual_thm_remove, + .driver = { + .owner = THIS_MODULE, + .name = "virtual_thermal", + .of_match_table = virtual_thermal_of_match, + }, +}; + +module_platform_driver(virtual_thermal_driver); + +MODULE_AUTHOR("Jeson Gao <jeson.gao@xxxxxxxxxx>"); +MODULE_DESCRIPTION("Unisoc virtual thermal driver"); +MODULE_LICENSE("GPL v2"); -- 2.28.0