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 d4fdb5fcec20..b30e921cc3a9 100644 --- a/arch/arm/mach-tegra/pmc.c +++ b/arch/arm/mach-tegra/pmc.c | |||
@@ -1,5 +1,5 @@ | |||
1 | /* | 1 | /* |
2 | * Copyright (C) 2012 NVIDIA CORPORATION. All rights reserved. | 2 | * Copyright (C) 2012,2013 NVIDIA CORPORATION. All rights reserved. |
3 | * | 3 | * |
4 | * This program is free software; you can redistribute it and/or modify it | 4 | * This program is free software; you can redistribute it and/or modify it |
5 | * under the terms and conditions of the GNU General Public License, | 5 | * under the terms and conditions of the GNU General Public License, |
@@ -18,57 +18,149 @@ | |||
18 | #include <linux/kernel.h> | 18 | #include <linux/kernel.h> |
19 | #include <linux/io.h> | 19 | #include <linux/io.h> |
20 | #include <linux/of.h> | 20 | #include <linux/of.h> |
21 | #include <linux/of_address.h> | ||
21 | 22 | ||
22 | #include "iomap.h" | 23 | #define PMC_CTRL 0x0 |
24 | #define PMC_CTRL_INTR_LOW (1 << 17) | ||
25 | #define PMC_PWRGATE_TOGGLE 0x30 | ||
26 | #define PMC_PWRGATE_TOGGLE_START (1 << 8) | ||
27 | #define PMC_REMOVE_CLAMPING 0x34 | ||
28 | #define PMC_PWRGATE_STATUS 0x38 | ||
23 | 29 | ||
24 | #define PMC_CTRL 0x0 | 30 | #define TEGRA_POWERGATE_PCIE 3 |
25 | #define PMC_CTRL_INTR_LOW (1 << 17) | 31 | #define TEGRA_POWERGATE_VDEC 4 |
32 | #define TEGRA_POWERGATE_CPU1 9 | ||
33 | #define TEGRA_POWERGATE_CPU2 10 | ||
34 | #define TEGRA_POWERGATE_CPU3 11 | ||
35 | |||
36 | static u8 tegra_cpu_domains[] = { | ||
37 | 0xFF, /* not available for CPU0 */ | ||
38 | TEGRA_POWERGATE_CPU1, | ||
39 | TEGRA_POWERGATE_CPU2, | ||
40 | TEGRA_POWERGATE_CPU3, | ||
41 | }; | ||
42 | static DEFINE_SPINLOCK(tegra_powergate_lock); | ||
43 | |||
44 | static void __iomem *tegra_pmc_base; | ||
45 | static bool tegra_pmc_invert_interrupt; | ||
26 | 46 | ||
27 | static inline u32 tegra_pmc_readl(u32 reg) | 47 | static inline u32 tegra_pmc_readl(u32 reg) |
28 | { | 48 | { |
29 | return readl(IO_ADDRESS(TEGRA_PMC_BASE + reg)); | 49 | return readl(tegra_pmc_base + reg); |
30 | } | 50 | } |
31 | 51 | ||
32 | static inline void tegra_pmc_writel(u32 val, u32 reg) | 52 | static inline void tegra_pmc_writel(u32 val, u32 reg) |
33 | { | 53 | { |
34 | writel(val, IO_ADDRESS(TEGRA_PMC_BASE + reg)); | 54 | writel(val, tegra_pmc_base + reg); |
55 | } | ||
56 | |||
57 | static int tegra_pmc_get_cpu_powerdomain_id(int cpuid) | ||
58 | { | ||
59 | if (cpuid <= 0 || cpuid >= num_possible_cpus()) | ||
60 | return -EINVAL; | ||
61 | return tegra_cpu_domains[cpuid]; | ||
62 | } | ||
63 | |||
64 | static bool tegra_pmc_powergate_is_powered(int id) | ||
65 | { | ||
66 | return (tegra_pmc_readl(PMC_PWRGATE_STATUS) >> id) & 1; | ||
67 | } | ||
68 | |||
69 | static int tegra_pmc_powergate_set(int id, bool new_state) | ||
70 | { | ||
71 | bool old_state; | ||
72 | unsigned long flags; | ||
73 | |||
74 | spin_lock_irqsave(&tegra_powergate_lock, flags); | ||
75 | |||
76 | old_state = tegra_pmc_powergate_is_powered(id); | ||
77 | WARN_ON(old_state == new_state); | ||
78 | |||
79 | tegra_pmc_writel(PMC_PWRGATE_TOGGLE_START | id, PMC_PWRGATE_TOGGLE); | ||
80 | |||
81 | spin_unlock_irqrestore(&tegra_powergate_lock, flags); | ||
82 | |||
83 | return 0; | ||
84 | } | ||
85 | |||
86 | static int tegra_pmc_powergate_remove_clamping(int id) | ||
87 | { | ||
88 | u32 mask; | ||
89 | |||
90 | /* | ||
91 | * Tegra has a bug where PCIE and VDE clamping masks are | ||
92 | * swapped relatively to the partition ids. | ||
93 | */ | ||
94 | if (id == TEGRA_POWERGATE_VDEC) | ||
95 | mask = (1 << TEGRA_POWERGATE_PCIE); | ||
96 | else if (id == TEGRA_POWERGATE_PCIE) | ||
97 | mask = (1 << TEGRA_POWERGATE_VDEC); | ||
98 | else | ||
99 | mask = (1 << id); | ||
100 | |||
101 | tegra_pmc_writel(mask, PMC_REMOVE_CLAMPING); | ||
102 | |||
103 | return 0; | ||
104 | } | ||
105 | |||
106 | bool tegra_pmc_cpu_is_powered(int cpuid) | ||
107 | { | ||
108 | int id; | ||
109 | |||
110 | id = tegra_pmc_get_cpu_powerdomain_id(cpuid); | ||
111 | if (id < 0) | ||
112 | return false; | ||
113 | return tegra_pmc_powergate_is_powered(id); | ||
114 | } | ||
115 | |||
116 | int tegra_pmc_cpu_power_on(int cpuid) | ||
117 | { | ||
118 | int id; | ||
119 | |||
120 | id = tegra_pmc_get_cpu_powerdomain_id(cpuid); | ||
121 | if (id < 0) | ||
122 | return id; | ||
123 | return tegra_pmc_powergate_set(id, true); | ||
124 | } | ||
125 | |||
126 | int tegra_pmc_cpu_remove_clamping(int cpuid) | ||
127 | { | ||
128 | int id; | ||
129 | |||
130 | id = tegra_pmc_get_cpu_powerdomain_id(cpuid); | ||
131 | if (id < 0) | ||
132 | return id; | ||
133 | return tegra_pmc_powergate_remove_clamping(id); | ||
35 | } | 134 | } |
36 | 135 | ||
37 | #ifdef CONFIG_OF | ||
38 | static const struct of_device_id matches[] __initconst = { | 136 | static const struct of_device_id matches[] __initconst = { |
137 | { .compatible = "nvidia,tegra114-pmc" }, | ||
138 | { .compatible = "nvidia,tegra30-pmc" }, | ||
39 | { .compatible = "nvidia,tegra20-pmc" }, | 139 | { .compatible = "nvidia,tegra20-pmc" }, |
40 | { } | 140 | { } |
41 | }; | 141 | }; |
42 | #endif | ||
43 | 142 | ||
44 | void __init tegra_pmc_init(void) | 143 | static void tegra_pmc_parse_dt(void) |
45 | { | 144 | { |
46 | /* | 145 | struct device_node *np; |
47 | * For now, Harmony is the only board that uses the PMC, and it wants | ||
48 | * the signal inverted. Seaboard would too if it used the PMC. | ||
49 | * Hopefully by the time other boards want to use the PMC, everything | ||
50 | * will be device-tree, or they also want it inverted. | ||
51 | */ | ||
52 | bool invert_interrupt = true; | ||
53 | u32 val; | ||
54 | 146 | ||
55 | #ifdef CONFIG_OF | 147 | np = of_find_matching_node(NULL, matches); |
56 | if (of_have_populated_dt()) { | 148 | BUG_ON(!np); |
57 | struct device_node *np; | ||
58 | 149 | ||
59 | invert_interrupt = false; | 150 | tegra_pmc_base = of_iomap(np, 0); |
151 | |||
152 | tegra_pmc_invert_interrupt = of_property_read_bool(np, | ||
153 | "nvidia,invert-interrupt"); | ||
154 | } | ||
155 | |||
156 | void __init tegra_pmc_init(void) | ||
157 | { | ||
158 | u32 val; | ||
60 | 159 | ||
61 | np = of_find_matching_node(NULL, matches); | 160 | tegra_pmc_parse_dt(); |
62 | if (np) { | ||
63 | if (of_find_property(np, "nvidia,invert-interrupt", | ||
64 | NULL)) | ||
65 | invert_interrupt = true; | ||
66 | } | ||
67 | } | ||
68 | #endif | ||
69 | 161 | ||
70 | val = tegra_pmc_readl(PMC_CTRL); | 162 | val = tegra_pmc_readl(PMC_CTRL); |
71 | if (invert_interrupt) | 163 | if (tegra_pmc_invert_interrupt) |
72 | val |= PMC_CTRL_INTR_LOW; | 164 | val |= PMC_CTRL_INTR_LOW; |
73 | else | 165 | else |
74 | val &= ~PMC_CTRL_INTR_LOW; | 166 | val &= ~PMC_CTRL_INTR_LOW; |