forked from mirrors/linux
		
	ARM: tegra: Convert PMC to a driver
This commit converts the PMC support code to a platform driver. Because the boot process needs to call into this driver very early, also set up a minimal environment via an early initcall. Signed-off-by: Thierry Reding <treding@nvidia.com>
This commit is contained in:
		
							parent
							
								
									24fa5af810
								
							
						
					
					
						commit
						7232398abc
					
				
					 17 changed files with 1054 additions and 1020 deletions
				
			
		|  | @ -2,9 +2,7 @@ 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					+= pm.o | ||||
| obj-y					+= reset.o | ||||
| obj-y					+= reset-handler.o | ||||
|  |  | |||
|  | @ -28,13 +28,6 @@ | |||
| void __init tegra_map_common_io(void); | ||||
| void __init tegra_init_irq(void); | ||||
| 
 | ||||
| int __init tegra_powergate_init(void); | ||||
| #if defined(CONFIG_ARCH_TEGRA_2x_SOC) && defined(CONFIG_DEBUG_FS) | ||||
| int __init tegra_powergate_debugfs_init(void); | ||||
| #else | ||||
| static inline int tegra_powergate_debugfs_init(void) { return 0; } | ||||
| #endif | ||||
| 
 | ||||
| void __init tegra_paz00_wifikill_init(void); | ||||
| 
 | ||||
| #endif | ||||
|  |  | |||
|  | @ -22,6 +22,7 @@ | |||
| #include <linux/smp.h> | ||||
| 
 | ||||
| #include <soc/tegra/fuse.h> | ||||
| #include <soc/tegra/pmc.h> | ||||
| 
 | ||||
| #include <asm/cacheflush.h> | ||||
| #include <asm/mach-types.h> | ||||
|  | @ -31,7 +32,6 @@ | |||
| #include "common.h" | ||||
| #include "flowctrl.h" | ||||
| #include "iomap.h" | ||||
| #include "pmc.h" | ||||
| #include "reset.h" | ||||
| 
 | ||||
| static cpumask_t tegra_cpu_init_mask; | ||||
|  |  | |||
|  | @ -28,6 +28,8 @@ | |||
| #include <linux/suspend.h> | ||||
| 
 | ||||
| #include <soc/tegra/fuse.h> | ||||
| #include <soc/tegra/pm.h> | ||||
| #include <soc/tegra/pmc.h> | ||||
| 
 | ||||
| #include <asm/cacheflush.h> | ||||
| #include <asm/idmap.h> | ||||
|  | @ -38,7 +40,6 @@ | |||
| 
 | ||||
| #include "flowctrl.h" | ||||
| #include "iomap.h" | ||||
| #include "pmc.h" | ||||
| #include "pm.h" | ||||
| #include "reset.h" | ||||
| #include "sleep.h" | ||||
|  | @ -167,9 +168,29 @@ static int tegra_sleep_cpu(unsigned long v2p) | |||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static void tegra_pm_set(enum tegra_suspend_mode mode) | ||||
| { | ||||
| 	u32 value; | ||||
| 
 | ||||
| 	switch (tegra_get_chip_id()) { | ||||
| 	case TEGRA20: | ||||
| 	case TEGRA30: | ||||
| 		break; | ||||
| 	default: | ||||
| 		/* Turn off CRAIL */ | ||||
| 		value = flowctrl_read_cpu_csr(0); | ||||
| 		value &= ~FLOW_CTRL_CSR_ENABLE_EXT_MASK; | ||||
| 		value |= FLOW_CTRL_CSR_ENABLE_EXT_CRAIL; | ||||
| 		flowctrl_write_cpu_csr(0, value); | ||||
| 		break; | ||||
| 	} | ||||
| 
 | ||||
| 	tegra_pmc_enter_suspend_mode(mode); | ||||
| } | ||||
| 
 | ||||
| void tegra_idle_lp2_last(void) | ||||
| { | ||||
| 	tegra_pmc_pm_set(TEGRA_SUSPEND_LP2); | ||||
| 	tegra_pm_set(TEGRA_SUSPEND_LP2); | ||||
| 
 | ||||
| 	cpu_cluster_pm_enter(); | ||||
| 	suspend_cpu_complex(); | ||||
|  | @ -268,8 +289,6 @@ static bool tegra_sleep_core_init(void) | |||
| 
 | ||||
| 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_LPx_RESUME_AREA), | ||||
| 		iram_save_size); | ||||
|  | @ -281,8 +300,6 @@ static void tegra_suspend_enter_lp1(void) | |||
| 
 | ||||
