Files
linux-nv-oot/drivers/cpuidle/cpuidle-tegra-auto.c
Preetham Chandru Ramchandra c2357c8af5 cpuidle: tegra: Fix sparse issue.
symbol 'tegra_auto_cpuidle_s2idle_exit' is not declared so made it
static.

Bug 3954363

Change-Id: I715a3c4c4553a533ed59c9b85f628ce1e9baa308
Signed-off-by: Preetham Chandru Ramchandra <pchandru@nvidia.com>
Reviewed-on: https://git-master.nvidia.com/r/c/linux-nv-oot/+/2899670
Reviewed-by: svc-mobile-coverity <svc-mobile-coverity@nvidia.com>
Reviewed-by: svc-mobile-cert <svc-mobile-cert@nvidia.com>
Reviewed-by: Sandeep Trasi <strasi@nvidia.com>
GVS: Gerrit_Virtual_Submit <buildbot_gerritrpt@nvidia.com>
2023-05-08 05:34:09 -07:00

241 lines
5.8 KiB
C

// SPDX-License-Identifier: GPL-2.0
// Copyright (c) 2022-2023, NVIDIA CORPORATION. All rights reserved.
#include <linux/cpu_cooling.h>
#include <linux/cpuidle.h>
#include <linux/cpumask.h>
#include <linux/cpu_pm.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/slab.h>
#include <linux/suspend.h>
#include <linux/smp.h>
#include <linux/irq.h>
#include <linux/delay.h>
#include <linux/sched.h>
#include <linux/platform_device.h>
#include <asm/cpuidle.h>
#include <linux/suspend.h>
#include <linux/wait.h>
#include <soc/tegra/virt/syscalls.h>
#include <soc/tegra/virt/tegra_hv_sysmgr.h>
enum {
CPUIDLE_TEGRA_AUTO_SC7_NONE,
CPUIDLE_TEGRA_AUTO_SC7_SUSPEND_START,
CPUIDLE_TEGRA_AUTO_SC7_RESUME_START,
};
static struct cpumask cpumask;
static bool s2idle_sc7_state;
static DEFINE_PER_CPU(struct cpuidle_driver *, tegra_auto_cpuidle_drivers);
static bool tegra_auto_cpuidle_s2idle_exit(int cpu_number)
{
return (!cpumask_test_cpu(cpu_number, (const struct cpumask *)&cpumask));
}
static int tegra_auto_suspend_notify_callback(struct notifier_block *nb,
unsigned long action, void *pcpu)
{
switch (action) {
case PM_SUSPEND_PREPARE:
s2idle_sc7_state = CPUIDLE_TEGRA_AUTO_SC7_SUSPEND_START;
break;
case PM_POST_SUSPEND:
s2idle_sc7_state = CPUIDLE_TEGRA_AUTO_SC7_NONE;
break;
}
return NOTIFY_OK;
}
static struct notifier_block suspend_notifier = {
.notifier_call = tegra_auto_suspend_notify_callback,
};
/*
* tegra_auto_enter_idle_state - Programs CPU to enter the specified state
*
* dev: cpuidle device
* drv: cpuidle driver
* idx: state index
*
* Called from the CPUidle framework to program the device to the
* specified target state selected by the governor.
*/
static int tegra_auto_enter_idle_state(struct cpuidle_device *dev,
struct cpuidle_driver *drv, int idx)
{
asm volatile("wfi\n");
return 0;
}
static int tegra_auto_enter_s2idle_state(struct cpuidle_device *dev,
struct cpuidle_driver *drv, int idx)
{
int cpu_id = smp_processor_id();
int boot_cpu_id = 0;
if (s2idle_sc7_state != CPUIDLE_TEGRA_AUTO_SC7_SUSPEND_START) {
asm volatile("wfi\n");
return 0;
}
if (cpu_id == boot_cpu_id) {
int error = 0;
int cpu_number = 0;
for_each_online_cpu(cpu_number) {
if (cpu_number == boot_cpu_id)
continue;
while (!cpumask_test_cpu(cpu_number, (const struct cpumask *)&cpumask))
udelay(10);
}
/*
* Causes the linux guest VM to suspend.
* After SC7 resume it resumes from this point.
*/
pr_debug("%s: before HVC: GUEST_PAUSE_CMD, %d\n", __func__, boot_cpu_id);
error = hyp_guest_reset(GUEST_PAUSE_CMD(0), NULL);
if (error < 0)
pr_err("%s: Failed to trigger suspend, %d\n", __func__, error);
pr_debug("%s: after HVC: GUEST_PAUSE_CMD, %d\n", __func__, boot_cpu_id);
s2idle_sc7_state = CPUIDLE_TEGRA_AUTO_SC7_RESUME_START;
cpumask_clear(&cpumask);
} else {
cpumask_test_and_set_cpu(cpu_id, &cpumask);
do {
asm volatile("wfi\n");
} while (tegra_auto_cpuidle_s2idle_exit(cpu_id) != true);
}
pr_debug("%s: exiting s2idle, %d, %d\n", __func__, cpu_id, boot_cpu_id);
return 0;
}
static struct cpuidle_driver tegra_auto_idle_driver __initdata = {
.name = "tegra_auto_idle",
.owner = THIS_MODULE,
/*
* State at index 0 is standby wfi and considered standard
* on all ARM platforms.
*/
.state_count = 2,
.states[0] = {
.enter = tegra_auto_enter_idle_state,
.exit_latency = 1,
.target_residency = 1,
.power_usage = UINT_MAX,
.name = "WFI",
.desc = "ARM WFI",
},
.states[1] = {
.enter = tegra_auto_enter_idle_state,
.enter_s2idle = tegra_auto_enter_s2idle_state,
.exit_latency = 1,
.target_residency = 1,
.power_usage = UINT_MAX,
.name = "TEGRA_S2IDLE",
.desc = "TEGRA AUTO S2IDLE",
},
};
/*
* tegra_auto_idle_init_cpu
*
* Registers the tegra_auto specific cpuidle driver with the cpuidle framework.
*/
static int __init tegra_auto_idle_init_cpu(int cpu)
{
int ret = 0;
struct cpuidle_driver *drv;
drv = kmemdup(&tegra_auto_idle_driver, sizeof(*drv), GFP_KERNEL);
if (!drv)
return -ENOMEM;
drv->cpumask = (struct cpumask *)cpumask_of(cpu);
ret = cpuidle_register(drv, NULL);
if (ret) {
pr_err("cpu register failed\n");
goto out_kfree_drv;
}
per_cpu(tegra_auto_cpuidle_drivers, cpu) = drv;
return 0;
out_kfree_drv:
kfree(drv);
return ret;
}
/*
* tegra_auto_cpuidle_probe - Initializes cpuidle driver
*
* Initializes cpuidle driver for all CPUs, if any CPU fails
* to register cpuidle driver then rollback to cancel all CPUs
* registeration.
*/
static int __init tegra_auto_cpuidle_probe(struct platform_device *pdev)
{
int cpu, ret;
struct cpuidle_driver *drv;
for_each_possible_cpu(cpu) {
ret = tegra_auto_idle_init_cpu(cpu);
if (ret)
goto out_fail;
}
register_pm_notifier(&suspend_notifier);
return 0;
out_fail:
while (--cpu >= 0) {
drv = per_cpu(tegra_auto_cpuidle_drivers, cpu);
cpuidle_unregister(drv);
per_cpu(tegra_auto_cpuidle_drivers, cpu) = NULL;
kfree(drv);
}
return ret;
}
static int tegra_auto_cpuidle_remove(struct platform_device *pdev)
{
int cpu;
struct cpuidle_driver *drv;
for_each_possible_cpu(cpu) {
drv = per_cpu(tegra_auto_cpuidle_drivers, cpu);
cpuidle_unregister(drv);
per_cpu(tegra_auto_cpuidle_drivers, cpu) = NULL;
kfree(drv);
}
return 0;
}
static const struct of_device_id tegra_auto_cpuidle_of[] = {
{ .compatible = "nvidia,cpuidle-tegra-auto" },
{ },
};
static struct platform_driver tegra_auto_cpuidle_driver __refdata = {
.probe = tegra_auto_cpuidle_probe,
.remove = tegra_auto_cpuidle_remove,
.driver = {
.owner = THIS_MODULE,
.name = "cpuidle_tegra_auto",
.of_match_table = of_match_ptr(tegra_auto_cpuidle_of)
}
};
module_platform_driver(tegra_auto_cpuidle_driver);
MODULE_LICENSE("GPL v2");