Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b2b2111a authored by Ram Chandrasekar's avatar Ram Chandrasekar
Browse files

drivers: thermal: Add support for cpu isolation cooling device



Add support for CPU isolation cooling device driver. CPU Isolation was
part of the CPU cooling device as a last cooling state. This change has
separated the CPU frequency and isolation as separate cooling devices.

This cooling device will register one cooling device per CPU, which can
be used to place isolation mitigation request.

Change-Id: I7f2164e85e2557c07982ecc4316cb6e97793b4d0
Signed-off-by: default avatarRam Chandrasekar <rkumbako@codeaurora.org>
parent 074dd914
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
@@ -101,3 +101,14 @@ config REGULATOR_COOLING_DEVICE
	  voltage.

	  If you want this support, you should say Y here.

config QTI_CPU_ISOLATE_COOLING_DEVICE
	bool "QTI CPU Isolate cooling devices"
	depends on THERMAL_OF
	help
	   This enables the QTI CPU Isolation cooling devices. These cooling
	   devices will be used by QTI chipset to isolate a CPU from being
	   scheduled and hence will let the CPU to power collapse. Isolating
	   a CPU will be used when the CPU frequency mitigation
	   is not good enough to achieve the necessary cooling.
+1 −0
Original line number Diff line number Diff line
@@ -9,3 +9,4 @@ obj-$(CONFIG_QTI_QMI_COOLING_DEVICE) += thermal_mitigation_device_service_v01.o
obj-$(CONFIG_QTI_THERMAL_LIMITS_DCVS) += msm_lmh_dcvs.o lmh_dbg.o
obj-$(CONFIG_QTI_AOP_REG_COOLING_DEVICE) += regulator_aop_cdev.o
obj-$(CONFIG_REGULATOR_COOLING_DEVICE) += regulator_cdev.o
obj-$(CONFIG_QTI_CPU_ISOLATE_COOLING_DEVICE) += cpu_isolate.o
+314 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (c) 2019, The Linux Foundation. All rights reserved.
 */
#include <linux/module.h>
#include <linux/thermal.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/cpu.h>
#include <linux/of_device.h>
#include <linux/suspend.h>

#define CPU_ISOLATE_LEVEL 1

struct cpu_isolate_cdev {
	struct list_head node;
	int cpu_id;
	bool cpu_isolate_state;
	struct thermal_cooling_device *cdev;
	struct device_node *np;
	struct work_struct reg_work;
};

static DEFINE_MUTEX(cpu_isolate_lock);
static LIST_HEAD(cpu_isolate_cdev_list);
static atomic_t in_suspend;
static struct cpumask cpus_pending_online;
static struct cpumask cpus_isolated_by_thermal;

static int cpu_isolate_pm_notify(struct notifier_block *nb,
				unsigned long mode, void *_unused)
{
	struct cpu_isolate_cdev *cpu_isolate_cdev;
	unsigned int cpu;

	switch (mode) {
	case PM_HIBERNATION_PREPARE:
	case PM_RESTORE_PREPARE:
	case PM_SUSPEND_PREPARE:
		atomic_set(&in_suspend, 1);
		break;
	case PM_POST_HIBERNATION:
	case PM_POST_RESTORE:
	case PM_POST_SUSPEND:
		mutex_lock(&cpu_isolate_lock);
		list_for_each_entry(cpu_isolate_cdev, &cpu_isolate_cdev_list,
					node) {
			if (cpu_isolate_cdev->cpu_id == -1)
				continue;
			if (cpu_isolate_cdev->cpu_isolate_state) {
				cpu = cpu_isolate_cdev->cpu_id;
				if (cpu_online(cpu) &&
					!cpumask_test_and_set_cpu(cpu,
					&cpus_isolated_by_thermal)) {
					if (sched_isolate_cpu(cpu))
						cpumask_clear_cpu(cpu,
						&cpus_isolated_by_thermal);
				}
				continue;
			}
		}
		mutex_unlock(&cpu_isolate_lock);
		atomic_set(&in_suspend, 0);
		break;
	default:
		break;
	}
	return 0;
}

static struct notifier_block cpu_isolate_pm_nb = {
	.notifier_call = cpu_isolate_pm_notify,
};

