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

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

drivers: thermal: cpu_isolate: Add a snapshot of cpu isolate



This is a snapshot of the cpu isolate cooling device driver from
msm-4.19 to msm-5.4 as of 'commit
<5069a29e82b3c83577a2bde5739f603750db5e89> (drivers: thermal:
validate cdev sysfs cur_state request before using it)'.

Change-Id: I046d38f76bb8bcf5cfd0f2b03a166611fad955f0
Signed-off-by: default avatarRam Chandrasekar <rkumbako@codeaurora.org>
parent 635b6273
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -20,3 +20,13 @@ config QCOM_SPMI_TEMP_ALARM
	  trip points. The temperature reported by the thermal sensor reflects the
	  real time die temperature if an ADC is present or an estimate of the
	  temperature based upon the over temperature stage value.

config QTI_CPU_ISOLATE_COOLING_DEVICE
	tristate "QTI CPU Isolate cooling devices"
	depends on THERMAL_OF && QTI_THERMAL
	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
@@ -4,3 +4,4 @@ obj-$(CONFIG_QCOM_TSENS) += qcom_tsens.o
qcom_tsens-y			+= tsens.o tsens-common.o tsens-v0_1.o \
				   tsens-8960.o tsens-v2.o tsens-v1.o
obj-$(CONFIG_QCOM_SPMI_TEMP_ALARM)	+= qcom-spmi-temp-alarm.o
obj-$(CONFIG_QTI_CPU_ISOLATE_COOLING_DEVICE) += cpu_isolate.o
+352 −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 struct cpumask cpus_in_max_cooling_level;
static BLOCKING_NOTIFIER_HEAD(cpu_max_cooling_level_notifer);

void cpu_cooling_max_level_notifier_register(struct notifier_block *n)
{
	blocking_notifier_chain_register(&cpu_max_cooling_level_notifer, n);
}

void cpu_cooling_max_level_notifier_unregister(struct notifier_block *n)
{
	blocking_notifier_chain_unregister(&cpu_max_cooling_level_notifer, n);
}

const struct cpumask *cpu_cooling_get_max_level_cpumask(void)
{
	return &cpus_in_max_cooling_level;
}

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)) {
					mutex_unlock(&cpu_isolate_lock);
					if (sched_isolate_cpu(cpu))
						cpumask_clear_cpu(cpu,
						&cpus_isolated_by_thermal);
					mutex_lock(&cpu_isolate_lock);
				}
				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)
		return -EINVAL;

	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))) {
			mutex_unlock(&cpu_isolate_lock);
			if (sched_isolate_cpu(cpu))
				cpumask_clear_cpu(cpu,
					&cpus_isolated_by_thermal);
			mutex_lock(&cpu_isolate_lock);
		}
		cpumask_set_cpu(cpu, &cpus_in_max_cooling_level);
		blocking_notifier_call_chain(&cpu_max_cooling_level_notifer,
						1, (void *)(long)cpu);
	} else {
		if (cpumask_test_and_clear_cpu(cpu, &cpus_pending_online)) {
			cpu_dev = get_cpu_device(cpu);
			if (!cpu_dev) {
				pr_err("CPU:%d cpu dev error\n", cpu);
				mutex_unlock(&cpu_isolate_lock);
				return ret;
			}
			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)) {
			mutex_unlock(&cpu_isolate_lock);
			sched_unisolate_cpu(cpu);
			mutex_lock(&cpu_isolate_lock);
		}
		cpumask_clear_cpu(cpu, &cpus_in_max_cooling_level);
		blocking_notifier_call_chain(&cpu_max_cooling_level_notifer,
						0, (void *)(long)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:%d\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 = NULL;
	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) {
			of_node_put(subsys_np);
			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);
	cpumask_clear(&cpus_in_max_cooling_level);

	return 0;
}

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,
	},
};
module_platform_driver(cpu_isolate_driver);
MODULE_LICENSE("GPL v2");