diff options
Diffstat (limited to 'arch/arm/mach-tegra')
32 files changed, 4509 insertions, 0 deletions
diff --git a/arch/arm/mach-tegra/Kconfig b/arch/arm/mach-tegra/Kconfig new file mode 100644 index 000000000..5d1a318f1 --- /dev/null +++ b/arch/arm/mach-tegra/Kconfig @@ -0,0 +1,67 @@ +menuconfig ARCH_TEGRA + bool "NVIDIA Tegra" if ARCH_MULTI_V7 + select ARCH_REQUIRE_GPIOLIB + select ARCH_SUPPORTS_TRUSTED_FOUNDATIONS + select ARM_AMBA + select ARM_GIC + select CLKSRC_MMIO + select HAVE_ARM_SCU if SMP + select HAVE_ARM_TWD if SMP + select PINCTRL + select ARCH_HAS_RESET_CONTROLLER + select RESET_CONTROLLER + select SOC_BUS + select USB_ULPI if USB_PHY + select USB_ULPI_VIEWPORT if USB_PHY + help + This enables support for NVIDIA Tegra based systems. + +if ARCH_TEGRA + +config ARCH_TEGRA_2x_SOC + bool "Enable support for Tegra20 family" + select ARCH_NEEDS_CPU_IDLE_COUPLED if SMP + select ARM_ERRATA_720789 + select ARM_ERRATA_754327 if SMP + select ARM_ERRATA_764369 if SMP + select PINCTRL_TEGRA20 + select PL310_ERRATA_727915 if CACHE_L2X0 + select PL310_ERRATA_769419 if CACHE_L2X0 + select TEGRA_TIMER + help + Support for NVIDIA Tegra AP20 and T20 processors, based on the + ARM CortexA9MP CPU and the ARM PL310 L2 cache controller + +config ARCH_TEGRA_3x_SOC + bool "Enable support for Tegra30 family" + select ARM_ERRATA_754322 + select ARM_ERRATA_764369 if SMP + select PINCTRL_TEGRA30 + select PL310_ERRATA_769419 if CACHE_L2X0 + select TEGRA_TIMER + help + Support for NVIDIA Tegra T30 processor family, based on the + ARM CortexA9MP CPU and the ARM PL310 L2 cache controller + +config ARCH_TEGRA_114_SOC + bool "Enable support for Tegra114 family" + select ARM_ERRATA_798181 if SMP + select ARM_L1_CACHE_SHIFT_6 + select HAVE_ARM_ARCH_TIMER + select PINCTRL_TEGRA114 + select TEGRA_TIMER + help + Support for NVIDIA Tegra T114 processor family, based on the + ARM CortexA15MP CPU + +config ARCH_TEGRA_124_SOC + bool "Enable support for Tegra124 family" + select ARM_L1_CACHE_SHIFT_6 + select HAVE_ARM_ARCH_TIMER + select PINCTRL_TEGRA124 + select TEGRA_TIMER + help + Support for NVIDIA Tegra T124 processor family, based on the + ARM CortexA15MP CPU + +endif diff --git a/arch/arm/mach-tegra/Makefile b/arch/arm/mach-tegra/Makefile new file mode 100644 index 000000000..e48a74458 --- /dev/null +++ b/arch/arm/mach-tegra/Makefile @@ -0,0 +1,36 @@ +asflags-y += -march=armv7-a + +obj-y += io.o +obj-y += irq.o +obj-y += flowctrl.o +obj-y += pm.o +obj-y += reset.o +obj-y += reset-handler.o +obj-y += sleep.o +obj-y += tegra.o +obj-$(CONFIG_CPU_IDLE) += cpuidle.o +obj-$(CONFIG_ARCH_TEGRA_2x_SOC) += sleep-tegra20.o +obj-$(CONFIG_ARCH_TEGRA_2x_SOC) += pm-tegra20.o +ifeq ($(CONFIG_CPU_IDLE),y) +obj-$(CONFIG_ARCH_TEGRA_2x_SOC) += cpuidle-tegra20.o +endif +obj-$(CONFIG_ARCH_TEGRA_3x_SOC) += sleep-tegra30.o +obj-$(CONFIG_ARCH_TEGRA_3x_SOC) += pm-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_HOTPLUG_CPU) += hotplug.o + +obj-$(CONFIG_ARCH_TEGRA_114_SOC) += sleep-tegra30.o +obj-$(CONFIG_ARCH_TEGRA_114_SOC) += pm-tegra30.o +ifeq ($(CONFIG_CPU_IDLE),y) +obj-$(CONFIG_ARCH_TEGRA_114_SOC) += cpuidle-tegra114.o +endif +obj-$(CONFIG_ARCH_TEGRA_124_SOC) += sleep-tegra30.o +obj-$(CONFIG_ARCH_TEGRA_124_SOC) += pm-tegra30.o +ifeq ($(CONFIG_CPU_IDLE),y) +obj-$(CONFIG_ARCH_TEGRA_124_SOC) += cpuidle-tegra114.o +endif + +obj-$(CONFIG_ARCH_TEGRA_2x_SOC) += board-paz00.o diff --git a/arch/arm/mach-tegra/board-paz00.c b/arch/arm/mach-tegra/board-paz00.c new file mode 100644 index 000000000..fbe74c680 --- /dev/null +++ b/arch/arm/mach-tegra/board-paz00.c @@ -0,0 +1,52 @@ +/* + * arch/arm/mach-tegra/board-paz00.c + * + * Copyright (C) 2011 Marc Dietrich <marvin24@gmx.de> + * + * Based on board-harmony.c + * Copyright (C) 2010 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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/gpio/machine.h> +#include <linux/platform_device.h> +#include <linux/rfkill-gpio.h> + +#include "board.h" + +static struct rfkill_gpio_platform_data wifi_rfkill_platform_data = { + .name = "wifi_rfkill", + .type = RFKILL_TYPE_WLAN, +}; + +static struct platform_device wifi_rfkill_device = { + .name = "rfkill_gpio", + .id = -1, + .dev = { + .platform_data = &wifi_rfkill_platform_data, + }, +}; + +static struct gpiod_lookup_table wifi_gpio_lookup = { + .dev_id = "rfkill_gpio", + .table = { + GPIO_LOOKUP_IDX("tegra-gpio", 25, NULL, 0, 0), + GPIO_LOOKUP_IDX("tegra-gpio", 85, NULL, 1, 0), + { }, + }, +}; + +void __init tegra_paz00_wifikill_init(void) +{ + gpiod_add_lookup_table(&wifi_gpio_lookup); + platform_device_register(&wifi_rfkill_device); +} diff --git a/arch/arm/mach-tegra/board.h b/arch/arm/mach-tegra/board.h new file mode 100644 index 000000000..da90c8929 --- /dev/null +++ b/arch/arm/mach-tegra/board.h @@ -0,0 +1,33 @@ +/* + * arch/arm/mach-tegra/board.h + * + * Copyright (c) 2013 NVIDIA Corporation. All rights reserved. + * Copyright (C) 2010 Google, Inc. + * + * Author: + * Colin Cross <ccross@google.com> + * Erik Gilling <konkers@google.com> + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#ifndef __MACH_TEGRA_BOARD_H +#define __MACH_TEGRA_BOARD_H + +#include <linux/types.h> +#include <linux/reboot.h> + +void __init tegra_map_common_io(void); +void __init tegra_init_irq(void); + +void __init tegra_paz00_wifikill_init(void); + +#endif diff --git a/arch/arm/mach-tegra/common.h b/arch/arm/mach-tegra/common.h new file mode 100644 index 000000000..5900cc44f --- /dev/null +++ b/arch/arm/mach-tegra/common.h @@ -0,0 +1,4 @@ +extern struct smp_operations tegra_smp_ops; + +extern int tegra_cpu_kill(unsigned int cpu); +extern void tegra_cpu_die(unsigned int cpu); diff --git a/arch/arm/mach-tegra/cpuidle-tegra114.c b/arch/arm/mach-tegra/cpuidle-tegra114.c new file mode 100644 index 000000000..155807fa6 --- /dev/null +++ b/arch/arm/mach-tegra/cpuidle-tegra114.c @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2013, 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 <asm/firmware.h> +#include <linux/tick.h> +#include <linux/cpuidle.h> +#include <linux/cpu_pm.h> +#include <linux/kernel.h> +#include <linux/module.h> + +#include <asm/cpuidle.h> +#include <asm/smp_plat.h> +#include <asm/suspend.h> + +#include "pm.h" +#include "sleep.h" + +#ifdef CONFIG_PM_SLEEP +#define TEGRA114_MAX_STATES 2 +#else +#define TEGRA114_MAX_STATES 1 +#endif + +#ifdef CONFIG_PM_SLEEP +static int tegra114_idle_power_down(struct cpuidle_device *dev, + struct cpuidle_driver *drv, + int index) +{ + local_fiq_disable(); + + tegra_set_cpu_in_lp2(); + cpu_pm_enter(); + + tick_broadcast_enter(); + + call_firmware_op(prepare_idle); + + /* Do suspend by ourselves if the firmware does not implement it */ + if (call_firmware_op(do_idle, 0) == -ENOSYS) + cpu_suspend(0, tegra30_sleep_cpu_secondary_finish); + + tick_broadcast_exit(); + + cpu_pm_exit(); + tegra_clear_cpu_in_lp2(); + + local_fiq_enable(); + + return index; +} +#endif + +static struct cpuidle_driver tegra_idle_driver = { + .name = "tegra_idle", + .owner = THIS_MODULE, + .state_count = TEGRA114_MAX_STATES, + .states = { + [0] = ARM_CPUIDLE_WFI_STATE_PWR(600), +#ifdef CONFIG_PM_SLEEP + [1] = { + .enter = tegra114_idle_power_down, + .exit_latency = 500, + .target_residency = 1000, + .power_usage = 0, + .name = "powered-down", + .desc = "CPU power gated", + }, +#endif + }, +}; + +int __init tegra114_cpuidle_init(void) +{ + return cpuidle_register(&tegra_idle_driver, NULL); +} diff --git a/arch/arm/mach-tegra/cpuidle-tegra20.c b/arch/arm/mach-tegra/cpuidle-tegra20.c new file mode 100644 index 000000000..7469347b1 --- /dev/null +++ b/arch/arm/mach-tegra/cpuidle-tegra20.c @@ -0,0 +1,226 @@ +/* + * 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/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 <asm/cpuidle.h> +#include <asm/smp_plat.h> +#include <asm/suspend.h> + +#include "flowctrl.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, + .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; + + tick_broadcast_enter(); + + tegra_idle_lp2_last(); + + tick_broadcast_exit(); + + 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) +{ + tick_broadcast_enter(); + + cpu_suspend(0, tegra20_sleep_cpu_secondary_finish); + + tegra20_cpu_clear_resettable(); + + tick_broadcast_exit(); + + 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()) + ACCESS_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); +} diff --git a/arch/arm/mach-tegra/cpuidle-tegra30.c b/arch/arm/mach-tegra/cpuidle-tegra30.c new file mode 100644 index 000000000..4dbe1dae9 --- /dev/null +++ b/arch/arm/mach-tegra/cpuidle-tegra30.c @@ -0,0 +1,147 @@ +/* + * 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/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 <asm/cpuidle.h> +#include <asm/smp_plat.h> +#include <asm/suspend.h> + +#include "pm.h" +#include "sleep.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, +#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, + .name = "powered-down", + .desc = "CPU power gated", + }, +#endif + }, +}; + +#ifdef CONFIG_PM_SLEEP +static bool tegra30_cpu_cluster_power_down(struct cpuidle_device *dev, + struct cpuidle_driver *drv, + int index) +{ + /* 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; + } + + tick_broadcast_enter(); + + tegra_idle_lp2_last(); + + tick_broadcast_exit(); + + return true; +} + +#ifdef CONFIG_SMP +static bool tegra30_cpu_core_power_down(struct cpuidle_device *dev, + struct cpuidle_driver *drv, + int index) +{ + tick_broadcast_enter(); + + smp_wmb(); + + cpu_suspend(0, tegra30_sleep_cpu_secondary_finish); + + tick_broadcast_exit(); + + 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 tegra30_idle_lp2(struct cpuidle_device *dev, + struct cpuidle_driver *drv, + int index) +{ + bool entered_lp2 = false; + bool last_cpu; + + local_fiq_disable(); + + last_cpu = tegra_set_cpu_in_lp2(); + cpu_pm_enter(); + + if (dev->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(); + + local_fiq_enable(); + + smp_rmb(); + + return (entered_lp2) ? index : 0; +} +#endif + +int __init tegra30_cpuidle_init(void) +{ + return cpuidle_register(&tegra_idle_driver, NULL); +} diff --git a/arch/arm/mach-tegra/cpuidle.c b/arch/arm/mach-tegra/cpuidle.c new file mode 100644 index 000000000..316563141 --- /dev/null +++ b/arch/arm/mach-tegra/cpuidle.c @@ -0,0 +1,59 @@ +/* + * arch/arm/mach-tegra/cpuidle.c + * + * 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 <soc/tegra/fuse.h> + +#include "cpuidle.h" + +void __init tegra_cpuidle_init(void) +{ + switch (tegra_get_chip_id()) { + case TEGRA20: + if (IS_ENABLED(CONFIG_ARCH_TEGRA_2x_SOC)) + tegra20_cpuidle_init(); + break; + case TEGRA30: + if (IS_ENABLED(CONFIG_ARCH_TEGRA_3x_SOC)) + tegra30_cpuidle_init(); + break; + case TEGRA114: + case TEGRA124: + if (IS_ENABLED(CONFIG_ARCH_TEGRA_114_SOC) || + IS_ENABLED(CONFIG_ARCH_TEGRA_124_SOC)) + tegra114_cpuidle_init(); + break; + } +} + +void tegra_cpuidle_pcie_irqs_in_use(void) +{ + switch (tegra_get_chip_id()) { + case TEGRA20: + if (IS_ENABLED(CONFIG_ARCH_TEGRA_2x_SOC)) + tegra20_cpuidle_pcie_irqs_in_use(); + break; + } +} diff --git a/arch/arm/mach-tegra/cpuidle.h b/arch/arm/mach-tegra/cpuidle.h new file mode 100644 index 000000000..c017dab60 --- /dev/null +++ b/arch/arm/mach-tegra/cpuidle.h @@ -0,0 +1,30 @@ +/* + * 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_CPU_IDLE +int tegra20_cpuidle_init(void); +void tegra20_cpuidle_pcie_irqs_in_use(void); +int tegra30_cpuidle_init(void); +int tegra114_cpuidle_init(void); +void tegra_cpuidle_init(void); +#else +static inline void tegra_cpuidle_init(void) {} +#endif + +#endif diff --git a/arch/arm/mach-tegra/flowctrl.c b/arch/arm/mach-tegra/flowctrl.c new file mode 100644 index 000000000..475e78399 --- /dev/null +++ b/arch/arm/mach-tegra/flowctrl.c @@ -0,0 +1,171 @@ +/* + * arch/arm/mach-tegra/flowctrl.c + * + * functions and macros to control the flowcontroller + * + * Copyright (c) 2010-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 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. + * + * 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/cpumask.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/of.h> +#include <linux/of_address.h> + +#include <soc/tegra/fuse.h> + +#include "flowctrl.h" + +static u8 flowctrl_offset_halt_cpu[] = { + FLOW_CTRL_HALT_CPU0_EVENTS, + FLOW_CTRL_HALT_CPU1_EVENTS, + FLOW_CTRL_HALT_CPU1_EVENTS + 8, + FLOW_CTRL_HALT_CPU1_EVENTS + 16, +}; + +static u8 flowctrl_offset_cpu_csr[] = { + FLOW_CTRL_CPU0_CSR, + FLOW_CTRL_CPU1_CSR, + FLOW_CTRL_CPU1_CSR + 8, + FLOW_CTRL_CPU1_CSR + 16, +}; + +static void __iomem *tegra_flowctrl_base; + +static void flowctrl_update(u8 offset, u32 value) +{ + writel(value, tegra_flowctrl_base + offset); + + /* ensure the update has reached the flow controller */ + wmb(); + readl_relaxed(tegra_flowctrl_base + offset); +} + +u32 flowctrl_read_cpu_csr(unsigned int cpuid) +{ + u8 offset = flowctrl_offset_cpu_csr[cpuid]; + + return readl(tegra_flowctrl_base + offset); +} + +void flowctrl_write_cpu_csr(unsigned int cpuid, u32 value) +{ + return flowctrl_update(flowctrl_offset_cpu_csr[cpuid], value); +} + +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); + switch (tegra_get_chip_id()) { + case TEGRA20: + /* clear wfe bitmap */ + reg &= ~TEGRA20_FLOW_CTRL_CSR_WFE_BITMAP; + /* clear wfi bitmap */ + reg &= ~TEGRA20_FLOW_CTRL_CSR_WFI_BITMAP; + /* pwr gating on wfe */ + reg |= TEGRA20_FLOW_CTRL_CSR_WFE_CPU0 << cpuid; + break; + case TEGRA30: + case TEGRA114: + case TEGRA124: + /* clear wfe bitmap */ + reg &= ~TEGRA30_FLOW_CTRL_CSR_WFE_BITMAP; + /* clear wfi bitmap */ + reg &= ~TEGRA30_FLOW_CTRL_CSR_WFI_BITMAP; + /* pwr gating on wfi */ + reg |= TEGRA30_FLOW_CTRL_CSR_WFI_CPU0 << cpuid; + break; + } + reg |= FLOW_CTRL_CSR_INTR_FLAG; /* clear intr flag */ + reg |= FLOW_CTRL_CSR_EVENT_FLAG; /* clear event flag */ + 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); + switch (tegra_get_chip_id()) { + case TEGRA20: + /* clear wfe bitmap */ + reg &= ~TEGRA20_FLOW_CTRL_CSR_WFE_BITMAP; + /* clear wfi bitmap */ + reg &= ~TEGRA20_FLOW_CTRL_CSR_WFI_BITMAP; + break; + case TEGRA30: + case TEGRA114: + case TEGRA124: + /* clear wfe bitmap */ + reg &= ~TEGRA30_FLOW_CTRL_CSR_WFE_BITMAP; + /* clear wfi bitmap */ + reg &= ~TEGRA30_FLOW_CTRL_CSR_WFI_BITMAP; + break; + } + 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); +} + +static const struct of_device_id matches[] __initconst = { + { .compatible = "nvidia,tegra124-flowctrl" }, + { .compatible = "nvidia,tegra114-flowctrl" }, + { .compatible = "nvidia,tegra30-flowctrl" }, + { .compatible = "nvidia,tegra20-flowctrl" }, + { } +}; + +void __init tegra_flowctrl_init(void) +{ + /* hardcoded fallback if device tree node is missing */ + unsigned long base = 0x60007000; + unsigned long size = SZ_4K; + struct device_node *np; + + np = of_find_matching_node(NULL, matches); + if (np) { + struct resource res; + + if (of_address_to_resource(np, 0, &res) == 0) { + size = resource_size(&res); + base = res.start; + } + + of_node_put(np); + } + + tegra_flowctrl_base = ioremap_nocache(base, size); +} diff --git a/arch/arm/mach-tegra/flowctrl.h b/arch/arm/mach-tegra/flowctrl.h new file mode 100644 index 000000000..73a9c5016 --- /dev/null +++ b/arch/arm/mach-tegra/flowctrl.h @@ -0,0 +1,66 @@ +/* + * arch/arm/mach-tegra/flowctrl.h + * + * functions and macros to control the flowcontroller + * + * Copyright (c) 2010-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 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. + * + * 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_FLOWCTRL_H +#define __MACH_TEGRA_FLOWCTRL_H + +#define FLOW_CTRL_HALT_CPU0_EVENTS 0x0 +#define FLOW_CTRL_WAITEVENT (2 << 29) +#define FLOW_CTRL_WAIT_FOR_INTERRUPT (4 << 29) +#define FLOW_CTRL_JTAG_RESUME (1 << 28) +#define FLOW_CTRL_SCLK_RESUME (1 << 27) +#define FLOW_CTRL_HALT_CPU_IRQ (1 << 10) +#define FLOW_CTRL_HALT_CPU_FIQ (1 << 8) +#define FLOW_CTRL_HALT_LIC_IRQ (1 << 11) +#define FLOW_CTRL_HALT_LIC_FIQ (1 << 10) +#define FLOW_CTRL_HALT_GIC_IRQ (1 << 9) +#define FLOW_CTRL_HALT_GIC_FIQ (1 << 8) +#define FLOW_CTRL_CPU0_CSR 0x8 +#define FLOW_CTRL_CSR_INTR_FLAG (1 << 15) +#define FLOW_CTRL_CSR_EVENT_FLAG (1 << 14) +#define FLOW_CTRL_CSR_ENABLE_EXT_CRAIL (1 << 13) +#define FLOW_CTRL_CSR_ENABLE_EXT_NCPU (1 << 12) +#define FLOW_CTRL_CSR_ENABLE_EXT_MASK ( \ + FLOW_CTRL_CSR_ENABLE_EXT_NCPU | \ + FLOW_CTRL_CSR_ENABLE_EXT_CRAIL) +#define FLOW_CTRL_CSR_ENABLE (1 << 0) +#define FLOW_CTRL_HALT_CPU1_EVENTS 0x14 +#define FLOW_CTRL_CPU1_CSR 0x18 + +#define TEGRA20_FLOW_CTRL_CSR_WFE_CPU0 (1 << 4) +#define TEGRA20_FLOW_CTRL_CSR_WFE_BITMAP (3 << 4) +#define TEGRA20_FLOW_CTRL_CSR_WFI_BITMAP 0 + +#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); + +void tegra_flowctrl_init(void); +#endif + +#endif diff --git a/arch/arm/mach-tegra/headsmp.S b/arch/arm/mach-tegra/headsmp.S new file mode 100644 index 000000000..2072e7322 --- /dev/null +++ b/arch/arm/mach-tegra/headsmp.S @@ -0,0 +1,12 @@ +#include <linux/linkage.h> +#include <linux/init.h> + +#include "sleep.h" + + .section ".text.head", "ax" + +ENTRY(tegra_secondary_startup) + check_cpu_part_num 0xc09, r8, r9 + bleq v7_invalidate_l1 + b secondary_startup +ENDPROC(tegra_secondary_startup) diff --git a/arch/arm/mach-tegra/hotplug.c b/arch/arm/mach-tegra/hotplug.c new file mode 100644 index 000000000..6fc71f153 --- /dev/null +++ b/arch/arm/mach-tegra/hotplug.c @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2002 ARM Ltd. + * All Rights Reserved + * Copyright (c) 2010, 2012-2013, NVIDIA Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/clk/tegra.h> +#include <linux/kernel.h> +#include <linux/smp.h> + +#include <soc/tegra/common.h> +#include <soc/tegra/fuse.h> + +#include <asm/smp_plat.h> + +#include "sleep.h" + +static void (*tegra_hotplug_shutdown)(void); + +int tegra_cpu_kill(unsigned cpu) +{ + cpu = cpu_logical_map(cpu); + + /* Clock gate the CPU */ + tegra_wait_cpu_in_reset(cpu); + tegra_disable_cpu_clock(cpu); + + return 1; +} + +/* + * platform-specific code to shutdown a CPU + * + * Called with IRQs disabled + */ +void __ref tegra_cpu_die(unsigned int cpu) +{ + if (!tegra_hotplug_shutdown) { + WARN(1, "hotplug is not yet initialized\n"); + return; + } + + /* Clean L1 data cache */ + tegra_disable_clean_inv_dcache(TEGRA_FLUSH_CACHE_LOUIS); + + /* Shut down the current CPU. */ + tegra_hotplug_shutdown(); + + /* Should never return here. */ + BUG(); +} + +static int __init tegra_hotplug_init(void) +{ + if (!IS_ENABLED(CONFIG_HOTPLUG_CPU)) + return 0; + + if (!soc_is_tegra()) + return 0; + + if (IS_ENABLED(CONFIG_ARCH_TEGRA_2x_SOC) && tegra_get_chip_id() == TEGRA20) + tegra_hotplug_shutdown = tegra20_hotplug_shutdown; + if (IS_ENABLED(CONFIG_ARCH_TEGRA_3x_SOC) && tegra_get_chip_id() == TEGRA30) + tegra_hotplug_shutdown = tegra30_hotplug_shutdown; + if (IS_ENABLED(CONFIG_ARCH_TEGRA_114_SOC) && tegra_get_chip_id() == TEGRA114) + tegra_hotplug_shutdown = tegra30_hotplug_shutdown; + if (IS_ENABLED(CONFIG_ARCH_TEGRA_124_SOC) && tegra_get_chip_id() == TEGRA124) + tegra_hotplug_shutdown = tegra30_hotplug_shutdown; + + return 0; +} +pure_initcall(tegra_hotplug_init); diff --git a/arch/arm/mach-tegra/io.c b/arch/arm/mach-tegra/io.c new file mode 100644 index 000000000..352de159d --- /dev/null +++ b/arch/arm/mach-tegra/io.c @@ -0,0 +1,64 @@ +/* + * arch/arm/mach-tegra/io.c + * + * Copyright (C) 2010 Google, Inc. + * + * Author: + * Colin Cross <ccross@google.com> + * Erik Gilling <konkers@google.com> + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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/init.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/mm.h> +#include <linux/module.h> + +#include <asm/mach/map.h> +#include <asm/page.h> + +#include "board.h" +#include "iomap.h" + +static struct map_desc tegra_io_desc[] __initdata = { + { + .virtual = (unsigned long)IO_PPSB_VIRT, + .pfn = __phys_to_pfn(IO_PPSB_PHYS), + .length = IO_PPSB_SIZE, + .type = MT_DEVICE, + }, + { + .virtual = (unsigned long)IO_APB_VIRT, + .pfn = __phys_to_pfn(IO_APB_PHYS), + .length = IO_APB_SIZE, + .type = MT_DEVICE, + }, + { + .virtual = (unsigned long)IO_CPU_VIRT, + .pfn = __phys_to_pfn(IO_CPU_PHYS), + .length = IO_CPU_SIZE, + .type = MT_DEVICE, + }, + { + .virtual = (unsigned long)IO_IRAM_VIRT, + .pfn = __phys_to_pfn(IO_IRAM_PHYS), + .length = IO_IRAM_SIZE, + .type = MT_DEVICE, + }, +}; + +void __init tegra_map_common_io(void) +{ + debug_ll_io_init(); + iotable_init(tegra_io_desc, ARRAY_SIZE(tegra_io_desc)); +} diff --git a/arch/arm/mach-tegra/iomap.h b/arch/arm/mach-tegra/iomap.h new file mode 100644 index 000000000..81dc950b4 --- /dev/null +++ b/arch/arm/mach-tegra/iomap.h @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2010 Google, Inc. + * + * Author: + * Colin Cross <ccross@google.com> + * Erik Gilling <konkers@google.com> + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#ifndef __MACH_TEGRA_IOMAP_H +#define __MACH_TEGRA_IOMAP_H + +#include <asm/pgtable.h> +#include <asm/sizes.h> + +#define TEGRA_IRAM_BASE 0x40000000 +#define TEGRA_IRAM_SIZE SZ_256K + +#define TEGRA_ARM_PERIF_BASE 0x50040000 +#define TEGRA_ARM_PERIF_SIZE SZ_8K + +#define TEGRA_ARM_INT_DIST_BASE 0x50041000 +#define TEGRA_ARM_INT_DIST_SIZE SZ_4K + +#define TEGRA_TMR1_BASE 0x60005000 +#define TEGRA_TMR1_SIZE SZ_8 + +#define TEGRA_TMR2_BASE 0x60005008 +#define TEGRA_TMR2_SIZE SZ_8 + +#define TEGRA_TMRUS_BASE 0x60005010 +#define TEGRA_TMRUS_SIZE SZ_64 + +#define TEGRA_TMR3_BASE 0x60005050 +#define TEGRA_TMR3_SIZE SZ_8 + +#define TEGRA_TMR4_BASE 0x60005058 +#define TEGRA_TMR4_SIZE SZ_8 + +#define TEGRA_CLK_RESET_BASE 0x60006000 +#define TEGRA_CLK_RESET_SIZE SZ_4K + +#define TEGRA_FLOW_CTRL_BASE 0x60007000 +#define TEGRA_FLOW_CTRL_SIZE 20 + +#define TEGRA_SB_BASE 0x6000C200 +#define TEGRA_SB_SIZE 256 + +#define TEGRA_EXCEPTION_VECTORS_BASE 0x6000F000 +#define TEGRA_EXCEPTION_VECTORS_SIZE SZ_4K + +#define TEGRA_APB_MISC_BASE 0x70000000 +#define TEGRA_APB_MISC_SIZE SZ_4K + +#define TEGRA_UARTA_BASE 0x70006000 +#define TEGRA_UARTA_SIZE SZ_64 + +#define TEGRA_UARTB_BASE 0x70006040 +#define TEGRA_UARTB_SIZE SZ_64 + +#define TEGRA_UARTC_BASE 0x70006200 +#define TEGRA_UARTC_SIZE SZ_256 + +#define TEGRA_UARTD_BASE 0x70006300 +#define TEGRA_UARTD_SIZE SZ_256 + +#define TEGRA_UARTE_BASE 0x70006400 +#define TEGRA_UARTE_SIZE SZ_256 + +#define TEGRA_PMC_BASE 0x7000E400 +#define TEGRA_PMC_SIZE SZ_256 + +#define TEGRA_EMC_BASE 0x7000F400 +#define TEGRA_EMC_SIZE SZ_1K + +#define TEGRA_FUSE_BASE 0x7000F800 +#define TEGRA_FUSE_SIZE SZ_1K + +#define TEGRA_EMC0_BASE 0x7001A000 +#define TEGRA_EMC0_SIZE SZ_2K + +#define TEGRA_EMC1_BASE 0x7001A800 +#define TEGRA_EMC1_SIZE SZ_2K + +#define TEGRA124_EMC_BASE 0x7001B000 +#define TEGRA124_EMC_SIZE SZ_2K + +#define TEGRA_CSITE_BASE 0x70040000 +#define TEGRA_CSITE_SIZE SZ_256K + +/* On TEGRA, many peripherals are very closely packed in + * two 256MB io windows (that actually only use about 64KB + * at the start of each). + * + * We will just map the first MMU section of each window (to minimize + * pt entries needed) and provide a macro to transform physical + * io addresses to an appropriate void __iomem *. + */ + +#define IO_IRAM_PHYS 0x40000000 +#define IO_IRAM_VIRT IOMEM(0xFE400000) +#define IO_IRAM_SIZE SZ_256K + +#define IO_CPU_PHYS 0x50040000 +#define IO_CPU_VIRT IOMEM(0xFE440000) +#define IO_CPU_SIZE SZ_16K + +#define IO_PPSB_PHYS 0x60000000 +#define IO_PPSB_VIRT IOMEM(0xFE200000) +#define IO_PPSB_SIZE SECTION_SIZE + +#define IO_APB_PHYS 0x70000000 +#define IO_APB_VIRT IOMEM(0xFE000000) +#define IO_APB_SIZE SECTION_SIZE + +#define IO_TO_VIRT_BETWEEN(p, st, sz) ((p) >= (st) && (p) < ((st) + (sz))) +#define IO_TO_VIRT_XLATE(p, pst, vst) (((p) - (pst) + (vst))) + +#define IO_TO_VIRT(n) ( \ + IO_TO_VIRT_BETWEEN((n), IO_PPSB_PHYS, IO_PPSB_SIZE) ? \ + IO_TO_VIRT_XLATE((n), IO_PPSB_PHYS, IO_PPSB_VIRT) : \ + IO_TO_VIRT_BETWEEN((n), IO_APB_PHYS, IO_APB_SIZE) ? \ + IO_TO_VIRT_XLATE((n), IO_APB_PHYS, IO_APB_VIRT) : \ + IO_TO_VIRT_BETWEEN((n), IO_CPU_PHYS, IO_CPU_SIZE) ? \ + IO_TO_VIRT_XLATE((n), IO_CPU_PHYS, IO_CPU_VIRT) : \ + IO_TO_VIRT_BETWEEN((n), IO_IRAM_PHYS, IO_IRAM_SIZE) ? \ + IO_TO_VIRT_XLATE((n), IO_IRAM_PHYS, IO_IRAM_VIRT) : \ + NULL) + +#define IO_ADDRESS(n) (IO_TO_VIRT(n)) + +#endif diff --git a/arch/arm/mach-tegra/irammap.h b/arch/arm/mach-tegra/irammap.h new file mode 100644 index 000000000..e32e1742c --- /dev/null +++ b/arch/arm/mach-tegra/irammap.h @@ -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_IRAMMAP_H +#define __MACH_TEGRA_IRAMMAP_H + +#include <asm/sizes.h> + +/* The first 1K of IRAM is permanently reserved for the CPU reset handler */ +#define TEGRA_IRAM_RESET_HANDLER_OFFSET 0 +#define TEGRA_IRAM_RESET_HANDLER_SIZE SZ_1K + +/* + * This area is used for LPx resume vector, only while LPx power state is + * active. At other times, the AVP may use this area for arbitrary purposes + */ +#define TEGRA_IRAM_LPx_RESUME_AREA (TEGRA_IRAM_BASE + SZ_4K) + +#endif diff --git a/arch/arm/mach-tegra/irq.c b/arch/arm/mach-tegra/irq.c new file mode 100644 index 000000000..3b9098d27 --- /dev/null +++ b/arch/arm/mach-tegra/irq.c @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2011 Google, Inc. + * + * Author: + * Colin Cross <ccross@android.com> + * + * Copyright (C) 2010,2013, NVIDIA Corporation + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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/cpu_pm.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/irqchip/arm-gic.h> +#include <linux/irq.h> +#include <linux/kernel.h> +#include <linux/of_address.h> +#include <linux/of.h> +#include <linux/syscore_ops.h> + +#include "board.h" +#include "iomap.h" + +#define SGI_MASK 0xFFFF + +#ifdef CONFIG_PM_SLEEP +static void __iomem *tegra_gic_cpu_base; +#endif + +bool tegra_pending_sgi(void) +{ + u32 pending_set; + void __iomem *distbase = IO_ADDRESS(TEGRA_ARM_INT_DIST_BASE); + + pending_set = readl_relaxed(distbase + GIC_DIST_PENDING_SET); + + if (pending_set & SGI_MASK) + return true; + + return false; +} + +#ifdef CONFIG_PM_SLEEP +static int tegra_gic_notifier(struct notifier_block *self, + unsigned long cmd, void *v) +{ + switch (cmd) { + case CPU_PM_ENTER: + writel_relaxed(0x1E0, tegra_gic_cpu_base + GIC_CPU_CTRL); + break; + } + + return NOTIFY_OK; +} + +static struct notifier_block tegra_gic_notifier_block = { + .notifier_call = tegra_gic_notifier, +}; + +static const struct of_device_id tegra114_dt_gic_match[] __initconst = { + { .compatible = "arm,cortex-a15-gic" }, + { } +}; + +static void tegra114_gic_cpu_pm_registration(void) +{ + struct device_node *dn; + + dn = of_find_matching_node(NULL, tegra114_dt_gic_match); + if (!dn) + return; + + tegra_gic_cpu_base = of_iomap(dn, 1); + + cpu_pm_register_notifier(&tegra_gic_notifier_block); +} +#else +static void tegra114_gic_cpu_pm_registration(void) { } +#endif + +static const struct of_device_id tegra_ictlr_match[] __initconst = { + { .compatible = "nvidia,tegra20-ictlr" }, + { .compatible = "nvidia,tegra30-ictlr" }, + { } +}; + +void __init tegra_init_irq(void) +{ + if (WARN_ON(!of_find_matching_node(NULL, tegra_ictlr_match))) + pr_warn("Outdated DT detected, suspend/resume will NOT work\n"); + + tegra114_gic_cpu_pm_registration(); +} diff --git a/arch/arm/mach-tegra/irq.h b/arch/arm/mach-tegra/irq.h new file mode 100644 index 000000000..5142649bb --- /dev/null +++ b/arch/arm/mach-tegra/irq.h @@ -0,0 +1,22 @@ +/* + * 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 __TEGRA_IRQ_H +#define __TEGRA_IRQ_H + +bool tegra_pending_sgi(void); + +#endif diff --git a/arch/arm/mach-tegra/platsmp.c b/arch/arm/mach-tegra/platsmp.c new file mode 100644 index 000000000..b45086666 --- /dev/null +++ b/arch/arm/mach-tegra/platsmp.c @@ -0,0 +1,203 @@ +/* + * linux/arch/arm/mach-tegra/platsmp.c + * + * Copyright (C) 2002 ARM Ltd. + * All Rights Reserved + * + * Copyright (C) 2009 Palm + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/clk/tegra.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/jiffies.h> +#include <linux/smp.h> + +#include <soc/tegra/fuse.h> +#include <soc/tegra/pmc.h> + +#include <asm/cacheflush.h> +#include <asm/mach-types.h> +#include <asm/smp_plat.h> +#include <asm/smp_scu.h> + +#include "common.h" +#include "flowctrl.h" +#include "iomap.h" +#include "reset.h" + +static cpumask_t tegra_cpu_init_mask; + +static void tegra_secondary_init(unsigned int cpu) +{ + cpumask_set_cpu(cpu, &tegra_cpu_init_mask); +} + + +static int tegra20_boot_secondary(unsigned int cpu, struct task_struct *idle) +{ + cpu = cpu_logical_map(cpu); + + /* + * Force the CPU into reset. The CPU must remain in reset when + * the flow controller state is cleared (which will cause the + * flow controller to stop driving reset if the CPU has been + * power-gated via the flow controller). This will have no + * effect on first boot of the CPU since it should already be + * in reset. + */ + tegra_put_cpu_in_reset(cpu); + + /* + * Unhalt the CPU. If the flow controller was used to + * power-gate the CPU this will cause the flow controller to + * stop driving reset. The CPU will remain in reset because the + * clock and reset block is now driving reset. + */ + flowctrl_write_cpu_halt(cpu, 0); + + tegra_enable_cpu_clock(cpu); + flowctrl_write_cpu_csr(cpu, 0); /* Clear flow controller CSR. */ + tegra_cpu_out_of_reset(cpu); + return 0; +} + +static int tegra30_boot_secondary(unsigned int cpu, struct task_struct *idle) +{ + int ret; + unsigned long timeout; + + cpu = cpu_logical_map(cpu); + tegra_put_cpu_in_reset(cpu); + flowctrl_write_cpu_halt(cpu, 0); + + /* + * The power up sequence of cold boot CPU and warm boot CPU + * was different. + * + * For warm boot CPU that was resumed from CPU hotplug, the + * power will be resumed automatically after un-halting the + * flow controller of the warm boot CPU. We need to wait for + * the confirmaiton that the CPU is powered then removing + * the IO clamps. + * For cold boot CPU, do not wait. After the cold boot CPU be + * booted, it will run to tegra_secondary_init() and set + * tegra_cpu_init_mask which influences what tegra30_boot_secondary() + * next time around. + */ + if (cpumask_test_cpu(cpu, &tegra_cpu_init_mask)) { + timeout = jiffies + msecs_to_jiffies(50); + do { + if (tegra_pmc_cpu_is_powered(cpu)) + goto remove_clamps; + udelay(10); + } while (time_before(jiffies, timeout)); + } + + /* + * The power status of the cold boot CPU is power gated as + * default. To power up the cold boot CPU, the power should + * be un-gated by un-toggling the power gate register + * manually. + */ + if (!tegra_pmc_cpu_is_powered(cpu)) { + ret = tegra_pmc_cpu_power_on(cpu); + if (ret) + return ret; + + /* Wait for the power to come up. */ + timeout = jiffies + msecs_to_jiffies(100); + while (!tegra_pmc_cpu_is_powered(cpu)) { + if (time_after(jiffies, timeout)) + return -ETIMEDOUT; + udelay(10); + } + } + +remove_clamps: + /* CPU partition is powered. Enable the CPU clock. */ + tegra_enable_cpu_clock(cpu); + udelay(10); + + /* Remove I/O clamps. */ + ret = tegra_pmc_cpu_remove_clamping(cpu); + if (ret) + return ret; + + udelay(10); + + flowctrl_write_cpu_csr(cpu, 0); /* Clear flow controller CSR. */ + tegra_cpu_out_of_reset(cpu); + return 0; +} + +static int tegra114_boot_secondary(unsigned int cpu, struct task_struct *idle) +{ + int ret = 0; + + cpu = cpu_logical_map(cpu); + + if (cpumask_test_cpu(cpu, &tegra_cpu_init_mask)) { + /* + * Warm boot flow + * The flow controller in charge of the power state and + * control for each CPU. + */ + /* set SCLK as event trigger for flow controller */ + flowctrl_write_cpu_csr(cpu, 1); + flowctrl_write_cpu_halt(cpu, + FLOW_CTRL_WAITEVENT | FLOW_CTRL_SCLK_RESUME); + } else { + /* + * Cold boot flow + * The CPU is powered up by toggling PMC directly. It will + * also initial power state in flow controller. After that, + * the CPU's power state is maintained by flow controller. + */ + ret = tegra_pmc_cpu_power_on(cpu); + } + + return ret; +} + +static int tegra_boot_secondary(unsigned int cpu, + struct task_struct *idle) +{ + if (IS_ENABLED(CONFIG_ARCH_TEGRA_2x_SOC) && tegra_get_chip_id() == TEGRA20) + return tegra20_boot_secondary(cpu, idle); + if (IS_ENABLED(CONFIG_ARCH_TEGRA_3x_SOC) && tegra_get_chip_id() == TEGRA30) + return tegra30_boot_secondary(cpu, idle); + if (IS_ENABLED(CONFIG_ARCH_TEGRA_114_SOC) && tegra_get_chip_id() == TEGRA114) + return tegra114_boot_secondary(cpu, idle); + if (IS_ENABLED(CONFIG_ARCH_TEGRA_124_SOC) && tegra_get_chip_id() == TEGRA124) + return tegra114_boot_secondary(cpu, idle); + + return -EINVAL; +} + +static void __init tegra_smp_prepare_cpus(unsigned int max_cpus) +{ + /* Always mark the boot CPU (CPU0) as initialized. */ + cpumask_set_cpu(0, &tegra_cpu_init_mask); + + if (scu_a9_has_base()) + scu_enable(IO_ADDRESS(scu_a9_get_base())); +} + +struct smp_operations tegra_smp_ops __initdata = { + .smp_prepare_cpus = tegra_smp_prepare_cpus, + .smp_secondary_init = tegra_secondary_init, + .smp_boot_secondary = tegra_boot_secondary, +#ifdef CONFIG_HOTPLUG_CPU + .cpu_kill = tegra_cpu_kill, + .cpu_die = tegra_cpu_die, +#endif +}; diff --git a/arch/arm/mach-tegra/pm-tegra20.c b/arch/arm/mach-tegra/pm-tegra20.c new file mode 100644 index 000000000..39ac2b723 --- /dev/null +++ b/arch/arm/mach-tegra/pm-tegra20.c @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2013, 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 "pm.h" + +#ifdef CONFIG_PM_SLEEP +extern u32 tegra20_iram_start, tegra20_iram_end; +extern void tegra20_sleep_core_finish(unsigned long); + +void tegra20_lp1_iram_hook(void) +{ + tegra_lp1_iram.start_addr = &tegra20_iram_start; + tegra_lp1_iram.end_addr = &tegra20_iram_end; +} + +void tegra20_sleep_core_init(void) +{ + tegra_sleep_core_finish = tegra20_sleep_core_finish; +} +#endif diff --git a/arch/arm/mach-tegra/pm-tegra30.c b/arch/arm/mach-tegra/pm-tegra30.c new file mode 100644 index 000000000..46cc19de9 --- /dev/null +++ b/arch/arm/mach-tegra/pm-tegra30.c @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2013, 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 "pm.h" + +#ifdef CONFIG_PM_SLEEP +extern u32 tegra30_iram_start, tegra30_iram_end; +extern void tegra30_sleep_core_finish(unsigned long); + +void tegra30_lp1_iram_hook(void) +{ + tegra_lp1_iram.start_addr = &tegra30_iram_start; + tegra_lp1_iram.end_addr = &tegra30_iram_end; +} + +void tegra30_sleep_core_init(void) +{ + tegra_sleep_core_finish = tegra30_sleep_core_finish; +} +#endif diff --git a/arch/arm/mach-tegra/pm.c b/arch/arm/mach-tegra/pm.c new file mode 100644 index 000000000..b0f48a394 --- /dev/null +++ b/arch/arm/mach-tegra/pm.c @@ -0,0 +1,400 @@ +/* + * 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/clk/tegra.h> +#include <linux/cpumask.h> +#include <linux/cpu_pm.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/suspend.h> + +#include <soc/tegra/fuse.h> +#include <soc/tegra/pm.h> +#include <soc/tegra/pmc.h> + +#include <asm/cacheflush.h> +#include <asm/idmap.h> +#include <asm/proc-fns.h> +#include <asm/smp_plat.h> +#include <asm/suspend.h> +#include <asm/tlbflush.h> + +#include "flowctrl.h" +#include "iomap.h" +#include "pm.h" +#include "reset.h" +#include "sleep.h" + +#ifdef CONFIG_PM_SLEEP +static DEFINE_SPINLOCK(tegra_lp2_lock); +static u32 iram_save_size; +static void *iram_save_addr; +struct tegra_lp1_iram tegra_lp1_iram; +void (*tegra_tear_down_cpu)(void); +void (*tegra_sleep_core_finish)(unsigned long v2p); +static int (*tegra_sleep_func)(unsigned long v2p); + +static void tegra_tear_down_cpu_init(void) +{ + switch (tegra_get_chip_id()) { + case TEGRA20: + if (IS_ENABLED(CONFIG_ARCH_TEGRA_2x_SOC)) + tegra_tear_down_cpu = tegra20_tear_down_cpu; + break; + case TEGRA30: + case TEGRA114: + case TEGRA124: + if (IS_ENABLED(CONFIG_ARCH_TEGRA_3x_SOC) || + IS_ENABLED(CONFIG_ARCH_TEGRA_114_SOC) || + IS_ENABLED(CONFIG_ARCH_TEGRA_124_SOC)) + tegra_tear_down_cpu = tegra30_tear_down_cpu; + break; + } +} + +/* + * 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); +} + +/* + * 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); +} + +void tegra_clear_cpu_in_lp2(void) +{ + int phy_cpu_id = cpu_logical_map(smp_processor_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 tegra_set_cpu_in_lp2(void) +{ + int phy_cpu_id = cpu_logical_map(smp_processor_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; + else if (tegra_get_chip_id() == TEGRA20 && phy_cpu_id == 1) + tegra20_cpu_set_resettable_soon(); + + spin_unlock(&tegra_lp2_lock); + return last_cpu; +} + +int tegra_cpu_do_idle(void) +{ + return cpu_do_idle(); +} + +static int tegra_sleep_cpu(unsigned long v2p) +{ + setup_mm_for_reboot(); + tegra_sleep_cpu_finish(v2p); + + /* should never here */ + BUG(); + + return 0; +} + +static void tegra_pm_set(enum tegra_suspend_mode mode) +{ + u32 value; + + switch (tegra_get_chip_id()) { + case TEGRA20: + case TEGRA30: + break; + default: + /* Turn off CRAIL */ + value = flowctrl_read_cpu_csr(0); + value &= ~FLOW_CTRL_CSR_ENABLE_EXT_MASK; + value |= FLOW_CTRL_CSR_ENABLE_EXT_CRAIL; + flowctrl_write_cpu_csr(0, value); + break; + } + + tegra_pmc_enter_suspend_mode(mode); +} + +void tegra_idle_lp2_last(void) +{ + tegra_pm_set(TEGRA_SUSPEND_LP2); + + cpu_cluster_pm_enter(); + suspend_cpu_complex(); + + cpu_suspend(PHYS_OFFSET - PAGE_OFFSET, &tegra_sleep_cpu); + + restore_cpu_complex(); + cpu_cluster_pm_exit(); +} + +enum tegra_suspend_mode tegra_pm_validate_suspend_mode( + enum tegra_suspend_mode mode) +{ + /* + * The Tegra devices support suspending to LP1 or lower currently. + */ + if (mode > TEGRA_SUSPEND_LP1) + return TEGRA_SUSPEND_LP1; + + return mode; +} + +static int tegra_sleep_core(unsigned long v2p) +{ + setup_mm_for_reboot(); + tegra_sleep_core_finish(v2p); + + /* should never here */ + BUG(); + + return 0; +} + +/* + * tegra_lp1_iram_hook + * + * Hooking the address of LP1 reset vector and SDRAM self-refresh code in + * SDRAM. These codes not be copied to IRAM in this fuction. We need to + * copy these code to IRAM before LP0/LP1 suspend and restore the content + * of IRAM after resume. + */ +static bool tegra_lp1_iram_hook(void) +{ + switch (tegra_get_chip_id()) { + case TEGRA20: + if (IS_ENABLED(CONFIG_ARCH_TEGRA_2x_SOC)) + tegra20_lp1_iram_hook(); + break; + case TEGRA30: + case TEGRA114: + case TEGRA124: + if (IS_ENABLED(CONFIG_ARCH_TEGRA_3x_SOC) || + IS_ENABLED(CONFIG_ARCH_TEGRA_114_SOC) || + IS_ENABLED(CONFIG_ARCH_TEGRA_124_SOC)) + tegra30_lp1_iram_hook(); + break; + default: + break; + } + + if (!tegra_lp1_iram.start_addr || !tegra_lp1_iram.end_addr) + return false; + + iram_save_size = tegra_lp1_iram.end_addr - tegra_lp1_iram.start_addr; + iram_save_addr = kmalloc(iram_save_size, GFP_KERNEL); + if (!iram_save_addr) + return false; + + return true; +} + +static bool tegra_sleep_core_init(void) +{ + switch (tegra_get_chip_id()) { + case TEGRA20: + if (IS_ENABLED(CONFIG_ARCH_TEGRA_2x_SOC)) + tegra20_sleep_core_init(); + break; + case TEGRA30: + case TEGRA114: + case TEGRA124: + if (IS_ENABLED(CONFIG_ARCH_TEGRA_3x_SOC) || + IS_ENABLED(CONFIG_ARCH_TEGRA_114_SOC) || + IS_ENABLED(CONFIG_ARCH_TEGRA_124_SOC)) + tegra30_sleep_core_init(); + break; + default: + break; + } + + if (!tegra_sleep_core_finish) + return false; + + return true; +} + +static void tegra_suspend_enter_lp1(void) +{ + /* copy the reset vector & SDRAM shutdown code into IRAM */ + memcpy(iram_save_addr, IO_ADDRESS(TEGRA_IRAM_LPx_RESUME_AREA), + iram_save_size); + memcpy(IO_ADDRESS(TEGRA_IRAM_LPx_RESUME_AREA), + tegra_lp1_iram.start_addr, iram_save_size); + + *((u32 *)tegra_cpu_lp1_mask) = 1; +} + +static void tegra_suspend_exit_lp1(void) +{ + /* restore IRAM */ + memcpy(IO_ADDRESS(TEGRA_IRAM_LPx_RESUME_AREA), iram_save_addr, + iram_save_size); + + *(u32 *)tegra_cpu_lp1_mask = 0; +} + +static const char *lp_state[TEGRA_MAX_SUSPEND_MODE] = { + [TEGRA_SUSPEND_NONE] = "none", + [TEGRA_SUSPEND_LP2] = "LP2", + [TEGRA_SUSPEND_LP1] = "LP1", + [TEGRA_SUSPEND_LP0] = "LP0", +}; + +static int tegra_suspend_enter(suspend_state_t state) +{ + enum tegra_suspend_mode mode = tegra_pmc_get_suspend_mode(); + + if (WARN_ON(mode < TEGRA_SUSPEND_NONE || + mode >= TEGRA_MAX_SUSPEND_MODE)) + return -EINVAL; + + pr_info("Entering suspend state %s\n", lp_state[mode]); + + tegra_pm_set(mode); + + local_fiq_disable(); + + suspend_cpu_complex(); + switch (mode) { + case TEGRA_SUSPEND_LP1: + tegra_suspend_enter_lp1(); + break; + case TEGRA_SUSPEND_LP2: + tegra_set_cpu_in_lp2(); + break; + default: + break; + } + + cpu_suspend(PHYS_OFFSET - PAGE_OFFSET, tegra_sleep_func); + + switch (mode) { + case TEGRA_SUSPEND_LP1: + tegra_suspend_exit_lp1(); + break; + case TEGRA_SUSPEND_LP2: + tegra_clear_cpu_in_lp2(); + break; + default: + break; + } + restore_cpu_complex(); + + local_fiq_enable(); + + return 0; +} + +static const struct platform_suspend_ops tegra_suspend_ops = { + .valid = suspend_valid_only_mem, + .enter = tegra_suspend_enter, +}; + +void __init tegra_init_suspend(void) +{ + enum tegra_suspend_mode mode = tegra_pmc_get_suspend_mode(); + + if (mode == TEGRA_SUSPEND_NONE) + return; + + tegra_tear_down_cpu_init(); + + if (mode >= TEGRA_SUSPEND_LP1) { + if (!tegra_lp1_iram_hook() || !tegra_sleep_core_init()) { + pr_err("%s: unable to allocate memory for SDRAM" + "self-refresh -- LP0/LP1 unavailable\n", + __func__); + tegra_pmc_set_suspend_mode(TEGRA_SUSPEND_LP2); + mode = TEGRA_SUSPEND_LP2; + } + } + + /* set up sleep function for cpu_suspend */ + switch (mode) { + case TEGRA_SUSPEND_LP1: + tegra_sleep_func = tegra_sleep_core; + break; + case TEGRA_SUSPEND_LP2: + tegra_sleep_func = tegra_sleep_cpu; + break; + default: + break; + } + + suspend_set_ops(&tegra_suspend_ops); +} +#endif diff --git a/arch/arm/mach-tegra/pm.h b/arch/arm/mach-tegra/pm.h new file mode 100644 index 000000000..83bc87583 --- /dev/null +++ b/arch/arm/mach-tegra/pm.h @@ -0,0 +1,49 @@ +/* + * 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_ + +struct tegra_lp1_iram { + void *start_addr; + void *end_addr; +}; + +extern struct tegra_lp1_iram tegra_lp1_iram; +extern void (*tegra_sleep_core_finish)(unsigned long v2p); + +void tegra20_lp1_iram_hook(void); +void tegra20_sleep_core_init(void); +void tegra30_lp1_iram_hook(void); +void tegra30_sleep_core_init(void); + +void tegra_clear_cpu_in_lp2(void); +bool tegra_set_cpu_in_lp2(void); + +void tegra_idle_lp2_last(void); +extern void (*tegra_tear_down_cpu)(void); + +#ifdef CONFIG_PM_SLEEP +void tegra_init_suspend(void); +#else +static inline void tegra_init_suspend(void) {} +#endif + +#endif /* _MACH_TEGRA_PM_H_ */ diff --git a/arch/arm/mach-tegra/reset-handler.S b/arch/arm/mach-tegra/reset-handler.S new file mode 100644 index 000000000..e3070fdab --- /dev/null +++ b/arch/arm/mach-tegra/reset-handler.S @@ -0,0 +1,290 @@ +/* + * 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/>. + */ + +#include <linux/init.h> +#include <linux/linkage.h> + +#include <soc/tegra/fuse.h> + +#include <asm/asm-offsets.h> +#include <asm/cache.h> + +#include "flowctrl.h" +#include "iomap.h" +#include "reset.h" +#include "sleep.h" + +#define PMC_SCRATCH41 0x140 + +#define RESET_DATA(x) ((TEGRA_RESET_##x)*4) + +#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. + * + * r6: SoC ID + * r8: CPU part number + */ +ENTRY(tegra_resume) + check_cpu_part_num 0xc09, r8, r9 + bleq v7_invalidate_l1 + + cpu_id r0 + cmp r0, #0 @ CPU0? + THUMB( it ne ) + bne cpu_resume @ no + + tegra_get_soc_id TEGRA_APB_MISC_BASE, r6 + /* Are we on Tegra20? */ + cmp r6, #TEGRA20 + beq 1f @ Yes + /* Clear the flow controller flags for this CPU. */ + cpu_to_csr_reg r1, r0 + mov32 r2, TEGRA_FLOW_CTRL_BASE + ldr r1, [r2, r1] + /* Clear event & intr flag */ + orr r1, r1, \ + #FLOW_CTRL_CSR_INTR_FLAG | FLOW_CTRL_CSR_EVENT_FLAG + movw r0, #0x3FFD @ enable, cluster_switch, immed, bitmaps + @ & ext flags for CPU power mgnt + bic r1, r1, r0 + str r1, [r2] +1: + + mov32 r9, 0xc09 + cmp r8, r9 + bne end_ca9_scu_l2_resume +#ifdef CONFIG_HAVE_ARM_SCU + /* enable SCU */ + mov32 r0, TEGRA_ARM_PERIF_BASE + ldr r1, [r0] + orr r1, r1, #1 + str r1, [r0] +#endif + +#ifdef CONFIG_CACHE_L2X0 + /* L2 cache resume & re-enable */ + bl l2c310_early_resume +#endif +end_ca9_scu_l2_resume: + mov32 r9, 0xc0f + cmp r8, r9 + bleq tegra_init_l2_for_a15 + + b cpu_resume +ENDPROC(tegra_resume) +#endif + + .align L1_CACHE_SHIFT +ENTRY(__tegra_cpu_reset_handler_start) + +/* + * __tegra_cpu_reset_handler: + * + * Common handler for all CPU reset events. + * + * Register usage within the reset handler: + * + * Others: scratch + * R6 = SoC ID + * R7 = CPU present (to the OS) mask + * R8 = CPU in LP1 state mask + * R9 = CPU in LP2 state mask + * R10 = CPU number + * R11 = CPU mask + * R12 = pointer to reset handler data + * + * NOTE: This code is copied to IRAM. All code and data accesses + * must be position-independent. + */ + + .align L1_CACHE_SHIFT +ENTRY(__tegra_cpu_reset_handler) + + cpsid aif, 0x13 @ SVC mode, interrupts disabled + + tegra_get_soc_id TEGRA_APB_MISC_BASE, r6 +#ifdef CONFIG_ARCH_TEGRA_2x_SOC +t20_check: + cmp r6, #TEGRA20 + bne after_t20_check +t20_errata: + # Tegra20 is a Cortex-A9 r1p1 + mrc p15, 0, r0, c1, c0, 0 @ read system control register + orr r0, r0, #1 << 14 @ erratum 716044 + mcr p15, 0, r0, c1, c0, 0 @ write system control register + mrc p15, 0, r0, c15, c0, 1 @ read diagnostic register + orr r0, r0, #1 << 4 @ erratum 742230 + orr r0, r0, #1 << 11 @ erratum 751472 + mcr p15, 0, r0, c15, c0, 1 @ write diagnostic register + b after_errata +after_t20_check: +#endif +#ifdef CONFIG_ARCH_TEGRA_3x_SOC +t30_check: + cmp r6, #TEGRA30 + bne after_t30_check +t30_errata: + # Tegra30 is a Cortex-A9 r2p9 + mrc p15, 0, r0, c15, c0, 1 @ read diagnostic register + orr r0, r0, #1 << 6 @ erratum 743622 + orr r0, r0, #1 << 11 @ erratum 751472 + mcr p15, 0, r0, c15, c0, 1 @ write diagnostic register + b after_errata +after_t30_check: +#endif +after_errata: + mrc p15, 0, r10, c0, c0, 5 @ MPIDR + and r10, r10, #0x3 @ R10 = CPU number + mov r11, #1 + mov r11, r11, lsl r10 @ R11 = CPU mask + adr r12, __tegra_cpu_reset_handler_data + +#ifdef CONFIG_SMP + /* Does the OS know about this CPU? */ + ldr r7, [r12, #RESET_DATA(MASK_PRESENT)] + tst r7, r11 @ if !present + bleq __die @ CPU not present (to OS) +#endif + +#ifdef CONFIG_ARCH_TEGRA_2x_SOC + /* Are we on Tegra20? */ + cmp r6, #TEGRA20 + bne 1f + /* If not CPU0, don't let CPU0 reset CPU1 now that CPU1 is coming up. */ + mov32 r5, TEGRA_IRAM_BASE + TEGRA_IRAM_RESET_HANDLER_OFFSET + mov r0, #CPU_NOT_RESETTABLE + cmp r10, #0 + strneb r0, [r5, #__tegra20_cpu1_resettable_status_offset] +1: +#endif + + /* Waking up from LP1? */ + ldr r8, [r12, #RESET_DATA(MASK_LP1)] + tst r8, r11 @ if in_lp1 + beq __is_not_lp1 + cmp r10, #0 + bne __die @ only CPU0 can be here + ldr lr, [r12, #RESET_DATA(STARTUP_LP1)] + cmp lr, #0 + bleq __die @ no LP1 startup handler + THUMB( add lr, lr, #1 ) @ switch to Thumb mode + bx lr +__is_not_lp1: + + /* 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) + * CPU0 can't be here for Tegra20/30 + */ + cmp r6, #TEGRA114 + beq __no_cpu0_chk + cmp r10, #0 + bleq __die @ CPU0 cannot be here +__no_cpu0_chk: + ldr lr, [r12, #RESET_DATA(STARTUP_SECONDARY)] + cmp lr, #0 + bleq __die @ no secondary startup handler + bx lr +#endif + +/* + * We don't know why the CPU reset. Just kill it. + * The LR register will contain the address we died at + 4. + */ + +__die: + sub lr, lr, #4 + mov32 r7, TEGRA_PMC_BASE + str lr, [r7, #PMC_SCRATCH41] + + mov32 r7, TEGRA_CLK_RESET_BASE + + /* Are we on Tegra20? */ + cmp r6, #TEGRA20 + bne 1f + +#ifdef CONFIG_ARCH_TEGRA_2x_SOC + mov32 r0, 0x1111 + mov r1, r0, lsl r10 + str r1, [r7, #0x340] @ CLK_RST_CPU_CMPLX_SET +#endif +1: +#ifdef CONFIG_ARCH_TEGRA_3x_SOC + mov32 r6, TEGRA_FLOW_CTRL_BASE + + cmp r10, #0 + moveq r1, #FLOW_CTRL_HALT_CPU0_EVENTS + moveq r2, #FLOW_CTRL_CPU0_CSR + movne r1, r10, lsl #3 + addne r2, r1, #(FLOW_CTRL_CPU1_CSR-8) + addne r1, r1, #(FLOW_CTRL_HALT_CPU1_EVENTS-8) + + /* Clear CPU "event" and "interrupt" flags and power gate + it when halting but not before it is in the "WFI" state. */ + 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] + + /* Unconditionally halt this CPU */ + mov r0, #FLOW_CTRL_WAITEVENT + str r0, [r6, +r1] + ldr r0, [r6, +r1] @ memory barrier + + dsb + isb + wfi @ CPU should be power gated here + + /* If the CPU didn't power gate above just kill it's clock. */ + + mov r0, r11, lsl #8 + str r0, [r7, #348] @ CLK_CPU_CMPLX_SET +#endif + + /* If the CPU still isn't dead, just spin here. */ + b . +ENDPROC(__tegra_cpu_reset_handler) + + .align L1_CACHE_SHIFT + .type __tegra_cpu_reset_handler_data, %object + .globl __tegra_cpu_reset_handler_data +__tegra_cpu_reset_handler_data: + .rept TEGRA_RESET_DATA_SIZE + .long 0 + .endr + .globl __tegra20_cpu1_resettable_status_offset + .equ __tegra20_cpu1_resettable_status_offset, \ + . - __tegra_cpu_reset_handler_start + .byte 0 + .align L1_CACHE_SHIFT + +ENTRY(__tegra_cpu_reset_handler_end) diff --git a/arch/arm/mach-tegra/reset.c b/arch/arm/mach-tegra/reset.c new file mode 100644 index 000000000..894c5c472 --- /dev/null +++ b/arch/arm/mach-tegra/reset.c @@ -0,0 +1,108 @@ +/* + * arch/arm/mach-tegra/reset.c + * + * Copyright (C) 2011,2012 NVIDIA Corporation. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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/bitops.h> +#include <linux/cpumask.h> +#include <linux/init.h> +#include <linux/io.h> + +#include <soc/tegra/fuse.h> + +#include <asm/cacheflush.h> +#include <asm/firmware.h> +#include <asm/hardware/cache-l2x0.h> + +#include "iomap.h" +#include "irammap.h" +#include "reset.h" +#include "sleep.h" + +#define TEGRA_IRAM_RESET_BASE (TEGRA_IRAM_BASE + \ + TEGRA_IRAM_RESET_HANDLER_OFFSET) + +static bool is_enabled; + +static void __init tegra_cpu_reset_handler_set(const u32 reset_address) +{ + void __iomem *evp_cpu_reset = + IO_ADDRESS(TEGRA_EXCEPTION_VECTORS_BASE + 0x100); + void __iomem *sb_ctrl = IO_ADDRESS(TEGRA_SB_BASE); + u32 reg; + + /* + * NOTE: This must be the one and only write to the EVP CPU reset + * vector in the entire system. + */ + writel(reset_address, evp_cpu_reset); + wmb(); + reg = readl(evp_cpu_reset); + + /* + * Prevent further modifications to the physical reset vector. + * NOTE: Has no effect on chips prior to Tegra30. + */ + reg = readl(sb_ctrl); + reg |= 2; + writel(reg, sb_ctrl); + wmb(); +} + +static void __init tegra_cpu_reset_handler_enable(void) +{ + void __iomem *iram_base = IO_ADDRESS(TEGRA_IRAM_RESET_BASE); + const u32 reset_address = TEGRA_IRAM_RESET_BASE + + tegra_cpu_reset_handler_offset; + int err; + + BUG_ON(is_enabled); + BUG_ON(tegra_cpu_reset_handler_size > TEGRA_IRAM_RESET_HANDLER_SIZE); + + memcpy(iram_base, (void *)__tegra_cpu_reset_handler_start, + tegra_cpu_reset_handler_size); + + err = call_firmware_op(set_cpu_boot_addr, 0, reset_address); + switch (err) { + case -ENOSYS: + tegra_cpu_reset_handler_set(reset_address); + /* pass-through */ + case 0: + is_enabled = true; + break; + default: + pr_crit("Cannot set CPU reset handler: %d\n", err); + BUG(); + } +} + +void __init tegra_cpu_reset_handler_init(void) +{ + +#ifdef CONFIG_SMP + __tegra_cpu_reset_handler_data[TEGRA_RESET_MASK_PRESENT] = + *((u32 *)cpu_possible_mask); + __tegra_cpu_reset_handler_data[TEGRA_RESET_STARTUP_SECONDARY] = + virt_to_phys((void *)tegra_secondary_startup); +#endif + +#ifdef CONFIG_PM_SLEEP + __tegra_cpu_reset_handler_data[TEGRA_RESET_STARTUP_LP1] = + TEGRA_IRAM_LPx_RESUME_AREA; + __tegra_cpu_reset_handler_data[TEGRA_RESET_STARTUP_LP2] = + virt_to_phys((void *)tegra_resume); +#endif + + tegra_cpu_reset_handler_enable(); +} diff --git a/arch/arm/mach-tegra/reset.h b/arch/arm/mach-tegra/reset.h new file mode 100644 index 000000000..29c3dec01 --- /dev/null +++ b/arch/arm/mach-tegra/reset.h @@ -0,0 +1,67 @@ +/* + * arch/arm/mach-tegra/reset.h + * + * CPU reset dispatcher. + * + * Copyright (c) 2011, NVIDIA Corporation. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#ifndef __MACH_TEGRA_RESET_H +#define __MACH_TEGRA_RESET_H + +#define TEGRA_RESET_MASK_PRESENT 0 +#define TEGRA_RESET_MASK_LP1 1 +#define TEGRA_RESET_MASK_LP2 2 +#define TEGRA_RESET_STARTUP_SECONDARY 3 +#define TEGRA_RESET_STARTUP_LP2 4 +#define TEGRA_RESET_STARTUP_LP1 5 +#define TEGRA_RESET_DATA_SIZE 6 + +#ifndef __ASSEMBLY__ + +#include "irammap.h" + +extern unsigned long __tegra_cpu_reset_handler_data[TEGRA_RESET_DATA_SIZE]; + +void __tegra_cpu_reset_handler_start(void); +void __tegra_cpu_reset_handler(void); +void __tegra20_cpu1_resettable_status_offset(void); +void __tegra_cpu_reset_handler_end(void); +void tegra_secondary_startup(void); + +#ifdef CONFIG_PM_SLEEP +#define tegra_cpu_lp1_mask \ + (IO_ADDRESS(TEGRA_IRAM_BASE + TEGRA_IRAM_RESET_HANDLER_OFFSET + \ + ((u32)&__tegra_cpu_reset_handler_data[TEGRA_RESET_MASK_LP1] - \ + (u32)__tegra_cpu_reset_handler_start))) +#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))) +#define tegra20_cpu1_resettable_status \ + (IO_ADDRESS(TEGRA_IRAM_BASE + TEGRA_IRAM_RESET_HANDLER_OFFSET + \ + (u32)__tegra20_cpu1_resettable_status_offset)) +#endif + +#define tegra_cpu_reset_handler_offset \ + ((u32)__tegra_cpu_reset_handler - \ + (u32)__tegra_cpu_reset_handler_start) + +#define tegra_cpu_reset_handler_size \ + (__tegra_cpu_reset_handler_end - \ + __tegra_cpu_reset_handler_start) + +void __init tegra_cpu_reset_handler_init(void); + +#endif +#endif diff --git a/arch/arm/mach-tegra/sleep-tegra20.S b/arch/arm/mach-tegra/sleep-tegra20.S new file mode 100644 index 000000000..e6b684e14 --- /dev/null +++ b/arch/arm/mach-tegra/sleep-tegra20.S @@ -0,0 +1,580 @@ +/* + * Copyright (c) 2010-2012, NVIDIA Corporation. All rights reserved. + * Copyright (c) 2011, Google, Inc. + * + * Author: Colin Cross <ccross@android.com> + * Gary King <gking@nvidia.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/>. + */ + +#include <linux/linkage.h> + +#include <asm/assembler.h> +#include <asm/proc-fns.h> +#include <asm/cp15.h> +#include <asm/cache.h> + +#include "irammap.h" +#include "sleep.h" +#include "flowctrl.h" + +#define EMC_CFG 0xc +#define EMC_ADR_CFG 0x10 +#define EMC_REFRESH 0x70 +#define EMC_NOP 0xdc +#define EMC_SELF_REF 0xe0 +#define EMC_REQ_CTRL 0x2b0 +#define EMC_EMC_STATUS 0x2b4 + +#define CLK_RESET_CCLK_BURST 0x20 +#define CLK_RESET_CCLK_DIVIDER 0x24 +#define CLK_RESET_SCLK_BURST 0x28 +#define CLK_RESET_SCLK_DIVIDER 0x2c +#define CLK_RESET_PLLC_BASE 0x80 +#define CLK_RESET_PLLM_BASE 0x90 +#define CLK_RESET_PLLP_BASE 0xa0 + +#define APB_MISC_XM2CFGCPADCTRL 0x8c8 +#define APB_MISC_XM2CFGDPADCTRL 0x8cc +#define APB_MISC_XM2CLKCFGPADCTRL 0x8d0 +#define APB_MISC_XM2COMPPADCTRL 0x8d4 +#define APB_MISC_XM2VTTGENPADCTRL 0x8d8 +#define APB_MISC_XM2CFGCPADCTRL2 0x8e4 +#define APB_MISC_XM2CFGDPADCTRL2 0x8e8 + +.macro pll_enable, rd, r_car_base, pll_base + ldr \rd, [\r_car_base, #\pll_base] + tst \rd, #(1 << 30) + orreq \rd, \rd, #(1 << 30) + streq \rd, [\r_car_base, #\pll_base] +.endm + +.macro emc_device_mask, rd, base + ldr \rd, [\base, #EMC_ADR_CFG] + tst \rd, #(0x3 << 24) + moveq \rd, #(0x1 << 8) @ just 1 device + movne \rd, #(0x3 << 8) @ 2 devices +.endm + +#if defined(CONFIG_HOTPLUG_CPU) || defined(CONFIG_PM_SLEEP) +/* + * tegra20_hotplug_shutdown(void) + * + * puts the current cpu in reset + * should never return + */ +ENTRY(tegra20_hotplug_shutdown) + /* Put this CPU down */ + cpu_id r0 + bl tegra20_cpu_shutdown + ret lr @ should never get here +ENDPROC(tegra20_hotplug_shutdown) + +/* + * tegra20_cpu_shutdown(int cpu) + * + * r0 is cpu to reset + * + * puts the specified CPU in wait-for-event mode on the flow controller + * and puts the CPU in reset + * can be called on the current cpu or another cpu + * if called on the current cpu, does not return + * MUST NOT BE CALLED FOR CPU 0. + * + * corrupts r0-r3, r12 + */ +ENTRY(tegra20_cpu_shutdown) + cmp r0, #0 + reteq lr @ must not be called for CPU 0 + mov32 r1, TEGRA_IRAM_RESET_BASE_VIRT + ldr r2, =__tegra20_cpu1_resettable_status_offset + mov r12, #CPU_RESETTABLE + strb r12, [r1, r2] + + cpu_to_halt_reg r1, r0 + ldr r3, =TEGRA_FLOW_CTRL_VIRT + mov r2, #FLOW_CTRL_WAITEVENT | FLOW_CTRL_JTAG_RESUME + str r2, [r3, r1] @ put flow controller in wait event mode + ldr r2, [r3, r1] + isb + dsb + movw r1, 0x1011 + mov r1, r1, lsl r0 + ldr r3, =TEGRA_CLK_RESET_VIRT + str r1, [r3, #0x340] @ put slave CPU in reset + isb + dsb + cpu_id r3 + cmp r3, r0 + beq . + ret lr +ENDPROC(tegra20_cpu_shutdown) +#endif + +#ifdef CONFIG_PM_SLEEP +/* + * tegra_pen_lock + * + * spinlock implementation with no atomic test-and-set and no coherence + * using Peterson's algorithm on strongly-ordered registers + * used to synchronize a cpu waking up from wfi with entering lp2 on idle + * + * The reference link of Peterson's algorithm: + * http://en.wikipedia.org/wiki/Peterson's_algorithm + * + * SCRATCH37 = r1 = !turn (inverted from Peterson's algorithm) + * on cpu 0: + * r2 = flag[0] (in SCRATCH38) + * r3 = flag[1] (in SCRATCH39) + * on cpu1: + * r2 = flag[1] (in SCRATCH39) + * r3 = flag[0] (in SCRATCH38) + * + * must be called with MMU on + * corrupts r0-r3, r12 + */ +ENTRY(tegra_pen_lock) + mov32 r3, TEGRA_PMC_VIRT + cpu_id r0 + add r1, r3, #PMC_SCRATCH37 + cmp r0, #0 + addeq r2, r3, #PMC_SCRATCH38 + addeq r3, r3, #PMC_SCRATCH39 + addne r2, r3, #PMC_SCRATCH39 + addne r3, r3, #PMC_SCRATCH38 + + mov r12, #1 + str r12, [r2] @ flag[cpu] = 1 + dsb + str r12, [r1] @ !turn = cpu +1: dsb + ldr r12, [r3] + cmp r12, #1 @ flag[!cpu] == 1? + ldreq r12, [r1] + cmpeq r12, r0 @ !turn == cpu? + beq 1b @ while !turn == cpu && flag[!cpu] == 1 + + ret lr @ locked +ENDPROC(tegra_pen_lock) + +ENTRY(tegra_pen_unlock) + dsb + mov32 r3, TEGRA_PMC_VIRT + cpu_id r0 + cmp r0, #0 + addeq r2, r3, #PMC_SCRATCH38 + addne r2, r3, #PMC_SCRATCH39 + mov r12, #0 + str r12, [r2] + ret lr +ENDPROC(tegra_pen_unlock) + +/* + * tegra20_cpu_clear_resettable(void) + * + * Called to clear the "resettable soon" flag in IRAM variable when + * it is expected that the secondary CPU will be idle soon. + */ +ENTRY(tegra20_cpu_clear_resettable) + mov32 r1, TEGRA_IRAM_RESET_BASE_VIRT + ldr r2, =__tegra20_cpu1_resettable_status_offset + mov r12, #CPU_NOT_RESETTABLE + strb r12, [r1, r2] + ret lr +ENDPROC(tegra20_cpu_clear_resettable) + +/* + * tegra20_cpu_set_resettable_soon(void) + * + * Called to set the "resettable soon" flag in IRAM variable when + * it is expected that the secondary CPU will be idle soon. + */ +ENTRY(tegra20_cpu_set_resettable_soon) + mov32 r1, TEGRA_IRAM_RESET_BASE_VIRT + ldr r2, =__tegra20_cpu1_resettable_status_offset + mov r12, #CPU_RESETTABLE_SOON + strb r12, [r1, r2] + ret lr +ENDPROC(tegra20_cpu_set_resettable_soon) + +/* + * tegra20_cpu_is_resettable_soon(void) + * + * Returns true if the "resettable soon" flag in IRAM variable has been + * set because it is expected that the secondary CPU will be idle soon. + */ +ENTRY(tegra20_cpu_is_resettable_soon) + mov32 r1, TEGRA_IRAM_RESET_BASE_VIRT + ldr r2, =__tegra20_cpu1_resettable_status_offset + ldrb r12, [r1, r2] + cmp r12, #CPU_RESETTABLE_SOON + moveq r0, #1 + movne r0, #0 + ret lr +ENDPROC(tegra20_cpu_is_resettable_soon) + +/* + * tegra20_sleep_core_finish(unsigned long v2p) + * + * Enters suspend in LP0 or LP1 by turning off the mmu and jumping to + * tegra20_tear_down_core in IRAM + */ +ENTRY(tegra20_sleep_core_finish) + /* Flush, disable the L1 data cache and exit SMP */ + bl tegra_disable_clean_inv_dcache + + mov32 r3, tegra_shut_off_mmu + add r3, r3, r0 + + mov32 r0, tegra20_tear_down_core + mov32 r1, tegra20_iram_start + sub r0, r0, r1 + mov32 r1, TEGRA_IRAM_LPx_RESUME_AREA + add r0, r0, r1 + + ret r3 +ENDPROC(tegra20_sleep_core_finish) + +/* + * tegra20_sleep_cpu_secondary_finish(unsigned long v2p) + * + * Enters WFI on secondary CPU by exiting coherency. + */ +ENTRY(tegra20_sleep_cpu_secondary_finish) + stmfd sp!, {r4-r11, lr} + + mrc p15, 0, r11, c1, c0, 1 @ save actlr before exiting coherency + + /* Flush and disable the L1 data cache */ + mov r0, #TEGRA_FLUSH_CACHE_LOUIS + bl tegra_disable_clean_inv_dcache + + mov32 r0, TEGRA_IRAM_RESET_BASE_VIRT + ldr r4, =__tegra20_cpu1_resettable_status_offset + mov r3, #CPU_RESETTABLE + strb r3, [r0, r4] + + bl tegra_cpu_do_idle + + /* + * cpu may be reset while in wfi, which will return through + * tegra_resume to cpu_resume + * or interrupt may wake wfi, which will return here + * cpu state is unchanged - MMU is on, cache is on, coherency + * is off, and the data cache is off + * + * r11 contains the original actlr + */ + + bl tegra_pen_lock + + mov32 r0, TEGRA_IRAM_RESET_BASE_VIRT + ldr r4, =__tegra20_cpu1_resettable_status_offset + mov r3, #CPU_NOT_RESETTABLE + strb r3, [r0, r4] + + bl tegra_pen_unlock + + /* Re-enable the data cache */ + mrc p15, 0, r10, c1, c0, 0 + orr r10, r10, #CR_C + mcr p15, 0, r10, c1, c0, 0 + isb + + mcr p15, 0, r11, c1, c0, 1 @ reenable coherency + + /* Invalidate the TLBs & BTAC */ + mov r1, #0 + mcr p15, 0, r1, c8, c3, 0 @ invalidate shared TLBs + mcr p15, 0, r1, c7, c1, 6 @ invalidate shared BTAC + dsb + isb + + /* the cpu was running with coherency disabled, + * caches may be out of date */ + bl v7_flush_kern_cache_louis + + ldmfd sp!, {r4 - r11, pc} +ENDPROC(tegra20_sleep_cpu_secondary_finish) + +/* + * tegra20_tear_down_cpu + * + * Switches the CPU cluster to PLL-P and enters sleep. + */ +ENTRY(tegra20_tear_down_cpu) + bl tegra_switch_cpu_to_pllp + b tegra20_enter_sleep +ENDPROC(tegra20_tear_down_cpu) + +/* START OF ROUTINES COPIED TO IRAM */ + .align L1_CACHE_SHIFT + .globl tegra20_iram_start +tegra20_iram_start: + +/* + * tegra20_lp1_reset + * + * reset vector for LP1 restore; copied into IRAM during suspend. + * Brings the system back up to a safe staring point (SDRAM out of + * self-refresh, PLLC, PLLM and PLLP reenabled, CPU running on PLLP, + * system clock running on the same PLL that it suspended at), and + * jumps to tegra_resume to restore virtual addressing and PLLX. + * The physical address of tegra_resume expected to be stored in + * PMC_SCRATCH41. + * + * NOTE: THIS *MUST* BE RELOCATED TO TEGRA_IRAM_LPx_RESUME_AREA. + */ +ENTRY(tegra20_lp1_reset) + /* + * The CPU and system bus are running at 32KHz and executing from + * IRAM when this code is executed; immediately switch to CLKM and + * enable PLLM, PLLP, PLLC. + */ + mov32 r0, TEGRA_CLK_RESET_BASE + + mov r1, #(1 << 28) + str r1, [r0, #CLK_RESET_SCLK_BURST] + str r1, [r0, #CLK_RESET_CCLK_BURST] + mov r1, #0 + str r1, [r0, #CLK_RESET_CCLK_DIVIDER] + str r1, [r0, #CLK_RESET_SCLK_DIVIDER] + + pll_enable r1, r0, CLK_RESET_PLLM_BASE + pll_enable r1, r0, CLK_RESET_PLLP_BASE + pll_enable r1, r0, CLK_RESET_PLLC_BASE + + adr r2, tegra20_sdram_pad_address + adr r4, tegra20_sdram_pad_save + mov r5, #0 + + ldr r6, tegra20_sdram_pad_size +padload: + ldr r7, [r2, r5] @ r7 is the addr in the pad_address + + ldr r1, [r4, r5] + str r1, [r7] @ restore the value in pad_save + + add r5, r5, #4 + cmp r6, r5 + bne padload + +padload_done: + /* 255uS delay for PLL stabilization */ + mov32 r7, TEGRA_TMRUS_BASE + ldr r1, [r7] + add r1, r1, #0xff + wait_until r1, r7, r9 + + adr r4, tegra20_sclk_save + ldr r4, [r4] + str r4, [r0, #CLK_RESET_SCLK_BURST] + mov32 r4, ((1 << 28) | (4)) @ burst policy is PLLP + str r4, [r0, #CLK_RESET_CCLK_BURST] + + mov32 r0, TEGRA_EMC_BASE + ldr r1, [r0, #EMC_CFG] + bic r1, r1, #(1 << 31) @ disable DRAM_CLK_STOP + str r1, [r0, #EMC_CFG] + + mov r1, #0 + str r1, [r0, #EMC_SELF_REF] @ take DRAM out of self refresh + mov r1, #1 + str r1, [r0, #EMC_NOP] + str r1, [r0, #EMC_NOP] + str r1, [r0, #EMC_REFRESH] + + emc_device_mask r1, r0 + +exit_selfrefresh_loop: + ldr r2, [r0, #EMC_EMC_STATUS] + ands r2, r2, r1 + bne exit_selfrefresh_loop + + mov r1, #0 @ unstall all transactions + str r1, [r0, #EMC_REQ_CTRL] + + mov32 r0, TEGRA_PMC_BASE + ldr r0, [r0, #PMC_SCRATCH41] + ret r0 @ jump to tegra_resume +ENDPROC(tegra20_lp1_reset) + +/* + * tegra20_tear_down_core + * + * copied into and executed from IRAM + * puts memory in self-refresh for LP0 and LP1 + */ +tegra20_tear_down_core: + bl tegra20_sdram_self_refresh + bl tegra20_switch_cpu_to_clk32k + b tegra20_enter_sleep + +/* + * tegra20_switch_cpu_to_clk32k + * + * In LP0 and LP1 all PLLs will be turned off. Switch the CPU and system clock + * to the 32KHz clock. + */ +tegra20_switch_cpu_to_clk32k: + /* + * start by switching to CLKM to safely disable PLLs, then switch to + * CLKS. + */ + mov r0, #(1 << 28) + str r0, [r5, #CLK_RESET_SCLK_BURST] + str r0, [r5, #CLK_RESET_CCLK_BURST] + mov r0, #0 + str r0, [r5, #CLK_RESET_CCLK_DIVIDER] + str r0, [r5, #CLK_RESET_SCLK_DIVIDER] + + /* 2uS delay delay between changing SCLK and disabling PLLs */ + mov32 r7, TEGRA_TMRUS_BASE + ldr r1, [r7] + add r1, r1, #2 + wait_until r1, r7, r9 + + /* disable PLLM, PLLP and PLLC */ + ldr r0, [r5, #CLK_RESET_PLLM_BASE] + bic r0, r0, #(1 << 30) + str r0, [r5, #CLK_RESET_PLLM_BASE] + ldr r0, [r5, #CLK_RESET_PLLP_BASE] + bic r0, r0, #(1 << 30) + str r0, [r5, #CLK_RESET_PLLP_BASE] + ldr r0, [r5, #CLK_RESET_PLLC_BASE] + bic r0, r0, #(1 << 30) + str r0, [r5, #CLK_RESET_PLLC_BASE] + + /* switch to CLKS */ + mov r0, #0 /* brust policy = 32KHz */ + str r0, [r5, #CLK_RESET_SCLK_BURST] + + ret lr + +/* + * tegra20_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 + */ +tegra20_enter_sleep: + mov32 r6, TEGRA_FLOW_CTRL_BASE + + mov r0, #FLOW_CTRL_WAIT_FOR_INTERRUPT + orr r0, r0, #FLOW_CTRL_HALT_CPU_IRQ | FLOW_CTRL_HALT_CPU_FIQ + cpu_id r1 + cpu_to_halt_reg r1, r1 + str r0, [r6, r1] + dsb + ldr r0, [r6, r1] /* memory barrier */ + +halted: + dsb + wfe /* CPU should be power gated here */ + isb + b halted + +/* + * tegra20_sdram_self_refresh + * + * called with MMU off and caches disabled + * puts sdram in self refresh + * must be executed from IRAM + */ +tegra20_sdram_self_refresh: + mov32 r1, TEGRA_EMC_BASE @ r1 reserved for emc base addr + + mov r2, #3 + str r2, [r1, #EMC_REQ_CTRL] @ stall incoming DRAM requests + +emcidle: + ldr r2, [r1, #EMC_EMC_STATUS] + tst r2, #4 + beq emcidle + + mov r2, #1 + str r2, [r1, #EMC_SELF_REF] + + emc_device_mask r2, r1 + +emcself: + ldr r3, [r1, #EMC_EMC_STATUS] + and r3, r3, r2 + cmp r3, r2 + bne emcself @ loop until DDR in self-refresh + + adr r2, tegra20_sdram_pad_address + adr r3, tegra20_sdram_pad_safe + adr r4, tegra20_sdram_pad_save + mov r5, #0 + + ldr r6, tegra20_sdram_pad_size +padsave: + ldr r0, [r2, r5] @ r0 is the addr in the pad_address + + ldr r1, [r0] + str r1, [r4, r5] @ save the content of the addr + + ldr r1, [r3, r5] + str r1, [r0] @ set the save val to the addr + + add r5, r5, #4 + cmp r6, r5 + bne padsave +padsave_done: + + mov32 r5, TEGRA_CLK_RESET_BASE + ldr r0, [r5, #CLK_RESET_SCLK_BURST] + adr r2, tegra20_sclk_save + str r0, [r2] + dsb + ret lr + +tegra20_sdram_pad_address: + .word TEGRA_APB_MISC_BASE + APB_MISC_XM2CFGCPADCTRL + .word TEGRA_APB_MISC_BASE + APB_MISC_XM2CFGDPADCTRL + .word TEGRA_APB_MISC_BASE + APB_MISC_XM2CLKCFGPADCTRL + .word TEGRA_APB_MISC_BASE + APB_MISC_XM2COMPPADCTRL + .word TEGRA_APB_MISC_BASE + APB_MISC_XM2VTTGENPADCTRL + .word TEGRA_APB_MISC_BASE + APB_MISC_XM2CFGCPADCTRL2 + .word TEGRA_APB_MISC_BASE + APB_MISC_XM2CFGDPADCTRL2 + +tegra20_sdram_pad_size: + .word tegra20_sdram_pad_size - tegra20_sdram_pad_address + +tegra20_sdram_pad_safe: + .word 0x8 + .word 0x8 + .word 0x0 + .word 0x8 + .word 0x5500 + .word 0x08080040 + .word 0x0 + +tegra20_sclk_save: + .word 0x0 + +tegra20_sdram_pad_save: + .rept (tegra20_sdram_pad_size - tegra20_sdram_pad_address) / 4 + .long 0 + .endr + + .ltorg +/* dummy symbol for end of IRAM */ + .align L1_CACHE_SHIFT + .globl tegra20_iram_end +tegra20_iram_end: + b . +#endif diff --git a/arch/arm/mach-tegra/sleep-tegra30.S b/arch/arm/mach-tegra/sleep-tegra30.S new file mode 100644 index 000000000..5d8d13aea --- /dev/null +++ b/arch/arm/mach-tegra/sleep-tegra30.S @@ -0,0 +1,831 @@ +/* + * 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/>. + */ + +#include <linux/linkage.h> + +#include <soc/tegra/fuse.h> + +#include <asm/asm-offsets.h> +#include <asm/assembler.h> +#include <asm/cache.h> + +#include "flowctrl.h" +#include "irammap.h" +#include "sleep.h" + +#define EMC_CFG 0xc +#define EMC_ADR_CFG 0x10 +#define EMC_TIMING_CONTROL 0x28 +#define EMC_REFRESH 0x70 +#define EMC_NOP 0xdc +#define EMC_SELF_REF 0xe0 +#define EMC_MRW 0xe8 +#define EMC_FBIO_CFG5 0x104 +#define EMC_AUTO_CAL_CONFIG 0x2a4 +#define EMC_AUTO_CAL_INTERVAL 0x2a8 +#define EMC_AUTO_CAL_STATUS 0x2ac +#define EMC_REQ_CTRL 0x2b0 +#define EMC_CFG_DIG_DLL 0x2bc +#define EMC_EMC_STATUS 0x2b4 +#define EMC_ZCAL_INTERVAL 0x2e0 +#define EMC_ZQ_CAL 0x2ec +#define EMC_XM2VTTGENPADCTRL 0x310 +#define EMC_XM2VTTGENPADCTRL2 0x314 + +#define PMC_CTRL 0x0 +#define PMC_CTRL_SIDE_EFFECT_LP0 (1 << 14) /* enter LP0 when CPU pwr gated */ + +#define PMC_PLLP_WB0_OVERRIDE 0xf8 +#define PMC_IO_DPD_REQ 0x1b8 +#define PMC_IO_DPD_STATUS 0x1bc + +#define CLK_RESET_CCLK_BURST 0x20 +#define CLK_RESET_CCLK_DIVIDER 0x24 +#define CLK_RESET_SCLK_BURST 0x28 +#define CLK_RESET_SCLK_DIVIDER 0x2c + +#define CLK_RESET_PLLC_BASE 0x80 +#define CLK_RESET_PLLC_MISC 0x8c +#define CLK_RESET_PLLM_BASE 0x90 +#define CLK_RESET_PLLM_MISC 0x9c +#define CLK_RESET_PLLP_BASE 0xa0 +#define CLK_RESET_PLLP_MISC 0xac +#define CLK_RESET_PLLA_BASE 0xb0 +#define CLK_RESET_PLLA_MISC 0xbc +#define CLK_RESET_PLLX_BASE 0xe0 +#define CLK_RESET_PLLX_MISC 0xe4 +#define CLK_RESET_PLLX_MISC3 0x518 +#define CLK_RESET_PLLX_MISC3_IDDQ 3 +#define CLK_RESET_PLLM_MISC_IDDQ 5 +#define CLK_RESET_PLLC_MISC_IDDQ 26 + +#define CLK_RESET_CLK_SOURCE_MSELECT 0x3b4 + +#define MSELECT_CLKM (0x3 << 30) + +#define LOCK_DELAY 50 /* safety delay after lock is detected */ + +#define TEGRA30_POWER_HOTPLUG_SHUTDOWN (1 << 27) /* Hotplug shutdown */ + +.macro emc_device_mask, rd, base + ldr \rd, [\base, #EMC_ADR_CFG] + tst \rd, #0x1 + moveq \rd, #(0x1 << 8) @ just 1 device + movne \rd, #(0x3 << 8) @ 2 devices +.endm + +.macro emc_timing_update, rd, base + mov \rd, #1 + str \rd, [\base, #EMC_TIMING_CONTROL] +1001: + ldr \rd, [\base, #EMC_EMC_STATUS] + tst \rd, #(0x1<<23) @ wait EMC_STATUS_TIMING_UPDATE_STALLED is clear + bne 1001b +.endm + +.macro pll_enable, rd, r_car_base, pll_base, pll_misc + ldr \rd, [\r_car_base, #\pll_base] + tst \rd, #(1 << 30) + orreq \rd, \rd, #(1 << 30) + streq \rd, [\r_car_base, #\pll_base] + /* Enable lock detector */ + .if \pll_misc + ldr \rd, [\r_car_base, #\pll_misc] + bic \rd, \rd, #(1 << 18) + str \rd, [\r_car_base, #\pll_misc] + ldr \rd, [\r_car_base, #\pll_misc] + ldr \rd, [\r_car_base, #\pll_misc] + orr \rd, \rd, #(1 << 18) + str \rd, [\r_car_base, #\pll_misc] + .endif +.endm + +.macro pll_locked, rd, r_car_base, pll_base +1: + ldr \rd, [\r_car_base, #\pll_base] + tst \rd, #(1 << 27) + beq 1b +.endm + +.macro pll_iddq_exit, rd, car, iddq, iddq_bit + ldr \rd, [\car, #\iddq] + bic \rd, \rd, #(1<<\iddq_bit) + str \rd, [\car, #\iddq] +.endm + +.macro pll_iddq_entry, rd, car, iddq, iddq_bit + ldr \rd, [\car, #\iddq] + orr \rd, \rd, #(1<<\iddq_bit) + str \rd, [\car, #\iddq] +.endm + +#if defined(CONFIG_HOTPLUG_CPU) || defined(CONFIG_PM_SLEEP) +/* + * tegra30_hotplug_shutdown(void) + * + * Powergates the current CPU. + * Should never return. + */ +ENTRY(tegra30_hotplug_shutdown) + /* Powergate this CPU */ + mov r0, #TEGRA30_POWER_HOTPLUG_SHUTDOWN + bl tegra30_cpu_shutdown + ret lr @ should never get here +ENDPROC(tegra30_hotplug_shutdown) + +/* + * tegra30_cpu_shutdown(unsigned long flags) + * + * Puts the current CPU in wait-for-event mode on the flow controller + * and powergates it -- flags (in R0) indicate the request type. + * + * r10 = SoC ID + * corrupts r0-r4, r10-r12 + */ +ENTRY(tegra30_cpu_shutdown) + cpu_id r3 + tegra_get_soc_id TEGRA_APB_MISC_VIRT, r10 + cmp r10, #TEGRA30 + bne _no_cpu0_chk @ It's not Tegra30 + + cmp r3, #0 + reteq lr @ Must never be called for CPU 0 +_no_cpu0_chk: + + ldr r12, =TEGRA_FLOW_CTRL_VIRT + cpu_to_csr_reg r1, r3 + add r1, r1, r12 @ virtual CSR address for this CPU + cpu_to_halt_reg r2, r3 + add r2, r2, r12 @ virtual HALT_EVENTS address for this CPU + + /* + * Clear this CPU's "event" and "interrupt" flags and power gate + * it when halting but not before it is in the "WFE" state. + */ + movw r12, \ + FLOW_CTRL_CSR_INTR_FLAG | FLOW_CTRL_CSR_EVENT_FLAG | \ + FLOW_CTRL_CSR_ENABLE + cmp r10, #TEGRA30 + moveq r4, #(1 << 4) @ wfe bitmap + movne r4, #(1 << 8) @ wfi bitmap + ARM( orr r12, r12, r4, lsl r3 ) + THUMB( lsl r4, r4, r3 ) + THUMB( orr r12, r12, r4 ) + str r12, [r1] + + /* Halt this CPU. */ + mov r3, #0x400 +delay_1: + subs r3, r3, #1 @ delay as a part of wfe war. + bge delay_1; + cpsid a @ disable imprecise aborts. + ldr r3, [r1] @ read CSR + str r3, [r1] @ clear CSR + + tst r0, #TEGRA30_POWER_HOTPLUG_SHUTDOWN + beq flow_ctrl_setting_for_lp2 + + /* flow controller set up for hotplug */ + mov r3, #FLOW_CTRL_WAITEVENT @ For hotplug + b flow_ctrl_done +flow_ctrl_setting_for_lp2: + /* flow controller set up for LP2 */ + cmp r10, #TEGRA30 + moveq r3, #FLOW_CTRL_WAIT_FOR_INTERRUPT @ For LP2 + movne r3, #FLOW_CTRL_WAITEVENT + orrne r3, r3, #FLOW_CTRL_HALT_GIC_IRQ + orrne r3, r3, #FLOW_CTRL_HALT_GIC_FIQ +flow_ctrl_done: + cmp r10, #TEGRA30 + str r3, [r2] + ldr r0, [r2] + b wfe_war + +__cpu_reset_again: + dsb + .align 5 + wfeeq @ CPU should be power gated here + wfine +wfe_war: + b __cpu_reset_again + + /* + * 38 nop's, which fills reset of wfe cache line and + * 4 more cachelines with nop + */ + .rept 38 + nop + .endr + b . @ should never get here + +ENDPROC(tegra30_cpu_shutdown) +#endif + +#ifdef CONFIG_PM_SLEEP +/* + * tegra30_sleep_core_finish(unsigned long v2p) + * + * Enters suspend in LP0 or LP1 by turning off the MMU and jumping to + * tegra30_tear_down_core in IRAM + */ +ENTRY(tegra30_sleep_core_finish) + /* Flush, disable the L1 data cache and exit SMP */ + bl tegra_disable_clean_inv_dcache + + /* + * Preload all the address literals that are needed for the + * CPU power-gating process, to avoid loading from SDRAM which + * are not supported once SDRAM is put into self-refresh. + * LP0 / LP1 use physical address, since the MMU needs to be + * disabled before putting SDRAM into self-refresh to avoid + * memory access due to page table walks. + */ + mov32 r4, TEGRA_PMC_BASE + mov32 r5, TEGRA_CLK_RESET_BASE + mov32 r6, TEGRA_FLOW_CTRL_BASE + mov32 r7, TEGRA_TMRUS_BASE + + mov32 r3, tegra_shut_off_mmu + add r3, r3, r0 + + mov32 r0, tegra30_tear_down_core + mov32 r1, tegra30_iram_start + sub r0, r0, r1 + mov32 r1, TEGRA_IRAM_LPx_RESUME_AREA + add r0, r0, r1 + + ret r3 +ENDPROC(tegra30_sleep_core_finish) + +/* + * 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 */ + mov r0, #TEGRA_FLUSH_CACHE_LOUIS + 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 + ret 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) + +/* START OF ROUTINES COPIED TO IRAM */ + .align L1_CACHE_SHIFT + .globl tegra30_iram_start +tegra30_iram_start: + +/* + * tegra30_lp1_reset + * + * reset vector for LP1 restore; copied into IRAM during suspend. + * Brings the system back up to a safe staring point (SDRAM out of + * self-refresh, PLLC, PLLM and PLLP reenabled, CPU running on PLLX, + * system clock running on the same PLL that it suspended at), and + * jumps to tegra_resume to restore virtual addressing. + * The physical address of tegra_resume expected to be stored in + * PMC_SCRATCH41. + * + * NOTE: THIS *MUST* BE RELOCATED TO TEGRA_IRAM_LPx_RESUME_AREA. + */ +ENTRY(tegra30_lp1_reset) + /* + * The CPU and system bus are running at 32KHz and executing from + * IRAM when this code is executed; immediately switch to CLKM and + * enable PLLP, PLLM, PLLC, PLLA and PLLX. + */ + mov32 r0, TEGRA_CLK_RESET_BASE + + mov r1, #(1 << 28) + str r1, [r0, #CLK_RESET_SCLK_BURST] + str r1, [r0, #CLK_RESET_CCLK_BURST] + mov r1, #0 + str r1, [r0, #CLK_RESET_CCLK_DIVIDER] + str r1, [r0, #CLK_RESET_SCLK_DIVIDER] + + tegra_get_soc_id TEGRA_APB_MISC_BASE, r10 + cmp r10, #TEGRA30 + beq _no_pll_iddq_exit + + pll_iddq_exit r1, r0, CLK_RESET_PLLM_MISC, CLK_RESET_PLLM_MISC_IDDQ + pll_iddq_exit r1, r0, CLK_RESET_PLLC_MISC, CLK_RESET_PLLC_MISC_IDDQ + pll_iddq_exit r1, r0, CLK_RESET_PLLX_MISC3, CLK_RESET_PLLX_MISC3_IDDQ + + mov32 r7, TEGRA_TMRUS_BASE + ldr r1, [r7] + add r1, r1, #2 + wait_until r1, r7, r3 + + /* enable PLLM via PMC */ + mov32 r2, TEGRA_PMC_BASE + ldr r1, [r2, #PMC_PLLP_WB0_OVERRIDE] + orr r1, r1, #(1 << 12) + str r1, [r2, #PMC_PLLP_WB0_OVERRIDE] + + pll_enable r1, r0, CLK_RESET_PLLM_BASE, 0 + pll_enable r1, r0, CLK_RESET_PLLC_BASE, 0 + pll_enable r1, r0, CLK_RESET_PLLX_BASE, 0 + + b _pll_m_c_x_done + +_no_pll_iddq_exit: + /* enable PLLM via PMC */ + mov32 r2, TEGRA_PMC_BASE + ldr r1, [r2, #PMC_PLLP_WB0_OVERRIDE] + orr r1, r1, #(1 << 12) + str r1, [r2, #PMC_PLLP_WB0_OVERRIDE] + + pll_enable r1, r0, CLK_RESET_PLLM_BASE, CLK_RESET_PLLM_MISC + pll_enable r1, r0, CLK_RESET_PLLC_BASE, CLK_RESET_PLLC_MISC + pll_enable r1, r0, CLK_RESET_PLLX_BASE, CLK_RESET_PLLX_MISC + +_pll_m_c_x_done: + pll_enable r1, r0, CLK_RESET_PLLP_BASE, CLK_RESET_PLLP_MISC + pll_enable r1, r0, CLK_RESET_PLLA_BASE, CLK_RESET_PLLA_MISC + + pll_locked r1, r0, CLK_RESET_PLLM_BASE + pll_locked r1, r0, CLK_RESET_PLLP_BASE + pll_locked r1, r0, CLK_RESET_PLLA_BASE + pll_locked r1, r0, CLK_RESET_PLLC_BASE + pll_locked r1, r0, CLK_RESET_PLLX_BASE + + mov32 r7, TEGRA_TMRUS_BASE + ldr r1, [r7] + add r1, r1, #LOCK_DELAY + wait_until r1, r7, r3 + + adr r5, tegra_sdram_pad_save + + ldr r4, [r5, #0x18] @ restore CLK_SOURCE_MSELECT + str r4, [r0, #CLK_RESET_CLK_SOURCE_MSELECT] + + ldr r4, [r5, #0x1C] @ restore SCLK_BURST + str r4, [r0, #CLK_RESET_SCLK_BURST] + + cmp r10, #TEGRA30 + movweq r4, #:lower16:((1 << 28) | (0x8)) @ burst policy is PLLX + movteq r4, #:upper16:((1 << 28) | (0x8)) + movwne r4, #:lower16:((1 << 28) | (0xe)) + movtne r4, #:upper16:((1 << 28) | (0xe)) + str r4, [r0, #CLK_RESET_CCLK_BURST] + + /* Restore pad power state to normal */ + ldr r1, [r5, #0x14] @ PMC_IO_DPD_STATUS + mvn r1, r1 + bic r1, r1, #(1 << 31) + orr r1, r1, #(1 << 30) + str r1, [r2, #PMC_IO_DPD_REQ] @ DPD_OFF + + cmp r10, #TEGRA30 + movweq r0, #:lower16:TEGRA_EMC_BASE @ r0 reserved for emc base + movteq r0, #:upper16:TEGRA_EMC_BASE + cmp r10, #TEGRA114 + movweq r0, #:lower16:TEGRA_EMC0_BASE + movteq r0, #:upper16:TEGRA_EMC0_BASE + cmp r10, #TEGRA124 + movweq r0, #:lower16:TEGRA124_EMC_BASE + movteq r0, #:upper16:TEGRA124_EMC_BASE + +exit_self_refresh: + ldr r1, [r5, #0xC] @ restore EMC_XM2VTTGENPADCTRL + str r1, [r0, #EMC_XM2VTTGENPADCTRL] + ldr r1, [r5, #0x10] @ restore EMC_XM2VTTGENPADCTRL2 + str r1, [r0, #EMC_XM2VTTGENPADCTRL2] + ldr r1, [r5, #0x8] @ restore EMC_AUTO_CAL_INTERVAL + str r1, [r0, #EMC_AUTO_CAL_INTERVAL] + + /* Relock DLL */ + ldr r1, [r0, #EMC_CFG_DIG_DLL] + orr r1, r1, #(1 << 30) @ set DLL_RESET + str r1, [r0, #EMC_CFG_DIG_DLL] + + emc_timing_update r1, r0 + + cmp r10, #TEGRA114 + movweq r1, #:lower16:TEGRA_EMC1_BASE + movteq r1, #:upper16:TEGRA_EMC1_BASE + cmpeq r0, r1 + + ldr r1, [r0, #EMC_AUTO_CAL_CONFIG] + orr r1, r1, #(1 << 31) @ set AUTO_CAL_ACTIVE + orreq r1, r1, #(1 << 27) @ set slave mode for channel 1 + str r1, [r0, #EMC_AUTO_CAL_CONFIG] + +emc_wait_auto_cal_onetime: + ldr r1, [r0, #EMC_AUTO_CAL_STATUS] + tst r1, #(1 << 31) @ wait until AUTO_CAL_ACTIVE is cleared + bne emc_wait_auto_cal_onetime + + ldr r1, [r0, #EMC_CFG] + bic r1, r1, #(1 << 31) @ disable DRAM_CLK_STOP_PD + str r1, [r0, #EMC_CFG] + + mov r1, #0 + str r1, [r0, #EMC_SELF_REF] @ take DRAM out of self refresh + mov r1, #1 + cmp r10, #TEGRA30 + streq r1, [r0, #EMC_NOP] + streq r1, [r0, #EMC_NOP] + streq r1, [r0, #EMC_REFRESH] + + emc_device_mask r1, r0 + +exit_selfrefresh_loop: + ldr r2, [r0, #EMC_EMC_STATUS] + ands r2, r2, r1 + bne exit_selfrefresh_loop + + lsr r1, r1, #8 @ devSel, bit0:dev0, bit1:dev1 + + mov32 r7, TEGRA_TMRUS_BASE + ldr r2, [r0, #EMC_FBIO_CFG5] + + and r2, r2, #3 @ check DRAM_TYPE + cmp r2, #2 + beq emc_lpddr2 + + /* Issue a ZQ_CAL for dev0 - DDR3 */ + mov32 r2, 0x80000011 @ DEV_SELECTION=2, LENGTH=LONG, CMD=1 + str r2, [r0, #EMC_ZQ_CAL] + ldr r2, [r7] + add r2, r2, #10 + wait_until r2, r7, r3 + + tst r1, #2 + beq zcal_done + + /* Issue a ZQ_CAL for dev1 - DDR3 */ + mov32 r2, 0x40000011 @ DEV_SELECTION=1, LENGTH=LONG, CMD=1 + str r2, [r0, #EMC_ZQ_CAL] + ldr r2, [r7] + add r2, r2, #10 + wait_until r2, r7, r3 + b zcal_done + +emc_lpddr2: + /* Issue a ZQ_CAL for dev0 - LPDDR2 */ + mov32 r2, 0x800A00AB @ DEV_SELECTION=2, MA=10, OP=0xAB + str r2, [r0, #EMC_MRW] + ldr r2, [r7] + add r2, r2, #1 + wait_until r2, r7, r3 + + tst r1, #2 + beq zcal_done + + /* Issue a ZQ_CAL for dev0 - LPDDR2 */ + mov32 r2, 0x400A00AB @ DEV_SELECTION=1, MA=10, OP=0xAB + str r2, [r0, #EMC_MRW] + ldr r2, [r7] + add r2, r2, #1 + wait_until r2, r7, r3 + +zcal_done: + mov r1, #0 @ unstall all transactions + str r1, [r0, #EMC_REQ_CTRL] + ldr r1, [r5, #0x4] @ restore EMC_ZCAL_INTERVAL + str r1, [r0, #EMC_ZCAL_INTERVAL] + ldr r1, [r5, #0x0] @ restore EMC_CFG + str r1, [r0, #EMC_CFG] + + /* Tegra114 had dual EMC channel, now config the other one */ + cmp r10, #TEGRA114 + bne __no_dual_emc_chanl + mov32 r1, TEGRA_EMC1_BASE + cmp r0, r1 + movne r0, r1 + addne r5, r5, #0x20 + bne exit_self_refresh +__no_dual_emc_chanl: + + mov32 r0, TEGRA_PMC_BASE + ldr r0, [r0, #PMC_SCRATCH41] + ret r0 @ jump to tegra_resume +ENDPROC(tegra30_lp1_reset) + + .align L1_CACHE_SHIFT +tegra30_sdram_pad_address: + .word TEGRA_EMC_BASE + EMC_CFG @0x0 + .word TEGRA_EMC_BASE + EMC_ZCAL_INTERVAL @0x4 + .word TEGRA_EMC_BASE + EMC_AUTO_CAL_INTERVAL @0x8 + .word TEGRA_EMC_BASE + EMC_XM2VTTGENPADCTRL @0xc + .word TEGRA_EMC_BASE + EMC_XM2VTTGENPADCTRL2 @0x10 + .word TEGRA_PMC_BASE + PMC_IO_DPD_STATUS @0x14 + .word TEGRA_CLK_RESET_BASE + CLK_RESET_CLK_SOURCE_MSELECT @0x18 + .word TEGRA_CLK_RESET_BASE + CLK_RESET_SCLK_BURST @0x1c +tegra30_sdram_pad_address_end: + +tegra114_sdram_pad_address: + .word TEGRA_EMC0_BASE + EMC_CFG @0x0 + .word TEGRA_EMC0_BASE + EMC_ZCAL_INTERVAL @0x4 + .word TEGRA_EMC0_BASE + EMC_AUTO_CAL_INTERVAL @0x8 + .word TEGRA_EMC0_BASE + EMC_XM2VTTGENPADCTRL @0xc + .word TEGRA_EMC0_BASE + EMC_XM2VTTGENPADCTRL2 @0x10 + .word TEGRA_PMC_BASE + PMC_IO_DPD_STATUS @0x14 + .word TEGRA_CLK_RESET_BASE + CLK_RESET_CLK_SOURCE_MSELECT @0x18 + .word TEGRA_CLK_RESET_BASE + CLK_RESET_SCLK_BURST @0x1c + .word TEGRA_EMC1_BASE + EMC_CFG @0x20 + .word TEGRA_EMC1_BASE + EMC_ZCAL_INTERVAL @0x24 + .word TEGRA_EMC1_BASE + EMC_AUTO_CAL_INTERVAL @0x28 + .word TEGRA_EMC1_BASE + EMC_XM2VTTGENPADCTRL @0x2c + .word TEGRA_EMC1_BASE + EMC_XM2VTTGENPADCTRL2 @0x30 +tegra114_sdram_pad_adress_end: + +tegra124_sdram_pad_address: + .word TEGRA124_EMC_BASE + EMC_CFG @0x0 + .word TEGRA124_EMC_BASE + EMC_ZCAL_INTERVAL @0x4 + .word TEGRA124_EMC_BASE + EMC_AUTO_CAL_INTERVAL @0x8 + .word TEGRA124_EMC_BASE + EMC_XM2VTTGENPADCTRL @0xc + .word TEGRA124_EMC_BASE + EMC_XM2VTTGENPADCTRL2 @0x10 + .word TEGRA_PMC_BASE + PMC_IO_DPD_STATUS @0x14 + .word TEGRA_CLK_RESET_BASE + CLK_RESET_CLK_SOURCE_MSELECT @0x18 + .word TEGRA_CLK_RESET_BASE + CLK_RESET_SCLK_BURST @0x1c +tegra124_sdram_pad_address_end: + +tegra30_sdram_pad_size: + .word tegra30_sdram_pad_address_end - tegra30_sdram_pad_address + +tegra114_sdram_pad_size: + .word tegra114_sdram_pad_adress_end - tegra114_sdram_pad_address + + .type tegra_sdram_pad_save, %object +tegra_sdram_pad_save: + .rept (tegra114_sdram_pad_adress_end - tegra114_sdram_pad_address) / 4 + .long 0 + .endr + +/* + * tegra30_tear_down_core + * + * copied into and executed from IRAM + * puts memory in self-refresh for LP0 and LP1 + */ +tegra30_tear_down_core: + bl tegra30_sdram_self_refresh + bl tegra30_switch_cpu_to_clk32k + b tegra30_enter_sleep + +/* + * tegra30_switch_cpu_to_clk32k + * + * In LP0 and LP1 all PLLs will be turned off. Switching the CPU and System CLK + * to the 32KHz clock. + * r4 = TEGRA_PMC_BASE + * r5 = TEGRA_CLK_RESET_BASE + * r6 = TEGRA_FLOW_CTRL_BASE + * r7 = TEGRA_TMRUS_BASE + * r10= SoC ID + */ +tegra30_switch_cpu_to_clk32k: + /* + * start by jumping to CLKM to safely disable PLLs, then jump to + * CLKS. + */ + mov r0, #(1 << 28) + str r0, [r5, #CLK_RESET_SCLK_BURST] + /* 2uS delay delay between changing SCLK and CCLK */ + ldr r1, [r7] + add r1, r1, #2 + wait_until r1, r7, r9 + str r0, [r5, #CLK_RESET_CCLK_BURST] + mov r0, #0 + str r0, [r5, #CLK_RESET_CCLK_DIVIDER] + str r0, [r5, #CLK_RESET_SCLK_DIVIDER] + + /* switch the clock source of mselect to be CLK_M */ + ldr r0, [r5, #CLK_RESET_CLK_SOURCE_MSELECT] + orr r0, r0, #MSELECT_CLKM + str r0, [r5, #CLK_RESET_CLK_SOURCE_MSELECT] + + /* 2uS delay delay between changing SCLK and disabling PLLs */ + ldr r1, [r7] + add r1, r1, #2 + wait_until r1, r7, r9 + + /* disable PLLM via PMC in LP1 */ + ldr r0, [r4, #PMC_PLLP_WB0_OVERRIDE] + bic r0, r0, #(1 << 12) + str r0, [r4, #PMC_PLLP_WB0_OVERRIDE] + + /* disable PLLP, PLLA, PLLC and PLLX */ + ldr r0, [r5, #CLK_RESET_PLLP_BASE] + bic r0, r0, #(1 << 30) + str r0, [r5, #CLK_RESET_PLLP_BASE] + ldr r0, [r5, #CLK_RESET_PLLA_BASE] + bic r0, r0, #(1 << 30) + str r0, [r5, #CLK_RESET_PLLA_BASE] + ldr r0, [r5, #CLK_RESET_PLLC_BASE] + bic r0, r0, #(1 << 30) + str r0, [r5, #CLK_RESET_PLLC_BASE] + ldr r0, [r5, #CLK_RESET_PLLX_BASE] + bic r0, r0, #(1 << 30) + str r0, [r5, #CLK_RESET_PLLX_BASE] + + cmp r10, #TEGRA30 + beq _no_pll_in_iddq + pll_iddq_entry r1, r5, CLK_RESET_PLLX_MISC3, CLK_RESET_PLLX_MISC3_IDDQ +_no_pll_in_iddq: + + /* switch to CLKS */ + mov r0, #0 /* brust policy = 32KHz */ + str r0, [r5, #CLK_RESET_SCLK_BURST] + + ret lr + +/* + * 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] + + tegra_get_soc_id TEGRA_APB_MISC_BASE, r10 + cmp r10, #TEGRA30 + mov r0, #FLOW_CTRL_WAIT_FOR_INTERRUPT + orreq r0, r0, #FLOW_CTRL_HALT_CPU_IRQ | FLOW_CTRL_HALT_CPU_FIQ + orrne r0, r0, #FLOW_CTRL_HALT_LIC_IRQ | FLOW_CTRL_HALT_LIC_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 + +/* + * tegra30_sdram_self_refresh + * + * called with MMU off and caches disabled + * must be executed from IRAM + * r4 = TEGRA_PMC_BASE + * r5 = TEGRA_CLK_RESET_BASE + * r6 = TEGRA_FLOW_CTRL_BASE + * r7 = TEGRA_TMRUS_BASE + * r10= SoC ID + */ +tegra30_sdram_self_refresh: + + adr r8, tegra_sdram_pad_save + tegra_get_soc_id TEGRA_APB_MISC_BASE, r10 + cmp r10, #TEGRA30 + adreq r2, tegra30_sdram_pad_address + ldreq r3, tegra30_sdram_pad_size + cmp r10, #TEGRA114 + adreq r2, tegra114_sdram_pad_address + ldreq r3, tegra114_sdram_pad_size + cmp r10, #TEGRA124 + adreq r2, tegra124_sdram_pad_address + ldreq r3, tegra30_sdram_pad_size + + mov r9, #0 + +padsave: + ldr r0, [r2, r9] @ r0 is the addr in the pad_address + + ldr r1, [r0] + str r1, [r8, r9] @ save the content of the addr + + add r9, r9, #4 + cmp r3, r9 + bne padsave +padsave_done: + + dsb + + cmp r10, #TEGRA30 + ldreq r0, =TEGRA_EMC_BASE @ r0 reserved for emc base addr + cmp r10, #TEGRA114 + ldreq r0, =TEGRA_EMC0_BASE + cmp r10, #TEGRA124 + ldreq r0, =TEGRA124_EMC_BASE + +enter_self_refresh: + cmp r10, #TEGRA30 + mov r1, #0 + str r1, [r0, #EMC_ZCAL_INTERVAL] + str r1, [r0, #EMC_AUTO_CAL_INTERVAL] + ldr r1, [r0, #EMC_CFG] + bic r1, r1, #(1 << 28) + bicne r1, r1, #(1 << 29) + str r1, [r0, #EMC_CFG] @ disable DYN_SELF_REF + + emc_timing_update r1, r0 + + ldr r1, [r7] + add r1, r1, #5 + wait_until r1, r7, r2 + +emc_wait_auto_cal: + ldr r1, [r0, #EMC_AUTO_CAL_STATUS] + tst r1, #(1 << 31) @ wait until AUTO_CAL_ACTIVE is cleared + bne emc_wait_auto_cal + + mov r1, #3 + str r1, [r0, #EMC_REQ_CTRL] @ stall incoming DRAM requests + +emcidle: + ldr r1, [r0, #EMC_EMC_STATUS] + tst r1, #4 + beq emcidle + + mov r1, #1 + str r1, [r0, #EMC_SELF_REF] + + emc_device_mask r1, r0 + +emcself: + ldr r2, [r0, #EMC_EMC_STATUS] + and r2, r2, r1 + cmp r2, r1 + bne emcself @ loop until DDR in self-refresh + + /* Put VTTGEN in the lowest power mode */ + ldr r1, [r0, #EMC_XM2VTTGENPADCTRL] + mov32 r2, 0xF8F8FFFF @ clear XM2VTTGEN_DRVUP and XM2VTTGEN_DRVDN + and r1, r1, r2 + str r1, [r0, #EMC_XM2VTTGENPADCTRL] + ldr r1, [r0, #EMC_XM2VTTGENPADCTRL2] + cmp r10, #TEGRA30 + orreq r1, r1, #7 @ set E_NO_VTTGEN + orrne r1, r1, #0x3f + str r1, [r0, #EMC_XM2VTTGENPADCTRL2] + + emc_timing_update r1, r0 + + /* Tegra114 had dual EMC channel, now config the other one */ + cmp r10, #TEGRA114 + bne no_dual_emc_chanl + mov32 r1, TEGRA_EMC1_BASE + cmp r0, r1 + movne r0, r1 + bne enter_self_refresh +no_dual_emc_chanl: + + ldr r1, [r4, #PMC_CTRL] + tst r1, #PMC_CTRL_SIDE_EFFECT_LP0 + bne pmc_io_dpd_skip + /* + * Put DDR_DATA, DISC_ADDR_CMD, DDR_ADDR_CMD, POP_ADDR_CMD, POP_CLK + * and COMP in the lowest power mode when LP1. + */ + mov32 r1, 0x8EC00000 + str r1, [r4, #PMC_IO_DPD_REQ] +pmc_io_dpd_skip: + + dsb + + ret lr + + .ltorg +/* dummy symbol for end of IRAM */ + .align L1_CACHE_SHIFT + .global tegra30_iram_end +tegra30_iram_end: + b . +#endif diff --git a/arch/arm/mach-tegra/sleep.S b/arch/arm/mach-tegra/sleep.S new file mode 100644 index 000000000..f024a5109 --- /dev/null +++ b/arch/arm/mach-tegra/sleep.S @@ -0,0 +1,161 @@ +/* + * arch/arm/mach-tegra/sleep.S + * + * Copyright (c) 2010-2011, NVIDIA Corporation. + * Copyright (c) 2011, Google, Inc. + * + * Author: Colin Cross <ccross@android.com> + * Gary King <gking@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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#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" + +#define CLK_RESET_CCLK_BURST 0x20 +#define CLK_RESET_CCLK_DIVIDER 0x24 + +#if defined(CONFIG_HOTPLUG_CPU) || defined(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 */ + cmp r0, #TEGRA_FLUSH_CACHE_ALL + blne v7_flush_dcache_louis + bleq v7_flush_dcache_all + + /* Trun off coherency */ + exit_smp r4, r5 + + ldmfd sp!, {r0, r4-r5, r7, r9-r11, pc} +ENDPROC(tegra_disable_clean_inv_dcache) +#endif + +#ifdef CONFIG_PM_SLEEP +/* + * tegra_init_l2_for_a15 + * + * set up the correct L2 cache data RAM latency + */ +ENTRY(tegra_init_l2_for_a15) + mrc p15, 0, r0, c0, c0, 5 + ubfx r0, r0, #8, #4 + tst r0, #1 @ only need for cluster 0 + bne _exit_init_l2_a15 + + mrc p15, 0x1, r0, c9, c0, 2 + and r0, r0, #7 + cmp r0, #2 + bicne r0, r0, #7 + orrne r0, r0, #2 + mcrne p15, 0x1, r0, c9, c0, 2 +_exit_init_l2_a15: + + ret lr +ENDPROC(tegra_init_l2_for_a15) + +/* + * 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) + mov r4, r0 + /* Flush and disable the L1 data cache */ + mov r0, #TEGRA_FLUSH_CACHE_ALL + bl tegra_disable_clean_inv_dcache + + mov r0, r4 + 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 + + ret 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 */ + check_cpu_part_num 0xc09, r9, r10 + movweq r2, #:lower16:(TEGRA_ARM_PERIF_BASE + 0x3000) + movteq r2, #:upper16:(TEGRA_ARM_PERIF_BASE + 0x3000) + moveq r3, #0 + streq r3, [r2, #L2X0_CTRL] +#endif + ret r0 +ENDPROC(tegra_shut_off_mmu) + .popsection + +/* + * tegra_switch_cpu_to_pllp + * + * In LP2 the normal cpu clock pllx will be turned off. Switch the CPU to pllp + */ +ENTRY(tegra_switch_cpu_to_pllp) + /* in LP2 idle (SDRAM active), set the CPU burst policy to PLLP */ + mov32 r5, TEGRA_CLK_RESET_BASE + mov r0, #(2 << 28) @ burst policy = run mode + orr r0, r0, #(4 << 4) @ use PLLP in run mode burst + str r0, [r5, #CLK_RESET_CCLK_BURST] + mov r0, #0 + str r0, [r5, #CLK_RESET_CCLK_DIVIDER] + ret lr +ENDPROC(tegra_switch_cpu_to_pllp) +#endif diff --git a/arch/arm/mach-tegra/sleep.h b/arch/arm/mach-tegra/sleep.h new file mode 100644 index 000000000..0d59360d8 --- /dev/null +++ b/arch/arm/mach-tegra/sleep.h @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2010-2013, 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_SLEEP_H +#define __MACH_TEGRA_SLEEP_H + +#include "iomap.h" +#include "irammap.h" + +#define TEGRA_ARM_PERIF_VIRT (TEGRA_ARM_PERIF_BASE - IO_CPU_PHYS \ + + IO_CPU_VIRT) +#define TEGRA_FLOW_CTRL_VIRT (TEGRA_FLOW_CTRL_BASE - IO_PPSB_PHYS \ + + IO_PPSB_VIRT) +#define TEGRA_CLK_RESET_VIRT (TEGRA_CLK_RESET_BASE - IO_PPSB_PHYS \ + + IO_PPSB_VIRT) +#define TEGRA_APB_MISC_VIRT (TEGRA_APB_MISC_BASE - IO_APB_PHYS \ + + IO_APB_VIRT) +#define TEGRA_PMC_VIRT (TEGRA_PMC_BASE - IO_APB_PHYS + IO_APB_VIRT) + +#define TEGRA_IRAM_RESET_BASE_VIRT (IO_IRAM_VIRT + \ + TEGRA_IRAM_RESET_HANDLER_OFFSET) + +/* PMC_SCRATCH37-39 and 41 are used for tegra_pen_lock and idle */ +#define PMC_SCRATCH37 0x130 +#define PMC_SCRATCH38 0x134 +#define PMC_SCRATCH39 0x138 +#define PMC_SCRATCH41 0x140 + +#ifdef CONFIG_ARCH_TEGRA_2x_SOC +#define CPU_RESETTABLE 2 +#define CPU_RESETTABLE_SOON 1 +#define CPU_NOT_RESETTABLE 0 +#endif + +/* flag of tegra_disable_clean_inv_dcache to do LoUIS or all */ +#define TEGRA_FLUSH_CACHE_LOUIS 0 +#define TEGRA_FLUSH_CACHE_ALL 1 + +#ifdef __ASSEMBLY__ +/* waits until the microsecond counter (base) is > rn */ +.macro wait_until, rn, base, tmp + add \rn, \rn, #1 +1001: ldr \tmp, [\base] + cmp \tmp, \rn + bmi 1001b +.endm + +/* returns the offset of the flow controller halt register for a cpu */ +.macro cpu_to_halt_reg rd, rcpu + cmp \rcpu, #0 + subne \rd, \rcpu, #1 + movne \rd, \rd, lsl #3 + addne \rd, \rd, #0x14 + moveq \rd, #0 +.endm + +/* returns the offset of the flow controller csr register for a cpu */ +.macro cpu_to_csr_reg rd, rcpu + cmp \rcpu, #0 + subne \rd, \rcpu, #1 + movne \rd, \rd, lsl #3 + addne \rd, \rd, #0x18 + moveq \rd, #8 +.endm + +/* returns the ID of the current processor */ +.macro cpu_id, rd + mrc p15, 0, \rd, c0, c0, 5 + and \rd, \rd, #0xF +.endm + +/* loads a 32-bit value into a register without a data access */ +.macro mov32, reg, val + movw \reg, #:lower16:\val + movt \reg, #:upper16:\val +.endm + +/* Marco to check CPU part num */ +.macro check_cpu_part_num part_num, tmp1, tmp2 + mrc p15, 0, \tmp1, c0, c0, 0 + ubfx \tmp1, \tmp1, #4, #12 + mov32 \tmp2, \part_num + cmp \tmp1, \tmp2 +.endm + +/* Macro to exit SMP coherency. */ +.macro exit_smp, tmp1, tmp2 + mrc p15, 0, \tmp1, c1, c0, 1 @ ACTLR + bic \tmp1, \tmp1, #(1<<6) | (1<<0) @ clear ACTLR.SMP | ACTLR.FW + mcr p15, 0, \tmp1, c1, c0, 1 @ ACTLR + isb +#ifdef CONFIG_HAVE_ARM_SCU + check_cpu_part_num 0xc09, \tmp1, \tmp2 + mrceq p15, 0, \tmp1, c0, c0, 5 + andeq \tmp1, \tmp1, #0xF + moveq \tmp1, \tmp1, lsl #2 + moveq \tmp2, #0xf + moveq \tmp2, \tmp2, lsl \tmp1 + ldreq \tmp1, =(TEGRA_ARM_PERIF_VIRT + 0xC) + streq \tmp2, [\tmp1] @ invalidate SCU tags for CPU + dsb +#endif +.endm + +/* Macro to check Tegra revision */ +#define APB_MISC_GP_HIDREV 0x804 +.macro tegra_get_soc_id base, tmp1 + mov32 \tmp1, \base + ldr \tmp1, [\tmp1, #APB_MISC_GP_HIDREV] + and \tmp1, \tmp1, #0xff00 + mov \tmp1, \tmp1, lsr #8 +.endm + +#else +void tegra_pen_lock(void); +void tegra_pen_unlock(void); +void tegra_resume(void); +int tegra_sleep_cpu_finish(unsigned long); +void tegra_disable_clean_inv_dcache(u32 flag); + +#ifdef CONFIG_HOTPLUG_CPU +void tegra20_hotplug_shutdown(void); +void tegra30_hotplug_shutdown(void); +#endif + +void tegra20_cpu_shutdown(int cpu); +int tegra20_cpu_is_resettable_soon(void); +void tegra20_cpu_clear_resettable(void); +#ifdef CONFIG_ARCH_TEGRA_2x_SOC +void tegra20_cpu_set_resettable_soon(void); +#else +static inline void tegra20_cpu_set_resettable_soon(void) {} +#endif + +int tegra20_sleep_cpu_secondary_finish(unsigned long); +void tegra20_tear_down_cpu(void); +int tegra30_sleep_cpu_secondary_finish(unsigned long); +void tegra30_tear_down_cpu(void); + +#endif +#endif diff --git a/arch/arm/mach-tegra/tegra.c b/arch/arm/mach-tegra/tegra.c new file mode 100644 index 000000000..861d88486 --- /dev/null +++ b/arch/arm/mach-tegra/tegra.c @@ -0,0 +1,168 @@ +/* + * NVIDIA Tegra SoC device tree board support + * + * Copyright (C) 2011, 2013, NVIDIA Corporation + * Copyright (C) 2010 Secret Lab Technologies, Ltd. + * Copyright (C) 2010 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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/clk.h> +#include <linux/clk/tegra.h> +#include <linux/dma-mapping.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/irqchip.h> +#include <linux/irqdomain.h> +#include <linux/kernel.h> +#include <linux/of_address.h> +#include <linux/of_fdt.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/pda_power.h> +#include <linux/platform_device.h> +#include <linux/serial_8250.h> +#include <linux/slab.h> +#include <linux/sys_soc.h> +#include <linux/usb/tegra_usb_phy.h> + +#include <soc/tegra/fuse.h> +#include <soc/tegra/pmc.h> + +#include <asm/hardware/cache-l2x0.h> +#include <asm/mach/arch.h> +#include <asm/mach/time.h> +#include <asm/mach-types.h> +#include <asm/setup.h> +#include <asm/trusted_foundations.h> + +#include "board.h" +#include "common.h" +#include "cpuidle.h" +#include "flowctrl.h" +#include "iomap.h" +#include "irq.h" +#include "pm.h" +#include "reset.h" +#include "sleep.h" + +/* + * Storage for debug-macro.S's state. + * + * This must be in .data not .bss so that it gets initialized each time the + * kernel is loaded. The data is declared here rather than debug-macro.S so + * that multiple inclusions of debug-macro.S point at the same data. + */ +u32 tegra_uart_config[3] = { + /* Debug UART initialization required */ + 1, + /* Debug UART physical address */ + 0, + /* Debug UART virtual address */ + 0, +}; + +static void __init tegra_init_early(void) +{ + of_register_trusted_foundations(); + tegra_cpu_reset_handler_init(); + tegra_flowctrl_init(); +} + +static void __init tegra_dt_init_irq(void) +{ + tegra_init_irq(); + irqchip_init(); +} + +static void __init tegra_dt_init(void) +{ + struct soc_device_attribute *soc_dev_attr; + struct soc_device *soc_dev; + struct device *parent = NULL; + + soc_dev_attr = kzalloc(sizeof(*soc_dev_attr), GFP_KERNEL); + if (!soc_dev_attr) + goto out; + + soc_dev_attr->family = kasprintf(GFP_KERNEL, "Tegra"); + soc_dev_attr->revision = kasprintf(GFP_KERNEL, "%d", + tegra_sku_info.revision); + soc_dev_attr->soc_id = kasprintf(GFP_KERNEL, "%u", tegra_get_chip_id()); + + soc_dev = soc_device_register(soc_dev_attr); + if (IS_ERR(soc_dev)) { + kfree(soc_dev_attr->family); + kfree(soc_dev_attr->revision); + kfree(soc_dev_attr->soc_id); + kfree(soc_dev_attr); + goto out; + } + + parent = soc_device_to_device(soc_dev); + + /* + * Finished with the static registrations now; fill in the missing + * devices + */ +out: + of_platform_populate(NULL, of_default_bus_match_table, NULL, parent); +} + +static void __init paz00_init(void) +{ + if (IS_ENABLED(CONFIG_ARCH_TEGRA_2x_SOC)) + tegra_paz00_wifikill_init(); +} + +static struct { + char *machine; + void (*init)(void); +} board_init_funcs[] = { + { "compal,paz00", paz00_init }, +}; + +static void __init tegra_dt_init_late(void) +{ + int i; + + tegra_init_suspend(); + tegra_cpuidle_init(); + + for (i = 0; i < ARRAY_SIZE(board_init_funcs); i++) { + if (of_machine_is_compatible(board_init_funcs[i].machine)) { + board_init_funcs[i].init(); + break; + } + } +} + +static const char * const tegra_dt_board_compat[] = { + "nvidia,tegra124", + "nvidia,tegra114", + "nvidia,tegra30", + "nvidia,tegra20", + NULL +}; + +DT_MACHINE_START(TEGRA_DT, "NVIDIA Tegra SoC (Flattened Device Tree)") + .l2c_aux_val = 0x3c400001, + .l2c_aux_mask = 0xc20fc3fe, + .smp = smp_ops(tegra_smp_ops), + .map_io = tegra_map_common_io, + .init_early = tegra_init_early, + .init_irq = tegra_dt_init_irq, + .init_machine = tegra_dt_init, + .init_late = tegra_dt_init_late, + .restart = tegra_pmc_restart, + .dt_compat = tegra_dt_board_compat, +MACHINE_END |