diff options
Diffstat (limited to 'arch/arm/mach-tegra/pmc.c')
| -rw-r--r-- | arch/arm/mach-tegra/pmc.c | 310 | 
1 files changed, 282 insertions, 28 deletions
diff --git a/arch/arm/mach-tegra/pmc.c b/arch/arm/mach-tegra/pmc.c index d4fdb5fcec2..32360e540ce 100644 --- a/arch/arm/mach-tegra/pmc.c +++ b/arch/arm/mach-tegra/pmc.c @@ -1,5 +1,5 @@  /* - * Copyright (C) 2012 NVIDIA CORPORATION. All rights reserved. + * 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, @@ -16,59 +16,313 @@   */  #include <linux/kernel.h> +#include <linux/clk.h>  #include <linux/io.h>  #include <linux/of.h> +#include <linux/of_address.h> -#include "iomap.h" +#include "fuse.h" +#include "pm.h" +#include "pmc.h" +#include "sleep.h" -#define PMC_CTRL		0x0 -#define PMC_CTRL_INTR_LOW	(1 << 17) +#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_INTR_LOW		(1 << 17) +#define PMC_PWRGATE_TOGGLE		0x30 +#define PMC_PWRGATE_TOGGLE_START	(1 << 8) +#define PMC_REMOVE_CLAMPING		0x34 +#define PMC_PWRGATE_STATUS		0x38 + +#define PMC_CPUPWRGOOD_TIMER	0xc8 +#define PMC_CPUPWROFF_TIMER	0xcc + +#define TEGRA_POWERGATE_PCIE	3 +#define TEGRA_POWERGATE_VDEC	4 +#define TEGRA_POWERGATE_CPU1	9 +#define TEGRA_POWERGATE_CPU2	10 +#define TEGRA_POWERGATE_CPU3	11 + +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; +}; +static struct pmc_pm_data pmc_pm_data;  static inline u32 tegra_pmc_readl(u32 reg)  { -	return readl(IO_ADDRESS(TEGRA_PMC_BASE + reg)); +	return readl(tegra_pmc_base + reg);  }  static inline void tegra_pmc_writel(u32 val, u32 reg)  { -	writel(val, IO_ADDRESS(TEGRA_PMC_BASE + 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);  } -#ifdef CONFIG_OF +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); +} + +#ifdef CONFIG_PM_SLEEP +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; +} + +enum tegra_suspend_mode tegra_pmc_get_suspend_mode(void) +{ +	return pmc_pm_data.suspend_mode; +} + +void tegra_pmc_pm_set(enum tegra_suspend_mode mode) +{ +	u32 reg; +	unsigned long rate = 0; + +	reg = tegra_pmc_readl(PMC_CTRL); +	reg |= TEGRA_POWER_CPU_PWRREQ_OE; +	reg &= ~TEGRA_POWER_EFFECT_LP0; + +	switch (mode) { +	case TEGRA_SUSPEND_LP2: +		rate = clk_get_rate(tegra_pclk); +		break; +	default: +		break; +	} + +	set_power_timers(pmc_pm_data.cpu_good_time, pmc_pm_data.cpu_off_time, +			 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); +} +#endif +  static const struct of_device_id matches[] __initconst = { +	{ .compatible = "nvidia,tegra114-pmc" }, +	{ .compatible = "nvidia,tegra30-pmc" },  	{ .compatible = "nvidia,tegra20-pmc" },  	{ }  }; -#endif -void __init tegra_pmc_init(void) +static void tegra_pmc_parse_dt(void)  { -	/* -	 * For now, Harmony is the only board that uses the PMC, and it wants -	 * the signal inverted. Seaboard would too if it used the PMC. -	 * Hopefully by the time other boards want to use the PMC, everything -	 * will be device-tree, or they also want it inverted. -	 */ -	bool invert_interrupt = true; -	u32 val; +	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}; -#ifdef CONFIG_OF -	if (of_have_populated_dt()) { -		struct device_node *np; +	np = of_find_matching_node(NULL, matches); +	BUG_ON(!np); -		invert_interrupt = false; +	tegra_pmc_base = of_iomap(np, 0); -		np = of_find_matching_node(NULL, matches); -		if (np) { -			if (of_find_property(np, "nvidia,invert-interrupt", -						NULL)) -				invert_interrupt = true; +	tegra_pmc_invert_interrupt = of_property_read_bool(np, +				     "nvidia,invert-interrupt"); +	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;  		}  	} -#endif +	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 (of_property_read_u32_array(np, "nvidia,lp0-vec", lp0_vec, +				       ARRAY_SIZE(lp0_vec))) +		if (suspend_mode == TEGRA_SUSPEND_LP0) +			suspend_mode = TEGRA_SUSPEND_LP1; + +	pmc_pm_data.lp0_vec_phy_addr = lp0_vec[0]; +	pmc_pm_data.lp0_vec_size = lp0_vec[1]; + +	pmc_pm_data.suspend_mode = suspend_mode; +} + +void __init tegra_pmc_init(void) +{ +	u32 val; + +	tegra_pmc_parse_dt();  	val = tegra_pmc_readl(PMC_CTRL); -	if (invert_interrupt) +	if (tegra_pmc_invert_interrupt)  		val |= PMC_CTRL_INTR_LOW;  	else  		val &= ~PMC_CTRL_INTR_LOW;  |