/*
* arch/arm64/mach-tegra/pm-tegra132.c
*
* Copyright (c) 2013-2014, NVIDIA CORPORATION. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope 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.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "pm.h"
#include "pm-soc.h"
#include "sleep.h"
#include "flowctrl.h"
#include "common.h"
#include "iomap.h"
#include "flowctrl.h"
#include "denver-knobs.h"
#include "pm-tegra132.h"
#define PMC_SCRATCH41 0x140 // stores AARCH64 reset vector
#define HALT_REG_WAKE \
FLOW_CTRL_WAIT_FOR_INTERRUPT | \
FLOW_CTRL_HALT_LIC_IRQ | \
FLOW_CTRL_HALT_LIC_FIQ
#define HALT_REG_NO_WAKE FLOW_CTRL_WAITEVENT
/* AARCH64 reset vector */
extern void tegra_resume(void);
extern bool tegra_suspend_in_progress(void);
static int tegra132_enter_sleep(unsigned long pmstate)
{
u32 reg;
int cpu = smp_processor_id();
/* Null wake events for CORE1 in non-LP0 hotplug case.
* For all other cases, we enable IRQ/FIQ wake events.
*/
if (cpu == 0 || !tegra_suspend_in_progress())
reg = HALT_REG_WAKE;
else
reg = HALT_REG_NO_WAKE;
flowctrl_write_cpu_halt(cpu, reg);
reg = readl(FLOW_CTRL_HALT_CPU(cpu));
do {
asm volatile(
" isb\n"
" msr actlr_el1, %0\n"
" wfi\n"
:
: "r" (pmstate));
} while (0);
return 0;
}
/*
* tegra132_tear_down_cpu
* - core power gating entry finisher
*/
static void tegra132_tear_down_cpu(void)
{
int cpu = smp_processor_id();
u32 reg;
tegra_psci_suspend_cpu(tegra_resume);
local_irq_disable();
local_fiq_disable();
cpu_pm_enter();
/* FlowCtrl programming */
reg = readl(FLOW_CTRL_CPU_CSR(cpu));
reg &= ~FLOW_CTRL_CSR_WFE_BITMAP; /* clear wfe bitmap */
reg &= ~FLOW_CTRL_CSR_WFI_BITMAP; /* clear wfi bitmap */
reg |= FLOW_CTRL_CSR_INTR_FLAG; /* clear intr flag */
reg |= FLOW_CTRL_CSR_EVENT_FLAG; /* clear event flag */
reg |= FLOW_CTRL_CSR_WFI_CPU0 << cpu; /* enable power gating on wfi */
reg |= FLOW_CTRL_CSR_ENABLE; /* enable power gating */
flowctrl_writel(reg, FLOW_CTRL_CPU_CSR(cpu));
reg = readl(FLOW_CTRL_CPU_CSR(cpu));
cpu_suspend(T132_CORE_C7, tegra132_enter_sleep);
cpu_pm_exit();
}
/*
* tegra132_sleep_core_finish(unsigned long v2p)
* - cluster power state entry finisher
* - v2p: ignored
*/
static int tegra132_sleep_core_finish(unsigned long v2p)
{
tegra132_enter_sleep(T132_SYSTEM_LP0);
return 0;
}
static int cpu_pm_notify(struct notifier_block *self,
unsigned long action, void *hcpu)
{
int cpu = smp_processor_id();
switch (action) {
case CPU_PM_ENTER:
case CPU_CLUSTER_PM_ENTER:
denver_set_bg_allowed(cpu, false);
break;
case CPU_CLUSTER_PM_EXIT:
denver_set_bg_allowed(cpu, true);
break;
}
return NOTIFY_OK;
}
static void set_cpu_reset_vector(u32 vec_phys)
{
void __iomem *pmc = IO_ADDRESS(TEGRA_PMC_BASE);
/* SecureOS controls reset vector if present */
if (tegra_cpu_is_secure())
return;
writel(vec_phys, pmc + PMC_SCRATCH41);
readl(pmc + PMC_SCRATCH41);
}
static struct notifier_block cpu_pm_notifier_block = {
.notifier_call = cpu_pm_notify,
};
static int cpu_notify(struct notifier_block *self,
unsigned long action, void *hcpu)
{
long cpu = (long) hcpu;
switch (action) {
case CPU_ONLINE:
case CPU_ONLINE_FROZEN:
denver_set_bg_allowed(cpu, true);
set_cpu_reset_vector(0);
break;
}
return NOTIFY_OK;
}
static struct notifier_block cpu_notifier_block = {
.notifier_call = cpu_notify,
};
static void tegra132_boot_secondary_cpu(int cpu)
{
/* CPU1 is taken out of reset by bootloader for cold boot */
if (tegra_powergate_is_powered(TEGRA_CPU_POWERGATE_ID(cpu)))
return;
/* AARCH64 reset vector */
set_cpu_reset_vector(virt_to_phys(tegra_resume));
/* Power ungate CPU */
tegra_unpowergate_partition(TEGRA_CPU_POWERGATE_ID(cpu));
/* Remove CPU from reset */
flowctrl_write_cpu_halt(cpu, 0);
tegra_cpu_car_ops->out_of_reset(cpu);
}
void tegra_soc_suspend_init(void)
{
tegra_tear_down_cpu = tegra132_tear_down_cpu;
tegra_sleep_core_finish = tegra132_sleep_core_finish;
tegra_boot_secondary_cpu = tegra132_boot_secondary_cpu;
/* Notifier to disable/enable BGALLOW */
cpu_pm_register_notifier(&cpu_pm_notifier_block);
/* Notifier to enable BGALLOW for CPU1-only */
register_hotcpu_notifier(&cpu_notifier_block);
}