Initial commit; kernel source import

This commit is contained in:
Nathan
2025-04-06 23:50:55 -05:00
commit 25c6d769f4
45093 changed files with 18199410 additions and 0 deletions

149
arch/arm/mach-tegra/Kconfig Normal file
View File

@@ -0,0 +1,149 @@
config ARCH_TEGRA
bool "NVIDIA Tegra" if ARCH_MULTI_V7
select ARCH_HAS_CPUFREQ
select ARCH_REQUIRE_GPIOLIB
select CLKDEV_LOOKUP
select CLKSRC_MMIO
select CLKSRC_OF
select COMMON_CLK
select GENERIC_CLOCKEVENTS
select HAVE_ARM_SCU if SMP
select HAVE_ARM_TWD if LOCAL_TIMERS
select HAVE_CLK
select HAVE_SMP
select MIGHT_HAVE_CACHE_L2X0
select SOC_BUS
select SPARSE_IRQ
select USE_OF
help
This enables support for NVIDIA Tegra based systems.
menu "NVIDIA Tegra options"
depends on 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 ARM_GIC
select CPU_V7
select PINCTRL
select PINCTRL_TEGRA20
select PL310_ERRATA_727915 if CACHE_L2X0
select PL310_ERRATA_769419 if CACHE_L2X0
select USB_ULPI if USB_PHY
select USB_ULPI_VIEWPORT if USB_PHY
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 ARM_GIC
select CPU_V7
select PINCTRL
select PINCTRL_TEGRA30
select PL310_ERRATA_769419 if CACHE_L2X0
select USB_ULPI if USB_PHY
select USB_ULPI_VIEWPORT if USB_PHY
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_ARCH_TIMER
select ARM_ERRATA_798181
select ARM_GIC
select ARM_L1_CACHE_SHIFT_6
select CPU_FREQ_TABLE if CPU_FREQ
select CPU_V7
select PINCTRL
select PINCTRL_TEGRA114
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 ARCH_DMA_ADDR_T_64BIT if ARM_LPAE
select ARM_ARCH_TIMER
select ARM_GIC
select ARM_L1_CACHE_SHIFT_6
select CPU_V7
select PINCTRL
select PINCTRL_TEGRA124
help
Support for NVIDIA Tegra T124 processor family, based on the
ARM CortexA15MP CPU
config TEGRA_PCI
bool "PCI Express support"
depends on ARCH_TEGRA_2x_SOC
select PCI
config TEGRA_AHB
bool "Enable AHB driver for NVIDIA Tegra SoCs"
default y
help
Adds AHB configuration functionality for NVIDIA Tegra SoCs,
which controls AHB bus master arbitration and some
performance parameters(priority, prefech size).
config TEGRA_DVFS
bool "Tegra DVFS support"
help
This adds Tegra DVFS support. There could be several power
rails involved and there might be rails dependency based
on different SoCs, this config enable the generic DVFS
library needed by each SoCs DVFS files.
If in doubt, say N.
config TEGRA_124_DVFS
bool "Tegra124 DVFS support"
depends on ARCH_TEGRA_124_SOC
select TEGRA_DVFS
help
This enable Tegra124 DVFS functionality, it implements SoC
specific initialization code to be invoked by probe function
defined in generic Tegra DVFS driver, so while enabled it
needs the config TEGRA_DVFS to be enabled as well.
config TEGRA_EMC_SCALING_ENABLE
bool "Enable scaling the memory frequency"
config EDP_MANAGEMENT
bool "EDP management"
help
Power “sources” like batteries and regulators have limits
on how much current they can supply, the maximum current
the power source can supply is its “EDP limit”.
This is the NVIDIA technique for managing the peak current
consumption of the power rail.
EDP management manages the components to avoid exceeding
the design limits, it limits peak current consumption
while maximizing performance.
if EDP_MANAGEMENT
config TEGRA_CPU_EDP_LIMITS
bool "VDD_CPU EDP"
depends on ARCH_TEGRA
depends on CPU_FREQ && THERMAL
default n
help
Enforce electrical design limits on CPU rail.
VDD_CPU EDP prevents the CPU from drawing more current than
its voltage regulator can supply.
Limit maximum CPU frequency based on temperature and number
of on-line CPUs to keep CPU rail current within power supply
capabilities.
If in doubt, say "Y".
endif
endmenu

View File

@@ -0,0 +1,57 @@
asflags-y += -march=armv7-a
obj-y += io.o
obj-y += irq.o
obj-y += pmc.o
obj-y += flowctrl.o
obj-y += powergate.o
obj-y += apbio.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) += tegra2_emc.o
obj-$(CONFIG_ARCH_TEGRA_2x_SOC) += sleep-tegra20.o
obj-$(CONFIG_ARCH_TEGRA_2x_SOC) += pm-tegra20.o
obj-$(CONFIG_ARCH_TEGRA_2x_SOC) += powergate-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
obj-$(CONFIG_ARCH_TEGRA_3x_SOC) += powergate-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_TEGRA_PCI) += pcie.o
obj-$(CONFIG_ARCH_TEGRA_114_SOC) += sleep-tegra30.o
obj-$(CONFIG_ARCH_TEGRA_114_SOC) += pm-tegra30.o
obj-$(CONFIG_ARCH_TEGRA_114_SOC) += powergate-tegra114.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
obj-$(CONFIG_ARCH_TEGRA_124_SOC) += powergate-tegra124.o
obj-$(CONFIG_ARCH_TEGRA_124_SOC) += cluster-control.o
ifeq ($(CONFIG_CPU_IDLE),y)
obj-$(CONFIG_ARCH_TEGRA_124_SOC) += cpuidle-tegra114.o
endif
obj-$(CONFIG_ARCH_TEGRA_2x_SOC) += board-harmony-pcie.o
obj-$(CONFIG_ARCH_TEGRA_2x_SOC) += board-paz00.o
obj-$(CONFIG_TEGRA_DVFS) += tegra-dvfs.o
obj-$(CONFIG_TEGRA_124_DVFS) += tegra124-dvfs.o
obj-$(CONFIG_TEGRA_CPU_EDP_LIMITS) += tegra_cpu_edp.o
ifeq ($(CONFIG_TEGRA_CPU_EDP_LIMITS),y)
obj-$(CONFIG_ARCH_TEGRA_124_SOC) += tegra124_cpu_edp.o
endif
obj-y += board-venice-panel.o

217
arch/arm/mach-tegra/apbio.c Normal file
View File

@@ -0,0 +1,217 @@
/*
* Copyright (C) 2010 NVIDIA Corporation.
* 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/kernel.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/dmaengine.h>
#include <linux/dma-mapping.h>
#include <linux/spinlock.h>
#include <linux/completion.h>
#include <linux/sched.h>
#include <linux/mutex.h>
#include "apbio.h"
#include "iomap.h"
#if defined(CONFIG_TEGRA20_APB_DMA)
static DEFINE_MUTEX(tegra_apb_dma_lock);
static u32 *tegra_apb_bb;
static dma_addr_t tegra_apb_bb_phys;
static DECLARE_COMPLETION(tegra_apb_wait);
static int tegra_apb_readl_direct(unsigned long offset, u32 *value);
static int tegra_apb_writel_direct(u32 value, unsigned long offset);
static struct dma_chan *tegra_apb_dma_chan;
static struct dma_slave_config dma_sconfig;
static bool tegra_apb_dma_init(void)
{
dma_cap_mask_t mask;
mutex_lock(&tegra_apb_dma_lock);
/* Check to see if we raced to setup */
if (tegra_apb_dma_chan)
goto skip_init;
dma_cap_zero(mask);
dma_cap_set(DMA_SLAVE, mask);
tegra_apb_dma_chan = dma_request_channel(mask, NULL, NULL);
if (!tegra_apb_dma_chan) {
/*
* This is common until the device is probed, so don't
* shout about it.
*/
pr_debug("%s: can not allocate dma channel\n", __func__);
goto err_dma_alloc;
}
tegra_apb_bb = dma_alloc_coherent(NULL, sizeof(u32),
&tegra_apb_bb_phys, GFP_KERNEL);
if (!tegra_apb_bb) {
pr_err("%s: can not allocate bounce buffer\n", __func__);
goto err_buff_alloc;
}
dma_sconfig.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
dma_sconfig.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
dma_sconfig.src_maxburst = 1;
dma_sconfig.dst_maxburst = 1;
skip_init:
mutex_unlock(&tegra_apb_dma_lock);
return true;
err_buff_alloc:
dma_release_channel(tegra_apb_dma_chan);
tegra_apb_dma_chan = NULL;
err_dma_alloc:
mutex_unlock(&tegra_apb_dma_lock);
return false;
}
static void apb_dma_complete(void *args)
{
complete(&tegra_apb_wait);
}
static int do_dma_transfer(unsigned long apb_add,
enum dma_transfer_direction dir)
{
struct dma_async_tx_descriptor *dma_desc;
int ret;
if (dir == DMA_DEV_TO_MEM)
dma_sconfig.src_addr = apb_add;
else
dma_sconfig.dst_addr = apb_add;
ret = dmaengine_slave_config(tegra_apb_dma_chan, &dma_sconfig);
if (ret)
return ret;
dma_desc = dmaengine_prep_slave_single(tegra_apb_dma_chan,
tegra_apb_bb_phys, sizeof(u32), dir,
DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
if (!dma_desc)
return -EINVAL;
dma_desc->callback = apb_dma_complete;
dma_desc->callback_param = NULL;
INIT_COMPLETION(tegra_apb_wait);
dmaengine_submit(dma_desc);
dma_async_issue_pending(tegra_apb_dma_chan);
ret = wait_for_completion_timeout(&tegra_apb_wait,
msecs_to_jiffies(50));
if (WARN(ret == 0, "apb read dma timed out")) {
dmaengine_terminate_all(tegra_apb_dma_chan);
return -EFAULT;
}
return 0;
}
int tegra_apb_readl_using_dma(unsigned long offset, u32 *value)
{
int ret;
if (!tegra_apb_dma_chan && !tegra_apb_dma_init())
return tegra_apb_readl_direct(offset, value);
mutex_lock(&tegra_apb_dma_lock);
ret = do_dma_transfer(offset, DMA_DEV_TO_MEM);
if (ret < 0)
pr_err("error in reading offset 0x%08lx using dma\n", offset);
else
*value = *tegra_apb_bb;
mutex_unlock(&tegra_apb_dma_lock);
return ret;
}
int tegra_apb_writel_using_dma(u32 value, unsigned long offset)
{
int ret;
if (!tegra_apb_dma_chan && !tegra_apb_dma_init())
return tegra_apb_writel_direct(value, offset);
mutex_lock(&tegra_apb_dma_lock);
*((u32 *)tegra_apb_bb) = value;
ret = do_dma_transfer(offset, DMA_MEM_TO_DEV);
mutex_unlock(&tegra_apb_dma_lock);
if (ret < 0)
pr_err("error in writing offset 0x%08lx using dma\n", offset);
return ret;
}
#else
#define tegra_apb_readl_using_dma tegra_apb_readl_direct
#define tegra_apb_writel_using_dma tegra_apb_writel_direct
#endif
typedef int (*apbio_read_fptr)(unsigned long offset, u32 *value);
typedef int (*apbio_write_fptr)(u32 value, unsigned long offset);
static apbio_read_fptr apbio_read;
static apbio_write_fptr apbio_write;
static int tegra_apb_readl_direct(unsigned long offset, u32 *value)
{
*value = readl(IO_ADDRESS(offset));
return 0;
}
static int tegra_apb_writel_direct(u32 value, unsigned long offset)
{
writel(value, IO_ADDRESS(offset));
return 0;
}
void tegra_apb_io_init(void)
{
/* Need to use dma only when it is Tegra20 based platform */
if (of_machine_is_compatible("nvidia,tegra20") ||
!of_have_populated_dt()) {
apbio_read = tegra_apb_readl_using_dma;
apbio_write = tegra_apb_writel_using_dma;
} else {
apbio_read = tegra_apb_readl_direct;
apbio_write = tegra_apb_writel_direct;
}
}
u32 tegra_apb_readl(unsigned long offset)
{
u32 val;
if (apbio_read(offset, &val) < 0)
return 0;
else
return val;
}
void tegra_apb_writel(u32 value, unsigned long offset)
{
apbio_write(value, offset);
}

View File

@@ -0,0 +1,22 @@
/*
* Copyright (C) 2010 NVIDIA Corporation.
* 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.
*
*/
#ifndef __MACH_TEGRA_APBIO_H
#define __MACH_TEGRA_APBIO_H
void tegra_apb_io_init(void);
u32 tegra_apb_readl(unsigned long offset);
void tegra_apb_writel(u32 value, unsigned long offset);
#endif

View File

@@ -0,0 +1,89 @@
/*
* arch/arm/mach-tegra/board-harmony-pcie.c
*
* Copyright (C) 2010 CompuLab, Ltd.
* Mike Rapoport <mike@compulab.co.il>
*
* 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/kernel.h>
#include <linux/gpio.h>
#include <linux/err.h>
#include <linux/of_gpio.h>
#include <linux/regulator/consumer.h>
#include <asm/mach-types.h>
#include "board.h"
#ifdef CONFIG_TEGRA_PCI
int __init harmony_pcie_init(void)
{
struct device_node *np;
int en_vdd_1v05;
struct regulator *regulator = NULL;
int err;
np = of_find_node_by_path("/regulators/regulator@3");
if (!np) {
pr_err("%s: of_find_node_by_path failed\n", __func__);
return -ENODEV;
}
en_vdd_1v05 = of_get_named_gpio(np, "gpio", 0);
if (en_vdd_1v05 < 0) {
pr_err("%s: of_get_named_gpio failed: %d\n", __func__,
en_vdd_1v05);
return en_vdd_1v05;
}
err = gpio_request(en_vdd_1v05, "EN_VDD_1V05");
if (err) {
pr_err("%s: gpio_request failed: %d\n", __func__, err);
return err;
}
gpio_direction_output(en_vdd_1v05, 1);
regulator = regulator_get(NULL, "vdd_ldo0,vddio_pex_clk");
if (IS_ERR(regulator)) {
err = PTR_ERR(regulator);
pr_err("%s: regulator_get failed: %d\n", __func__, err);
goto err_reg;
}
err = regulator_enable(regulator);
if (err) {
pr_err("%s: regulator_enable failed: %d\n", __func__, err);
goto err_en;
}
err = tegra_pcie_init(true, true);
if (err) {
pr_err("%s: tegra_pcie_init failed: %d\n", __func__, err);
goto err_pcie;
}
return 0;
err_pcie:
regulator_disable(regulator);
err_en:
regulator_put(regulator);
err_reg:
gpio_free(en_vdd_1v05);
return err;
}
#endif

View File

@@ -0,0 +1,38 @@
/*
* Copyright (c) 2012-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_BOARD_PANEL_H
#define __MACH_TEGRA_BOARD_PANEL_H
#include <linux/platform_device.h>
#include <linux/platform_data/tegra_dc.h>
extern struct platform_pwm_backlight_data venice_bl_data;
#ifdef CONFIG_TEGRA_DC
extern atomic_t sd_brightness;
extern struct tegra_dc_platform_data venice_disp1_pdata;
extern struct tegra_dc_platform_data venice_disp2_pdata;
extern int venice_panel_init(void);
#else
static inline int venice_panel_init(void)
{
return -EINVAL;
}
#endif
#endif /* __MACH_TEGRA_BOARD_PANEL_H */

View File

@@ -0,0 +1,43 @@
/*
* 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/platform_device.h>
#include <linux/rfkill-gpio.h>
#include "board.h"
#include "board-paz00.h"
static struct rfkill_gpio_platform_data wifi_rfkill_platform_data = {
.name = "wifi_rfkill",
.reset_gpio = TEGRA_WIFI_RST,
.shutdown_gpio = TEGRA_WIFI_PWRN,
.type = RFKILL_TYPE_WLAN,
};
static struct platform_device wifi_rfkill_device = {
.name = "rfkill_gpio",
.id = -1,
.dev = {
.platform_data = &wifi_rfkill_platform_data,
},
};
void __init tegra_paz00_wifikill_init(void)
{
platform_device_register(&wifi_rfkill_device);
}

View File

@@ -0,0 +1,25 @@
/*
* arch/arm/mach-tegra/board-paz00.h
*
* Copyright (C) 2010 Marc Dietrich <marvin24@gmx.de>
*
* 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_PAZ00_H
#define _MACH_TEGRA_BOARD_PAZ00_H
#include "gpio-names.h"
#define TEGRA_WIFI_PWRN TEGRA_GPIO_PK5
#define TEGRA_WIFI_RST TEGRA_GPIO_PD1
#endif

View File

@@ -0,0 +1,821 @@
/*
* arch/arm/mach-tegra/board-venice-panel.c
*
* Copyright (c) 2011-2013, NVIDIA Corporation.
*
* 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/ioport.h>
#include <linux/fb.h>
#include <linux/nvhost.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/gpio.h>
#include <linux/ktime.h>
#include <linux/regulator/consumer.h>
#include <linux/pwm_backlight.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/of_gpio.h>
#ifdef CONFIG_TEGRA_DC
#include <linux/platform_data/tegra_dc.h>
#endif
#include <dt-bindings/gpio/tegra-gpio.h>
#include "irq.h"
#include "iomap.h"
#include "board.h"
#include "board-panel.h"
#include "common.h"
#ifdef CONFIG_TEGRA_DC
atomic_t sd_brightness = ATOMIC_INIT(255);
EXPORT_SYMBOL(sd_brightness);
static struct regulator *avdd_lcd_3v3;
static int power_off_time;
static ktime_t last_power_off;
struct platform_device * __init venice_host1x_init(void)
{
struct device_node *node = NULL;
struct platform_device *pdev = NULL;
node = of_find_compatible_node(NULL, NULL, "nvidia,tegra124-host1x");
if (!node)
return NULL;
pdev = of_find_device_by_node(node);
of_node_put(node);
return pdev;
}
#endif
static struct regulator *vdd_lcd_bl;
static struct regulator *lcd_bl_en;
static int pwm_to_bl_on;
static int bl_off_to_pwm;
static bool bl_enabled;
#ifdef CONFIG_TEGRA_DC
static bool edp_probed, edp_enabled;
static int venice_edp_regulator_probe(struct device *dev)
{
int ret;
avdd_lcd_3v3 = devm_regulator_get_optional(dev, "avdd-lcd");
if (IS_ERR(avdd_lcd_3v3)) {
dev_err(dev, "edp: avdd_lcd regulator get failed: %ld\n",
PTR_ERR(avdd_lcd_3v3));
return PTR_ERR(avdd_lcd_3v3);
}
vdd_lcd_bl = devm_regulator_get_optional(dev, "vdd-lcd-bl");
if (IS_ERR(vdd_lcd_bl)) {
dev_err(dev, "edp: vdd_bl regulator get failed: %ld\n",
PTR_ERR(vdd_lcd_bl));
return PTR_ERR(vdd_lcd_bl);
}
lcd_bl_en = devm_regulator_get_optional(dev, "lcd-bl-en");
if (IS_ERR(lcd_bl_en)) {
dev_err(dev, "edp: bl_en regulator get failed: %ld\n",
PTR_ERR(lcd_bl_en));
ret = PTR_ERR(lcd_bl_en);
lcd_bl_en = NULL;
return ret;
}
edp_probed = true;
return 0;
}
static int venice_edp_enable(struct device *dev)
{
int err = 0;
int diff = ktime_to_ms(ktime_sub(ktime_get_real(), last_power_off));
if (edp_enabled)
return 0;
if (ktime_to_ms(last_power_off) > 0 && diff < power_off_time)
msleep(power_off_time - diff);
err = regulator_enable(avdd_lcd_3v3);
if (err) {
dev_err(dev, "Enable regulator avdd_lcd failed: %d\n", err);
goto fail;
}
err = regulator_enable(vdd_lcd_bl);
if (err) {
dev_err(dev, "Enable regulator vdd_lcd_bl failed: %d\n", err);
regulator_disable(avdd_lcd_3v3);
goto fail;
}
edp_enabled = 1;
return 0;
fail:
return err;
}
static int venice_edp_disable(void)
{
if (!edp_enabled)
return 1;
if (vdd_lcd_bl)
regulator_disable(vdd_lcd_bl);
if (avdd_lcd_3v3)
regulator_disable(avdd_lcd_3v3);
last_power_off = ktime_get_real();
edp_enabled = 0;
return 0;
}
static int venice_edp_prepoweroff(void)
{
int ret;
if (lcd_bl_en && bl_enabled) {
ret = regulator_disable(lcd_bl_en);
if (ret)
pr_err("Disable lcd_bl_en failed: %d\n", ret);
else
bl_enabled = false;
msleep(bl_off_to_pwm);
}
return ret;
}
static struct tegra_dc_sd_settings venice_sd_settings = {
.enable = 0, /* disabled by default. */
.use_auto_pwm = false,
.hw_update_delay = 0,
.bin_width = -1,
.aggressiveness = 5,
.use_vid_luma = false,
.phase_in_adjustments = 0,
.k_limit_enable = true,
.k_limit = 200,
.sd_window_enable = false,
.soft_clipping_enable = true,
/* Low soft clipping threshold to compensate for aggressive k_limit */
.soft_clipping_threshold = 128,
.smooth_k_enable = false,
.smooth_k_incr = 64,
/* Default video coefficients */
.coeff = {5, 9, 2},
.fc = {0, 0},
/* Immediate backlight changes */
.blp = {1024, 255},
/* Gammas: R: 2.2 G: 2.2 B: 2.2 */
/* Default BL TF */
.bltf = {
{
{57, 65, 73, 82},
{92, 103, 114, 125},
{138, 150, 164, 178},
{193, 208, 224, 241},
},
},
/* Default LUT */
.lut = {
{
{255, 255, 255},
{199, 199, 199},
{153, 153, 153},
{116, 116, 116},
{85, 85, 85},
{59, 59, 59},
{36, 36, 36},
{17, 17, 17},
{0, 0, 0},
},
},
.sd_brightness = &sd_brightness,
.use_vpulse2 = true,
.bl_device_name = "backlight",
};
static struct tegra_dc_out_pin venice_edp_out_pins[] = {
{
.name = TEGRA_DC_OUT_PIN_H_SYNC,
.pol = TEGRA_DC_OUT_PIN_POL_LOW,
},
{
.name = TEGRA_DC_OUT_PIN_V_SYNC,
.pol = TEGRA_DC_OUT_PIN_POL_LOW,
},
{
.name = TEGRA_DC_OUT_PIN_PIXEL_CLOCK,
.pol = TEGRA_DC_OUT_PIN_POL_LOW,
},
{
.name = TEGRA_DC_OUT_PIN_DATA_ENABLE,
.pol = TEGRA_DC_OUT_PIN_POL_HIGH,
},
};
static struct tegra_dc_mode venice_mode;
static struct tegra_dp_out venice_dp;
static struct tegra_dc_out venice_disp1_out = {
.type = TEGRA_DC_OUT_DP,
.flags = TEGRA_DC_OUT_CONTINUOUS_MODE,
.parent_clk = "pll_d_out0",
.align = TEGRA_DC_ALIGN_MSB,
.order = TEGRA_DC_ORDER_RED_BLUE,
.dither = TEGRA_DC_TEMPORAL_DITHER,
.sd_settings = &venice_sd_settings,
.out_pins = venice_edp_out_pins,
.n_out_pins = ARRAY_SIZE(venice_edp_out_pins),
.enable = venice_edp_enable,
.disable = venice_edp_disable,
.prepoweroff = venice_edp_prepoweroff,
.regulator_probe = venice_edp_regulator_probe,
.dp = &venice_dp,
};
static struct tegra_fb_data venice_disp1_fb_data = {
.win = 0,
.bits_per_pixel = 32,
.flags = TEGRA_FB_FLIP_ON_PROBE,
};
struct tegra_dc_platform_data venice_disp1_pdata = {
.flags = TEGRA_DC_FLAG_ENABLED,
.default_out = &venice_disp1_out,
.fb = &venice_disp1_fb_data,
.emc_clk_rate = 204000000,
#ifdef CONFIG_TEGRA_DC_CMU
.cmu_enable = 1,
#endif
};
EXPORT_SYMBOL(venice_disp1_pdata);
static struct regulator *avdd_hdmi_pex;
static struct regulator *avdd_hdmi_pll;
static struct regulator *vdd_hdmi_5v0;
static int venice_hdmi_regulator_probe(struct device *dev)
{
struct tegra_dc_platform_data *pdata = dev_get_platdata(dev);
int gpio_hdmi_hpd;
if (!edp_probed)
return -EPROBE_DEFER;
avdd_hdmi_pex = devm_regulator_get_optional(dev, "avdd-hdmi-pex");
if (IS_ERR(avdd_hdmi_pex)) {
dev_err(dev, "HDMI: avdd-hdmi-pex regulator get failed: %ld\n",
PTR_ERR(avdd_hdmi_pex));
return PTR_ERR(avdd_hdmi_pex);
}
avdd_hdmi_pll = devm_regulator_get_optional(dev, "avdd-hdmi-pll");
if (IS_ERR(avdd_hdmi_pll)) {
dev_err(dev, "HDMI: avdd-hdmi-pll regulator get failed: %ld\n",
PTR_ERR(avdd_hdmi_pll));
return PTR_ERR(avdd_hdmi_pll);
}
vdd_hdmi_5v0 = devm_regulator_get_optional(dev, "vdd-hdmi-5v0");
if (IS_ERR(vdd_hdmi_5v0)) {
dev_err(dev, "HDMI: vdd-hdmi-5v0 regulator get failed: %ld\n",
PTR_ERR(vdd_hdmi_5v0));
return PTR_ERR(vdd_hdmi_5v0);
}
gpio_hdmi_hpd = of_get_named_gpio(dev->of_node, "hdmi-hpd-gpios", 0);
if (!gpio_is_valid(gpio_hdmi_hpd)) {
dev_err(dev, "HDMI: hdmi-hpd gpio get failed: %d\n",
gpio_hdmi_hpd);
return gpio_hdmi_hpd;
}
pdata->default_out->hotplug_gpio = gpio_hdmi_hpd;
return 0;
}
static int venice_hdmi_enable(struct device *dev)
{
int ret;
ret = regulator_enable(avdd_hdmi_pex);
if (ret < 0) {
pr_err("hdmi: couldn't enable regulator pex\n");
return ret;
}
ret = regulator_enable(avdd_hdmi_pll);
if (ret < 0) {
pr_err("hdmi: couldn't enable regulator pll\n");
regulator_disable(avdd_hdmi_pex);
return ret;
}
return 0;
}
static int venice_hdmi_disable(void)
{
if (avdd_hdmi_pex)
regulator_disable(avdd_hdmi_pex);
if (avdd_hdmi_pll)
regulator_disable(avdd_hdmi_pll);
return 0;
}
static int venice_hdmi_hotplug_init(struct device *dev)
{
int ret;
ret = regulator_enable(vdd_hdmi_5v0);
if (ret < 0)
pr_err("hdmi: couldn't enable regulator vdd\n");
return ret;
}
static int venice_hdmi_postsuspend(void)
{
if (vdd_hdmi_5v0)
regulator_disable(vdd_hdmi_5v0);
return 0;
}
struct tmds_config nyan_tmds_config[] = {
{ /* 480p/576p / 25.2MHz/27MHz modes */
.pclk = 27000000,
.pll0 = 0x01003010,
.pll1 = 0x00301B00,
.pe_current = 0x00000000,
.drive_current = 0x1F1F1F1F,
.peak_current = 0x03030303,
.bg_vref_level = 4,
},
{ /* 720p / 74.25MHz modes */
.pclk = 74250000,
.pll0 = 0x01003110,
.pll1 = 0x00301500,
.pe_current = 0x00000000,
.drive_current = 0x2C2C2C2C,
.peak_current = 0x07070707,
.bg_vref_level = 4,
},
{ /* 1080p / 148.5MHz modes */
.pclk = 148500000,
.pll0 = 0x01003310,
.pll1 = 0x00301500,
.pe_current = 0x00000000,
.drive_current = 0x33333333,
.peak_current = 0x0C0C0C0C,
.bg_vref_level = 4,
},
{
.pclk = INT_MAX,
.pll0 = 0x01003F10,
.pll1 = 0x00300F00,
.pe_current = 0x00000000,
.drive_current = 0x37373737, /* lane3 needs a slightly lower current */
.peak_current = 0x17171717,
.bg_vref_level = 6,
},
};
struct tegra_hdmi_out nyan_hdmi_out = {
.tmds_config = nyan_tmds_config,
.n_tmds_config = ARRAY_SIZE(nyan_tmds_config),
};
static struct tegra_dc_out venice_disp2_out = {
.type = TEGRA_DC_OUT_HDMI,
.flags = TEGRA_DC_OUT_HOTPLUG_HIGH |
TEGRA_DC_OUT_NVHDCP_POLICY_ON_DEMAND,
.parent_clk = "pll_d2_out0",
.dcc_bus = 3,
.max_pixclock = KHZ2PICOS(297000),
.align = TEGRA_DC_ALIGN_MSB,
.order = TEGRA_DC_ORDER_RED_BLUE,
.enable = venice_hdmi_enable,
.disable = venice_hdmi_disable,
.hotplug_init = venice_hdmi_hotplug_init,
.regulator_probe = venice_hdmi_regulator_probe,
.postsuspend = venice_hdmi_postsuspend,
.hdmi_out = &nyan_hdmi_out,
};
static struct tegra_fb_data venice_disp2_fb_data = {
.win = 0,
.xres = 1280,
.yres = 720,
.bits_per_pixel = 32,
.flags = TEGRA_FB_FLIP_ON_PROBE,
};
struct tegra_dc_platform_data venice_disp2_pdata = {
.default_out = &venice_disp2_out,
.fb = &venice_disp2_fb_data,
.emc_clk_rate = 300000000,
};
EXPORT_SYMBOL(venice_disp2_pdata);
static struct platform_device *disp1_device;
static int venice_bl_init(struct device *dev)
{
struct platform_pwm_backlight_data *data = dev_get_platdata(dev);
struct platform_pwm_backlight_data defdata;
int ret;
ret = pwm_backlight_parse_dt(dev, &defdata);
if (ret < 0) {
dev_err(dev, "failed to find platform data\n");
return ret;
}
data->max_brightness = defdata.max_brightness;
data->dft_brightness = defdata.dft_brightness;
data->levels = defdata.levels;
return 0;
}
static int venice_bl_notify(struct device *unused, int brightness)
{
int cur_sd_brightness = atomic_read(&sd_brightness);
int ret;
ret = (brightness * cur_sd_brightness) / 255;
if (lcd_bl_en) {
if (!brightness && bl_enabled) {
ret = regulator_disable(lcd_bl_en);
if (ret)
pr_err("Disable lcd_bl_en failed: %d\n", ret);
else
bl_enabled = false;
msleep(bl_off_to_pwm);
}
}
/* SD brightness is a percentage, max SD brightness is 255. */
return ret;
}
static void venice_bl_notify_after(struct device *unused, int brightness)
{
int ret;
/* Make sure that tegra dc 0 enable is completed */
if (tegra_dc_is_enabled(disp1_device) != 1)
return;
if (lcd_bl_en) {
if (brightness && !bl_enabled) {
msleep(pwm_to_bl_on);
ret = regulator_enable(lcd_bl_en);
if (ret)
pr_err("Enable lcd_bl_en failed: %d\n", ret);
else
bl_enabled = true;
}
}
}
static int venice_check_fb(struct device *dev, struct fb_info *info)
{
return info->device == &disp1_device->dev;
}
#else
static int venice_bl_init(struct device *dev)
{
struct platform_pwm_backlight_data *data = dev_get_platdata(dev);
struct platform_pwm_backlight_data defdata;
int ret;
u32 val;
ret = pwm_backlight_parse_dt(dev, &defdata);
if (ret < 0) {
dev_err(dev, "failed to find platform data\n");
return ret;
}
data->max_brightness = defdata.max_brightness;
data->dft_brightness = defdata.dft_brightness;
data->levels = defdata.levels;
/* avdd_lcd_3v3 is controlled by panel driver now */
vdd_lcd_bl = devm_regulator_get_optional(dev, "vdd-lcd-bl");
if (IS_ERR(vdd_lcd_bl)) {
dev_err(dev, "edp: vdd_bl regulator get failed: %ld\n",
PTR_ERR(vdd_lcd_bl));
return PTR_ERR(vdd_lcd_bl);
}
lcd_bl_en = devm_regulator_get_optional(dev, "lcd-bl-en");
if (IS_ERR(lcd_bl_en)) {
dev_err(dev, "edp: bl_en regulator get failed: %ld\n",
PTR_ERR(lcd_bl_en));
ret = PTR_ERR(lcd_bl_en);
lcd_bl_en = NULL;
return ret;
}
ret = of_property_read_u32(dev->of_node, "pwm-to-bl-on", &val);
if (ret < 0)
pwm_to_bl_on = 0;
else
pwm_to_bl_on = val;
ret = of_property_read_u32(dev->of_node, "bl-off-to-pwm", &val);
if (ret < 0)
bl_off_to_pwm = 0;
else
bl_off_to_pwm = val;
return 0;
}
static int venice_bl_notify(struct device *dev, int brightness)
{
int ret;
if (!lcd_bl_en)
return 0;
if (!brightness && bl_enabled) {
ret = regulator_disable(lcd_bl_en);
if (ret) {
dev_err(dev, "Disable lcd_bl_en failed: %d\n", ret);
return brightness;
}
msleep(bl_off_to_pwm); /* bl-off-to-pwm */
ret = regulator_disable(vdd_lcd_bl);
if (ret) {
dev_err(dev, "Disable lcd_bl_en failed: %d\n", ret);
ret = regulator_enable(lcd_bl_en);
return brightness;
}
bl_enabled = false;
}
dev_dbg(dev, "notify brightness=%d, bl_enabled=%d\n",
brightness, bl_enabled);
return brightness;
}
static void venice_bl_notify_after(struct device *dev, int brightness)
{
int ret;
if (!lcd_bl_en)
return;
if (brightness && !bl_enabled) {
ret = regulator_enable(vdd_lcd_bl);
if (ret) {
dev_err(dev, "Enable vdd_lcd_bl failed: %d\n", ret);
return;
}
msleep(pwm_to_bl_on);
ret = regulator_enable(lcd_bl_en);
if (ret) {
dev_err(dev, "Enable lcd_bl_en failed: %d\n", ret);
regulator_disable(vdd_lcd_bl);
return;
}
bl_enabled = true;
}
dev_dbg(dev, "notify_after brightness=%d, bl_enabled=%d\n",
brightness, bl_enabled);
}
#endif
struct platform_pwm_backlight_data venice_bl_data = {
.init = venice_bl_init,
.notify = venice_bl_notify,
.notify_after = venice_bl_notify_after,
#ifdef CONFIG_TEGRA_DC
/* Only toggle backlight on fb blank notifications for disp1 */
.check_fb = venice_check_fb,
#endif
.enable_gpio = -1,
};
EXPORT_SYMBOL(venice_bl_data);
#ifdef CONFIG_TEGRA_DC
static int venice_panel_mode_init(struct platform_device *dcs)
{
struct device *dev = &dcs->dev;
u32 val;
int ret;
/* Get mode information from dc0 */
ret = of_property_read_u32(dev->of_node, "depth", &val);
if (ret < 0) {
dev_err(dev, "Could not find depth property\n");
return ret;
}
venice_disp1_out.depth = val;
ret = of_property_read_u32(dev->of_node, "width", &val);
if (ret < 0) {
dev_err(dev, "Could not find width property\n");
return ret;
}
venice_disp1_out.width = val;
ret = of_property_read_u32(dev->of_node, "height", &val);
if (ret < 0) {
dev_err(dev, "Could not find height property\n");
return ret;
}
venice_disp1_out.height = val;
ret = of_property_read_u32(dev->of_node, "power-off-time", &val);
if (ret < 0)
power_off_time = 0;
else
power_off_time = val;
ret = of_property_read_u32(dev->of_node, "pwm-to-bl-on", &val);
if (ret < 0)
pwm_to_bl_on = 0;
else
pwm_to_bl_on = val;
ret = of_property_read_u32(dev->of_node, "bl-off-to-pwm", &val);
if (ret < 0)
bl_off_to_pwm = 0;
else
bl_off_to_pwm = val;
ret = of_property_read_u32(dev->of_node, "drive-current",
&venice_dp.drive_current);
if (ret < 0)
venice_dp.drive_current = 0;
ret = of_property_read_u32(dev->of_node, "preemphasis",
&venice_dp.preemphasis);
if (ret < 0)
venice_dp.preemphasis = 0;
/* Optional mode definitions follows */
ret = of_property_read_u32(dev->of_node, "pclk", &val);
if (ret == 0) {
venice_mode.pclk = val;
ret = of_property_read_u32(dev->of_node, "h-ref-to-sync",
&val);
if (ret < 0) {
dev_err(dev, "Could not find h-ref-to-sync property\n");
return ret;
}
venice_mode.h_ref_to_sync = val;
ret = of_property_read_u32(dev->of_node, "v-ref-to-sync",
&val);
if (ret < 0) {
dev_err(dev, "Could not find v-ref-to-sync property\n");
return ret;
}
venice_mode.v_ref_to_sync = val;
ret = of_property_read_u32(dev->of_node, "h-sync-width", &val);
if (ret < 0) {
dev_err(dev, "Could not find h-sync-width property\n");
return ret;
}
venice_mode.h_sync_width = val;
ret = of_property_read_u32(dev->of_node, "v-sync-width", &val);
if (ret < 0) {
dev_err(dev, "Could not find v-sync-width property\n");
return ret;
}
venice_mode.v_sync_width = val;
ret = of_property_read_u32(dev->of_node, "h-back-porch", &val);
if (ret < 0) {
dev_err(dev, "Could not find h-back-porch property\n");
return ret;
}
venice_mode.h_back_porch = val;
ret = of_property_read_u32(dev->of_node, "v-back-porch", &val);
if (ret < 0) {
dev_err(dev, "Could not find v-back-porch property\n");
return ret;
}
venice_mode.v_back_porch = val;
ret = of_property_read_u32(dev->of_node, "h-active", &val);
if (ret < 0) {
dev_err(dev, "Could not find h-active property\n");
return ret;
}
venice_mode.h_active = val;
ret = of_property_read_u32(dev->of_node, "v-active", &val);
if (ret < 0) {
dev_err(dev, "Could not find v-active property\n");
return ret;
}
venice_mode.v_active = val;
ret = of_property_read_u32(dev->of_node, "h-front-porch", &val);
if (ret < 0) {
dev_err(dev, "Could not find h-front-porch property\n");
return ret;
}
venice_mode.h_front_porch = val;
ret = of_property_read_u32(dev->of_node, "v-front-porch", &val);
if (ret < 0) {
dev_err(dev, "Could not find v-front-porch property\n");
return ret;
}
venice_mode.v_front_porch = val;
venice_disp1_out.modes = &venice_mode;
venice_disp1_out.n_modes = 1;
venice_disp1_fb_data.xres = venice_mode.h_active;
venice_disp1_fb_data.yres = venice_mode.v_active;
}
return 0;
}
void find_dc_dev(struct platform_device **dcs)
{
struct platform_device *pdev = NULL;
pdev = to_platform_device(bus_find_device_by_name(
&platform_bus_type, NULL, "tegradc.0"));
dcs[0] = pdev;
pdev = to_platform_device(bus_find_device_by_name(
&platform_bus_type, NULL, "tegradc.1"));
dcs[1] = pdev;
}
EXPORT_SYMBOL(find_dc_dev);
int __init venice_panel_init(void)
{
struct platform_device *phost1x = NULL;
struct platform_device *dc_devs[2];
int ret;
phost1x = venice_host1x_init();
if (!phost1x) {
pr_err("Could not find host1x device.\n");
return -EINVAL;
}
phost1x->dev.parent = NULL;
find_dc_dev(dc_devs);
if (!dc_devs[0] || !dc_devs[1]) {
pr_err("Could not find dc devices.\n");
return -EINVAL;
}
disp1_device = dc_devs[0];
ret = venice_panel_mode_init(disp1_device);
if (ret < 0)
return ret;
return 0;
}
#endif

