mirror of
https://github.com/AuxXxilium/linux_dsm_epyc7002.git
synced 2024-11-24 15:01:13 +07:00
c942fddf87
Based on 3 normalized pattern(s): this program is free software you can redistribute it and or modify it under the terms of the gnu general public license as published by the free software foundation either version 2 of the license or at your option any later version this program is distributed in the hope that it will be useful but without any warranty without even the implied warranty of merchantability or fitness for a particular purpose see the gnu general public license for more details this program is free software you can redistribute it and or modify it under the terms of the gnu general public license as published by the free software foundation either version 2 of the license or at your option any later version [author] [kishon] [vijay] [abraham] [i] [kishon]@[ti] [com] this program is distributed in the hope that it will be useful but without any warranty without even the implied warranty of merchantability or fitness for a particular purpose see the gnu general public license for more details this program is free software you can redistribute it and or modify it under the terms of the gnu general public license as published by the free software foundation either version 2 of the license or at your option any later version [author] [graeme] [gregory] [gg]@[slimlogic] [co] [uk] [author] [kishon] [vijay] [abraham] [i] [kishon]@[ti] [com] [based] [on] [twl6030]_[usb] [c] [author] [hema] [hk] [hemahk]@[ti] [com] this program is distributed in the hope that it will be useful but without any warranty without even the implied warranty of merchantability or fitness for a particular purpose see the gnu general public license for more details extracted by the scancode license scanner the SPDX license identifier GPL-2.0-or-later has been chosen to replace the boilerplate/reference in 1105 file(s). Signed-off-by: Thomas Gleixner <tglx@linutronix.de> Reviewed-by: Allison Randal <allison@lohutok.net> Reviewed-by: Richard Fontana <rfontana@redhat.com> Reviewed-by: Kate Stewart <kstewart@linuxfoundation.org> Cc: linux-spdx@vger.kernel.org Link: https://lkml.kernel.org/r/20190527070033.202006027@linutronix.de Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
213 lines
4.3 KiB
C
213 lines
4.3 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* CPU idle driver for Tegra CPUs
|
|
*
|
|
* Copyright (c) 2010-2012, NVIDIA Corporation.
|
|
* Copyright (c) 2011 Google, Inc.
|
|
* Author: Colin Cross <ccross@android.com>
|
|
* Gary King <gking@nvidia.com>
|
|
*
|
|
* Rework for 3.3 by Peter De Schrijver <pdeschrijver@nvidia.com>
|
|
*/
|
|
|
|
#include <linux/clk/tegra.h>
|
|
#include <linux/tick.h>
|
|
#include <linux/cpuidle.h>
|
|
#include <linux/cpu_pm.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
|
|
#include <soc/tegra/flowctrl.h>
|
|
|
|
#include <asm/cpuidle.h>
|
|
#include <asm/smp_plat.h>
|
|
#include <asm/suspend.h>
|
|
|
|
#include "cpuidle.h"
|
|
#include "iomap.h"
|
|
#include "irq.h"
|
|
#include "pm.h"
|
|
#include "reset.h"
|
|
#include "sleep.h"
|
|
|
|
#ifdef CONFIG_PM_SLEEP
|
|
static bool abort_flag;
|
|
static atomic_t abort_barrier;
|
|
static int tegra20_idle_lp2_coupled(struct cpuidle_device *dev,
|
|
struct cpuidle_driver *drv,
|
|
int index);
|
|
#define TEGRA20_MAX_STATES 2
|
|
#else
|
|
#define TEGRA20_MAX_STATES 1
|
|
#endif
|
|
|
|
static struct cpuidle_driver tegra_idle_driver = {
|
|
.name = "tegra_idle",
|
|
.owner = THIS_MODULE,
|
|
.states = {
|
|
ARM_CPUIDLE_WFI_STATE_PWR(600),
|
|
#ifdef CONFIG_PM_SLEEP
|
|
{
|
|
.enter = tegra20_idle_lp2_coupled,
|
|
.exit_latency = 5000,
|
|
.target_residency = 10000,
|
|
.power_usage = 0,
|
|
.flags = CPUIDLE_FLAG_COUPLED |
|
|
CPUIDLE_FLAG_TIMER_STOP,
|
|
.name = "powered-down",
|
|
.desc = "CPU power gated",
|
|
},
|
|
#endif
|
|
},
|
|
.state_count = TEGRA20_MAX_STATES,
|
|
.safe_state_index = 0,
|
|
};
|
|
|
|
#ifdef CONFIG_PM_SLEEP
|
|
#ifdef CONFIG_SMP
|
|
static int tegra20_reset_sleeping_cpu_1(void)
|
|
{
|
|
int ret = 0;
|
|
|
|
tegra_pen_lock();
|
|
|
|
if (readb(tegra20_cpu1_resettable_status) == CPU_RESETTABLE)
|
|
tegra20_cpu_shutdown(1);
|
|
else
|
|
ret = -EINVAL;
|
|
|
|
tegra_pen_unlock();
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void tegra20_wake_cpu1_from_reset(void)
|
|
{
|
|
tegra_pen_lock();
|
|
|
|
tegra20_cpu_clear_resettable();
|
|
|
|
/* enable cpu clock on cpu */
|
|
tegra_enable_cpu_clock(1);
|
|
|
|
/* take the CPU out of reset */
|
|
tegra_cpu_out_of_reset(1);
|
|
|
|
/* unhalt the cpu */
|
|
flowctrl_write_cpu_halt(1, 0);
|
|
|
|
tegra_pen_unlock();
|
|
}
|
|
|
|
static int tegra20_reset_cpu_1(void)
|
|
{
|
|
if (!cpu_online(1) || !tegra20_reset_sleeping_cpu_1())
|
|
return 0;
|
|
|
|
tegra20_wake_cpu1_from_reset();
|
|
return -EBUSY;
|
|
}
|
|
#else
|
|
static inline void tegra20_wake_cpu1_from_reset(void)
|
|
{
|
|
}
|
|
|
|
static inline int tegra20_reset_cpu_1(void)
|
|
{
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static bool tegra20_cpu_cluster_power_down(struct cpuidle_device *dev,
|
|
struct cpuidle_driver *drv,
|
|
int index)
|
|
{
|
|
while (tegra20_cpu_is_resettable_soon())
|
|
cpu_relax();
|
|
|
|
if (tegra20_reset_cpu_1() || !tegra_cpu_rail_off_ready())
|
|
return false;
|
|
|
|
tegra_idle_lp2_last();
|
|
|
|
if (cpu_online(1))
|
|
tegra20_wake_cpu1_from_reset();
|
|
|
|
return true;
|
|
}
|
|
|
|
#ifdef CONFIG_SMP
|
|
static bool tegra20_idle_enter_lp2_cpu_1(struct cpuidle_device *dev,
|
|
struct cpuidle_driver *drv,
|
|
int index)
|
|
{
|
|
cpu_suspend(0, tegra20_sleep_cpu_secondary_finish);
|
|
|
|
tegra20_cpu_clear_resettable();
|
|
|
|
return true;
|
|
}
|
|
#else
|
|
static inline bool tegra20_idle_enter_lp2_cpu_1(struct cpuidle_device *dev,
|
|
struct cpuidle_driver *drv,
|
|
int index)
|
|
{
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
static int tegra20_idle_lp2_coupled(struct cpuidle_device *dev,
|
|
struct cpuidle_driver *drv,
|
|
int index)
|
|
{
|
|
bool entered_lp2 = false;
|
|
|
|
if (tegra_pending_sgi())
|
|
WRITE_ONCE(abort_flag, true);
|
|
|
|
cpuidle_coupled_parallel_barrier(dev, &abort_barrier);
|
|
|
|
if (abort_flag) {
|
|
cpuidle_coupled_parallel_barrier(dev, &abort_barrier);
|
|
abort_flag = false; /* clean flag for next coming */
|
|
return -EINTR;
|
|
}
|
|
|
|
local_fiq_disable();
|
|
|
|
tegra_set_cpu_in_lp2();
|
|
cpu_pm_enter();
|
|
|
|
if (dev->cpu == 0)
|
|
entered_lp2 = tegra20_cpu_cluster_power_down(dev, drv, index);
|
|
else
|
|
entered_lp2 = tegra20_idle_enter_lp2_cpu_1(dev, drv, index);
|
|
|
|
cpu_pm_exit();
|
|
tegra_clear_cpu_in_lp2();
|
|
|
|
local_fiq_enable();
|
|
|
|
smp_rmb();
|
|
|
|
return entered_lp2 ? index : 0;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Tegra20 HW appears to have a bug such that PCIe device interrupts, whether
|
|
* they are legacy IRQs or MSI, are lost when LP2 is enabled. To work around
|
|
* this, simply disable LP2 if the PCI driver and DT node are both enabled.
|
|
*/
|
|
void tegra20_cpuidle_pcie_irqs_in_use(void)
|
|
{
|
|
pr_info_once(
|
|
"Disabling cpuidle LP2 state, since PCIe IRQs are in use\n");
|
|
tegra_idle_driver.states[1].disabled = true;
|
|
}
|
|
|
|
int __init tegra20_cpuidle_init(void)
|
|
{
|
|
return cpuidle_register(&tegra_idle_driver, cpu_possible_mask);
|
|
}
|