1016 lines
26 KiB
C
1016 lines
26 KiB
C
/*
|
|
* 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/>.
|
|
*
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/clk-provider.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/gpio.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/io.h>
|
|
#include <linux/irq.h>
|
|
#include <linux/firmware.h>
|
|
#include <linux/memblock.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_address.h>
|
|
#include <linux/of_gpio.h>
|
|
#include <linux/gpio.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/tegra-powergate.h>
|
|
#include <linux/tegra-soc.h>
|
|
#include <linux/of_gpio.h>
|
|
#include <linux/of_platform.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/syscore_ops.h>
|
|
#include <linux/tegra-soc.h>
|
|
|
|
#include "flowctrl.h"
|
|
#include "pm.h"
|
|
#include "pmc.h"
|
|
#include "sleep.h"
|
|
|
|
/*
|
|
* When another Tegra variant supports loading LP0 code with request_firmware,
|
|
* this #define should be converted to a lookup table so each SoC can have its
|
|
* own firmware name.
|
|
*/
|
|
#define LP0_FW_NAME "tegra12x/tegra_lp0_resume.fw"
|
|
|
|
#define TEGRA_POWER_PWRREQ_POLARITY (1 << 8) /* core power req polarity */
|
|
#define TEGRA_POWER_PWRREQ_OE (1 << 9) /* core power req enable */
|
|
#define TEGRA_POWER_SYSCLK_POLARITY (1 << 10) /* sys clk polarity */
|
|
#define TEGRA_POWER_SYSCLK_OE (1 << 11) /* system clock enable */
|
|
#define TEGRA_POWER_EFFECT_LP0 (1 << 14) /* LP0 when CPU pwr gated */
|
|
#define TEGRA_POWER_CPU_PWRREQ_POLARITY (1 << 15) /* CPU pwr req polarity */
|
|
#define TEGRA_POWER_CPU_PWRREQ_OE (1 << 16) /* CPU pwr req enable */
|
|
|
|
#define PMC_CTRL 0x0
|
|
#define PMC_CTRL_LATCH_WAKEUPS (1 << 5)
|
|
#define PMC_CTRL_INTR_LOW (1 << 17)
|
|
#define PMC_WAKE_MASK 0xc
|
|
#define PMC_WAKE_LEVEL 0x10
|
|
#define PMC_WAKE_STATUS 0x14
|
|
#define PMC_SW_WAKE_STATUS 0x18
|
|
#define PMC_DPD_SAMPLE 0x20
|
|
#define PMC_DPD_ENABLE 0x24
|
|
#define PMC_DPD_ENABLE_ON 0x1
|
|
#define PMC_DPD_ENABLE_TSC_MULT_ENABLE (1 << 1)
|
|
#define PMC_PWRGATE_TOGGLE 0x30
|
|
#define PMC_PWRGATE_TOGGLE_START (1 << 8)
|
|
#define PMC_REMOVE_CLAMPING 0x34
|
|
#define PMC_PWRGATE_STATUS 0x38
|
|
#define PMC_COREPWRGOOD_TIMER 0x3c
|
|
#define PMC_SCRATCH0 0x50
|
|
#define PMC_SCRATCH1 0x54
|
|
#define PMC_CPUPWRGOOD_TIMER 0xc8
|
|
#define PMC_CPUPWROFF_TIMER 0xcc
|
|
#define PMC_WAKE_DELAY 0xe0
|
|
#define PMC_COREPWROFF_TIMER PMC_WAKE_DELAY
|
|
#define PMC_WAKE2_MASK 0x160
|
|
#define PMC_WAKE2_LEVEL 0x164
|
|
#define PMC_WAKE2_STATUS 0x168
|
|
#define PMC_SW_WAKE2_STATUS 0x16C
|
|
#define PMC_IO_DPD_REQ 0x1B8
|
|
#define IO_DPD_CSIA (1 << 0)
|
|
#define IO_DPD_CSIB (1 << 1)
|
|
#define IO_DPD_DSI (1 << 2)
|
|
#define IO_DPD_MIPI_BIAS (1 << 3)
|
|
#define IO_DPD_PEX_BIAS (1 << 4)
|
|
#define IO_DPD_PEX_CLK1 (1 << 5)
|
|
#define IO_DPD_PEX_CLK2 (1 << 6)
|
|
#define IO_DPD_PEX_CLK3 (1 << 7)
|
|
#define IO_DPD_DAC (1 << 8)
|
|
#define IO_DPD_USB0 (1 << 9)
|
|
#define IO_DPD_USB1 (1 << 10)
|
|
#define IO_DPD_USB2 (1 << 11)
|
|
#define IO_DPD_USB_BIAS (1 << 12)
|
|
#define IO_DPD_NAND (1 << 13)
|
|
#define IO_DPD_UART (1 << 14)
|
|
#define IO_DPD_BB (1 << 15)
|
|
#define IO_DPD_VI (1 << 16)
|
|
#define IO_DPD_AUDIO (1 << 17)
|
|
#define IO_DPD_LCD (1 << 18)
|
|
#define IO_DPD_HSIC (1 << 19)
|
|
#define IO_DPD_ON (2 << 30)
|
|
#define IO_DPD_OFF (1 << 30)
|
|
#define PMC_IO_DPD2_REQ 0x1C0
|
|
#define IO_DPD2_PEX_CNTRL (1 << 0)
|
|
#define IO_DPD2_SDMMC1 (1 << 1)
|
|
#define IO_DPD2_SDMMC3 (1 << 2)
|
|
#define IO_DPD2_SDMMC4 (1 << 3)
|
|
#define IO_DPD2_CAM (1 << 4)
|
|
#define IO_DPD2_RES_RAIL (1 << 5)
|
|
#define IO_DPD2_HV (1 << 6)
|
|
#define IO_DPD2_DSIB (1 << 7)
|
|
#define IO_DPD2_DSIC (1 << 8)
|
|
#define IO_DPD2_DSID (1 << 9)
|
|
#define IO_DPD2_CSIC (1 << 10)
|
|
#define IO_DPD2_CSID (1 << 11)
|
|
#define IO_DPD2_CSIE (1 << 12)
|
|
|
|
#define DPD_STATE_CHANGE_DELAY 700
|
|
|
|
static u8 tegra_cpu_domains[] = {
|
|
0xFF, /* not available for CPU0 */
|
|
TEGRA_POWERGATE_CPU1,
|
|
TEGRA_POWERGATE_CPU2,
|
|
TEGRA_POWERGATE_CPU3,
|
|
};
|
|
static DEFINE_SPINLOCK(tegra_powergate_lock);
|
|
|
|
static void __iomem *tegra_pmc_base;
|
|
static bool tegra_pmc_invert_interrupt;
|
|
static struct clk *tegra_pclk;
|
|
|
|
struct pmc_pm_data {
|
|
u32 cpu_good_time; /* CPU power good time in uS */
|
|
u32 cpu_off_time; /* CPU power off time in uS */
|
|
u32 core_osc_time; /* Core power good osc time in uS */
|
|
u32 core_pmu_time; /* Core power good pmu time in uS */
|
|
u32 core_off_time; /* Core power off time in uS */
|
|
bool corereq_high; /* Core power request active-high */
|
|
bool sysclkreq_high; /* System clock request active-high */
|
|
bool combined_req; /* Combined pwr req for CPU & Core */
|
|
bool cpu_pwr_good_en; /* CPU power good signal is enabled */
|
|
u32 lp0_vec_phy_addr; /* The phy addr of LP0 warm boot code */
|
|
u32 lp0_vec_size; /* The size of LP0 warm boot code */
|
|
enum tegra_suspend_mode suspend_mode;
|
|
int reset_gpio; /* GPIO to assert to reset the system */
|
|
bool reset_active_low; /* Reset GPIO is active-low */
|
|
};
|
|
static struct pmc_pm_data pmc_pm_data;
|
|
|
|
#ifdef CONFIG_PM_SLEEP
|
|
#define PMC_WAKE_TYPE_GPIO 0
|
|
#define PMC_WAKE_TYPE_EVENT 1
|
|
#define PMC_WAKE_TYPE_INDEX 0
|
|
#define PMC_WAKE_MASK_INDEX 1
|
|
#define PMC_TRIGGER_TYPE_INDEX 2
|
|
struct pmc_wakeup {
|
|
u32 wake_type;
|
|
u32 wake_mask_offset;
|
|
u32 irq_num;
|
|
struct list_head list;
|
|
};
|
|
|
|
struct pmc_lp0_wakeup {
|
|
struct device_node *of_node;
|
|
u64 enable;
|
|
u64 level;
|
|
u64 level_any;
|
|
struct list_head wake_list;
|
|
};
|
|
static struct pmc_lp0_wakeup tegra_lp0_wakeup;
|
|
static u32 io_dpd_reg, io_dpd2_reg;
|
|
#endif
|
|
|
|
static inline u32 tegra_pmc_readl(u32 reg)
|
|
{
|
|
return readl(tegra_pmc_base + reg);
|
|
}
|
|
|
|
static inline void tegra_pmc_writel(u32 val, u32 reg)
|
|
{
|
|
writel(val, tegra_pmc_base + reg);
|
|
}
|
|
|
|
static int tegra_pmc_get_cpu_powerdomain_id(int cpuid)
|
|
{
|
|
if (cpuid <= 0 || cpuid >= num_possible_cpus())
|
|
return -EINVAL;
|
|
return tegra_cpu_domains[cpuid];
|
|
}
|
|
|
|
static bool tegra_pmc_powergate_is_powered(int id)
|
|
{
|
|
return (tegra_pmc_readl(PMC_PWRGATE_STATUS) >> id) & 1;
|
|
}
|
|
|
|
static int tegra_pmc_powergate_set(int id, bool new_state)
|
|
{
|
|
bool old_state;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&tegra_powergate_lock, flags);
|
|
|
|
old_state = tegra_pmc_powergate_is_powered(id);
|
|
WARN_ON(old_state == new_state);
|
|
|
|
tegra_pmc_writel(PMC_PWRGATE_TOGGLE_START | id, PMC_PWRGATE_TOGGLE);
|
|
|
|
spin_unlock_irqrestore(&tegra_powergate_lock, flags);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tegra_pmc_powergate_remove_clamping(int id)
|
|
{
|
|
u32 mask;
|
|
|
|
/*
|
|
* Tegra 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);
|
|
|
|
tegra_pmc_writel(mask, PMC_REMOVE_CLAMPING);
|
|
|
|
return 0;
|
|
}
|
|
|
|
bool tegra_pmc_cpu_is_powered(int cpuid)
|
|
{
|
|
int id;
|
|
|
|
id = tegra_pmc_get_cpu_powerdomain_id(cpuid);
|
|
if (id < 0)
|
|
return false;
|
|
return tegra_pmc_powergate_is_powered(id);
|
|
}
|
|
|
|
int tegra_pmc_cpu_power_on(int cpuid)
|
|
{
|
|
int id;
|
|
|
|
id = tegra_pmc_get_cpu_powerdomain_id(cpuid);
|
|
if (id < 0)
|
|
return id;
|
|
return tegra_pmc_powergate_set(id, true);
|
|
}
|
|
|
|
int tegra_pmc_cpu_remove_clamping(int cpuid)
|
|
{
|
|
int id;
|
|
|
|
id = tegra_pmc_get_cpu_powerdomain_id(cpuid);
|
|
if (id < 0)
|
|
return id;
|
|
return tegra_pmc_powergate_remove_clamping(id);
|
|
}
|
|
|
|
void tegra_pmc_restart(char mode, const char *cmd)
|
|
{
|
|
u32 val;
|
|
|
|
/*
|
|
* If there's a reset GPIO, attempt to use that first and then fall
|
|
* back to PMC reset if that fails.
|
|
*/
|
|
if (gpio_is_valid(pmc_pm_data.reset_gpio)) {
|
|
val = pmc_pm_data.reset_active_low ? 0 : 1;
|
|
gpio_direction_output(pmc_pm_data.reset_gpio, val);
|
|
udelay(100);
|
|
pr_err("GPIO reset failed; using PMC reset...\n");
|
|
}
|
|
|
|
val = tegra_pmc_readl(0);
|
|
val |= 0x10;
|
|
tegra_pmc_writel(val, 0);
|
|
}
|
|
|
|
#ifdef CONFIG_PM_SLEEP
|
|
void tegra_tsc_suspend(void)
|
|
{
|
|
if (IS_ENABLED(CONFIG_ARM_ARCH_TIMER)) {
|
|
u32 reg;
|
|
reg = tegra_pmc_readl(PMC_DPD_ENABLE);
|
|
reg |= PMC_DPD_ENABLE_TSC_MULT_ENABLE;
|
|
tegra_pmc_writel(reg, PMC_DPD_ENABLE);
|
|
}
|
|
}
|
|
|
|
void tegra_tsc_resume(void)
|
|
{
|
|
if (IS_ENABLED(CONFIG_ARM_ARCH_TIMER)) {
|
|
u32 reg;
|
|
reg = tegra_pmc_readl(PMC_DPD_ENABLE);
|
|
reg &= ~PMC_DPD_ENABLE_TSC_MULT_ENABLE;
|
|
if (tegra_chip_id == TEGRA124) {
|
|
/* WAR to avoid PMC wake status getting cleared */
|
|
reg &= ~PMC_DPD_ENABLE_ON;
|
|
}
|
|
tegra_pmc_writel(reg, PMC_DPD_ENABLE);
|
|
}
|
|
}
|
|
|
|
static void tegra_pmc_add_wakeup_event(struct of_phandle_args *ph_args,
|
|
struct device *dev,
|
|
struct device_node *np)
|
|
{
|
|
if (ph_args->np == tegra_lp0_wakeup.of_node) {
|
|
struct platform_device *pdev;
|
|
struct pmc_wakeup *pmc_wake_source;
|
|
int pmc_wake_type, wake;
|
|
int irq = 0, pmc_trigger_type = 0;
|
|
|
|
pdev = to_platform_device(dev);
|
|
irq = platform_get_irq(pdev, 0);
|
|
pmc_wake_type = ph_args->args[PMC_WAKE_TYPE_INDEX];
|
|
|
|
switch (pmc_wake_type) {
|
|
case PMC_WAKE_TYPE_GPIO:
|
|
if (pdev != NULL) {
|
|
struct irq_desc *irqd;
|
|
struct irq_data *irq_data;
|
|
|
|
if (irq < 0) {
|
|
int gpio;
|
|
gpio = of_get_named_gpio(np,
|
|
"gpios", 0);
|
|
irq = gpio_to_irq(gpio);
|
|
if (irq < 0) {
|
|
WARN_ON(1);
|
|
return;
|
|
}
|
|
}
|
|
irqd = irq_to_desc(irq);
|
|
irq_data = &irqd->irq_data;
|
|
pmc_trigger_type =
|
|
irqd_get_trigger_type(irq_data);
|
|
}
|
|
break;
|
|
case PMC_WAKE_TYPE_EVENT:
|
|
pmc_trigger_type =
|
|
ph_args->args[PMC_TRIGGER_TYPE_INDEX];
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
|
|
pmc_wake_source = kzalloc(sizeof(*pmc_wake_source),
|
|
GFP_KERNEL);
|
|
if (!pmc_wake_source) {
|
|
pr_err("%s: fail to alloc memory.", __func__);
|
|
return;
|
|
}
|
|
|
|
pmc_wake_source->wake_type = pmc_wake_type;
|
|
pmc_wake_source->irq_num = irq;
|
|
pmc_wake_source->wake_mask_offset =
|
|
ph_args->args[PMC_WAKE_MASK_INDEX];
|
|
wake = pmc_wake_source->wake_mask_offset;
|
|
|
|
list_add_tail(&pmc_wake_source->list,
|
|
&tegra_lp0_wakeup.wake_list);
|
|
|
|
tegra_lp0_wakeup.enable |= 1ull << wake;
|
|
switch (pmc_trigger_type) {
|
|
case IRQF_TRIGGER_FALLING:
|
|
case IRQF_TRIGGER_LOW:
|
|
tegra_lp0_wakeup.level &= ~(1ull << wake);
|
|
tegra_lp0_wakeup.level_any &= ~(1ull << wake);
|
|
break;
|
|
case IRQF_TRIGGER_HIGH:
|
|
case IRQF_TRIGGER_RISING:
|
|
tegra_lp0_wakeup.level |= (1ull << wake);
|
|
tegra_lp0_wakeup.level_any &= ~(1ull << wake);
|
|
break;
|
|
case IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING:
|
|
tegra_lp0_wakeup.level_any |= (1ull << wake);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void tegra_of_device_add_pmc_wake(struct device *dev)
|
|
{
|
|
struct of_phandle_args ph_args;
|
|
struct device_node *np = NULL;
|
|
int child_node_num;
|
|
|
|
child_node_num = of_get_child_count(dev->of_node);
|
|
if (child_node_num == 0) {
|
|
if (!of_parse_phandle_with_args(dev->of_node,
|
|
"nvidia,pmc-wakeup",
|
|
"#wake-cells", 0, &ph_args))
|
|
tegra_pmc_add_wakeup_event(&ph_args, dev, dev->of_node);
|
|
} else {
|
|
for_each_child_of_node(dev->of_node, np)
|
|
if (!of_parse_phandle_with_args(np,
|
|
"nvidia,pmc-wakeup",
|
|
"#wake-cells", 0, &ph_args))
|
|
tegra_pmc_add_wakeup_event(&ph_args, dev, np);
|
|
}
|
|
|
|
of_node_put(ph_args.np);
|
|
}
|
|
|
|
static int tegra_pmc_wake_notifier_call(struct notifier_block *nb,
|
|
unsigned long event, void *data)
|
|
{
|
|
struct device *dev = data;
|
|
|
|
switch (event) {
|
|
case BUS_NOTIFY_BOUND_DRIVER:
|
|
if (dev->of_node)
|
|
tegra_of_device_add_pmc_wake(dev);
|
|
break;
|
|
}
|
|
return NOTIFY_DONE;
|
|
}
|
|
|
|
static struct notifier_block tegra_pmc_wake_notifier = {
|
|
.notifier_call = tegra_pmc_wake_notifier_call,
|
|
};
|
|
|
|
void __init tegra_pmc_lp0_wakeup_init(void)
|
|
{
|
|
bus_register_notifier(&platform_bus_type, &tegra_pmc_wake_notifier);
|
|
}
|
|
|
|
static inline void write_pmc_wake_mask(u64 value)
|
|
{
|
|
pr_info("PMC wake enable = 0x%llx\n", value);
|
|
tegra_pmc_writel((u32)value, PMC_WAKE_MASK);
|
|
if (tegra_chip_id != TEGRA20)
|
|
tegra_pmc_writel((u32)(value >> 32), PMC_WAKE2_MASK);
|
|
}
|
|
|
|
static inline u64 read_pmc_wake_level(void)
|
|
{
|
|
u64 reg;
|
|
|
|
reg = tegra_pmc_readl(PMC_WAKE_LEVEL);
|
|
if (tegra_chip_id != TEGRA20)
|
|
reg |= ((u64)tegra_pmc_readl(PMC_WAKE2_LEVEL)) << 32;
|
|
|
|
return reg;
|
|
}
|
|
|
|
static inline void write_pmc_wake_level(u64 value)
|
|
{
|
|
pr_info("PMC wake level = 0x%llx\n", value);
|
|
tegra_pmc_writel((u32)value, PMC_WAKE_LEVEL);
|
|
if (tegra_chip_id != TEGRA20)
|
|
tegra_pmc_writel((u32)(value >> 32), PMC_WAKE2_LEVEL);
|
|
}
|
|
|
|
static inline u64 read_pmc_wake_status(void)
|
|
{
|
|
u64 reg;
|
|
|
|
reg = tegra_pmc_readl(PMC_WAKE_STATUS);
|
|
if (tegra_chip_id != TEGRA20)
|
|
reg |= ((u64)tegra_pmc_readl(PMC_WAKE2_STATUS)) << 32;
|
|
|
|
return reg;
|
|
}
|
|
|
|
static inline void clear_pmc_wake_status(void)
|
|
{
|
|
u32 reg;
|
|
|
|
reg = tegra_pmc_readl(PMC_WAKE_STATUS);
|
|
if (reg)
|
|
tegra_pmc_writel(reg, PMC_WAKE_STATUS);
|
|
if (tegra_chip_id != TEGRA20) {
|
|
reg = tegra_pmc_readl(PMC_WAKE2_STATUS);
|
|
if (reg)
|
|
tegra_pmc_writel(reg, PMC_WAKE2_STATUS);
|
|
}
|
|
}
|
|
|
|
static inline u64 read_pmc_sw_wake_status(void)
|
|
{
|
|
u64 reg;
|
|
|
|
reg = tegra_pmc_readl(PMC_SW_WAKE_STATUS);
|
|
if (tegra_chip_id != TEGRA20)
|
|
reg |= ((u64)tegra_pmc_readl(PMC_SW_WAKE2_STATUS)) << 32;
|
|
|
|
return reg;
|
|
}
|
|
|
|
static inline void clear_pmc_sw_wake_status(void)
|
|
{
|
|
tegra_pmc_writel(0, PMC_SW_WAKE_STATUS);
|
|
if (tegra_chip_id != TEGRA20)
|
|
tegra_pmc_writel(0, PMC_SW_WAKE2_STATUS);
|
|
}
|
|
|
|
/* translate lp0 wake sources back into irqs to catch edge triggered wakeups */
|
|
static void tegra_pmc_wake_irq_helper(unsigned long wake_status, u32 index)
|
|
{
|
|
u32 wake;
|
|
|
|
for_each_set_bit(wake, &wake_status, 32) {
|
|
struct pmc_wakeup *wake_source;
|
|
list_for_each_entry(wake_source,
|
|
&tegra_lp0_wakeup.wake_list, list) {
|
|
if (wake_source->wake_mask_offset ==
|
|
(wake + 32 * index)) {
|
|
struct irq_desc *desc;
|
|
|
|
if (wake_source->irq_num <= 0) {
|
|
pr_info("Resume caused by PMC WAKE%d\n",
|
|
(wake + 32 * index));
|
|
continue;
|
|
}
|
|
|
|
desc = irq_to_desc(wake_source->irq_num);
|
|
if (!desc || !desc->action ||
|
|
!desc->action->name) {
|
|
pr_info("Resume caused by PMC WAKE%d",
|
|
(wake + 32 * index));
|
|
pr_cont(", irq %d\n",
|
|
wake_source->irq_num);
|
|
continue;
|
|
}
|
|
|
|
pr_info("Resume caused by PMC WAKE%d, %s\n",
|
|
(wake + 32 * index),
|
|
desc->action->name);
|
|
generic_handle_irq(wake_source->irq_num);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void tegra_pmc_wake_syscore_resume(void)
|
|
{
|
|
u64 wake_status = read_pmc_wake_status();
|
|
|
|
pr_info("PMC wake status = 0x%llx\n", wake_status);
|
|
tegra_pmc_wake_irq_helper((unsigned long)wake_status, 0);
|
|
if (tegra_chip_id != TEGRA20)
|
|
tegra_pmc_wake_irq_helper((unsigned long)(wake_status >> 32),
|
|
1);
|
|
}
|
|
|
|
static int tegra_pmc_wake_syscore_suspend(void)
|
|
{
|
|
u32 reg;
|
|
u64 status;
|
|
u64 lvl;
|
|
u64 wake_level;
|
|
u64 wake_enb;
|
|
|
|
clear_pmc_sw_wake_status();
|
|
|
|
/* enable PMC wake */
|
|
reg = tegra_pmc_readl(PMC_CTRL);
|
|
reg |= PMC_CTRL_LATCH_WAKEUPS;
|
|
tegra_pmc_writel(reg, PMC_CTRL);
|
|
udelay(120);
|
|
|
|
reg &= ~PMC_CTRL_LATCH_WAKEUPS;
|
|
tegra_pmc_writel(reg, PMC_CTRL);
|
|
udelay(120);
|
|
|
|
status = read_pmc_sw_wake_status();
|
|
|
|
lvl = read_pmc_wake_level();
|
|
|
|
/*
|
|
* flip the wakeup trigger for any-edge triggered pads
|
|
* which are currently asserting as wakeups
|
|
*/
|
|
lvl ^= status;
|
|
|
|
lvl &= tegra_lp0_wakeup.level_any;
|
|
|
|
wake_level = lvl | tegra_lp0_wakeup.level;
|
|
wake_enb = tegra_lp0_wakeup.enable;
|
|
|
|
/* Clear PMC Wake Status registers while going to suspend */
|
|
clear_pmc_wake_status();
|
|
|
|
write_pmc_wake_level(wake_level);
|
|
|
|
write_pmc_wake_mask(wake_enb);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct syscore_ops tegra_pmc_wake_syscore_ops = {
|
|
.suspend = tegra_pmc_wake_syscore_suspend,
|
|
.resume = tegra_pmc_wake_syscore_resume,
|
|
};
|
|
|
|
static void tegra_pmc_wake_syscore_init(void)
|
|
{
|
|
register_syscore_ops(&tegra_pmc_wake_syscore_ops);
|
|
}
|
|
|
|
static void set_core_power_timers(void)
|
|
{
|
|
unsigned long osc, pmu, off;
|
|
|
|
osc = DIV_ROUND_UP_ULL(pmc_pm_data.core_osc_time * 32768, 1000000);
|
|
pmu = DIV_ROUND_UP_ULL(pmc_pm_data.core_pmu_time * 32768, 1000000);
|
|
off = DIV_ROUND_UP_ULL(pmc_pm_data.core_off_time * 32768, 1000000);
|
|
|
|
tegra_pmc_writel(((osc << 8) & 0xff00) | (pmu & 0xff),
|
|
PMC_COREPWRGOOD_TIMER);
|
|
tegra_pmc_writel(off, PMC_COREPWROFF_TIMER);
|
|
}
|
|
|
|
static void set_power_timers(u32 us_on, u32 us_off, unsigned long rate)
|
|
{
|
|
unsigned long long ticks;
|
|
unsigned long long pclk;
|
|
static unsigned long tegra_last_pclk;
|
|
|
|
if (WARN_ON_ONCE(rate <= 0))
|
|
pclk = 100000000;
|
|
else
|
|
pclk = rate;
|
|
|
|
if ((rate != tegra_last_pclk)) {
|
|
ticks = (us_on * pclk) + 999999ull;
|
|
do_div(ticks, 1000000);
|
|
tegra_pmc_writel((unsigned long)ticks, PMC_CPUPWRGOOD_TIMER);
|
|
|
|
ticks = (us_off * pclk) + 999999ull;
|
|
do_div(ticks, 1000000);
|
|
tegra_pmc_writel((unsigned long)ticks, PMC_CPUPWROFF_TIMER);
|
|
wmb();
|
|
}
|
|
tegra_last_pclk = pclk;
|
|
}
|
|
|
|
void tegra_pmc_remove_dpd_req(void)
|
|
{
|
|
/* Clear DPD req */
|
|
tegra_pmc_writel(io_dpd_reg | IO_DPD_OFF, PMC_IO_DPD_REQ);
|
|
tegra_pmc_readl(PMC_IO_DPD_REQ); /* unblock posted write */
|
|
/* delay apb_clk * (SEL_DPD_TIM*5) */
|
|
udelay(DPD_STATE_CHANGE_DELAY);
|
|
|
|
tegra_pmc_writel(io_dpd2_reg | IO_DPD_OFF, PMC_IO_DPD2_REQ);
|
|
tegra_pmc_readl(PMC_IO_DPD2_REQ); /* unblock posted write */
|
|
udelay(DPD_STATE_CHANGE_DELAY);
|
|
}
|
|
EXPORT_SYMBOL(tegra_pmc_remove_dpd_req);
|
|
|
|
void tegra_pmc_clear_dpd_sample(void)
|
|
{
|
|
/* Clear DPD sample */
|
|
tegra_pmc_writel(0x0, PMC_DPD_SAMPLE);
|
|
}
|
|
EXPORT_SYMBOL(tegra_pmc_clear_dpd_sample);
|
|
|
|
enum tegra_suspend_mode tegra_pmc_get_suspend_mode(void)
|
|
{
|
|
return pmc_pm_data.suspend_mode;
|
|
}
|
|
|
|
void tegra_pmc_set_suspend_mode(enum tegra_suspend_mode mode)
|
|
{
|
|
if (mode < TEGRA_SUSPEND_NONE || mode >= TEGRA_MAX_SUSPEND_MODE)
|
|
return;
|
|
|
|
pmc_pm_data.suspend_mode = mode;
|
|
}
|
|
|
|
void tegra_pmc_suspend(void)
|
|
{
|
|
tegra_pmc_writel(virt_to_phys(tegra_resume), PMC_SCRATCH41);
|
|
}
|
|
|
|
void tegra_pmc_resume(void)
|
|
{
|
|
/* Clear DPD Enable */
|
|
if (tegra_chip_id == TEGRA124)
|
|
tegra_pmc_writel(0x0, PMC_DPD_ENABLE);
|
|
|
|
tegra_pmc_writel(0x0, PMC_SCRATCH41);
|
|
}
|
|
|
|
void tegra_pmc_pm_set(enum tegra_suspend_mode mode)
|
|
{
|
|
u32 reg, csr_reg, boot_flag;
|
|
u32 us_off = pmc_pm_data.cpu_off_time;
|
|
unsigned long rate = 0;
|
|
|
|
reg = tegra_pmc_readl(PMC_CTRL);
|
|
reg |= TEGRA_POWER_CPU_PWRREQ_OE;
|
|
if (pmc_pm_data.combined_req)
|
|
reg &= ~TEGRA_POWER_PWRREQ_OE;
|
|
else
|
|
reg |= TEGRA_POWER_PWRREQ_OE;
|
|
reg &= ~TEGRA_POWER_EFFECT_LP0;
|
|
|
|
switch (tegra_chip_id) {
|
|
case TEGRA20:
|
|
case TEGRA30:
|
|
break;
|
|
default:
|
|
/* Turn off CRAIL */
|
|
if (mode != TEGRA_CLUSTER_SWITCH) {
|
|
csr_reg = flowctrl_read_cpu_csr(0);
|
|
csr_reg &= ~FLOW_CTRL_CSR_ENABLE_EXT_MASK;
|
|
if (is_lp_cluster())
|
|
csr_reg |= FLOW_CTRL_CSR_ENABLE_EXT_NCPU;
|
|
else
|
|
csr_reg |= FLOW_CTRL_CSR_ENABLE_EXT_CRAIL;
|
|
flowctrl_write_cpu_csr(0, csr_reg);
|
|
}
|
|
break;
|
|
}
|
|
|
|
switch (mode) {
|
|
case TEGRA_SUSPEND_LP0:
|
|
/*
|
|
* Enable DPD sample to trigger sampling pads data and direction
|
|
* in which pad will be driven during LP0 mode.
|
|
*/
|
|
tegra_pmc_writel(0x1, PMC_DPD_SAMPLE);
|
|
|
|
/*
|
|
* Power down IO logic
|
|
*/
|
|
switch (tegra_chip_id) {
|
|
case TEGRA114:
|
|
case TEGRA124:
|
|
io_dpd_reg = IO_DPD_CSIA | IO_DPD_CSIB | IO_DPD_DSI |
|
|
IO_DPD_MIPI_BIAS | IO_DPD_PEX_BIAS |
|
|
IO_DPD_PEX_CLK1 | IO_DPD_PEX_CLK2 |
|
|
IO_DPD_PEX_CLK3 | IO_DPD_DAC | IO_DPD_USB0 |
|
|
IO_DPD_USB1 | IO_DPD_USB2 | IO_DPD_USB_BIAS |
|
|
IO_DPD_UART | IO_DPD_BB | IO_DPD_VI |
|
|
IO_DPD_AUDIO | IO_DPD_LCD | IO_DPD_HSIC;
|
|
io_dpd2_reg = IO_DPD2_PEX_CNTRL | IO_DPD2_SDMMC1 |
|
|
IO_DPD2_SDMMC3 | IO_DPD2_SDMMC4 | IO_DPD2_CAM |
|
|
IO_DPD2_RES_RAIL | IO_DPD2_HV | IO_DPD2_DSIB |
|
|
IO_DPD2_DSIC | IO_DPD2_DSID | IO_DPD2_CSIC |
|
|
IO_DPD2_CSID | IO_DPD2_CSIE;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
tegra_pmc_writel(io_dpd_reg | IO_DPD_ON, PMC_IO_DPD_REQ);
|
|
tegra_pmc_readl(PMC_IO_DPD_REQ); /* unblock posted write */
|
|
|
|
/* delay apb_clk * (SEL_DPD_TIM*5) */
|
|
udelay(DPD_STATE_CHANGE_DELAY);
|
|
|
|
tegra_pmc_writel(io_dpd2_reg | IO_DPD_ON, PMC_IO_DPD2_REQ);
|
|
tegra_pmc_readl(PMC_IO_DPD2_REQ); /* unblock posted write */
|
|
udelay(DPD_STATE_CHANGE_DELAY);
|
|
|
|
/* Set warmboot flag */
|
|
boot_flag = tegra_pmc_readl(PMC_SCRATCH0);
|
|
tegra_pmc_writel(boot_flag | 1, PMC_SCRATCH0);
|
|
|
|
tegra_pmc_writel(pmc_pm_data.lp0_vec_phy_addr, PMC_SCRATCH1);
|
|
reg |= TEGRA_POWER_EFFECT_LP0;
|
|
case TEGRA_SUSPEND_LP1:
|
|
rate = 32768;
|
|
break;
|
|
case TEGRA_SUSPEND_LP2:
|
|
rate = __clk_get_rate(tegra_pclk);
|
|
break;
|
|
case TEGRA_CLUSTER_SWITCH:
|
|
rate = __clk_get_rate(tegra_pclk);
|
|
us_off = 2;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
set_power_timers(pmc_pm_data.cpu_good_time, us_off, rate);
|
|
|
|
tegra_pmc_writel(reg, PMC_CTRL);
|
|
}
|
|
|
|
void tegra_pmc_suspend_init(void)
|
|
{
|
|
u32 reg;
|
|
|
|
/* Always enable CPU power request */
|
|
reg = tegra_pmc_readl(PMC_CTRL);
|
|
reg |= TEGRA_POWER_CPU_PWRREQ_OE;
|
|
tegra_pmc_writel(reg, PMC_CTRL);
|
|
|
|
set_core_power_timers();
|
|
|
|
reg = tegra_pmc_readl(PMC_CTRL);
|
|
|
|
if (!pmc_pm_data.sysclkreq_high)
|
|
reg |= TEGRA_POWER_SYSCLK_POLARITY;
|
|
else
|
|
reg &= ~TEGRA_POWER_SYSCLK_POLARITY;
|
|
|
|
if (!pmc_pm_data.corereq_high)
|
|
reg |= TEGRA_POWER_PWRREQ_POLARITY;
|
|
else
|
|
reg &= ~TEGRA_POWER_PWRREQ_POLARITY;
|
|
|
|
/* configure the output polarity while the request is tristated */
|
|
tegra_pmc_writel(reg, PMC_CTRL);
|
|
|
|
/* now enable the request */
|
|
reg |= TEGRA_POWER_SYSCLK_OE;
|
|
tegra_pmc_writel(reg, PMC_CTRL);
|
|
|
|
tegra_pmc_wake_syscore_init();
|
|
}
|
|
|
|
/*
|
|
* When starting to enter LP0 without LP0 boot code, try to request the
|
|
* code with request_firmware, if it can't be loaded, switch to LP1.
|
|
*/
|
|
int tegra_pmc_suspend_valid(void)
|
|
{
|
|
const struct firmware *fw;
|
|
int ret;
|
|
uint8_t *fw_buff;
|
|
|
|
if (pmc_pm_data.suspend_mode != TEGRA_SUSPEND_LP0 ||
|
|
pmc_pm_data.lp0_vec_size)
|
|
return 0;
|
|
|
|
ret = request_firmware(&fw, LP0_FW_NAME, NULL);
|
|
if (ret) {
|
|
pr_info("Disabling LP0, no resume code found\n");
|
|
pmc_pm_data.suspend_mode = TEGRA_SUSPEND_LP1;
|
|
return 0;
|
|
}
|
|
|
|
fw_buff = kmalloc(fw->size, GFP_KERNEL);
|
|
if (!fw_buff) {
|
|
pr_debug("Couldn't allocate %zu bytes LP0 code, disabling\n",
|
|
fw->size);
|
|
pmc_pm_data.suspend_mode = TEGRA_SUSPEND_LP1;
|
|
goto suspend_check_done;
|
|
}
|
|
pr_info("Loaded LP0 firmware with request_firmware.\n");
|
|
|
|
memcpy(fw_buff, fw->data, fw->size);
|
|
pmc_pm_data.lp0_vec_phy_addr = virt_to_phys(fw_buff);
|
|
pmc_pm_data.lp0_vec_size = fw->size;
|
|
|
|
suspend_check_done:
|
|
release_firmware(fw);
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static const struct of_device_id matches[] __initconst = {
|
|
{ .compatible = "nvidia,tegra124-pmc" },
|
|
{ .compatible = "nvidia,tegra114-pmc" },
|
|
{ .compatible = "nvidia,tegra30-pmc" },
|
|
{ .compatible = "nvidia,tegra20-pmc" },
|
|
{ }
|
|
};
|
|
|
|
void __init tegra_pmc_init_irq(void)
|
|
{
|
|
struct device_node *np;
|
|
u32 val;
|
|
|
|
np = of_find_matching_node(NULL, matches);
|
|
BUG_ON(!np);
|
|
|
|
tegra_pmc_base = of_iomap(np, 0);
|
|
|
|
tegra_pmc_invert_interrupt = of_property_read_bool(np,
|
|
"nvidia,invert-interrupt");
|
|
|
|
val = tegra_pmc_readl(PMC_CTRL);
|
|
if (tegra_pmc_invert_interrupt)
|
|
val |= PMC_CTRL_INTR_LOW;
|
|
else
|
|
val &= ~PMC_CTRL_INTR_LOW;
|
|
tegra_pmc_writel(val, PMC_CTRL);
|
|
}
|
|
|
|
void __init tegra_pmc_init(void)
|
|
{
|
|
struct device_node *np;
|
|
u32 prop;
|
|
enum tegra_suspend_mode suspend_mode;
|
|
u32 core_good_time[2] = {0, 0};
|
|
u32 lp0_vec[2] = {0, 0};
|
|
|
|
np = of_find_matching_node(NULL, matches);
|
|
BUG_ON(!np);
|
|
|
|
tegra_pclk = of_clk_get_by_name(np, "pclk");
|
|
WARN_ON(IS_ERR(tegra_pclk));
|
|
|
|
/* Grabbing the power management configurations */
|
|
if (of_property_read_u32(np, "nvidia,suspend-mode", &prop)) {
|
|
suspend_mode = TEGRA_SUSPEND_NONE;
|
|
} else {
|
|
switch (prop) {
|
|
case 0:
|
|
suspend_mode = TEGRA_SUSPEND_LP0;
|
|
break;
|
|
case 1:
|
|
suspend_mode = TEGRA_SUSPEND_LP1;
|
|
break;
|
|
case 2:
|
|
suspend_mode = TEGRA_SUSPEND_LP2;
|
|
break;
|
|
default:
|
|
suspend_mode = TEGRA_SUSPEND_NONE;
|
|
break;
|
|
}
|
|
}
|
|
suspend_mode = tegra_pm_validate_suspend_mode(suspend_mode);
|
|
|
|
if (of_property_read_u32(np, "nvidia,cpu-pwr-good-time", &prop))
|
|
suspend_mode = TEGRA_SUSPEND_NONE;
|
|
pmc_pm_data.cpu_good_time = prop;
|
|
|
|
if (of_property_read_u32(np, "nvidia,cpu-pwr-off-time", &prop))
|
|
suspend_mode = TEGRA_SUSPEND_NONE;
|
|
pmc_pm_data.cpu_off_time = prop;
|
|
|
|
if (of_property_read_u32_array(np, "nvidia,core-pwr-good-time",
|
|
core_good_time, ARRAY_SIZE(core_good_time)))
|
|
suspend_mode = TEGRA_SUSPEND_NONE;
|
|
pmc_pm_data.core_osc_time = core_good_time[0];
|
|
pmc_pm_data.core_pmu_time = core_good_time[1];
|
|
|
|
if (of_property_read_u32(np, "nvidia,core-pwr-off-time",
|
|
&prop))
|
|
suspend_mode = TEGRA_SUSPEND_NONE;
|
|
pmc_pm_data.core_off_time = prop;
|
|
|
|
pmc_pm_data.corereq_high = of_property_read_bool(np,
|
|
"nvidia,core-power-req-active-high");
|
|
|
|
pmc_pm_data.sysclkreq_high = of_property_read_bool(np,
|
|
"nvidia,sys-clock-req-active-high");
|
|
|
|
pmc_pm_data.combined_req = of_property_read_bool(np,
|
|
"nvidia,combined-power-req");
|
|
|
|
pmc_pm_data.cpu_pwr_good_en = of_property_read_bool(np,
|
|
"nvidia,cpu-pwr-good-en");
|
|
|
|
/*
|
|
* If LP0 suspend is requested, try to load the address of the resume
|
|
* code. If the resume code isn't passed from the bootloader, an
|
|
* attempt to load it will be made at the first suspend.
|
|
*/
|
|
if (suspend_mode == TEGRA_SUSPEND_LP0 &&
|
|
!of_property_read_u32_array(np, "nvidia,lp0-vec", lp0_vec,
|
|
ARRAY_SIZE(lp0_vec))) {
|
|
pmc_pm_data.lp0_vec_phy_addr = lp0_vec[0];
|
|
pmc_pm_data.lp0_vec_size = lp0_vec[1];
|
|
|
|
if (!memblock_is_reserved(pmc_pm_data.lp0_vec_phy_addr)) {
|
|
pr_info("Suspend mode LP0 requested, but init failed");
|
|
pr_cont("-- disabling LP0\n");
|
|
suspend_mode = TEGRA_SUSPEND_LP1;
|
|
}
|
|
}
|
|
|
|
pmc_pm_data.suspend_mode = suspend_mode;
|
|
|
|
#ifdef CONFIG_PM_SLEEP
|
|
tegra_lp0_wakeup.of_node = np;
|
|
INIT_LIST_HEAD(&tegra_lp0_wakeup.wake_list);
|
|
#endif
|
|
}
|
|
|
|
void __init tegra_pmc_init_late(void)
|
|
{
|
|
struct device_node *np;
|
|
enum of_gpio_flags flags;
|
|
int ret;
|
|
|
|
np = of_find_matching_node(NULL, matches);
|
|
if (!np)
|
|
return;
|
|
|
|
pmc_pm_data.reset_gpio = of_get_named_gpio_flags(np,
|
|
"nvidia,reset-gpio", 0, &flags);
|
|
if (gpio_is_valid(pmc_pm_data.reset_gpio)) {
|
|
ret = gpio_request_one(pmc_pm_data.reset_gpio,
|
|
GPIOF_OUT_INIT_HIGH, "soc-warm-reset");
|
|
if (ret) {
|
|
pr_err("Failed to request reset GPIO: %d\n", ret);
|
|
pmc_pm_data.reset_gpio = -1;
|
|
}
|
|
if (flags & OF_GPIO_ACTIVE_LOW)
|
|
pmc_pm_data.reset_active_low = true;
|
|
}
|
|
}
|