forked from Minki/linux
Merge tag 'tegra-for-3.8-cpuidle' of git://git.kernel.org/pub/scm/linux/kernel/git/swarren/linux-tegra into next/soc
From Stephen Warren: ARM: tegra: cpuidle enhancements A cpuidle state "LP2" is added, which power-gates the CPUs. Support for CPUs 1..n is essentially complete, although support for CPU0 could benefit from future use of coupled-cpuidle or similar techniques. A couple of very minor cleanups to cpuidle were included too. This pull request is based on tegra-for-3.8-soc. * tag 'tegra-for-3.8-cpuidle' of git://git.kernel.org/pub/scm/linux/kernel/git/swarren/linux-tegra: ARM: tegra: retain L2 content over CPU suspend/resume ARM: tegra30: cpuidle: add powered-down state for CPU0 ARM: tegra30: flowctrl: add cpu_suspend_exter/exit function ARM: tegra30: clocks: add CPU low-power function into tegra_cpu_car_ops ARM: tegra30: common: enable csite clock ARM: tegra30: cpuidle: add powered-down state for secondary CPUs ARM: tegra: cpuidle: add CPU resume function ARM: tegra: cpuidle: separate cpuidle driver for different chips ARM: tegra: rename the file of "sleep-tXX" to "sleep-tegraXX" ARM: tegra: cpuidle: replace LP3 with ARM_CPUIDLE_WFI_STATE
This commit is contained in:
commit
d5fe60d379
@ -8,17 +8,24 @@ obj-y += pmc.o
|
||||
obj-y += flowctrl.o
|
||||
obj-y += powergate.o
|
||||
obj-y += apbio.o
|
||||
obj-y += pm.o
|
||||
obj-$(CONFIG_CPU_IDLE) += cpuidle.o
|
||||
obj-$(CONFIG_CPU_IDLE) += sleep.o
|
||||
obj-$(CONFIG_ARCH_TEGRA_2x_SOC) += tegra20_clocks.o
|
||||
obj-$(CONFIG_ARCH_TEGRA_2x_SOC) += tegra20_clocks_data.o
|
||||
obj-$(CONFIG_ARCH_TEGRA_2x_SOC) += tegra20_speedo.o
|
||||
obj-$(CONFIG_ARCH_TEGRA_2x_SOC) += tegra2_emc.o
|
||||
obj-$(CONFIG_ARCH_TEGRA_2x_SOC) += sleep-t20.o
|
||||
obj-$(CONFIG_ARCH_TEGRA_2x_SOC) += sleep-tegra20.o
|
||||
ifeq ($(CONFIG_CPU_IDLE),y)
|
||||
obj-$(CONFIG_ARCH_TEGRA_2x_SOC) += cpuidle-tegra20.o
|
||||
endif
|
||||
obj-$(CONFIG_ARCH_TEGRA_3x_SOC) += tegra30_clocks.o
|
||||
obj-$(CONFIG_ARCH_TEGRA_3x_SOC) += tegra30_clocks_data.o
|
||||
obj-$(CONFIG_ARCH_TEGRA_3x_SOC) += tegra30_speedo.o
|
||||
obj-$(CONFIG_ARCH_TEGRA_3x_SOC) += sleep-t30.o
|
||||
obj-$(CONFIG_ARCH_TEGRA_3x_SOC) += sleep-tegra30.o
|
||||
ifeq ($(CONFIG_CPU_IDLE),y)
|
||||
obj-$(CONFIG_ARCH_TEGRA_3x_SOC) += cpuidle-tegra30.o
|
||||
endif
|
||||
obj-$(CONFIG_SMP) += platsmp.o headsmp.o
|
||||
obj-$(CONFIG_SMP) += reset.o
|
||||
obj-$(CONFIG_HOTPLUG_CPU) += hotplug.o
|
||||
|
@ -36,6 +36,7 @@
|
||||
#include "pmc.h"
|
||||
#include "apbio.h"
|
||||
#include "sleep.h"
|
||||
#include "pm.h"
|
||||
|
||||
/*
|
||||
* Storage for debug-macro.S's state.
|
||||
@ -108,6 +109,7 @@ static __initdata struct tegra_clk_init_table tegra30_clk_init_table[] = {
|
||||
{ "sclk", "pll_p_out4", 102000000, true },
|
||||
{ "hclk", "sclk", 102000000, true },
|
||||
{ "pclk", "hclk", 51000000, true },
|
||||
{ "csite", NULL, 0, true },
|
||||
{ NULL, NULL, 0, 0},
|
||||
};
|
||||
#endif
|
||||
@ -116,6 +118,7 @@ static __initdata struct tegra_clk_init_table tegra30_clk_init_table[] = {
|
||||
static void __init tegra_init_cache(void)
|
||||
{
|
||||
#ifdef CONFIG_CACHE_L2X0
|
||||
int ret;
|
||||
void __iomem *p = IO_ADDRESS(TEGRA_ARM_PERIF_BASE) + 0x3000;
|
||||
u32 aux_ctrl, cache_type;
|
||||
|
||||
@ -123,7 +126,9 @@ static void __init tegra_init_cache(void)
|
||||
aux_ctrl = (cache_type & 0x700) << (17-8);
|
||||
aux_ctrl |= 0x7C400001;
|
||||
|
||||
l2x0_of_init(aux_ctrl, 0x8200c3fe);
|
||||
ret = l2x0_of_init(aux_ctrl, 0x8200c3fe);
|
||||
if (!ret)
|
||||
l2x0_saved_regs_addr = virt_to_phys(&l2x0_saved_regs);
|
||||
#endif
|
||||
|
||||
}
|
||||
|
66
arch/arm/mach-tegra/cpuidle-tegra20.c
Normal file
66
arch/arm/mach-tegra/cpuidle-tegra20.c
Normal file
@ -0,0 +1,66 @@
|
||||
/*
|
||||
* 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>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/cpuidle.h>
|
||||
|
||||
#include <asm/cpuidle.h>
|
||||
|
||||
static struct cpuidle_driver tegra_idle_driver = {
|
||||
.name = "tegra_idle",
|
||||
.owner = THIS_MODULE,
|
||||
.en_core_tk_irqen = 1,
|
||||
.state_count = 1,
|
||||
.states = {
|
||||
[0] = ARM_CPUIDLE_WFI_STATE_PWR(600),
|
||||
},
|
||||
};
|
||||
|
||||
static DEFINE_PER_CPU(struct cpuidle_device, tegra_idle_device);
|
||||
|
||||
int __init tegra20_cpuidle_init(void)
|
||||
{
|
||||
int ret;
|
||||
unsigned int cpu;
|
||||
struct cpuidle_device *dev;
|
||||
struct cpuidle_driver *drv = &tegra_idle_driver;
|
||||
|
||||
ret = cpuidle_register_driver(&tegra_idle_driver);
|
||||
if (ret) {
|
||||
pr_err("CPUidle driver registration failed\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
for_each_possible_cpu(cpu) {
|
||||
dev = &per_cpu(tegra_idle_device, cpu);
|
||||
dev->cpu = cpu;
|
||||
|
||||
dev->state_count = drv->state_count;
|
||||
ret = cpuidle_register_device(dev);
|
||||
if (ret) {
|
||||
pr_err("CPU%u: CPUidle device registration failed\n",
|
||||
cpu);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
188
arch/arm/mach-tegra/cpuidle-tegra30.c
Normal file
188
arch/arm/mach-tegra/cpuidle-tegra30.c
Normal file
@ -0,0 +1,188 @@
|
||||
/*
|
||||
* 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>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/cpuidle.h>
|
||||
#include <linux/cpu_pm.h>
|
||||
#include <linux/clockchips.h>
|
||||
|
||||
#include <asm/cpuidle.h>
|
||||
#include <asm/proc-fns.h>
|
||||
#include <asm/suspend.h>
|
||||
#include <asm/smp_plat.h>
|
||||
|
||||
#include "pm.h"
|
||||
#include "sleep.h"
|
||||
#include "tegra_cpu_car.h"
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int tegra30_idle_lp2(struct cpuidle_device *dev,
|
||||
struct cpuidle_driver *drv,
|
||||
int index);
|
||||
#endif
|
||||
|
||||
static struct cpuidle_driver tegra_idle_driver = {
|
||||
.name = "tegra_idle",
|
||||
.owner = THIS_MODULE,
|
||||
.en_core_tk_irqen = 1,
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
.state_count = 2,
|
||||
#else
|
||||
.state_count = 1,
|
||||
#endif
|
||||
.states = {
|
||||
[0] = ARM_CPUIDLE_WFI_STATE_PWR(600),
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
[1] = {
|
||||
.enter = tegra30_idle_lp2,
|
||||
.exit_latency = 2000,
|
||||
.target_residency = 2200,
|
||||
.power_usage = 0,
|
||||
.flags = CPUIDLE_FLAG_TIME_VALID,
|
||||
.name = "powered-down",
|
||||
.desc = "CPU power gated",
|
||||
},
|
||||
#endif
|
||||
},
|
||||
};
|
||||
|
||||
static DEFINE_PER_CPU(struct cpuidle_device, tegra_idle_device);
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static bool tegra30_cpu_cluster_power_down(struct cpuidle_device *dev,
|
||||
struct cpuidle_driver *drv,
|
||||
int index)
|
||||
{
|
||||
struct cpuidle_state *state = &drv->states[index];
|
||||
u32 cpu_on_time = state->exit_latency;
|
||||
u32 cpu_off_time = state->target_residency - state->exit_latency;
|
||||
|
||||
/* All CPUs entering LP2 is not working.
|
||||
* Don't let CPU0 enter LP2 when any secondary CPU is online.
|
||||
*/
|
||||
if (num_online_cpus() > 1 || !tegra_cpu_rail_off_ready()) {
|
||||
cpu_do_idle();
|
||||
return false;
|
||||
}
|
||||
|
||||
clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_ENTER, &dev->cpu);
|
||||
|
||||
tegra_idle_lp2_last(cpu_on_time, cpu_off_time);
|
||||
|
||||
clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_EXIT, &dev->cpu);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_SMP
|
||||
static bool tegra30_cpu_core_power_down(struct cpuidle_device *dev,
|
||||
struct cpuidle_driver *drv,
|
||||
int index)
|
||||
{
|
||||
clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_ENTER, &dev->cpu);
|
||||
|
||||
smp_wmb();
|
||||
|
||||
save_cpu_arch_register();
|
||||
|
||||
cpu_suspend(0, tegra30_sleep_cpu_secondary_finish);
|
||||
|
||||
restore_cpu_arch_register();
|
||||
|
||||
clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_EXIT, &dev->cpu);
|
||||
|
||||
return true;
|
||||
}
|
||||
#else
|
||||
static inline bool tegra30_cpu_core_power_down(struct cpuidle_device *dev,
|
||||
struct cpuidle_driver *drv,
|
||||
int index)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int __cpuinit tegra30_idle_lp2(struct cpuidle_device *dev,
|
||||
struct cpuidle_driver *drv,
|
||||
int index)
|
||||
{
|
||||
u32 cpu = is_smp() ? cpu_logical_map(dev->cpu) : dev->cpu;
|
||||
bool entered_lp2 = false;
|
||||
bool last_cpu;
|
||||
|
||||
local_fiq_disable();
|
||||
|
||||
last_cpu = tegra_set_cpu_in_lp2(cpu);
|
||||
cpu_pm_enter();
|
||||
|
||||
if (cpu == 0) {
|
||||
if (last_cpu)
|
||||
entered_lp2 = tegra30_cpu_cluster_power_down(dev, drv,
|
||||
index);
|
||||
else
|
||||
cpu_do_idle();
|
||||
} else {
|
||||
entered_lp2 = tegra30_cpu_core_power_down(dev, drv, index);
|
||||
}
|
||||
|
||||
cpu_pm_exit();
|
||||
tegra_clear_cpu_in_lp2(cpu);
|
||||
|
||||
local_fiq_enable();
|
||||
|
||||
smp_rmb();
|
||||
|
||||
return (entered_lp2) ? index : 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
int __init tegra30_cpuidle_init(void)
|
||||
{
|
||||
int ret;
|
||||
unsigned int cpu;
|
||||
struct cpuidle_device *dev;
|
||||
struct cpuidle_driver *drv = &tegra_idle_driver;
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
tegra_tear_down_cpu = tegra30_tear_down_cpu;
|
||||
#endif
|
||||
|
||||
ret = cpuidle_register_driver(&tegra_idle_driver);
|
||||
if (ret) {
|
||||
pr_err("CPUidle driver registration failed\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
for_each_possible_cpu(cpu) {
|
||||
dev = &per_cpu(tegra_idle_device, cpu);
|
||||
dev->cpu = cpu;
|
||||
|
||||
dev->state_count = drv->state_count;
|
||||
ret = cpuidle_register_device(dev);
|
||||
if (ret) {
|
||||
pr_err("CPU%u: CPUidle device registration failed\n",
|
||||
cpu);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
@ -23,83 +23,26 @@
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/cpu.h>
|
||||
#include <linux/cpuidle.h>
|
||||
#include <linux/hrtimer.h>
|
||||
|
||||
#include <asm/proc-fns.h>
|
||||
|
||||
static int tegra_idle_enter_lp3(struct cpuidle_device *dev,
|
||||
struct cpuidle_driver *drv, int index);
|
||||
|
||||
struct cpuidle_driver tegra_idle_driver = {
|
||||
.name = "tegra_idle",
|
||||
.owner = THIS_MODULE,
|
||||
.state_count = 1,
|
||||
.states = {
|
||||
[0] = {
|
||||
.enter = tegra_idle_enter_lp3,
|
||||
.exit_latency = 10,
|
||||
.target_residency = 10,
|
||||
.power_usage = 600,
|
||||
.flags = CPUIDLE_FLAG_TIME_VALID,
|
||||
.name = "LP3",
|
||||
.desc = "CPU flow-controlled",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
static DEFINE_PER_CPU(struct cpuidle_device, tegra_idle_device);
|
||||
|
||||
static int tegra_idle_enter_lp3(struct cpuidle_device *dev,
|
||||
struct cpuidle_driver *drv, int index)
|
||||
{
|
||||
ktime_t enter, exit;
|
||||
s64 us;
|
||||
|
||||
local_irq_disable();
|
||||
local_fiq_disable();
|
||||
|
||||
enter = ktime_get();
|
||||
|
||||
cpu_do_idle();
|
||||
|
||||
exit = ktime_sub(ktime_get(), enter);
|
||||
us = ktime_to_us(exit);
|
||||
|
||||
local_fiq_enable();
|
||||
local_irq_enable();
|
||||
|
||||
dev->last_residency = us;
|
||||
|
||||
return index;
|
||||
}
|
||||
#include "fuse.h"
|
||||
#include "cpuidle.h"
|
||||
|
||||
static int __init tegra_cpuidle_init(void)
|
||||
{
|
||||
int ret;
|
||||
unsigned int cpu;
|
||||
struct cpuidle_device *dev;
|
||||
struct cpuidle_driver *drv = &tegra_idle_driver;
|
||||
|
||||
ret = cpuidle_register_driver(&tegra_idle_driver);
|
||||
if (ret) {
|
||||
pr_err("CPUidle driver registration failed\n");
|
||||
return ret;
|
||||
switch (tegra_chip_id) {
|
||||
case TEGRA20:
|
||||
ret = tegra20_cpuidle_init();
|
||||
break;
|
||||
case TEGRA30:
|
||||
ret = tegra30_cpuidle_init();
|
||||
break;
|
||||
default:
|
||||
ret = -ENODEV;
|
||||
break;
|
||||
}
|
||||
|
||||
for_each_possible_cpu(cpu) {
|
||||
dev = &per_cpu(tegra_idle_device, cpu);
|
||||
dev->cpu = cpu;
|
||||
|
||||
dev->state_count = drv->state_count;
|
||||
ret = cpuidle_register_device(dev);
|
||||
if (ret) {
|
||||
pr_err("CPU%u: CPUidle device registration failed\n",
|
||||
cpu);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
return ret;
|
||||
}
|
||||
device_initcall(tegra_cpuidle_init);
|
||||
|
32
arch/arm/mach-tegra/cpuidle.h
Normal file
32
arch/arm/mach-tegra/cpuidle.h
Normal file
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (c) 2012, 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __MACH_TEGRA_CPUIDLE_H
|
||||
#define __MACH_TEGRA_CPUIDLE_H
|
||||
|
||||
#ifdef CONFIG_ARCH_TEGRA_2x_SOC
|
||||
int tegra20_cpuidle_init(void);
|
||||
#else
|
||||
static inline int tegra20_cpuidle_init(void) { return -ENODEV; }
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_ARCH_TEGRA_3x_SOC
|
||||
int tegra30_cpuidle_init(void);
|
||||
#else
|
||||
static inline int tegra30_cpuidle_init(void) { return -ENODEV; }
|
||||
#endif
|
||||
|
||||
#endif
|
@ -21,6 +21,7 @@
|
||||
#include <linux/init.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/cpumask.h>
|
||||
|
||||
#include "flowctrl.h"
|
||||
#include "iomap.h"
|
||||
@ -50,6 +51,14 @@ static void flowctrl_update(u8 offset, u32 value)
|
||||
readl_relaxed(addr);
|
||||
}
|
||||
|
||||
u32 flowctrl_read_cpu_csr(unsigned int cpuid)
|
||||
{
|
||||
u8 offset = flowctrl_offset_cpu_csr[cpuid];
|
||||
void __iomem *addr = IO_ADDRESS(TEGRA_FLOW_CTRL_BASE) + offset;
|
||||
|
||||
return readl(addr);
|
||||
}
|
||||
|
||||
void flowctrl_write_cpu_csr(unsigned int cpuid, u32 value)
|
||||
{
|
||||
return flowctrl_update(flowctrl_offset_cpu_csr[cpuid], value);
|
||||
@ -59,3 +68,41 @@ void flowctrl_write_cpu_halt(unsigned int cpuid, u32 value)
|
||||
{
|
||||
return flowctrl_update(flowctrl_offset_halt_cpu[cpuid], value);
|
||||
}
|
||||
|
||||
void flowctrl_cpu_suspend_enter(unsigned int cpuid)
|
||||
{
|
||||
unsigned int reg;
|
||||
int i;
|
||||
|
||||
reg = flowctrl_read_cpu_csr(cpuid);
|
||||
reg &= ~TEGRA30_FLOW_CTRL_CSR_WFE_BITMAP; /* clear wfe bitmap */
|
||||
reg &= ~TEGRA30_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 |= TEGRA30_FLOW_CTRL_CSR_WFI_CPU0 << cpuid; /* pwr gating on wfi */
|
||||
reg |= FLOW_CTRL_CSR_ENABLE; /* pwr gating */
|
||||
flowctrl_write_cpu_csr(cpuid, reg);
|
||||
|
||||
for (i = 0; i < num_possible_cpus(); i++) {
|
||||
if (i == cpuid)
|
||||
continue;
|
||||
reg = flowctrl_read_cpu_csr(i);
|
||||
reg |= FLOW_CTRL_CSR_EVENT_FLAG;
|
||||
reg |= FLOW_CTRL_CSR_INTR_FLAG;
|
||||
flowctrl_write_cpu_csr(i, reg);
|
||||
}
|
||||
}
|
||||
|
||||
void flowctrl_cpu_suspend_exit(unsigned int cpuid)
|
||||
{
|
||||
unsigned int reg;
|
||||
|
||||
/* Disable powergating via flow controller for CPU0 */
|
||||
reg = flowctrl_read_cpu_csr(cpuid);
|
||||
reg &= ~TEGRA30_FLOW_CTRL_CSR_WFE_BITMAP; /* clear wfe bitmap */
|
||||
reg &= ~TEGRA30_FLOW_CTRL_CSR_WFI_BITMAP; /* clear wfi bitmap */
|
||||
reg &= ~FLOW_CTRL_CSR_ENABLE; /* clear enable */
|
||||
reg |= FLOW_CTRL_CSR_INTR_FLAG; /* clear intr */
|
||||
reg |= FLOW_CTRL_CSR_EVENT_FLAG; /* clear event */
|
||||
flowctrl_write_cpu_csr(cpuid, reg);
|
||||
}
|
||||
|
@ -34,9 +34,17 @@
|
||||
#define FLOW_CTRL_HALT_CPU1_EVENTS 0x14
|
||||
#define FLOW_CTRL_CPU1_CSR 0x18
|
||||
|
||||
#define TEGRA30_FLOW_CTRL_CSR_WFI_CPU0 (1 << 8)
|
||||
#define TEGRA30_FLOW_CTRL_CSR_WFE_BITMAP (0xF << 4)
|
||||
#define TEGRA30_FLOW_CTRL_CSR_WFI_BITMAP (0xF << 8)
|
||||
|
||||
#ifndef __ASSEMBLY__
|
||||
u32 flowctrl_read_cpu_csr(unsigned int cpuid);
|
||||
void flowctrl_write_cpu_csr(unsigned int cpuid, u32 value);
|
||||
void flowctrl_write_cpu_halt(unsigned int cpuid, u32 value);
|
||||
|
||||
void flowctrl_cpu_suspend_enter(unsigned int cpuid);
|
||||
void flowctrl_cpu_suspend_exit(unsigned int cpuid);
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
@ -2,6 +2,8 @@
|
||||
#include <linux/init.h>
|
||||
|
||||
#include <asm/cache.h>
|
||||
#include <asm/asm-offsets.h>
|
||||
#include <asm/hardware/cache-l2x0.h>
|
||||
|
||||
#include "flowctrl.h"
|
||||
#include "iomap.h"
|
||||
@ -68,6 +70,64 @@ ENTRY(tegra_secondary_startup)
|
||||
b secondary_startup
|
||||
ENDPROC(tegra_secondary_startup)
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
/*
|
||||
* tegra_resume
|
||||
*
|
||||
* CPU boot vector when restarting the a CPU following
|
||||
* an LP2 transition. Also branched to by LP0 and LP1 resume after
|
||||
* re-enabling sdram.
|
||||
*/
|
||||
ENTRY(tegra_resume)
|
||||
bl v7_invalidate_l1
|
||||
/* Enable coresight */
|
||||
mov32 r0, 0xC5ACCE55
|
||||
mcr p14, 0, r0, c7, c12, 6
|
||||
|
||||
cpu_id r0
|
||||
cmp r0, #0 @ CPU0?
|
||||
bne cpu_resume @ no
|
||||
|
||||
#ifdef CONFIG_ARCH_TEGRA_3x_SOC
|
||||
/* Are we on Tegra20? */
|
||||
mov32 r6, TEGRA_APB_MISC_BASE
|
||||
ldr r0, [r6, #APB_MISC_GP_HIDREV]
|
||||
and r0, r0, #0xff00
|
||||
cmp r0, #(0x20 << 8)
|
||||
beq 1f @ Yes
|
||||
/* Clear the flow controller flags for this CPU. */
|
||||
mov32 r2, TEGRA_FLOW_CTRL_BASE + FLOW_CTRL_CPU0_CSR @ CPU0 CSR
|
||||
ldr r1, [r2]
|
||||
/* Clear event & intr flag */
|
||||
orr r1, r1, \
|
||||
#FLOW_CTRL_CSR_INTR_FLAG | FLOW_CTRL_CSR_EVENT_FLAG
|
||||
movw r0, #0x0FFD @ enable, cluster_switch, immed, & bitmaps
|
||||
bic r1, r1, r0
|
||||
str r1, [r2]
|
||||
1:
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_HAVE_ARM_SCU
|
||||
/* enable SCU */
|
||||
mov32 r0, TEGRA_ARM_PERIF_BASE
|
||||
ldr r1, [r0]
|
||||
orr r1, r1, #1
|
||||
str r1, [r0]
|
||||
#endif
|
||||
|
||||
/* L2 cache resume & re-enable */
|
||||
l2_cache_resume r0, r1, r2, l2x0_saved_regs_addr
|
||||
|
||||
b cpu_resume
|
||||
ENDPROC(tegra_resume)
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_CACHE_L2X0
|
||||
.globl l2x0_saved_regs_addr
|
||||
l2x0_saved_regs_addr:
|
||||
.long 0
|
||||
#endif
|
||||
|
||||
.align L1_CACHE_SHIFT
|
||||
ENTRY(__tegra_cpu_reset_handler_start)
|
||||
|
||||
@ -121,6 +181,17 @@ ENTRY(__tegra_cpu_reset_handler)
|
||||
1:
|
||||
#endif
|
||||
|
||||
/* Waking up from LP2? */
|
||||
ldr r9, [r12, #RESET_DATA(MASK_LP2)]
|
||||
tst r9, r11 @ if in_lp2
|
||||
beq __is_not_lp2
|
||||
ldr lr, [r12, #RESET_DATA(STARTUP_LP2)]
|
||||
cmp lr, #0
|
||||
bleq __die @ no LP2 startup handler
|
||||
bx lr
|
||||
|
||||
__is_not_lp2:
|
||||
|
||||
#ifdef CONFIG_SMP
|
||||
/*
|
||||
* Can only be secondary boot (initial or hotplug) but CPU 0
|
||||
|
216
arch/arm/mach-tegra/pm.c
Normal file
216
arch/arm/mach-tegra/pm.c
Normal file
@ -0,0 +1,216 @@
|
||||
/*
|
||||
* CPU complex suspend & resume functions for Tegra SoCs
|
||||
*
|
||||
* Copyright (c) 2009-2012, 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/cpumask.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/cpu_pm.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/err.h>
|
||||
|
||||
#include <asm/smp_plat.h>
|
||||
#include <asm/cacheflush.h>
|
||||
#include <asm/suspend.h>
|
||||
#include <asm/idmap.h>
|
||||
#include <asm/proc-fns.h>
|
||||
#include <asm/tlbflush.h>
|
||||
|
||||
#include "iomap.h"
|
||||
#include "reset.h"
|
||||
#include "flowctrl.h"
|
||||
#include "sleep.h"
|
||||
#include "tegra_cpu_car.h"
|
||||
|
||||
#define TEGRA_POWER_CPU_PWRREQ_OE (1 << 16) /* CPU pwr req enable */
|
||||
|
||||
#define PMC_CTRL 0x0
|
||||
#define PMC_CPUPWRGOOD_TIMER 0xc8
|
||||
#define PMC_CPUPWROFF_TIMER 0xcc
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static unsigned int g_diag_reg;
|
||||
static DEFINE_SPINLOCK(tegra_lp2_lock);
|
||||
static void __iomem *pmc = IO_ADDRESS(TEGRA_PMC_BASE);
|
||||
static struct clk *tegra_pclk;
|
||||
void (*tegra_tear_down_cpu)(void);
|
||||
|
||||
void save_cpu_arch_register(void)
|
||||
{
|
||||
/* read diagnostic register */
|
||||
asm("mrc p15, 0, %0, c15, c0, 1" : "=r"(g_diag_reg) : : "cc");
|
||||
return;
|
||||
}
|
||||
|
||||
void restore_cpu_arch_register(void)
|
||||
{
|
||||
/* write diagnostic register */
|
||||
asm("mcr p15, 0, %0, c15, c0, 1" : : "r"(g_diag_reg) : "cc");
|
||||
return;
|
||||
}
|
||||
|
||||
static void set_power_timers(unsigned long us_on, unsigned long us_off)
|
||||
{
|
||||
unsigned long long ticks;
|
||||
unsigned long long pclk;
|
||||
unsigned long rate;
|
||||
static unsigned long tegra_last_pclk;
|
||||
|
||||
if (tegra_pclk == NULL) {
|
||||
tegra_pclk = clk_get_sys(NULL, "pclk");
|
||||
WARN_ON(IS_ERR(tegra_pclk));
|
||||
}
|
||||
|
||||
rate = clk_get_rate(tegra_pclk);
|
||||
|
||||
if (WARN_ON_ONCE(rate <= 0))
|
||||
pclk = 100000000;
|
||||
else
|
||||
pclk = rate;
|
||||
|
||||
if ((rate != tegra_last_pclk)) {
|
||||
ticks = (us_on * pclk) + 999999ull;
|
||||
do_div(ticks, 1000000);
|
||||
writel((unsigned long)ticks, pmc + PMC_CPUPWRGOOD_TIMER);
|
||||
|
||||
ticks = (us_off * pclk) + 999999ull;
|
||||
do_div(ticks, 1000000);
|
||||
writel((unsigned long)ticks, pmc + PMC_CPUPWROFF_TIMER);
|
||||
wmb();
|
||||
}
|
||||
tegra_last_pclk = pclk;
|
||||
}
|
||||
|
||||
/*
|
||||
* restore_cpu_complex
|
||||
*
|
||||
* restores cpu clock setting, clears flow controller
|
||||
*
|
||||
* Always called on CPU 0.
|
||||
*/
|
||||
static void restore_cpu_complex(void)
|
||||
{
|
||||
int cpu = smp_processor_id();
|
||||
|
||||
BUG_ON(cpu != 0);
|
||||
|
||||
#ifdef CONFIG_SMP
|
||||
cpu = cpu_logical_map(cpu);
|
||||
#endif
|
||||
|
||||
/* Restore the CPU clock settings */
|
||||
tegra_cpu_clock_resume();
|
||||
|
||||
flowctrl_cpu_suspend_exit(cpu);
|
||||
|
||||
restore_cpu_arch_register();
|
||||
}
|
||||
|
||||
/*
|
||||
* suspend_cpu_complex
|
||||
*
|
||||
* saves pll state for use by restart_plls, prepares flow controller for
|
||||
* transition to suspend state
|
||||
*
|
||||
* Must always be called on cpu 0.
|
||||
*/
|
||||
static void suspend_cpu_complex(void)
|
||||
{
|
||||
int cpu = smp_processor_id();
|
||||
|
||||
BUG_ON(cpu != 0);
|
||||
|
||||
#ifdef CONFIG_SMP
|
||||
cpu = cpu_logical_map(cpu);
|
||||
#endif
|
||||
|
||||
/* Save the CPU clock settings */
|
||||
tegra_cpu_clock_suspend();
|
||||
|
||||
flowctrl_cpu_suspend_enter(cpu);
|
||||
|
||||
save_cpu_arch_register();
|
||||
}
|
||||
|
||||
void __cpuinit tegra_clear_cpu_in_lp2(int phy_cpu_id)
|
||||
{
|
||||
u32 *cpu_in_lp2 = tegra_cpu_lp2_mask;
|
||||
|
||||
spin_lock(&tegra_lp2_lock);
|
||||
|
||||
BUG_ON(!(*cpu_in_lp2 & BIT(phy_cpu_id)));
|
||||
*cpu_in_lp2 &= ~BIT(phy_cpu_id);
|
||||
|
||||
spin_unlock(&tegra_lp2_lock);
|
||||
}
|
||||
|
||||
bool __cpuinit tegra_set_cpu_in_lp2(int phy_cpu_id)
|
||||
{
|
||||
bool last_cpu = false;
|
||||
cpumask_t *cpu_lp2_mask = tegra_cpu_lp2_mask;
|
||||
u32 *cpu_in_lp2 = tegra_cpu_lp2_mask;
|
||||
|
||||
spin_lock(&tegra_lp2_lock);
|
||||
|
||||
BUG_ON((*cpu_in_lp2 & BIT(phy_cpu_id)));
|
||||
*cpu_in_lp2 |= BIT(phy_cpu_id);
|
||||
|
||||
if ((phy_cpu_id == 0) && cpumask_equal(cpu_lp2_mask, cpu_online_mask))
|
||||
last_cpu = true;
|
||||
|
||||
spin_unlock(&tegra_lp2_lock);
|
||||
return last_cpu;
|
||||
}
|
||||
|
||||
static int tegra_sleep_cpu(unsigned long v2p)
|
||||
{
|
||||
/* Switch to the identity mapping. */
|
||||
cpu_switch_mm(idmap_pgd, &init_mm);
|
||||
|
||||
/* Flush the TLB. */
|
||||
local_flush_tlb_all();
|
||||
|
||||
tegra_sleep_cpu_finish(v2p);
|
||||
|
||||
/* should never here */
|
||||
BUG();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void tegra_idle_lp2_last(u32 cpu_on_time, u32 cpu_off_time)
|
||||
{
|
||||
u32 mode;
|
||||
|
||||
/* Only the last cpu down does the final suspend steps */
|
||||
mode = readl(pmc + PMC_CTRL);
|
||||
mode |= TEGRA_POWER_CPU_PWRREQ_OE;
|
||||
writel(mode, pmc + PMC_CTRL);
|
||||
|
||||
set_power_timers(cpu_on_time, cpu_off_time);
|
||||
|
||||
cpu_cluster_pm_enter();
|
||||
suspend_cpu_complex();
|
||||
|
||||
cpu_suspend(PHYS_OFFSET - PAGE_OFFSET, &tegra_sleep_cpu);
|
||||
|
||||
restore_cpu_complex();
|
||||
cpu_cluster_pm_exit();
|
||||
}
|
||||
#endif
|
35
arch/arm/mach-tegra/pm.h
Normal file
35
arch/arm/mach-tegra/pm.h
Normal file
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (C) 2010 Google, Inc.
|
||||
* Copyright (c) 2010-2012 NVIDIA Corporation. All rights reserved.
|
||||
*
|
||||
* Author:
|
||||
* Colin Cross <ccross@google.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef _MACH_TEGRA_PM_H_
|
||||
#define _MACH_TEGRA_PM_H_
|
||||
|
||||
extern unsigned long l2x0_saved_regs_addr;
|
||||
|
||||
void save_cpu_arch_register(void);
|
||||
void restore_cpu_arch_register(void);
|
||||
|
||||
void tegra_clear_cpu_in_lp2(int phy_cpu_id);
|
||||
bool tegra_set_cpu_in_lp2(int phy_cpu_id);
|
||||
|
||||
void tegra_idle_lp2_last(u32 cpu_on_time, u32 cpu_off_time);
|
||||
extern void (*tegra_tear_down_cpu)(void);
|
||||
|
||||
#endif /* _MACH_TEGRA_PM_H_ */
|
@ -25,6 +25,7 @@
|
||||
#include "iomap.h"
|
||||
#include "irammap.h"
|
||||
#include "reset.h"
|
||||
#include "sleep.h"
|
||||
#include "fuse.h"
|
||||
|
||||
#define TEGRA_IRAM_RESET_BASE (TEGRA_IRAM_BASE + \
|
||||
@ -79,5 +80,10 @@ void __init tegra_cpu_reset_handler_init(void)
|
||||
virt_to_phys((void *)tegra_secondary_startup);
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
__tegra_cpu_reset_handler_data[TEGRA_RESET_STARTUP_LP2] =
|
||||
virt_to_phys((void *)tegra_resume);
|
||||
#endif
|
||||
|
||||
tegra_cpu_reset_handler_enable();
|
||||
}
|
||||
|
@ -29,6 +29,8 @@
|
||||
|
||||
#ifndef __ASSEMBLY__
|
||||
|
||||
#include "irammap.h"
|
||||
|
||||
extern unsigned long __tegra_cpu_reset_handler_data[TEGRA_RESET_DATA_SIZE];
|
||||
|
||||
void __tegra_cpu_reset_handler_start(void);
|
||||
@ -36,6 +38,13 @@ void __tegra_cpu_reset_handler(void);
|
||||
void __tegra_cpu_reset_handler_end(void);
|
||||
void tegra_secondary_startup(void);
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
#define tegra_cpu_lp2_mask \
|
||||
(IO_ADDRESS(TEGRA_IRAM_BASE + TEGRA_IRAM_RESET_HANDLER_OFFSET + \
|
||||
((u32)&__tegra_cpu_reset_handler_data[TEGRA_RESET_MASK_LP2] - \
|
||||
(u32)__tegra_cpu_reset_handler_start)))
|
||||
#endif
|
||||
|
||||
#define tegra_cpu_reset_handler_offset \
|
||||
((u32)__tegra_cpu_reset_handler - \
|
||||
(u32)__tegra_cpu_reset_handler_start)
|
||||
|
@ -17,6 +17,7 @@
|
||||
#include <linux/linkage.h>
|
||||
|
||||
#include <asm/assembler.h>
|
||||
#include <asm/asm-offsets.h>
|
||||
|
||||
#include "sleep.h"
|
||||
#include "flowctrl.h"
|
||||
@ -80,6 +81,7 @@ delay_1:
|
||||
ldr r3, [r1] @ read CSR
|
||||
str r3, [r1] @ clear CSR
|
||||
tst r0, #TEGRA30_POWER_HOTPLUG_SHUTDOWN
|
||||
moveq r3, #FLOW_CTRL_WAIT_FOR_INTERRUPT @ For LP2
|
||||
movne r3, #FLOW_CTRL_WAITEVENT @ For hotplug
|
||||
str r3, [r2]
|
||||
ldr r0, [r2]
|
||||
@ -103,3 +105,67 @@ wfe_war:
|
||||
|
||||
ENDPROC(tegra30_cpu_shutdown)
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
/*
|
||||
* tegra30_sleep_cpu_secondary_finish(unsigned long v2p)
|
||||
*
|
||||
* Enters LP2 on secondary CPU by exiting coherency and powergating the CPU.
|
||||
*/
|
||||
ENTRY(tegra30_sleep_cpu_secondary_finish)
|
||||
mov r7, lr
|
||||
|
||||
/* Flush and disable the L1 data cache */
|
||||
bl tegra_disable_clean_inv_dcache
|
||||
|
||||
/* Powergate this CPU. */
|
||||
mov r0, #0 @ power mode flags (!hotplug)
|
||||
bl tegra30_cpu_shutdown
|
||||
mov r0, #1 @ never return here
|
||||
mov pc, r7
|
||||
ENDPROC(tegra30_sleep_cpu_secondary_finish)
|
||||
|
||||
/*
|
||||
* tegra30_tear_down_cpu
|
||||
*
|
||||
* Switches the CPU to enter sleep.
|
||||
*/
|
||||
ENTRY(tegra30_tear_down_cpu)
|
||||
mov32 r6, TEGRA_FLOW_CTRL_BASE
|
||||
|
||||
b tegra30_enter_sleep
|
||||
ENDPROC(tegra30_tear_down_cpu)
|
||||
|
||||
/*
|
||||
* tegra30_enter_sleep
|
||||
*
|
||||
* uses flow controller to enter sleep state
|
||||
* executes from IRAM with SDRAM in selfrefresh when target state is LP0 or LP1
|
||||
* executes from SDRAM with target state is LP2
|
||||
* r6 = TEGRA_FLOW_CTRL_BASE
|
||||
*/
|
||||
tegra30_enter_sleep:
|
||||
cpu_id r1
|
||||
|
||||
cpu_to_csr_reg r2, r1
|
||||
ldr r0, [r6, r2]
|
||||
orr r0, r0, #FLOW_CTRL_CSR_INTR_FLAG | FLOW_CTRL_CSR_EVENT_FLAG
|
||||
orr r0, r0, #FLOW_CTRL_CSR_ENABLE
|
||||
str r0, [r6, r2]
|
||||
|
||||
mov r0, #FLOW_CTRL_WAIT_FOR_INTERRUPT
|
||||
orr r0, r0, #FLOW_CTRL_HALT_CPU_IRQ | FLOW_CTRL_HALT_CPU_FIQ
|
||||
cpu_to_halt_reg r2, r1
|
||||
str r0, [r6, r2]
|
||||
dsb
|
||||
ldr r0, [r6, r2] /* memory barrier */
|
||||
|
||||
halted:
|
||||
isb
|
||||
dsb
|
||||
wfi /* CPU should be power gated here */
|
||||
|
||||
/* !!!FIXME!!! Implement halt failure handler */
|
||||
b halted
|
||||
|
||||
#endif
|
@ -25,9 +25,87 @@
|
||||
#include <linux/linkage.h>
|
||||
|
||||
#include <asm/assembler.h>
|
||||
#include <asm/cache.h>
|
||||
#include <asm/cp15.h>
|
||||
#include <asm/hardware/cache-l2x0.h>
|
||||
|
||||
#include "iomap.h"
|
||||
|
||||
#include "flowctrl.h"
|
||||
#include "sleep.h"
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
/*
|
||||
* tegra_disable_clean_inv_dcache
|
||||
*
|
||||
* disable, clean & invalidate the D-cache
|
||||
*
|
||||
* Corrupted registers: r1-r3, r6, r8, r9-r11
|
||||
*/
|
||||
ENTRY(tegra_disable_clean_inv_dcache)
|
||||
stmfd sp!, {r0, r4-r5, r7, r9-r11, lr}
|
||||
dmb @ ensure ordering
|
||||
|
||||
/* Disable the D-cache */
|
||||
mrc p15, 0, r2, c1, c0, 0
|
||||
bic r2, r2, #CR_C
|
||||
mcr p15, 0, r2, c1, c0, 0
|
||||
isb
|
||||
|
||||
/* Flush the D-cache */
|
||||
bl v7_flush_dcache_louis
|
||||
|
||||
/* Trun off coherency */
|
||||
exit_smp r4, r5
|
||||
|
||||
ldmfd sp!, {r0, r4-r5, r7, r9-r11, pc}
|
||||
ENDPROC(tegra_disable_clean_inv_dcache)
|
||||
|
||||
/*
|
||||
* tegra_sleep_cpu_finish(unsigned long v2p)
|
||||
*
|
||||
* enters suspend in LP2 by turning off the mmu and jumping to
|
||||
* tegra?_tear_down_cpu
|
||||
*/
|
||||
ENTRY(tegra_sleep_cpu_finish)
|
||||
/* Flush and disable the L1 data cache */
|
||||
bl tegra_disable_clean_inv_dcache
|
||||
|
||||
mov32 r6, tegra_tear_down_cpu
|
||||
ldr r1, [r6]
|
||||
add r1, r1, r0
|
||||
|
||||
mov32 r3, tegra_shut_off_mmu
|
||||
add r3, r3, r0
|
||||
mov r0, r1
|
||||
|
||||
mov pc, r3
|
||||
ENDPROC(tegra_sleep_cpu_finish)
|
||||
|
||||
/*
|
||||
* tegra_shut_off_mmu
|
||||
*
|
||||
* r0 = physical address to jump to with mmu off
|
||||
*
|
||||
* called with VA=PA mapping
|
||||
* turns off MMU, icache, dcache and branch prediction
|
||||
*/
|
||||
.align L1_CACHE_SHIFT
|
||||
.pushsection .idmap.text, "ax"
|
||||
ENTRY(tegra_shut_off_mmu)
|
||||
mrc p15, 0, r3, c1, c0, 0
|
||||
movw r2, #CR_I | CR_Z | CR_C | CR_M
|
||||
bic r3, r3, r2
|
||||
dsb
|
||||
mcr p15, 0, r3, c1, c0, 0
|
||||
isb
|
||||
#ifdef CONFIG_CACHE_L2X0
|
||||
/* Disable L2 cache */
|
||||
mov32 r4, TEGRA_ARM_PERIF_BASE + 0x3000
|
||||
mov r5, #0
|
||||
str r5, [r4, #L2X0_CTRL]
|
||||
#endif
|
||||
mov pc, r0
|
||||
ENDPROC(tegra_shut_off_mmu)
|
||||
.popsection
|
||||
#endif
|
||||
|
@ -71,7 +71,41 @@
|
||||
str \tmp2, [\tmp1] @ invalidate SCU tags for CPU
|
||||
dsb
|
||||
.endm
|
||||
|
||||
/* Macro to resume & re-enable L2 cache */
|
||||
#ifndef L2X0_CTRL_EN
|
||||
#define L2X0_CTRL_EN 1
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_CACHE_L2X0
|
||||
.macro l2_cache_resume, tmp1, tmp2, tmp3, phys_l2x0_saved_regs
|
||||
adr \tmp1, \phys_l2x0_saved_regs
|
||||
ldr \tmp1, [\tmp1]
|
||||
ldr \tmp2, [\tmp1, #L2X0_R_PHY_BASE]
|
||||
ldr \tmp3, [\tmp2, #L2X0_CTRL]
|
||||
tst \tmp3, #L2X0_CTRL_EN
|
||||
bne exit_l2_resume
|
||||
ldr \tmp3, [\tmp1, #L2X0_R_TAG_LATENCY]
|
||||
str \tmp3, [\tmp2, #L2X0_TAG_LATENCY_CTRL]
|
||||
ldr \tmp3, [\tmp1, #L2X0_R_DATA_LATENCY]
|
||||
str \tmp3, [\tmp2, #L2X0_DATA_LATENCY_CTRL]
|
||||
ldr \tmp3, [\tmp1, #L2X0_R_PREFETCH_CTRL]
|
||||
str \tmp3, [\tmp2, #L2X0_PREFETCH_CTRL]
|
||||
ldr \tmp3, [\tmp1, #L2X0_R_PWR_CTRL]
|
||||
str \tmp3, [\tmp2, #L2X0_POWER_CTRL]
|
||||
ldr \tmp3, [\tmp1, #L2X0_R_AUX_CTRL]
|
||||
str \tmp3, [\tmp2, #L2X0_AUX_CTRL]
|
||||
mov \tmp3, #L2X0_CTRL_EN
|
||||
str \tmp3, [\tmp2, #L2X0_CTRL]
|
||||
exit_l2_resume:
|
||||
.endm
|
||||
#else /* CONFIG_CACHE_L2X0 */
|
||||
.macro l2_cache_resume, tmp1, tmp2, tmp3, phys_l2x0_saved_regs
|
||||
.endm
|
||||
#endif /* CONFIG_CACHE_L2X0 */
|
||||
#else
|
||||
void tegra_resume(void);
|
||||
int tegra_sleep_cpu_finish(unsigned long);
|
||||
|
||||
#ifdef CONFIG_HOTPLUG_CPU
|
||||
void tegra20_hotplug_init(void);
|
||||
@ -81,5 +115,8 @@ static inline void tegra20_hotplug_init(void) {}
|
||||
static inline void tegra30_hotplug_init(void) {}
|
||||
#endif
|
||||
|
||||
int tegra30_sleep_cpu_secondary_finish(unsigned long);
|
||||
void tegra30_tear_down_cpu(void);
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
@ -31,6 +31,8 @@
|
||||
|
||||
#include <asm/clkdev.h>
|
||||
|
||||
#include <mach/powergate.h>
|
||||
|
||||
#include "clock.h"
|
||||
#include "fuse.h"
|
||||
#include "iomap.h"
|
||||
@ -309,6 +311,31 @@
|
||||
#define CPU_CLOCK(cpu) (0x1 << (8 + cpu))
|
||||
#define CPU_RESET(cpu) (0x1111ul << (cpu))
|
||||
|
||||
#define CLK_RESET_CCLK_BURST 0x20
|
||||
#define CLK_RESET_CCLK_DIVIDER 0x24
|
||||
#define CLK_RESET_PLLX_BASE 0xe0
|
||||
#define CLK_RESET_PLLX_MISC 0xe4
|
||||
|
||||
#define CLK_RESET_SOURCE_CSITE 0x1d4
|
||||
|
||||
#define CLK_RESET_CCLK_BURST_POLICY_SHIFT 28
|
||||
#define CLK_RESET_CCLK_RUN_POLICY_SHIFT 4
|
||||
#define CLK_RESET_CCLK_IDLE_POLICY_SHIFT 0
|
||||
#define CLK_RESET_CCLK_IDLE_POLICY 1
|
||||
#define CLK_RESET_CCLK_RUN_POLICY 2
|
||||
#define CLK_RESET_CCLK_BURST_POLICY_PLLX 8
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static struct cpu_clk_suspend_context {
|
||||
u32 pllx_misc;
|
||||
u32 pllx_base;
|
||||
|
||||
u32 cpu_burst;
|
||||
u32 clk_csite_src;
|
||||
u32 cclk_divider;
|
||||
} tegra30_cpu_clk_sctx;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Structure defining the fields for USB UTMI clocks Parameters.
|
||||
*/
|
||||
@ -2386,12 +2413,93 @@ static void tegra30_disable_cpu_clock(u32 cpu)
|
||||
reg_clk_base + TEGRA_CLK_RST_CONTROLLER_CLK_CPU_CMPLX);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static bool tegra30_cpu_rail_off_ready(void)
|
||||
{
|
||||
unsigned int cpu_rst_status;
|
||||
int cpu_pwr_status;
|
||||
|
||||
cpu_rst_status = readl(reg_clk_base +
|
||||
TEGRA30_CLK_RST_CONTROLLER_CPU_CMPLX_STATUS);
|
||||
cpu_pwr_status = tegra_powergate_is_powered(TEGRA_POWERGATE_CPU1) ||
|
||||
tegra_powergate_is_powered(TEGRA_POWERGATE_CPU2) ||
|
||||
tegra_powergate_is_powered(TEGRA_POWERGATE_CPU3);
|
||||
|
||||
if (((cpu_rst_status & 0xE) != 0xE) || cpu_pwr_status)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void tegra30_cpu_clock_suspend(void)
|
||||
{
|
||||
/* switch coresite to clk_m, save off original source */
|
||||
tegra30_cpu_clk_sctx.clk_csite_src =
|
||||
readl(reg_clk_base + CLK_RESET_SOURCE_CSITE);
|
||||
writel(3<<30, reg_clk_base + CLK_RESET_SOURCE_CSITE);
|
||||
|
||||
tegra30_cpu_clk_sctx.cpu_burst =
|
||||
readl(reg_clk_base + CLK_RESET_CCLK_BURST);
|
||||
tegra30_cpu_clk_sctx.pllx_base =
|
||||
readl(reg_clk_base + CLK_RESET_PLLX_BASE);
|
||||
tegra30_cpu_clk_sctx.pllx_misc =
|
||||
readl(reg_clk_base + CLK_RESET_PLLX_MISC);
|
||||
tegra30_cpu_clk_sctx.cclk_divider =
|
||||
readl(reg_clk_base + CLK_RESET_CCLK_DIVIDER);
|
||||
}
|
||||
|
||||
static void tegra30_cpu_clock_resume(void)
|
||||
{
|
||||
unsigned int reg, policy;
|
||||
|
||||
/* Is CPU complex already running on PLLX? */
|
||||
reg = readl(reg_clk_base + CLK_RESET_CCLK_BURST);
|
||||
policy = (reg >> CLK_RESET_CCLK_BURST_POLICY_SHIFT) & 0xF;
|
||||
|
||||
if (policy == CLK_RESET_CCLK_IDLE_POLICY)
|
||||
reg = (reg >> CLK_RESET_CCLK_IDLE_POLICY_SHIFT) & 0xF;
|
||||
else if (policy == CLK_RESET_CCLK_RUN_POLICY)
|
||||
reg = (reg >> CLK_RESET_CCLK_RUN_POLICY_SHIFT) & 0xF;
|
||||
else
|
||||
BUG();
|
||||
|
||||
if (reg != CLK_RESET_CCLK_BURST_POLICY_PLLX) {
|
||||
/* restore PLLX settings if CPU is on different PLL */
|
||||
writel(tegra30_cpu_clk_sctx.pllx_misc,
|
||||
reg_clk_base + CLK_RESET_PLLX_MISC);
|
||||
writel(tegra30_cpu_clk_sctx.pllx_base,
|
||||
reg_clk_base + CLK_RESET_PLLX_BASE);
|
||||
|
||||
/* wait for PLL stabilization if PLLX was enabled */
|
||||
if (tegra30_cpu_clk_sctx.pllx_base & (1 << 30))
|
||||
udelay(300);
|
||||
}
|
||||
|
||||
/*
|
||||
* Restore original burst policy setting for calls resulting from CPU
|
||||
* LP2 in idle or system suspend.
|
||||
*/
|
||||
writel(tegra30_cpu_clk_sctx.cclk_divider,
|
||||
reg_clk_base + CLK_RESET_CCLK_DIVIDER);
|
||||
writel(tegra30_cpu_clk_sctx.cpu_burst,
|
||||
reg_clk_base + CLK_RESET_CCLK_BURST);
|
||||
|
||||
writel(tegra30_cpu_clk_sctx.clk_csite_src,
|
||||
reg_clk_base + CLK_RESET_SOURCE_CSITE);
|
||||
}
|
||||
#endif
|
||||
|
||||
static struct tegra_cpu_car_ops tegra30_cpu_car_ops = {
|
||||
.wait_for_reset = tegra30_wait_cpu_in_reset,
|
||||
.put_in_reset = tegra30_put_cpu_in_reset,
|
||||
.out_of_reset = tegra30_cpu_out_of_reset,
|
||||
.enable_clock = tegra30_enable_cpu_clock,
|
||||
.disable_clock = tegra30_disable_cpu_clock,
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
.rail_off_ready = tegra30_cpu_rail_off_ready,
|
||||
.suspend = tegra30_cpu_clock_suspend,
|
||||
.resume = tegra30_cpu_clock_resume,
|
||||
#endif
|
||||
};
|
||||
|
||||
void __init tegra30_cpu_car_ops_init(void)
|
||||
|
@ -30,6 +30,12 @@
|
||||
* CPU clock un-gate
|
||||
* disable_clock:
|
||||
* CPU clock gate
|
||||
* rail_off_ready:
|
||||
* CPU is ready for rail off
|
||||
* suspend:
|
||||
* save the clock settings when CPU go into low-power state
|
||||
* resume:
|
||||
* restore the clock settings when CPU exit low-power state
|
||||
*/
|
||||
struct tegra_cpu_car_ops {
|
||||
void (*wait_for_reset)(u32 cpu);
|
||||
@ -37,6 +43,11 @@ struct tegra_cpu_car_ops {
|
||||
void (*out_of_reset)(u32 cpu);
|
||||
void (*enable_clock)(u32 cpu);
|
||||
void (*disable_clock)(u32 cpu);
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
bool (*rail_off_ready)(void);
|
||||
void (*suspend)(void);
|
||||
void (*resume)(void);
|
||||
#endif
|
||||
};
|
||||
|
||||
extern struct tegra_cpu_car_ops *tegra_cpu_car_ops;
|
||||
@ -81,6 +92,32 @@ static inline void tegra_disable_cpu_clock(u32 cpu)
|
||||
tegra_cpu_car_ops->disable_clock(cpu);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static inline bool tegra_cpu_rail_off_ready(void)
|
||||
{
|
||||
if (WARN_ON(!tegra_cpu_car_ops->rail_off_ready))
|
||||
return false;
|
||||
|
||||
return tegra_cpu_car_ops->rail_off_ready();
|
||||
}
|
||||
|
||||
static inline void tegra_cpu_clock_suspend(void)
|
||||
{
|
||||
if (WARN_ON(!tegra_cpu_car_ops->suspend))
|
||||
return;
|
||||
|
||||
tegra_cpu_car_ops->suspend();
|
||||
}
|
||||
|
||||
static inline void tegra_cpu_clock_resume(void)
|
||||
{
|
||||
if (WARN_ON(!tegra_cpu_car_ops->resume))
|
||||
return;
|
||||
|
||||
tegra_cpu_car_ops->resume();
|
||||
}
|
||||
#endif
|
||||
|
||||
void tegra20_cpu_car_ops_init(void);
|
||||
void tegra30_cpu_car_ops_init(void);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user