View File

@@ -0,0 +1,53 @@
/*
* 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>
void __init tegra_map_common_io(void);
void __init tegra_init_irq(void);
int __init tegra_pcie_init(bool init_port0, bool init_port1);
#ifdef CONFIG_DEBUG_FS
int tegra_clk_debugfs_init(void);
#else
static inline int tegra_clk_debugfs_init(void) { return 0; }
#endif
int __init tegra_powergate_init(void);
#ifdef CONFIG_DEBUG_FS
int __init tegra_powergate_debugfs_init(void);
#else
static inline int tegra_powergate_debugfs_init(void) { return 0; }
#endif
int __init harmony_regulator_init(void);
#ifdef CONFIG_TEGRA_PCI
int __init harmony_pcie_init(void);
#else
static inline int harmony_pcie_init(void) { return 0; }
#endif
void __init tegra_paz00_wifikill_init(void);
#endif

View File

@@ -0,0 +1,392 @@
#include <linux/kernel.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/smp.h>
#include <linux/cpu_pm.h>
#include <linux/clockchips.h>
#include <linux/hrtimer.h>
#include <linux/debugfs.h>
#include <linux/module.h>
#include <linux/tegra-dvfs.h>
#include <linux/tegra-soc.h>
#include <linux/syscore_ops.h>
#include <linux/clk/tegra124-dfll.h>
#include <asm/smp_plat.h>
#include "sleep.h"
#include "pm.h"
#include "flowctrl.h"
#include "irq.h"
static ktime_t last_g2lp;
static unsigned int lp_cpu_max_rate;
static struct clk *cclk_g, *cclk_lp, *dfll_clk, *pll_x;
static u64 g_time, lp_time, last_update;
static DEFINE_SPINLOCK(cluster_stats_lock);
static void cluster_stats_update(void)
{
u64 cur_time, diff;
spin_lock(&cluster_stats_lock);
cur_time = get_jiffies_64();
diff = cur_time - last_update;
if (is_lp_cluster())
lp_time += diff;
else
g_time += diff;
last_update = cur_time;
spin_unlock(&cluster_stats_lock);
}
static inline void enable_pllx_cluster_port(void)
{
if (is_lp_cluster())
clk_prepare_enable(cclk_g);
else
clk_prepare_enable(cclk_lp);
}
static inline void disable_pllx_cluster_port(void)
{
if (is_lp_cluster())
clk_disable_unprepare(cclk_g);
else
clk_disable_unprepare(cclk_lp);
}
static void _setup_flowctrl(unsigned int flags)
{
unsigned int target_cluster = flags & TEGRA_POWER_CLUSTER_MASK;
unsigned int current_cluster, cpu;
u32 reg;
current_cluster = is_lp_cluster() ? TEGRA_POWER_CLUSTER_LP :
TEGRA_POWER_CLUSTER_G;
cpu = cpu_logical_map(smp_processor_id());
/*
* Read the flow controler CSR register and clear the CPU switch
* and immediate flags. If an actual CPU switch is to be performed,
* re-write the CSR register with the desired values.
*/
reg = flowctrl_read_cpu_csr(cpu);
reg &= ~(FLOW_CTRL_CSR_IMMEDIATE_WAKE |
FLOW_CTRL_CSR_SWITCH_CLUSTER);
if (flags & TEGRA_POWER_CLUSTER_IMMEDIATE)
reg |= FLOW_CTRL_CSR_IMMEDIATE_WAKE;
if (!target_cluster)
goto done;
reg &= ~FLOW_CTRL_CSR_ENABLE_EXT_MASK;
if (flags & TEGRA_POWER_CLUSTER_PART_CRAIL) {
if ((flags & TEGRA_POWER_CLUSTER_PART_NONCPU) == 0 &&
(current_cluster == TEGRA_POWER_CLUSTER_LP))
reg |= FLOW_CTRL_CSR_ENABLE_EXT_NCPU;
else
reg |= FLOW_CTRL_CSR_ENABLE_EXT_CRAIL;
}
if (flags & TEGRA_POWER_CLUSTER_PART_NONCPU)
reg |= FLOW_CTRL_CSR_ENABLE_EXT_NCPU;
if ((current_cluster != target_cluster) ||
(flags & TEGRA_POWER_CLUSTER_FORCE)) {
reg |= FLOW_CTRL_CSR_SWITCH_CLUSTER;
}
done:
flowctrl_write_cpu_csr(cpu, reg);
}
static void _restore_flowctrl(void)
{
unsigned int cpu;
u32 reg;
cpu = cpu_logical_map(smp_processor_id());
/*
* Make sure the switch and immediate flags are cleared in
* the flow controller to prevent undesirable side-effects
* for future users of the flow controller.
*/
reg = flowctrl_read_cpu_csr(cpu);
reg &= ~(FLOW_CTRL_CSR_IMMEDIATE_WAKE |
FLOW_CTRL_CSR_SWITCH_CLUSTER);
reg &= ~FLOW_CTRL_CSR_ENABLE_EXT_MASK;
flowctrl_write_cpu_csr(cpu, reg);
}
static int tegra_idle_power_down_last(unsigned int sleep_time,
unsigned int flags)
{
tegra_pmc_pm_set(TEGRA_CLUSTER_SWITCH);
if (flags & TEGRA_POWER_CLUSTER_G) {
if (is_lp_cluster())
flowctrl_cpu_rail_enable();
}
_setup_flowctrl(flags);
tegra_idle_last();
_restore_flowctrl();
return 0;
}
static int tegra_cluster_control(unsigned int flags)
{
unsigned int target_cluster = flags & TEGRA_POWER_CLUSTER_MASK;
unsigned int current_cluster;
unsigned long irq_flags;
int cpu, err = 0;
current_cluster = is_lp_cluster() ? TEGRA_POWER_CLUSTER_LP :
TEGRA_POWER_CLUSTER_G;
if ((target_cluster == TEGRA_POWER_CLUSTER_MASK) || !target_cluster)
return -EINVAL;
if ((current_cluster == target_cluster) &&
!(flags & TEGRA_POWER_CLUSTER_FORCE))
return -EEXIST;
enable_pllx_cluster_port();
local_irq_save(irq_flags);
if (num_online_cpus() > 1) {
err = -EBUSY;
goto out;
}
if (current_cluster != target_cluster && !timekeeping_suspended) {
ktime_t now = ktime_get();
if (target_cluster == TEGRA_POWER_CLUSTER_G) {
s64 t = ktime_to_us(ktime_sub(now, last_g2lp));
s64 t_off = 300; /* need to obtain this from DT? */
if (t_off > t)
udelay((unsigned int)(t_off - t));
} else
last_g2lp = now;
}
cpu = cpu_logical_map(smp_processor_id());
tegra_set_cpu_in_lp2();
cpu_pm_enter();
if (!timekeeping_suspended)
clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_ENTER, &cpu);
tegra_idle_power_down_last(0, flags);
if (!is_lp_cluster())
tegra_cluster_switch_restore_gic();
if (!timekeeping_suspended)
clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_EXIT, &cpu);
cpu_pm_exit();
tegra_clear_cpu_in_lp2();
out:
local_irq_restore(irq_flags);
disable_pllx_cluster_port();
return err;
}
int tegra_switch_cluster(int new_cluster)
{
int err = 0;
unsigned int flags;
unsigned long rate;
struct clk *new_clk;
bool on_dfll = (clk_get_parent(cclk_g) == dfll_clk);
flags = TEGRA_POWER_CLUSTER_IMMEDIATE;
flags |= TEGRA_POWER_CLUSTER_PART_DEFAULT;
if (new_cluster != TEGRA_CLUSTER_G && new_cluster != TEGRA_CLUSTER_LP)
return -EINVAL;
if (new_cluster == is_lp_cluster())
return 0;
if (new_cluster == TEGRA_CLUSTER_LP) {
rate = clk_get_rate(cclk_g);
if (rate > lp_cpu_max_rate) {
pr_warn("%s: No mode switch to LP at rate %lu\n",
__func__, rate);
return -EINVAL;
}
flags |= TEGRA_POWER_CLUSTER_LP;
new_clk = cclk_lp;
} else {
rate = clk_get_rate(cclk_lp);
flags |= TEGRA_POWER_CLUSTER_G;
new_clk = cclk_g;
}
if (on_dfll && new_cluster == TEGRA_CLUSTER_LP)
tegra124_dfll_unlock_loop();
err = clk_set_rate(new_clk, rate);
if (err) {
pr_err("%s: failed to set cluster clock rate: %d\n", __func__,
err);
goto abort;
}
cluster_stats_update();
err = tegra_cluster_control(flags);
if (err) {
pr_err("%s: failed to switch to %s cluster\n", __func__,
new_cluster == TEGRA_CLUSTER_LP ? "LP" : "G");
goto abort;
}
if (on_dfll && new_cluster == TEGRA_CLUSTER_G)
tegra124_dfll_lock_loop();
return 0;
abort:
if (on_dfll && new_cluster == TEGRA_CLUSTER_LP)
tegra124_dfll_lock_loop();
pr_err("%s: aborted switch to %s cluster\n", __func__,
new_cluster == TEGRA_CLUSTER_LP ? "LP" : "G");
return err;
}
#ifdef CONFIG_DEBUG_FS
static int cluster_get(void *data, u64 *val)
{
*val = (u64) is_lp_cluster();
return 0;
}
DEFINE_SIMPLE_ATTRIBUTE(cluster_ops, cluster_get, NULL, "%llu\n");
static int cluster_stats_show(struct seq_file *s, void *data)
{
u64 g, lp;
cluster_stats_update();
spin_lock(&cluster_stats_lock);
g = g_time;
lp = lp_time;
spin_unlock(&cluster_stats_lock);
seq_printf(s, "G %-10llu\n", g);
seq_printf(s, "LP %-10llu\n", lp);
return 0;
}
static int cluster_stats_open(struct inode *inode, struct file *file)
{
return single_open(file, cluster_stats_show, inode->i_private);
}
static const struct file_operations cluster_stats_ops = {
.open = cluster_stats_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
#endif
int tegra_cluster_control_init(void)
{
int err = 0, num_lp_freqs;
unsigned long *freqs_lp;
struct clk *tmp_parent;
#ifdef CONFIG_DEBUG_FS
struct dentry *rootdir;
#endif
cclk_g = clk_get(NULL, "cclk_g");
cclk_lp = clk_get(NULL, "cclk_lp");
dfll_clk = clk_get(NULL, "dfllCPU_out");
pll_x = clk_get(NULL, "pll_x");
tmp_parent = clk_get(NULL, "pll_p_out4");
if (IS_ERR(cclk_g) || IS_ERR(cclk_lp) || IS_ERR(dfll_clk) ||
IS_ERR(pll_x) || IS_ERR(tmp_parent)) {
pr_err("%s: Failed to get CPU clocks\n", __func__);
err = -EPROBE_DEFER;
goto err;
}
err = tegra_dvfs_get_freqs(cclk_lp, &freqs_lp, &num_lp_freqs);
if (err || !num_lp_freqs) {
pr_err("%s: Failed to get LP CPU freq-table\n", __func__);
goto err;
}
lp_cpu_max_rate = freqs_lp[num_lp_freqs - 1];
/*
* cclk_lp may initially be parented by pll_x_out0 (i.e. pll_x/2).
* To bypass the divider, switch to a non-pll_x clock source and
* then back to pll_x.
*/
if (clk_get_parent(cclk_lp) != pll_x) {
err = clk_set_parent(cclk_lp, tmp_parent);
if (err) {
pr_err("%s: Failed to LP CPU parent: %d\n", __func__,
err);
goto err;
}
err = clk_set_parent(cclk_lp, pll_x);
if (err) {
pr_err("%s: Failed to LP CPU parent: %d\n", __func__,
err);
goto err;
}
}
clk_put(tmp_parent);
#ifdef CONFIG_DEBUG_FS
rootdir = debugfs_create_dir("tegra_cluster", NULL);
if (rootdir) {
debugfs_create_file("current_cluster", S_IRUGO, rootdir,
NULL, &cluster_ops);
debugfs_create_file("time_in_state", S_IRUGO, rootdir,
NULL, &cluster_stats_ops);
}
#endif
last_update = get_jiffies_64();
return 0;
err:
if (!IS_ERR(cclk_g))
clk_put(cclk_g);
if (!IS_ERR(cclk_lp))
clk_put(cclk_lp);
if (!IS_ERR(dfll_clk))
clk_put(dfll_clk);
if (!IS_ERR(pll_x))
clk_put(pll_x);
if (!IS_ERR(tmp_parent))
clk_put(tmp_parent);
return err;
}

View File

@@ -0,0 +1,5 @@
extern u32 tegra_uart_config[4];
extern struct smp_operations tegra_smp_ops;
extern int tegra_cpu_kill(unsigned int cpu);
extern void tegra_cpu_die(unsigned int cpu);

View File

@@ -0,0 +1,110 @@
/*
* 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 <linux/module.h>
#include <linux/cpuidle.h>
#include <linux/cpu_pm.h>
#include <linux/clockchips.h>
#include <linux/clk/tegra.h>
#include <linux/tegra-soc.h>
#include <asm/cpuidle.h>
#include <asm/suspend.h>
#include <asm/smp_plat.h>
#include "pm.h"
#include "sleep.h"
#ifdef CONFIG_PM_SLEEP
#define TEGRA114_MAX_STATES 3
#else
#define TEGRA114_MAX_STATES 1
#endif
#ifdef CONFIG_PM_SLEEP
static void tegra114_cpu_cluster_power_down(void)
{
if (num_online_cpus() > 1 || !tegra_cpu_rail_off_ready()) {
cpu_suspend(0, tegra30_sleep_cpu_secondary_finish);
return;
}
tegra_idle_lp2_last();
}
static int tegra114_idle_power_down(struct cpuidle_device *dev,
struct cpuidle_driver *drv,
int index)
{
bool last_cpu;
local_fiq_disable();
last_cpu = tegra_set_cpu_in_lp2();
cpu_pm_enter();
clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_ENTER, &dev->cpu);
if (index == 2 && last_cpu)
tegra114_cpu_cluster_power_down();
else
cpu_suspend(0, tegra30_sleep_cpu_secondary_finish);
clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_EXIT, &dev->cpu);
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,
.flags = CPUIDLE_FLAG_TIME_VALID,
.name = "powered-down",
.desc = "CPU power gated",
},
[2] = {
.enter = tegra114_idle_power_down,
.exit_latency = 500,
.target_residency = 10000,
.power_usage = 0,
.flags = CPUIDLE_FLAG_TIME_VALID,
.name = "CPU-cluster-off",
.desc = "CPU cluster power gated",
},
#endif
},
};
int __init tegra114_cpuidle_init(void)
{
return cpuidle_register(&tegra_idle_driver, NULL);
}

View File

@@ -0,0 +1,217 @@
/*
* CPU idle driver for Tegra CPUs
*
* Copyright (c) 2010-2012, NVIDIA Corporation.
* Copyright (c) 2011 Google, Inc.
* Author: Colin Cross <ccross@android.com>
* Gary King <gking@nvidia.com>
*
* Rework for 3.3 by Peter De Schrijver <pdeschrijver@nvidia.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/cpuidle.h>
#include <linux/cpu_pm.h>
#include <linux/clockchips.h>
#include <linux/clk/tegra.h>
#include <asm/cpuidle.h>
#include <asm/proc-fns.h>
#include <asm/suspend.h>
#include <asm/smp_plat.h>
#include "pm.h"
#include "sleep.h"
#include "iomap.h"
#include "irq.h"
#include "flowctrl.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_TIME_VALID |
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 void __iomem *pmc = IO_ADDRESS(TEGRA_PMC_BASE);
static int tegra20_reset_sleeping_cpu_1(void)
{
int ret = 0;
tegra_pen_lock();
if (readl(pmc + PMC_SCRATCH41) == 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;
clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_ENTER, &dev->cpu);
tegra_idle_lp2_last();
clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_EXIT, &dev->cpu);
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)
{
clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_ENTER, &dev->cpu);
cpu_suspend(0, tegra20_sleep_cpu_secondary_finish);
tegra20_cpu_clear_resettable();
clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_EXIT, &dev->cpu);
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
int __init tegra20_cpuidle_init(void)
{
return cpuidle_register(&tegra_idle_driver, cpu_possible_mask);
}

View File

@@ -0,0 +1,149 @@
/*
* CPU idle driver for Tegra CPUs
*
* Copyright (c) 2010-2012, NVIDIA Corporation.
* Copyright (c) 2011 Google, Inc.
* Author: Colin Cross <ccross@android.com>
* Gary King <gking@nvidia.com>
*
* Rework for 3.3 by Peter De Schrijver <pdeschrijver@nvidia.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/cpuidle.h>
#include <linux/cpu_pm.h>
#include <linux/clockchips.h>
#include <linux/clk/tegra.h>
#include <asm/cpuidle.h>
#include <asm/proc-fns.h>
#include <asm/suspend.h>
#include <asm/smp_plat.h>
#include "pm.h"
#include "sleep.h"
#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,
.flags = CPUIDLE_FLAG_TIME_VALID,
.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;
}
clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_ENTER, &dev->cpu);
tegra_idle_lp2_last();
clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_EXIT, &dev->cpu);
return true;
}
#ifdef CONFIG_SMP
static bool tegra30_cpu_core_power_down(struct cpuidle_device *dev,
struct cpuidle_driver *drv,
int index)
{
clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_ENTER, &dev->cpu);
smp_wmb();
cpu_suspend(0, tegra30_sleep_cpu_secondary_finish);
clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_EXIT, &dev->cpu);
return true;
}
#else
static inline bool tegra30_cpu_core_power_down(struct cpuidle_device *dev,
struct cpuidle_driver *drv,
int index)
{
return true;
}
#endif
static int 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);
}

View File

@@ -0,0 +1,48 @@
/*
* 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 <linux/tegra-soc.h>
#include "cpuidle.h"
void __init tegra_cpuidle_init(void)
{
switch (tegra_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;
}
}

View File

@@ -0,0 +1,29 @@
/*
* 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);
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

View File

@@ -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/init.h>
#include <linux/kernel.h>
#include <linux/io.h>
#include <linux/cpumask.h>
#include <linux/tegra-soc.h>
#include "flowctrl.h"
#include "iomap.h"
#define FLOW_CTRL_RAM_REPAIR 0x40
#define FLOW_CTRL_RAM_REPAIR_BYPASS_EN BIT(2)
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 flowctrl_update(u8 offset, u32 value)
{
void __iomem *addr = IO_ADDRESS(TEGRA_FLOW_CTRL_BASE) + offset;
writel(value, addr);
/* ensure the update has reached the flow controller */
wmb();
readl_relaxed(addr);
}
static u32 flowctrl_read(u8 offset)
{
void __iomem *addr = IO_ADDRESS(TEGRA_FLOW_CTRL_BASE) + offset;
return readl(addr);
}
void flowctrl_ram_repair_enable(void)
{
u32 reg;
reg = flowctrl_read(FLOW_CTRL_RAM_REPAIR);
reg &= ~FLOW_CTRL_RAM_REPAIR_BYPASS_EN;
flowctrl_update(FLOW_CTRL_RAM_REPAIR, reg);
}
u32 flowctrl_read_cpu_csr(unsigned int cpuid)
{
return flowctrl_read(flowctrl_offset_cpu_csr[cpuid]);
}
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_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_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;
if (tegra_chip_id != TEGRA30)
flowctrl_ram_repair_enable();
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);
}
void flowctrl_cpu_rail_enable(void)
{
u32 reg;
reg = flowctrl_read(FLOW_CTLR_CPU_PWR_CSR);
reg |= FLOW_CTRL_CPU_PWR_CSR_RAIL_ENABLE;
flowctrl_update(FLOW_CTLR_CPU_PWR_CSR, reg);
}
void __init tegra_flowctrl_ram_repair_init(void)
{
flowctrl_ram_repair_enable();
}

View File

