Commit | Line | Data |
---|---|---|
ce1e3262 CC |
1 | /* |
2 | * drivers/powergate/tegra-powergate.c | |
3 | * | |
4 | * Copyright (c) 2010 Google, Inc | |
5 | * | |
6 | * Author: | |
7 | * Colin Cross <ccross@google.com> | |
8 | * | |
9 | * This software is licensed under the terms of the GNU General Public | |
10 | * License version 2, as published by the Free Software Foundation, and | |
11 | * may be copied, distributed, and modified under those terms. | |
12 | * | |
13 | * This program is distributed in the hope that it will be useful, | |
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
16 | * GNU General Public License for more details. | |
17 | * | |
18 | */ | |
19 | ||
20 | #include <linux/kernel.h> | |
21 | #include <linux/clk.h> | |
22 | #include <linux/debugfs.h> | |
23 | #include <linux/delay.h> | |
24 | #include <linux/err.h> | |
25 | #include <linux/init.h> | |
26 | #include <linux/io.h> | |
27 | #include <linux/seq_file.h> | |
28 | #include <linux/spinlock.h> | |
61fd290d | 29 | #include <linux/clk/tegra.h> |
ce1e3262 | 30 | |
ce1e3262 CC |
31 | #include <mach/powergate.h> |
32 | ||
8f5d6f1b | 33 | #include "fuse.h" |
2be39c07 | 34 | #include "iomap.h" |
8f5d6f1b | 35 | |
ce1e3262 CC |
36 | #define PWRGATE_TOGGLE 0x30 |
37 | #define PWRGATE_TOGGLE_START (1 << 8) | |
38 | ||
39 | #define REMOVE_CLAMPING 0x34 | |
40 | ||
41 | #define PWRGATE_STATUS 0x38 | |
42 | ||
8f5d6f1b | 43 | static int tegra_num_powerdomains; |
65fe31da PDS |
44 | static int tegra_num_cpu_domains; |
45 | static u8 *tegra_cpu_domains; | |
46 | static u8 tegra30_cpu_domains[] = { | |
47 | TEGRA_POWERGATE_CPU0, | |
48 | TEGRA_POWERGATE_CPU1, | |
49 | TEGRA_POWERGATE_CPU2, | |
50 | TEGRA_POWERGATE_CPU3, | |
51 | }; | |
8f5d6f1b | 52 | |
ce1e3262 CC |
53 | static DEFINE_SPINLOCK(tegra_powergate_lock); |
54 | ||
55 | static void __iomem *pmc = IO_ADDRESS(TEGRA_PMC_BASE); | |
56 | ||
57 | static u32 pmc_read(unsigned long reg) | |
58 | { | |
59 | return readl(pmc + reg); | |
60 | } | |
61 | ||
62 | static void pmc_write(u32 val, unsigned long reg) | |
63 | { | |
64 | writel(val, pmc + reg); | |
65 | } | |
66 | ||
67 | static int tegra_powergate_set(int id, bool new_state) | |
68 | { | |
69 | bool status; | |
70 | unsigned long flags; | |
71 | ||
72 | spin_lock_irqsave(&tegra_powergate_lock, flags); | |
73 | ||
74 | status = pmc_read(PWRGATE_STATUS) & (1 << id); | |
75 | ||
76 | if (status == new_state) { | |
77 | spin_unlock_irqrestore(&tegra_powergate_lock, flags); | |
78 | return -EINVAL; | |
79 | } | |
80 | ||
81 | pmc_write(PWRGATE_TOGGLE_START | id, PWRGATE_TOGGLE); | |
82 | ||
83 | spin_unlock_irqrestore(&tegra_powergate_lock, flags); | |
84 | ||
85 | return 0; | |
86 | } | |
87 | ||
88 | int tegra_powergate_power_on(int id) | |
89 | { | |
8f5d6f1b | 90 | if (id < 0 || id >= tegra_num_powerdomains) |
ce1e3262 CC |
91 | return -EINVAL; |
92 | ||
93 | return tegra_powergate_set(id, true); | |
94 | } | |
95 | ||
96 | int tegra_powergate_power_off(int id) | |
97 | { | |
8f5d6f1b | 98 | if (id < 0 || id >= tegra_num_powerdomains) |
ce1e3262 CC |
99 | return -EINVAL; |
100 | ||
101 | return tegra_powergate_set(id, false); | |
102 | } | |
103 | ||
6ac8cb5c | 104 | int tegra_powergate_is_powered(int id) |
ce1e3262 CC |
105 | { |
106 | u32 status; | |
107 | ||
8f5d6f1b PDS |
108 | if (id < 0 || id >= tegra_num_powerdomains) |
109 | return -EINVAL; | |
ce1e3262 CC |
110 | |
111 | status = pmc_read(PWRGATE_STATUS) & (1 << id); | |
112 | return !!status; | |
113 | } | |
114 | ||
115 | int tegra_powergate_remove_clamping(int id) | |
116 | { | |
117 | u32 mask; | |
118 | ||
8f5d6f1b | 119 | if (id < 0 || id >= tegra_num_powerdomains) |
ce1e3262 CC |
120 | return -EINVAL; |
121 | ||
122 | /* | |
123 | * Tegra 2 has a bug where PCIE and VDE clamping masks are | |
124 | * swapped relatively to the partition ids | |
125 | */ | |
126 | if (id == TEGRA_POWERGATE_VDEC) | |
127 | mask = (1 << TEGRA_POWERGATE_PCIE); | |
128 | else if (id == TEGRA_POWERGATE_PCIE) | |
129 | mask = (1 << TEGRA_POWERGATE_VDEC); | |
130 | else | |
131 | mask = (1 << id); | |
132 | ||
133 | pmc_write(mask, REMOVE_CLAMPING); | |
134 | ||
135 | return 0; | |
136 | } | |
137 | ||
138 | /* Must be called with clk disabled, and returns with clk enabled */ | |
139 | int tegra_powergate_sequence_power_up(int id, struct clk *clk) | |
140 | { | |
141 | int ret; | |
142 | ||
143 | tegra_periph_reset_assert(clk); | |
144 | ||
145 | ret = tegra_powergate_power_on(id); | |
146 | if (ret) | |
147 | goto err_power; | |
148 | ||
6a5278d0 | 149 | ret = clk_prepare_enable(clk); |
ce1e3262 CC |
150 | if (ret) |
151 | goto err_clk; | |
152 | ||
153 | udelay(10); | |
154 | ||
155 | ret = tegra_powergate_remove_clamping(id); | |
156 | if (ret) | |
157 | goto err_clamp; | |
158 | ||
159 | udelay(10); | |
160 | tegra_periph_reset_deassert(clk); | |
161 | ||
162 | return 0; | |
163 | ||
164 | err_clamp: | |
6a5278d0 | 165 | clk_disable_unprepare(clk); |
ce1e3262 CC |
166 | err_clk: |
167 | tegra_powergate_power_off(id); | |
168 | err_power: | |
169 | return ret; | |
170 | } | |
171 | ||
65fe31da PDS |
172 | int tegra_cpu_powergate_id(int cpuid) |
173 | { | |
174 | if (cpuid > 0 && cpuid < tegra_num_cpu_domains) | |
175 | return tegra_cpu_domains[cpuid]; | |
176 | ||
177 | return -EINVAL; | |
178 | } | |
179 | ||
8f5d6f1b PDS |
180 | int __init tegra_powergate_init(void) |
181 | { | |
182 | switch (tegra_chip_id) { | |
183 | case TEGRA20: | |
184 | tegra_num_powerdomains = 7; | |
185 | break; | |
6cafa97d PDS |
186 | case TEGRA30: |
187 | tegra_num_powerdomains = 14; | |
65fe31da PDS |
188 | tegra_num_cpu_domains = 4; |
189 | tegra_cpu_domains = tegra30_cpu_domains; | |
6cafa97d | 190 | break; |
8f5d6f1b PDS |
191 | default: |
192 | /* Unknown Tegra variant. Disable powergating */ | |
193 | tegra_num_powerdomains = 0; | |
194 | break; | |
195 | } | |
196 | ||
197 | return 0; | |
198 | } | |
8f5d6f1b | 199 | |
ce1e3262 CC |
200 | #ifdef CONFIG_DEBUG_FS |
201 | ||
b48d6aab PDS |
202 | static const char * const *powergate_name; |
203 | ||
204 | static const char * const powergate_name_t20[] = { | |
ce1e3262 CC |
205 | [TEGRA_POWERGATE_CPU] = "cpu", |
206 | [TEGRA_POWERGATE_3D] = "3d", | |
207 | [TEGRA_POWERGATE_VENC] = "venc", | |
208 | [TEGRA_POWERGATE_VDEC] = "vdec", | |
209 | [TEGRA_POWERGATE_PCIE] = "pcie", | |
210 | [TEGRA_POWERGATE_L2] = "l2", | |
211 | [TEGRA_POWERGATE_MPE] = "mpe", | |
212 | }; | |
213 | ||
b48d6aab PDS |
214 | static const char * const powergate_name_t30[] = { |
215 | [TEGRA_POWERGATE_CPU] = "cpu0", | |
216 | [TEGRA_POWERGATE_3D] = "3d0", | |
217 | [TEGRA_POWERGATE_VENC] = "venc", | |
218 | [TEGRA_POWERGATE_VDEC] = "vdec", | |
219 | [TEGRA_POWERGATE_PCIE] = "pcie", | |
220 | [TEGRA_POWERGATE_L2] = "l2", | |
221 | [TEGRA_POWERGATE_MPE] = "mpe", | |
222 | [TEGRA_POWERGATE_HEG] = "heg", | |
223 | [TEGRA_POWERGATE_SATA] = "sata", | |
224 | [TEGRA_POWERGATE_CPU1] = "cpu1", | |
225 | [TEGRA_POWERGATE_CPU2] = "cpu2", | |
226 | [TEGRA_POWERGATE_CPU3] = "cpu3", | |
227 | [TEGRA_POWERGATE_CELP] = "celp", | |
228 | [TEGRA_POWERGATE_3D1] = "3d1", | |
229 | }; | |
230 | ||
ce1e3262 CC |
231 | static int powergate_show(struct seq_file *s, void *data) |
232 | { | |
233 | int i; | |
234 | ||
235 | seq_printf(s, " powergate powered\n"); | |
236 | seq_printf(s, "------------------\n"); | |
237 | ||
8f5d6f1b | 238 | for (i = 0; i < tegra_num_powerdomains; i++) |
ce1e3262 CC |
239 | seq_printf(s, " %9s %7s\n", powergate_name[i], |
240 | tegra_powergate_is_powered(i) ? "yes" : "no"); | |
241 | return 0; | |
242 | } | |
243 | ||
244 | static int powergate_open(struct inode *inode, struct file *file) | |
245 | { | |
246 | return single_open(file, powergate_show, inode->i_private); | |
247 | } | |
248 | ||
249 | static const struct file_operations powergate_fops = { | |
250 | .open = powergate_open, | |
251 | .read = seq_read, | |
252 | .llseek = seq_lseek, | |
253 | .release = single_release, | |
254 | }; | |
255 | ||
390e0cfd | 256 | int __init tegra_powergate_debugfs_init(void) |
ce1e3262 CC |
257 | { |
258 | struct dentry *d; | |
ce1e3262 | 259 | |
b48d6aab PDS |
260 | switch (tegra_chip_id) { |
261 | case TEGRA20: | |
262 | powergate_name = powergate_name_t20; | |
263 | break; | |
264 | case TEGRA30: | |
265 | powergate_name = powergate_name_t30; | |
266 | break; | |
267 | } | |
268 | ||
269 | if (powergate_name) { | |
270 | d = debugfs_create_file("powergate", S_IRUGO, NULL, NULL, | |
271 | &powergate_fops); | |
272 | if (!d) | |
273 | return -ENOMEM; | |
274 | } | |
ce1e3262 | 275 | |
f858b6f2 | 276 | return 0; |
ce1e3262 CC |
277 | } |
278 | ||
ce1e3262 | 279 | #endif |