static int cpu_isolate_hp_offline(unsigned int offline_cpu)
{
	struct cpu_isolate_cdev *cpu_isolate_cdev;

	mutex_lock(&cpu_isolate_lock);
	list_for_each_entry(cpu_isolate_cdev, &cpu_isolate_cdev_list, node) {
		if (offline_cpu != cpu_isolate_cdev->cpu_id)
			continue;

		if (!cpu_isolate_cdev->cdev)
			break;

		if ((cpu_isolate_cdev->cpu_isolate_state)
			&& (cpumask_test_and_clear_cpu(offline_cpu,
			&cpus_isolated_by_thermal)))
			sched_unisolate_cpu_unlocked(offline_cpu);
		break;
	}
	mutex_unlock(&cpu_isolate_lock);

	return 0;
}

static int cpu_isolate_hp_online(unsigned int online_cpu)
{
	struct cpu_isolate_cdev *cpu_isolate_cdev;
	int ret = 0;

	if (atomic_read(&in_suspend))
		return 0;

	mutex_lock(&cpu_isolate_lock);
	list_for_each_entry(cpu_isolate_cdev, &cpu_isolate_cdev_list, node) {
		if (online_cpu != cpu_isolate_cdev->cpu_id)
			continue;

		if (cpu_isolate_cdev->cdev) {
			if (cpu_isolate_cdev->cpu_isolate_state) {
				cpumask_set_cpu(online_cpu,
						&cpus_pending_online);
				ret = NOTIFY_BAD;
			}
		} else {
			queue_work(system_highpri_wq,
					&cpu_isolate_cdev->reg_work);
		}

		break;
	}
	mutex_unlock(&cpu_isolate_lock);

	return ret;
}

/**
 * cpu_isolate_set_cur_state - callback function to set the current cooling
 *				state.
 * @cdev: thermal cooling device pointer.
 * @state: set this variable to the current cooling state.
 *
 * Callback for the thermal cooling device to change the cpu isolation
 * current cooling state.
 *
 * Return: 0 on success, an error code otherwise.
 */
static int cpu_isolate_set_cur_state(struct thermal_cooling_device *cdev,
				 unsigned long state)
{
	struct cpu_isolate_cdev *cpu_isolate_cdev = cdev->devdata;
	struct device *cpu_dev;
	int ret = 0;
	int cpu = 0;

	if (cpu_isolate_cdev->cpu_id == -1)
		return -ENODEV;

	/* Request state should be less than max_level */
	if (state > CPU_ISOLATE_LEVEL)
		state = CPU_ISOLATE_LEVEL;

	state = !!state;
	/* Check if the old cooling action is same as new cooling action */
	if (cpu_isolate_cdev->cpu_isolate_state == state)
		return 0;

	mutex_lock(&cpu_isolate_lock);
	cpu = cpu_isolate_cdev->cpu_id;
	cpu_isolate_cdev->cpu_isolate_state = state;
	if (state == CPU_ISOLATE_LEVEL) {
		if (cpu_online(cpu) &&
			(!cpumask_test_and_set_cpu(cpu,
			&cpus_isolated_by_thermal))) {
			if (sched_isolate_cpu(cpu))
				cpumask_clear_cpu(cpu,
					&cpus_isolated_by_thermal);
		}
	} else {
		if (cpumask_test_and_clear_cpu(cpu, &cpus_pending_online)) {
			cpu_dev = get_cpu_device(cpu);
			mutex_unlock(&cpu_isolate_lock);
			ret = device_online(cpu_dev);
			if (ret)
				pr_err("CPU:%d online error:%d\n", cpu, ret);
			return ret;
		} else if (cpumask_test_and_clear_cpu(cpu,
			&cpus_isolated_by_thermal)) {
			sched_unisolate_cpu(cpu);
		}
	}
	mutex_unlock(&cpu_isolate_lock);

	return 0;
}

/**
 * cpu_isolate_get_cur_state - callback function to get the current cooling
 *				state.
 * @cdev: thermal cooling device pointer.
 * @state: fill this variable with the current cooling state.
 *
 * Callback for the thermal cooling device to return the cpu isolation
 * current cooling state.
 *
 * Return: 0 on success, an error code otherwise.
 */