@@ -0,0 +1,69 @@
/*
* 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_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_IMMEDIATE_WAKE (1 << 3)
#define FLOW_CTRL_CSR_SWITCH_CLUSTER (1 << 2)
#define FLOW_CTRL_CSR_ENABLE (1 << 0)
#define FLOW_CTRL_HALT_CPU1_EVENTS 0x14
#define FLOW_CTRL_CPU1_CSR 0x18
#define FLOW_CTLR_CPU_PWR_CSR 0x38
#define FLOW_CTRL_CPU_PWR_CSR_RAIL_ENABLE BIT(0)
#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 flowctrl_cpu_rail_enable(void);
void tegra_flowctrl_ram_repair_init(void);
#endif
#endif

View File

@@ -0,0 +1,247 @@
/*
* arch/arm/mach-tegra/include/mach/gpio-names.h
*
* Copyright (c) 2010 Google, Inc
*
* Author:
* 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_GPIO_NAMES_H
#define __MACH_TEGRA_GPIO_NAMES_H
#define TEGRA_GPIO_PA0 0
#define TEGRA_GPIO_PA1 1
#define TEGRA_GPIO_PA2 2
#define TEGRA_GPIO_PA3 3
#define TEGRA_GPIO_PA4 4
#define TEGRA_GPIO_PA5 5
#define TEGRA_GPIO_PA6 6
#define TEGRA_GPIO_PA7 7
#define TEGRA_GPIO_PB0 8
#define TEGRA_GPIO_PB1 9
#define TEGRA_GPIO_PB2 10
#define TEGRA_GPIO_PB3 11
#define TEGRA_GPIO_PB4 12
#define TEGRA_GPIO_PB5 13
#define TEGRA_GPIO_PB6 14
#define TEGRA_GPIO_PB7 15
#define TEGRA_GPIO_PC0 16
#define TEGRA_GPIO_PC1 17
#define TEGRA_GPIO_PC2 18
#define TEGRA_GPIO_PC3 19
#define TEGRA_GPIO_PC4 20
#define TEGRA_GPIO_PC5 21
#define TEGRA_GPIO_PC6 22
#define TEGRA_GPIO_PC7 23
#define TEGRA_GPIO_PD0 24
#define TEGRA_GPIO_PD1 25
#define TEGRA_GPIO_PD2 26
#define TEGRA_GPIO_PD3 27
#define TEGRA_GPIO_PD4 28
#define TEGRA_GPIO_PD5 29
#define TEGRA_GPIO_PD6 30
#define TEGRA_GPIO_PD7 31
#define TEGRA_GPIO_PE0 32
#define TEGRA_GPIO_PE1 33
#define TEGRA_GPIO_PE2 34
#define TEGRA_GPIO_PE3 35
#define TEGRA_GPIO_PE4 36
#define TEGRA_GPIO_PE5 37
#define TEGRA_GPIO_PE6 38
#define TEGRA_GPIO_PE7 39
#define TEGRA_GPIO_PF0 40
#define TEGRA_GPIO_PF1 41
#define TEGRA_GPIO_PF2 42
#define TEGRA_GPIO_PF3 43
#define TEGRA_GPIO_PF4 44
#define TEGRA_GPIO_PF5 45
#define TEGRA_GPIO_PF6 46
#define TEGRA_GPIO_PF7 47
#define TEGRA_GPIO_PG0 48
#define TEGRA_GPIO_PG1 49
#define TEGRA_GPIO_PG2 50
#define TEGRA_GPIO_PG3 51
#define TEGRA_GPIO_PG4 52
#define TEGRA_GPIO_PG5 53
#define TEGRA_GPIO_PG6 54
#define TEGRA_GPIO_PG7 55
#define TEGRA_GPIO_PH0 56
#define TEGRA_GPIO_PH1 57
#define TEGRA_GPIO_PH2 58
#define TEGRA_GPIO_PH3 59
#define TEGRA_GPIO_PH4 60
#define TEGRA_GPIO_PH5 61
#define TEGRA_GPIO_PH6 62
#define TEGRA_GPIO_PH7 63
#define TEGRA_GPIO_PI0 64
#define TEGRA_GPIO_PI1 65
#define TEGRA_GPIO_PI2 66
#define TEGRA_GPIO_PI3 67
#define TEGRA_GPIO_PI4 68
#define TEGRA_GPIO_PI5 69
#define TEGRA_GPIO_PI6 70
#define TEGRA_GPIO_PI7 71
#define TEGRA_GPIO_PJ0 72
#define TEGRA_GPIO_PJ1 73
#define TEGRA_GPIO_PJ2 74
#define TEGRA_GPIO_PJ3 75
#define TEGRA_GPIO_PJ4 76
#define TEGRA_GPIO_PJ5 77
#define TEGRA_GPIO_PJ6 78
#define TEGRA_GPIO_PJ7 79
#define TEGRA_GPIO_PK0 80
#define TEGRA_GPIO_PK1 81
#define TEGRA_GPIO_PK2 82
#define TEGRA_GPIO_PK3 83
#define TEGRA_GPIO_PK4 84
#define TEGRA_GPIO_PK5 85
#define TEGRA_GPIO_PK6 86
#define TEGRA_GPIO_PK7 87
#define TEGRA_GPIO_PL0 88
#define TEGRA_GPIO_PL1 89
#define TEGRA_GPIO_PL2 90
#define TEGRA_GPIO_PL3 91
#define TEGRA_GPIO_PL4 92
#define TEGRA_GPIO_PL5 93
#define TEGRA_GPIO_PL6 94
#define TEGRA_GPIO_PL7 95
#define TEGRA_GPIO_PM0 96
#define TEGRA_GPIO_PM1 97
#define TEGRA_GPIO_PM2 98
#define TEGRA_GPIO_PM3 99
#define TEGRA_GPIO_PM4 100
#define TEGRA_GPIO_PM5 101
#define TEGRA_GPIO_PM6 102
#define TEGRA_GPIO_PM7 103
#define TEGRA_GPIO_PN0 104
#define TEGRA_GPIO_PN1 105
#define TEGRA_GPIO_PN2 106
#define TEGRA_GPIO_PN3 107
#define TEGRA_GPIO_PN4 108
#define TEGRA_GPIO_PN5 109
#define TEGRA_GPIO_PN6 110
#define TEGRA_GPIO_PN7 111
#define TEGRA_GPIO_PO0 112
#define TEGRA_GPIO_PO1 113
#define TEGRA_GPIO_PO2 114
#define TEGRA_GPIO_PO3 115
#define TEGRA_GPIO_PO4 116
#define TEGRA_GPIO_PO5 117
#define TEGRA_GPIO_PO6 118
#define TEGRA_GPIO_PO7 119
#define TEGRA_GPIO_PP0 120
#define TEGRA_GPIO_PP1 121
#define TEGRA_GPIO_PP2 122
#define TEGRA_GPIO_PP3 123
#define TEGRA_GPIO_PP4 124
#define TEGRA_GPIO_PP5 125
#define TEGRA_GPIO_PP6 126
#define TEGRA_GPIO_PP7 127
#define TEGRA_GPIO_PQ0 128
#define TEGRA_GPIO_PQ1 129
#define TEGRA_GPIO_PQ2 130
#define TEGRA_GPIO_PQ3 131
#define TEGRA_GPIO_PQ4 132
#define TEGRA_GPIO_PQ5 133
#define TEGRA_GPIO_PQ6 134
#define TEGRA_GPIO_PQ7 135
#define TEGRA_GPIO_PR0 136
#define TEGRA_GPIO_PR1 137
#define TEGRA_GPIO_PR2 138
#define TEGRA_GPIO_PR3 139
#define TEGRA_GPIO_PR4 140
#define TEGRA_GPIO_PR5 141
#define TEGRA_GPIO_PR6 142
#define TEGRA_GPIO_PR7 143
#define TEGRA_GPIO_PS0 144
#define TEGRA_GPIO_PS1 145
#define TEGRA_GPIO_PS2 146
#define TEGRA_GPIO_PS3 147
#define TEGRA_GPIO_PS4 148
#define TEGRA_GPIO_PS5 149
#define TEGRA_GPIO_PS6 150
#define TEGRA_GPIO_PS7 151
#define TEGRA_GPIO_PT0 152
#define TEGRA_GPIO_PT1 153
#define TEGRA_GPIO_PT2 154
#define TEGRA_GPIO_PT3 155
#define TEGRA_GPIO_PT4 156
#define TEGRA_GPIO_PT5 157
#define TEGRA_GPIO_PT6 158
#define TEGRA_GPIO_PT7 159
#define TEGRA_GPIO_PU0 160
#define TEGRA_GPIO_PU1 161
#define TEGRA_GPIO_PU2 162
#define TEGRA_GPIO_PU3 163
#define TEGRA_GPIO_PU4 164
#define TEGRA_GPIO_PU5 165
#define TEGRA_GPIO_PU6 166
#define TEGRA_GPIO_PU7 167
#define TEGRA_GPIO_PV0 168
#define TEGRA_GPIO_PV1 169
#define TEGRA_GPIO_PV2 170
#define TEGRA_GPIO_PV3 171
#define TEGRA_GPIO_PV4 172
#define TEGRA_GPIO_PV5 173
#define TEGRA_GPIO_PV6 174
#define TEGRA_GPIO_PV7 175
#define TEGRA_GPIO_PW0 176
#define TEGRA_GPIO_PW1 177
#define TEGRA_GPIO_PW2 178
#define TEGRA_GPIO_PW3 179
#define TEGRA_GPIO_PW4 180
#define TEGRA_GPIO_PW5 181
#define TEGRA_GPIO_PW6 182
#define TEGRA_GPIO_PW7 183
#define TEGRA_GPIO_PX0 184
#define TEGRA_GPIO_PX1 185
#define TEGRA_GPIO_PX2 186
#define TEGRA_GPIO_PX3 187
#define TEGRA_GPIO_PX4 188
#define TEGRA_GPIO_PX5 189
#define TEGRA_GPIO_PX6 190
#define TEGRA_GPIO_PX7 191
#define TEGRA_GPIO_PY0 192
#define TEGRA_GPIO_PY1 193
#define TEGRA_GPIO_PY2 194
#define TEGRA_GPIO_PY3 195
#define TEGRA_GPIO_PY4 196
#define TEGRA_GPIO_PY5 197
#define TEGRA_GPIO_PY6 198
#define TEGRA_GPIO_PY7 199
#define TEGRA_GPIO_PZ0 200
#define TEGRA_GPIO_PZ1 201
#define TEGRA_GPIO_PZ2 202
#define TEGRA_GPIO_PZ3 203
#define TEGRA_GPIO_PZ4 204
#define TEGRA_GPIO_PZ5 205
#define TEGRA_GPIO_PZ6 206
#define TEGRA_GPIO_PZ7 207
#define TEGRA_GPIO_PAA0 208
#define TEGRA_GPIO_PAA1 209
#define TEGRA_GPIO_PAA2 210
#define TEGRA_GPIO_PAA3 211
#define TEGRA_GPIO_PAA4 212
#define TEGRA_GPIO_PAA5 213
#define TEGRA_GPIO_PAA6 214
#define TEGRA_GPIO_PAA7 215
#define TEGRA_GPIO_PBB0 216
#define TEGRA_GPIO_PBB1 217
#define TEGRA_GPIO_PBB2 218
#define TEGRA_GPIO_PBB3 219
#define TEGRA_GPIO_PBB4 220
#define TEGRA_GPIO_PBB5 221
#define TEGRA_GPIO_PBB6 222
#define TEGRA_GPIO_PBB7 223
#endif

View File

@@ -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)

View File

@@ -0,0 +1,62 @@
/*
* 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/kernel.h>
#include <linux/smp.h>
#include <linux/clk/tegra.h>
#include <linux/tegra-soc.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)
{
/* 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();
}
void __init tegra_hotplug_init(void)
{
if (!IS_ENABLED(CONFIG_HOTPLUG_CPU))
return;
if (IS_ENABLED(CONFIG_ARCH_TEGRA_2x_SOC) && tegra_chip_id == TEGRA20)
tegra_hotplug_shutdown = tegra20_hotplug_shutdown;
if (IS_ENABLED(CONFIG_ARCH_TEGRA_3x_SOC) && tegra_chip_id == TEGRA30)
tegra_hotplug_shutdown = tegra30_hotplug_shutdown;
if (IS_ENABLED(CONFIG_ARCH_TEGRA_114_SOC) && tegra_chip_id == TEGRA114)
tegra_hotplug_shutdown = tegra30_hotplug_shutdown;
if (IS_ENABLED(CONFIG_ARCH_TEGRA_124_SOC) && tegra_chip_id == TEGRA124)
tegra_hotplug_shutdown = tegra30_hotplug_shutdown;
}

64
arch/arm/mach-tegra/io.c Normal file
View File

@@ -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/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/mm.h>
#include <linux/io.h>
#include <asm/page.h>
#include <asm/mach/map.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));
}

315
arch/arm/mach-tegra/iomap.h Normal file
View File

@@ -0,0 +1,315 @@
/*
* 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
/* First 1K of IRAM is reserved for cpu reset handler. */
#define TEGRA_RESET_HANDLER_BASE TEGRA_IRAM_BASE
#define TEGRA_RESET_HANDLER_SIZE SZ_1K
#define TEGRA_IRAM_CODE_AREA (TEGRA_IRAM_BASE + SZ_4K)
#define TEGRA_HOST1X_BASE 0x50000000
#define TEGRA_HOST1X_SIZE 0x24000
#define TEGRA_ARM_PERIF_BASE 0x50040000
#define TEGRA_ARM_PERIF_SIZE SZ_8K
#define TEGRA_ARM_PL310_BASE 0x50043000
#define TEGRA_ARM_PL310_SIZE SZ_4K
#define TEGRA_ARM_INT_DIST_BASE 0x50041000
#define TEGRA_ARM_INT_DIST_SIZE SZ_4K
#define TEGRA_MPE_BASE 0x54040000
#define TEGRA_MPE_SIZE SZ_256K
#define TEGRA_VI_BASE 0x54080000
#define TEGRA_VI_SIZE SZ_256K
#define TEGRA_ISP_BASE 0x54100000
#define TEGRA_ISP_SIZE SZ_256K
#define TEGRA_DISPLAY_BASE 0x54200000
#define TEGRA_DISPLAY_SIZE SZ_256K
#define TEGRA_DISPLAY2_BASE 0x54240000
#define TEGRA_DISPLAY2_SIZE SZ_256K
#define TEGRA_HDMI_BASE 0x54280000
#define TEGRA_HDMI_SIZE SZ_256K
#define TEGRA_GART_BASE 0x58000000
#define TEGRA_GART_SIZE SZ_32M
#define TEGRA_RES_SEMA_BASE 0x60001000
#define TEGRA_RES_SEMA_SIZE SZ_4K
#define TEGRA_PRIMARY_ICTLR_BASE 0x60004000
#define TEGRA_PRIMARY_ICTLR_SIZE SZ_64
#define TEGRA_SECONDARY_ICTLR_BASE 0x60004100
#define TEGRA_SECONDARY_ICTLR_SIZE SZ_64
#define TEGRA_TERTIARY_ICTLR_BASE 0x60004200
#define TEGRA_TERTIARY_ICTLR_SIZE SZ_64
#define TEGRA_QUATERNARY_ICTLR_BASE 0x60004300
#define TEGRA_QUATERNARY_ICTLR_SIZE SZ_64
#define TEGRA_QUINARY_ICTLR_BASE 0x60004400
#define TEGRA_QUINARY_ICTLR_SIZE SZ_64
#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_AHB_DMA_BASE 0x60008000
#define TEGRA_AHB_DMA_SIZE SZ_4K
#define TEGRA_AHB_DMA_CH0_BASE 0x60009000
#define TEGRA_AHB_DMA_CH0_SIZE 32
#define TEGRA_APB_DMA_BASE 0x6000A000
#define TEGRA_APB_DMA_SIZE SZ_4K
#define TEGRA_APB_DMA_CH0_BASE 0x6000B000
#define TEGRA_APB_DMA_CH0_SIZE 32
#define TEGRA_AHB_GIZMO_BASE 0x6000C004
#define TEGRA_AHB_GIZMO_SIZE 0x10C
#define TEGRA_SB_BASE 0x6000C200
#define TEGRA_SB_SIZE 256
#define TEGRA_STATMON_BASE 0x6000C400
#define TEGRA_STATMON_SIZE SZ_1K
#define TEGRA_GPIO_BASE 0x6000D000
#define TEGRA_GPIO_SIZE SZ_4K
#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_APB_MISC_DAS_BASE 0x70000c00
#define TEGRA_APB_MISC_DAS_SIZE SZ_128
#define TEGRA_AC97_BASE 0x70002000
#define TEGRA_AC97_SIZE SZ_512
#define TEGRA_SPDIF_BASE 0x70002400
#define TEGRA_SPDIF_SIZE SZ_512
#define TEGRA_I2S1_BASE 0x70002800
#define TEGRA_I2S1_SIZE SZ_256
#define TEGRA_I2S2_BASE 0x70002A00
#define TEGRA_I2S2_SIZE SZ_256
#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_NAND_BASE 0x70008000
#define TEGRA_NAND_SIZE SZ_256
#define TEGRA_HSMMC_BASE 0x70008500
#define TEGRA_HSMMC_SIZE SZ_256
#define TEGRA_SNOR_BASE 0x70009000
#define TEGRA_SNOR_SIZE SZ_4K
#define TEGRA_PWFM_BASE 0x7000A000
#define TEGRA_PWFM_SIZE SZ_256
#define TEGRA_PWFM0_BASE 0x7000A000
#define TEGRA_PWFM0_SIZE 4
#define TEGRA_PWFM1_BASE 0x7000A010
#define TEGRA_PWFM1_SIZE 4
#define TEGRA_PWFM2_BASE 0x7000A020
#define TEGRA_PWFM2_SIZE 4
#define TEGRA_PWFM3_BASE 0x7000A030
#define TEGRA_PWFM3_SIZE 4
#define TEGRA_MIPI_BASE 0x7000B000
#define TEGRA_MIPI_SIZE SZ_256
#define TEGRA_I2C_BASE 0x7000C000
#define TEGRA_I2C_SIZE SZ_256
#define TEGRA_TWC_BASE 0x7000C100
#define TEGRA_TWC_SIZE SZ_256
#define TEGRA_SPI_BASE 0x7000C380
#define TEGRA_SPI_SIZE 48
#define TEGRA_I2C2_BASE 0x7000C400
#define TEGRA_I2C2_SIZE SZ_256
#define TEGRA_I2C3_BASE 0x7000C500
#define TEGRA_I2C3_SIZE SZ_256
#define TEGRA_OWR_BASE 0x7000C600
#define TEGRA_OWR_SIZE 80
#define TEGRA_DVC_BASE 0x7000D000
#define TEGRA_DVC_SIZE SZ_512
#define TEGRA_SPI1_BASE 0x7000D400
#define TEGRA_SPI1_SIZE SZ_512
#define TEGRA_SPI2_BASE 0x7000D600
#define TEGRA_SPI2_SIZE SZ_512
#define TEGRA_SPI3_BASE 0x7000D800
#define TEGRA_SPI3_SIZE SZ_512
#define TEGRA_SPI4_BASE 0x7000DA00
#define TEGRA_SPI4_SIZE SZ_512
#define TEGRA_RTC_BASE 0x7000E000
#define TEGRA_RTC_SIZE SZ_256
#define TEGRA_KBC_BASE 0x7000E200
#define TEGRA_KBC_SIZE SZ_256
#define TEGRA_PMC_BASE 0x7000E400
#define TEGRA_PMC_SIZE SZ_256
#define TEGRA_MC_BASE 0x7000F000
#define TEGRA_MC_SIZE SZ_1K
#define TEGRA_EMC_BASE 0x7000F400
#define TEGRA_EMC_SIZE SZ_1K
#define TEGRA_FUSE_BASE 0x7000F800
#define TEGRA_FUSE_SIZE SZ_1K
#define TEGRA_KFUSE_BASE 0x7000FC00
#define TEGRA_KFUSE_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
#define TEGRA_SDMMC1_BASE 0xC8000000
#define TEGRA_SDMMC1_SIZE SZ_512
#define TEGRA_SDMMC2_BASE 0xC8000200
#define TEGRA_SDMMC2_SIZE SZ_512
#define TEGRA_SDMMC3_BASE 0xC8000400
#define TEGRA_SDMMC3_SIZE SZ_512
#define TEGRA_SDMMC4_BASE 0xC8000600
#define TEGRA_SDMMC4_SIZE SZ_512
/* 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 TEGRA_PCIE_BASE 0x80000000
#define TEGRA_PCIE_IO_BASE (TEGRA_PCIE_BASE + SZ_4M)
#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

View File

@@ -0,0 +1,26 @@
/*
* 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
#endif

319
arch/arm/mach-tegra/irq.c Normal file
View File

@@ -0,0 +1,319 @@
/*
* 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/kernel.h>
#include <linux/cpu_pm.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/irqchip/arm-gic.h>
#include <linux/syscore_ops.h>
#include "board.h"
#include "iomap.h"
#define ICTLR_CPU_IEP_VFIQ 0x08
#define ICTLR_CPU_IEP_FIR 0x14
#define ICTLR_CPU_IEP_FIR_SET 0x18
#define ICTLR_CPU_IEP_FIR_CLR 0x1c
#define ICTLR_CPU_IER 0x20
#define ICTLR_CPU_IER_SET 0x24
#define ICTLR_CPU_IER_CLR 0x28
#define ICTLR_CPU_IEP_CLASS 0x2C
#define ICTLR_COP_IER 0x30
#define ICTLR_COP_IER_SET 0x34
#define ICTLR_COP_IER_CLR 0x38
#define ICTLR_COP_IEP_CLASS 0x3c
#define FIRST_LEGACY_IRQ 32
#define TEGRA_MAX_NUM_ICTLRS 5
#define SGI_MASK 0xFFFF
static int num_ictlrs;
static void __iomem *ictlr_reg_base[] = {
IO_ADDRESS(TEGRA_PRIMARY_ICTLR_BASE),
IO_ADDRESS(TEGRA_SECONDARY_ICTLR_BASE),
IO_ADDRESS(TEGRA_TERTIARY_ICTLR_BASE),
IO_ADDRESS(TEGRA_QUATERNARY_ICTLR_BASE),
IO_ADDRESS(TEGRA_QUINARY_ICTLR_BASE),
};
#ifdef CONFIG_PM_SLEEP
static u32 cop_ier[TEGRA_MAX_NUM_ICTLRS];
static u32 cop_iep[TEGRA_MAX_NUM_ICTLRS];
static u32 cpu_ier[TEGRA_MAX_NUM_ICTLRS];
static u32 cpu_iep[TEGRA_MAX_NUM_ICTLRS];
static u32 ictlr_wake_mask[TEGRA_MAX_NUM_ICTLRS];
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;
}
static inline void tegra_irq_write_mask(unsigned int irq, unsigned long reg)
{
void __iomem *base;
u32 mask;
BUG_ON(irq < FIRST_LEGACY_IRQ ||
irq >= FIRST_LEGACY_IRQ + num_ictlrs * 32);
base = ictlr_reg_base[(irq - FIRST_LEGACY_IRQ) / 32];
mask = BIT((irq - FIRST_LEGACY_IRQ) % 32);
__raw_writel(mask, base + reg);
}
static void tegra_mask(struct irq_data *d)
{
if (d->irq < FIRST_LEGACY_IRQ)
return;
tegra_irq_write_mask(d->irq, ICTLR_CPU_IER_CLR);
}
static void tegra_unmask(struct irq_data *d)
{
if (d->irq < FIRST_LEGACY_IRQ)
return;
tegra_irq_write_mask(d->irq, ICTLR_CPU_IER_SET);
}
static void tegra_ack(struct irq_data *d)
{
if (d->irq < FIRST_LEGACY_IRQ)
return;
tegra_irq_write_mask(d->irq, ICTLR_CPU_IEP_FIR_CLR);
}
static void tegra_eoi(struct irq_data *d)
{
if (d->irq < FIRST_LEGACY_IRQ)
return;
tegra_irq_write_mask(d->irq, ICTLR_CPU_IEP_FIR_CLR);
}
static int tegra_retrigger(struct irq_data *d)
{
if (d->irq < FIRST_LEGACY_IRQ)
return 0;
tegra_irq_write_mask(d->irq, ICTLR_CPU_IEP_FIR_SET);
return 1;
}
#ifdef CONFIG_PM_SLEEP
static int tegra_set_wake(struct irq_data *d, unsigned int enable)
{
u32 irq = d->irq;
u32 index, mask;
if (irq < FIRST_LEGACY_IRQ ||
irq >= FIRST_LEGACY_IRQ + num_ictlrs * 32)
return -EINVAL;
index = ((irq - FIRST_LEGACY_IRQ) / 32);
mask = BIT((irq - FIRST_LEGACY_IRQ) % 32);
if (enable)
ictlr_wake_mask[index] |= mask;
else
ictlr_wake_mask[index] &= ~mask;
return 0;
}
static int tegra_legacy_irq_suspend(void)
{
unsigned long flags;
int i;
local_irq_save(flags);
for (i = 0; i < num_ictlrs; i++) {
void __iomem *ictlr = ictlr_reg_base[i];
/* Save interrupt state */
cpu_ier[i] = readl_relaxed(ictlr + ICTLR_CPU_IER);
cpu_iep[i] = readl_relaxed(ictlr + ICTLR_CPU_IEP_CLASS);
cop_ier[i] = readl_relaxed(ictlr + ICTLR_COP_IER);
cop_iep[i] = readl_relaxed(ictlr + ICTLR_COP_IEP_CLASS);
/* Disable COP interrupts */
writel_relaxed(~0ul, ictlr + ICTLR_COP_IER_CLR);
/* Disable CPU interrupts */
writel_relaxed(~0ul, ictlr + ICTLR_CPU_IER_CLR);
/* Enable the wakeup sources of ictlr */
writel_relaxed(ictlr_wake_mask[i], ictlr + ICTLR_CPU_IER_SET);
}
local_irq_restore(flags);
return 0;
}
static void tegra_legacy_irq_resume(void)
{
unsigned long flags;
int i;
local_irq_save(flags);
for (i = 0; i < num_ictlrs; i++) {
void __iomem *ictlr = ictlr_reg_base[i];
writel_relaxed(cpu_iep[i], ictlr + ICTLR_CPU_IEP_CLASS);
writel_relaxed(~0ul, ictlr + ICTLR_CPU_IER_CLR);
writel_relaxed(cpu_ier[i], ictlr + ICTLR_CPU_IER_SET);
writel_relaxed(cop_iep[i], ictlr + ICTLR_COP_IEP_CLASS);
writel_relaxed(~0ul, ictlr + ICTLR_COP_IER_CLR);
writel_relaxed(cop_ier[i], ictlr + ICTLR_COP_IER_SET);
}
local_irq_restore(flags);
}
static struct syscore_ops tegra_legacy_irq_syscore_ops = {
.suspend = tegra_legacy_irq_suspend,
.resume = tegra_legacy_irq_resume,
};
int tegra_legacy_irq_syscore_init(void)
{
register_syscore_ops(&tegra_legacy_irq_syscore_ops);
return 0;
}
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
#define tegra_set_wake NULL
static void tegra114_gic_cpu_pm_registration(void) { }
#endif
void tegra_cluster_switch_restore_gic(void)
{
unsigned int max_irq, i;
void __iomem *distbase = IO_ADDRESS(TEGRA_ARM_INT_DIST_BASE);
/*
* Reprogram the interrupt affinity because on the LP CPU,
* the interrupt distributor affinity regsiters are stubbed out
* by ARM (reads as zero, writes ignored). So when the LP CPU
* context save code runs, the affinity registers will read
* as all zero. This causes all interrupts to be effectively
* disabled when back on the G CPU because they aren't routable
* to any CPU.
*/
max_irq = readl_relaxed(distbase + GIC_DIST_CTR) & 0x1f;
max_irq = (max_irq + 1) * 32;
for (i = 32; i < max_irq; i += 4) {
u32 val = 0x01010101;
writel(val, distbase + GIC_DIST_TARGET + i * 4 / 4);
}
}
void __init tegra_init_irq(void)
{
int i;
void __iomem *distbase;
distbase = IO_ADDRESS(TEGRA_ARM_INT_DIST_BASE);
num_ictlrs = readl_relaxed(distbase + GIC_DIST_CTR) & 0x1f;
if (num_ictlrs > ARRAY_SIZE(ictlr_reg_base)) {
WARN(1, "Too many (%d) interrupt controllers found. Maximum is %d.",
num_ictlrs, ARRAY_SIZE(ictlr_reg_base));
num_ictlrs = ARRAY_SIZE(ictlr_reg_base);
}
for (i = 0; i < num_ictlrs; i++) {
void __iomem *ictlr = ictlr_reg_base[i];
writel(~0, ictlr + ICTLR_CPU_IER_CLR);
writel(0, ictlr + ICTLR_CPU_IEP_CLASS);
}
gic_arch_extn.irq_ack = tegra_ack;
gic_arch_extn.irq_eoi = tegra_eoi;
gic_arch_extn.irq_mask = tegra_mask;
gic_arch_extn.irq_unmask = tegra_unmask;
gic_arch_extn.irq_retrigger = tegra_retrigger;
gic_arch_extn.irq_set_wake = tegra_set_wake;
gic_arch_extn.flags = IRQCHIP_MASK_ON_SUSPEND;
/*
* Check if there is a devicetree present, since the GIC will be
* initialized elsewhere under DT.
*/
if (!of_have_populated_dt())
gic_init(0, 29, distbase,
IO_ADDRESS(TEGRA_ARM_PERIF_BASE + 0x100));
tegra114_gic_cpu_pm_registration();
}

29
arch/arm/mach-tegra/irq.h Normal file
View File