| static void tegra_suspend_exit_lp1(void) | ||||
| { | ||||
| 	tegra_pmc_resume(); | ||||
| 
 | ||||
| 	/* restore IRAM */ | ||||
| 	memcpy(IO_ADDRESS(TEGRA_IRAM_LPx_RESUME_AREA), iram_save_addr, | ||||
| 		iram_save_size); | ||||
|  | @ -307,7 +324,7 @@ static int tegra_suspend_enter(suspend_state_t state) | |||
| 
 | ||||
| 	pr_info("Entering suspend state %s\n", lp_state[mode]); | ||||
| 
 | ||||
| 	tegra_pmc_pm_set(mode); | ||||
| 	tegra_pm_set(mode); | ||||
| 
 | ||||
| 	local_fiq_disable(); | ||||
| 
 | ||||
|  | @ -355,7 +372,6 @@ void __init tegra_init_suspend(void) | |||
| 		return; | ||||
| 
 | ||||
| 	tegra_tear_down_cpu_init(); | ||||
| 	tegra_pmc_suspend_init(); | ||||
| 
 | ||||
| 	if (mode >= TEGRA_SUSPEND_LP1) { | ||||
| 		if (!tegra_lp1_iram_hook() || !tegra_sleep_core_init()) { | ||||
|  |  | |||
|  | @ -21,12 +21,11 @@ | |||
| #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); | ||||
| 
 | ||||
|  | @ -42,15 +41,8 @@ void tegra_idle_lp2_last(void); | |||
| extern void (*tegra_tear_down_cpu)(void); | ||||
| 
 | ||||
| #ifdef CONFIG_PM_SLEEP | ||||
| 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 | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,414 +0,0 @@ | |||
| /*
 | ||||
|  * 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/clk.h> | ||||
| #include <linux/io.h> | ||||
| #include <linux/kernel.h> | ||||
| #include <linux/of.h> | ||||
| #include <linux/of_address.h> | ||||
| 
 | ||||
| #include <soc/tegra/fuse.h> | ||||
| #include <soc/tegra/powergate.h> | ||||
| 
 | ||||
| #include "flowctrl.h" | ||||
| #include "pm.h" | ||||
| #include "pmc.h" | ||||
| #include "sleep.h" | ||||
| 
 | ||||
| #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_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_SCRATCH0			0x50 | ||||
| #define PMC_SCRATCH0_MODE_RECOVERY	(1 << 31) | ||||
| #define PMC_SCRATCH0_MODE_BOOTLOADER	(1 << 30) | ||||
| #define PMC_SCRATCH0_MODE_RCM		(1 << 1) | ||||
| #define PMC_SCRATCH0_MODE_MASK		(PMC_SCRATCH0_MODE_RECOVERY | \ | ||||
| 					 PMC_SCRATCH0_MODE_BOOTLOADER | \ | ||||
| 					 PMC_SCRATCH0_MODE_RCM) | ||||
| 
 | ||||
| #define PMC_CPUPWRGOOD_TIMER	0xc8 | ||||
| #define PMC_CPUPWROFF_TIMER	0xcc | ||||
| 
 | ||||
| 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(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(enum reboot_mode mode, const char *cmd) | ||||
| { | ||||
| 	u32 val; | ||||
| 
 | ||||
| 	val = tegra_pmc_readl(PMC_SCRATCH0); | ||||
| 	val &= ~PMC_SCRATCH0_MODE_MASK; | ||||
| 
 | ||||
| 	if (cmd) { | ||||
| 		if (strcmp(cmd, "recovery") == 0) | ||||
| 			val |= PMC_SCRATCH0_MODE_RECOVERY; | ||||
| 
 | ||||
| 		if (strcmp(cmd, "bootloader") == 0) | ||||
| 			val |= PMC_SCRATCH0_MODE_BOOTLOADER; | ||||
| 
 | ||||
| 		if (strcmp(cmd, "forced-recovery") == 0) | ||||
| 			val |= PMC_SCRATCH0_MODE_RCM; | ||||
| 	} | ||||
| 
 | ||||
| 	tegra_pmc_writel(val, PMC_SCRATCH0); | ||||
| 
 | ||||
| 	val = tegra_pmc_readl(0); | ||||
| 	val |= 0x10; | ||||
| 	tegra_pmc_writel(val, 0); | ||||
| } | ||||
| 
 | ||||
| #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_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) | ||||
| { | ||||
| 	tegra_pmc_writel(0x0, PMC_SCRATCH41); | ||||
| } | ||||
| 
 | ||||
| void tegra_pmc_pm_set(enum tegra_suspend_mode mode) | ||||
| { | ||||
| 	u32 reg, csr_reg; | ||||
| 	unsigned long rate = 0; | ||||
| 
 | ||||
| 	reg = tegra_pmc_readl(PMC_CTRL); | ||||
| 	reg |= TEGRA_POWER_CPU_PWRREQ_OE; | ||||
| 	reg &= ~TEGRA_POWER_EFFECT_LP0; | ||||
| 
 | ||||
| 	switch (tegra_get_chip_id()) { | ||||
| 	case TEGRA20: | ||||
| 	case TEGRA30: | ||||
| 		break; | ||||
| 	default: | ||||
| 		/* Turn off CRAIL */ | ||||
| 		csr_reg = flowctrl_read_cpu_csr(0); | ||||
| 		csr_reg &= ~FLOW_CTRL_CSR_ENABLE_EXT_MASK; | ||||
| 		csr_reg |= FLOW_CTRL_CSR_ENABLE_EXT_CRAIL; | ||||
| 		flowctrl_write_cpu_csr(0, csr_reg); | ||||
| 		break; | ||||
| 	} | ||||
| 
 | ||||
| 	switch (mode) { | ||||
| 	case TEGRA_SUSPEND_LP1: | ||||
| 		rate = 32768; | ||||
| 		break; | ||||
| 	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); | ||||
| 
 | ||||
| 	reg = tegra_pmc_readl(PMC_CTRL); | ||||
| 
 | ||||
| 	if (!pmc_pm_data.sysclkreq_high) | ||||
| 		reg |= TEGRA_POWER_SYSCLK_POLARITY; | ||||
| 	else | ||||
| 		reg &= ~TEGRA_POWER_SYSCLK_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); | ||||
| } | ||||
| #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 (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; | ||||
| } | ||||
|  | @ -1,49 +0,0 @@ | |||
| /*
 | ||||
|  * 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_MAX_SUSPEND_MODE, | ||||
| }; | ||||
| 
 | ||||
| #ifdef CONFIG_PM_SLEEP | ||||
| 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); | ||||
| #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(enum reboot_mode mode, const char *cmd); | ||||
| 
 | ||||
| void tegra_pmc_init_irq(void); | ||||
| void tegra_pmc_init(void); | ||||
| 
 | ||||
| #endif | ||||
|  | @ -1,516 +0,0 @@ | |||
| /*
 | ||||
|  * 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/clk.h> | ||||
| #include <linux/clk/tegra.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/kernel.h> | ||||
| #include <linux/reset.h> | ||||
| #include <linux/seq_file.h> | ||||
| #include <linux/spinlock.h> | ||||
| 
 | ||||
| #include <soc/tegra/fuse.h> | ||||
| #include <soc/tegra/powergate.h> | ||||
| 
 | ||||
| #include "iomap.h" | ||||
| 
 | ||||
| #define DPD_SAMPLE		0x020 | ||||
| #define  DPD_SAMPLE_ENABLE	(1 << 0) | ||||
| #define  DPD_SAMPLE_DISABLE	(0 << 0) | ||||
| 
 | ||||
| #define PWRGATE_TOGGLE		0x30 | ||||
| #define  PWRGATE_TOGGLE_START	(1 << 8) | ||||
| 
 | ||||
| #define REMOVE_CLAMPING		0x34 | ||||
| 
 | ||||
| #define PWRGATE_STATUS		0x38 | ||||
| 
 | ||||
| #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 const u8 tegra30_cpu_domains[] = { | ||||
| 	TEGRA_POWERGATE_CPU, | ||||
| 	TEGRA_POWERGATE_CPU1, | ||||
| 	TEGRA_POWERGATE_CPU2, | ||||
| 	TEGRA_POWERGATE_CPU3, | ||||
| }; | ||||
| 
 | ||||
| static const u8 tegra114_cpu_domains[] = { | ||||
| 	TEGRA_POWERGATE_CPU0, | ||||
| 	TEGRA_POWERGATE_CPU1, | ||||
| 	TEGRA_POWERGATE_CPU2, | ||||
| 	TEGRA_POWERGATE_CPU3, | ||||
| }; | ||||
| 
 | ||||
| static const u8 tegra124_cpu_domains[] = { | ||||
| 	TEGRA_POWERGATE_CPU0, | ||||
| 	TEGRA_POWERGATE_CPU1, | ||||
| 	TEGRA_POWERGATE_CPU2, | ||||
| 	TEGRA_POWERGATE_CPU3, | ||||
| }; | ||||
| 
 | ||||
| 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; | ||||
| 
 | ||||
| 	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); | ||||
| 
 | ||||
| 	spin_unlock_irqrestore(&tegra_powergate_lock, flags); | ||||
| 
 | ||||
| 	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); | ||||
| } | ||||
| EXPORT_SYMBOL(tegra_powergate_power_off); | ||||
| 
 | ||||
| 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; | ||||
| 
 | ||||
| 	if (id < 0 || id >= tegra_num_powerdomains) | ||||
| 		return -EINVAL; | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * The Tegra124 GPU has a separate register (with different semantics) | ||||
| 	 * to remove clamps. | ||||
| 	 */ | ||||
| 	if (tegra_get_chip_id() == TEGRA124) { | ||||
| 		if (id == TEGRA_POWERGATE_3D) { | ||||
| 			pmc_write(0, GPU_RG_CNTRL); | ||||
| 			return 0; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * 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); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| EXPORT_SYMBOL(tegra_powergate_remove_clamping); | ||||
| 
 | ||||
| /* Must be called with clk disabled, and returns with clk enabled */ | ||||
| int tegra_powergate_sequence_power_up(int id, struct clk *clk, | ||||
| 					struct reset_control *rst) | ||||
| { | ||||
| 	int ret; | ||||
| 
 | ||||
| 	reset_control_assert(rst); | ||||
| 
 | ||||
| 	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); | ||||
| 	reset_control_deassert(rst); | ||||
| 
 | ||||
| 	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_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_get_chip_id()) { | ||||
| 	case TEGRA20: | ||||
| 		tegra_num_powerdomains = 7; | ||||
| 		break; | ||||
| 	case TEGRA30: | ||||
| 		tegra_num_powerdomains = 14; | ||||
| 		tegra_num_cpu_domains = 4; | ||||
| 		tegra_cpu_domains = tegra30_cpu_domains; | ||||
| 		break; | ||||
| 	case TEGRA114: | ||||
| 		tegra_num_powerdomains = 23; | ||||
| 		tegra_num_cpu_domains = 4; | ||||
| 		tegra_cpu_domains = tegra114_cpu_domains; | ||||
| 		break; | ||||
| 	case TEGRA124: | ||||
| 		tegra_num_powerdomains = 25; | ||||
| 		tegra_num_cpu_domains = 4; | ||||
| 		tegra_cpu_domains = tegra124_cpu_domains; | ||||
| 		break; | ||||
| 	default: | ||||
| 		/* Unknown Tegra variant. Disable powergating */ | ||||
| 		tegra_num_powerdomains = 0; | ||||
| 		break; | ||||
| 	} | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| #ifdef CONFIG_DEBUG_FS | ||||
| 
 | ||||
| static const char * const *powergate_name; | ||||
| 
 | ||||
| static const char * const powergate_name_t20[] = { | ||||
| 	[TEGRA_POWERGATE_CPU]	= "cpu", | ||||
| 	[TEGRA_POWERGATE_3D]	= "3d", | ||||
| 	[TEGRA_POWERGATE_VENC]	= "venc", | ||||
| 	[TEGRA_POWERGATE_VDEC]	= "vdec", | ||||
| 	[TEGRA_POWERGATE_PCIE]	= "pcie", | ||||
| 	[TEGRA_POWERGATE_L2]	= "l2", | ||||
| 	[TEGRA_POWERGATE_MPE]	= "mpe", | ||||
| }; | ||||
| 
 | ||||