static int cpu_isolate_get_cur_state(struct thermal_cooling_device *cdev,
				 unsigned long *state)
{
	struct cpu_isolate_cdev *cpu_isolate_cdev = cdev->devdata;

	*state = (cpu_isolate_cdev->cpu_isolate_state) ?
			CPU_ISOLATE_LEVEL : 0;

	return 0;
}

/**
 * cpu_isolate_get_max_state - callback function to get the max cooling state.
 * @cdev: thermal cooling device pointer.
 * @state: fill this variable with the max cooling state.
 *
 * Callback for the thermal cooling device to return the cpu
 * isolation max cooling state.
 *
 * Return: 0 on success, an error code otherwise.
 */
static int cpu_isolate_get_max_state(struct thermal_cooling_device *cdev,
				 unsigned long *state)
{
	*state = CPU_ISOLATE_LEVEL;
	return 0;
}

static struct thermal_cooling_device_ops cpu_isolate_cooling_ops = {
	.get_max_state = cpu_isolate_get_max_state,
	.get_cur_state = cpu_isolate_get_cur_state,
	.set_cur_state = cpu_isolate_set_cur_state,
};

static void cpu_isolate_register_cdev(struct work_struct *work)
{
	struct cpu_isolate_cdev *cpu_isolate_cdev =
			container_of(work, struct cpu_isolate_cdev, reg_work);
	char cdev_name[THERMAL_NAME_LENGTH] = "";
	int ret = 0;

	snprintf(cdev_name, THERMAL_NAME_LENGTH, "cpu-isolate%d",
			cpu_isolate_cdev->cpu_id);

	cpu_isolate_cdev->cdev = thermal_of_cooling_device_register(
					cpu_isolate_cdev->np,
					cdev_name,
					cpu_isolate_cdev,
					&cpu_isolate_cooling_ops);
	if (IS_ERR(cpu_isolate_cdev->cdev)) {
		ret = PTR_ERR(cpu_isolate_cdev->cdev);
		pr_err("Cooling register failed for %s, ret:%ld\n",
			cdev_name, ret);
		cpu_isolate_cdev->cdev = NULL;
		return;
	}
	pr_debug("Cooling device [%s] registered.\n", cdev_name);
}

static int cpu_isolate_probe(struct platform_device *pdev)
{
	int ret = 0, cpu = 0;
	struct device_node *dev_phandle, *subsys_np;
	struct device *cpu_dev;
	struct cpu_isolate_cdev *cpu_isolate_cdev = NULL;
	struct device_node *np = pdev->dev.of_node;

	INIT_LIST_HEAD(&cpu_isolate_cdev_list);
	for_each_available_child_of_node(np, subsys_np) {
		cpu_isolate_cdev = devm_kzalloc(&pdev->dev,
				sizeof(*cpu_isolate_cdev), GFP_KERNEL);
		if (!cpu_isolate_cdev)
			return -ENOMEM;
		cpu_isolate_cdev->cpu_id = -1;
		cpu_isolate_cdev->cpu_isolate_state = false;
		cpu_isolate_cdev->cdev = NULL;
		cpu_isolate_cdev->np = subsys_np;

		dev_phandle = of_parse_phandle(subsys_np, "qcom,cpu", 0);
		for_each_possible_cpu(cpu) {
			cpu_dev = get_cpu_device(cpu);
			if (cpu_dev && cpu_dev->of_node == dev_phandle) {
				cpu_isolate_cdev->cpu_id = cpu;
				break;
			}
		}
		INIT_WORK(&cpu_isolate_cdev->reg_work,
				cpu_isolate_register_cdev);
		list_add(&cpu_isolate_cdev->node, &cpu_isolate_cdev_list);
	}

	atomic_set(&in_suspend, 0);
	ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "cpu-isolate/cdev:online",
				cpu_isolate_hp_online, cpu_isolate_hp_offline);
	if (ret < 0)
		return ret;
	register_pm_notifier(&cpu_isolate_pm_nb);
	ret = 0;

	return ret;
}

static const struct of_device_id cpu_isolate_match[] = {
	{ .compatible = "qcom,cpu-isolate", },
	{},
};

static struct platform_driver cpu_isolate_driver = {
	.probe		= cpu_isolate_probe,
	.driver		= {
		.name = KBUILD_MODNAME,
		.of_match_table = cpu_isolate_match,
	},
};
builtin_platform_driver(cpu_isolate_driver);