@@ -0,0 +1,29 @@
/*
* 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);
void tegra_cluster_switch_restore_gic(void);
#ifdef CONFIG_PM_SLEEP
int tegra_legacy_irq_syscore_init(void);
#else
static inline int tegra_legacy_irq_syscore_init(void) { return 0; }
#endif
#endif

886
arch/arm/mach-tegra/pcie.c Normal file
View File

@@ -0,0 +1,886 @@
/*
* arch/arm/mach-tegra/pci.c
*
* PCIe host controller driver for TEGRA(2) SOCs
*
* Copyright (c) 2010, CompuLab, Ltd.
* Author: Mike Rapoport <mike@compulab.co.il>
*
* Based on NVIDIA PCIe driver
* Copyright (c) 2008-2009, NVIDIA Corporation.
*
* Bits taken from arch/arm/mach-dove/pcie.c
*
* 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/kernel.h>
#include <linux/pci.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/export.h>
#include <linux/clk/tegra.h>
#include <linux/tegra-powergate.h>
#include <asm/sizes.h>
#include <asm/mach/pci.h>
#include "board.h"
#include "iomap.h"
/* Hack - need to parse this from DT */
#define INT_PCIE_INTR 130
/* register definitions */
#define AFI_OFFSET 0x3800
#define PADS_OFFSET 0x3000
#define RP0_OFFSET 0x0000
#define RP1_OFFSET 0x1000
#define AFI_AXI_BAR0_SZ 0x00
#define AFI_AXI_BAR1_SZ 0x04
#define AFI_AXI_BAR2_SZ 0x08
#define AFI_AXI_BAR3_SZ 0x0c
#define AFI_AXI_BAR4_SZ 0x10
#define AFI_AXI_BAR5_SZ 0x14
#define AFI_AXI_BAR0_START 0x18
#define AFI_AXI_BAR1_START 0x1c
#define AFI_AXI_BAR2_START 0x20
#define AFI_AXI_BAR3_START 0x24
#define AFI_AXI_BAR4_START 0x28
#define AFI_AXI_BAR5_START 0x2c
#define AFI_FPCI_BAR0 0x30
#define AFI_FPCI_BAR1 0x34
#define AFI_FPCI_BAR2 0x38
#define AFI_FPCI_BAR3 0x3c
#define AFI_FPCI_BAR4 0x40
#define AFI_FPCI_BAR5 0x44
#define AFI_CACHE_BAR0_SZ 0x48
#define AFI_CACHE_BAR0_ST 0x4c
#define AFI_CACHE_BAR1_SZ 0x50
#define AFI_CACHE_BAR1_ST 0x54
#define AFI_MSI_BAR_SZ 0x60
#define AFI_MSI_FPCI_BAR_ST 0x64
#define AFI_MSI_AXI_BAR_ST 0x68
#define AFI_CONFIGURATION 0xac
#define AFI_CONFIGURATION_EN_FPCI (1 << 0)
#define AFI_FPCI_ERROR_MASKS 0xb0
#define AFI_INTR_MASK 0xb4
#define AFI_INTR_MASK_INT_MASK (1 << 0)
#define AFI_INTR_MASK_MSI_MASK (1 << 8)
#define AFI_INTR_CODE 0xb8
#define AFI_INTR_CODE_MASK 0xf
#define AFI_INTR_MASTER_ABORT 4
#define AFI_INTR_LEGACY 6
#define AFI_INTR_SIGNATURE 0xbc
#define AFI_SM_INTR_ENABLE 0xc4
#define AFI_AFI_INTR_ENABLE 0xc8
#define AFI_INTR_EN_INI_SLVERR (1 << 0)
#define AFI_INTR_EN_INI_DECERR (1 << 1)
#define AFI_INTR_EN_TGT_SLVERR (1 << 2)
#define AFI_INTR_EN_TGT_DECERR (1 << 3)
#define AFI_INTR_EN_TGT_WRERR (1 << 4)
#define AFI_INTR_EN_DFPCI_DECERR (1 << 5)
#define AFI_INTR_EN_AXI_DECERR (1 << 6)
#define AFI_INTR_EN_FPCI_TIMEOUT (1 << 7)
#define AFI_PCIE_CONFIG 0x0f8
#define AFI_PCIE_CONFIG_PCIEC0_DISABLE_DEVICE (1 << 1)
#define AFI_PCIE_CONFIG_PCIEC1_DISABLE_DEVICE (1 << 2)
#define AFI_PCIE_CONFIG_SM2TMS0_XBAR_CONFIG_MASK (0xf << 20)
#define AFI_PCIE_CONFIG_SM2TMS0_XBAR_CONFIG_SINGLE (0x0 << 20)
#define AFI_PCIE_CONFIG_SM2TMS0_XBAR_CONFIG_DUAL (0x1 << 20)
#define AFI_FUSE 0x104
#define AFI_FUSE_PCIE_T0_GEN2_DIS (1 << 2)
#define AFI_PEX0_CTRL 0x110
#define AFI_PEX1_CTRL 0x118
#define AFI_PEX_CTRL_RST (1 << 0)
#define AFI_PEX_CTRL_REFCLK_EN (1 << 3)
#define RP_VEND_XP 0x00000F00
#define RP_VEND_XP_DL_UP (1 << 30)
#define RP_LINK_CONTROL_STATUS 0x00000090
#define RP_LINK_CONTROL_STATUS_LINKSTAT_MASK 0x3fff0000
#define PADS_CTL_SEL 0x0000009C
#define PADS_CTL 0x000000A0
#define PADS_CTL_IDDQ_1L (1 << 0)
#define PADS_CTL_TX_DATA_EN_1L (1 << 6)
#define PADS_CTL_RX_DATA_EN_1L (1 << 10)
#define PADS_PLL_CTL 0x000000B8
#define PADS_PLL_CTL_RST_B4SM (1 << 1)
#define PADS_PLL_CTL_LOCKDET (1 << 8)
#define PADS_PLL_CTL_REFCLK_MASK (0x3 << 16)
#define PADS_PLL_CTL_REFCLK_INTERNAL_CML (0 << 16)
#define PADS_PLL_CTL_REFCLK_INTERNAL_CMOS (1 << 16)
#define PADS_PLL_CTL_REFCLK_EXTERNAL (2 << 16)
#define PADS_PLL_CTL_TXCLKREF_MASK (0x1 << 20)
#define PADS_PLL_CTL_TXCLKREF_DIV10 (0 << 20)
#define PADS_PLL_CTL_TXCLKREF_DIV5 (1 << 20)
/* PMC access is required for PCIE xclk (un)clamping */
#define PMC_SCRATCH42 0x144
#define PMC_SCRATCH42_PCX_CLAMP (1 << 0)
static void __iomem *reg_pmc_base = IO_ADDRESS(TEGRA_PMC_BASE);
#define pmc_writel(value, reg) \
__raw_writel(value, reg_pmc_base + (reg))
#define pmc_readl(reg) \
__raw_readl(reg_pmc_base + (reg))
/*
* Tegra2 defines 1GB in the AXI address map for PCIe.
*
* That address space is split into different regions, with sizes and
* offsets as follows:
*
* 0x80000000 - 0x80003fff - PCI controller registers
* 0x80004000 - 0x80103fff - PCI configuration space
* 0x80104000 - 0x80203fff - PCI extended configuration space
* 0x80203fff - 0x803fffff - unused
* 0x80400000 - 0x8040ffff - downstream IO
* 0x80410000 - 0x8fffffff - unused
* 0x90000000 - 0x9fffffff - non-prefetchable memory
* 0xa0000000 - 0xbfffffff - prefetchable memory
*/
#define PCIE_REGS_SZ SZ_16K
#define PCIE_CFG_OFF PCIE_REGS_SZ
#define PCIE_CFG_SZ SZ_1M
#define PCIE_EXT_CFG_OFF (PCIE_CFG_SZ + PCIE_CFG_OFF)
#define PCIE_EXT_CFG_SZ SZ_1M
#define PCIE_IOMAP_SZ (PCIE_REGS_SZ + PCIE_CFG_SZ + PCIE_EXT_CFG_SZ)
#define MEM_BASE_0 (TEGRA_PCIE_BASE + SZ_256M)
#define MEM_SIZE_0 SZ_128M
#define MEM_BASE_1 (MEM_BASE_0 + MEM_SIZE_0)
#define MEM_SIZE_1 SZ_128M
#define PREFETCH_MEM_BASE_0 (MEM_BASE_1 + MEM_SIZE_1)
#define PREFETCH_MEM_SIZE_0 SZ_128M
#define PREFETCH_MEM_BASE_1 (PREFETCH_MEM_BASE_0 + PREFETCH_MEM_SIZE_0)
#define PREFETCH_MEM_SIZE_1 SZ_128M
#define PCIE_CONF_BUS(b) ((b) << 16)
#define PCIE_CONF_DEV(d) ((d) << 11)
#define PCIE_CONF_FUNC(f) ((f) << 8)
#define PCIE_CONF_REG(r) \
(((r) & ~0x3) | (((r) < 256) ? PCIE_CFG_OFF : PCIE_EXT_CFG_OFF))
struct tegra_pcie_port {
int index;
u8 root_bus_nr;
void __iomem *base;
bool link_up;
char mem_space_name[16];
char prefetch_space_name[20];
struct resource res[2];
};
struct tegra_pcie_info {
struct tegra_pcie_port port[2];
int num_ports;
void __iomem *regs;
struct resource res_mmio;
struct clk *pex_clk;
struct clk *afi_clk;
struct clk *pcie_xclk;
struct clk *pll_e;
};
static struct tegra_pcie_info tegra_pcie;
static inline void afi_writel(u32 value, unsigned long offset)
{
writel(value, offset + AFI_OFFSET + tegra_pcie.regs);
}
static inline u32 afi_readl(unsigned long offset)
{
return readl(offset + AFI_OFFSET + tegra_pcie.regs);
}
static inline void pads_writel(u32 value, unsigned long offset)
{
writel(value, offset + PADS_OFFSET + tegra_pcie.regs);
}
static inline u32 pads_readl(unsigned long offset)
{
return readl(offset + PADS_OFFSET + tegra_pcie.regs);
}
static struct tegra_pcie_port *bus_to_port(int bus)
{
int i;
for (i = tegra_pcie.num_ports - 1; i >= 0; i--) {
int rbus = tegra_pcie.port[i].root_bus_nr;
if (rbus != -1 && rbus == bus)
break;
}
return i >= 0 ? tegra_pcie.port + i : NULL;
}
static int tegra_pcie_read_conf(struct pci_bus *bus, unsigned int devfn,
int where, int size, u32 *val)
{
struct tegra_pcie_port *pp = bus_to_port(bus->number);
void __iomem *addr;
if (pp) {
if (devfn != 0) {
*val = 0xffffffff;
return PCIBIOS_DEVICE_NOT_FOUND;
}
addr = pp->base + (where & ~0x3);
} else {
addr = tegra_pcie.regs + (PCIE_CONF_BUS(bus->number) +
PCIE_CONF_DEV(PCI_SLOT(devfn)) +
PCIE_CONF_FUNC(PCI_FUNC(devfn)) +
PCIE_CONF_REG(where));
}
*val = readl(addr);
if (size == 1)
*val = (*val >> (8 * (where & 3))) & 0xff;
else if (size == 2)
*val = (*val >> (8 * (where & 3))) & 0xffff;
return PCIBIOS_SUCCESSFUL;
}
static int tegra_pcie_write_conf(struct pci_bus *bus, unsigned int devfn,
int where, int size, u32 val)
{
struct tegra_pcie_port *pp = bus_to_port(bus->number);
void __iomem *addr;
u32 mask;
u32 tmp;
if (pp) {
if (devfn != 0)
return PCIBIOS_DEVICE_NOT_FOUND;
addr = pp->base + (where & ~0x3);
} else {
addr = tegra_pcie.regs + (PCIE_CONF_BUS(bus->number) +
PCIE_CONF_DEV(PCI_SLOT(devfn)) +
PCIE_CONF_FUNC(PCI_FUNC(devfn)) +
PCIE_CONF_REG(where));
}
if (size == 4) {
writel(val, addr);
return PCIBIOS_SUCCESSFUL;
}
if (size == 2)
mask = ~(0xffff << ((where & 0x3) * 8));
else if (size == 1)
mask = ~(0xff << ((where & 0x3) * 8));
else
return PCIBIOS_BAD_REGISTER_NUMBER;
tmp = readl(addr) & mask;
tmp |= val << ((where & 0x3) * 8);
writel(tmp, addr);
return PCIBIOS_SUCCESSFUL;
}
static struct pci_ops tegra_pcie_ops = {
.read = tegra_pcie_read_conf,
.write = tegra_pcie_write_conf,
};
static void tegra_pcie_fixup_bridge(struct pci_dev *dev)
{
u16 reg;
if ((dev->class >> 16) == PCI_BASE_CLASS_BRIDGE) {
pci_read_config_word(dev, PCI_COMMAND, &reg);
reg |= (PCI_COMMAND_IO | PCI_COMMAND_MEMORY |
PCI_COMMAND_MASTER | PCI_COMMAND_SERR);
pci_write_config_word(dev, PCI_COMMAND, reg);
}
}
DECLARE_PCI_FIXUP_FINAL(PCI_ANY_ID, PCI_ANY_ID, tegra_pcie_fixup_bridge);
/* Tegra PCIE root complex wrongly reports device class */
static void tegra_pcie_fixup_class(struct pci_dev *dev)
{
dev->class = PCI_CLASS_BRIDGE_PCI << 8;
}
DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_NVIDIA, 0x0bf0, tegra_pcie_fixup_class);
DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_NVIDIA, 0x0bf1, tegra_pcie_fixup_class);
/* Tegra PCIE requires relaxed ordering */
static void tegra_pcie_relax_enable(struct pci_dev *dev)
{
pcie_capability_set_word(dev, PCI_EXP_DEVCTL, PCI_EXP_DEVCTL_RELAX_EN);
}
DECLARE_PCI_FIXUP_FINAL(PCI_ANY_ID, PCI_ANY_ID, tegra_pcie_relax_enable);
static int tegra_pcie_setup(int nr, struct pci_sys_data *sys)
{
struct tegra_pcie_port *pp;
if (nr >= tegra_pcie.num_ports)
return 0;
pp = tegra_pcie.port + nr;
pp->root_bus_nr = sys->busnr;
pci_ioremap_io(nr * SZ_64K, TEGRA_PCIE_IO_BASE);
/*
* IORESOURCE_MEM
*/
snprintf(pp->mem_space_name, sizeof(pp->mem_space_name),
"PCIe %d MEM", pp->index);
pp->mem_space_name[sizeof(pp->mem_space_name) - 1] = 0;
pp->res[0].name = pp->mem_space_name;
if (pp->index == 0) {
pp->res[0].start = MEM_BASE_0;
pp->res[0].end = pp->res[0].start + MEM_SIZE_0 - 1;
} else {
pp->res[0].start = MEM_BASE_1;
pp->res[0].end = pp->res[0].start + MEM_SIZE_1 - 1;
}
pp->res[0].flags = IORESOURCE_MEM;
if (request_resource(&iomem_resource, &pp->res[0]))
panic("Request PCIe Memory resource failed\n");
pci_add_resource_offset(&sys->resources, &pp->res[0], sys->mem_offset);
/*
* IORESOURCE_MEM | IORESOURCE_PREFETCH
*/
snprintf(pp->prefetch_space_name, sizeof(pp->prefetch_space_name),
"PCIe %d PREFETCH MEM", pp->index);
pp->prefetch_space_name[sizeof(pp->prefetch_space_name) - 1] = 0;
pp->res[1].name = pp->prefetch_space_name;
if (pp->index == 0) {
pp->res[1].start = PREFETCH_MEM_BASE_0;
pp->res[1].end = pp->res[1].start + PREFETCH_MEM_SIZE_0 - 1;
} else {
pp->res[1].start = PREFETCH_MEM_BASE_1;
pp->res[1].end = pp->res[1].start + PREFETCH_MEM_SIZE_1 - 1;
}
pp->res[1].flags = IORESOURCE_MEM | IORESOURCE_PREFETCH;
if (request_resource(&iomem_resource, &pp->res[1]))
panic("Request PCIe Prefetch Memory resource failed\n");
pci_add_resource_offset(&sys->resources, &pp->res[1], sys->mem_offset);
return 1;
}
static int tegra_pcie_map_irq(const struct pci_dev *dev, u8 slot, u8 pin)
{
return INT_PCIE_INTR;
}
static struct pci_bus __init *tegra_pcie_scan_bus(int nr,
struct pci_sys_data *sys)
{
struct tegra_pcie_port *pp;
if (nr >= tegra_pcie.num_ports)
return NULL;
pp = tegra_pcie.port + nr;
pp->root_bus_nr = sys->busnr;
return pci_scan_root_bus(NULL, sys->busnr, &tegra_pcie_ops, sys,
&sys->resources);
}
static struct hw_pci tegra_pcie_hw __initdata = {
.nr_controllers = 2,
.setup = tegra_pcie_setup,
.scan = tegra_pcie_scan_bus,
.map_irq = tegra_pcie_map_irq,
};
static irqreturn_t tegra_pcie_isr(int irq, void *arg)
{
const char *err_msg[] = {
"Unknown",
"AXI slave error",
"AXI decode error",
"Target abort",
"Master abort",
"Invalid write",
"Response decoding error",
"AXI response decoding error",
"Transcation timeout",
};
u32 code, signature;
code = afi_readl(AFI_INTR_CODE) & AFI_INTR_CODE_MASK;
signature = afi_readl(AFI_INTR_SIGNATURE);
afi_writel(0, AFI_INTR_CODE);
if (code == AFI_INTR_LEGACY)
return IRQ_NONE;
if (code >= ARRAY_SIZE(err_msg))
code = 0;
/*
* do not pollute kernel log with master abort reports since they
* happen a lot during enumeration
*/
if (code == AFI_INTR_MASTER_ABORT)
pr_debug("PCIE: %s, signature: %08x\n", err_msg[code], signature);
else
pr_err("PCIE: %s, signature: %08x\n", err_msg[code], signature);
return IRQ_HANDLED;
}
static void tegra_pcie_setup_translations(void)
{
u32 fpci_bar;
u32 size;
u32 axi_address;
/* Bar 0: config Bar */
fpci_bar = ((u32)0xfdff << 16);
size = PCIE_CFG_SZ;
axi_address = TEGRA_PCIE_BASE + PCIE_CFG_OFF;
afi_writel(axi_address, AFI_AXI_BAR0_START);
afi_writel(size >> 12, AFI_AXI_BAR0_SZ);
afi_writel(fpci_bar, AFI_FPCI_BAR0);
/* Bar 1: extended config Bar */
fpci_bar = ((u32)0xfe1 << 20);
size = PCIE_EXT_CFG_SZ;
axi_address = TEGRA_PCIE_BASE + PCIE_EXT_CFG_OFF;
afi_writel(axi_address, AFI_AXI_BAR1_START);
afi_writel(size >> 12, AFI_AXI_BAR1_SZ);
afi_writel(fpci_bar, AFI_FPCI_BAR1);
/* Bar 2: downstream IO bar */
fpci_bar = ((__u32)0xfdfc << 16);
size = SZ_128K;
axi_address = TEGRA_PCIE_IO_BASE;
afi_writel(axi_address, AFI_AXI_BAR2_START);
afi_writel(size >> 12, AFI_AXI_BAR2_SZ);
afi_writel(fpci_bar, AFI_FPCI_BAR2);
/* Bar 3: prefetchable memory BAR */
fpci_bar = (((PREFETCH_MEM_BASE_0 >> 12) & 0x0fffffff) << 4) | 0x1;
size = PREFETCH_MEM_SIZE_0 + PREFETCH_MEM_SIZE_1;
axi_address = PREFETCH_MEM_BASE_0;
afi_writel(axi_address, AFI_AXI_BAR3_START);
afi_writel(size >> 12, AFI_AXI_BAR3_SZ);
afi_writel(fpci_bar, AFI_FPCI_BAR3);
/* Bar 4: non prefetchable memory BAR */
fpci_bar = (((MEM_BASE_0 >> 12) & 0x0FFFFFFF) << 4) | 0x1;
size = MEM_SIZE_0 + MEM_SIZE_1;
axi_address = MEM_BASE_0;
afi_writel(axi_address, AFI_AXI_BAR4_START);
afi_writel(size >> 12, AFI_AXI_BAR4_SZ);
afi_writel(fpci_bar, AFI_FPCI_BAR4);
/* Bar 5: NULL out the remaining BAR as it is not used */
fpci_bar = 0;
size = 0;
axi_address = 0;
afi_writel(axi_address, AFI_AXI_BAR5_START);
afi_writel(size >> 12, AFI_AXI_BAR5_SZ);
afi_writel(fpci_bar, AFI_FPCI_BAR5);
/* map all upstream transactions as uncached */
afi_writel(PHYS_OFFSET, AFI_CACHE_BAR0_ST);
afi_writel(0, AFI_CACHE_BAR0_SZ);
afi_writel(0, AFI_CACHE_BAR1_ST);
afi_writel(0, AFI_CACHE_BAR1_SZ);
/* No MSI */
afi_writel(0, AFI_MSI_FPCI_BAR_ST);
afi_writel(0, AFI_MSI_BAR_SZ);
afi_writel(0, AFI_MSI_AXI_BAR_ST);
afi_writel(0, AFI_MSI_BAR_SZ);
}
static int tegra_pcie_enable_controller(void)
{
u32 val, reg;
int i, timeout;
/* Enable slot clock and pulse the reset signals */
for (i = 0, reg = AFI_PEX0_CTRL; i < 2; i++, reg += 0x8) {
val = afi_readl(reg) | AFI_PEX_CTRL_REFCLK_EN;
afi_writel(val, reg);
val &= ~AFI_PEX_CTRL_RST;
afi_writel(val, reg);
val = afi_readl(reg) | AFI_PEX_CTRL_RST;
afi_writel(val, reg);
}
/* Enable dual controller and both ports */
val = afi_readl(AFI_PCIE_CONFIG);
val &= ~(AFI_PCIE_CONFIG_PCIEC0_DISABLE_DEVICE |
AFI_PCIE_CONFIG_PCIEC1_DISABLE_DEVICE |
AFI_PCIE_CONFIG_SM2TMS0_XBAR_CONFIG_MASK);
val |= AFI_PCIE_CONFIG_SM2TMS0_XBAR_CONFIG_DUAL;
afi_writel(val, AFI_PCIE_CONFIG);
val = afi_readl(AFI_FUSE) & ~AFI_FUSE_PCIE_T0_GEN2_DIS;
afi_writel(val, AFI_FUSE);
/* Initialze internal PHY, enable up to 16 PCIE lanes */
pads_writel(0x0, PADS_CTL_SEL);
/* override IDDQ to 1 on all 4 lanes */
val = pads_readl(PADS_CTL) | PADS_CTL_IDDQ_1L;
pads_writel(val, PADS_CTL);
/*
* set up PHY PLL inputs select PLLE output as refclock,
* set TX ref sel to div10 (not div5)
*/
val = pads_readl(PADS_PLL_CTL);
val &= ~(PADS_PLL_CTL_REFCLK_MASK | PADS_PLL_CTL_TXCLKREF_MASK);
val |= (PADS_PLL_CTL_REFCLK_INTERNAL_CML | PADS_PLL_CTL_TXCLKREF_DIV10);
pads_writel(val, PADS_PLL_CTL);
/* take PLL out of reset */
val = pads_readl(PADS_PLL_CTL) | PADS_PLL_CTL_RST_B4SM;
pads_writel(val, PADS_PLL_CTL);
/*
* Hack, set the clock voltage to the DEFAULT provided by hw folks.
* This doesn't exist in the documentation
*/
pads_writel(0xfa5cfa5c, 0xc8);
/* Wait for the PLL to lock */
timeout = 300;
do {
val = pads_readl(PADS_PLL_CTL);
usleep_range(1000, 1000);
if (--timeout == 0) {
pr_err("Tegra PCIe error: timeout waiting for PLL\n");
return -EBUSY;
}
} while (!(val & PADS_PLL_CTL_LOCKDET));
/* turn off IDDQ override */
val = pads_readl(PADS_CTL) & ~PADS_CTL_IDDQ_1L;
pads_writel(val, PADS_CTL);
/* enable TX/RX data */
val = pads_readl(PADS_CTL);
val |= (PADS_CTL_TX_DATA_EN_1L | PADS_CTL_RX_DATA_EN_1L);
pads_writel(val, PADS_CTL);
/* Take the PCIe interface module out of reset */
tegra_periph_reset_deassert(tegra_pcie.pcie_xclk);
/* Finally enable PCIe */
val = afi_readl(AFI_CONFIGURATION) | AFI_CONFIGURATION_EN_FPCI;
afi_writel(val, AFI_CONFIGURATION);
val = (AFI_INTR_EN_INI_SLVERR | AFI_INTR_EN_INI_DECERR |
AFI_INTR_EN_TGT_SLVERR | AFI_INTR_EN_TGT_DECERR |
AFI_INTR_EN_TGT_WRERR | AFI_INTR_EN_DFPCI_DECERR);
afi_writel(val, AFI_AFI_INTR_ENABLE);
afi_writel(0xffffffff, AFI_SM_INTR_ENABLE);
/* FIXME: No MSI for now, only INT */
afi_writel(AFI_INTR_MASK_INT_MASK, AFI_INTR_MASK);
/* Disable all execptions */
afi_writel(0, AFI_FPCI_ERROR_MASKS);
return 0;
}
static void tegra_pcie_xclk_clamp(bool clamp)
{
u32 reg;
reg = pmc_readl(PMC_SCRATCH42) & ~PMC_SCRATCH42_PCX_CLAMP;
if (clamp)
reg |= PMC_SCRATCH42_PCX_CLAMP;
pmc_writel(reg, PMC_SCRATCH42);
}
static void tegra_pcie_power_off(void)
{
tegra_periph_reset_assert(tegra_pcie.pcie_xclk);
tegra_periph_reset_assert(tegra_pcie.afi_clk);
tegra_periph_reset_assert(tegra_pcie.pex_clk);
tegra_powergate_power_off(TEGRA_POWERGATE_PCIE);
tegra_pcie_xclk_clamp(true);
}
static int tegra_pcie_power_regate(void)
{
int err;
tegra_pcie_power_off();
tegra_pcie_xclk_clamp(true);
tegra_periph_reset_assert(tegra_pcie.pcie_xclk);
tegra_periph_reset_assert(tegra_pcie.afi_clk);
err = tegra_powergate_sequence_power_up(TEGRA_POWERGATE_PCIE,
tegra_pcie.pex_clk);
if (err) {
pr_err("PCIE: powerup sequence failed: %d\n", err);
return err;
}
tegra_periph_reset_deassert(tegra_pcie.afi_clk);
tegra_pcie_xclk_clamp(false);
clk_prepare_enable(tegra_pcie.afi_clk);
clk_prepare_enable(tegra_pcie.pex_clk);
return clk_prepare_enable(tegra_pcie.pll_e);
}
static int tegra_pcie_clocks_get(void)
{
int err;
tegra_pcie.pex_clk = clk_get(NULL, "pex");
if (IS_ERR(tegra_pcie.pex_clk))
return PTR_ERR(tegra_pcie.pex_clk);
tegra_pcie.afi_clk = clk_get(NULL, "afi");
if (IS_ERR(tegra_pcie.afi_clk)) {
err = PTR_ERR(tegra_pcie.afi_clk);
goto err_afi_clk;
}
tegra_pcie.pcie_xclk = clk_get(NULL, "pcie_xclk");
if (IS_ERR(tegra_pcie.pcie_xclk)) {
err = PTR_ERR(tegra_pcie.pcie_xclk);
goto err_pcie_xclk;
}
tegra_pcie.pll_e = clk_get_sys(NULL, "pll_e");
if (IS_ERR(tegra_pcie.pll_e)) {
err = PTR_ERR(tegra_pcie.pll_e);
goto err_pll_e;
}
return 0;
err_pll_e:
clk_put(tegra_pcie.pcie_xclk);
err_pcie_xclk:
clk_put(tegra_pcie.afi_clk);
err_afi_clk:
clk_put(tegra_pcie.pex_clk);
return err;
}
static void tegra_pcie_clocks_put(void)
{
clk_put(tegra_pcie.pll_e);
clk_put(tegra_pcie.pcie_xclk);
clk_put(tegra_pcie.afi_clk);
clk_put(tegra_pcie.pex_clk);
}
static int __init tegra_pcie_get_resources(void)
{
int err;
err = tegra_pcie_clocks_get();
if (err) {
pr_err("PCIE: failed to get clocks: %d\n", err);
return err;
}
err = tegra_pcie_power_regate();
if (err) {
pr_err("PCIE: failed to power up: %d\n", err);
goto err_pwr_on;
}
tegra_pcie.regs = ioremap_nocache(TEGRA_PCIE_BASE, PCIE_IOMAP_SZ);
if (tegra_pcie.regs == NULL) {
pr_err("PCIE: Failed to map PCI/AFI registers\n");
err = -ENOMEM;
goto err_map_reg;
}
err = request_irq(INT_PCIE_INTR, tegra_pcie_isr,
IRQF_SHARED, "PCIE", &tegra_pcie);
if (err) {
pr_err("PCIE: Failed to register IRQ: %d\n", err);
goto err_req_io;
}
set_irq_flags(INT_PCIE_INTR, IRQF_VALID);
return 0;
err_req_io:
iounmap(tegra_pcie.regs);
err_map_reg:
tegra_pcie_power_off();
err_pwr_on:
tegra_pcie_clocks_put();
return err;
}
/*
* FIXME: If there are no PCIe cards attached, then calling this function
* can result in the increase of the bootup time as there are big timeout
* loops.
*/
#define TEGRA_PCIE_LINKUP_TIMEOUT 200 /* up to 1.2 seconds */
static bool tegra_pcie_check_link(struct tegra_pcie_port *pp, int idx,
u32 reset_reg)
{
u32 reg;
int retries = 3;
int timeout;
do {
timeout = TEGRA_PCIE_LINKUP_TIMEOUT;
while (timeout) {
reg = readl(pp->base + RP_VEND_XP);
if (reg & RP_VEND_XP_DL_UP)
break;
mdelay(1);
timeout--;
}
if (!timeout) {
pr_err("PCIE: port %d: link down, retrying\n", idx);
goto retry;
}
timeout = TEGRA_PCIE_LINKUP_TIMEOUT;
while (timeout) {
reg = readl(pp->base + RP_LINK_CONTROL_STATUS);
if (reg & 0x20000000)
return true;
mdelay(1);
timeout--;
}
retry:
/* Pulse the PEX reset */
reg = afi_readl(reset_reg) | AFI_PEX_CTRL_RST;
afi_writel(reg, reset_reg);
mdelay(1);
reg = afi_readl(reset_reg) & ~AFI_PEX_CTRL_RST;
afi_writel(reg, reset_reg);
retries--;
} while (retries);
return false;
}
static void __init tegra_pcie_add_port(int index, u32 offset, u32 reset_reg)
{
struct tegra_pcie_port *pp;
pp = tegra_pcie.port + tegra_pcie.num_ports;
pp->index = -1;
pp->base = tegra_pcie.regs + offset;
pp->link_up = tegra_pcie_check_link(pp, index, reset_reg);
if (!pp->link_up) {
pp->base = NULL;
printk(KERN_INFO "PCIE: port %d: link down, ignoring\n", index);
return;
}
tegra_pcie.num_ports++;
pp->index = index;
pp->root_bus_nr = -1;
memset(pp->res, 0, sizeof(pp->res));
}
int __init tegra_pcie_init(bool init_port0, bool init_port1)
{
int err;
if (!(init_port0 || init_port1))
return -ENODEV;
pcibios_min_mem = 0;
err = tegra_pcie_get_resources();
if (err)
return err;
err = tegra_pcie_enable_controller();
if (err)
return err;
/* setup the AFI address translations */
tegra_pcie_setup_translations();
if (init_port0)
tegra_pcie_add_port(0, RP0_OFFSET, AFI_PEX0_CTRL);
if (init_port1)
tegra_pcie_add_port(1, RP1_OFFSET, AFI_PEX1_CTRL);
pci_common_init(&tegra_pcie_hw);
return 0;
}

View File

