[RFC 2/2] thermal: sprd: Add virtual thermal driver

From: gao . yunxiao6
Date: Fri Nov 27 2020 - 03:37:10 EST


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