| static const char * const powergate_name_t30[] = { | ||||
| 	[TEGRA_POWERGATE_CPU]	= "cpu0", | ||||
| 	[TEGRA_POWERGATE_3D]	= "3d0", | ||||
| 	[TEGRA_POWERGATE_VENC]	= "venc", | ||||
| 	[TEGRA_POWERGATE_VDEC]	= "vdec", | ||||
| 	[TEGRA_POWERGATE_PCIE]	= "pcie", | ||||
| 	[TEGRA_POWERGATE_L2]	= "l2", | ||||
| 	[TEGRA_POWERGATE_MPE]	= "mpe", | ||||
| 	[TEGRA_POWERGATE_HEG]	= "heg", | ||||
| 	[TEGRA_POWERGATE_SATA]	= "sata", | ||||
| 	[TEGRA_POWERGATE_CPU1]	= "cpu1", | ||||
| 	[TEGRA_POWERGATE_CPU2]	= "cpu2", | ||||
| 	[TEGRA_POWERGATE_CPU3]	= "cpu3", | ||||
| 	[TEGRA_POWERGATE_CELP]	= "celp", | ||||
| 	[TEGRA_POWERGATE_3D1]	= "3d1", | ||||
| }; | ||||
| 
 | ||||
| static const char * const powergate_name_t114[] = { | ||||
| 	[TEGRA_POWERGATE_CPU]	= "crail", | ||||
| 	[TEGRA_POWERGATE_3D]	= "3d", | ||||
| 	[TEGRA_POWERGATE_VENC]	= "venc", | ||||
| 	[TEGRA_POWERGATE_VDEC]	= "vdec", | ||||
| 	[TEGRA_POWERGATE_MPE]	= "mpe", | ||||
| 	[TEGRA_POWERGATE_HEG]	= "heg", | ||||
| 	[TEGRA_POWERGATE_CPU1]	= "cpu1", | ||||
| 	[TEGRA_POWERGATE_CPU2]	= "cpu2", | ||||
| 	[TEGRA_POWERGATE_CPU3]	= "cpu3", | ||||
| 	[TEGRA_POWERGATE_CELP]	= "celp", | ||||
| 	[TEGRA_POWERGATE_CPU0]	= "cpu0", | ||||
| 	[TEGRA_POWERGATE_C0NC]	= "c0nc", | ||||
| 	[TEGRA_POWERGATE_C1NC]	= "c1nc", | ||||
| 	[TEGRA_POWERGATE_DIS]	= "dis", | ||||
| 	[TEGRA_POWERGATE_DISB]	= "disb", | ||||
| 	[TEGRA_POWERGATE_XUSBA]	= "xusba", | ||||
| 	[TEGRA_POWERGATE_XUSBB]	= "xusbb", | ||||
| 	[TEGRA_POWERGATE_XUSBC]	= "xusbc", | ||||
| }; | ||||
| 
 | ||||
| static const char * const powergate_name_t124[] = { | ||||
| 	[TEGRA_POWERGATE_CPU]	= "crail", | ||||
| 	[TEGRA_POWERGATE_3D]	= "3d", | ||||
| 	[TEGRA_POWERGATE_VENC]	= "venc", | ||||
| 	[TEGRA_POWERGATE_PCIE]	= "pcie", | ||||
| 	[TEGRA_POWERGATE_VDEC]	= "vdec", | ||||
| 	[TEGRA_POWERGATE_L2]	= "l2", | ||||
| 	[TEGRA_POWERGATE_MPE]	= "mpe", | ||||
| 	[TEGRA_POWERGATE_HEG]	= "heg", | ||||
| 	[TEGRA_POWERGATE_SATA]	= "sata", | ||||
| 	[TEGRA_POWERGATE_CPU1]	= "cpu1", | ||||
| 	[TEGRA_POWERGATE_CPU2]	= "cpu2", | ||||
| 	[TEGRA_POWERGATE_CPU3]	= "cpu3", | ||||
| 	[TEGRA_POWERGATE_CELP]	= "celp", | ||||
| 	[TEGRA_POWERGATE_CPU0]	= "cpu0", | ||||
| 	[TEGRA_POWERGATE_C0NC]	= "c0nc", | ||||
| 	[TEGRA_POWERGATE_C1NC]	= "c1nc", | ||||
| 	[TEGRA_POWERGATE_SOR]	= "sor", | ||||
| 	[TEGRA_POWERGATE_DIS]	= "dis", | ||||
| 	[TEGRA_POWERGATE_DISB]	= "disb", | ||||
| 	[TEGRA_POWERGATE_XUSBA]	= "xusba", | ||||
| 	[TEGRA_POWERGATE_XUSBB]	= "xusbb", | ||||
| 	[TEGRA_POWERGATE_XUSBC]	= "xusbc", | ||||
| 	[TEGRA_POWERGATE_VIC]	= "vic", | ||||
| 	[TEGRA_POWERGATE_IRAM]	= "iram", | ||||
| }; | ||||
| 
 | ||||