@@ -0,0 +1,210 @@
/*
* 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/init.h>
#include <linux/errno.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/jiffies.h>
#include <linux/smp.h>
#include <linux/io.h>
#include <linux/clk/tegra.h>
#include <linux/tegra-soc.h>
#include <asm/cacheflush.h>
#include <asm/mach-types.h>
#include <asm/smp_scu.h>
#include <asm/smp_plat.h>
#include "flowctrl.h"
#include "reset.h"
#include "pmc.h"
#include "common.h"
#include "iomap.h"
static cpumask_t tegra_cpu_init_mask;
static void __cpuinit 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 __cpuinit tegra_boot_secondary(unsigned int cpu,
struct task_struct *idle)
{
if (IS_ENABLED(CONFIG_ARCH_TEGRA_2x_SOC) && tegra_chip_id == TEGRA20)
return tegra20_boot_secondary(cpu, idle);
if (IS_ENABLED(CONFIG_ARCH_TEGRA_3x_SOC) && tegra_chip_id == TEGRA30)
return tegra30_boot_secondary(cpu, idle);
if (IS_ENABLED(CONFIG_ARCH_TEGRA_114_SOC) && tegra_chip_id == TEGRA114)
return tegra114_boot_secondary(cpu, idle);
if (IS_ENABLED(CONFIG_ARCH_TEGRA_124_SOC) && tegra_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()));
}
#ifdef CONFIG_PM_SLEEP
void tegra_smp_clear_cpu_init_mask(void)
{
cpumask_clear(&tegra_cpu_init_mask);
cpumask_set_cpu(0, &tegra_cpu_init_mask);
}
#endif
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
};

View File

@@ -0,0 +1,34 @@
/*
* 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

View File

@@ -0,0 +1,34 @@
/*
* 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

518
arch/arm/mach-tegra/pm.c Normal file
View File

@@ -0,0 +1,518 @@
/*
* CPU complex suspend & resume functions for Tegra SoCs
*
* Copyright (c) 2009-2012, NVIDIA Corporation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <linux/kernel.h>
#include <linux/spinlock.h>
#include <linux/io.h>
#include <linux/cpumask.h>
#include <linux/delay.h>
#include <linux/cpu_pm.h>
#include <linux/suspend.h>
#include <linux/err.h>
#include <linux/clk/tegra.h>
#include <linux/serial_reg.h>
#include <linux/syscore_ops.h>
#include <linux/tegra-soc.h>
#include <linux/regulator/machine.h>
#include <asm/smp_plat.h>
#include <asm/cacheflush.h>
#include <asm/suspend.h>
#include <asm/idmap.h>
#include <asm/proc-fns.h>
#include <asm/tlbflush.h>
#include "common.h"
#include "iomap.h"
#include "reset.h"
#include "flowctrl.h"
#include "pm.h"
#include "pmc.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);
#ifdef CONFIG_DEBUG_LL
static u8 sctx_uart[5];
static int tegra_debug_uart_suspend(void)
{
void __iomem *uart;
u32 lcr;
/* Debug UART virtual address */
if (!tegra_uart_config[2])
return 0;
else
uart = (void __iomem *)tegra_uart_config[2];
lcr = readb(uart + UART_LCR * 4);
sctx_uart[0] = lcr;
sctx_uart[1] = readb(uart + UART_MCR * 4);
/* DLAB = 0 */
writeb(lcr & ~UART_LCR_DLAB, uart + UART_LCR * 4);
sctx_uart[2] = readb(uart + UART_IER * 4);
/* DLAB = 1 */
writeb(lcr | UART_LCR_DLAB, uart + UART_LCR * 4);
sctx_uart[3] = readb(uart + UART_DLL * 4);
sctx_uart[4] = readb(uart + UART_DLM * 4);
writeb(lcr, uart + UART_LCR * 4);
return 0;
}
static void tegra_debug_uart_resume(void)
{
void __iomem *uart;
u32 lcr;
/* Debug UART virtual address */
if (!tegra_uart_config[2])
return;
else
uart = (void __iomem *)tegra_uart_config[2];
lcr = sctx_uart[0];
writeb(sctx_uart[1], uart + UART_MCR * 4);
/* DLAB = 0 */
writeb(lcr & ~UART_LCR_DLAB, uart + UART_LCR * 4);
writeb(UART_FCR_ENABLE_FIFO | UART_FCR_T_TRIG_01 | UART_FCR_R_TRIG_01,
uart + UART_FCR * 4);
writeb(sctx_uart[2], uart + UART_IER * 4);
/* DLAB = 1 */
writeb(lcr | UART_LCR_DLAB, uart + UART_LCR * 4);
writeb(sctx_uart[3], uart + UART_DLL * 4);
writeb(sctx_uart[4], uart + UART_DLM * 4);
writeb(lcr, uart + UART_LCR * 4);
}
static struct syscore_ops tegra_debug_uart_syscore_ops = {
.suspend = tegra_debug_uart_suspend,
.resume = tegra_debug_uart_resume,
};
static void tegra_debug_uart_syscore_init(void)
{
register_syscore_ops(&tegra_debug_uart_syscore_ops);
}
#endif
static void tegra_tear_down_cpu_init(void)
{
switch (tegra_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.
*/
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.
*/
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_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;
}
void tegra_idle_last(void)
{
cpu_cluster_pm_enter();
suspend_cpu_complex();
cpu_suspend(PHYS_OFFSET - PAGE_OFFSET, &tegra_sleep_cpu);
restore_cpu_complex();
cpu_cluster_pm_exit();
}
void tegra_idle_lp2_last(void)
{
tegra_pmc_pm_set(TEGRA_SUSPEND_LP2);
tegra_idle_last();
}
enum tegra_suspend_mode tegra_pm_validate_suspend_mode(
enum tegra_suspend_mode mode)
{
enum tegra_suspend_mode avail_mode = TEGRA_SUSPEND_NONE;
/*
* The Tegra devices support suspending to LP1 or lower currently.
* Only Tegra114 & Tegra124 supports LP0.
*/
switch (tegra_chip_id) {
case TEGRA114:
case TEGRA124:
avail_mode = mode;
break;
default:
if (mode > TEGRA_SUSPEND_LP1)
avail_mode = TEGRA_SUSPEND_LP1;
break;
}
return avail_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_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_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_lp0(void)
{
tegra_smp_clear_cpu_init_mask();
tegra_cpu_reset_handler_save();
tegra_tsc_suspend();
}
static void tegra_suspend_exit_lp0(void)
{
tegra_tsc_resume();
tegra_cpu_reset_handler_restore();
}
static void tegra_suspend_enter_lp1(void)
{
tegra_pmc_suspend();
/* copy the reset vector & SDRAM shutdown code into IRAM */
memcpy(iram_save_addr, IO_ADDRESS(TEGRA_IRAM_CODE_AREA),
iram_save_size);
memcpy(IO_ADDRESS(TEGRA_IRAM_CODE_AREA), tegra_lp1_iram.start_addr,
iram_save_size);
*((u32 *)tegra_cpu_lp1_mask) = 1;
}
static void tegra_suspend_exit_lp1(void)
{
tegra_pmc_resume();
/* restore IRAM */
memcpy(IO_ADDRESS(TEGRA_IRAM_CODE_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 __cpuinit 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_pmc_pm_set(mode);
local_fiq_disable();
suspend_cpu_complex();
switch (mode) {
case TEGRA_SUSPEND_LP0:
tegra_suspend_enter_lp0();
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_LP0:
tegra_suspend_exit_lp0();
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;
}
/*
* Allow pmc to check if the state is valid first, then return "valid_only_mem".
* The pmc check has to be done in the valid callback so that user space isn't
* frozen yet and Tegra124 can load firmware if needed.
*/
static int tegra_suspend_valid(suspend_state_t state)
{
int ret = tegra_pmc_suspend_valid();
if (ret)
return ret;
ret = regulator_suspend_prepare(state);
if (ret)
return ret;
return suspend_valid_only_mem(state);
}
static const struct platform_suspend_ops tegra_suspend_ops = {
.valid = tegra_suspend_valid,
.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;
#ifdef CONFIG_DEBUG_LL
tegra_debug_uart_syscore_init();
#endif
tegra_tear_down_cpu_init();
tegra_pmc_suspend_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_LP0:
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

67
arch/arm/mach-tegra/pm.h Normal file
View File

@@ -0,0 +1,67 @@
/*
* 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_
#include "pmc.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);
extern unsigned long l2x0_saved_regs_addr;
void suspend_cpu_complex(void);
void restore_cpu_complex(void);
void save_cpu_arch_register(void);
void restore_cpu_arch_register(void);
void tegra_clear_cpu_in_lp2(void);
bool tegra_set_cpu_in_lp2(void);
void tegra_idle_lp2_last(void);
void tegra_idle_last(void);
extern void (*tegra_tear_down_cpu)(void);
#ifdef CONFIG_PM_SLEEP
void tegra_smp_clear_cpu_init_mask(void);
enum tegra_suspend_mode tegra_pm_validate_suspend_mode(
enum tegra_suspend_mode mode);
void tegra_init_suspend(void);
#else
static inline enum tegra_suspend_mode tegra_pm_validate_suspend_mode(
enum tegra_suspend_mode mode)
{
return TEGRA_SUSPEND_NONE;
}
static inline void tegra_init_suspend(void) {}
#endif
#endif /* _MACH_TEGRA_PM_H_ */

1015
arch/arm/mach-tegra/pmc.c Normal file

File diff suppressed because it is too large Load Diff

55
arch/arm/mach-tegra/pmc.h Normal file
View File

@@ -0,0 +1,55 @@
/*
* 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_PMC_H
#define __MACH_TEGRA_PMC_H
#include <linux/reboot.h>
enum tegra_suspend_mode {
TEGRA_SUSPEND_NONE = 0,
TEGRA_SUSPEND_LP2, /* CPU voltage off */
TEGRA_SUSPEND_LP1, /* CPU voltage off, DRAM self-refresh */
TEGRA_SUSPEND_LP0, /* CPU + core voltage off, DRAM self-refresh */
TEGRA_CLUSTER_SWITCH,
TEGRA_MAX_SUSPEND_MODE,
};
#ifdef CONFIG_PM_SLEEP
void tegra_tsc_suspend(void);
void tegra_tsc_resume(void);
void tegra_pmc_lp0_wakeup_init(void);
enum tegra_suspend_mode tegra_pmc_get_suspend_mode(void);
void tegra_pmc_set_suspend_mode(enum tegra_suspend_mode mode);
void tegra_pmc_suspend(void);
void tegra_pmc_resume(void);
void tegra_pmc_pm_set(enum tegra_suspend_mode mode);
void tegra_pmc_suspend_init(void);
int tegra_pmc_suspend_valid(void);
#endif
bool tegra_pmc_cpu_is_powered(int cpuid);
int tegra_pmc_cpu_power_on(int cpuid);
int tegra_pmc_cpu_remove_clamping(int cpuid);
void tegra_pmc_restart(char mode, const char *cmd);
void tegra_pmc_init_irq(void);
void tegra_pmc_init(void);
void tegra_pmc_init_late(void);
#endif

View File

@@ -0,0 +1,69 @@
/*
* Copyright (c) 2013, NVIDIA CORPORATION. All rights reserved.
*
* 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/spinlock.h>
#include <linux/tegra-powergate.h>
static struct powergate_partition_info tegra114_powergate_partition_info[] = {
[TEGRA_POWERGATE_CPU] = { .name = "cpu0" },
[TEGRA_POWERGATE_3D] = { .name = "3d" },
[TEGRA_POWERGATE_VENC] = { .name = "venc" },
[TEGRA_POWERGATE_VDEC] = { .name = "vdec" },
[TEGRA_POWERGATE_MPE] = { .name = "mpe" },
[TEGRA_POWERGATE_HEG] = { .name = "heg" },
[TEGRA_POWERGATE_CPU1] = { .name = "cpu1" },
[TEGRA_POWERGATE_CPU2] = { .name = "cpu2" },
[TEGRA_POWERGATE_CPU3] = { .name = "cpu3" },
[TEGRA_POWERGATE_CELP] = { .name = "celp" },
[TEGRA_POWERGATE_CPU0] = { .name = "cpu0" },
[TEGRA_POWERGATE_C0NC] = { .name = "c0nc" },
[TEGRA_POWERGATE_C1NC] = { .name = "c1nc" },
[TEGRA_POWERGATE_DIS] = { .name = "dis" },
[TEGRA_POWERGATE_DISB] = { .name = "disb" },
[TEGRA_POWERGATE_XUSBA] = { .name = "xusba" },
[TEGRA_POWERGATE_XUSBB] = { .name = "xusbb" },
[TEGRA_POWERGATE_XUSBC] = { .name = "xusbc" },
};
static const u8 tegra114_cpu_domains[] = {
TEGRA_POWERGATE_CPU0,
TEGRA_POWERGATE_CPU1,
TEGRA_POWERGATE_CPU2,
TEGRA_POWERGATE_CPU3,
};
static const char *tegra114_get_powerdomain_name(int id)
{
return tegra114_powergate_partition_info[id].name;
}
static struct powergate_ops tegra114_powergate_ops = {
.get_powergate_domain_name = tegra114_get_powerdomain_name,
.powergate_partition = NULL,
.unpowergate_partition = NULL,
};
static struct powergate t114_powergate = {
.soc_name = "tegra114",
.num_powerdomains = 23,
.num_cpu_domains = 4,
.cpu_domain_map = tegra114_cpu_domains,
.ops = &tegra114_powergate_ops,
};
struct powergate * __init tegra114_powergate_init(void)
{
return &t114_powergate;
}

View File

@@ -0,0 +1,683 @@
/*
* Copyright (c) 2013, NVIDIA CORPORATION. All rights reserved.
*
* 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/delay.h>
#include <linux/io.h>
#include <linux/platform_data/tegra_mc.h>
#include <linux/tegra-powergate.h>
#include <linux/notifier.h>
#include "iomap.h"
#define TEGRA124_POWERGATE_NUM (TEGRA_POWERGATE_VIC + 1)
#define MAX_HOTRESET_CLIENT_NUM 4
enum mc_client {
MC_CLIENT_AFI = 0,
MC_CLIENT_DC = 2,
MC_CLIENT_DCB = 3,
MC_CLIENT_ISP = 8,
MC_CLIENT_MSENC = 11,
MC_CLIENT_SATA = 15,
MC_CLIENT_VDE = 16,
MC_CLIENT_VI = 17,
MC_CLIENT_VIC = 18,
MC_CLIENT_XUSB_HOST = 19,
MC_CLIENT_XUSB_DEV = 20,
MC_CLIENT_ISPB = 33,
MC_CLIENT_GPU = 34,
MC_CLIENT_LAST = -1,
};
struct mc_client_info {
enum mc_client hot_reset_clients[MAX_HOTRESET_CLIENT_NUM];
};
static int tegra124_powergate_partition(int id);
static int tegra124_unpowergate_partition(int id);
static struct mc_client_info tegra124_pg_mc_info[TEGRA124_POWERGATE_NUM] = {
[TEGRA_POWERGATE_VDEC] = {
.hot_reset_clients = {
[0] = MC_CLIENT_VDE,
[1] = MC_CLIENT_LAST,
},
},
[TEGRA_POWERGATE_MPE] = {
.hot_reset_clients = {
[0] = MC_CLIENT_MSENC,
[1] = MC_CLIENT_LAST,
},
},
[TEGRA_POWERGATE_VENC] = {
.hot_reset_clients = {
[0] = MC_CLIENT_ISP,
[1] = MC_CLIENT_ISPB,
[2] = MC_CLIENT_VI,
[3] = MC_CLIENT_LAST,
},
},
[TEGRA_POWERGATE_DIS] = {
.hot_reset_clients = {
[0] = MC_CLIENT_DC,
[1] = MC_CLIENT_LAST,
},
},
[TEGRA_POWERGATE_DISB] = {
.hot_reset_clients = {
[0] = MC_CLIENT_DCB,
[1] = MC_CLIENT_LAST,
},
},
[TEGRA_POWERGATE_XUSBA] = {
.hot_reset_clients = {
[0] = MC_CLIENT_LAST,
},
},
[TEGRA_POWERGATE_XUSBB] = {
.hot_reset_clients = {
[0] = MC_CLIENT_XUSB_DEV,
[1] = MC_CLIENT_LAST
},
},
[TEGRA_POWERGATE_XUSBC] = {
.hot_reset_clients = {
[0] = MC_CLIENT_XUSB_HOST,
[1] = MC_CLIENT_LAST,
},
},
[TEGRA_POWERGATE_SOR] = {
.hot_reset_clients = {
[0] = MC_CLIENT_LAST,
},
},
[TEGRA_POWERGATE_VIC] = {
.hot_reset_clients = {
[0] = MC_CLIENT_VIC,
[1] = MC_CLIENT_LAST,
},
},
};
static struct powergate_partition_info
tegra124_powergate_partition_info[TEGRA124_POWERGATE_NUM] = {
[TEGRA_POWERGATE_VDEC] = {
.name = "vde",
.clk_info = {
[0] = { .clk_name = "vde" },
},
},
[TEGRA_POWERGATE_MPE] = {
.name = "mpe",
.clk_info = {
[0] = { .clk_name = "msenc" },
},
},
[TEGRA_POWERGATE_VENC] = {
.name = "ve",
.clk_info = {
[0] = { .clk_name = "isp" },
[1] = { .clk_name = "ispb" },
[2] = { .clk_name = "vi" },
[3] = { .clk_name = "csi" },
},
},
[TEGRA_POWERGATE_SOR] = {
.name = "sor",
.clk_info = {
[0] = { .clk_name = "sor0" },
[1] = { .clk_name = "dsia" },
[2] = { .clk_name = "dsib" },
[3] = { .clk_name = "hdmi" },
[4] = { .clk_name = "mipi-cal" },
[5] = { .clk_name = "dpaux" },
},
},
[TEGRA_POWERGATE_DIS] = {
.name = "disa",
.clk_info = {
[0] = { .clk_name = "disp1" },
},
},
[TEGRA_POWERGATE_DISB] = {
.name = "disb",
.clk_info = {
[0] = { .clk_name = "disp2" },
},
},
[TEGRA_POWERGATE_XUSBA] = {
.name = "xusba",
.clk_info = {
[0] = { .clk_name = "xusb_ss" },
},
},
[TEGRA_POWERGATE_XUSBB] = {
.name = "xusbb",
.clk_info = {
[0] = { .clk_name = "xusb_dev" },
},
},
[TEGRA_POWERGATE_XUSBC] = {
.name = "xusbc",
.clk_info = {
[0] = { .clk_name = "xusb_host" },
},
},
[TEGRA_POWERGATE_VIC] = {
.name = "vic",
.clk_info = {
[0] = { .clk_name = "vic03" },
},
},
};
static const char *tegra124_get_powerdomain_name(int id)
{
return tegra124_powergate_partition_info[id].name;
}
static atomic_t ref_count_dispa = ATOMIC_INIT(0);
static atomic_t ref_count_dispb = ATOMIC_INIT(0);
static atomic_t ref_count_sor = ATOMIC_INIT(0);
static atomic_t ref_count_venc = ATOMIC_INIT(0);
static DEFINE_MUTEX(tegra124_powergate_lock);
static bool is_clk_inited;
static bool is_mc_ready;
static struct notifier_block nb;
static void release_clk(struct powergate_partition_info *pg_info, int last)
{
struct partition_clk_info *clk_info;
while (last--) {
clk_info = &pg_info->clk_info[last];
clk_put(clk_info->clk_ptr);
}
}
static void tegra124_powergate_init_clk(void)
{
int i, j;
struct clk *clk;
struct powergate_partition_info *pg_info;
struct partition_clk_info *clk_info;
for (i = 0; i < TEGRA124_POWERGATE_NUM; i++) {
pg_info = &tegra124_powergate_partition_info[i];
if (!pg_info->clk_info)
break;
for (j = 0; j < MAX_CLK_EN_NUM; j++) {
clk_info = &pg_info->clk_info[j];
if (!clk_info || !clk_info->clk_name)
break;
clk = clk_get(NULL, clk_info->clk_name);
if (IS_ERR(clk)) {
pr_err("Tegra124 powergate can't find the clk %s\n",
clk_info->clk_name);
release_clk(pg_info, j);
break;
}
clk_info->clk_ptr = clk;
}
}
is_clk_inited = true;
}
static int enable_clk(int id)
{
int ret, i;
struct clk *clk;
struct powergate_partition_info *pg_info;
struct partition_clk_info *clk_info;
pg_info = &tegra124_powergate_partition_info[id];
for (i = 0; i < MAX_CLK_EN_NUM; i++) {
clk_info = &pg_info->clk_info[i];
clk = clk_info->clk_ptr;
if (!clk)
break;
ret = clk_prepare_enable(clk);
if (ret)
goto err_clk_en;
}
return 0;
err_clk_en:
WARN(1, "Could not enable clk %s, error %d",
pg_info->clk_info[i].clk_name, ret);
while (i--) {
clk_info = &pg_info->clk_info[i];
clk_disable_unprepare(clk_info->clk_ptr);
}
return ret;
}
static void reset_clk(int id, bool assert)
{
int i;
struct clk *clk;
struct powergate_partition_info *pg_info;
struct partition_clk_info *clk_info;
pg_info = &tegra124_powergate_partition_info[id];
for (i = 0; i < MAX_CLK_EN_NUM; i++) {
clk_info = &pg_info->clk_info[i];
clk = clk_info->clk_ptr;
if (!clk)
break;
if (assert)
tegra_periph_reset_assert(clk);
else
tegra_periph_reset_deassert(clk);
}
}
static void disable_clk(int id)
{
int i;
struct clk *clk;
struct powergate_partition_info *pg_info;
struct partition_clk_info *clk_info;
pg_info = &tegra124_powergate_partition_info[id];
for (i = 0; i < MAX_CLK_EN_NUM; i++) {
clk_info = &pg_info->clk_info[i];
clk = clk_info->clk_ptr;
if (!clk)
break;
clk_disable_unprepare(clk);
}
}
static int reset_module(int id)
{
int ret;
reset_clk(id, true);
udelay(10);
ret = enable_clk(id);
if (ret)
return ret;
udelay(10);
reset_clk(id, false);
disable_clk(id);
return 0;
}
/*
* MC related internal functions
*/
static int mc_flush(int id)
{
u32 i;
enum mc_client mc_client_bit;
if (!is_mc_ready) {
WARN(1, "Tegra124 memory controller is not ready\n");
return -EPERM;
}
for (i = 0; i < MAX_HOTRESET_CLIENT_NUM; i++) {
mc_client_bit =
tegra124_pg_mc_info[id].hot_reset_clients[i];
if (mc_client_bit == MC_CLIENT_LAST)
break;
tegra_mc_flush(mc_client_bit);
}
return 0;
}
static int mc_flush_done(int id)
{
u32 i;
enum mc_client mc_client_bit;
if (!is_mc_ready) {
WARN(1, "Tegra124 memory controller is not ready\n");
return -EPERM;
}
for (i = 0; i < MAX_HOTRESET_CLIENT_NUM; i++) {
mc_client_bit =
tegra124_pg_mc_info[id].hot_reset_clients[i];
if (mc_client_bit == MC_CLIENT_LAST)
break;
tegra_mc_flush_done(mc_client_bit);
}
return 0;
}
/**
* do_powergate - power gating routine
*
* @id: power partition
*/
static int do_powergate(int id)
{
int ret;
ret = enable_clk(id);
if (ret)
WARN(1, "Couldn't enable clock");
udelay(10);
mc_flush(id);
udelay(10);
reset_clk(id, true);
udelay(10);
/* Powergating is done only if refcnt of all clks is 0 */
disable_clk(id);
udelay(10);
ret = tegra_powergate_power_off(id);
if (ret)
goto err_power_off;
return 0;
err_power_off:
WARN(1, "Could not Powergate Partition %d", id);
return ret;
}
/**
* do_unpowergate - power ungating routine
*
* @id: power partition
* @reset_needed: reset or not when the power is already on
*/
static int do_unpowergate(int id, int reset_needed)
{
int ret;
if (tegra_powergate_is_powered(id))
return reset_needed ? reset_module(id) : 0;
ret = tegra_powergate_power_on(id);
if (ret)
goto err_power;
udelay(10);
/* Un-Powergating fails if all clks are not enabled */
ret = enable_clk(id);
if (ret)
goto err_clk_on;
udelay(10);
ret = tegra_powergate_remove_clamping(id);
if (ret)
goto err_clamp;
udelay(10);
/* deassert reset */
reset_clk(id, false);
udelay(10);
mc_flush_done(id);
udelay(10);
/* Disable all clks enabled earlier. Drivers should enable clks */
disable_clk(id);
return 0;
err_clamp:
disable_clk(id);
err_clk_on:
tegra_powergate_power_off(id);
err_power:
WARN(1, "Could not Un-Powergate %d", id);
return ret;
}
/**
* do_group_powergate - solve the dependency between power domains
*
* @id: the power domain to be shut off
*
* "->" means "depends on"
* VENC -> DISA
* DISB -> DISA & SOR
* DISA -> SOR
*/
static int do_group_powergate(int id)
{
int ret;
int counta = atomic_read(&ref_count_dispa);
int countb = atomic_read(&ref_count_dispb);
int countsor = atomic_read(&ref_count_sor);
int countvenc = atomic_read(&ref_count_venc);
switch (id) {
case TEGRA_POWERGATE_DIS:
if (WARN_ON(counta <= countvenc || counta <= countb))
return -EINVAL;
counta = atomic_dec_return(&ref_count_dispa);
countsor = atomic_dec_return(&ref_count_sor);
break;
case TEGRA_POWERGATE_DISB:
countb = atomic_dec_return(&ref_count_dispb);
counta = atomic_dec_return(&ref_count_dispa);
countsor = atomic_dec_return(&ref_count_sor);
break;
case TEGRA_POWERGATE_VENC:
countvenc = atomic_dec_return(&ref_count_venc);
counta = atomic_dec_return(&ref_count_dispa);
countsor = atomic_dec_return(&ref_count_sor);
break;
case TEGRA_POWERGATE_SOR:
if (WARN_ON(countsor <= counta || countsor <= countb))
return -EINVAL;
countsor = atomic_dec_return(&ref_count_sor);
break;
default:
break;
}
WARN_ONCE(counta < 0, "DISPA ref count underflow");
WARN_ONCE(countb < 0, "DISPB ref count underflow");
WARN_ONCE(countsor < 0, "SOR ref count underflow");
WARN_ONCE(countvenc < 0, "VENC ref count underflow");
ret = 0;
if (countvenc <= 0)
ret = do_powergate(TEGRA_POWERGATE_VENC);
if (countb <= 0 && !ret)
ret = do_powergate(TEGRA_POWERGATE_DISB);
if (counta <= 0 && !ret)
ret = do_powergate(TEGRA_POWERGATE_DIS);
if (countsor <= 0 && !ret)
ret = do_powergate(TEGRA_POWERGATE_SOR);
return ret;
}
/**
* do_group_unpowergate - solve the dependency between power domains
*
* @id: the power domain to be powered on
*
* "->" means "depends on"
* VENC -> DISA
* DISB -> DISA & SOR
* DISA -> SOR
*/
static int do_group_unpowergate(int id)
{
int ret;
int counta = atomic_read(&ref_count_dispa);
int countb = atomic_read(&ref_count_dispb);
int countsor = atomic_read(&ref_count_sor);
int countvenc = atomic_read(&ref_count_venc);
switch (id) {
case TEGRA_POWERGATE_DIS:
counta = atomic_inc_return(&ref_count_dispa);
countsor = atomic_inc_return(&ref_count_sor);
break;
case TEGRA_POWERGATE_DISB:
countb = atomic_inc_return(&ref_count_dispb);
counta = atomic_inc_return(&ref_count_dispa);
countsor = atomic_inc_return(&ref_count_sor);
break;
case TEGRA_POWERGATE_VENC:
countvenc = atomic_inc_return(&ref_count_venc);
counta = atomic_inc_return(&ref_count_dispa);
countsor = atomic_inc_return(&ref_count_sor);
break;
case TEGRA_POWERGATE_SOR:
countsor = atomic_inc_return(&ref_count_sor);
break;
default:
break;
}
ret = 0;
if (countsor > 0)
ret = do_unpowergate(TEGRA_POWERGATE_SOR, 0);
if (counta > 0 && !ret)
ret = do_unpowergate(TEGRA_POWERGATE_DIS, 0);
if (countb > 0 && !ret)
ret = do_unpowergate(TEGRA_POWERGATE_DISB, 0);
if (countvenc > 0 && !ret)
ret = do_unpowergate(TEGRA_POWERGATE_VENC, 0);
return ret;
}
static int tegra124_powergate_partition(int id)
{
int ret;
mutex_lock(&tegra124_powergate_lock);
if (!is_clk_inited)
tegra124_powergate_init_clk();
switch (id) {
case TEGRA_POWERGATE_DIS:
case TEGRA_POWERGATE_DISB:
case TEGRA_POWERGATE_VENC:
/*
* SOR should not be powergated by someone outside the powergate code,
* but who knows?
*/
case TEGRA_POWERGATE_SOR:
ret = do_group_powergate(id);
break;
default:
ret = do_powergate(id);
break;
};
mutex_unlock(&tegra124_powergate_lock);
return ret;
}
static int tegra124_unpowergate_partition(int id)
{
int ret;
mutex_lock(&tegra124_powergate_lock);
if (!is_clk_inited)
tegra124_powergate_init_clk();
switch (id) {
case TEGRA_POWERGATE_DIS:
case TEGRA_POWERGATE_DISB:
case TEGRA_POWERGATE_VENC:
/*
* SOR should not be powergated by someone outside the powergate code,
* but who knows?
*/
case TEGRA_POWERGATE_SOR:
ret = do_group_unpowergate(id);
break;
default:
ret = do_unpowergate(id, 1);
break;
};
mutex_unlock(&tegra124_powergate_lock);
return ret;
}
static struct powergate_ops tegra124_powergate_ops = {
.get_powergate_domain_name = tegra124_get_powerdomain_name,
.powergate_partition = tegra124_powergate_partition,
.unpowergate_partition = tegra124_unpowergate_partition,
};
static struct powergate t124_powergate = {
.soc_name = "tegra124",
.num_powerdomains = TEGRA124_POWERGATE_NUM,
.num_cpu_domains = 0,
.cpu_domain_map = NULL,
.ops = &tegra124_powergate_ops,
};
static int tegra124_powergate_clean(struct notifier_block *self,
unsigned long event, void *data)
{
is_mc_ready = true;
if (!is_clk_inited)
tegra124_powergate_init_clk();
/* Powergate venc/dis/disb/sor to get a clean hardware environment. */
WARN(do_powergate(TEGRA_POWERGATE_VENC), "Powergate VENC failed.");
WARN(do_powergate(TEGRA_POWERGATE_DISB), "Powergate DISB failed.");
WARN(do_powergate(TEGRA_POWERGATE_DIS), "Powergate DIS failed.");
WARN(do_powergate(TEGRA_POWERGATE_SOR), "Powergate SOR failed.");
return NOTIFY_DONE;
}
struct powergate * __init tegra124_powergate_init(void)
{
memset(&nb, 0, sizeof(nb));
nb.notifier_call = tegra124_powergate_clean;
tegra124_mc_register_notify(&nb);
return &t124_powergate;
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright (c) 2013, NVIDIA CORPORATION. All rights reserved.
*
* 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/spinlock.h>
#include <linux/tegra-powergate.h>
static struct powergate_partition_info tegra20_powergate_partition_info[] = {
[TEGRA_POWERGATE_CPU] = { .name = "cpu" },
[TEGRA_POWERGATE_3D] = { .name = "3d" },
[TEGRA_POWERGATE_VENC] = { .name = "venc" },
[TEGRA_POWERGATE_VDEC] = { .name = "vdec" },
[TEGRA_POWERGATE_PCIE] = { .name = "pcie" },
[TEGRA_POWERGATE_L2] = { .name = "l2" },
[TEGRA_POWERGATE_MPE] = { .name = "mpe" },
};
static const char *tegra20_get_powerdomain_name(int id)
{
return tegra20_powergate_partition_info[id].name;
}
static struct powergate_ops tegra20_powergate_ops = {
.get_powergate_domain_name = tegra20_get_powerdomain_name,
.powergate_partition = NULL,
.unpowergate_partition = NULL,
};
static struct powergate t20_powergate = {
.soc_name = "tegra20",
.num_powerdomains = 7,
.num_cpu_domains = 0,
.cpu_domain_map = NULL,
.ops = &tegra20_powergate_ops,
};
struct powergate * __init tegra20_powergate_init(void)
{
return &t20_powergate;
}

View File

@@ -0,0 +1,65 @@
/*
* Copyright (c) 2013, NVIDIA CORPORATION. All rights reserved.
*
* 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/spinlock.h>
#include <linux/tegra-powergate.h>
static struct powergate_partition_info tegra30_powergate_partition_info[] = {
[TEGRA_POWERGATE_CPU] = { .name = "cpu0" },
[TEGRA_POWERGATE_3D] = { .name = "3d0" },
[TEGRA_POWERGATE_VENC] = { .name = "venc" },
[TEGRA_POWERGATE_VDEC] = { .name = "vdec" },
[TEGRA_POWERGATE_PCIE] = { .name = "pcie" },
[TEGRA_POWERGATE_L2] = { .name = "l2" },
[TEGRA_POWERGATE_MPE] = { .name = "mpe" },
[TEGRA_POWERGATE_HEG] = { .name = "heg" },
[TEGRA_POWERGATE_SATA] = { .name = "sata" },
[TEGRA_POWERGATE_CPU1] = { .name = "cpu1" },
[TEGRA_POWERGATE_CPU2] = { .name = "cpu2" },
[TEGRA_POWERGATE_CPU3] = { .name = "cpu3" },
[TEGRA_POWERGATE_CELP] = { .name = "celp" },
[TEGRA_POWERGATE_3D1] = { .name = "3d1" },
};
static const u8 tegra30_cpu_domains[] = {
TEGRA_POWERGATE_CPU,
TEGRA_POWERGATE_CPU1,
TEGRA_POWERGATE_CPU2,
TEGRA_POWERGATE_CPU3,
};
static const char *tegra30_get_powerdomain_name(int id)
{
return tegra30_powergate_partition_info[id].name;
}
static struct powergate_ops tegra30_powergate_ops = {
.get_powergate_domain_name = tegra30_get_powerdomain_name,
.powergate_partition = NULL,
.unpowergate_partition = NULL,
};
static struct powergate t30_powergate = {
.soc_name = "tegra30",
.num_powerdomains = 14,
.num_cpu_domains = 4,
.cpu_domain_map = tegra30_cpu_domains,
.ops = &tegra30_powergate_ops,
};
struct powergate * __init tegra30_powergate_init(void)
{
return &t30_powergate;
}

View File

@@ -0,0 +1,445 @@
/*
* drivers/powergate/tegra-powergate.c
*
* Copyright (c) 2010 Google, Inc
*
* Author:
* Colin Cross <ccross@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/kernel.h>
#include <linux/clk.h>
#include <linux/debugfs.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/export.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/seq_file.h>
#include <linux/spinlock.h>
#include <linux/clk/tegra.h>
#include <linux/tegra-powergate.h>
#include <linux/tegra-soc.h>
#include "iomap.h"
#define DPD_SAMPLE 0x020
#define DPD_SAMPLE_ENABLE (1 << 0)
#define DPD_SAMPLE_DISABLE (0 << 0)
#define PWRGATE_CLAMP_STATUS 0x2c
#define PWRGATE_TOGGLE 0x30
#define PWRGATE_TOGGLE_START (1 << 8)
#define REMOVE_CLAMPING 0x34
#define PWRGATE_STATUS 0x38
/* Timeout for powergate toggle operation if it takes affect */
#define PWRGATE_TOGGLE_TIMEOUT 10
/* Timeout for PMC to complete other requests before this */
#define PWRGATE_CONTENTION_TIMEOUT 100
#define IO_DPD_REQ 0x1b8
#define IO_DPD_REQ_CODE_IDLE (0 << 30)
#define IO_DPD_REQ_CODE_OFF (1 << 30)
#define IO_DPD_REQ_CODE_ON (2 << 30)
#define IO_DPD_REQ_CODE_MASK (3 << 30)
#define IO_DPD_STATUS 0x1bc
#define IO_DPD2_REQ 0x1c0
#define IO_DPD2_STATUS 0x1c4
#define SEL_DPD_TIM 0x1c8
#define GPU_RG_CNTRL 0x2d4
static int tegra_num_powerdomains;
static int tegra_num_cpu_domains;
static const u8 *tegra_cpu_domains;
static struct powergate *powergate;
static DEFINE_SPINLOCK(tegra_powergate_lock);
static void __iomem *pmc = IO_ADDRESS(TEGRA_PMC_BASE);
static u32 pmc_read(unsigned long reg)
{
return readl(pmc + reg);
}
static void pmc_write(u32 val, unsigned long reg)
{
writel(val, pmc + reg);
}
static int tegra_powergate_set(int id, bool new_state)
{
bool status;
unsigned long flags;
/*
* (TOGGLE_TIMEOUT * CONTENTION_TIMEOUT) timeout in microsecond for
* toggle command to take affect in case of contention with h/w
* initiated CPU power gating.
*/
int timeout = PWRGATE_CONTENTION_TIMEOUT * PWRGATE_TOGGLE_TIMEOUT;
spin_lock_irqsave(&tegra_powergate_lock, flags);
status = pmc_read(PWRGATE_STATUS) & (1 << id);
if (status == new_state) {
spin_unlock_irqrestore(&tegra_powergate_lock, flags);
return 0;
}
pmc_write(PWRGATE_TOGGLE_START | id, PWRGATE_TOGGLE);
/* Check power gate status */
do {
udelay(1);
status = !!(pmc_read(PWRGATE_STATUS) & (1 << id));
timeout--;
} while ((status != new_state) && (timeout > 0));
spin_unlock_irqrestore(&tegra_powergate_lock, flags);
if (status != new_state) {
WARN(1, "Could not set powergate %d to %d",
id, new_state);
return -EBUSY;
}
return 0;
}
int tegra_powergate_power_on(int id)
{
if (id < 0 || id >= tegra_num_powerdomains)
return -EINVAL;
return tegra_powergate_set(id, true);
}
int tegra_powergate_power_off(int id)
{
if (id < 0 || id >= tegra_num_powerdomains)
return -EINVAL;
return tegra_powergate_set(id, false);
}
int tegra_powergate_is_powered(int id)
{
u32 status;
if (id < 0 || id >= tegra_num_powerdomains)
return -EINVAL;
status = pmc_read(PWRGATE_STATUS) & (1 << id);
return !!status;
}
int tegra_powergate_remove_clamping(int id)
{
u32 mask;
int contention_timeout = PWRGATE_CONTENTION_TIMEOUT;
if (id < 0 || id >= tegra_num_powerdomains)
return -EINVAL;
/*
* Tegra 2 has a bug where PCIE and VDE clamping masks are
* swapped relatively to the partition ids
*/
if (id == TEGRA_POWERGATE_VDEC)
mask = (1 << TEGRA_POWERGATE_PCIE);
else if (id == TEGRA_POWERGATE_PCIE)
mask = (1 << TEGRA_POWERGATE_VDEC);
else
mask = (1 << id);
pmc_write(mask, REMOVE_CLAMPING);
/* Wait until clamp is removed */
do {
udelay(1);
contention_timeout--;
} while ((contention_timeout > 0)
&& (pmc_read(PWRGATE_CLAMP_STATUS) & BIT(id)));
if (pmc_read(PWRGATE_CLAMP_STATUS) & BIT(id)) {
WARN(1, "Couldn't remove clamping");
return -EBUSY;
}
return 0;
}
/* Must be called with clk disabled, and returns with clk enabled */
int tegra_powergate_sequence_power_up(int id, struct clk *clk)
{
int ret;
tegra_periph_reset_assert(clk);
ret = tegra_powergate_power_on(id);
if (ret)
goto err_power;
ret = clk_prepare_enable(clk);
if (ret)
goto err_clk;
udelay(10);
ret = tegra_powergate_remove_clamping(id);
if (ret)
goto err_clamp;
udelay(10);
tegra_periph_reset_deassert(clk);
return 0;
err_clamp:
clk_disable_unprepare(clk);
err_clk:
tegra_powergate_power_off(id);
err_power:
return ret;
}
EXPORT_SYMBOL(tegra_powergate_sequence_power_up);
int tegra_powergate_partition(int id)
{
if (id < 0 || id >= tegra_num_powerdomains)
return -EINVAL;
return powergate->ops->powergate_partition(id);
}
EXPORT_SYMBOL(tegra_powergate_partition);
int tegra_unpowergate_partition(int id)
{
if (id < 0 || id >= tegra_num_powerdomains)
return -EINVAL;
return powergate->ops->unpowergate_partition(id);
}
EXPORT_SYMBOL(tegra_unpowergate_partition);
int tegra_cpu_powergate_id(int cpuid)
{
if (cpuid > 0 && cpuid < tegra_num_cpu_domains)
return tegra_cpu_domains[cpuid];
return -EINVAL;
}
int __init tegra_powergate_init(void)
{
switch (tegra_chip_id) {
case TEGRA20:
powergate = tegra20_powergate_init();
break;
case TEGRA30:
powergate = tegra30_powergate_init();
break;
case TEGRA114:
powergate = tegra114_powergate_init();
break;
case TEGRA124:
powergate = tegra124_powergate_init();
break;
default:
/* Unknown Tegra variant. Disable powergating */
tegra_num_powerdomains = 0;
break;
}
if (powergate) {
tegra_num_powerdomains = powergate->num_powerdomains;
tegra_num_cpu_domains = powergate->num_cpu_domains;
tegra_cpu_domains = powergate->cpu_domain_map;
}
return 0;
}
#ifdef CONFIG_DEBUG_FS
static int powergate_show(struct seq_file *s, void *data)
{
int i;
const char *name;
seq_printf(s, " powergate powered\n");
seq_printf(s, "------------------\n");
for (i = 0; i < tegra_num_powerdomains; i++) {
name = powergate->ops->get_powergate_domain_name(i);
if (!name)
continue;
seq_printf(s, " %9s %7s\n", name,
tegra_powergate_is_powered(i) ? "yes" : "no");
}
return 0;
}
static int powergate_open(struct inode *inode, struct file *file)
{
return single_open(file, powergate_show, inode->i_private);
}
static const struct file_operations powergate_fops = {
.open = powergate_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
int __init tegra_powergate_debugfs_init(void)
{
struct dentry *d;
if (powergate && powergate->ops &&
powergate->ops->get_powergate_domain_name) {
d = debugfs_create_file("powergate", S_IRUGO, NULL, NULL,
&powergate_fops);
if (!d)
return -ENOMEM;
}
return 0;
}
#endif
static int tegra_io_rail_prepare(int id, unsigned long *request,
unsigned long *status, unsigned int *bit)
{
unsigned long rate, value;
struct clk *clk;
*bit = id % 32;
/*
* There are two sets of 30 bits to select IO rails, but bits 30 and
* 31 are control bits rather than IO rail selection bits.
*/
if (id > 63 || *bit == 30 || *bit == 31)
return -EINVAL;
if (id < 32) {
*status = IO_DPD_STATUS;
*request = IO_DPD_REQ;
} else {
*status = IO_DPD2_STATUS;
*request = IO_DPD2_REQ;
}
clk = clk_get_sys(NULL, "pclk");
if (IS_ERR(clk))
return PTR_ERR(clk);
rate = clk_get_rate(clk);
clk_put(clk);
pmc_write(DPD_SAMPLE_ENABLE, DPD_SAMPLE);
/* must be at least 200 ns, in APB (PCLK) clock cycles */
value = DIV_ROUND_UP(1000000000, rate);
value = DIV_ROUND_UP(200, value);
pmc_write(value, SEL_DPD_TIM);
return 0;
}
static int tegra_io_rail_poll(unsigned long offset, unsigned long mask,
unsigned long val, unsigned long timeout)
{
unsigned long value;
timeout = jiffies + msecs_to_jiffies(timeout);
while (time_after(timeout, jiffies)) {
value = pmc_read(offset);
if ((value & mask) == val)
return 0;
usleep_range(250, 1000);
}
return -ETIMEDOUT;
}
static void tegra_io_rail_unprepare(void)
{
pmc_write(DPD_SAMPLE_DISABLE, DPD_SAMPLE);
}
int tegra_io_rail_power_on(int id)
{
unsigned long request, status, value;
unsigned int bit, mask;
int err;
err = tegra_io_rail_prepare(id, &request, &status, &bit);
if (err < 0)
return err;
mask = 1 << bit;
value = mask;
value &= ~IO_DPD_REQ_CODE_MASK;
value |= IO_DPD_REQ_CODE_OFF;
pmc_write(value, request);
err = tegra_io_rail_poll(status, mask, 0, 250);
if (err < 0)
return err;
tegra_io_rail_unprepare();
return 0;
}
int tegra_io_rail_power_off(int id)
{
unsigned long request, status, value;
unsigned int bit, mask;
int err;
err = tegra_io_rail_prepare(id, &request, &status, &bit);
if (err < 0)
return err;
mask = 1 << bit;
value = mask;
value &= ~IO_DPD_REQ_CODE_MASK;
value |= IO_DPD_REQ_CODE_ON;
pmc_write(value, request);
err = tegra_io_rail_poll(status, mask, mask, 250);
if (err < 0)
return err;
tegra_io_rail_unprepare();
return 0;
}

View File

@@ -0,0 +1,289 @@
/*
* 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 <linux/init.h>
#include <asm/cache.h>
#include <asm/asm-offsets.h>
#include <asm/hardware/cache-l2x0.h>
#include <linux/tegra-soc.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
/* 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
/* L2 cache resume & re-enable */
l2_cache_resume r0, r1, r2, l2x0_saved_regs_addr
end_ca9_scu_l2_resume:
mov32 r9, 0xc0f
cmp r8, r9
bleq tegra_init_l2_for_a15
b cpu_resume
ENDPROC(tegra_resume)
#endif
#ifdef CONFIG_CACHE_L2X0
.globl l2x0_saved_regs_addr
l2x0_saved_regs_addr:
.long 0
#endif
.align L1_CACHE_SHIFT
ENTRY(__tegra_cpu_reset_handler_start)
/*
* __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_PMC_BASE
mov r0, #0
cmp r10, #0
strne r0, [r5, #PMC_SCRATCH41]
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
.align L1_CACHE_SHIFT
ENTRY(__tegra_cpu_reset_handler_end)

105
arch/arm/mach-tegra/reset.c Normal file
View File

@@ -0,0 +1,105 @@
/*
* 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/init.h>
#include <linux/io.h>
#include <linux/cpumask.h>
#include <linux/bitops.h>
#include <linux/tegra-soc.h>
#include <asm/cacheflush.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 tegra_cpu_reset_handler_enable(void)
{
void __iomem *iram_base = IO_ADDRESS(TEGRA_IRAM_RESET_BASE);
void __iomem *evp_cpu_reset =
IO_ADDRESS(TEGRA_EXCEPTION_VECTORS_BASE + 0x100);
void __iomem *sb_ctrl = IO_ADDRESS(TEGRA_SB_BASE);
u32 reg;
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);
/*
* NOTE: This must be the one and only write to the EVP CPU reset
* vector in the entire system.
*/
writel(TEGRA_IRAM_RESET_BASE + tegra_cpu_reset_handler_offset,
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.
*/
if (tegra_chip_id != TEGRA20) {
reg = readl(sb_ctrl);
reg |= 2;
writel(reg, sb_ctrl);
wmb();
}
is_enabled = true;
}
#ifdef CONFIG_PM_SLEEP
void tegra_cpu_reset_handler_save(void)
{
WARN_ON(!is_enabled);
is_enabled = false;
}
void tegra_cpu_reset_handler_restore(void)
{
WARN_ON(is_enabled);
tegra_cpu_reset_handler_enable();
}
#endif
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_CODE_AREA;
__tegra_cpu_reset_handler_data[TEGRA_RESET_STARTUP_LP2] =
virt_to_phys((void *)tegra_resume);
#endif
tegra_cpu_reset_handler_enable();
}

View File

@@ -0,0 +1,71 @@
/*
* 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 __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 tegra_cpu_reset_handler_data_iram_ptr \
((u32 *)(IO_ADDRESS(TEGRA_IRAM_BASE + TEGRA_IRAM_RESET_HANDLER_OFFSET +\
((u32)__tegra_cpu_reset_handler_data - \
(u32)__tegra_cpu_reset_handler_start))))
#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)
#ifdef CONFIG_PM_SLEEP
void tegra_cpu_reset_handler_save(void);
void tegra_cpu_reset_handler_restore(void);
#endif
void __init tegra_cpu_reset_handler_init(void);
#endif
#endif

View File

@@ -0,0 +1,574 @@
/*
* 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 "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
mov pc, 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
moveq pc, lr @ must not be called for CPU 0
mov32 r1, TEGRA_PMC_VIRT + PMC_SCRATCH41
mov r12, #CPU_RESETTABLE
str r12, [r1]
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 .
mov pc, 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
mov pc, 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]
mov pc, lr
ENDPROC(tegra_pen_unlock)
/*
* tegra20_cpu_clear_resettable(void)
*
* Called to clear the "resettable soon" flag in PMC_SCRATCH41 when
* it is expected that the secondary CPU will be idle soon.
*/
ENTRY(tegra20_cpu_clear_resettable)
mov32 r1, TEGRA_PMC_VIRT + PMC_SCRATCH41
mov r12, #CPU_NOT_RESETTABLE
str r12, [r1]
mov pc, lr
ENDPROC(tegra20_cpu_clear_resettable)
/*
* tegra20_cpu_set_resettable_soon(void)
*
* Called to set the "resettable soon" flag in PMC_SCRATCH41 when
* it is expected that the secondary CPU will be idle soon.
*/
ENTRY(tegra20_cpu_set_resettable_soon)
mov32 r1, TEGRA_PMC_VIRT + PMC_SCRATCH41
mov r12, #CPU_RESETTABLE_SOON
str r12, [r1]
mov pc, lr
ENDPROC(tegra20_cpu_set_resettable_soon)
/*
* tegra20_cpu_is_resettable_soon(void)
*
* Returns true if the "resettable soon" flag in PMC_SCRATCH41 has been
* set because it is expected that the secondary CPU will be idle soon.
*/
ENTRY(tegra20_cpu_is_resettable_soon)
mov32 r1, TEGRA_PMC_VIRT + PMC_SCRATCH41
ldr r12, [r1]
cmp r12, #CPU_RESETTABLE_SOON
moveq r0, #1
movne r0, #0
mov pc, 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_CODE_AREA
add r0, r0, r1
mov pc, 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_PMC_VIRT + PMC_SCRATCH41
mov r3, #CPU_RESETTABLE
str r3, [r0]
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 r3, TEGRA_PMC_VIRT
add r0, r3, #PMC_SCRATCH41
mov r3, #CPU_NOT_RESETTABLE
str r3, [r0]
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_CODE_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]
mov pc, 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]
mov pc, 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
mov pc, 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

View File

@@ -0,0 +1,846 @@
/*
* 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 <linux/tegra-soc.h>
#include <asm/assembler.h>
#include <asm/asm-offsets.h>
#include <asm/cache.h>
#include "sleep.h"
#include "flowctrl.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 PMC_PLLM_WB0_OVERRIDE 0x1dc
#define PMC_SCRATCH1_ECO 0x264
#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
mov pc, 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
moveq pc, 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_CODE_AREA
add r0, r0, r1
mov pc, 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
mov pc, r7
ENDPROC(tegra30_sleep_cpu_secondary_finish)
/*
* tegra30_tear_down_cpu
*
* Switches the CPU to enter sleep.
*/
ENTRY(tegra30_tear_down_cpu)
mov32 r6, TEGRA_FLOW_CTRL_BASE
b tegra30_enter_sleep
ENDPROC(tegra30_tear_down_cpu)
/* 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_CODE_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]
mov pc, 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:
ldr r0, [r4, #PMC_CTRL]
tst r0, #PMC_CTRL_SIDE_EFFECT_LP0
beq lp1_clocks_prepare
/* enable PLLM auto-restart via PMC in LP0; restore override settings */
ldr r0, [r4, #PMC_PLLP_WB0_OVERRIDE]
orr r0, r0, #(1 << 12) | (1 << 11)
str r0, [r4, #PMC_PLLP_WB0_OVERRIDE]
ldr r0, [r4, #PMC_SCRATCH2]
str r0, [r4, #PMC_PLLM_WB0_OVERRIDE]
cmp r10, #(TEGRA114 << 8)
ldreq r1, [r4, #PMC_SCRATCH1_ECO]
orreq r1, r1, #0x3f
streq r1, [r4, #PMC_SCRATCH1_ECO]
mov pc, lr
lp1_clocks_prepare:
/*
* 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]
mov pc, 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]
mov r0, #FLOW_CTRL_WAIT_FOR_INTERRUPT
orr r0, r0, #FLOW_CTRL_HALT_CPU_IRQ | FLOW_CTRL_HALT_CPU_FIQ
cpu_to_halt_reg r2, r1
str r0, [r6, r2]
dsb
ldr r0, [r6, r2] /* memory barrier */
halted:
isb
dsb
wfi /* CPU should be power gated here */
/* !!!FIXME!!! Implement halt failure handler */
b halted
/*
* 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
mov pc, lr
.ltorg
/* dummy symbol for end of IRAM */
.align L1_CACHE_SHIFT
.global tegra30_iram_end
tegra30_iram_end:
b .
#endif

161
arch/arm/mach-tegra/sleep.S Normal file
View File

@@ -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:
mov pc, 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
mov pc, r3
ENDPROC(tegra_sleep_cpu_finish)
/*
* tegra_shut_off_mmu
*
* r0 = physical address to jump to with mmu off
*
* called with VA=PA mapping
* turns off MMU, icache, dcache and branch prediction
*/
.align L1_CACHE_SHIFT
.pushsection .idmap.text, "ax"
ENTRY(tegra_shut_off_mmu)
mrc p15, 0, r3, c1, c0, 0
movw r2, #CR_I | CR_Z | CR_C | CR_M
bic r3, r3, r2
dsb
mcr p15, 0, r3, c1, c0, 0
isb
#ifdef CONFIG_CACHE_L2X0
/* Disable L2 cache */
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
mov pc, 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]
mov pc, lr
ENDPROC(tegra_switch_cpu_to_pllp)
#endif

196
arch/arm/mach-tegra/sleep.h Normal file
View File

@@ -0,0 +1,196 @@
/*
* 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"
#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)
/* PMC_SCRATCH2 is used for PLLM boot state if PLLM auto-restart is enabled */
#define PMC_SCRATCH2 0x58
/* 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
#define TEGRA_POWER_CLUSTER_PART_NONCPU BIT(24)
#define TEGRA_POWER_CLUSTER_PART_CRAIL BIT(25)
#define TEGRA_POWER_CLUSTER_PART_DEFAULT TEGRA_POWER_CLUSTER_PART_CRAIL
#define TEGRA_POWER_CLUSTER_G BIT(28)
#define TEGRA_POWER_CLUSTER_LP BIT(29)
#define TEGRA_POWER_CLUSTER_MASK (TEGRA_POWER_CLUSTER_G |\
TEGRA_POWER_CLUSTER_LP)
#define TEGRA_POWER_CLUSTER_IMMEDIATE BIT(30)
#define TEGRA_POWER_CLUSTER_FORCE BIT(31)
#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
/* Macro to resume & re-enable L2 cache */
#ifndef L2X0_CTRL_EN
#define L2X0_CTRL_EN 1
#endif
#ifdef CONFIG_CACHE_L2X0
.macro l2_cache_resume, tmp1, tmp2, tmp3, phys_l2x0_saved_regs
W(adr) \tmp1, \phys_l2x0_saved_regs
ldr \tmp1, [\tmp1]
ldr \tmp2, [\tmp1, #L2X0_R_PHY_BASE]
ldr \tmp3, [\tmp2, #L2X0_CTRL]
tst \tmp3, #L2X0_CTRL_EN
bne exit_l2_resume
ldr \tmp3, [\tmp1, #L2X0_R_TAG_LATENCY]
str \tmp3, [\tmp2, #L2X0_TAG_LATENCY_CTRL]
ldr \tmp3, [\tmp1, #L2X0_R_DATA_LATENCY]
str \tmp3, [\tmp2, #L2X0_DATA_LATENCY_CTRL]
ldr \tmp3, [\tmp1, #L2X0_R_PREFETCH_CTRL]
str \tmp3, [\tmp2, #L2X0_PREFETCH_CTRL]
ldr \tmp3, [\tmp1, #L2X0_R_PWR_CTRL]
str \tmp3, [\tmp2, #L2X0_POWER_CTRL]
ldr \tmp3, [\tmp1, #L2X0_R_AUX_CTRL]
str \tmp3, [\tmp2, #L2X0_AUX_CTRL]
mov \tmp3, #L2X0_CTRL_EN
str \tmp3, [\tmp2, #L2X0_CTRL]
exit_l2_resume:
.endm
#else /* CONFIG_CACHE_L2X0 */
.macro l2_cache_resume, tmp1, tmp2, tmp3, phys_l2x0_saved_regs
.endm
#endif /* CONFIG_CACHE_L2X0 */
#else
void tegra_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);
void tegra_hotplug_init(void);
#else
static inline void tegra_hotplug_init(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

File diff suppressed because it is too large Load Diff

295
arch/arm/mach-tegra/tegra.c Normal file
View File

@@ -0,0 +1,295 @@
/*
* 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/clocksource.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/serial_8250.h>
#include <linux/clk.h>
#include <linux/dma-mapping.h>
#include <linux/irqdomain.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_fdt.h>
#include <linux/of_platform.h>
#include <linux/pda_power.h>
#include <linux/io.h>
#include <linux/slab.h>
#include <linux/sys_soc.h>
#include <linux/usb/tegra_usb_phy.h>
#include <linux/clk-provider.h>
#include <linux/clk/tegra.h>
#include <linux/regulator/machine.h>
#include <linux/tegra-dvfs.h>
#include <linux/tegra-soc.h>
#include <linux/irqchip.h>
#include <linux/tegra-soc.h>
#include <linux/nvmap.h>
#include <linux/memblock.h>
#include <asm/hardware/cache-l2x0.h>
#include <asm/mach-types.h>
#include <asm/mach/arch.h>
#include <asm/mach/time.h>
#include <asm/setup.h>
#include "apbio.h"
#include "board.h"
#include "board-panel.h"
#include "common.h"
#include "cpuidle.h"
#include "flowctrl.h"
#include "iomap.h"
#include "irq.h"
#include "pmc.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[4] = {
/* Debug UART initialization required */
1,
/* Debug UART physical address */
0,
/* Debug UART virtual address */
0,
/* Scratch space for debug macro */
0,
};
static void __init tegra_init_cache(void)
{
#ifdef CONFIG_CACHE_L2X0
int ret;
void __iomem *p = IO_ADDRESS(TEGRA_ARM_PERIF_BASE) + 0x3000;
u32 aux_ctrl, cache_type;
cache_type = readl(p + L2X0_CACHE_TYPE);
aux_ctrl = (cache_type & 0x700) << (17-8);
aux_ctrl |= 0x7C400001;
ret = l2x0_of_init(aux_ctrl, 0x8200c3fe);
if (!ret)
l2x0_saved_regs_addr = virt_to_phys(&l2x0_saved_regs);
#endif
}
static void __init tegra_init_early(void)
{
tegra_cpu_reset_handler_init();
tegra_apb_io_init();
tegra_init_fuse();
tegra_flowctrl_ram_repair_init();
tegra_init_cache();
tegra_powergate_init();
tegra_hotplug_init();
}
static void __init tegra_dt_init_irq(void)
{
tegra_pmc_init_irq();
tegra_init_irq();
irqchip_init();
tegra_legacy_irq_syscore_init();
}
#ifdef CONFIG_TEGRA_NVMAP
static struct nvmap_platform_carveout venice_carveouts[] = {
[0] = {
.name = "iram",
.usage_mask = NVMAP_HEAP_CARVEOUT_IRAM,
.base = TEGRA_IRAM_BASE + TEGRA_RESET_HANDLER_SIZE,
.size = TEGRA_IRAM_SIZE - TEGRA_RESET_HANDLER_SIZE,
.buddy_size = 0, /* no buddy allocation for IRAM */
},
[1] = {
.name = "generic-0",
.usage_mask = NVMAP_HEAP_CARVEOUT_GENERIC,
.base = 0, /* Filled later, if carveout is needed */
.size = 0, /* Filled later, if carveout is needed */
.buddy_size = SZ_32K,
},
[2] = {
.name = "vpr",
.usage_mask = NVMAP_HEAP_CARVEOUT_VPR,
.base = 0, /* Filled later */
.size = 0, /* Filled later */
.buddy_size = SZ_32K,
},
};
static struct nvmap_platform_data venice_nvmap_data = {
.carveouts = venice_carveouts,
.nr_carveouts = ARRAY_SIZE(venice_carveouts),
};
#endif
static void __init nyan_init(void)
{
venice_panel_init();
}
static struct of_dev_auxdata tegra_auxdata_lookup[] __initdata = {
OF_DEV_AUXDATA("pwm-backlight", 0, "pwm-backlight", &venice_bl_data),
#ifdef CONFIG_TEGRA_DC
OF_DEV_AUXDATA("nvidia,tegra124-dc", 0x54200000, "tegradc.0", &venice_disp1_pdata),
OF_DEV_AUXDATA("nvidia,tegra124-dc", 0x54240000, "tegradc.1", &venice_disp2_pdata),
#endif
#ifdef CONFIG_TEGRA_NVMAP
OF_DEV_AUXDATA("nvidia,tegra124-nvmap", 0, "tegra-nvmap", &venice_nvmap_data),
#endif
{}
};
static void __init tegra_dt_init(void)
{
struct soc_device_attribute *soc_dev_attr;
struct soc_device *soc_dev;
struct device *parent = NULL;
tegra_pmc_init();
tegra_clocks_apply_init_table();
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_revision);
soc_dev_attr->soc_id = kasprintf(GFP_KERNEL, "%d", tegra_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);
regulator_has_full_constraints();
/*
* Finished with the static registrations now; fill in the missing
* devices
*/
out:
#ifdef CONFIG_PM_SLEEP
tegra_pmc_lp0_wakeup_init();
#endif
of_platform_populate(NULL, of_default_bus_match_table,
tegra_auxdata_lookup, parent);
tegra_dvfs_init();
if (of_machine_is_compatible("google,nyan"))
nyan_init();
}
static void __init trimslice_init(void)
{
#ifdef CONFIG_TEGRA_PCI
int ret;
ret = tegra_pcie_init(true, true);
if (ret)
pr_err("tegra_pci_init() failed: %d\n", ret);
#endif
}
static void __init harmony_init(void)
{
#ifdef CONFIG_TEGRA_PCI
int ret;
ret = harmony_pcie_init();
if (ret)
pr_err("harmony_pcie_init() failed: %d\n", ret);
#endif
}
static void __init tegra_dt_init_time(void)
{
of_clk_init(NULL);
clocksource_of_init();
}
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[] = {
{ "compulab,trimslice", trimslice_init },
{ "nvidia,harmony", harmony_init },
{ "compal,paz00", paz00_init },
};
static void __init tegra_dt_init_late(void)
{
int i;
tegra_cpufreq_init();
tegra_init_suspend();
tegra_cpuidle_init();
tegra_powergate_debugfs_init();
tegra_pmc_init_late();
tegra_cpuquiet_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)")
.map_io = tegra_map_common_io,
.smp = smp_ops(tegra_smp_ops),
.init_early = tegra_init_early,
.init_irq = tegra_dt_init_irq,
.init_time = tegra_dt_init_time,
.init_machine = tegra_dt_init,
.init_late = tegra_dt_init_late,
.restart = tegra_pmc_restart,
.dt_compat = tegra_dt_board_compat,
MACHINE_END

View File

@@ -0,0 +1,521 @@
/*
* 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 <linux/module.h>
#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <linux/tegra-soc.h>
#include <linux/tegra-dvfs.h>
#include <linux/clk/tegra124-dfll.h>
#include <linux/platform_data/tegra_thermal.h>
#include <linux/platform_data/tegra_emc.h>
#define KHZ 1000
#define MHZ 1000000
#define VDD_SAFE_STEP 100
static bool tegra_dvfs_cpu_disabled;
static bool tegra_dvfs_core_disabled;
static int cpu_millivolts[MAX_DVFS_FREQS];
static int cpu_dfll_millivolts[MAX_DVFS_FREQS];
static int vdd_core_therm_trips_table[MAX_THERMAL_LIMITS] = { 20, };
static int vdd_core_therm_floors_table[MAX_THERMAL_LIMITS] = { 900, };
static struct tegra_cooling_device core_vmin_cdev = {
.cdev_type = "core_cold",
};
static struct dvfs_rail tegra124_dvfs_rail_vdd_cpu = {
.reg_id = "vdd_cpu",
.max_millivolts = 1300,
.min_millivolts = 700,
.step = VDD_SAFE_STEP,
.jmp_to_zero = true,
.alignment = {
.step_uv = 10000, /* 10mV */
},
};
static struct dvfs_rail tegra124_dvfs_rail_vdd_core = {
.reg_id = "vdd_core",
.max_millivolts = 1400,
.min_millivolts = 800,
.step = VDD_SAFE_STEP,
.step_up = 1400,
.vmin_cdev = &core_vmin_cdev,
};
static struct dvfs_rail *tegra124_dvfs_rails[] = {
&tegra124_dvfs_rail_vdd_cpu,
&tegra124_dvfs_rail_vdd_core,
};
static struct dvfs cpu_dvfs = {
.clk_name = "cclk_g",
.millivolts = cpu_millivolts,
.dfll_millivolts = cpu_dfll_millivolts,
.auto_dvfs = true,
.dvfs_rail = &tegra124_dvfs_rail_vdd_cpu,
};
/* CPU DVFS tables */
static unsigned long cpu_max_freq[] = {
/* speedo_id 0 1 2 3 4 5 */
2014500, 2320500, 2116500, 2524500, 1811000, 2218500,
};
static struct cpu_dvfs cpu_fv_dvfs_table[] = {
{
.speedo_id = -1,
.process_id = -1,
.min_mv = 720,
.max_mv = 1260,
.fv_table = {
{204000, 800},
{306000, 800},
{408000, 800},
{510000, 800},
{612000, 800},
{714000, 800},
{816000, 820},
{918000, 840},
{1020000, 880},
{1122000, 900},
{1224000, 930},
{1326000, 960},
{1428000, 990},
{1530000, 1020},
{1632000, 1070},
{1734000, 1100},
{1836000, 1140},
{1938000, 1180},
{2014500, 1220},
{2116500, 1260},
{2218500, 1310},
{2320500, 1360},
{2422500, 1400},
{2524500, 1400},
},
},
};
/* Core DVFS tables */
static const int core_millivolts[MAX_DVFS_FREQS] = {
800, 850, 900, 950, 1000, 1050, 1100, 1150};
#define CORE_DVFS(_clk_name, _speedo_id, _process_id, _auto, _mult, _freqs...) \
{ \
.clk_name = _clk_name, \
.speedo_id = _speedo_id, \
.process_id = _process_id, \
.freqs = {_freqs}, \
.freqs_mult = _mult, \
.millivolts = core_millivolts, \
.auto_dvfs = _auto, \
.dvfs_rail = &tegra124_dvfs_rail_vdd_core, \
}
static struct dvfs core_dvfs_table[] = {
/* Core voltages (mV): 800, 850, 900, 950, 1000, 1050, 1100, 1150 */
/* Clock limits for internal blocks, PLLs */
CORE_DVFS("emc", -1, -1, 1, KHZ, 264000, 348000, 384000, 384000, 528000, 528000, 1200000, 1200000),
CORE_DVFS("cclk_lp", 0, 0, 1, KHZ, 312000, 528000, 660000, 804000, 912000, 1044000, 1044000, 1044000),
CORE_DVFS("cclk_lp", 0, 1, 1, KHZ, 312000, 564000, 696000, 828000, 960000, 1044000, 1044000, 1044000),
CORE_DVFS("cclk_lp", 1, -1, 1, KHZ, 312000, 564000, 696000, 828000, 960000, 1092000, 1092000, 1092000),
CORE_DVFS("sbus", 0, 0, 1, KHZ, 120000, 192000, 228000, 264000, 312000, 348000, 372000, 372000),
CORE_DVFS("sbus", 0, 1, 1, KHZ, 120000, 204000, 252000, 288000, 324000, 360000, 372000, 372000),
CORE_DVFS("sbus", 1, -1, 1, KHZ, 120000, 204000, 252000, 288000, 324000, 360000, 384000, 384000),
CORE_DVFS("vic03", 0, 0, 1, KHZ, 180000, 324000, 408000, 492000, 588000, 660000, 708000, 756000),
CORE_DVFS("vic03", 0, 1, 1, KHZ, 180000, 336000, 420000, 504000, 600000, 684000, 756000, 756000),
CORE_DVFS("vic03", 1, -1, 1, KHZ, 180000, 336000, 420000, 504000, 600000, 684000, 756000, 828000),
CORE_DVFS("tsec", 0, 0, 1, KHZ, 180000, 324000, 408000, 492000, 588000, 660000, 708000, 756000),
CORE_DVFS("tsec", 0, 1, 1, KHZ, 180000, 336000, 420000, 504000, 600000, 684000, 756000, 756000),
CORE_DVFS("tsec", 1, -1, 1, KHZ, 180000, 336000, 420000, 504000, 600000, 684000, 756000, 828000),
CORE_DVFS("msenc", 0, 0, 1, KHZ, 120000, 216000, 288000, 336000, 384000, 432000, 456000, 480000),
CORE_DVFS("msenc", 0, 1, 1, KHZ, 120000, 228000, 276000, 348000, 396000, 444000, 480000, 480000),
CORE_DVFS("msenc", 1, -1, 1, KHZ, 120000, 228000, 276000, 348000, 396000, 444000, 480000, 528000),
CORE_DVFS("se", 0, 0, 1, KHZ, 120000, 216000, 288000, 336000, 384000, 432000, 456000, 480000),
CORE_DVFS("se", 0, 1, 1, KHZ, 120000, 228000, 276000, 348000, 396000, 444000, 480000, 480000),
CORE_DVFS("se", 1, -1, 1, KHZ, 120000, 228000, 276000, 348000, 396000, 444000, 480000, 528000),
CORE_DVFS("vde", 0, 0, 1, KHZ, 120000, 216000, 288000, 336000, 384000, 432000, 456000, 480000),
CORE_DVFS("vde", 0, 1, 1, KHZ, 120000, 228000, 276000, 348000, 396000, 444000, 480000, 480000),
CORE_DVFS("vde", 1, -1, 1, KHZ, 120000, 228000, 276000, 348000, 396000, 444000, 480000, 528000),
CORE_DVFS("host1x", 0, 0, 1, KHZ, 108000, 156000, 204000, 240000, 348000, 372000, 408000, 408000),
CORE_DVFS("host1x", 0, 1, 1, KHZ, 108000, 156000, 204000, 252000, 348000, 384000, 408000, 408000),
CORE_DVFS("host1x", 1, -1, 1, KHZ, 108000, 156000, 204000, 252000, 348000, 384000, 444000, 444000),
CORE_DVFS("vi", 0, 0, 1, KHZ, 228000, 408000, 480000, 600000, 600000, 600000, 600000, 600000),
CORE_DVFS("vi", 0, 1, 1, KHZ, 228000, 420000, 480000, 600000, 600000, 600000, 600000, 600000),
CORE_DVFS("vi", 1, -1, 1, KHZ, 228000, 420000, 480000, 600000, 600000, 600000, 600000, 600000),
CORE_DVFS("isp", 0, 0, 1, KHZ, 228000, 408000, 480000, 600000, 600000, 600000, 600000, 600000),
CORE_DVFS("isp", 0, 1, 1, KHZ, 228000, 420000, 480000, 600000, 600000, 600000, 600000, 600000),
CORE_DVFS("isp", 1, -1, 1, KHZ, 228000, 420000, 480000, 600000, 600000, 600000, 600000, 600000),
CORE_DVFS("c2bus", 0, 0, 1, KHZ, 120000, 216000, 288000, 336000, 384000, 432000, 456000, 480000),
CORE_DVFS("c2bus", 0, 1, 1, KHZ, 120000, 228000, 276000, 348000, 396000, 444000, 480000, 480000),
CORE_DVFS("c2bus", 1, -1, 1, KHZ, 120000, 228000, 276000, 348000, 396000, 444000, 480000, 528000),
CORE_DVFS("c3bus", 0, 0, 1, KHZ, 180000, 324000, 408000, 492000, 588000, 660000, 708000, 756000),
CORE_DVFS("c3bus", 0, 1, 1, KHZ, 180000, 336000, 420000, 504000, 600000, 684000, 756000, 756000),
CORE_DVFS("c3bus", 1, -1, 1, KHZ, 180000, 336000, 420000, 504000, 600000, 684000, 756000, 828000),
CORE_DVFS("pll_m", -1, -1, 1, KHZ, 800000, 800000, 1066000, 1066000, 1066000, 1066000, 1200000, 1200000),
CORE_DVFS("pll_c", -1, -1, 1, KHZ, 800000, 800000, 1066000, 1066000, 1066000, 1066000, 1066000, 1066000),
CORE_DVFS("pll_c2", -1, -1, 1, KHZ, 800000, 800000, 1066000, 1066000, 1066000, 1066000, 1066000, 1066000),
CORE_DVFS("pll_c3", -1, -1, 1, KHZ, 800000, 800000, 1066000, 1066000, 1066000, 1066000, 1066000, 1066000),
/* Clock limits for I/O peripherals */
CORE_DVFS("sbc1", -1, -1, 1, KHZ, 33000, 33000, 33000, 33000, 33000, 33000, 51000, 51000),
CORE_DVFS("sbc2", -1, -1, 1, KHZ, 33000, 33000, 33000, 33000, 33000, 33000, 51000, 51000),
CORE_DVFS("sbc3", -1, -1, 1, KHZ, 33000, 33000, 33000, 33000, 33000, 33000, 51000, 51000),
CORE_DVFS("sbc4", -1, -1, 1, KHZ, 33000, 33000, 33000, 33000, 33000, 33000, 51000, 51000),
CORE_DVFS("sbc5", -1, -1, 1, KHZ, 33000, 33000, 33000, 33000, 33000, 33000, 51000, 51000),
CORE_DVFS("sbc6", -1, -1, 1, KHZ, 33000, 33000, 33000, 33000, 33000, 33000, 51000, 51000),
CORE_DVFS("sdmmc1", -1, -1, 1, KHZ, 1, 1, 82000, 82000, 136000, 136000, 136000, 204000),
CORE_DVFS("sdmmc3", -1, -1, 1, KHZ, 1, 1, 82000, 82000, 136000, 136000, 136000, 204000),
CORE_DVFS("sdmmc4", -1, -1, 1, KHZ, 1, 1, 82000, 82000, 136000, 136000, 136000, 204000),
CORE_DVFS("hdmi", -1, -1, 1, KHZ, 1, 148500, 148500, 297000, 297000, 297000, 297000, 297000),
CORE_DVFS("nor", -1, -1, 1, KHZ, 102000, 102000, 102000, 102000, 102000, 102000, 102000, 102000),
CORE_DVFS("pciex", -1, -1, 1, KHZ, 1, 250000, 250000, 500000, 500000, 500000, 500000, 500000),
CORE_DVFS("mselect", -1, -1, 1, KHZ, 102000, 102000, 204000, 204000, 204000, 204000, 408000, 408000),
CORE_DVFS("xusb_falcon_src", -1, -1, 1, KHZ, 1, 336000, 336000, 336000, 336000, 336000, 336000, 336000),
CORE_DVFS("xusb_host_src", -1, -1, 1, KHZ, 1, 112000, 112000, 112000, 112000, 112000, 112000, 112000),
CORE_DVFS("xusb_dev_src", -1, -1, 1, KHZ, 1, 58300, 58300, 58300, 112000, 112000, 112000, 112000),
CORE_DVFS("xusb_ss_src", -1, -1, 1, KHZ, 1, 120000, 120000, 120000, 120000, 120000, 120000, 120000),
CORE_DVFS("xusb_fs_src", -1, -1, 1, KHZ, 1, 48000, 48000, 48000, 48000, 48000, 48000, 48000),
CORE_DVFS("xusb_hs_src", -1, -1, 1, KHZ, 1, 60000, 60000, 60000, 60000, 60000, 60000, 60000),
CORE_DVFS("hda", -1, -1, 1, KHZ, 1, 108000, 108000, 108000, 108000, 108000, 108000, 108000),
CORE_DVFS("hda2codec_2x", -1, -1, 1, KHZ, 1, 48000, 48000, 48000, 48000, 48000, 48000, 48000),
CORE_DVFS("sor0", -1, -1, 1, KHZ, 162000, 270000, 540000, 540000, 540000, 540000, 540000, 540000),
/*
* The clock rate for the display controllers that determines the
* necessary core voltage depends on a divider that is internal
* to the display block. Disable auto-dvfs on the display clocks,
* and let the display driver call tegra_dvfs_set_rate manually
*/
CORE_DVFS("disp1", 0, 0, 0, KHZ, 148500, 241000, 297000, 297000, 297000, 474000, 474000, 474000),
CORE_DVFS("disp1", 0, 1, 0, KHZ, 148500, 241000, 297000, 297000, 474000, 474000, 474000, 474000),
CORE_DVFS("disp1", 1, -1, 0, KHZ, 148500, 241000, 297000, 297000, 474000, 474000, 474000, 533000),
CORE_DVFS("disp2", 0, 0, 0, KHZ, 148500, 241000, 297000, 297000, 297000, 474000, 474000, 474000),
CORE_DVFS("disp2", 0, 1, 0, KHZ, 148500, 241000, 297000, 297000, 474000, 474000, 474000, 474000),
CORE_DVFS("disp2", 1, -1, 0, KHZ, 148500, 241000, 297000, 297000, 474000, 474000, 474000, 533000),
};
int tegra_dvfs_disable_core_set(const char *arg, const struct kernel_param *kp)
{
int ret;
ret = param_set_bool(arg, kp);
if (ret)
return ret;
if (tegra_dvfs_core_disabled)
tegra_dvfs_rail_disable(&tegra124_dvfs_rail_vdd_core);
else
tegra_dvfs_rail_enable(&tegra124_dvfs_rail_vdd_core);
return 0;
}
int tegra_dvfs_disable_cpu_set(const char *arg, const struct kernel_param *kp)
{
int ret;
ret = param_set_bool(arg, kp);
if (ret)
return ret;
if (tegra_dvfs_cpu_disabled)
tegra_dvfs_rail_disable(&tegra124_dvfs_rail_vdd_cpu);
else
tegra_dvfs_rail_enable(&tegra124_dvfs_rail_vdd_cpu);
return 0;
}
int tegra_dvfs_disable_get(char *buffer, const struct kernel_param *kp)
{
return param_get_bool(buffer, kp);
}
static struct kernel_param_ops tegra_dvfs_disable_core_ops = {
.set = tegra_dvfs_disable_core_set,
.get = tegra_dvfs_disable_get,
};
static struct kernel_param_ops tegra_dvfs_disable_cpu_ops = {
.set = tegra_dvfs_disable_cpu_set,
.get = tegra_dvfs_disable_get,
};
module_param_cb(disable_core, &tegra_dvfs_disable_core_ops,
&tegra_dvfs_core_disabled, 0644);
module_param_cb(disable_cpu, &tegra_dvfs_disable_cpu_ops,
&tegra_dvfs_cpu_disabled, 0644);
static void init_dvfs_one(struct dvfs *d, int max_freq_index)
{
int ret;
struct clk *c = clk_get_sys(d->clk_name, d->clk_name);
if (IS_ERR(c)) {
pr_debug("tegra124_dvfs: no clock found for %s\n",
d->clk_name);
return;
}
d->max_millivolts = d->dvfs_rail->nominal_millivolts;
ret = tegra_setup_dvfs(c, d);
if (ret)
pr_err("tegra124_dvfs: failed to enable dvfs on %s\n",
__clk_get_name(c));
}
static bool match_dvfs_one(const char *name,
int dvfs_speedo_id, int dvfs_process_id,
int speedo_id, int process_id)
{
if ((dvfs_process_id != -1 && dvfs_process_id != process_id) ||
(dvfs_speedo_id != -1 && dvfs_speedo_id != speedo_id)) {
pr_debug("tegra124_dvfs: rejected %s speedo %d, process %d\n",
name, dvfs_speedo_id, dvfs_process_id);
return false;
}
return true;
}
static int round_voltage(int mv, struct rail_alignment *align, bool up)
{
if (align->step_uv) {
int uv = max(mv * 1000, align->offset_uv) - align->offset_uv;
uv = (uv + (up ? align->step_uv - 1 : 0)) / align->step_uv;
return (uv * align->step_uv + align->offset_uv) / 1000;
}
return mv;
}
static int set_cpu_dvfs_data(unsigned long max_freq,
struct cpu_dvfs *d, struct dvfs *cpu_dvfs, int *max_freq_index)
{
int i, mv, dfll_mv, min_dfll_mv, num_freqs;
unsigned long fmax_at_vmin = 0;
unsigned long fmin_use_dfll = 0;
unsigned long *freqs;
int *dfll_millivolts;
struct rail_alignment *align = &tegra124_dvfs_rail_vdd_cpu.alignment;
min_dfll_mv = d->min_mv;
min_dfll_mv = round_voltage(min_dfll_mv, align, true);
d->max_mv = round_voltage(d->max_mv, align, false);
BUG_ON(min_dfll_mv < tegra124_dvfs_rail_vdd_cpu.min_millivolts);
if (tegra124_dfll_get_fv_table(&num_freqs, &freqs, &dfll_millivolts))
return -EPROBE_DEFER;
for (i = 0; i < num_freqs; i++) {
if (freqs[i] != d->fv_table[i].freq * 1000) {
pr_err("Err: DFLL freq ladder does not match PLL's\n");
return -EINVAL;
}
if (d->fv_table[i].freq > max_freq)
break;
mv = d->fv_table[i].volt;
/*
* Check maximum frequency at minimum voltage for dfll source;
* round down unless all table entries are above Vmin, then use
* the 1st entry as is.
*/
dfll_mv = max(dfll_millivolts[i], min_dfll_mv);
if (dfll_mv > min_dfll_mv) {
if (!i)
fmax_at_vmin = freqs[i];
if (!fmax_at_vmin)
fmax_at_vmin = freqs[i - 1];
}
/* Clip maximum frequency at maximum voltage for pll source */
if ((mv > d->max_mv) && !i) {
pr_err("Err: volt of 1st entry is higher than Vmax\n");
return -EINVAL;
}
/* Minimum rate with pll source voltage above dfll Vmin */
if ((mv >= min_dfll_mv) && !fmin_use_dfll)
fmin_use_dfll = freqs[i];
/* fill in dvfs tables */
cpu_dvfs->freqs[i] = freqs[i];
cpu_millivolts[i] = mv;
cpu_dfll_millivolts[i] = min(dfll_mv, d->max_mv);
}
/*
* In the dfll operating range dfll voltage at any rate should be
* better (below) than pll voltage
*/
if (!fmin_use_dfll || (fmin_use_dfll > fmax_at_vmin))
fmin_use_dfll = fmax_at_vmin;
/* dvfs tables are successfully populated - fill in the rest */
cpu_dvfs->speedo_id = d->speedo_id;
cpu_dvfs->process_id = d->process_id;
cpu_dvfs->dvfs_rail->nominal_millivolts = min(d->max_mv,
max(cpu_millivolts[i - 1], cpu_dfll_millivolts[i - 1]));
*max_freq_index = i - 1;
cpu_dvfs->use_dfll_rate_min = fmin_use_dfll;
return 0;
}
static int get_core_nominal_mv_index(int speedo_id)
{
int i;
int mv = tegra124_get_core_speedo_mv();
if (mv < 0) {
pr_err("Can't get Tegra124 speedo mv\n");
return -EINVAL;
}
/* Round nominal level down to the nearest core scaling step */
for (i = 0; i < MAX_DVFS_FREQS; i++) {
if ((core_millivolts[i] == 0) || (mv < core_millivolts[i]))
break;
}
if (i == 0) {
pr_err("tegra124-dvfs: failed to get nominal idx at volt %d\n",
mv);
return -ENOSYS;
}
return i - 1;
}
static void adjust_emc_dvfs_table(struct dvfs *d)
{
unsigned long rate;
int i;
for (i = 0; i < ARRAY_SIZE(core_millivolts); i++) {
if (core_millivolts[i] == 0)
return;
rate = tegra124_predict_emc_rate(core_millivolts[i]);
if (rate < 0)
return;
d->freqs[i] = rate;
}
}
int tegra124_init_dvfs(void)
{
int cpu_speedo_id = tegra_get_cpu_speedo_id();
int cpu_process_id = tegra_get_cpu_process_id();
int soc_speedo_id = tegra_get_soc_speedo_id();
int core_process_id = tegra_get_core_process_id();
int i, ret;
int core_nominal_mv_index;
int cpu_max_freq_index = 0;
/*
* Find nominal voltages for core (1st) and cpu rails before rail
* init. Nominal voltage index in core scaling ladder can also be
* used to determine max dvfs frequencies for all core clocks. In
* case of error disable core scaling and set index to 0, so that
* core clocks would not exceed rates allowed at minimum voltage.
*/
core_nominal_mv_index = get_core_nominal_mv_index(soc_speedo_id);
if (core_nominal_mv_index < 0) {
tegra124_dvfs_rail_vdd_core.disabled = true;
tegra_dvfs_core_disabled = true;
core_nominal_mv_index = 0;
}
tegra124_dvfs_rail_vdd_core.nominal_millivolts =
core_millivolts[core_nominal_mv_index];
/*
* Setup cpu dvfs and dfll tables from cvb data, determine nominal
* voltage for cpu rail, and cpu maximum frequency. Note that entire
* frequency range is guaranteed only when dfll is used as cpu clock
* source. Reaching maximum frequency with pll as cpu clock source
* may not be possible within nominal voltage range (dvfs mechanism
* would automatically fail frequency request in this case, so that
* voltage limit is not violated). Error when cpu dvfs table can not
* be constructed must never happen.
*/
BUG_ON(cpu_speedo_id >= ARRAY_SIZE(cpu_max_freq));
for (ret = 0, i = 0; i < ARRAY_SIZE(cpu_fv_dvfs_table); i++) {
struct cpu_dvfs *d = &cpu_fv_dvfs_table[i];
unsigned long max_freq = cpu_max_freq[cpu_speedo_id];
if (match_dvfs_one("cpu dvfs", d->speedo_id, d->process_id,
cpu_speedo_id, cpu_process_id)) {
ret = set_cpu_dvfs_data(max_freq,
d, &cpu_dvfs, &cpu_max_freq_index);
if (ret)
goto out;
break;
}
}
BUG_ON(i == ARRAY_SIZE(cpu_fv_dvfs_table));
/* Init core thermal profile */
tegra_dvfs_rail_init_vmin_thermal_profile(vdd_core_therm_trips_table,
vdd_core_therm_floors_table, &tegra124_dvfs_rail_vdd_core);
/* Init rail structures and dependencies */
tegra_dvfs_init_rails(tegra124_dvfs_rails,
ARRAY_SIZE(tegra124_dvfs_rails));
/*
* Search core dvfs table for speedo/process matching entries and
* initialize dvfs-ed clocks
*/
for (i = 0; i < ARRAY_SIZE(core_dvfs_table); i++) {
struct dvfs *d = &core_dvfs_table[i];
if (!match_dvfs_one(d->clk_name, d->speedo_id,
d->process_id, soc_speedo_id, core_process_id))
continue;
init_dvfs_one(d, core_nominal_mv_index);
/*
* EMC dvfs is board dependent, the EMC scaling frequencies are
* determined by the Tegra BCT and the board specific EMC DFS
* table owned by EMC driver.
*/
if (!strcmp(d->clk_name, "emc") && tegra124_emc_is_ready())
adjust_emc_dvfs_table(d);
}
/*
* Initialize matching cpu dvfs entry already found when nominal
* voltage was determined
*/
init_dvfs_one(&cpu_dvfs, cpu_max_freq_index);
pr_info("tegra dvfs: VDD_CPU nominal %dmV, scaling %s\n",
tegra124_dvfs_rail_vdd_cpu.nominal_millivolts,
tegra_dvfs_cpu_disabled ? "disabled" : "enabled");
pr_info("tegra dvfs: VDD_CORE nominal %dmV, scaling %s\n",
tegra124_dvfs_rail_vdd_core.nominal_millivolts,
tegra_dvfs_core_disabled ? "disabled" : "enabled");
return 0;
out:
return ret;
}

View File

@@ -0,0 +1,123 @@
/*
* Copyright (c) 2011-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/cpufreq.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_platform.h>
#include <linux/platform_data/tegra_edp.h>
#include <linux/tegra-soc.h>
#include "tegra_edp_private.h"
static const int tegra124_leakage_ijk_common[4][4][4] = {
/* i = 0 */
{ { -309609464, 197786326, -40763150, 1613941, },
{ 964716269, -569081375, 115781607, -4206296, },
{ -994324790, 529664031, -106360108, 3454033, },
{ 343209442, -160577505, 31928605, -895157, },
},
/* i = 1 */
{ { 616319664, -637007187, 137759592, -7194133, },
{ -1853817283, 1896032851, -407407611, 20868220, },
{ 1824097131, -1831611624, 390753403, -19530122, },
{ -589155245, 578838526, -122655676, 5985577, },
},
/* i = 2 */
{ { -439994037, 455845250, -104097013, 6191899, },
{ 1354650774, -1395561938, 318665647, -18886906, },
{ -1361677255, 1390149678, -317474532, 18728266, },
{ 447877887, -451382027, 103201434, -6046692, },
},
/* i = 3 */
{ { 56797556, -59779544, 13810295, -848290, },
{ -175867301, 184753957, -42708242, 2621537, },
{ 177626357, -185996541, 43029384, -2638283, },
{ -58587547, 61075322, -14145853, 865351, },
},
};
#define TEGRA124_EDP_PARAMS_COMMON_PART \
.temp_scaled = 10, \
.dyn_scaled = 1000, \
.dyn_consts_n = { 950, 1399, 2166, 3041 }, \
.consts_scaled = 100, \
.leakage_consts_n = { 45, 67, 87, 100 }, \
.ijk_scaled = 100000, \
.leakage_min = 30, \
.volt_temp_cap = { 70, 1240 }, \
.leakage_consts_ijk = tegra124_leakage_ijk_common
static const struct tegra_edp_cpu_leakage_params tegra124_leakage_params[] = {
{
.cpu_speedo_id = 0, /* Engg SKU */
TEGRA124_EDP_PARAMS_COMMON_PART,
},
{
.cpu_speedo_id = 1, /* Prod SKU */
TEGRA124_EDP_PARAMS_COMMON_PART,
},
{
.cpu_speedo_id = 2, /* Prod SKU */
TEGRA124_EDP_PARAMS_COMMON_PART,
},
{
.cpu_speedo_id = 3, /* Prod SKU */
TEGRA124_EDP_PARAMS_COMMON_PART,
},
{
.cpu_speedo_id = 5, /* Prod SKU */
TEGRA124_EDP_PARAMS_COMMON_PART,
},
};
static unsigned int tegra124_edp_get_max_cpu_freq(void)
{
struct cpufreq_frequency_table *table = cpufreq_frequency_get_table(0);
unsigned int freq = 0;
int i;
for (i = 0; table[i].frequency != CPUFREQ_TABLE_END; i++) {
if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
continue;
if (table[i].frequency > freq)
freq = table[i].frequency;
}
return freq;
}
int tegra124_cpu_edp_init(struct device_node *cpu_edp_dn)
{
struct platform_device *pdev;
struct tegra_edp *edp;
pdev = of_find_device_by_node(cpu_edp_dn);
if (!pdev) {
dev_err(&pdev->dev, "Failed to get cpu edp device\n");
return -EINVAL;
}
edp = dev_get_drvdata(&pdev->dev);
edp->params = tegra124_leakage_params;
edp->params_size = ARRAY_SIZE(tegra124_leakage_params);
edp->max_cpu_freq = tegra124_edp_get_max_cpu_freq();
return 0;
}

View File

@@ -0,0 +1,347 @@
/*
* Copyright (C) 2011 Google, Inc.
*
* Author:
* Colin Cross <ccross@android.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/kernel.h>
#include <linux/device.h>
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/platform_data/tegra_emc.h>
#include <linux/tegra-soc.h>
#include "tegra2_emc.h"
#ifdef CONFIG_TEGRA_EMC_SCALING_ENABLE
static bool emc_enable = true;
#else
static bool emc_enable;
#endif
module_param(emc_enable, bool, 0644);
static struct platform_device *emc_pdev;
static void __iomem *emc_regbase;
static inline void emc_writel(u32 val, unsigned long addr)
{
writel(val, emc_regbase + addr);
}
static inline u32 emc_readl(unsigned long addr)
{
return readl(emc_regbase + addr);
}
static const unsigned long emc_reg_addr[TEGRA_EMC_NUM_REGS] = {
0x2c, /* RC */
0x30, /* RFC */
0x34, /* RAS */
0x38, /* RP */
0x3c, /* R2W */
0x40, /* W2R */
0x44, /* R2P */
0x48, /* W2P */
0x4c, /* RD_RCD */
0x50, /* WR_RCD */
0x54, /* RRD */
0x58, /* REXT */
0x5c, /* WDV */
0x60, /* QUSE */
0x64, /* QRST */
0x68, /* QSAFE */
0x6c, /* RDV */
0x70, /* REFRESH */
0x74, /* BURST_REFRESH_NUM */
0x78, /* PDEX2WR */
0x7c, /* PDEX2RD */
0x80, /* PCHG2PDEN */
0x84, /* ACT2PDEN */
0x88, /* AR2PDEN */
0x8c, /* RW2PDEN */
0x90, /* TXSR */
0x94, /* TCKE */
0x98, /* TFAW */
0x9c, /* TRPAB */
0xa0, /* TCLKSTABLE */
0xa4, /* TCLKSTOP */
0xa8, /* TREFBW */
0xac, /* QUSE_EXTRA */
0x114, /* FBIO_CFG6 */
0xb0, /* ODT_WRITE */
0xb4, /* ODT_READ */
0x104, /* FBIO_CFG5 */
0x2bc, /* CFG_DIG_DLL */
0x2c0, /* DLL_XFORM_DQS */
0x2c4, /* DLL_XFORM_QUSE */
0x2e0, /* ZCAL_REF_CNT */
0x2e4, /* ZCAL_WAIT_CNT */
0x2a8, /* AUTO_CAL_INTERVAL */
0x2d0, /* CFG_CLKTRIM_0 */
0x2d4, /* CFG_CLKTRIM_1 */
0x2d8, /* CFG_CLKTRIM_2 */
};
/* Select the closest EMC rate that is higher than the requested rate */
long tegra_emc_round_rate(unsigned long rate)
{
struct tegra_emc_pdata *pdata;
int i;
int best = -1;
unsigned long distance = ULONG_MAX;
if (!emc_pdev)
return -EINVAL;
pdata = emc_pdev->dev.platform_data;
pr_debug("%s: %lu\n", __func__, rate);
/*
* The EMC clock rate is twice the bus rate, and the bus rate is
* measured in kHz
*/
rate = rate / 2 / 1000;
for (i = 0; i < pdata->num_tables; i++) {
if (pdata->tables[i].rate >= rate &&
(pdata->tables[i].rate - rate) < distance) {
distance = pdata->tables[i].rate - rate;
best = i;
}
}
if (best < 0)
return -EINVAL;
pr_debug("%s: using %lu\n", __func__, pdata->tables[best].rate);
return pdata->tables[best].rate * 2 * 1000;
}
/*
* The EMC registers have shadow registers. When the EMC clock is updated
* in the clock controller, the shadow registers are copied to the active
* registers, allowing glitchless memory bus frequency changes.
* This function updates the shadow registers for a new clock frequency,
* and relies on the clock lock on the emc clock to avoid races between
* multiple frequency changes
*/
int tegra_emc_set_rate(unsigned long rate)
{
struct tegra_emc_pdata *pdata;
int i;
int j;
if (!emc_pdev)
return -EINVAL;
pdata = emc_pdev->dev.platform_data;
/*
* The EMC clock rate is twice the bus rate, and the bus rate is
* measured in kHz
*/
rate = rate / 2 / 1000;
for (i = 0; i < pdata->num_tables; i++)
if (pdata->tables[i].rate == rate)
break;
if (i >= pdata->num_tables)
return -EINVAL;
pr_debug("%s: setting to %lu\n", __func__, rate);
for (j = 0; j < TEGRA_EMC_NUM_REGS; j++)
emc_writel(pdata->tables[i].regs[j], emc_reg_addr[j]);
emc_readl(pdata->tables[i].regs[TEGRA_EMC_NUM_REGS - 1]);
return 0;
}
#ifdef CONFIG_OF
static struct device_node *tegra_emc_ramcode_devnode(struct device_node *np)
{
struct device_node *iter;
u32 reg;
for_each_child_of_node(np, iter) {
if (of_property_read_u32(np, "nvidia,ram-code", &reg))
continue;
if (reg == tegra_bct_strapping)
return of_node_get(iter);
}
return NULL;
}
static struct tegra_emc_pdata *tegra_emc_dt_parse_pdata(
struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
struct device_node *tnp, *iter;
struct tegra_emc_pdata *pdata;
int ret, i, num_tables;
if (!np)
return NULL;
if (of_find_property(np, "nvidia,use-ram-code", NULL)) {
tnp = tegra_emc_ramcode_devnode(np);
if (!tnp)
dev_warn(&pdev->dev,
"can't find emc table for ram-code 0x%02x\n",
tegra_bct_strapping);
} else
tnp = of_node_get(np);
if (!tnp)
return NULL;
num_tables = 0;
for_each_child_of_node(tnp, iter)
if (of_device_is_compatible(iter, "nvidia,tegra20-emc-table"))
num_tables++;
if (!num_tables) {
pdata = NULL;
goto out;
}
pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
pdata->tables = devm_kzalloc(&pdev->dev,
sizeof(*pdata->tables) * num_tables,
GFP_KERNEL);
i = 0;
for_each_child_of_node(tnp, iter) {
u32 prop;
ret = of_property_read_u32(iter, "clock-frequency", &prop);
if (ret) {
dev_err(&pdev->dev, "no clock-frequency in %s\n",
iter->full_name);
continue;
}
pdata->tables[i].rate = prop;
ret = of_property_read_u32_array(iter, "nvidia,emc-registers",
pdata->tables[i].regs,
TEGRA_EMC_NUM_REGS);
if (ret) {
dev_err(&pdev->dev,
"malformed emc-registers property in %s\n",
iter->full_name);
continue;
}
i++;
}
pdata->num_tables = i;
out:
of_node_put(tnp);
return pdata;
}
#else
static struct tegra_emc_pdata *tegra_emc_dt_parse_pdata(
struct platform_device *pdev)
{
return NULL;
}
#endif
static struct tegra_emc_pdata *tegra_emc_fill_pdata(struct platform_device *pdev)
{
struct clk *c = clk_get_sys(NULL, "emc");
struct tegra_emc_pdata *pdata;
unsigned long khz;
int i;
WARN_ON(pdev->dev.platform_data);
BUG_ON(IS_ERR(c));
pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
pdata->tables = devm_kzalloc(&pdev->dev, sizeof(*pdata->tables),
GFP_KERNEL);
pdata->tables[0].rate = clk_get_rate(c) / 2 / 1000;
for (i = 0; i < TEGRA_EMC_NUM_REGS; i++)
pdata->tables[0].regs[i] = emc_readl(emc_reg_addr[i]);
pdata->num_tables = 1;
khz = pdata->tables[0].rate;
dev_info(&pdev->dev, "no tables provided, using %ld kHz emc, "
"%ld kHz mem\n", khz * 2, khz);
return pdata;
}
static int tegra_emc_probe(struct platform_device *pdev)
{
struct tegra_emc_pdata *pdata;
struct resource *res;
if (!emc_enable) {
dev_err(&pdev->dev, "disabled per module parameter\n");
return -ENODEV;
}
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
emc_regbase = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(emc_regbase))
return PTR_ERR(emc_regbase);
pdata = pdev->dev.platform_data;
if (!pdata)
pdata = tegra_emc_dt_parse_pdata(pdev);
if (!pdata)
pdata = tegra_emc_fill_pdata(pdev);
pdev->dev.platform_data = pdata;
emc_pdev = pdev;
return 0;
}
static struct of_device_id tegra_emc_of_match[] = {
{ .compatible = "nvidia,tegra20-emc", },
{ },
};
static struct platform_driver tegra_emc_driver = {
.driver = {
.name = "tegra-emc",
.owner = THIS_MODULE,
.of_match_table = tegra_emc_of_match,
},
.probe = tegra_emc_probe,
};
static int __init tegra_emc_init(void)
{
return platform_driver_register(&tegra_emc_driver);
}
device_initcall(tegra_emc_init);

View File

@@ -0,0 +1,24 @@
/*
* Copyright (C) 2011 Google, Inc.
*
* Author:
* Colin Cross <ccross@android.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_TEGRA2_EMC_H_
#define __MACH_TEGRA_TEGRA2_EMC_H
int tegra_emc_set_rate(unsigned long rate);
long tegra_emc_round_rate(unsigned long rate);
#endif

View File

@@ -0,0 +1,943 @@
/*
* Copyright (C) 2011-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.
*
* 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., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307, USA
*/
#include <linux/kernel.h>
#include <linux/seq_file.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/uaccess.h>
#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <linux/cpu.h>
#include <linux/cpufreq.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/suspend.h>
#include <linux/tegra-soc.h>
#include <linux/tegra-dvfs.h>
#include <linux/platform_data/tegra_edp.h>
#include "tegra_edp_private.h"
#define KHZ 1000
#define FREQ_STEP_KHZ 12750
#define OVERRIDE_DEFAULT 6000
/*
* "Safe entry" to be used when no match for speedo_id /
* regulator_cur is found; must be the last one
*/
static struct tegra_edp_limits edp_default_limits[] = {
{85, {1000000, 1000000, 1000000, 1000000} },
};
/* Constants for EDP calculations */
static const int temperatures[] = { /* degree celcius (C) */
23, 40, 50, 60, 70, 74, 78, 82, 86, 90, 94, 98, 102,
};
static DEFINE_MUTEX(tegra_cpu_lock);
static struct tegra_edp cpu_edp;
static inline s64 edp_pow(s64 val, int pwr)
{
s64 retval = 1;
while (pwr) {
if (pwr & 1)
retval *= val;
pwr >>= 1;
if (pwr)
val *= val;
}
return retval;
}
static int cal_leakage_ma(const struct tegra_edp_cpu_leakage_params *params,
int iddq_ma, unsigned int voltage_mv, int temp_c)
{
int i, j, k;
s64 leakage_ma, leakage_calc_step;
/* Calculate leakage current */
leakage_ma = 0;
for (i = 0; i <= 3; i++) {
for (j = 0; j <= 3; j++) {
for (k = 0; k <= 3; k++) {
unsigned int scaled;
int ijk;
ijk = params->leakage_consts_ijk[i][j][k];
leakage_calc_step = ijk * edp_pow(iddq_ma, i);
/* Convert (mA)^i to (A)^i */
leakage_calc_step = div64_s64(leakage_calc_step,
edp_pow(1000, i));
leakage_calc_step *= edp_pow(voltage_mv, j);
/* Convert (mV)^j to (V)^j */
leakage_calc_step = div64_s64(leakage_calc_step,
edp_pow(1000, j));
leakage_calc_step *= edp_pow(temp_c, k);
/* Convert (C)^k to (scaled_C)^k */
scaled = params->temp_scaled;
leakage_calc_step = div64_s64(leakage_calc_step,
edp_pow(scaled, k));
/* leakage_consts_ijk was scaled */
leakage_calc_step = div64_s64(leakage_calc_step,
params->ijk_scaled);
leakage_ma += leakage_calc_step;
}
}
}
return leakage_ma;
}
/*
* Find the maximum frequency that results in dynamic and leakage current that
* is less than the regulator current limit.
* temp_c - valid or -EINVAL
* power_mw - valid or -1 (infinite) or -EINVAL
*/
static int edp_calculate_maxf(struct device *dev, int temp_idx, int power_mw,
int iddq_ma, int n_cores_idx,
struct tegra_edp_freq_vol_table *freq_voltage_lut,
unsigned int freq_voltage_lut_size)
{
struct tegra_edp *edp = dev_get_drvdata(dev);
const struct tegra_edp_cpu_leakage_params *params;
int temp_c = edp->temps[temp_idx];
unsigned int voltage_mv, freq_khz;
unsigned int cur_effective;
int f;
s64 leakage_ma, dyn_ma;
s64 leakage_mw, dyn_mw;
params = &edp->params[edp->cpu_speedo_idx];
cur_effective = edp->regulator_cur - edp->edp_reg_override_ma;
for (f = freq_voltage_lut_size - 1; f >= 0; f--) {
freq_khz = freq_voltage_lut[f].freq / KHZ;
voltage_mv = freq_voltage_lut[f].voltage_mv;
/* Constrain Volt-Temp */
if (params->volt_temp_cap.temperature &&
temp_c > params->volt_temp_cap.temperature &&
params->volt_temp_cap.voltage_limit_mv &&
voltage_mv > params->volt_temp_cap.voltage_limit_mv)
continue;
/* Calculate leakage current */
leakage_ma = cal_leakage_ma(params,
iddq_ma, voltage_mv, temp_c);
/* if specified, set floor for leakage current */
if (params->leakage_min && leakage_ma <= params->leakage_min)
leakage_ma = params->leakage_min;
/* leakage cannot be negative => leakage model has error */
if (leakage_ma <= 0) {
dev_err(dev,
"VDD_CPU EDP failed: IDDQ too high (%d mA)\n",
iddq_ma);
return -EINVAL;
}
leakage_ma *= params->leakage_consts_n[n_cores_idx];
/* leakage_const_n was scaled */
leakage_ma = div64_s64(leakage_ma, params->consts_scaled);
/* Calculate dynamic current */
dyn_ma = voltage_mv * freq_khz / 1000;
/* Convert mV to V */
dyn_ma = div64_s64(dyn_ma, 1000);
dyn_ma *= params->dyn_consts_n[n_cores_idx];
/* dyn_const_n was scaled */
dyn_ma = div64_s64(dyn_ma, params->dyn_scaled);
if (power_mw != -1) {
leakage_mw = leakage_ma * voltage_mv;
dyn_mw = dyn_ma * voltage_mv;
if (div64_s64(leakage_mw + dyn_mw, 1000) <= power_mw)
return freq_khz;
} else if ((leakage_ma + dyn_ma) <= cur_effective) {
return freq_khz;
}
}
return -EINVAL;
}
static int edp_relate_freq_voltage(struct device *dev, struct clk *clk_cpu_g,
unsigned int cpu_speedo_idx,
struct tegra_edp_freq_vol_table *lut,
unsigned int lut_size)
{
unsigned int i, freq;
int voltage_mv;
for (i = 0, freq = 0; i < lut_size; i++, freq += FREQ_STEP_KHZ) {
/* Predict voltages */
voltage_mv = tegra_dvfs_predict_millivolts(clk_cpu_g,
freq * KHZ);
if (voltage_mv < 0) {
dev_err(dev,
"Couldn't predict voltage: freq %u; err %d\n",
freq, voltage_mv);
return -EINVAL;
}
/* Cache frequency / voltage / voltage constant relationship */
lut[i].freq = freq * KHZ;
lut[i].voltage_mv = voltage_mv;
}
return 0;
}
static int edp_find_speedo_idx(struct device *dev, int cpu_speedo_id)
{
struct tegra_edp *edp = dev_get_drvdata(dev);
int i;
for (i = 0; i < edp->params_size; i++)
if (cpu_speedo_id == edp->params[i].cpu_speedo_id)
return i;
dev_err(dev, "Couldn't find cpu speedo id %d in freq/voltage LUT\n",
cpu_speedo_id);
return -EINVAL;
}
static int tegra_cpu_edp_cal_limits(struct device *dev)
{
struct tegra_edp *edp = dev_get_drvdata(dev);
unsigned int temp_idx, n_cores_idx;
unsigned int cpu_g_minf, cpu_g_maxf;
unsigned int cpu_speedo_idx;
unsigned int cap, limit;
struct tegra_edp_limits *edp_calculated_limits;
const struct tegra_edp_cpu_leakage_params *params;
int cpu_speedo_id, iddq_ma;
size_t size;
int ret;
struct clk *clk_cpu_g = clk_get_sys(NULL, "cclk_g");
struct tegra_edp_freq_vol_table *freq_voltage_lut;
unsigned int freq_voltage_lut_size;
/* Determine all inputs to EDP formula */
cpu_speedo_id = tegra_get_cpu_speedo_id();
iddq_ma = tegra_get_cpu_iddq_value();
cpu_speedo_idx = edp_find_speedo_idx(dev, cpu_speedo_id);
if (cpu_speedo_idx < 0)
return -EINVAL;
edp->cpu_speedo_idx = cpu_speedo_idx;
params = &edp->params[cpu_speedo_idx];
cpu_g_minf = 0;
cpu_g_maxf = edp->max_cpu_freq;
freq_voltage_lut_size = (cpu_g_maxf - cpu_g_minf) / FREQ_STEP_KHZ + 1;
size = sizeof(struct tegra_edp_freq_vol_table) * freq_voltage_lut_size;
freq_voltage_lut = kzalloc(size, GFP_KERNEL);
if (!freq_voltage_lut) {
dev_err(dev, "Failed to alloc mem for freq/voltage LUT\n");
return -ENOMEM;
}
ret = edp_relate_freq_voltage(dev, clk_cpu_g, cpu_speedo_idx,
freq_voltage_lut,
freq_voltage_lut_size);
if (ret)
goto err_free_lut;
size = sizeof(struct tegra_edp_limits) * edp->temps_size;
edp_calculated_limits = devm_kzalloc(dev, size, GFP_KERNEL);
if (!edp_calculated_limits) {
dev_err(dev, "Failed to alloc mem for edp calculatd limits\n");
ret = -ENOMEM;
goto err_free_lut;
}
/* Calculate EDP table */
for (n_cores_idx = 0;
n_cores_idx < num_possible_cpus(); n_cores_idx++) {
for (temp_idx = 0;
temp_idx < edp->temps_size; temp_idx++) {
edp_calculated_limits[temp_idx].temperature =
edp->temps[temp_idx];
limit = edp_calculate_maxf(dev,
temp_idx,
-1,
iddq_ma,
n_cores_idx,
freq_voltage_lut,
freq_voltage_lut_size);
if (limit == -EINVAL) {
ret = -EINVAL;
goto err_free_limits;
}
/* apply safety cap if it is specified */
if (n_cores_idx < 4) {
cap = params->safety_cap[n_cores_idx];
if (cap && cap < limit)
limit = cap;
}
edp_calculated_limits[temp_idx].
freq_limits[n_cores_idx] = limit;
}
}
/*
* If this is an EDP table update, need to overwrite old table.
* The old table's address must remain valid.
*/
if (edp->edp_limits != edp->def_edp_limits) {
memcpy(edp->edp_limits, edp_calculated_limits, size);
devm_kfree(dev, edp_calculated_limits);
} else {
edp->edp_limits = edp_calculated_limits;
edp->edp_limits_size = edp->temps_size;
}
kfree(freq_voltage_lut);
return 0;
err_free_limits:
devm_kfree(dev, edp_calculated_limits);
err_free_lut:
kfree(freq_voltage_lut);
return ret;
}
int tegra_cpu_edp_init_trips(struct platform_device *pdev,
struct thermal_trip_info *trips,
int *num_trips, char *cdev_type,
int margin)
{
struct device *dev;
struct tegra_edp *cpu_edp;
struct thermal_trip_info *trip_state;
const struct tegra_edp_limits *cpu_edp_limits;
int i, cpu_edp_limits_size;
if (!pdev)
return -EINVAL;
dev = &pdev->dev;
cpu_edp = dev_get_drvdata(dev);
if (!cpu_edp || !cpu_edp->edp_init_done)
return -EPROBE_DEFER;
if (!trips || !num_trips)
return -EINVAL;
/* edp capping */
cpu_edp_limits = cpu_edp->edp_limits;
cpu_edp_limits_size = cpu_edp->edp_limits_size;
for (i = 0; i < cpu_edp_limits_size-1; i++) {
trip_state = &trips[*num_trips];
trip_state->cdev_type = cdev_type;
trip_state->trip_temp =
(cpu_edp_limits[i].temperature * 1000) - margin;
trip_state->trip_type = THERMAL_TRIP_ACTIVE;
trip_state->upper = i + 1;
trip_state->lower = trip_state->upper;
trip_state->hysteresis = 1000;
(*num_trips)++;
if (*num_trips >= THERMAL_MAX_TRIPS)
BUG();
}
return 0;
}
EXPORT_SYMBOL(tegra_cpu_edp_init_trips);
void tegra_cpu_edp_recal_limits(void)
{
if (!cpu_edp.edp_limits)
return;
if (tegra_cpu_edp_cal_limits(&cpu_edp.pdev->dev) == 0)
return;
/* Revert to default EDP table on error */
cpu_edp.edp_limits = cpu_edp.def_edp_limits;
cpu_edp.edp_limits_size = cpu_edp.def_edp_limits_size;
}
EXPORT_SYMBOL(tegra_cpu_edp_recal_limits);
static unsigned int edp_predict_limit(struct tegra_edp *edp, unsigned int cpus)
{
unsigned int limit = 0;
BUG_ON(cpus == 0);
if (edp->edp_limits) {
int i = edp->edp_thermal_index;
int size = edp->edp_limits_size;
BUG_ON(i >= size);
limit = edp->edp_limits[i].freq_limits[cpus - 1];
}
return limit;
}
/* Must be called while holding tegra_cpu_lock */
static void tegra_edp_update_limit(struct device *dev)
{
struct tegra_edp *edp = dev_get_drvdata(dev);
unsigned int cpus, limit;
if (!edp->edp_limits)
return;
BUG_ON(!mutex_is_locked(&tegra_cpu_lock));
cpus = cpumask_weight(&edp->edp_cpumask);
limit = edp_predict_limit(edp, cpus);
edp->edp_freq_limit = limit;
}
static unsigned int tegra_edp_governor_speed(struct device *dev,
unsigned int requested_speed)
{
struct tegra_edp *edp = dev_get_drvdata(dev);
if ((!edp->edp_freq_limit) ||
(requested_speed <= edp->edp_freq_limit))
return requested_speed;
else
return edp->edp_freq_limit;
}
static int tegra_cpu_edp_init_data(struct device *dev,
unsigned int regulator_ma)
{
struct tegra_edp *edp = dev_get_drvdata(dev);
edp->edp_reg_override_ma = OVERRIDE_DEFAULT;
edp->regulator_cur = regulator_ma + OVERRIDE_DEFAULT;
edp->def_edp_limits = edp_default_limits;
edp->def_edp_limits_size = ARRAY_SIZE(edp_default_limits);
edp->temps = temperatures;
edp->temps_size = ARRAY_SIZE(temperatures);
edp->edp_limits = edp_default_limits;
edp->edp_limits_size = ARRAY_SIZE(edp_default_limits);
tegra_cpu_edp_cal_limits(dev);
mutex_lock(&tegra_cpu_lock);
edp->edp_cpumask = *cpu_online_mask;
tegra_edp_update_limit(dev);
mutex_unlock(&tegra_cpu_lock);
dev_info(dev, "Init EDP limit: %u MHz\n", edp->edp_freq_limit/1000);
return 0;
}
int tegra_cpu_edp_get_thermal_index(struct platform_device *pdev)
{
struct tegra_edp *edp = dev_get_drvdata(&pdev->dev);
return edp->edp_thermal_index;
}
EXPORT_SYMBOL(tegra_cpu_edp_get_thermal_index);
int tegra_cpu_edp_count_therm_floors(struct platform_device *pdev)
{
struct tegra_edp *edp = dev_get_drvdata(&pdev->dev);
return edp->edp_limits_size - 1;
}
EXPORT_SYMBOL(tegra_cpu_edp_count_therm_floors);
int tegra_cpu_edp_update_thermal_index(struct platform_device *pdev,
unsigned long new_idx)
{
struct tegra_edp *edp = dev_get_drvdata(&pdev->dev);
if (new_idx > (edp->edp_limits_size - 1))
return -ERANGE;
if (!cpufreq_get(0))
return 0;
mutex_lock(&tegra_cpu_lock);
edp->edp_thermal_index = new_idx;
edp->edp_cpumask = *cpu_online_mask;
tegra_edp_update_limit(&pdev->dev);
mutex_unlock(&tegra_cpu_lock);
cpufreq_update_policy(0);
return 0;
}
EXPORT_SYMBOL(tegra_cpu_edp_update_thermal_index);
bool tegra_cpu_edp_ready(void)
{
return cpu_edp.edp_init_done;
}
EXPORT_SYMBOL(tegra_cpu_edp_ready);
#ifdef CONFIG_DEBUG_FS
static struct dentry *edp_debugfs_dir;
static int edp_limit_debugfs_show(struct seq_file *s, void *data)
{
struct device *dev = s->private;
struct tegra_edp *edp = dev_get_drvdata(dev);
seq_printf(s, "%u\n", edp->edp_freq_limit);
return 0;
}
static int edp_debugfs_show(struct seq_file *s, void *data)
{
struct device *dev = s->private;
struct tegra_edp *edp = dev_get_drvdata(dev);
int i, th_idx;
th_idx = edp->edp_thermal_index;
seq_printf(s, "-- VDD_CPU %sEDP table (%umA = %umA - %umA) --\n",
edp->edp_limits == edp->def_edp_limits ? "**default** " : "",
edp->regulator_cur - edp->edp_reg_override_ma,
edp->regulator_cur, edp->edp_reg_override_ma);
seq_printf(s, "%6s %10s %10s %10s %10s\n",
" Temp.", "1-core", "2-cores", "3-cores", "4-cores");
for (i = 0; i < edp->edp_limits_size; i++) {
seq_printf(s, "%c%3dC: %10u %10u %10u %10u\n",
i == th_idx ? '>' : ' ',
edp->edp_limits[i].temperature,
edp->edp_limits[i].freq_limits[0],
edp->edp_limits[i].freq_limits[1],
edp->edp_limits[i].freq_limits[2],
edp->edp_limits[i].freq_limits[3]);
}
return 0;
}
static int edp_reg_override_show(struct seq_file *s, void *data)
{
struct device *dev = s->private;
struct tegra_edp *edp = dev_get_drvdata(dev);
seq_printf(s, "Limit override: %u mA. Effective limit: %u mA\n",
edp->edp_reg_override_ma,
edp->regulator_cur - edp->edp_reg_override_ma);
return 0;
}
static int edp_reg_override_write(struct file *file,
const char __user *userbuf, size_t count, loff_t *ppos)
{
struct device *dev = file->f_path.dentry->d_inode->i_private;
struct tegra_edp *edp = dev_get_drvdata(dev);
char buf[32];
unsigned long res;
unsigned int edp_reg_override_ma_temp;
unsigned int edp_reg_override_ma_prev = edp->edp_reg_override_ma;
if (sizeof(buf) <= count)
goto override_err;
if (copy_from_user(buf, userbuf, count))
goto override_err;
/* terminate buffer and trim - white spaces may be appended
* at the end when invoked from shell command line */
buf[count] = '\0';
strim(buf);
if (!kstrtoul(buf, 10, &res))
edp_reg_override_ma_temp = (unsigned int)res;
else
goto override_err;
if (edp_reg_override_ma_temp >= edp->regulator_cur)
goto override_err;
if (edp->edp_reg_override_ma == edp_reg_override_ma_temp)
return count;
edp->edp_reg_override_ma = edp_reg_override_ma_temp;
if (tegra_cpu_edp_cal_limits(dev)) {
/* Revert to previous override value if new value fails */
edp->edp_reg_override_ma = edp_reg_override_ma_prev;
goto override_err;
}
if (cpufreq_update_policy(0)) {
dev_err(dev,
"FAILED: Set CPU freq cap with new VDD_CPU EDP table\n");
goto override_out;
}
dev_info(dev,
"Reinitialized VDD_CPU EDP table with regulator current limit %u mA\n",
edp->regulator_cur - edp->edp_reg_override_ma);
return count;
override_err:
dev_err(dev,
"FAILED: Reinitialize VDD_CPU EDP table with override \"%s\"",
buf);
override_out:
return -EINVAL;
}
static int edp_debugfs_open(struct inode *inode, struct file *file)
{
return single_open(file, edp_debugfs_show, inode->i_private);
}
static int edp_limit_debugfs_open(struct inode *inode, struct file *file)
{
return single_open(file, edp_limit_debugfs_show, inode->i_private);
}
static int edp_reg_override_open(struct inode *inode, struct file *file)
{
return single_open(file, edp_reg_override_show, inode->i_private);
}
static const struct file_operations edp_debugfs_fops = {
.open = edp_debugfs_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static const struct file_operations edp_limit_debugfs_fops = {
.open = edp_limit_debugfs_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static const struct file_operations edp_reg_override_debugfs_fops = {
.open = edp_reg_override_open,
.read = seq_read,
.write = edp_reg_override_write,
.llseek = seq_lseek,
.release = single_release,
};
static int tegra_cpu_edp_debugfs_init(struct device *dev)
{
struct tegra_edp *edp = dev_get_drvdata(dev);
struct dentry *d_edp;
struct dentry *d_edp_limit;
struct dentry *d_edp_reg_override;
struct dentry *edp_dir;
struct dentry *vdd_cpu_dir;
if (!edp_debugfs_dir)
edp_debugfs_dir = debugfs_create_dir("edp", NULL);
if (!edp_debugfs_dir)
goto edp_dir_err;
edp_dir = edp_debugfs_dir;
vdd_cpu_dir = debugfs_create_dir("edp_vdd_cpu", edp_dir);
if (!vdd_cpu_dir)
goto vdd_cpu_dir_err;
d_edp = debugfs_create_file("edp", S_IRUGO, vdd_cpu_dir, dev,
&edp_debugfs_fops);
if (!d_edp)
goto edp_err;
d_edp_limit = debugfs_create_file("edp_limit", S_IRUGO, vdd_cpu_dir,
dev, &edp_limit_debugfs_fops);
if (!d_edp_limit)
goto edp_limit_err;
d_edp_reg_override = debugfs_create_file("edp_reg_override",
S_IRUGO | S_IWUSR, vdd_cpu_dir, dev,
&edp_reg_override_debugfs_fops);
if (!d_edp_reg_override)
goto edp_reg_override_err;
edp->dir = vdd_cpu_dir;
return 0;
edp_reg_override_err:
debugfs_remove(d_edp_limit);
edp_limit_err:
debugfs_remove(d_edp);
edp_err:
debugfs_remove(vdd_cpu_dir);
vdd_cpu_dir_err:
debugfs_remove(edp_dir);
edp_dir_err:
return -ENOMEM;
}
#endif /* CONFIG_DEBUG_FS */
static int tegra_cpu_edp_notify(
struct notifier_block *nb, unsigned long event, void *hcpu)
{
struct platform_device *pdev = cpu_edp.pdev;
struct device *dev = &pdev->dev;
int ret = 0;
unsigned int cpu_speed, new_speed;
int cpu = (long)hcpu;
switch (event) {
case CPU_UP_PREPARE:
mutex_lock(&tegra_cpu_lock);
cpu_set(cpu, cpu_edp.edp_cpumask);
tegra_edp_update_limit(dev);
cpu_speed = cpufreq_get(0);
new_speed = tegra_edp_governor_speed(dev, cpu_speed);
if (new_speed < cpu_speed) {
ret = cpufreq_update_policy(0);
dev_dbg(dev, "cpu-tegra:%sforce EDP limit %u kHz\n",
ret ? " failed to " : " ", new_speed);
}
if (ret) {
cpu_clear(cpu, cpu_edp.edp_cpumask);
tegra_edp_update_limit(dev);
}
mutex_unlock(&tegra_cpu_lock);
break;
case CPU_DEAD:
mutex_lock(&tegra_cpu_lock);
cpu_clear(cpu, cpu_edp.edp_cpumask);
tegra_edp_update_limit(dev);
mutex_unlock(&tegra_cpu_lock);
cpufreq_update_policy(0);
break;
}
return notifier_from_errno(ret);
}
static struct notifier_block tegra_cpu_edp_notifier = {
.notifier_call = tegra_cpu_edp_notify,
};
/**
* edp_cpufreq_policy_notifier - Notifier callback for cpufreq policy change.
* @nb: struct notifier_block * with callback info.
* @event: value showing cpufreq event for which this function invoked.
* @data: callback-specific data
*
* Callback to highjack the notification on cpufreq policy transition.
* Every time there is a change in policy, we will intercept and
* update the cpufreq policy with edp constraints.
*
* Return: 0 (success)
*/
static int edp_cpufreq_policy_notifier(struct notifier_block *nb,
unsigned long event, void *data)
{
struct cpufreq_policy *policy = data;
if (event != CPUFREQ_ADJUST)
return 0;
/* Limit max freq to be within edp limit. */
if (policy->max != cpu_edp.edp_freq_limit)
cpufreq_verify_within_limits(policy, 0, cpu_edp.edp_freq_limit);
return 0;
}
/* Notifier for cpufreq policy change */
static struct notifier_block edp_cpufreq_notifier_block = {
.notifier_call = edp_cpufreq_policy_notifier,
};
typedef int (*of_cpu_edp_init_t)(struct device_node *);
static int of_cpu_edp_init(struct device_node *np,
const struct of_device_id *matches)
{
int ret;
for_each_matching_node(np, matches) {
const struct of_device_id *match = of_match_node(matches, np);
of_cpu_edp_init_t cpu_edp_init = (of_cpu_edp_init_t)match->data;
if (!cpu_edp_init)
return -EINVAL;
ret = cpu_edp_init(np);
if (ret)
return ret;
}
cpufreq_register_notifier(&edp_cpufreq_notifier_block,
CPUFREQ_POLICY_NOTIFIER);
return 0;
}
static const struct of_device_id tegra_cpu_edp_match[] = {
{ .compatible = "nvidia,tegra124-cpu-edp",
.data = tegra124_cpu_edp_init },
{},
};
static int tegra_cpu_edp_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
unsigned int regulator_ma;
u32 val;
int err;
struct device_node *np = pdev->dev.of_node;
if (!cpufreq_get(0)) {
dev_warn(dev, "CPU clocks are not ready.\n");
return -EPROBE_DEFER;
}
err = of_property_read_u32(np, "regulator-ma", &val);
if (!err)
regulator_ma = val;
else
regulator_ma = 15000;
dev_info(dev, "CPU regulator %d mA\n", regulator_ma);
platform_set_drvdata(pdev, &cpu_edp);
of_cpu_edp_init(np, tegra_cpu_edp_match);
cpu_edp.pdev = pdev;
err = tegra_cpu_edp_init_data(dev, regulator_ma);
if (err) {
dev_err(dev, "Failed to calculate cpu edp limits\n");
return 0;
}
register_hotcpu_notifier(&tegra_cpu_edp_notifier);
#ifdef CONFIG_DEBUG_FS
tegra_cpu_edp_debugfs_init(dev);
#endif
cpu_edp.edp_init_done = true;
return 0;
}
static int tegra_cpu_edp_remove(struct platform_device *pdev)
{
#ifdef CONFIG_DEBUG_FS
debugfs_remove_recursive(cpu_edp.dir);
#endif
unregister_hotcpu_notifier(&tegra_cpu_edp_notifier);
return 0;
}
#ifdef CONFIG_PM_SLEEP
static int tegra_cpu_edp_suspend(struct device *dev)
{
struct tegra_edp *edp = dev_get_drvdata(dev);
int size;
mutex_lock(&tegra_cpu_lock);
if (edp->edp_limits) {
size = edp->edp_limits_size - 1;
edp->edp_freq_limit = edp->edp_limits[size].freq_limits[3];
} else {
size = edp->def_edp_limits_size - 1;
edp->edp_freq_limit = edp->def_edp_limits[size].freq_limits[3];
}
mutex_unlock(&tegra_cpu_lock);
cpufreq_update_policy(0);
return 0;
}
static int tegra_cpu_edp_resume(struct device *dev)
{
struct tegra_edp *edp = dev_get_drvdata(dev);
mutex_lock(&tegra_cpu_lock);
edp->edp_cpumask = *cpu_online_mask;
tegra_edp_update_limit(dev);
mutex_unlock(&tegra_cpu_lock);
cpufreq_update_policy(0);
return 0;
}
static const struct dev_pm_ops tegra_cpu_edp_pm_ops = {
.suspend = tegra_cpu_edp_suspend,
.resume = tegra_cpu_edp_resume,
};
#endif
static struct platform_driver tegra_cpu_edp_driver = {
.probe = tegra_cpu_edp_probe,
.remove = tegra_cpu_edp_remove,
.driver = {
.name = "tegra-cpu-edp",
.owner = THIS_MODULE,
.of_match_table = tegra_cpu_edp_match,
#ifdef CONFIG_PM_SLEEP
.pm = &tegra_cpu_edp_pm_ops,
#endif
},
};
static int __init tegra_cpu_edp_init(void)
{
return platform_driver_register(&tegra_cpu_edp_driver);
}
static void __exit tegra_cpu_edp_exit(void)
{
platform_driver_unregister(&tegra_cpu_edp_driver);
}
module_init(tegra_cpu_edp_init);
module_exit(tegra_cpu_edp_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Tegra VDD_CPU EDP management");

View File

@@ -0,0 +1,115 @@
/*
* Copyright (c) 2011-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 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.
*/
#ifndef __TEGRA_EDP_PRIVATE_H
#define __TEGRA_EDP_PRIVATE_H
#include <linux/debugfs.h>
#include <linux/platform_data/tegra_thermal.h>
struct tegra_edp_vdd_cpu_entry {
char speedo_id;
char regulator_100ma;
char temperature;
char freq_limits[4];
};
struct tegra_edp_limits {
int temperature;
unsigned int freq_limits[4];
};
struct tegra_edp_voltage_temp_constraint {
int temperature;
unsigned int voltage_limit_mv;
};
struct tegra_edp_cpu_leakage_params {
int cpu_speedo_id;
unsigned int temp_scaled;
unsigned int dyn_scaled;
int dyn_consts_n[4]; /* pre-multiplied by 'scaled */
unsigned int consts_scaled;
int leakage_consts_n[4]; /* pre-multiplied by 'scaled */
unsigned int ijk_scaled;
const int (*leakage_consts_ijk)[4][4]; /* pre-multiplied by 'scaled */
unsigned int leakage_min; /* minimum leakage current */
unsigned int safety_cap[4];
struct tegra_edp_voltage_temp_constraint volt_temp_cap;
};
struct tegra_edp_freq_vol_table {
unsigned int freq;
int voltage_mv;
};
struct tegra_edp {
struct platform_device *pdev;
struct tegra_edp_limits *def_edp_limits;
int def_edp_limits_size;
const int *temps;
int temps_size;
const struct tegra_edp_cpu_leakage_params *params;
int params_size;
unsigned int cpu_speedo_idx;
struct tegra_edp_limits *edp_limits;
int edp_limits_size;
unsigned int regulator_cur;
/* Value to subtract from regulator current limit */
unsigned int edp_reg_override_ma;
unsigned int max_cpu_freq;
int edp_thermal_index;
unsigned int edp_freq_limit;
cpumask_t edp_cpumask;
bool suspended;
#ifdef CONFIG_DEBUG_FS
struct dentry *dir;
#endif
bool edp_init_done;
};
struct tegra_cpu_edp_cooling_data {
const struct tegra_edp_limits *cpu_edp_limits;
int cpu_edp_limits_size;
int edp_thermal_index;
cpumask_t edp_cpumask;
unsigned int edp_limit;
};
#ifdef CONFIG_ARCH_TEGRA_124_SOC
int tegra124_cpu_edp_init(struct device_node *cpu_edp_dn);
#else
static inline int tegra124_cpu_edp_init(struct device_node *cpu_edp_dn)
{ return 0; }
#endif
#endif /* __TEGRA_EDP_PRIVATE_H */