diff options
Diffstat (limited to 'arch/arm/mach-tegra/pmc.c')
| -rw-r--r-- | arch/arm/mach-tegra/pmc.c | 152 | 
1 files changed, 122 insertions, 30 deletions
diff --git a/arch/arm/mach-tegra/pmc.c b/arch/arm/mach-tegra/pmc.c index d4fdb5fcec2..b30e921cc3a 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, @@ -18,57 +18,149 @@  #include <linux/kernel.h>  #include <linux/io.h>  #include <linux/of.h> +#include <linux/of_address.h> -#include "iomap.h" +#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_CTRL		0x0 -#define PMC_CTRL_INTR_LOW	(1 << 17) +#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 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); +} + +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_OF  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; -#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); + +	tegra_pmc_invert_interrupt = of_property_read_bool(np, +				     "nvidia,invert-interrupt"); +} + +void __init tegra_pmc_init(void) +{ +	u32 val; -		np = of_find_matching_node(NULL, matches); -		if (np) { -			if (of_find_property(np, "nvidia,invert-interrupt", -						NULL)) -				invert_interrupt = true; -		} -	} -#endif +	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;  |