| static int powergate_show(struct seq_file *s, void *data) | ||||
| { | ||||
| 	int i; | ||||
| 
 | ||||
| 	seq_printf(s, " powergate powered\n"); | ||||
| 	seq_printf(s, "------------------\n"); | ||||
| 
 | ||||
| 	for (i = 0; i < tegra_num_powerdomains; i++) { | ||||
| 		if (!powergate_name[i]) | ||||
| 			continue; | ||||
| 
 | ||||
| 		seq_printf(s, " %9s %7s\n", powergate_name[i], | ||||
| 			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; | ||||
| 
 | ||||
| 	switch (tegra_get_chip_id()) { | ||||
| 	case TEGRA20: | ||||
| 		powergate_name = powergate_name_t20; | ||||
| 		break; | ||||
| 	case TEGRA30: | ||||
| 		powergate_name = powergate_name_t30; | ||||
| 		break; | ||||
| 	case TEGRA114: | ||||
| 		powergate_name = powergate_name_t114; | ||||
| 		break; | ||||
| 	case TEGRA124: | ||||
| 		powergate_name = powergate_name_t124; | ||||
| 		break; | ||||
| 	} | ||||
| 
 | ||||
| 	if (powergate_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 = pmc_read(request); | ||||
| 	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; | ||||
| } | ||||
| EXPORT_SYMBOL(tegra_io_rail_power_on); | ||||
| 
 | ||||
| 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 = pmc_read(request); | ||||
| 	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; | ||||
| } | ||||
| EXPORT_SYMBOL(tegra_io_rail_power_off); | ||||
|  | @ -36,6 +36,7 @@ | |||
| #include <linux/usb/tegra_usb_phy.h> | ||||
| 
 | ||||
| #include <soc/tegra/fuse.h> | ||||
| #include <soc/tegra/pmc.h> | ||||
| 
 | ||||
| #include <asm/hardware/cache-l2x0.h> | ||||
| #include <asm/mach/arch.h> | ||||
|  | @ -49,7 +50,6 @@ | |||
| #include "cpuidle.h" | ||||
| #include "iomap.h" | ||||
| #include "irq.h" | ||||
| #include "pmc.h" | ||||
| #include "pm.h" | ||||
| #include "reset.h" | ||||
| #include "sleep.h" | ||||
|  | @ -74,12 +74,10 @@ static void __init tegra_init_early(void) | |||
| { | ||||
| 	of_register_trusted_foundations(); | ||||
| 	tegra_cpu_reset_handler_init(); | ||||
| 	tegra_powergate_init(); | ||||
| } | ||||
| 
 | ||||
| static void __init tegra_dt_init_irq(void) | ||||
| { | ||||
| 	tegra_pmc_init_irq(); | ||||
| 	tegra_init_irq(); | ||||
| 	irqchip_init(); | ||||
| 	tegra_legacy_irq_syscore_init(); | ||||
|  | @ -91,8 +89,6 @@ static void __init tegra_dt_init(void) | |||
| 	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); | ||||
|  | @ -142,7 +138,6 @@ static void __init tegra_dt_init_late(void) | |||
| 
 | ||||
| 	tegra_init_suspend(); | ||||
| 	tegra_cpuidle_init(); | ||||
| 	tegra_powergate_debugfs_init(); | ||||
| 
 | ||||
| 	for (i = 0; i < ARRAY_SIZE(board_init_funcs); i++) { | ||||
| 		if (of_machine_is_compatible(board_init_funcs[i].machine)) { | ||||
|  |  | |||
|  | @ -23,7 +23,7 @@ | |||
| #include <linux/of_address.h> | ||||
| #include <linux/clk/tegra.h> | ||||
| 
 | ||||
| #include <soc/tegra/powergate.h> | ||||
| #include <soc/tegra/pmc.h> | ||||
| 
 | ||||
| #include <dt-bindings/clock/tegra30-car.h> | ||||
| 
 | ||||
|  |  | |||
|  | @ -13,7 +13,7 @@ | |||
| #include <linux/platform_device.h> | ||||
| #include <linux/reset.h> | ||||
| 
 | ||||
| #include <soc/tegra/powergate.h> | ||||
| #include <soc/tegra/pmc.h> | ||||
| 
 | ||||
| #include "drm.h" | ||||
| #include "gem.h" | ||||
|  |  | |||
|  | @ -12,7 +12,7 @@ | |||
| #include <linux/platform_device.h> | ||||
| #include <linux/reset.h> | ||||
| 
 | ||||
| #include <soc/tegra/powergate.h> | ||||
| #include <soc/tegra/pmc.h> | ||||
| 
 | ||||
| #include <drm/drm_dp_helper.h> | ||||
| 
 | ||||
|  |  | |||
|  | @ -45,7 +45,7 @@ | |||
| #include <linux/regulator/consumer.h> | ||||
| 
 | ||||
| #include <soc/tegra/cpuidle.h> | ||||
| #include <soc/tegra/powergate.h> | ||||
| #include <soc/tegra/pmc.h> | ||||
| 
 | ||||
| #include <asm/mach/irq.h> | ||||
| #include <asm/mach/map.h> | ||||
|  |  | |||
|  | @ -1,3 +1,4 @@ | |||
| obj-$(CONFIG_ARCH_TEGRA) += fuse/ | ||||
| 
 | ||||
| obj-$(CONFIG_ARCH_TEGRA) += common.o | ||||
| obj-$(CONFIG_ARCH_TEGRA) += pmc.o | ||||
|  |  | |||
							
								
								
									
										957
									
								
								drivers/soc/tegra/pmc.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										957
									
								
								drivers/soc/tegra/pmc.c
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,957 @@ | |||
| /*
 | ||||
|  * drivers/soc/tegra/pmc.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/clk/tegra.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/of.h> | ||||
| #include <linux/of_address.h> | ||||
| #include <linux/platform_device.h> | ||||
| #include <linux/reboot.h> | ||||
| #include <linux/reset.h> | ||||
| #include <linux/seq_file.h> | ||||
| #include <linux/spinlock.h> | ||||
| 
 | ||||
| #include <soc/tegra/common.h> | ||||
| #include <soc/tegra/fuse.h> | ||||
| #include <soc/tegra/pmc.h> | ||||
| 
 | ||||
| #define PMC_CNTRL			0x0 | ||||
| #define  PMC_CNTRL_SYSCLK_POLARITY	(1 << 10)  /* sys clk polarity */ | ||||
| #define  PMC_CNTRL_SYSCLK_OE		(1 << 11)  /* system clock enable */ | ||||
| #define  PMC_CNTRL_SIDE_EFFECT_LP0	(1 << 14)  /* LP0 when CPU pwr gated */ | ||||
| #define  PMC_CNTRL_CPU_PWRREQ_POLARITY	(1 << 15)  /* CPU pwr req polarity */ | ||||
| #define  PMC_CNTRL_CPU_PWRREQ_OE	(1 << 16)  /* CPU pwr req enable */ | ||||
| #define  PMC_CNTRL_INTR_POLARITY	(1 << 17)  /* inverts INTR polarity */ | ||||
| 
 | ||||
| #define DPD_SAMPLE			0x020 | ||||
| #define  DPD_SAMPLE_ENABLE		(1 << 0) | ||||
| #define  DPD_SAMPLE_DISABLE		(0 << 0) | ||||
| 
 | ||||
| #define PWRGATE_TOGGLE			0x30 | ||||
| #define  PWRGATE_TOGGLE_START		(1 << 8) | ||||
| 
 | ||||
| #define REMOVE_CLAMPING			0x34 | ||||
| 
 | ||||
| #define PWRGATE_STATUS			0x38 | ||||
| 
 | ||||
| #define PMC_SCRATCH0			0x50 | ||||
| #define  PMC_SCRATCH0_MODE_RECOVERY	(1 << 31) | ||||
| #define  PMC_SCRATCH0_MODE_BOOTLOADER	(1 << 30) | ||||
| #define  PMC_SCRATCH0_MODE_RCM		(1 << 1) | ||||
| #define  PMC_SCRATCH0_MODE_MASK		(PMC_SCRATCH0_MODE_RECOVERY | \ | ||||
| 					 PMC_SCRATCH0_MODE_BOOTLOADER | \ | ||||
| 					 PMC_SCRATCH0_MODE_RCM) | ||||
| 
 | ||||
| #define PMC_CPUPWRGOOD_TIMER		0xc8 | ||||
| #define PMC_CPUPWROFF_TIMER		0xcc | ||||
| 
 | ||||
| #define PMC_SCRATCH41			0x140 | ||||
| 
 | ||||
| #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 | ||||
| 
 | ||||
| struct tegra_pmc_soc { | ||||
| 	unsigned int num_powergates; | ||||
| 	const char *const *powergates; | ||||
| 	unsigned int num_cpu_powergates; | ||||
| 	const u8 *cpu_powergates; | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
|  * struct tegra_pmc - NVIDIA Tegra PMC | ||||
|  * @base: pointer to I/O remapped register region | ||||
|  * @clk: pointer to pclk clock | ||||
|  * @rate: currently configured rate of pclk | ||||
|  * @suspend_mode: lowest suspend mode available | ||||
|  * @cpu_good_time: CPU power good time (in microseconds) | ||||
|  * @cpu_off_time: CPU power off time (in microsecends) | ||||
|  * @core_osc_time: core power good OSC time (in microseconds) | ||||
|  * @core_pmu_time: core power good PMU time (in microseconds) | ||||
|  * @core_off_time: core power off time (in microseconds) | ||||
|  * @corereq_high: core power request is active-high | ||||
|  * @sysclkreq_high: system clock request is active-high | ||||
|  * @combined_req: combined power request for CPU & core | ||||
|  * @cpu_pwr_good_en: CPU power good signal is enabled | ||||
|  * @lp0_vec_phys: physical base address of the LP0 warm boot code | ||||
|  * @lp0_vec_size: size of the LP0 warm boot code | ||||
|  * @powergates_lock: mutex for power gate register access | ||||
|  */ | ||||
| struct tegra_pmc { | ||||
| 	void __iomem *base; | ||||
| 	struct clk *clk; | ||||
| 
 | ||||
| 	const struct tegra_pmc_soc *soc; | ||||
| 
 | ||||
| 	unsigned long rate; | ||||
| 
 | ||||
| 	enum tegra_suspend_mode suspend_mode; | ||||
| 	u32 cpu_good_time; | ||||
| 	u32 cpu_off_time; | ||||
| 	u32 core_osc_time; | ||||
| 	u32 core_pmu_time; | ||||
| 	u32 core_off_time; | ||||
| 	bool corereq_high; | ||||
| 	bool sysclkreq_high; | ||||
| 	bool combined_req; | ||||
| 	bool cpu_pwr_good_en; | ||||
| 	u32 lp0_vec_phys; | ||||
| 	u32 lp0_vec_size; | ||||
| 
 | ||||
| 	struct mutex powergates_lock; | ||||
| }; | ||||
| 
 | ||||
| static struct tegra_pmc *pmc = &(struct tegra_pmc) { | ||||
| 	.base = NULL, | ||||
| 	.suspend_mode = TEGRA_SUSPEND_NONE, | ||||
| }; | ||||
| 
 | ||||
| static u32 tegra_pmc_readl(unsigned long offset) | ||||
| { | ||||
| 	return readl(pmc->base + offset); | ||||
| } | ||||
| 
 | ||||
| static void tegra_pmc_writel(u32 value, unsigned long offset) | ||||
| { | ||||
| 	writel(value, pmc->base + offset); | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * tegra_powergate_set() - set the state of a partition | ||||
|  * @id: partition ID | ||||
|  * @new_state: new state of the partition | ||||
|  */ | ||||
| static int tegra_powergate_set(int id, bool new_state) | ||||
| { | ||||
| 	bool status; | ||||
| 
 | ||||
| 	mutex_lock(&pmc->powergates_lock); | ||||
| 
 | ||||
| 	status = tegra_pmc_readl(PWRGATE_STATUS) & (1 << id); | ||||
| 
 | ||||
| 	if (status == new_state) { | ||||
| 		mutex_unlock(&pmc->powergates_lock); | ||||
| 		return 0; | ||||
| 	} | ||||
| 
 | ||||
| 	tegra_pmc_writel(PWRGATE_TOGGLE_START | id, PWRGATE_TOGGLE); | ||||
| 
 | ||||
| 	mutex_unlock(&pmc->powergates_lock); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * tegra_powergate_power_on() - power on partition | ||||
|  * @id: partition ID | ||||
|  */ | ||||
| int tegra_powergate_power_on(int id) | ||||
| { | ||||
| 	if (!pmc->soc || id < 0 || id >= pmc->soc->num_powergates) | ||||
| 		return -EINVAL; | ||||
| 
 | ||||
| 	return tegra_powergate_set(id, true); | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * tegra_powergate_power_off() - power off partition | ||||
|  * @id: partition ID | ||||
|  */ | ||||
| int tegra_powergate_power_off(int id) | ||||
| { | ||||
| 	if (!pmc->soc || id < 0 || id >= pmc->soc->num_powergates) | ||||
| 		return -EINVAL; | ||||
| 
 | ||||
| 	return tegra_powergate_set(id, false); | ||||
| } | ||||
| EXPORT_SYMBOL(tegra_powergate_power_off); | ||||
| 
 | ||||
| /**
 | ||||
|  * tegra_powergate_is_powered() - check if partition is powered | ||||
|  * @id: partition ID | ||||
|  */ | ||||
| int tegra_powergate_is_powered(int id) | ||||
| { | ||||
| 	u32 status; | ||||
| 
 | ||||
| 	if (!pmc->soc || id < 0 || id >= pmc->soc->num_powergates) | ||||
| 		return -EINVAL; | ||||
| 
 | ||||
| 	status = tegra_pmc_readl(PWRGATE_STATUS) & (1 << id); | ||||
| 	return !!status; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * tegra_powergate_remove_clamping() - remove power clamps for partition | ||||
|  * @id: partition ID | ||||
|  */ | ||||
| int tegra_powergate_remove_clamping(int id) | ||||
| { | ||||
| 	u32 mask; | ||||
| 
 | ||||
| 	if (!pmc->soc || id < 0 || id >= pmc->soc->num_powergates) | ||||
| 		return -EINVAL; | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * The Tegra124 GPU has a separate register (with different semantics) | ||||
| 	 * to remove clamps. | ||||
| 	 */ | ||||
| 	if (tegra_get_chip_id() == TEGRA124) { | ||||
| 		if (id == TEGRA_POWERGATE_3D) { | ||||
| 			tegra_pmc_writel(0, GPU_RG_CNTRL); | ||||
| 			return 0; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * 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); | ||||
| 
 | ||||
| 	tegra_pmc_writel(mask, REMOVE_CLAMPING); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| EXPORT_SYMBOL(tegra_powergate_remove_clamping); | ||||
| 
 | ||||
| /**
 | ||||
|  * tegra_powergate_sequence_power_up() - power up partition | ||||
|  * @id: partition ID | ||||
|  * @clk: clock for partition | ||||
|  * @rst: reset for partition | ||||
|  * | ||||
|  * Must be called with clk disabled, and returns with clk enabled. | ||||
|  */ | ||||
| int tegra_powergate_sequence_power_up(int id, struct clk *clk, | ||||
| 				      struct reset_control *rst) | ||||
| { | ||||
| 	int ret; | ||||
| 
 | ||||
| 	reset_control_assert(rst); | ||||
| 
 | ||||
| 	ret = tegra_powergate_power_on(id); | ||||
| 	if (ret) | ||||
| 		goto err_power; | ||||
| 
 | ||||
| 	ret = clk_prepare_enable(clk); | ||||
| 	if (ret) | ||||
| 		goto err_clk; | ||||
| 
 | ||||
| 	usleep_range(10, 20); | ||||
| 
 | ||||
| 	ret = tegra_powergate_remove_clamping(id); | ||||
| 	if (ret) | ||||
| 		goto err_clamp; | ||||
| 
 | ||||
| 	usleep_range(10, 20); | ||||
| 	reset_control_deassert(rst); | ||||
| 
 | ||||
| 	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); | ||||
| 
 | ||||
| #ifdef CONFIG_SMP | ||||
| /**
 | ||||
|  * tegra_get_cpu_powergate_id() - convert from CPU ID to partition ID | ||||
|  * @cpuid: CPU partition ID | ||||
|  * | ||||
|  * Returns the partition ID corresponding to the CPU partition ID or a | ||||
|  * negative error code on failure. | ||||
|  */ | ||||
| static int tegra_get_cpu_powergate_id(int cpuid) | ||||
| { | ||||
| 	if (pmc->soc && cpuid > 0 && cpuid < pmc->soc->num_cpu_powergates) | ||||
| 		return pmc->soc->cpu_powergates[cpuid]; | ||||
| 
 | ||||
| 	return -EINVAL; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * tegra_pmc_cpu_is_powered() - check if CPU partition is powered | ||||
|  * @cpuid: CPU partition ID | ||||
|  */ | ||||
| bool tegra_pmc_cpu_is_powered(int cpuid) | ||||
| { | ||||
| 	int id; | ||||
| 
 | ||||
| 	id = tegra_get_cpu_powergate_id(cpuid); | ||||
| 	if (id < 0) | ||||
| 		return false; | ||||
| 
 | ||||
| 	return tegra_powergate_is_powered(id); | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * tegra_pmc_cpu_power_on() - power on CPU partition | ||||
|  * @cpuid: CPU partition ID | ||||
|  */ | ||||
| int tegra_pmc_cpu_power_on(int cpuid) | ||||
| { | ||||
| 	int id; | ||||
| 
 | ||||
| 	id = tegra_get_cpu_powergate_id(cpuid); | ||||
| 	if (id < 0) | ||||
| 		return id; | ||||
| 
 | ||||
| 	return tegra_powergate_set(id, true); | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * tegra_pmc_cpu_remove_clamping() - remove power clamps for CPU partition | ||||
|  * @cpuid: CPU partition ID | ||||
|  */ | ||||
| int tegra_pmc_cpu_remove_clamping(int cpuid) | ||||
| { | ||||
| 	int id; | ||||
| 
 | ||||
| 	id = tegra_get_cpu_powergate_id(cpuid); | ||||
| 	if (id < 0) | ||||
| 		return id; | ||||
| 
 | ||||
| 	return tegra_powergate_remove_clamping(id); | ||||
| } | ||||
| #endif /* CONFIG_SMP */ | ||||
| 
 | ||||
| /**
 | ||||
|  * tegra_pmc_restart() - reboot the system | ||||
|  * @mode: which mode to reboot in | ||||
|  * @cmd: reboot command | ||||
|  */ | ||||
| void tegra_pmc_restart(enum reboot_mode mode, const char *cmd) | ||||
| { | ||||
| 	u32 value; | ||||
| 
 | ||||
| 	value = tegra_pmc_readl(PMC_SCRATCH0); | ||||
| 	value &= ~PMC_SCRATCH0_MODE_MASK; | ||||
| 
 | ||||
| 	if (cmd) { | ||||
| 		if (strcmp(cmd, "recovery") == 0) | ||||
| 			value |= PMC_SCRATCH0_MODE_RECOVERY; | ||||
| 
 | ||||
| 		if (strcmp(cmd, "bootloader") == 0) | ||||
| 			value |= PMC_SCRATCH0_MODE_BOOTLOADER; | ||||
| 
 | ||||
| 		if (strcmp(cmd, "forced-recovery") == 0) | ||||
| 			value |= PMC_SCRATCH0_MODE_RCM; | ||||
| 	} | ||||
| 
 | ||||
| 	tegra_pmc_writel(value, PMC_SCRATCH0); | ||||
| 
 | ||||
| 	value = tegra_pmc_readl(0); | ||||
| 	value |= 0x10; | ||||
| 	tegra_pmc_writel(value, 0); | ||||
| } | ||||
| 
 | ||||
| static int powergate_show(struct seq_file *s, void *data) | ||||
| { | ||||
| 	unsigned int i; | ||||
| 
 | ||||
| 	seq_printf(s, " powergate powered\n"); | ||||
| 	seq_printf(s, "------------------\n"); | ||||
| 
 | ||||
| 	for (i = 0; i < pmc->soc->num_powergates; i++) { | ||||
| 		if (!pmc->soc->powergates[i]) | ||||
| 			continue; | ||||
| 
 | ||||
| 		seq_printf(s, " %9s %7s\n", pmc->soc->powergates[i], | ||||
| 			   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, | ||||
| }; | ||||
| 
 | ||||
| static int tegra_powergate_debugfs_init(void) | ||||
| { | ||||
| 	struct dentry *d; | ||||
| 
 | ||||
| 	d = debugfs_create_file("powergate", S_IRUGO, NULL, NULL, | ||||
| 				&powergate_fops); | ||||
| 	if (!d) | ||||
| 		return -ENOMEM; | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| 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); | ||||
| 
 | ||||
| 	tegra_pmc_writel(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); | ||||
| 	tegra_pmc_writel(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 = tegra_pmc_readl(offset); | ||||
| 		if ((value & mask) == val) | ||||
| 			return 0; | ||||
| 
 | ||||
| 		usleep_range(250, 1000); | ||||
| 	} | ||||
| 
 | ||||
| 	return -ETIMEDOUT; | ||||
| } | ||||
| 
 | ||||
| static void tegra_io_rail_unprepare(void) | ||||
| { | ||||
| 	tegra_pmc_writel(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 = tegra_pmc_readl(request); | ||||
| 	value |= mask; | ||||
| 	value &= ~IO_DPD_REQ_CODE_MASK; | ||||
| 	value |= IO_DPD_REQ_CODE_OFF; | ||||
| 	tegra_pmc_writel(value, request); | ||||
| 
 | ||||
| 	err = tegra_io_rail_poll(status, mask, 0, 250); | ||||
| 	if (err < 0) | ||||
| 		return err; | ||||
| 
 | ||||
| 	tegra_io_rail_unprepare(); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| EXPORT_SYMBOL(tegra_io_rail_power_on); | ||||
| 
 | ||||
| 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 = tegra_pmc_readl(request); | ||||
| 	value |= mask; | ||||
| 	value &= ~IO_DPD_REQ_CODE_MASK; | ||||
| 	value |= IO_DPD_REQ_CODE_ON; | ||||
| 	tegra_pmc_writel(value, request); | ||||
| 
 | ||||
| 	err = tegra_io_rail_poll(status, mask, mask, 250); | ||||
| 	if (err < 0) | ||||
| 		return err; | ||||
| 
 | ||||
| 	tegra_io_rail_unprepare(); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| EXPORT_SYMBOL(tegra_io_rail_power_off); | ||||
| 
 | ||||
| #ifdef CONFIG_PM_SLEEP | ||||
| enum tegra_suspend_mode tegra_pmc_get_suspend_mode(void) | ||||
| { | ||||
| 	return pmc->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->suspend_mode = mode; | ||||
| } | ||||
| 
 | ||||
| void tegra_pmc_enter_suspend_mode(enum tegra_suspend_mode mode) | ||||
| { | ||||
| 	unsigned long long rate = 0; | ||||
| 	u32 value; | ||||
| 
 | ||||
| 	switch (mode) { | ||||
| 	case TEGRA_SUSPEND_LP1: | ||||
| 		rate = 32768; | ||||
| 		break; | ||||
| 
 | ||||
| 	case TEGRA_SUSPEND_LP2: | ||||
| 		rate = clk_get_rate(pmc->clk); | ||||
| 		break; | ||||
| 
 | ||||
| 	default: | ||||
| 		break; | ||||
| 	} | ||||
| 
 | ||||
| 	if (WARN_ON_ONCE(rate == 0)) | ||||
| 		rate = 100000000; | ||||
| 
 | ||||
| 	if (rate != pmc->rate) { | ||||
| 		u64 ticks; | ||||
| 
 | ||||
| 		ticks = pmc->cpu_good_time * rate + USEC_PER_SEC - 1; | ||||
| 		do_div(ticks, USEC_PER_SEC); | ||||
| 		tegra_pmc_writel(ticks, PMC_CPUPWRGOOD_TIMER); | ||||
| 
 | ||||
| 		ticks = pmc->cpu_off_time * rate + USEC_PER_SEC - 1; | ||||
| 		do_div(ticks, USEC_PER_SEC); | ||||
| 		tegra_pmc_writel(ticks, PMC_CPUPWROFF_TIMER); | ||||
| 
 | ||||
| 		wmb(); | ||||
| 
 | ||||
| 		pmc->rate = rate; | ||||
| 	} | ||||
| 
 | ||||
| 	value = tegra_pmc_readl(PMC_CNTRL); | ||||
| 	value &= ~PMC_CNTRL_SIDE_EFFECT_LP0; | ||||
| 	value |= PMC_CNTRL_CPU_PWRREQ_OE; | ||||
| 	tegra_pmc_writel(value, PMC_CNTRL); | ||||
| } | ||||
| #endif | ||||
| 
 | ||||
| static int tegra_pmc_parse_dt(struct tegra_pmc *pmc, struct device_node *np) | ||||
| { | ||||
| 	u32 value, values[2]; | ||||
| 
 | ||||
| 	if (of_property_read_u32(np, "nvidia,suspend-mode", &value)) { | ||||
| 	} else { | ||||
| 		switch (value) { | ||||
| 		case 0: | ||||
| 			pmc->suspend_mode = TEGRA_SUSPEND_LP0; | ||||
| 			break; | ||||
| 
 | ||||
| 		case 1: | ||||
| 			pmc->suspend_mode = TEGRA_SUSPEND_LP1; | ||||
| 			break; | ||||
| 
 | ||||
| 		case 2: | ||||
| 			pmc->suspend_mode = TEGRA_SUSPEND_LP2; | ||||
| 			break; | ||||
| 
 | ||||
| 		default: | ||||
| 			pmc->suspend_mode = TEGRA_SUSPEND_NONE; | ||||
| 			break; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	pmc->suspend_mode = tegra_pm_validate_suspend_mode(pmc->suspend_mode); | ||||
| 
 | ||||
| 	if (of_property_read_u32(np, "nvidia,cpu-pwr-good-time", &value)) | ||||
| 		pmc->suspend_mode = TEGRA_SUSPEND_NONE; | ||||
| 
 | ||||
| 	pmc->cpu_good_time = value; | ||||
| 
 | ||||
| 	if (of_property_read_u32(np, "nvidia,cpu-pwr-off-time", &value)) | ||||
| 		pmc->suspend_mode = TEGRA_SUSPEND_NONE; | ||||
| 
 | ||||
| 	pmc->cpu_off_time = value; | ||||
| 
 | ||||
| 	if (of_property_read_u32_array(np, "nvidia,core-pwr-good-time", | ||||
| 				       values, ARRAY_SIZE(values))) | ||||
| 		pmc->suspend_mode = TEGRA_SUSPEND_NONE; | ||||
| 
 | ||||
| 	pmc->core_osc_time = values[0]; | ||||
| 	pmc->core_pmu_time = values[1]; | ||||
| 
 | ||||
| 	if (of_property_read_u32(np, "nvidia,core-pwr-off-time", &value)) | ||||
| 		pmc->suspend_mode = TEGRA_SUSPEND_NONE; | ||||
| 
 | ||||
| 	pmc->core_off_time = value; | ||||
| 
 | ||||
| 	pmc->corereq_high = of_property_read_bool(np, | ||||
| 				"nvidia,core-power-req-active-high"); | ||||
| 
 | ||||
| 	pmc->sysclkreq_high = of_property_read_bool(np, | ||||
| 				"nvidia,sys-clock-req-active-high"); | ||||
| 
 | ||||
| 	pmc->combined_req = of_property_read_bool(np, | ||||
| 				"nvidia,combined-power-req"); | ||||
| 
 | ||||
| 	pmc->cpu_pwr_good_en = of_property_read_bool(np, | ||||
| 				"nvidia,cpu-pwr-good-en"); | ||||
| 
 | ||||
| 	if (of_property_read_u32_array(np, "nvidia,lp0-vec", values, | ||||
| 				       ARRAY_SIZE(values))) | ||||
| 		if (pmc->suspend_mode == TEGRA_SUSPEND_LP0) | ||||
| 			pmc->suspend_mode = TEGRA_SUSPEND_LP1; | ||||
| 
 | ||||
| 	pmc->lp0_vec_phys = values[0]; | ||||
| 	pmc->lp0_vec_size = values[1]; | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static void tegra_pmc_init(struct tegra_pmc *pmc) | ||||
| { | ||||
| 	u32 value; | ||||
| 
 | ||||
| 	/* Always enable CPU power request */ | ||||
| 	value = tegra_pmc_readl(PMC_CNTRL); | ||||
| 	value |= PMC_CNTRL_CPU_PWRREQ_OE; | ||||
| 	tegra_pmc_writel(value, PMC_CNTRL); | ||||
| 
 | ||||
| 	value = tegra_pmc_readl(PMC_CNTRL); | ||||
| 
 | ||||
| 	if (pmc->sysclkreq_high) | ||||
| 		value &= ~PMC_CNTRL_SYSCLK_POLARITY; | ||||
| 	else | ||||
| 		value |= PMC_CNTRL_SYSCLK_POLARITY; | ||||
| 
 | ||||
| 	/* configure the output polarity while the request is tristated */ | ||||
| 	tegra_pmc_writel(value, PMC_CNTRL); | ||||
| 
 | ||||
| 	/* now enable the request */ | ||||
| 	value = tegra_pmc_readl(PMC_CNTRL); | ||||
| 	value |= PMC_CNTRL_SYSCLK_OE; | ||||
| 	tegra_pmc_writel(value, PMC_CNTRL); | ||||
| } | ||||
| 
 | ||||
| static int tegra_pmc_probe(struct platform_device *pdev) | ||||
| { | ||||
| 	void __iomem *base = pmc->base; | ||||
| 	struct resource *res; | ||||
| 	int err; | ||||
| 
 | ||||
| 	err = tegra_pmc_parse_dt(pmc, pdev->dev.of_node); | ||||
| 	if (err < 0) | ||||
| 		return err; | ||||
| 
 | ||||
| 	/* take over the memory region from the early initialization */ | ||||
| 	res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||||
| 	pmc->base = devm_ioremap_resource(&pdev->dev, res); | ||||
| 	if (IS_ERR(pmc->base)) | ||||
| 		return PTR_ERR(pmc->base); | ||||
| 
 | ||||
| 	iounmap(base); | ||||
| 
 | ||||
| 	pmc->clk = devm_clk_get(&pdev->dev, "pclk"); | ||||
| 	if (IS_ERR(pmc->clk)) { | ||||
| 		err = PTR_ERR(pmc->clk); | ||||
| 		dev_err(&pdev->dev, "failed to get pclk: %d\n", err); | ||||
| 		return err; | ||||
| 	} | ||||
| 
 | ||||
| 	tegra_pmc_init(pmc); | ||||
| 
 | ||||
| 	if (IS_ENABLED(CONFIG_DEBUG_FS)) { | ||||
| 		err = tegra_powergate_debugfs_init(); | ||||
| 		if (err < 0) | ||||
| 			return err; | ||||
| 	} | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| #ifdef CONFIG_PM_SLEEP | ||||
| static int tegra_pmc_suspend(struct device *dev) | ||||
| { | ||||
| 	tegra_pmc_writel(virt_to_phys(tegra_resume), PMC_SCRATCH41); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static int tegra_pmc_resume(struct device *dev) | ||||
| { | ||||
| 	tegra_pmc_writel(0x0, PMC_SCRATCH41); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| #endif | ||||
| 
 | ||||
| static SIMPLE_DEV_PM_OPS(tegra_pmc_pm_ops, tegra_pmc_suspend, tegra_pmc_resume); | ||||
| 
 | ||||
| static const char * const tegra20_powergates[] = { | ||||
| 	[TEGRA_POWERGATE_CPU] = "cpu", | ||||
| 	[TEGRA_POWERGATE_3D] = "3d", | ||||
| 	[TEGRA_POWERGATE_VENC] = "venc", | ||||
| 	[TEGRA_POWERGATE_VDEC] = "vdec", | ||||
| 	[TEGRA_POWERGATE_PCIE] = "pcie", | ||||
| 	[TEGRA_POWERGATE_L2] = "l2", | ||||
| 	[TEGRA_POWERGATE_MPE] = "mpe", | ||||
| }; | ||||
| 
 | ||||
| static const struct tegra_pmc_soc tegra20_pmc_soc = { | ||||
| 	.num_powergates = ARRAY_SIZE(tegra20_powergates), | ||||
| 	.powergates = tegra20_powergates, | ||||
| 	.num_cpu_powergates = 0, | ||||
| 	.cpu_powergates = NULL, | ||||
| }; | ||||
| 
 | ||||
| static const char * const tegra30_powergates[] = { | ||||
| 	[TEGRA_POWERGATE_CPU] = "cpu0", | ||||
| 	[TEGRA_POWERGATE_3D] = "3d0", | ||||
| 	[TEGRA_POWERGATE_VENC] = "venc", | ||||
| 	[TEGRA_POWERGATE_VDEC] = "vdec", | ||||
| 	[TEGRA_POWERGATE_PCIE] = "pcie", | ||||
| 	[TEGRA_POWERGATE_L2] = "l2", | ||||
| 	[TEGRA_POWERGATE_MPE] = "mpe", | ||||
| 	[TEGRA_POWERGATE_HEG] = "heg", | ||||
| 	[TEGRA_POWERGATE_SATA] = "sata", | ||||
| 	[TEGRA_POWERGATE_CPU1] = "cpu1", | ||||
| 	[TEGRA_POWERGATE_CPU2] = "cpu2", | ||||
| 	[TEGRA_POWERGATE_CPU3] = "cpu3", | ||||
| 	[TEGRA_POWERGATE_CELP] = "celp", | ||||
| 	[TEGRA_POWERGATE_3D1] = "3d1", | ||||
| }; | ||||
| 
 | ||||
| static const u8 tegra30_cpu_powergates[] = { | ||||
| 	TEGRA_POWERGATE_CPU, | ||||
| 	TEGRA_POWERGATE_CPU1, | ||||
| 	TEGRA_POWERGATE_CPU2, | ||||
| 	TEGRA_POWERGATE_CPU3, | ||||
| }; | ||||
| 
 | ||||
| static const struct tegra_pmc_soc tegra30_pmc_soc = { | ||||
| 	.num_powergates = ARRAY_SIZE(tegra30_powergates), | ||||
| 	.powergates = tegra30_powergates, | ||||
| 	.num_cpu_powergates = ARRAY_SIZE(tegra30_cpu_powergates), | ||||
| 	.cpu_powergates = tegra30_cpu_powergates, | ||||
| }; | ||||
| 
 | ||||
| static const char * const tegra114_powergates[] = { | ||||
| 	[TEGRA_POWERGATE_CPU] = "crail", | ||||
| 	[TEGRA_POWERGATE_3D] = "3d", | ||||
| 	[TEGRA_POWERGATE_VENC] = "venc", | ||||
| 	[TEGRA_POWERGATE_VDEC] = "vdec", | ||||
| 	[TEGRA_POWERGATE_MPE] = "mpe", | ||||
| 	[TEGRA_POWERGATE_HEG] = "heg", | ||||
| 	[TEGRA_POWERGATE_CPU1] = "cpu1", | ||||
| 	[TEGRA_POWERGATE_CPU2] = "cpu2", | ||||
| 	[TEGRA_POWERGATE_CPU3] = "cpu3", | ||||
| 	[TEGRA_POWERGATE_CELP] = "celp", | ||||
| 	[TEGRA_POWERGATE_CPU0] = "cpu0", | ||||
| 	[TEGRA_POWERGATE_C0NC] = "c0nc", | ||||
| 	[TEGRA_POWERGATE_C1NC] = "c1nc", | ||||
| 	[TEGRA_POWERGATE_DIS] = "dis", | ||||
| 	[TEGRA_POWERGATE_DISB] = "disb", | ||||
| 	[TEGRA_POWERGATE_XUSBA] = "xusba", | ||||
| 	[TEGRA_POWERGATE_XUSBB] = "xusbb", | ||||
| 	[TEGRA_POWERGATE_XUSBC] = "xusbc", | ||||
| }; | ||||
| 
 | ||||
| static const u8 tegra114_cpu_powergates[] = { | ||||
| 	TEGRA_POWERGATE_CPU0, | ||||
| 	TEGRA_POWERGATE_CPU1, | ||||
| 	TEGRA_POWERGATE_CPU2, | ||||
| 	TEGRA_POWERGATE_CPU3, | ||||
| }; | ||||
| 
 | ||||
| static const struct tegra_pmc_soc tegra114_pmc_soc = { | ||||
| 	.num_powergates = ARRAY_SIZE(tegra114_powergates), | ||||
| 	.powergates = tegra114_powergates, | ||||
| 	.num_cpu_powergates = ARRAY_SIZE(tegra114_cpu_powergates), | ||||
| 	.cpu_powergates = tegra114_cpu_powergates, | ||||
| }; | ||||
| 
 | ||||
| static const char * const tegra124_powergates[] = { | ||||
| 	[TEGRA_POWERGATE_CPU] = "crail", | ||||
| 	[TEGRA_POWERGATE_3D] = "3d", | ||||
| 	[TEGRA_POWERGATE_VENC] = "venc", | ||||
| 	[TEGRA_POWERGATE_PCIE] = "pcie", | ||||
| 	[TEGRA_POWERGATE_VDEC] = "vdec", | ||||
| 	[TEGRA_POWERGATE_L2] = "l2", | ||||
| 	[TEGRA_POWERGATE_MPE] = "mpe", | ||||
| 	[TEGRA_POWERGATE_HEG] = "heg", | ||||
| 	[TEGRA_POWERGATE_SATA] = "sata", | ||||
| 	[TEGRA_POWERGATE_CPU1] = "cpu1", | ||||
| 	[TEGRA_POWERGATE_CPU2] = "cpu2", | ||||
| 	[TEGRA_POWERGATE_CPU3] = "cpu3", | ||||
| 	[TEGRA_POWERGATE_CELP] = "celp", | ||||
| 	[TEGRA_POWERGATE_CPU0] = "cpu0", | ||||
| 	[TEGRA_POWERGATE_C0NC] = "c0nc", | ||||
| 	[TEGRA_POWERGATE_C1NC] = "c1nc", | ||||
| 	[TEGRA_POWERGATE_SOR] = "sor", | ||||
| 	[TEGRA_POWERGATE_DIS] = "dis", | ||||
| 	[TEGRA_POWERGATE_DISB] = "disb", | ||||
| 	[TEGRA_POWERGATE_XUSBA] = "xusba", | ||||
| 	[TEGRA_POWERGATE_XUSBB] = "xusbb", | ||||
| 	[TEGRA_POWERGATE_XUSBC] = "xusbc", | ||||
| 	[TEGRA_POWERGATE_VIC] = "vic", | ||||
| 	[TEGRA_POWERGATE_IRAM] = "iram", | ||||
| }; | ||||
| 
 | ||||
| static const u8 tegra124_cpu_powergates[] = { | ||||
| 	TEGRA_POWERGATE_CPU0, | ||||
| 	TEGRA_POWERGATE_CPU1, | ||||
| 	TEGRA_POWERGATE_CPU2, | ||||
| 	TEGRA_POWERGATE_CPU3, | ||||
| }; | ||||
| 
 | ||||
| static const struct tegra_pmc_soc tegra124_pmc_soc = { | ||||
| 	.num_powergates = ARRAY_SIZE(tegra124_powergates), | ||||
| 	.powergates = tegra124_powergates, | ||||
| 	.num_cpu_powergates = ARRAY_SIZE(tegra124_cpu_powergates), | ||||
| 	.cpu_powergates = tegra124_cpu_powergates, | ||||
| }; | ||||
| 
 | ||||
| static const struct of_device_id tegra_pmc_match[] = { | ||||
| 	{ .compatible = "nvidia,tegra124-pmc", .data = &tegra124_pmc_soc }, | ||||
| 	{ .compatible = "nvidia,tegra114-pmc", .data = &tegra114_pmc_soc }, | ||||
| 	{ .compatible = "nvidia,tegra30-pmc", .data = &tegra30_pmc_soc }, | ||||
| 	{ .compatible = "nvidia,tegra20-pmc", .data = &tegra20_pmc_soc }, | ||||
| 	{ } | ||||
| }; | ||||
| 
 | ||||
| static struct platform_driver tegra_pmc_driver = { | ||||
| 	.driver = { | ||||
| 		.name = "tegra-pmc", | ||||
| 		.suppress_bind_attrs = true, | ||||
| 		.of_match_table = tegra_pmc_match, | ||||
| 		.pm = &tegra_pmc_pm_ops, | ||||
| 	}, | ||||
| 	.probe = tegra_pmc_probe, | ||||
| }; | ||||
| module_platform_driver(tegra_pmc_driver); | ||||
| 
 | ||||
| /*
 | ||||
|  * Early initialization to allow access to registers in the very early boot | ||||
|  * process. | ||||
|  */ | ||||
| static int __init tegra_pmc_early_init(void) | ||||
| { | ||||
| 	const struct of_device_id *match; | ||||
| 	struct device_node *np; | ||||
| 	struct resource regs; | ||||
| 	bool invert; | ||||
| 	u32 value; | ||||
| 
 | ||||
| 	if (!soc_is_tegra()) | ||||
| 		return 0; | ||||
| 
 | ||||
| 	np = of_find_matching_node_and_match(NULL, tegra_pmc_match, &match); | ||||
| 	if (!np) { | ||||
| 		pr_warn("PMC device node not found, disabling powergating\n"); | ||||
| 
 | ||||
| 		regs.start = 0x7000e400; | ||||
| 		regs.end = 0x7000e7ff; | ||||
| 		regs.flags = IORESOURCE_MEM; | ||||
| 
 | ||||
| 		pr_warn("Using memory region %pR\n", ®s); | ||||
| 	} else { | ||||
| 		pmc->soc = match->data; | ||||
| 	} | ||||
| 
 | ||||
| 	if (of_address_to_resource(np, 0, ®s) < 0) { | ||||
| 		pr_err("failed to get PMC registers\n"); | ||||
| 		return -ENXIO; | ||||
| 	} | ||||
| 
 | ||||
| 	pmc->base = ioremap_nocache(regs.start, resource_size(®s)); | ||||
| 	if (!pmc->base) { | ||||
| 		pr_err("failed to map PMC registers\n"); | ||||
| 		return -ENXIO; | ||||
| 	} | ||||
| 
 | ||||
| 	mutex_init(&pmc->powergates_lock); | ||||
| 
 | ||||
| 	invert = of_property_read_bool(np, "nvidia,invert-interrupt"); | ||||
| 
 | ||||
| 	value = tegra_pmc_readl(PMC_CNTRL); | ||||
| 
 | ||||
| 	if (invert) | ||||
| 		value |= PMC_CNTRL_INTR_POLARITY; | ||||
| 	else | ||||
| 		value &= ~PMC_CNTRL_INTR_POLARITY; | ||||
| 
 | ||||
| 	tegra_pmc_writel(value, PMC_CNTRL); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| early_initcall(tegra_pmc_early_init); | ||||
							
								
								
									
										38
									
								
								include/soc/tegra/pm.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								include/soc/tegra/pm.h
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,38 @@ | |||
| /*
 | ||||
|  * Copyright (C) 2014 NVIDIA Corporation | ||||
|  * | ||||
|  * 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. | ||||
|  */ | ||||
| 
 | ||||
| #ifndef __SOC_TEGRA_PM_H__ | ||||
| #define __SOC_TEGRA_PM_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_MAX_SUSPEND_MODE, | ||||
| }; | ||||
| 
 | ||||
| #ifdef CONFIG_PM_SLEEP | ||||
| enum tegra_suspend_mode | ||||
| tegra_pm_validate_suspend_mode(enum tegra_suspend_mode mode); | ||||
| 
 | ||||
| /* low-level resume entry point */ | ||||
| void tegra_resume(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_resume(void) | ||||
| { | ||||
| } | ||||
| #endif /* CONFIG_PM_SLEEP */ | ||||
| 
 | ||||
| #endif /* __SOC_TEGRA_PM_H__ */ | ||||
|  | @ -1,5 +1,6 @@ | |||
| /*
 | ||||
|  * Copyright (c) 2010 Google, Inc | ||||
|  * Copyright (c) 2014 NVIDIA Corporation | ||||
|  * | ||||
|  * Author: | ||||
|  *	Colin Cross <ccross@google.com> | ||||
|  | @ -15,12 +16,34 @@ | |||
|  * | ||||
|  */ | ||||
| 
 | ||||
| #ifndef __SOC_TEGRA_POWERGATE_H__ | ||||
| #define __SOC_TEGRA_POWERGATE_H__ | ||||
| #ifndef __SOC_TEGRA_PMC_H__ | ||||
| #define __SOC_TEGRA_PMC_H__ | ||||
| 
 | ||||
| #include <linux/reboot.h> | ||||
| 
 | ||||
| #include <soc/tegra/pm.h> | ||||
| 
 | ||||
| struct clk; | ||||
| struct reset_control; | ||||
| 
 | ||||
| void tegra_pmc_restart(enum reboot_mode mode, const char *cmd); | ||||
| 
 | ||||
| #ifdef CONFIG_PM_SLEEP | ||||
| enum tegra_suspend_mode tegra_pmc_get_suspend_mode(void); | ||||
| void tegra_pmc_set_suspend_mode(enum tegra_suspend_mode mode); | ||||
| void tegra_pmc_enter_suspend_mode(enum tegra_suspend_mode mode); | ||||
| #endif /* CONFIG_PM_SLEEP */ | ||||
| 
 | ||||
| #ifdef CONFIG_SMP | ||||
| bool tegra_pmc_cpu_is_powered(int cpuid); | ||||
| int tegra_pmc_cpu_power_on(int cpuid); | ||||
| int tegra_pmc_cpu_remove_clamping(int cpuid); | ||||
| #endif /* CONFIG_SMP */ | ||||
| 
 | ||||
| /*
 | ||||
|  * powergate and I/O rail APIs | ||||
|  */ | ||||
| 
 | ||||
| #define TEGRA_POWERGATE_CPU	0 | ||||
| #define TEGRA_POWERGATE_3D	1 | ||||
| #define TEGRA_POWERGATE_VENC	2 | ||||
|  | @ -129,6 +152,6 @@ static inline int tegra_io_rail_power_off(int id) | |||
| { | ||||
| 	return -ENOSYS; | ||||
| } | ||||
| #endif | ||||
| #endif /* CONFIG_ARCH_TEGRA */ | ||||
| 
 | ||||
| #endif /* __SOC_TEGRA_POWERGATE_H__ */ | ||||
| #endif /* __SOC_TEGRA_PMC_H__ */ | ||||
		Loading…
	
		Reference in a new issue
	
	 Thierry Reding
						Thierry Reding