diff options
author | dmitry pervushin <dpervushin@embeddedalley.com> | 2009-04-23 07:24:13 -0400 |
---|---|---|
committer | Russell King <rmk+kernel@arm.linux.org.uk> | 2009-04-27 05:28:08 -0400 |
commit | 5cccd37ea15970846a93b4b01fafd6e043bafe8e (patch) | |
tree | 4ab99b59f91964028fbba128d8ae086f60bd8c82 | |
parent | e317872ac532fd845c597e55ceb5a9bceee878c1 (diff) |
[ARM] 5477/1: Freescale STMP platform support [6/10]
Sources: common STMP3xxx platform support
Signed-off-by: dmitry pervushin <dpervushin@embeddedalley.com>
Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
-rw-r--r-- | arch/arm/plat-stmp3xxx/Kconfig | 37 | ||||
-rw-r--r-- | arch/arm/plat-stmp3xxx/Makefile | 5 | ||||
-rw-r--r-- | arch/arm/plat-stmp3xxx/clock.c | 1112 | ||||
-rw-r--r-- | arch/arm/plat-stmp3xxx/clock.h | 61 | ||||
-rw-r--r-- | arch/arm/plat-stmp3xxx/core.c | 127 | ||||
-rw-r--r-- | arch/arm/plat-stmp3xxx/dma.c | 462 | ||||
-rw-r--r-- | arch/arm/plat-stmp3xxx/irq.c | 59 | ||||
-rw-r--r-- | arch/arm/plat-stmp3xxx/pinmux.c | 545 | ||||
-rw-r--r-- | arch/arm/plat-stmp3xxx/timer.c | 172 |
9 files changed, 2580 insertions, 0 deletions
diff --git a/arch/arm/plat-stmp3xxx/Kconfig b/arch/arm/plat-stmp3xxx/Kconfig new file mode 100644 index 000000000000..2cf37c35951b --- /dev/null +++ b/arch/arm/plat-stmp3xxx/Kconfig | |||
@@ -0,0 +1,37 @@ | |||
1 | if ARCH_STMP3XXX | ||
2 | |||
3 | menu "Freescale STMP3xxx implementations" | ||
4 | |||
5 | choice | ||
6 | prompt "Select STMP3xxx chip family" | ||
7 | |||
8 | config ARCH_STMP37XX | ||
9 | bool "Freescale SMTP37xx" | ||
10 | select CPU_ARM926T | ||
11 | ---help--- | ||
12 | STMP37xx refers to 3700 through 3769 chips | ||
13 | |||
14 | config ARCH_STMP378X | ||
15 | bool "Freescale STMP378x" | ||
16 | select CPU_ARM926T | ||
17 | ---help--- | ||
18 | STMP378x refers to 3780 through 3789 chips | ||
19 | |||
20 | endchoice | ||
21 | |||
22 | choice | ||
23 | prompt "Select STMP3xxx board type" | ||
24 | |||
25 | config MACH_STMP37XX | ||
26 | depends on ARCH_STMP37XX | ||
27 | bool "Freescale STMP37xx development board" | ||
28 | |||
29 | config MACH_STMP378X | ||
30 | depends on ARCH_STMP378X | ||
31 | bool "Freescale STMP378x development board" | ||
32 | |||
33 | endchoice | ||
34 | |||
35 | endmenu | ||
36 | |||
37 | endif | ||
diff --git a/arch/arm/plat-stmp3xxx/Makefile b/arch/arm/plat-stmp3xxx/Makefile new file mode 100644 index 000000000000..b63480066d6b --- /dev/null +++ b/arch/arm/plat-stmp3xxx/Makefile | |||
@@ -0,0 +1,5 @@ | |||
1 | # | ||
2 | # Makefile for the linux kernel. | ||
3 | # | ||
4 | # Object file lists. | ||
5 | obj-y += core.o timer.o irq.o dma.o clock.o pinmux.o | ||
diff --git a/arch/arm/plat-stmp3xxx/clock.c b/arch/arm/plat-stmp3xxx/clock.c new file mode 100644 index 000000000000..9a1d46b470cd --- /dev/null +++ b/arch/arm/plat-stmp3xxx/clock.c | |||
@@ -0,0 +1,1112 @@ | |||
1 | /* | ||
2 | * Clock manipulation routines for Freescale STMP37XX/STMP378X | ||
3 | * | ||
4 | * Author: Vitaly Wool <vital@embeddedalley.com> | ||
5 | * | ||
6 | * Copyright 2008 Freescale Semiconductor, Inc. All Rights Reserved. | ||
7 | * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. | ||
8 | */ | ||
9 | |||
10 | /* | ||
11 | * The code contained herein is licensed under the GNU General Public | ||
12 | * License. You may obtain a copy of the GNU General Public License | ||
13 | * Version 2 or later at the following locations: | ||
14 | * | ||
15 | * http://www.opensource.org/licenses/gpl-license.html | ||
16 | * http://www.gnu.org/copyleft/gpl.html | ||
17 | */ | ||
18 | #include <linux/kernel.h> | ||
19 | #include <linux/module.h> | ||
20 | #include <linux/init.h> | ||
21 | #include <linux/clk.h> | ||
22 | #include <linux/spinlock.h> | ||
23 | #include <linux/errno.h> | ||
24 | #include <linux/err.h> | ||
25 | #include <linux/delay.h> | ||
26 | #include <linux/io.h> | ||
27 | |||
28 | #include <asm/mach-types.h> | ||
29 | #include <asm/clkdev.h> | ||
30 | #include <mach/regs-clkctrl.h> | ||
31 | |||
32 | #include "clock.h" | ||
33 | |||
34 | static DEFINE_SPINLOCK(clocks_lock); | ||
35 | |||
36 | static struct clk osc_24M; | ||
37 | static struct clk pll_clk; | ||
38 | static struct clk cpu_clk; | ||
39 | static struct clk hclk; | ||
40 | |||
41 | static int propagate_rate(struct clk *); | ||
42 | |||
43 | static inline int clk_is_busy(struct clk *clk) | ||
44 | { | ||
45 | return __raw_readl(clk->busy_reg) & (1 << clk->busy_bit); | ||
46 | } | ||
47 | |||
48 | static inline int clk_good(struct clk *clk) | ||
49 | { | ||
50 | return clk && !IS_ERR(clk) && clk->ops; | ||
51 | } | ||
52 | |||
53 | static int std_clk_enable(struct clk *clk) | ||
54 | { | ||
55 | if (clk->enable_reg) { | ||
56 | u32 clk_reg = __raw_readl(clk->enable_reg); | ||
57 | if (clk->enable_negate) | ||
58 | clk_reg &= ~(1 << clk->enable_shift); | ||
59 | else | ||
60 | clk_reg |= (1 << clk->enable_shift); | ||
61 | __raw_writel(clk_reg, clk->enable_reg); | ||
62 | if (clk->enable_wait) | ||
63 | udelay(clk->enable_wait); | ||
64 | return 0; | ||
65 | } else | ||
66 | return -EINVAL; | ||
67 | } | ||
68 | |||
69 | static int std_clk_disable(struct clk *clk) | ||
70 | { | ||
71 | if (clk->enable_reg) { | ||
72 | u32 clk_reg = __raw_readl(clk->enable_reg); | ||
73 | if (clk->enable_negate) | ||
74 | clk_reg |= (1 << clk->enable_shift); | ||
75 | else | ||
76 | clk_reg &= ~(1 << clk->enable_shift); | ||
77 | __raw_writel(clk_reg, clk->enable_reg); | ||
78 | return 0; | ||
79 | } else | ||
80 | return -EINVAL; | ||
81 | } | ||
82 | |||
83 | static int io_set_rate(struct clk *clk, u32 rate) | ||
84 | { | ||
85 | u32 reg_frac, clkctrl_frac; | ||
86 | int i, ret = 0, mask = 0x1f; | ||
87 | |||
88 | clkctrl_frac = (clk->parent->rate * 18 + rate - 1) / rate; | ||
89 | |||
90 | if (clkctrl_frac < 18 || clkctrl_frac > 35) { | ||
91 | ret = -EINVAL; | ||
92 | goto out; | ||
93 | } | ||
94 | |||
95 | reg_frac = __raw_readl(clk->scale_reg); | ||
96 | reg_frac &= ~(mask << clk->scale_shift); | ||
97 | __raw_writel(reg_frac | (clkctrl_frac << clk->scale_shift), | ||
98 | clk->scale_reg); | ||
99 | if (clk->busy_reg) { | ||
100 | for (i = 10000; i; i--) | ||
101 | if (!clk_is_busy(clk)) | ||
102 | break; | ||
103 | if (!i) | ||
104 | ret = -ETIMEDOUT; | ||
105 | else | ||
106 | ret = 0; | ||
107 | } | ||
108 | out: | ||
109 | return ret; | ||
110 | } | ||
111 | |||
112 | static long io_get_rate(struct clk *clk) | ||
113 | { | ||
114 | long rate = clk->parent->rate * 18; | ||
115 | int mask = 0x1f; | ||
116 | |||
117 | rate /= (__raw_readl(clk->scale_reg) >> clk->scale_shift) & mask; | ||
118 | clk->rate = rate; | ||
119 | |||
120 | return rate; | ||
121 | } | ||
122 | |||
123 | static long per_get_rate(struct clk *clk) | ||
124 | { | ||
125 | long rate = clk->parent->rate; | ||
126 | long div; | ||
127 | const int mask = 0xff; | ||
128 | |||
129 | if (clk->enable_reg && | ||
130 | !(__raw_readl(clk->enable_reg) & clk->enable_shift)) | ||
131 | clk->rate = 0; | ||
132 | else { | ||
133 | div = (__raw_readl(clk->scale_reg) >> clk->scale_shift) & mask; | ||
134 | if (div) | ||
135 | rate /= div; | ||
136 | clk->rate = rate; | ||
137 | } | ||
138 | |||
139 | return clk->rate; | ||
140 | } | ||
141 | |||
142 | static int per_set_rate(struct clk *clk, u32 rate) | ||
143 | { | ||
144 | int ret = -EINVAL; | ||
145 | int div = (clk->parent->rate + rate - 1) / rate; | ||
146 | u32 reg_frac; | ||
147 | const int mask = 0xff; | ||
148 | int try = 10; | ||
149 | int i = -1; | ||
150 | |||
151 | if (div == 0 || div > mask) | ||
152 | goto out; | ||
153 | |||
154 | reg_frac = __raw_readl(clk->scale_reg); | ||
155 | reg_frac &= ~(mask << clk->scale_shift); | ||
156 | |||
157 | while (try--) { | ||
158 | __raw_writel(reg_frac | (div << clk->scale_shift), | ||
159 | clk->scale_reg); | ||
160 | |||
161 | if (clk->busy_reg) { | ||
162 | for (i = 10000; i; i--) | ||
163 | if (!clk_is_busy(clk)) | ||
164 | break; | ||
165 | } | ||
166 | if (i) | ||
167 | break; | ||
168 | } | ||
169 | |||
170 | if (!i) | ||
171 | ret = -ETIMEDOUT; | ||
172 | else | ||
173 | ret = 0; | ||
174 | |||
175 | out: | ||
176 | if (ret != 0) | ||
177 | printk(KERN_ERR "%s: error %d\n", __func__, ret); | ||
178 | return ret; | ||
179 | } | ||
180 | |||
181 | static long lcdif_get_rate(struct clk *clk) | ||
182 | { | ||
183 | long rate = clk->parent->rate; | ||
184 | long div; | ||
185 | const int mask = 0xff; | ||
186 | |||
187 | div = (__raw_readl(clk->scale_reg) >> clk->scale_shift) & mask; | ||
188 | if (div) { | ||
189 | rate /= div; | ||
190 | div = (HW_CLKCTRL_FRAC_RD() & BM_CLKCTRL_FRAC_PIXFRAC) >> | ||
191 | BP_CLKCTRL_FRAC_PIXFRAC; | ||
192 | rate /= div; | ||
193 | } | ||
194 | clk->rate = rate; | ||
195 | |||
196 | return rate; | ||
197 | } | ||
198 | |||
199 | static int lcdif_set_rate(struct clk *clk, u32 rate) | ||
200 | { | ||
201 | int ret = 0; | ||
202 | /* | ||
203 | * On 3700, we can get most timings exact by modifying ref_pix | ||
204 | * and the divider, but keeping the phase timings at 1 (2 | ||
205 | * phases per cycle). | ||
206 | * | ||
207 | * ref_pix can be between 480e6*18/35=246.9MHz and 480e6*18/18=480MHz, | ||
208 | * which is between 18/(18*480e6)=2.084ns and 35/(18*480e6)=4.050ns. | ||
209 | * | ||
210 | * ns_cycle >= 2*18e3/(18*480) = 25/6 | ||
211 | * ns_cycle <= 2*35e3/(18*480) = 875/108 | ||
212 | * | ||
213 | * Multiply the ns_cycle by 'div' to lengthen it until it fits the | ||
214 | * bounds. This is the divider we'll use after ref_pix. | ||
215 | * | ||
216 | * 6 * ns_cycle >= 25 * div | ||
217 | * 108 * ns_cycle <= 875 * div | ||
218 | */ | ||
219 | u32 ns_cycle = 1000000 / rate; | ||
220 | u32 div, reg_val; | ||
221 | u32 lowest_result = (u32) -1; | ||
222 | u32 lowest_div = 0, lowest_fracdiv = 0; | ||
223 | |||
224 | for (div = 1; div < 256; ++div) { | ||
225 | u32 fracdiv; | ||
226 | u32 ps_result; | ||
227 | int lower_bound = 6 * ns_cycle >= 25 * div; | ||
228 | int upper_bound = 108 * ns_cycle <= 875 * div; | ||
229 | if (!lower_bound) | ||
230 | break; | ||
231 | if (!upper_bound) | ||
232 | continue; | ||
233 | /* | ||
234 | * Found a matching div. Calculate fractional divider needed, | ||
235 | * rounded up. | ||
236 | */ | ||
237 | fracdiv = ((clk->parent->rate / 1000 * 18 / 2) * | ||
238 | ns_cycle + 1000 * div - 1) / | ||
239 | (1000 * div); | ||
240 | if (fracdiv < 18 || fracdiv > 35) { | ||
241 | ret = -EINVAL; | ||
242 | goto out; | ||
243 | } | ||
244 | /* Calculate the actual cycle time this results in */ | ||
245 | ps_result = 6250 * div * fracdiv / 27; | ||
246 | |||
247 | /* Use the fastest result that doesn't break ns_cycle */ | ||
248 | if (ps_result <= lowest_result) { | ||
249 | lowest_result = ps_result; | ||
250 | lowest_div = div; | ||
251 | lowest_fracdiv = fracdiv; | ||
252 | } | ||
253 | } | ||
254 | |||
255 | if (div >= 256 || lowest_result == (u32) -1) { | ||
256 | ret = -EINVAL; | ||
257 | goto out; | ||
258 | } | ||
259 | pr_debug("Programming PFD=%u,DIV=%u ref_pix=%uMHz " | ||
260 | "PIXCLK=%uMHz cycle=%u.%03uns\n", | ||
261 | lowest_fracdiv, lowest_div, | ||
262 | 480*18/lowest_fracdiv, 480*18/lowest_fracdiv/lowest_div, | ||
263 | lowest_result / 1000, lowest_result % 1000); | ||
264 | |||
265 | /* Program ref_pix phase fractional divider */ | ||
266 | HW_CLKCTRL_FRAC_WR((HW_CLKCTRL_FRAC_RD() & ~BM_CLKCTRL_FRAC_PIXFRAC) | | ||
267 | BF_CLKCTRL_FRAC_PIXFRAC(lowest_fracdiv)); | ||
268 | /* Ungate PFD */ | ||
269 | HW_CLKCTRL_FRAC_CLR(BM_CLKCTRL_FRAC_CLKGATEPIX); | ||
270 | |||
271 | /* Program pix divider */ | ||
272 | reg_val = __raw_readl(clk->scale_reg); | ||
273 | reg_val &= ~(BM_CLKCTRL_PIX_DIV | BM_CLKCTRL_PIX_CLKGATE); | ||
274 | reg_val |= BF_CLKCTRL_PIX_DIV(lowest_div); | ||
275 | __raw_writel(reg_val, clk->scale_reg); | ||
276 | |||
277 | /* Wait for divider update */ | ||
278 | if (clk->busy_reg) { | ||
279 | int i; | ||
280 | for (i = 10000; i; i--) | ||
281 | if (!clk_is_busy(clk)) | ||
282 | break; | ||
283 | if (!i) { | ||
284 | ret = -ETIMEDOUT; | ||
285 | goto out; | ||
286 | } | ||
287 | } | ||
288 | |||
289 | /* Switch to ref_pix source */ | ||
290 | HW_CLKCTRL_CLKSEQ_CLR(BM_CLKCTRL_CLKSEQ_BYPASS_PIX); | ||
291 | |||
292 | out: | ||
293 | return ret; | ||
294 | } | ||
295 | |||
296 | |||
297 | static int cpu_set_rate(struct clk *clk, u32 rate) | ||
298 | { | ||
299 | if (rate < 24000) | ||
300 | return -EINVAL; | ||
301 | else if (rate == 24000) { | ||
302 | /* switch to the 24M source */ | ||
303 | clk_set_parent(clk, &osc_24M); | ||
304 | } else { | ||
305 | int i; | ||
306 | u32 clkctrl_cpu = 1; | ||
307 | u32 c = clkctrl_cpu; | ||
308 | u32 clkctrl_frac = 1; | ||
309 | u32 val; | ||
310 | for ( ; c < 0x40; c++) { | ||
311 | u32 f = (pll_clk.rate*18/c + rate/2) / rate; | ||
312 | int s1, s2; | ||
313 | |||
314 | if (f < 18 || f > 35) | ||
315 | continue; | ||
316 | s1 = pll_clk.rate*18/clkctrl_frac/clkctrl_cpu - rate; | ||
317 | s2 = pll_clk.rate*18/c/f - rate; | ||
318 | pr_debug("%s: s1 %d, s2 %d\n", __func__, s1, s2); | ||
319 | if (abs(s1) > abs(s2)) { | ||
320 | clkctrl_cpu = c; | ||
321 | clkctrl_frac = f; | ||
322 | } | ||
323 | if (s2 == 0) | ||
324 | break; | ||
325 | }; | ||
326 | pr_debug("%s: clkctrl_cpu %d, clkctrl_frac %d\n", __func__, | ||
327 | clkctrl_cpu, clkctrl_frac); | ||
328 | if (c == 0x40) { | ||
329 | int d = pll_clk.rate*18/clkctrl_frac/clkctrl_cpu - | ||
330 | rate; | ||
331 | if (abs(d) > 100 || | ||
332 | clkctrl_frac < 18 || clkctrl_frac > 35) | ||
333 | return -EINVAL; | ||
334 | } | ||
335 | |||
336 | /* 4.6.2 */ | ||
337 | val = __raw_readl(clk->scale_reg); | ||
338 | val &= ~(0x3f << clk->scale_shift); | ||
339 | val |= clkctrl_frac; | ||
340 | clk_set_parent(clk, &osc_24M); | ||
341 | udelay(10); | ||
342 | __raw_writel(val, clk->scale_reg); | ||
343 | /* ungate */ | ||
344 | __raw_writel(1<<7, clk->scale_reg + 8); | ||
345 | /* write clkctrl_cpu */ | ||
346 | clk->saved_div = clkctrl_cpu; | ||
347 | HW_CLKCTRL_CPU_WR((HW_CLKCTRL_CPU_RD() & ~0x3f) | clkctrl_cpu); | ||
348 | for (i = 10000; i; i--) | ||
349 | if (!clk_is_busy(clk)) | ||
350 | break; | ||
351 | if (!i) { | ||
352 | printk(KERN_ERR "couldn't set up CPU divisor\n"); | ||
353 | return -ETIMEDOUT; | ||
354 | } | ||
355 | clk_set_parent(clk, &pll_clk); | ||
356 | clk->saved_div = 0; | ||
357 | udelay(10); | ||
358 | } | ||
359 | return 0; | ||
360 | } | ||
361 | |||
362 | static long cpu_get_rate(struct clk *clk) | ||
363 | { | ||
364 | long rate = clk->parent->rate * 18; | ||
365 | |||
366 | rate /= (__raw_readl(clk->scale_reg) >> clk->scale_shift) & 0x3f; | ||
367 | rate /= HW_CLKCTRL_CPU_RD() & 0x3f; | ||
368 | rate = ((rate + 9) / 10) * 10; | ||
369 | clk->rate = rate; | ||
370 | |||
371 | return rate; | ||
372 | } | ||
373 | |||
374 | static long cpu_round_rate(struct clk *clk, u32 rate) | ||
375 | { | ||
376 | unsigned long r = 0; | ||
377 | |||
378 | if (rate <= 24000) | ||
379 | r = 24000; | ||
380 | else { | ||
381 | u32 clkctrl_cpu = 1; | ||
382 | u32 clkctrl_frac; | ||
383 | do { | ||
384 | clkctrl_frac = | ||
385 | (pll_clk.rate*18 / clkctrl_cpu + rate/2) / rate; | ||
386 | if (clkctrl_frac > 35) | ||
387 | continue; | ||
388 | if (pll_clk.rate*18 / clkctrl_frac / clkctrl_cpu/10 == | ||
389 | rate / 10) | ||
390 | break; | ||
391 | } while (pll_clk.rate / 2 >= clkctrl_cpu++ * rate); | ||
392 | if (pll_clk.rate / 2 < (clkctrl_cpu - 1) * rate) | ||
393 | clkctrl_cpu--; | ||
394 | pr_debug("%s: clkctrl_cpu %d, clkctrl_frac %d\n", __func__, | ||
395 | clkctrl_cpu, clkctrl_frac); | ||
396 | if (clkctrl_frac < 18) | ||
397 | clkctrl_frac = 18; | ||
398 | if (clkctrl_frac > 35) | ||
399 | clkctrl_frac = 35; | ||
400 | |||
401 | r = pll_clk.rate * 18; | ||
402 | r /= clkctrl_frac; | ||
403 | r /= clkctrl_cpu; | ||
404 | r = 10 * ((r + 9) / 10); | ||
405 | } | ||
406 | return r; | ||
407 | } | ||
408 | |||
409 | static long emi_get_rate(struct clk *clk) | ||
410 | { | ||
411 | long rate = clk->parent->rate * 18; | ||
412 | |||
413 | rate /= (__raw_readl(clk->scale_reg) >> clk->scale_shift) & 0x3f; | ||
414 | rate /= HW_CLKCTRL_EMI_RD() & 0x3f; | ||
415 | clk->rate = rate; | ||
416 | |||
417 | return rate; | ||
418 | } | ||
419 | |||
420 | static int clkseq_set_parent(struct clk *clk, struct clk *parent) | ||
421 | { | ||
422 | int ret = -EINVAL; | ||
423 | int shift = 8; | ||
424 | |||
425 | /* bypass? */ | ||
426 | if (parent == &osc_24M) | ||
427 | shift = 4; | ||
428 | |||
429 | if (clk->bypass_reg) { | ||
430 | u32 hbus_mask = BM_CLKCTRL_HBUS_DIV_FRAC_EN | | ||
431 | BM_CLKCTRL_HBUS_DIV; | ||
432 | |||
433 | if (clk == &cpu_clk && shift == 4) { | ||
434 | u32 hbus_val = HW_CLKCTRL_HBUS_RD(); | ||
435 | u32 cpu_val = HW_CLKCTRL_CPU_RD(); | ||
436 | hbus_val &= ~hbus_mask; | ||
437 | hbus_val |= 1; | ||
438 | clk->saved_div = cpu_val & BM_CLKCTRL_CPU_DIV_CPU; | ||
439 | cpu_val &= ~BM_CLKCTRL_CPU_DIV_CPU; | ||
440 | cpu_val |= 1; | ||
441 | __raw_writel(1 << clk->bypass_shift, | ||
442 | clk->bypass_reg + shift); | ||
443 | if (machine_is_stmp378x()) { | ||
444 | HW_CLKCTRL_HBUS_WR(hbus_val); | ||
445 | HW_CLKCTRL_CPU_WR(cpu_val); | ||
446 | hclk.rate = 0; | ||
447 | } | ||
448 | } else if (clk == &cpu_clk && shift == 8) { | ||
449 | u32 hbus_val = HW_CLKCTRL_HBUS_RD(); | ||
450 | u32 cpu_val = HW_CLKCTRL_CPU_RD(); | ||
451 | hbus_val &= ~hbus_mask; | ||
452 | hbus_val |= 2; | ||
453 | cpu_val &= ~BM_CLKCTRL_CPU_DIV_CPU; | ||
454 | if (clk->saved_div) | ||
455 | cpu_val |= clk->saved_div; | ||
456 | else | ||
457 | cpu_val |= 2; | ||
458 | if (machine_is_stmp378x()) { | ||
459 | HW_CLKCTRL_HBUS_WR(hbus_val); | ||
460 | HW_CLKCTRL_CPU_WR(cpu_val); | ||
461 | hclk.rate = 0; | ||
462 | } | ||
463 | __raw_writel(1 << clk->bypass_shift, | ||
464 | clk->bypass_reg + shift); | ||
465 | } else | ||
466 | __raw_writel(1 << clk->bypass_shift, | ||
467 | clk->bypass_reg + shift); | ||
468 | |||
469 | ret = 0; | ||
470 | } | ||
471 | |||
472 | return ret; | ||
473 | } | ||
474 | |||
475 | static int hbus_set_rate(struct clk *clk, u32 rate) | ||
476 | { | ||
477 | u8 div = 0; | ||
478 | int is_frac = 0; | ||
479 | u32 clkctrl_hbus; | ||
480 | struct clk *parent = clk->parent; | ||
481 | |||
482 | pr_debug("%s: rate %d, parent rate %d\n", __func__, rate, | ||
483 | parent->rate); | ||
484 | |||
485 | if (rate > parent->rate) | ||
486 | return -EINVAL; | ||
487 | |||
488 | if (((parent->rate + rate/2) / rate) * rate != parent->rate && | ||
489 | parent->rate / rate < 32) { | ||
490 | pr_debug("%s: switching to fractional mode\n", __func__); | ||
491 | is_frac = 1; | ||
492 | } | ||
493 | |||
494 | if (is_frac) | ||
495 | div = (32 * rate + parent->rate / 2) / parent->rate; | ||
496 | else | ||
497 | div = (parent->rate + rate - 1) / rate; | ||
498 | pr_debug("%s: div calculated is %d\n", __func__, div); | ||
499 | if (!div || div > 0x1f) | ||
500 | return -EINVAL; | ||
501 | |||
502 | clk_set_parent(&cpu_clk, &osc_24M); | ||
503 | udelay(10); | ||
504 | clkctrl_hbus = __raw_readl(clk->scale_reg); | ||
505 | clkctrl_hbus &= ~0x3f; | ||
506 | clkctrl_hbus |= div; | ||
507 | clkctrl_hbus |= (is_frac << 5); | ||
508 | |||
509 | __raw_writel(clkctrl_hbus, clk->scale_reg); | ||
510 | if (clk->busy_reg) { | ||
511 | int i; | ||
512 | for (i = 10000; i; i--) | ||
513 | if (!clk_is_busy(clk)) | ||
514 | break; | ||
515 | if (!i) { | ||
516 | printk(KERN_ERR "couldn't set up CPU divisor\n"); | ||
517 | return -ETIMEDOUT; | ||
518 | } | ||
519 | } | ||
520 | clk_set_parent(&cpu_clk, &pll_clk); | ||
521 | __raw_writel(clkctrl_hbus, clk->scale_reg); | ||
522 | udelay(10); | ||
523 | return 0; | ||
524 | } | ||
525 | |||
526 | static long hbus_get_rate(struct clk *clk) | ||
527 | { | ||
528 | long rate = clk->parent->rate; | ||
529 | |||
530 | if (__raw_readl(clk->scale_reg) & 0x20) { | ||
531 | rate *= __raw_readl(clk->scale_reg) & 0x1f; | ||
532 | rate /= 32; | ||
533 | } else | ||
534 | rate /= __raw_readl(clk->scale_reg) & 0x1f; | ||
535 | clk->rate = rate; | ||
536 | |||
537 | return rate; | ||
538 | } | ||
539 | |||
540 | static int xbus_set_rate(struct clk *clk, u32 rate) | ||
541 | { | ||
542 | u16 div = 0; | ||
543 | u32 clkctrl_xbus; | ||
544 | |||
545 | pr_debug("%s: rate %d, parent rate %d\n", __func__, rate, | ||
546 | clk->parent->rate); | ||
547 | |||
548 | div = (clk->parent->rate + rate - 1) / rate; | ||
549 | pr_debug("%s: div calculated is %d\n", __func__, div); | ||
550 | if (!div || div > 0x3ff) | ||
551 | return -EINVAL; | ||
552 | |||
553 | clkctrl_xbus = __raw_readl(clk->scale_reg); | ||
554 | clkctrl_xbus &= ~0x3ff; | ||
555 | clkctrl_xbus |= div; | ||
556 | __raw_writel(clkctrl_xbus, clk->scale_reg); | ||
557 | if (clk->busy_reg) { | ||
558 | int i; | ||
559 | for (i = 10000; i; i--) | ||
560 | if (!clk_is_busy(clk)) | ||
561 | break; | ||
562 | if (!i) { | ||
563 | printk(KERN_ERR "couldn't set up xbus divisor\n"); | ||
564 | return -ETIMEDOUT; | ||
565 | } | ||
566 | } | ||
567 | return 0; | ||
568 | } | ||
569 | |||
570 | static long xbus_get_rate(struct clk *clk) | ||
571 | { | ||
572 | long rate = clk->parent->rate; | ||
573 | |||
574 | rate /= __raw_readl(clk->scale_reg) & 0x3ff; | ||
575 | clk->rate = rate; | ||
576 | |||
577 | return rate; | ||
578 | } | ||
579 | |||
580 | |||
581 | /* Clock ops */ | ||
582 | |||
583 | static struct clk_ops std_ops = { | ||
584 | .enable = std_clk_enable, | ||
585 | .disable = std_clk_disable, | ||
586 | .get_rate = per_get_rate, | ||
587 | .set_rate = per_set_rate, | ||
588 | .set_parent = clkseq_set_parent, | ||
589 | }; | ||
590 | |||
591 | static struct clk_ops min_ops = { | ||
592 | .enable = std_clk_enable, | ||
593 | .disable = std_clk_disable, | ||
594 | }; | ||
595 | |||
596 | static struct clk_ops cpu_ops = { | ||
597 | .enable = std_clk_enable, | ||
598 | .disable = std_clk_disable, | ||
599 | .get_rate = cpu_get_rate, | ||
600 | .set_rate = cpu_set_rate, | ||
601 | .round_rate = cpu_round_rate, | ||
602 | .set_parent = clkseq_set_parent, | ||
603 | }; | ||
604 | |||
605 | static struct clk_ops io_ops = { | ||
606 | .enable = std_clk_enable, | ||
607 | .disable = std_clk_disable, | ||
608 | .get_rate = io_get_rate, | ||
609 | .set_rate = io_set_rate, | ||
610 | }; | ||
611 | |||
612 | static struct clk_ops hbus_ops = { | ||
613 | .get_rate = hbus_get_rate, | ||
614 | .set_rate = hbus_set_rate, | ||
615 | }; | ||
616 | |||
617 | static struct clk_ops xbus_ops = { | ||
618 | .get_rate = xbus_get_rate, | ||
619 | .set_rate = xbus_set_rate, | ||
620 | }; | ||
621 | |||
622 | static struct clk_ops lcdif_ops = { | ||
623 | .enable = std_clk_enable, | ||
624 | .disable = std_clk_disable, | ||
625 | .get_rate = lcdif_get_rate, | ||
626 | .set_rate = lcdif_set_rate, | ||
627 | .set_parent = clkseq_set_parent, | ||
628 | }; | ||
629 | |||
630 | static struct clk_ops emi_ops = { | ||
631 | .get_rate = emi_get_rate, | ||
632 | }; | ||
633 | |||
634 | /* List of on-chip clocks */ | ||
635 | |||
636 | static struct clk osc_24M = { | ||
637 | .flags = FIXED_RATE | ENABLED, | ||
638 | .rate = 24000, | ||
639 | }; | ||
640 | |||
641 | static struct clk pll_clk = { | ||
642 | .parent = &osc_24M, | ||
643 | .enable_reg = HW_CLKCTRL_PLLCTRL0_ADDR, | ||
644 | .enable_shift = 16, | ||
645 | .enable_wait = 10, | ||
646 | .flags = FIXED_RATE | ENABLED, | ||
647 | .rate = 480000, | ||
648 | .ops = &min_ops, | ||
649 | }; | ||
650 | |||
651 | static struct clk cpu_clk = { | ||
652 | .parent = &pll_clk, | ||
653 | .scale_reg = HW_CLKCTRL_FRAC_ADDR, | ||
654 | .scale_shift = 0, | ||
655 | .bypass_reg = HW_CLKCTRL_CLKSEQ_ADDR, | ||
656 | .bypass_shift = 7, | ||
657 | .busy_reg = HW_CLKCTRL_CPU_ADDR, | ||
658 | .busy_bit = 28, | ||
659 | .flags = RATE_PROPAGATES | ENABLED, | ||
660 | .ops = &cpu_ops, | ||
661 | }; | ||
662 | |||
663 | static struct clk io_clk = { | ||
664 | .parent = &pll_clk, | ||
665 | .enable_reg = HW_CLKCTRL_FRAC_ADDR, | ||
666 | .enable_shift = 31, | ||
667 | .enable_negate = 1, | ||
668 | .scale_reg = HW_CLKCTRL_FRAC_ADDR, | ||
669 | .scale_shift = 24, | ||
670 | .flags = RATE_PROPAGATES | ENABLED, | ||
671 | .ops = &io_ops, | ||
672 | }; | ||
673 | |||
674 | static struct clk hclk = { | ||
675 | .parent = &cpu_clk, | ||
676 | .scale_reg = HW_CLKCTRL_HBUS_ADDR, | ||
677 | .bypass_reg = HW_CLKCTRL_CLKSEQ_ADDR, | ||
678 | .bypass_shift = 7, | ||
679 | .busy_reg = HW_CLKCTRL_HBUS_ADDR, | ||
680 | .busy_bit = 29, | ||
681 | .flags = RATE_PROPAGATES | ENABLED, | ||
682 | .ops = &hbus_ops, | ||
683 | }; | ||
684 | |||
685 | static struct clk xclk = { | ||
686 | .parent = &osc_24M, | ||
687 | .scale_reg = HW_CLKCTRL_XBUS_ADDR, | ||
688 | .busy_reg = HW_CLKCTRL_XBUS_ADDR, | ||
689 | .busy_bit = 31, | ||
690 | .flags = RATE_PROPAGATES | ENABLED, | ||
691 | .ops = &xbus_ops, | ||
692 | }; | ||
693 | |||
694 | static struct clk uart_clk = { | ||
695 | .parent = &xclk, | ||
696 | .enable_reg = HW_CLKCTRL_XTAL_ADDR, | ||
697 | .enable_shift = 31, | ||
698 | .enable_negate = 1, | ||
699 | .flags = ENABLED, | ||
700 | .ops = &min_ops, | ||
701 | }; | ||
702 | |||
703 | static struct clk audio_clk = { | ||
704 | .parent = &xclk, | ||
705 | .enable_reg = HW_CLKCTRL_XTAL_ADDR, | ||
706 | .enable_shift = 30, | ||
707 | .enable_negate = 1, | ||
708 | .ops = &min_ops, | ||
709 | }; | ||
710 | |||
711 | static struct clk pwm_clk = { | ||
712 | .parent = &xclk, | ||
713 | .enable_reg = HW_CLKCTRL_XTAL_ADDR, | ||
714 | .enable_shift = 29, | ||
715 | .enable_negate = 1, | ||
716 | .ops = &min_ops, | ||
717 | }; | ||
718 | |||
719 | static struct clk dri_clk = { | ||
720 | .parent = &xclk, | ||
721 | .enable_reg = HW_CLKCTRL_XTAL_ADDR, | ||
722 | .enable_shift = 28, | ||
723 | .enable_negate = 1, | ||
724 | .ops = &min_ops, | ||
725 | }; | ||
726 | |||
727 | static struct clk digctl_clk = { | ||
728 | .parent = &xclk, | ||
729 | .enable_reg = HW_CLKCTRL_XTAL_ADDR, | ||
730 | .enable_shift = 27, | ||
731 | .enable_negate = 1, | ||
732 | .ops = &min_ops, | ||
733 | }; | ||
734 | |||
735 | static struct clk timer_clk = { | ||
736 | .parent = &xclk, | ||
737 | .enable_reg = HW_CLKCTRL_XTAL_ADDR, | ||
738 | .enable_shift = 26, | ||
739 | .enable_negate = 1, | ||
740 | .flags = ENABLED, | ||
741 | .ops = &min_ops, | ||
742 | }; | ||
743 | |||
744 | static struct clk lcdif_clk = { | ||
745 | .parent = &pll_clk, | ||
746 | .scale_reg = HW_CLKCTRL_PIX_ADDR, | ||
747 | .busy_reg = HW_CLKCTRL_PIX_ADDR, | ||
748 | .busy_bit = 29, | ||
749 | .enable_reg = HW_CLKCTRL_PIX_ADDR, | ||
750 | .enable_shift = 31, | ||
751 | .enable_negate = 1, | ||
752 | .bypass_reg = HW_CLKCTRL_CLKSEQ_ADDR, | ||
753 | .bypass_shift = 1, | ||
754 | .flags = NEEDS_SET_PARENT, | ||
755 | .ops = &lcdif_ops, | ||
756 | }; | ||
757 | |||
758 | static struct clk ssp_clk = { | ||
759 | .parent = &io_clk, | ||
760 | .scale_reg = HW_CLKCTRL_SSP_ADDR, | ||
761 | .busy_reg = HW_CLKCTRL_SSP_ADDR, | ||
762 | .busy_bit = 29, | ||
763 | .enable_reg = HW_CLKCTRL_SSP_ADDR, | ||
764 | .enable_shift = 31, | ||
765 | .bypass_reg = HW_CLKCTRL_CLKSEQ_ADDR, | ||
766 | .bypass_shift = 5, | ||
767 | .enable_negate = 1, | ||
768 | .flags = NEEDS_SET_PARENT, | ||
769 | .ops = &std_ops, | ||
770 | }; | ||
771 | |||
772 | static struct clk gpmi_clk = { | ||
773 | .parent = &io_clk, | ||
774 | .scale_reg = HW_CLKCTRL_GPMI_ADDR, | ||
775 | .busy_reg = HW_CLKCTRL_GPMI_ADDR, | ||
776 | .busy_bit = 29, | ||
777 | .enable_reg = HW_CLKCTRL_GPMI_ADDR, | ||
778 | .enable_shift = 31, | ||
779 | .enable_negate = 1, | ||
780 | .bypass_reg = HW_CLKCTRL_CLKSEQ_ADDR, | ||
781 | .bypass_shift = 4, | ||
782 | .flags = NEEDS_SET_PARENT, | ||
783 | .ops = &std_ops, | ||
784 | }; | ||
785 | |||
786 | static struct clk spdif_clk = { | ||
787 | .parent = &pll_clk, | ||
788 | .enable_reg = HW_CLKCTRL_SPDIF_ADDR, | ||
789 | .enable_shift = 31, | ||
790 | .enable_negate = 1, | ||
791 | .ops = &min_ops, | ||
792 | }; | ||
793 | |||
794 | static struct clk emi_clk = { | ||
795 | .parent = &pll_clk, | ||
796 | .enable_reg = HW_CLKCTRL_EMI_ADDR, | ||
797 | .enable_shift = 31, | ||
798 | .enable_negate = 1, | ||
799 | .scale_reg = HW_CLKCTRL_FRAC_ADDR, | ||
800 | .scale_shift = 8, | ||
801 | .busy_reg = HW_CLKCTRL_EMI_ADDR, | ||
802 | .busy_bit = 28, | ||
803 | .bypass_reg = HW_CLKCTRL_CLKSEQ_ADDR, | ||
804 | .bypass_shift = 6, | ||
805 | .flags = ENABLED, | ||
806 | .ops = &emi_ops, | ||
807 | }; | ||
808 | |||
809 | static struct clk ir_clk = { | ||
810 | .parent = &io_clk, | ||
811 | .enable_reg = HW_CLKCTRL_IR_ADDR, | ||
812 | .enable_shift = 31, | ||
813 | .enable_negate = 1, | ||
814 | .bypass_reg = HW_CLKCTRL_CLKSEQ_ADDR, | ||
815 | .bypass_shift = 3, | ||
816 | .ops = &min_ops, | ||
817 | }; | ||
818 | |||
819 | static struct clk saif_clk = { | ||
820 | .parent = &pll_clk, | ||
821 | .scale_reg = HW_CLKCTRL_SAIF_ADDR, | ||
822 | .busy_reg = HW_CLKCTRL_SAIF_ADDR, | ||
823 | .busy_bit = 29, | ||
824 | .enable_reg = HW_CLKCTRL_SAIF_ADDR, | ||
825 | .enable_shift = 31, | ||
826 | .enable_negate = 1, | ||
827 | .bypass_reg = HW_CLKCTRL_CLKSEQ_ADDR, | ||
828 | .bypass_shift = 0, | ||
829 | .ops = &std_ops, | ||
830 | }; | ||
831 | |||
832 | static struct clk usb_clk = { | ||
833 | .parent = &pll_clk, | ||
834 | .enable_reg = HW_CLKCTRL_PLLCTRL0_ADDR, | ||
835 | .enable_shift = 18, | ||
836 | .enable_negate = 1, | ||
837 | .ops = &min_ops, | ||
838 | }; | ||
839 | |||
840 | /* list of all the clocks */ | ||
841 | static __initdata struct clk_lookup onchip_clks[] = { | ||
842 | { | ||
843 | .con_id = "osc_24M", | ||
844 | .clk = &osc_24M, | ||
845 | }, { | ||
846 | .con_id = "pll", | ||
847 | .clk = &pll_clk, | ||
848 | }, { | ||
849 | .con_id = "cpu", | ||
850 | .clk = &cpu_clk, | ||
851 | }, { | ||
852 | .con_id = "hclk", | ||
853 | .clk = &hclk, | ||
854 | }, { | ||
855 | .con_id = "xclk", | ||
856 | .clk = &xclk, | ||
857 | }, { | ||
858 | .con_id = "io", | ||
859 | .clk = &io_clk, | ||
860 | }, { | ||
861 | .con_id = "uart", | ||
862 | .clk = &uart_clk, | ||
863 | }, { | ||
864 | .con_id = "audio", | ||
865 | .clk = &audio_clk, | ||
866 | }, { | ||
867 | .con_id = "pwm", | ||
868 | .clk = &pwm_clk, | ||
869 | }, { | ||
870 | .con_id = "dri", | ||
871 | .clk = &dri_clk, | ||
872 | }, { | ||
873 | .con_id = "digctl", | ||
874 | .clk = &digctl_clk, | ||
875 | }, { | ||
876 | .con_id = "timer", | ||
877 | .clk = &timer_clk, | ||
878 | }, { | ||
879 | .con_id = "lcdif", | ||
880 | .clk = &lcdif_clk, | ||
881 | }, { | ||
882 | .con_id = "ssp", | ||
883 | .clk = &ssp_clk, | ||
884 | }, { | ||
885 | .con_id = "gpmi", | ||
886 | .clk = &gpmi_clk, | ||
887 | }, { | ||
888 | .con_id = "spdif", | ||
889 | .clk = &spdif_clk, | ||
890 | }, { | ||
891 | .con_id = "emi", | ||
892 | .clk = &emi_clk, | ||
893 | }, { | ||
894 | .con_id = "ir", | ||
895 | .clk = &ir_clk, | ||
896 | }, { | ||
897 | .con_id = "saif", | ||
898 | .clk = &saif_clk, | ||
899 | }, { | ||
900 | .con_id = "usb", | ||
901 | .clk = &usb_clk, | ||
902 | }, | ||
903 | }; | ||
904 | |||
905 | static int __init propagate_rate(struct clk *clk) | ||
906 | { | ||
907 | struct clk_lookup *cl; | ||
908 | |||
909 | for (cl = onchip_clks; cl < onchip_clks + ARRAY_SIZE(onchip_clks); | ||
910 | cl++) { | ||
911 | if (unlikely(!clk_good(cl->clk))) | ||
912 | continue; | ||
913 | if (cl->clk->parent == clk && cl->clk->ops->get_rate) { | ||
914 | cl->clk->ops->get_rate(cl->clk); | ||
915 | if (cl->clk->flags & RATE_PROPAGATES) | ||
916 | propagate_rate(cl->clk); | ||
917 | } | ||
918 | } | ||
919 | |||
920 | return 0; | ||
921 | } | ||
922 | |||
923 | /* Exported API */ | ||
924 | unsigned long clk_get_rate(struct clk *clk) | ||
925 | { | ||
926 | if (unlikely(!clk_good(clk))) | ||
927 | return 0; | ||
928 | |||
929 | if (clk->rate != 0) | ||
930 | return clk->rate; | ||
931 | |||
932 | if (clk->ops->get_rate != NULL) | ||
933 | return clk->ops->get_rate(clk); | ||
934 | |||
935 | return clk_get_rate(clk->parent); | ||
936 | } | ||
937 | EXPORT_SYMBOL(clk_get_rate); | ||
938 | |||
939 | long clk_round_rate(struct clk *clk, unsigned long rate) | ||
940 | { | ||
941 | if (unlikely(!clk_good(clk))) | ||
942 | return 0; | ||
943 | |||
944 | if (clk->ops->round_rate) | ||
945 | return clk->ops->round_rate(clk, rate); | ||
946 | |||
947 | return 0; | ||
948 | } | ||
949 | EXPORT_SYMBOL(clk_round_rate); | ||
950 | |||
951 | static inline int close_enough(long rate1, long rate2) | ||
952 | { | ||
953 | return rate1 && !((rate2 - rate1) * 1000 / rate1); | ||
954 | } | ||
955 | |||
956 | int clk_set_rate(struct clk *clk, unsigned long rate) | ||
957 | { | ||
958 | int ret = -EINVAL; | ||
959 | |||
960 | if (unlikely(!clk_good(clk))) | ||
961 | goto out; | ||
962 | |||
963 | if (clk->flags & FIXED_RATE || !clk->ops->set_rate) | ||
964 | goto out; | ||
965 | |||
966 | else if (!close_enough(clk->rate, rate)) { | ||
967 | ret = clk->ops->set_rate(clk, rate); | ||
968 | if (ret < 0) | ||
969 | goto out; | ||
970 | clk->rate = rate; | ||
971 | if (clk->flags & RATE_PROPAGATES) | ||
972 | propagate_rate(clk); | ||
973 | } else | ||
974 | ret = 0; | ||
975 | |||
976 | out: | ||
977 | return ret; | ||
978 | } | ||
979 | EXPORT_SYMBOL(clk_set_rate); | ||
980 | |||
981 | int clk_enable(struct clk *clk) | ||
982 | { | ||
983 | unsigned long clocks_flags; | ||
984 | |||
985 | if (unlikely(!clk_good(clk))) | ||
986 | return -EINVAL; | ||
987 | |||
988 | if (clk->parent) | ||
989 | clk_enable(clk->parent); | ||
990 | |||
991 | spin_lock_irqsave(&clocks_lock, clocks_flags); | ||
992 | |||
993 | clk->usage++; | ||
994 | if (clk->ops && clk->ops->enable) | ||
995 | clk->ops->enable(clk); | ||
996 | |||
997 | spin_unlock_irqrestore(&clocks_lock, clocks_flags); | ||
998 | return 0; | ||
999 | } | ||
1000 | EXPORT_SYMBOL(clk_enable); | ||
1001 | |||
1002 | static void local_clk_disable(struct clk *clk) | ||
1003 | { | ||
1004 | if (unlikely(!clk_good(clk))) | ||
1005 | return; | ||
1006 | |||
1007 | if (clk->usage == 0 && clk->ops->disable) | ||
1008 | clk->ops->disable(clk); | ||
1009 | |||
1010 | if (clk->parent) | ||
1011 | local_clk_disable(clk->parent); | ||
1012 | } | ||
1013 | |||
1014 | void clk_disable(struct clk *clk) | ||
1015 | { | ||
1016 | unsigned long clocks_flags; | ||
1017 | |||
1018 | if (unlikely(!clk_good(clk))) | ||
1019 | return; | ||
1020 | |||
1021 | spin_lock_irqsave(&clocks_lock, clocks_flags); | ||
1022 | |||
1023 | if ((--clk->usage) == 0 && clk->ops->disable) | ||
1024 | clk->ops->disable(clk); | ||
1025 | |||
1026 | spin_unlock_irqrestore(&clocks_lock, clocks_flags); | ||
1027 | if (clk->parent) | ||
1028 | clk_disable(clk->parent); | ||
1029 | } | ||
1030 | EXPORT_SYMBOL(clk_disable); | ||
1031 | |||
1032 | /* Some additional API */ | ||
1033 | int clk_set_parent(struct clk *clk, struct clk *parent) | ||
1034 | { | ||
1035 | int ret = -ENODEV; | ||
1036 | unsigned long clocks_flags; | ||
1037 | |||
1038 | if (unlikely(!clk_good(clk))) | ||
1039 | goto out; | ||
1040 | |||
1041 | if (!clk->ops->set_parent) | ||
1042 | goto out; | ||
1043 | |||
1044 | spin_lock_irqsave(&clocks_lock, clocks_flags); | ||
1045 | |||
1046 | ret = clk->ops->set_parent(clk, parent); | ||
1047 | if (!ret) { | ||
1048 | /* disable if usage count is 0 */ | ||
1049 | local_clk_disable(parent); | ||
1050 | |||
1051 | parent->usage += clk->usage; | ||
1052 | clk->parent->usage -= clk->usage; | ||
1053 | |||
1054 | /* disable if new usage count is 0 */ | ||
1055 | local_clk_disable(clk->parent); | ||
1056 | |||
1057 | clk->parent = parent; | ||
1058 | } | ||
1059 | spin_unlock_irqrestore(&clocks_lock, clocks_flags); | ||
1060 | |||
1061 | out: | ||
1062 | return ret; | ||
1063 | } | ||
1064 | EXPORT_SYMBOL(clk_set_parent); | ||
1065 | |||
1066 | struct clk *clk_get_parent(struct clk *clk) | ||
1067 | { | ||
1068 | if (unlikely(!clk_good(clk))) | ||
1069 | return NULL; | ||
1070 | return clk->parent; | ||
1071 | } | ||
1072 | EXPORT_SYMBOL(clk_get_parent); | ||
1073 | |||
1074 | static int __init clk_init(void) | ||
1075 | { | ||
1076 | struct clk_lookup *cl; | ||
1077 | struct clk_ops *ops; | ||
1078 | |||
1079 | spin_lock_init(&clocks_lock); | ||
1080 | |||
1081 | for (cl = onchip_clks; cl < onchip_clks + ARRAY_SIZE(onchip_clks); | ||
1082 | cl++) { | ||
1083 | if (cl->clk->flags & ENABLED) | ||
1084 | clk_enable(cl->clk); | ||
1085 | else | ||
1086 | local_clk_disable(cl->clk); | ||
1087 | |||
1088 | ops = cl->clk->ops; | ||
1089 | |||
1090 | if ((cl->clk->flags & NEEDS_INITIALIZATION) && | ||
1091 | ops && ops->set_rate) | ||
1092 | ops->set_rate(cl->clk, cl->clk->rate); | ||
1093 | |||
1094 | if (cl->clk->flags & FIXED_RATE) { | ||
1095 | if (cl->clk->flags & RATE_PROPAGATES) | ||
1096 | propagate_rate(cl->clk); | ||
1097 | } else { | ||
1098 | if (ops && ops->get_rate) | ||
1099 | ops->get_rate(cl->clk); | ||
1100 | } | ||
1101 | |||
1102 | if (cl->clk->flags & NEEDS_SET_PARENT) { | ||
1103 | if (ops && ops->set_parent) | ||
1104 | ops->set_parent(cl->clk, cl->clk->parent); | ||
1105 | } | ||
1106 | |||
1107 | clkdev_add(cl); | ||
1108 | } | ||
1109 | return 0; | ||
1110 | } | ||
1111 | |||
1112 | arch_initcall(clk_init); | ||
diff --git a/arch/arm/plat-stmp3xxx/clock.h b/arch/arm/plat-stmp3xxx/clock.h new file mode 100644 index 000000000000..a6611e1a3510 --- /dev/null +++ b/arch/arm/plat-stmp3xxx/clock.h | |||
@@ -0,0 +1,61 @@ | |||
1 | /* | ||
2 | * Clock control driver for Freescale STMP37XX/STMP378X - internal header file | ||
3 | * | ||
4 | * Author: Vitaly Wool <vital@embeddedalley.com> | ||
5 | * | ||
6 | * Copyright 2008 Freescale Semiconductor, Inc. All Rights Reserved. | ||
7 | * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. | ||
8 | */ | ||
9 | |||
10 | /* | ||
11 | * The code contained herein is licensed under the GNU General Public | ||
12 | * License. You may obtain a copy of the GNU General Public License | ||
13 | * Version 2 or later at the following locations: | ||
14 | * | ||
15 | * http://www.opensource.org/licenses/gpl-license.html | ||
16 | * http://www.gnu.org/copyleft/gpl.html | ||
17 | */ | ||
18 | #ifndef __ARCH_ARM_STMX3XXX_CLOCK_H__ | ||
19 | #define __ARCH_ARM_STMX3XXX_CLOCK_H__ | ||
20 | |||
21 | #ifndef __ASSEMBLER__ | ||
22 | |||
23 | struct clk_ops { | ||
24 | int (*enable) (struct clk *); | ||
25 | int (*disable) (struct clk *); | ||
26 | long (*get_rate) (struct clk *); | ||
27 | long (*round_rate) (struct clk *, u32); | ||
28 | int (*set_rate) (struct clk *, u32); | ||
29 | int (*set_parent) (struct clk *, struct clk *); | ||
30 | }; | ||
31 | |||
32 | struct clk { | ||
33 | struct clk *parent; | ||
34 | u32 rate; | ||
35 | u32 flags; | ||
36 | u8 scale_shift; | ||
37 | u8 enable_shift; | ||
38 | u8 bypass_shift; | ||
39 | u8 busy_bit; | ||
40 | s8 usage; | ||
41 | int enable_wait; | ||
42 | int enable_negate; | ||
43 | u32 saved_div; | ||
44 | void __iomem *enable_reg; | ||
45 | void __iomem *scale_reg; | ||
46 | void __iomem *bypass_reg; | ||
47 | void __iomem *busy_reg; | ||
48 | struct clk_ops *ops; | ||
49 | }; | ||
50 | |||
51 | #endif /* __ASSEMBLER__ */ | ||
52 | |||
53 | /* Flags */ | ||
54 | #define RATE_PROPAGATES (1<<0) | ||
55 | #define NEEDS_INITIALIZATION (1<<1) | ||
56 | #define PARENT_SET_RATE (1<<2) | ||
57 | #define FIXED_RATE (1<<3) | ||
58 | #define ENABLED (1<<4) | ||
59 | #define NEEDS_SET_PARENT (1<<5) | ||
60 | |||
61 | #endif | ||
diff --git a/arch/arm/plat-stmp3xxx/core.c b/arch/arm/plat-stmp3xxx/core.c new file mode 100644 index 000000000000..6e2fef1639b0 --- /dev/null +++ b/arch/arm/plat-stmp3xxx/core.c | |||
@@ -0,0 +1,127 @@ | |||
1 | /* | ||
2 | * Freescale STMP37XX/STMP378X core routines | ||
3 | * | ||
4 | * Embedded Alley Solutions, Inc <source@embeddedalley.com> | ||
5 | * | ||
6 | * Copyright 2008 Freescale Semiconductor, Inc. All Rights Reserved. | ||
7 | * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. | ||
8 | */ | ||
9 | |||
10 | /* | ||
11 | * The code contained herein is licensed under the GNU General Public | ||
12 | * License. You may obtain a copy of the GNU General Public License | ||
13 | * Version 2 or later at the following locations: | ||
14 | * | ||
15 | * http://www.opensource.org/licenses/gpl-license.html | ||
16 | * http://www.gnu.org/copyleft/gpl.html | ||
17 | */ | ||
18 | #include <linux/kernel.h> | ||
19 | #include <linux/init.h> | ||
20 | #include <linux/io.h> | ||
21 | |||
22 | #include <mach/stmp3xxx.h> | ||
23 | #include <mach/dma.h> | ||
24 | #include <mach/regs-clkctrl.h> | ||
25 | |||
26 | static int __stmp3xxx_reset_block(void __iomem *hwreg, int just_enable) | ||
27 | { | ||
28 | u32 c; | ||
29 | int timeout; | ||
30 | |||
31 | /* the process of software reset of IP block is done | ||
32 | in several steps: | ||
33 | |||
34 | - clear SFTRST and wait for block is enabled; | ||
35 | - clear clock gating (CLKGATE bit); | ||
36 | - set the SFTRST again and wait for block is in reset; | ||
37 | - clear SFTRST and wait for reset completion. | ||
38 | */ | ||
39 | c = __raw_readl(hwreg); | ||
40 | c &= ~(1<<31); /* clear SFTRST */ | ||
41 | __raw_writel(c, hwreg); | ||
42 | for (timeout = 1000000; timeout > 0; timeout--) | ||
43 | /* still in SFTRST state ? */ | ||
44 | if ((__raw_readl(hwreg) & (1<<31)) == 0) | ||
45 | break; | ||
46 | if (timeout <= 0) { | ||
47 | printk(KERN_ERR"%s(%p): timeout when enabling\n", | ||
48 | __func__, hwreg); | ||
49 | return -ETIME; | ||
50 | } | ||
51 | |||
52 | c = __raw_readl(hwreg); | ||
53 | c &= ~(1<<30); /* clear CLKGATE */ | ||
54 | __raw_writel(c, hwreg); | ||
55 | |||
56 | if (!just_enable) { | ||
57 | c = __raw_readl(hwreg); | ||
58 | c |= (1<<31); /* now again set SFTRST */ | ||
59 | __raw_writel(c, hwreg); | ||
60 | for (timeout = 1000000; timeout > 0; timeout--) | ||
61 | /* poll until CLKGATE set */ | ||
62 | if (__raw_readl(hwreg) & (1<<30)) | ||
63 | break; | ||
64 | if (timeout <= 0) { | ||
65 | printk(KERN_ERR"%s(%p): timeout when resetting\n", | ||
66 | __func__, hwreg); | ||
67 | return -ETIME; | ||
68 | } | ||
69 | |||
70 | c = __raw_readl(hwreg); | ||
71 | c &= ~(1<<31); /* clear SFTRST */ | ||
72 | __raw_writel(c, hwreg); | ||
73 | for (timeout = 1000000; timeout > 0; timeout--) | ||
74 | /* still in SFTRST state ? */ | ||
75 | if ((__raw_readl(hwreg) & (1<<31)) == 0) | ||
76 | break; | ||
77 | if (timeout <= 0) { | ||
78 | printk(KERN_ERR"%s(%p): timeout when enabling " | ||
79 | "after reset\n", __func__, hwreg); | ||
80 | return -ETIME; | ||
81 | } | ||
82 | |||
83 | c = __raw_readl(hwreg); | ||
84 | c &= ~(1<<30); /* clear CLKGATE */ | ||
85 | __raw_writel(c, hwreg); | ||
86 | } | ||
87 | for (timeout = 1000000; timeout > 0; timeout--) | ||
88 | /* still in SFTRST state ? */ | ||
89 | if ((__raw_readl(hwreg) & (1<<30)) == 0) | ||
90 | break; | ||
91 | |||
92 | if (timeout <= 0) { | ||
93 | printk(KERN_ERR"%s(%p): timeout when unclockgating\n", | ||
94 | __func__, hwreg); | ||
95 | return -ETIME; | ||
96 | } | ||
97 | |||
98 | return 0; | ||
99 | } | ||
100 | |||
101 | int stmp3xxx_reset_block(void __iomem *hwreg, int just_enable) | ||
102 | { | ||
103 | int try = 10; | ||
104 | int r; | ||
105 | |||
106 | while (try--) { | ||
107 | r = __stmp3xxx_reset_block(hwreg, just_enable); | ||
108 | if (!r) | ||
109 | break; | ||
110 | pr_debug("%s: try %d failed\n", __func__, 10 - try); | ||
111 | } | ||
112 | return r; | ||
113 | } | ||
114 | EXPORT_SYMBOL(stmp3xxx_reset_block); | ||
115 | |||
116 | struct platform_device stmp3xxx_dbguart = { | ||
117 | .name = "stmp3xxx-dbguart", | ||
118 | .id = -1, | ||
119 | }; | ||
120 | |||
121 | void __init stmp3xxx_init(void) | ||
122 | { | ||
123 | /* Turn off auto-slow and other tricks */ | ||
124 | HW_CLKCTRL_HBUS_CLR(0x07f00000U); | ||
125 | |||
126 | stmp3xxx_dma_init(); | ||
127 | } | ||
diff --git a/arch/arm/plat-stmp3xxx/dma.c b/arch/arm/plat-stmp3xxx/dma.c new file mode 100644 index 000000000000..cf42de05e568 --- /dev/null +++ b/arch/arm/plat-stmp3xxx/dma.c | |||
@@ -0,0 +1,462 @@ | |||
1 | /* | ||
2 | * DMA helper routines for Freescale STMP37XX/STMP378X | ||
3 | * | ||
4 | * Author: dmitry pervushin <dpervushin@embeddedalley.com> | ||
5 | * | ||
6 | * Copyright 2008 Freescale Semiconductor, Inc. All Rights Reserved. | ||
7 | * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. | ||
8 | */ | ||
9 | |||
10 | /* | ||
11 | * The code contained herein is licensed under the GNU General Public | ||
12 | * License. You may obtain a copy of the GNU General Public License | ||
13 | * Version 2 or later at the following locations: | ||
14 | * | ||
15 | * http://www.opensource.org/licenses/gpl-license.html | ||
16 | * http://www.gnu.org/copyleft/gpl.html | ||
17 | */ | ||
18 | #include <linux/kernel.h> | ||
19 | #include <linux/device.h> | ||
20 | #include <linux/dmapool.h> | ||
21 | #include <linux/sysdev.h> | ||
22 | #include <linux/cpufreq.h> | ||
23 | |||
24 | #include <asm/page.h> | ||
25 | |||
26 | #include <mach/dma.h> | ||
27 | #include <mach/regs-apbx.h> | ||
28 | #include <mach/regs-apbh.h> | ||
29 | |||
30 | static const size_t pool_item_size = sizeof(struct stmp3xxx_dma_command); | ||
31 | static const size_t pool_alignment = 8; | ||
32 | static struct stmp3xxx_dma_user { | ||
33 | void *pool; | ||
34 | int inuse; | ||
35 | const char *name; | ||
36 | } channels[MAX_DMA_CHANNELS]; | ||
37 | |||
38 | static inline int dmach(int ch) | ||
39 | { | ||
40 | return ch % 16; | ||
41 | } | ||
42 | |||
43 | static inline int dmabus(int ch) | ||
44 | { | ||
45 | return ch / 16; | ||
46 | } | ||
47 | |||
48 | #define IS_VALID_CHANNEL(ch) ((ch) >= 0 && (ch) < MAX_DMA_CHANNELS) | ||
49 | #define IS_USED(ch) (channels[ch].inuse) | ||
50 | |||
51 | int stmp3xxx_dma_request(int ch, struct device *dev, const char *name) | ||
52 | { | ||
53 | struct stmp3xxx_dma_user *user; | ||
54 | int err = 0; | ||
55 | |||
56 | user = channels + ch; | ||
57 | if (!IS_VALID_CHANNEL(ch)) { | ||
58 | err = -ENODEV; | ||
59 | goto out; | ||
60 | } | ||
61 | if (IS_USED(ch)) { | ||
62 | err = -EBUSY; | ||
63 | goto out; | ||
64 | } | ||
65 | /* Create a pool to allocate dma commands from */ | ||
66 | user->pool = dma_pool_create(name, dev, pool_item_size, | ||
67 | pool_alignment, PAGE_SIZE); | ||
68 | if (user->pool == NULL) { | ||
69 | err = -ENOMEM; | ||
70 | goto out; | ||
71 | } | ||
72 | user->name = name; | ||
73 | user->inuse++; | ||
74 | out: | ||
75 | return err; | ||
76 | } | ||
77 | EXPORT_SYMBOL(stmp3xxx_dma_request); | ||
78 | |||
79 | int stmp3xxx_dma_release(int ch) | ||
80 | { | ||
81 | struct stmp3xxx_dma_user *user = channels + ch; | ||
82 | int err = 0; | ||
83 | |||
84 | if (!IS_VALID_CHANNEL(ch)) { | ||
85 | err = -ENODEV; | ||
86 | goto out; | ||
87 | } | ||
88 | if (!IS_USED(ch)) { | ||
89 | err = -EBUSY; | ||
90 | goto out; | ||
91 | } | ||
92 | BUG_ON(user->pool == NULL); | ||
93 | dma_pool_destroy(user->pool); | ||
94 | user->inuse--; | ||
95 | out: | ||
96 | return err; | ||
97 | } | ||
98 | EXPORT_SYMBOL(stmp3xxx_dma_release); | ||
99 | |||
100 | int stmp3xxx_dma_read_semaphore(int channel) | ||
101 | { | ||
102 | int sem = -1; | ||
103 | |||
104 | switch (dmabus(channel)) { | ||
105 | case STMP3XXX_BUS_APBH: | ||
106 | sem = | ||
107 | (HW_APBH_CHn_SEMA_RD(dmach(channel)) & | ||
108 | BM_APBH_CHn_SEMA_PHORE) >> BP_APBH_CHn_SEMA_PHORE; | ||
109 | break; | ||
110 | |||
111 | case STMP3XXX_BUS_APBX: | ||
112 | sem = | ||
113 | (HW_APBX_CHn_SEMA_RD(dmach(channel)) & | ||
114 | BM_APBX_CHn_SEMA_PHORE) >> BP_APBX_CHn_SEMA_PHORE; | ||
115 | break; | ||
116 | default: | ||
117 | BUG(); | ||
118 | } | ||
119 | return sem; | ||
120 | } | ||
121 | EXPORT_SYMBOL(stmp3xxx_dma_read_semaphore); | ||
122 | |||
123 | int stmp3xxx_dma_allocate_command(int channel, | ||
124 | struct stmp3xxx_dma_descriptor *descriptor) | ||
125 | { | ||
126 | struct stmp3xxx_dma_user *user = channels + channel; | ||
127 | int err = 0; | ||
128 | |||
129 | if (!IS_VALID_CHANNEL(channel)) { | ||
130 | err = -ENODEV; | ||
131 | goto out; | ||
132 | } | ||
133 | if (!IS_USED(channel)) { | ||
134 | err = -EBUSY; | ||
135 | goto out; | ||
136 | } | ||
137 | if (descriptor == NULL) { | ||
138 | err = -EINVAL; | ||
139 | goto out; | ||
140 | } | ||
141 | |||
142 | /* Allocate memory for a command from the buffer */ | ||
143 | descriptor->command = | ||
144 | dma_pool_alloc(user->pool, GFP_KERNEL, &descriptor->handle); | ||
145 | |||
146 | /* Check it worked */ | ||
147 | if (!descriptor->command) { | ||
148 | err = -ENOMEM; | ||
149 | goto out; | ||
150 | } | ||
151 | |||
152 | memset(descriptor->command, 0, pool_item_size); | ||
153 | out: | ||
154 | WARN_ON(err); | ||
155 | return err; | ||
156 | } | ||
157 | EXPORT_SYMBOL(stmp3xxx_dma_allocate_command); | ||
158 | |||
159 | int stmp3xxx_dma_free_command(int channel, | ||
160 | struct stmp3xxx_dma_descriptor *descriptor) | ||
161 | { | ||
162 | int err = 0; | ||
163 | |||
164 | if (!IS_VALID_CHANNEL(channel)) { | ||
165 | err = -ENODEV; | ||
166 | goto out; | ||
167 | } | ||
168 | if (!IS_USED(channel)) { | ||
169 | err = -EBUSY; | ||
170 | goto out; | ||
171 | } | ||
172 | |||
173 | /* Return the command memory to the pool */ | ||
174 | dma_pool_free(channels[channel].pool, descriptor->command, | ||
175 | descriptor->handle); | ||
176 | |||
177 | /* Initialise descriptor so we're not tempted to use it */ | ||
178 | descriptor->command = NULL; | ||
179 | descriptor->handle = 0; | ||
180 | descriptor->virtual_buf_ptr = NULL; | ||
181 | descriptor->next_descr = NULL; | ||
182 | |||
183 | WARN_ON(err); | ||
184 | out: | ||
185 | return err; | ||
186 | } | ||
187 | EXPORT_SYMBOL(stmp3xxx_dma_free_command); | ||
188 | |||
189 | void stmp3xxx_dma_go(int channel, | ||
190 | struct stmp3xxx_dma_descriptor *head, u32 semaphore) | ||
191 | { | ||
192 | int ch = dmach(channel); | ||
193 | |||
194 | switch (dmabus(channel)) { | ||
195 | case STMP3XXX_BUS_APBH: | ||
196 | /* Set next command */ | ||
197 | HW_APBH_CHn_NXTCMDAR_WR(ch, head->handle); | ||
198 | /* Set counting semaphore (kicks off transfer). Assumes | ||
199 | peripheral has been set up correctly */ | ||
200 | HW_APBH_CHn_SEMA_WR(ch, semaphore); | ||
201 | break; | ||
202 | |||
203 | case STMP3XXX_BUS_APBX: | ||
204 | /* Set next command */ | ||
205 | HW_APBX_CHn_NXTCMDAR_WR(ch, head->handle); | ||
206 | /* Set counting semaphore (kicks off transfer). Assumes | ||
207 | peripheral has been set up correctly */ | ||
208 | HW_APBX_CHn_SEMA_WR(ch, semaphore); | ||
209 | break; | ||
210 | } | ||
211 | } | ||
212 | EXPORT_SYMBOL(stmp3xxx_dma_go); | ||
213 | |||
214 | int stmp3xxx_dma_running(int channel) | ||
215 | { | ||
216 | switch (dmabus(channel)) { | ||
217 | case STMP3XXX_BUS_APBH: | ||
218 | return HW_APBH_CHn_SEMA_RD(dmach(channel)) & | ||
219 | BM_APBH_CHn_SEMA_PHORE; | ||
220 | |||
221 | case STMP3XXX_BUS_APBX: | ||
222 | return HW_APBX_CHn_SEMA_RD(dmach(channel)) & | ||
223 | BM_APBX_CHn_SEMA_PHORE; | ||
224 | |||
225 | default: | ||
226 | BUG(); | ||
227 | return 0; | ||
228 | } | ||
229 | } | ||
230 | EXPORT_SYMBOL(stmp3xxx_dma_running); | ||
231 | |||
232 | /* | ||
233 | * Circular dma chain management | ||
234 | */ | ||
235 | void stmp3xxx_dma_free_chain(struct stmp37xx_circ_dma_chain *chain) | ||
236 | { | ||
237 | int i; | ||
238 | |||
239 | for (i = 0; i < chain->total_count; i++) | ||
240 | stmp3xxx_dma_free_command( | ||
241 | STMP3xxx_DMA(chain->channel, chain->bus), | ||
242 | &chain->chain[i]); | ||
243 | } | ||
244 | EXPORT_SYMBOL(stmp3xxx_dma_free_chain); | ||
245 | |||
246 | int stmp3xxx_dma_make_chain(int ch, struct stmp37xx_circ_dma_chain *chain, | ||
247 | struct stmp3xxx_dma_descriptor descriptors[], | ||
248 | unsigned items) | ||
249 | { | ||
250 | int i; | ||
251 | int err = 0; | ||
252 | |||
253 | if (items == 0) | ||
254 | return err; | ||
255 | |||
256 | for (i = 0; i < items; i++) { | ||
257 | err = stmp3xxx_dma_allocate_command(ch, &descriptors[i]); | ||
258 | if (err) { | ||
259 | WARN_ON(err); | ||
260 | /* | ||
261 | * Couldn't allocate the whole chain. | ||
262 | * deallocate what has been allocated | ||
263 | */ | ||
264 | if (i) { | ||
265 | do { | ||
266 | stmp3xxx_dma_free_command(ch, | ||
267 | &descriptors | ||
268 | [i]); | ||
269 | } while (i-- >= 0); | ||
270 | } | ||
271 | return err; | ||
272 | } | ||
273 | |||
274 | /* link them! */ | ||
275 | if (i > 0) { | ||
276 | descriptors[i - 1].next_descr = &descriptors[i]; | ||
277 | descriptors[i - 1].command->next = | ||
278 | descriptors[i].handle; | ||
279 | } | ||
280 | } | ||
281 | |||
282 | /* make list circular */ | ||
283 | descriptors[items - 1].next_descr = &descriptors[0]; | ||
284 | descriptors[items - 1].command->next = descriptors[0].handle; | ||
285 | |||
286 | chain->total_count = items; | ||
287 | chain->chain = descriptors; | ||
288 | chain->free_index = 0; | ||
289 | chain->active_index = 0; | ||
290 | chain->cooked_index = 0; | ||
291 | chain->free_count = items; | ||
292 | chain->active_count = 0; | ||
293 | chain->cooked_count = 0; | ||
294 | chain->bus = dmabus(ch); | ||
295 | chain->channel = dmach(ch); | ||
296 | return err; | ||
297 | } | ||
298 | EXPORT_SYMBOL(stmp3xxx_dma_make_chain); | ||
299 | |||
300 | void stmp37xx_circ_clear_chain(struct stmp37xx_circ_dma_chain *chain) | ||
301 | { | ||
302 | BUG_ON(stmp3xxx_dma_running(STMP3xxx_DMA(chain->channel, chain->bus)) > | ||
303 | 0); | ||
304 | chain->free_index = 0; | ||
305 | chain->active_index = 0; | ||
306 | chain->cooked_index = 0; | ||
307 | chain->free_count = chain->total_count; | ||
308 | chain->active_count = 0; | ||
309 | chain->cooked_count = 0; | ||
310 | } | ||
311 | EXPORT_SYMBOL(stmp37xx_circ_clear_chain); | ||
312 | |||
313 | void stmp37xx_circ_advance_free(struct stmp37xx_circ_dma_chain *chain, | ||
314 | unsigned count) | ||
315 | { | ||
316 | BUG_ON(chain->cooked_count < count); | ||
317 | |||
318 | chain->cooked_count -= count; | ||
319 | chain->cooked_index += count; | ||
320 | chain->cooked_index %= chain->total_count; | ||
321 | chain->free_count += count; | ||
322 | } | ||
323 | EXPORT_SYMBOL(stmp37xx_circ_advance_free); | ||
324 | |||
325 | void stmp37xx_circ_advance_active(struct stmp37xx_circ_dma_chain *chain, | ||
326 | unsigned count) | ||
327 | { | ||
328 | BUG_ON(chain->free_count < count); | ||
329 | |||
330 | chain->free_count -= count; | ||
331 | chain->free_index += count; | ||
332 | chain->free_index %= chain->total_count; | ||
333 | chain->active_count += count; | ||
334 | |||
335 | switch (chain->bus) { | ||
336 | case STMP3XXX_BUS_APBH: | ||
337 | /* Set counting semaphore (kicks off transfer). Assumes | ||
338 | peripheral has been set up correctly */ | ||
339 | HW_APBH_CHn_SEMA_CLR(chain->channel, | ||
340 | BM_APBH_CHn_SEMA_INCREMENT_SEMA); | ||
341 | HW_APBH_CHn_SEMA_SET(chain->channel, | ||
342 | BF_APBH_CHn_SEMA_INCREMENT_SEMA(count)); | ||
343 | break; | ||
344 | |||
345 | case STMP3XXX_BUS_APBX: | ||
346 | /* Set counting semaphore (kicks off transfer). Assumes | ||
347 | peripheral has been set up correctly */ | ||
348 | HW_APBX_CHn_SEMA_CLR(chain->channel, | ||
349 | BM_APBX_CHn_SEMA_INCREMENT_SEMA); | ||
350 | HW_APBX_CHn_SEMA_SET(chain->channel, | ||
351 | BF_APBX_CHn_SEMA_INCREMENT_SEMA(count)); | ||
352 | break; | ||
353 | |||
354 | default: | ||
355 | BUG(); | ||
356 | } | ||
357 | } | ||
358 | EXPORT_SYMBOL(stmp37xx_circ_advance_active); | ||
359 | |||
360 | unsigned stmp37xx_circ_advance_cooked(struct stmp37xx_circ_dma_chain *chain) | ||
361 | { | ||
362 | unsigned cooked; | ||
363 | |||
364 | cooked = chain->active_count - | ||
365 | stmp3xxx_dma_read_semaphore(STMP3xxx_DMA(chain->channel, chain->bus)); | ||
366 | |||
367 | chain->active_count -= cooked; | ||
368 | chain->active_index += cooked; | ||
369 | chain->active_index %= chain->total_count; | ||
370 | |||
371 | chain->cooked_count += cooked; | ||
372 | |||
373 | return cooked; | ||
374 | } | ||
375 | EXPORT_SYMBOL(stmp37xx_circ_advance_cooked); | ||
376 | |||
377 | void stmp3xxx_dma_set_alt_target(int channel, int function) | ||
378 | { | ||
379 | #if defined(CONFIG_ARCH_STMP37XX) | ||
380 | unsigned bits = 4; | ||
381 | #elif defined(CONFIG_ARCH_STMP378X) | ||
382 | unsigned bits = 2; | ||
383 | #else | ||
384 | #error wrong arch | ||
385 | #endif | ||
386 | int shift = dmach(channel) * bits; | ||
387 | unsigned mask = (1<<bits) - 1; | ||
388 | |||
389 | BUG_ON(function < 0 || function >= (1<<bits)); | ||
390 | pr_debug("%s: channel = %d, using mask %x, " | ||
391 | "shift = %d\n", __func__, channel, mask, shift); | ||
392 | |||
393 | switch (dmabus(channel)) { | ||
394 | case STMP3XXX_BUS_APBH: | ||
395 | HW_APBH_DEVSEL_CLR(mask<<shift); | ||
396 | HW_APBH_DEVSEL_SET(function<<shift); | ||
397 | break; | ||
398 | case STMP3XXX_BUS_APBX: | ||
399 | HW_APBX_DEVSEL_CLR(mask<<shift); | ||
400 | HW_APBX_DEVSEL_SET(function<<shift); | ||
401 | break; | ||
402 | default: | ||
403 | BUG(); | ||
404 | } | ||
405 | } | ||
406 | EXPORT_SYMBOL(stmp3xxx_dma_set_alt_target); | ||
407 | |||
408 | void stmp3xxx_dma_suspend(void) | ||
409 | { | ||
410 | HW_APBH_CTRL0_SET(BM_APBH_CTRL0_CLKGATE); | ||
411 | HW_APBX_CTRL0_SET(BM_APBX_CTRL0_CLKGATE); | ||
412 | } | ||
413 | |||
414 | void stmp3xxx_dma_resume(void) | ||
415 | { | ||
416 | HW_APBH_CTRL0_CLR(BM_APBH_CTRL0_CLKGATE | BM_APBH_CTRL0_SFTRST); | ||
417 | HW_APBX_CTRL0_CLR(BM_APBX_CTRL0_CLKGATE | BM_APBX_CTRL0_SFTRST); | ||
418 | } | ||
419 | |||
420 | #ifdef CONFIG_CPU_FREQ | ||
421 | |||
422 | struct dma_notifier_block { | ||
423 | struct notifier_block nb; | ||
424 | void *data; | ||
425 | }; | ||
426 | |||
427 | static int dma_cpufreq_notifier(struct notifier_block *self, | ||
428 | unsigned long phase, void *p) | ||
429 | { | ||
430 | switch (phase) { | ||
431 | case CPUFREQ_POSTCHANGE: | ||
432 | stmp3xxx_dma_resume(); | ||
433 | break; | ||
434 | |||
435 | case CPUFREQ_PRECHANGE: | ||
436 | stmp3xxx_dma_suspend(); | ||
437 | break; | ||
438 | |||
439 | default: | ||
440 | break; | ||
441 | } | ||
442 | |||
443 | return NOTIFY_DONE; | ||
444 | } | ||
445 | |||
446 | static struct dma_notifier_block dma_cpufreq_nb = { | ||
447 | .nb = { | ||
448 | .notifier_call = dma_cpufreq_notifier, | ||
449 | }, | ||
450 | }; | ||
451 | #endif /* CONFIG_CPU_FREQ */ | ||
452 | |||
453 | void __init stmp3xxx_dma_init(void) | ||
454 | { | ||
455 | HW_APBH_CTRL0_CLR(BM_APBH_CTRL0_CLKGATE | BM_APBH_CTRL0_SFTRST); | ||
456 | HW_APBX_CTRL0_CLR(BM_APBX_CTRL0_CLKGATE | BM_APBX_CTRL0_SFTRST); | ||
457 | #ifdef CONFIG_CPU_FREQ | ||
458 | cpufreq_register_notifier(&dma_cpufreq_nb.nb, | ||
459 | CPUFREQ_TRANSITION_NOTIFIER); | ||
460 | #endif /* CONFIG_CPU_FREQ */ | ||
461 | |||
462 | } | ||
diff --git a/arch/arm/plat-stmp3xxx/irq.c b/arch/arm/plat-stmp3xxx/irq.c new file mode 100644 index 000000000000..cb3659096681 --- /dev/null +++ b/arch/arm/plat-stmp3xxx/irq.c | |||
@@ -0,0 +1,59 @@ | |||
1 | /* | ||
2 | * Freescale STMP37XX/STMP378X common interrupt handling code | ||
3 | * | ||
4 | * Author: Vladislav Buzov <vbuzov@embeddedalley.com> | ||
5 | * | ||
6 | * Copyright 2008 Freescale Semiconductor, Inc. All Rights Reserved. | ||
7 | * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. | ||
8 | */ | ||
9 | |||
10 | /* | ||
11 | * The code contained herein is licensed under the GNU General Public | ||
12 | * License. You may obtain a copy of the GNU General Public License | ||
13 | * Version 2 or later at the following locations: | ||
14 | * | ||
15 | * http://www.opensource.org/licenses/gpl-license.html | ||
16 | * http://www.gnu.org/copyleft/gpl.html | ||
17 | */ | ||
18 | #include <linux/init.h> | ||
19 | #include <linux/interrupt.h> | ||
20 | #include <linux/delay.h> | ||
21 | #include <linux/irq.h> | ||
22 | #include <linux/sysdev.h> | ||
23 | |||
24 | #include <mach/stmp3xxx.h> | ||
25 | #include <mach/regs-icoll.h> | ||
26 | |||
27 | void __init stmp3xxx_init_irq(struct irq_chip *chip) | ||
28 | { | ||
29 | unsigned int i; | ||
30 | |||
31 | /* Reset the interrupt controller */ | ||
32 | HW_ICOLL_CTRL_CLR(BM_ICOLL_CTRL_CLKGATE); | ||
33 | udelay(10); | ||
34 | HW_ICOLL_CTRL_CLR(BM_ICOLL_CTRL_SFTRST); | ||
35 | udelay(10); | ||
36 | HW_ICOLL_CTRL_SET(BM_ICOLL_CTRL_SFTRST); | ||
37 | while (!(HW_ICOLL_CTRL_RD() & BM_ICOLL_CTRL_CLKGATE)) | ||
38 | continue; | ||
39 | HW_ICOLL_CTRL_CLR(BM_ICOLL_CTRL_SFTRST | BM_ICOLL_CTRL_CLKGATE); | ||
40 | |||
41 | /* Disable all interrupts initially */ | ||
42 | for (i = 0; i < NR_REAL_IRQS; i++) { | ||
43 | chip->mask(i); | ||
44 | set_irq_chip(i, chip); | ||
45 | set_irq_handler(i, handle_level_irq); | ||
46 | set_irq_flags(i, IRQF_VALID | IRQF_PROBE); | ||
47 | } | ||
48 | |||
49 | /* Ensure vector is cleared */ | ||
50 | HW_ICOLL_LEVELACK_WR(1); | ||
51 | HW_ICOLL_LEVELACK_WR(2); | ||
52 | HW_ICOLL_LEVELACK_WR(4); | ||
53 | HW_ICOLL_LEVELACK_WR(8); | ||
54 | |||
55 | HW_ICOLL_VECTOR_WR(0); | ||
56 | /* Barrier */ | ||
57 | (void) HW_ICOLL_STAT_RD(); | ||
58 | } | ||
59 | |||
diff --git a/arch/arm/plat-stmp3xxx/pinmux.c b/arch/arm/plat-stmp3xxx/pinmux.c new file mode 100644 index 000000000000..9b28cc83f31c --- /dev/null +++ b/arch/arm/plat-stmp3xxx/pinmux.c | |||
@@ -0,0 +1,545 @@ | |||
1 | /* | ||
2 | * Freescale STMP378X/STMP378X Pin Multiplexing | ||
3 | * | ||
4 | * Author: Vladislav Buzov <vbuzov@embeddedalley.com> | ||
5 | * | ||
6 | * Copyright 2008 Freescale Semiconductor, Inc. All Rights Reserved. | ||
7 | * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. | ||
8 | */ | ||
9 | |||
10 | /* | ||
11 | * The code contained herein is licensed under the GNU General Public | ||
12 | * License. You may obtain a copy of the GNU General Public License | ||
13 | * Version 2 or later at the following locations: | ||
14 | * | ||
15 | * http://www.opensource.org/licenses/gpl-license.html | ||
16 | * http://www.gnu.org/copyleft/gpl.html | ||
17 | */ | ||
18 | #include <linux/module.h> | ||
19 | #include <linux/kernel.h> | ||
20 | #include <linux/errno.h> | ||
21 | #include <linux/sysdev.h> | ||
22 | #include <linux/string.h> | ||
23 | #include <linux/bitops.h> | ||
24 | #include <linux/sysdev.h> | ||
25 | #include <linux/irq.h> | ||
26 | |||
27 | #include <mach/hardware.h> | ||
28 | #include <mach/regs-pinctrl.h> | ||
29 | #include <mach/pins.h> | ||
30 | #include <mach/pinmux.h> | ||
31 | |||
32 | #define NR_BANKS ARRAY_SIZE(pinmux_banks) | ||
33 | static struct stmp3xxx_pinmux_bank pinmux_banks[] = { | ||
34 | [0] = { | ||
35 | .hw_muxsel = { | ||
36 | HW_PINCTRL_MUXSEL0_ADDR, | ||
37 | HW_PINCTRL_MUXSEL1_ADDR | ||
38 | }, | ||
39 | .hw_drive = { | ||
40 | HW_PINCTRL_DRIVE0_ADDR, | ||
41 | HW_PINCTRL_DRIVE1_ADDR, | ||
42 | HW_PINCTRL_DRIVE2_ADDR, | ||
43 | HW_PINCTRL_DRIVE3_ADDR | ||
44 | }, | ||
45 | .hw_pull = HW_PINCTRL_PULL0_ADDR, | ||
46 | .functions = { 0x0, 0x1, 0x2, 0x3 }, | ||
47 | .strengths = { 0x0, 0x1, 0x2, 0x3, 0xff }, | ||
48 | |||
49 | .hw_gpio_read = HW_PINCTRL_DIN0_ADDR, | ||
50 | .hw_gpio_set = HW_PINCTRL_DOUT0_ADDR + HW_STMP3xxx_SET, | ||
51 | .hw_gpio_clr = HW_PINCTRL_DOUT0_ADDR + HW_STMP3xxx_CLR, | ||
52 | .hw_gpio_doe = HW_PINCTRL_DOE0_ADDR, | ||
53 | .irq = IRQ_GPIO0, | ||
54 | |||
55 | .pin2irq = HW_PINCTRL_PIN2IRQ0_ADDR, | ||
56 | .irqstat = HW_PINCTRL_IRQSTAT0_ADDR, | ||
57 | .irqlevel = HW_PINCTRL_IRQLEVEL0_ADDR, | ||
58 | .irqpolarity = HW_PINCTRL_IRQPOL0_ADDR, | ||
59 | .irqen = HW_PINCTRL_IRQEN0_ADDR, | ||
60 | }, | ||
61 | [1] = { | ||
62 | .hw_muxsel = { | ||
63 | HW_PINCTRL_MUXSEL2_ADDR, | ||
64 | HW_PINCTRL_MUXSEL3_ADDR | ||
65 | }, | ||
66 | .hw_drive = { | ||
67 | HW_PINCTRL_DRIVE4_ADDR, | ||
68 | HW_PINCTRL_DRIVE5_ADDR, | ||
69 | HW_PINCTRL_DRIVE6_ADDR, | ||
70 | HW_PINCTRL_DRIVE7_ADDR | ||
71 | }, | ||
72 | .hw_pull = HW_PINCTRL_PULL1_ADDR, | ||
73 | .functions = { 0x0, 0x1, 0x2, 0x3 }, | ||
74 | .strengths = { 0x0, 0x1, 0x2, 0x3, 0xff }, | ||
75 | |||
76 | .hw_gpio_read = HW_PINCTRL_DIN1_ADDR, | ||
77 | .hw_gpio_set = HW_PINCTRL_DOUT1_ADDR + HW_STMP3xxx_SET, | ||
78 | .hw_gpio_clr = HW_PINCTRL_DOUT1_ADDR + HW_STMP3xxx_CLR, | ||
79 | .hw_gpio_doe = HW_PINCTRL_DOE1_ADDR, | ||
80 | .irq = IRQ_GPIO1, | ||
81 | |||
82 | .pin2irq = HW_PINCTRL_PIN2IRQ1_ADDR, | ||
83 | .irqstat = HW_PINCTRL_IRQSTAT1_ADDR, | ||
84 | .irqlevel = HW_PINCTRL_IRQLEVEL1_ADDR, | ||
85 | .irqpolarity = HW_PINCTRL_IRQPOL1_ADDR, | ||
86 | .irqen = HW_PINCTRL_IRQEN1_ADDR, | ||
87 | }, | ||
88 | [2] = { | ||
89 | .hw_muxsel = { | ||
90 | HW_PINCTRL_MUXSEL4_ADDR, | ||
91 | HW_PINCTRL_MUXSEL5_ADDR, | ||
92 | }, | ||
93 | .hw_drive = { | ||
94 | HW_PINCTRL_DRIVE8_ADDR, | ||
95 | HW_PINCTRL_DRIVE9_ADDR, | ||
96 | HW_PINCTRL_DRIVE10_ADDR, | ||
97 | HW_PINCTRL_DRIVE11_ADDR, | ||
98 | }, | ||
99 | .hw_pull = HW_PINCTRL_PULL2_ADDR, | ||
100 | .functions = { 0x0, 0x1, 0x2, 0x3 }, | ||
101 | .strengths = { 0x0, 0x1, 0x2, 0x1, 0x2 }, | ||
102 | |||
103 | .hw_gpio_read = HW_PINCTRL_DIN2_ADDR, | ||
104 | .hw_gpio_set = HW_PINCTRL_DOUT2_ADDR + HW_STMP3xxx_SET, | ||
105 | .hw_gpio_clr = HW_PINCTRL_DOUT2_ADDR + HW_STMP3xxx_CLR, | ||
106 | .hw_gpio_doe = HW_PINCTRL_DOE2_ADDR, | ||
107 | .irq = IRQ_GPIO2, | ||
108 | |||
109 | .pin2irq = HW_PINCTRL_PIN2IRQ2_ADDR, | ||
110 | .irqstat = HW_PINCTRL_IRQSTAT2_ADDR, | ||
111 | .irqlevel = HW_PINCTRL_IRQLEVEL2_ADDR, | ||
112 | .irqpolarity = HW_PINCTRL_IRQPOL2_ADDR, | ||
113 | .irqen = HW_PINCTRL_IRQEN2_ADDR, | ||
114 | }, | ||
115 | [3] = { | ||
116 | .hw_muxsel = { | ||
117 | HW_PINCTRL_MUXSEL6_ADDR, | ||
118 | HW_PINCTRL_MUXSEL7_ADDR, | ||
119 | }, | ||
120 | .hw_drive = { | ||
121 | HW_PINCTRL_DRIVE12_ADDR, | ||
122 | HW_PINCTRL_DRIVE13_ADDR, | ||
123 | HW_PINCTRL_DRIVE14_ADDR, | ||
124 | NULL, | ||
125 | }, | ||
126 | .hw_pull = HW_PINCTRL_PULL3_ADDR, | ||
127 | .functions = {0x0, 0x1, 0x2, 0x3}, | ||
128 | .strengths = {0x0, 0x1, 0x2, 0x3, 0xff}, | ||
129 | }, | ||
130 | }; | ||
131 | |||
132 | static inline struct stmp3xxx_pinmux_bank * | ||
133 | stmp3xxx_pinmux_bank(unsigned id, unsigned *bank, unsigned *pin) | ||
134 | { | ||
135 | unsigned b, p; | ||
136 | |||
137 | b = STMP3XXX_PINID_TO_BANK(id); | ||
138 | p = STMP3XXX_PINID_TO_PINNUM(id); | ||
139 | BUG_ON(b >= NR_BANKS); | ||
140 | if (bank) | ||
141 | *bank = b; | ||
142 | if (pin) | ||
143 | *pin = p; | ||
144 | return &pinmux_banks[b]; | ||
145 | } | ||
146 | |||
147 | /* Check if requested pin is owned by caller */ | ||
148 | static int stmp3xxx_check_pin(unsigned id, const char *label) | ||
149 | { | ||
150 | unsigned pin; | ||
151 | struct stmp3xxx_pinmux_bank *pm = stmp3xxx_pinmux_bank(id, NULL, &pin); | ||
152 | |||
153 | if (!test_bit(pin, &pm->pin_map)) { | ||
154 | printk(KERN_WARNING | ||
155 | "%s: Accessing free pin %x, caller %s\n", | ||
156 | __func__, id, label); | ||
157 | |||
158 | return -EINVAL; | ||
159 | } | ||
160 | |||
161 | if (label && pm->pin_labels[pin] && | ||
162 | strcmp(label, pm->pin_labels[pin])) { | ||
163 | printk(KERN_WARNING | ||
164 | "%s: Wrong pin owner %x, caller %s owner %s\n", | ||
165 | __func__, id, label, pm->pin_labels[pin]); | ||
166 | |||
167 | return -EINVAL; | ||
168 | } | ||
169 | return 0; | ||
170 | } | ||
171 | |||
172 | void stmp3xxx_pin_strength(unsigned id, enum pin_strength strength, | ||
173 | const char *label) | ||
174 | { | ||
175 | struct stmp3xxx_pinmux_bank *pbank; | ||
176 | void __iomem *hwdrive; | ||
177 | u32 shift, val; | ||
178 | u32 bank, pin; | ||
179 | |||
180 | pbank = stmp3xxx_pinmux_bank(id, &bank, &pin); | ||
181 | pr_debug("%s: label %s bank %d pin %d strength %d\n", __func__, label, | ||
182 | bank, pin, strength); | ||
183 | |||
184 | hwdrive = pbank->hw_drive[pin / HW_DRIVE_PIN_NUM]; | ||
185 | shift = (pin % HW_DRIVE_PIN_NUM) * HW_DRIVE_PIN_LEN; | ||
186 | val = pbank->strengths[strength]; | ||
187 | if (val == 0xff) { | ||
188 | printk(KERN_WARNING | ||
189 | "%s: strength is not supported for bank %d, caller %s", | ||
190 | __func__, bank, label); | ||
191 | return; | ||
192 | } | ||
193 | |||
194 | if (stmp3xxx_check_pin(id, label)) | ||
195 | return; | ||
196 | |||
197 | pr_debug("%s: writing 0x%x to 0x%p register\n", __func__, | ||
198 | val << shift, hwdrive); | ||
199 | __raw_writel(HW_DRIVE_PINDRV_MASK << shift, hwdrive + HW_STMP3xxx_CLR); | ||
200 | __raw_writel(val << shift, hwdrive + HW_STMP3xxx_SET); | ||
201 | } | ||
202 | |||
203 | void stmp3xxx_pin_voltage(unsigned id, enum pin_voltage voltage, | ||
204 | const char *label) | ||
205 | { | ||
206 | struct stmp3xxx_pinmux_bank *pbank; | ||
207 | void __iomem *hwdrive; | ||
208 | u32 shift; | ||
209 | u32 bank, pin; | ||
210 | |||
211 | pbank = stmp3xxx_pinmux_bank(id, &bank, &pin); | ||
212 | pr_debug("%s: label %s bank %d pin %d voltage %d\n", __func__, label, | ||
213 | bank, pin, voltage); | ||
214 | |||
215 | hwdrive = pbank->hw_drive[pin / HW_DRIVE_PIN_NUM]; | ||
216 | shift = (pin % HW_DRIVE_PIN_NUM) * HW_DRIVE_PIN_LEN; | ||
217 | |||
218 | if (stmp3xxx_check_pin(id, label)) | ||
219 | return; | ||
220 | |||
221 | pr_debug("%s: changing 0x%x bit in 0x%p register\n", | ||
222 | __func__, HW_DRIVE_PINV_MASK << shift, hwdrive); | ||
223 | if (voltage == PIN_1_8V) | ||
224 | __raw_writel(HW_DRIVE_PINV_MASK << shift, | ||
225 | hwdrive + HW_STMP3xxx_CLR); | ||
226 | else | ||
227 | __raw_writel(HW_DRIVE_PINV_MASK << shift, | ||
228 | hwdrive + HW_STMP3xxx_SET); | ||
229 | } | ||
230 | |||
231 | void stmp3xxx_pin_pullup(unsigned id, int enable, const char *label) | ||
232 | { | ||
233 | struct stmp3xxx_pinmux_bank *pbank; | ||
234 | void __iomem *hwpull; | ||
235 | u32 bank, pin; | ||
236 | |||
237 | pbank = stmp3xxx_pinmux_bank(id, &bank, &pin); | ||
238 | pr_debug("%s: label %s bank %d pin %d enable %d\n", __func__, label, | ||
239 | bank, pin, enable); | ||
240 | |||
241 | hwpull = pbank->hw_pull; | ||
242 | |||
243 | if (stmp3xxx_check_pin(id, label)) | ||
244 | return; | ||
245 | |||
246 | pr_debug("%s: changing 0x%x bit in 0x%p register\n", | ||
247 | __func__, 1 << pin, hwpull); | ||
248 | __raw_writel(1 << pin, | ||
249 | hwpull + (enable ? HW_STMP3xxx_SET : HW_STMP3xxx_CLR)); | ||
250 | } | ||
251 | |||
252 | int stmp3xxx_request_pin(unsigned id, enum pin_fun fun, const char *label) | ||
253 | { | ||
254 | struct stmp3xxx_pinmux_bank *pbank; | ||
255 | u32 bank, pin; | ||
256 | int ret = 0; | ||
257 | |||
258 | pbank = stmp3xxx_pinmux_bank(id, &bank, &pin); | ||
259 | pr_debug("%s: label %s bank %d pin %d fun %d\n", __func__, label, | ||
260 | bank, pin, fun); | ||
261 | |||
262 | if (test_bit(pin, &pbank->pin_map)) { | ||
263 | printk(KERN_WARNING | ||
264 | "%s: CONFLICT DETECTED pin %d:%d caller %s owner %s\n", | ||
265 | __func__, bank, pin, label, pbank->pin_labels[pin]); | ||
266 | return -EBUSY; | ||
267 | } | ||
268 | |||
269 | set_bit(pin, &pbank->pin_map); | ||
270 | pbank->pin_labels[pin] = label; | ||
271 | |||
272 | stmp3xxx_set_pin_type(id, fun); | ||
273 | |||
274 | return ret; | ||
275 | } | ||
276 | |||
277 | void stmp3xxx_set_pin_type(unsigned id, enum pin_fun fun) | ||
278 | { | ||
279 | struct stmp3xxx_pinmux_bank *pbank; | ||
280 | void __iomem *hwmux; | ||
281 | u32 shift, val; | ||
282 | u32 bank, pin; | ||
283 | |||
284 | pbank = stmp3xxx_pinmux_bank(id, &bank, &pin); | ||
285 | |||
286 | hwmux = pbank->hw_muxsel[pin / HW_MUXSEL_PIN_NUM]; | ||
287 | shift = (pin % HW_MUXSEL_PIN_NUM) * HW_MUXSEL_PIN_LEN; | ||
288 | |||
289 | val = pbank->functions[fun]; | ||
290 | shift = (pin % HW_MUXSEL_PIN_NUM) * HW_MUXSEL_PIN_LEN; | ||
291 | pr_debug("%s: writing 0x%x to 0x%p register\n", | ||
292 | __func__, val << shift, hwmux); | ||
293 | __raw_writel(HW_MUXSEL_PINFUN_MASK << shift, hwmux + HW_STMP3xxx_CLR); | ||
294 | __raw_writel(val << shift, hwmux + HW_STMP3xxx_SET); | ||
295 | } | ||
296 | |||
297 | void stmp3xxx_release_pin(unsigned id, const char *label) | ||
298 | { | ||
299 | struct stmp3xxx_pinmux_bank *pbank; | ||
300 | u32 bank, pin; | ||
301 | |||
302 | pbank = stmp3xxx_pinmux_bank(id, &bank, &pin); | ||
303 | pr_debug("%s: label %s bank %d pin %d\n", __func__, label, bank, pin); | ||
304 | |||
305 | if (stmp3xxx_check_pin(id, label)) | ||
306 | return; | ||
307 | |||
308 | clear_bit(pin, &pbank->pin_map); | ||
309 | pbank->pin_labels[pin] = NULL; | ||
310 | } | ||
311 | |||
312 | int stmp3xxx_request_pin_group(struct pin_group *pin_group, const char *label) | ||
313 | { | ||
314 | struct pin_desc *pin; | ||
315 | int p; | ||
316 | int err = 0; | ||
317 | |||
318 | /* Allocate and configure pins */ | ||
319 | for (p = 0; p < pin_group->nr_pins; p++) { | ||
320 | pr_debug("%s: #%d\n", __func__, p); | ||
321 | pin = &pin_group->pins[p]; | ||
322 | |||
323 | err = stmp3xxx_request_pin(pin->id, pin->fun, label); | ||
324 | if (err) | ||
325 | goto out_err; | ||
326 | |||
327 | stmp3xxx_pin_strength(pin->id, pin->strength, label); | ||
328 | stmp3xxx_pin_voltage(pin->id, pin->voltage, label); | ||
329 | stmp3xxx_pin_pullup(pin->id, pin->pullup, label); | ||
330 | } | ||
331 | |||
332 | return 0; | ||
333 | |||
334 | out_err: | ||
335 | /* Release allocated pins in case of error */ | ||
336 | while (--p >= 0) { | ||
337 | pr_debug("%s: releasing #%d\n", __func__, p); | ||
338 | stmp3xxx_release_pin(pin_group->pins[p].id, label); | ||
339 | } | ||
340 | return err; | ||
341 | } | ||
342 | EXPORT_SYMBOL(stmp3xxx_request_pin_group); | ||
343 | |||
344 | void stmp3xxx_release_pin_group(struct pin_group *pin_group, const char *label) | ||
345 | { | ||
346 | struct pin_desc *pin; | ||
347 | int p; | ||
348 | |||
349 | for (p = 0; p < pin_group->nr_pins; p++) { | ||
350 | pin = &pin_group->pins[p]; | ||
351 | stmp3xxx_release_pin(pin->id, label); | ||
352 | } | ||
353 | } | ||
354 | EXPORT_SYMBOL(stmp3xxx_release_pin_group); | ||
355 | |||
356 | static int stmp3xxx_irq_to_gpio(int irq, | ||
357 | struct stmp3xxx_pinmux_bank **bank, unsigned *gpio) | ||
358 | { | ||
359 | struct stmp3xxx_pinmux_bank *pm; | ||
360 | |||
361 | for (pm = pinmux_banks; pm < pinmux_banks + NR_BANKS; pm++) | ||
362 | if (pm->virq <= irq && irq < pm->virq + 32) { | ||
363 | *bank = pm; | ||
364 | *gpio = irq - pm->virq; | ||
365 | return 0; | ||
366 | } | ||
367 | return -ENOENT; | ||
368 | } | ||
369 | |||
370 | static int stmp3xxx_set_irqtype(unsigned irq, unsigned type) | ||
371 | { | ||
372 | struct stmp3xxx_pinmux_bank *pm; | ||
373 | unsigned gpio; | ||
374 | int l, p; | ||
375 | |||
376 | stmp3xxx_irq_to_gpio(irq, &pm, &gpio); | ||
377 | switch (type) { | ||
378 | case IRQ_TYPE_EDGE_RISING: | ||
379 | l = 0; p = 1; break; | ||
380 | case IRQ_TYPE_EDGE_FALLING: | ||
381 | l = 0; p = 0; break; | ||
382 | case IRQ_TYPE_LEVEL_HIGH: | ||
383 | l = 1; p = 1; break; | ||
384 | case IRQ_TYPE_LEVEL_LOW: | ||
385 | l = 1; p = 0; break; | ||
386 | default: | ||
387 | pr_debug("%s: Incorrect GPIO interrupt type 0x%x\n", | ||
388 | __func__, type); | ||
389 | return -ENXIO; | ||
390 | } | ||
391 | __raw_writel(1 << gpio, | ||
392 | pm->irqlevel + (l ? HW_STMP3xxx_SET : HW_STMP3xxx_CLR)); | ||
393 | __raw_writel(1 << gpio, | ||
394 | pm->irqpolarity + (p ? HW_STMP3xxx_SET : HW_STMP3xxx_CLR)); | ||
395 | return 0; | ||
396 | } | ||
397 | |||
398 | static void stmp3xxx_pin_ack_irq(unsigned irq) | ||
399 | { | ||
400 | u32 stat; | ||
401 | struct stmp3xxx_pinmux_bank *pm; | ||
402 | unsigned gpio; | ||
403 | |||
404 | stmp3xxx_irq_to_gpio(irq, &pm, &gpio); | ||
405 | stat = __raw_readl(pm->irqstat) & (1<<gpio); | ||
406 | __raw_writel(stat, pm->irqstat + HW_STMP3xxx_CLR); | ||
407 | } | ||
408 | |||
409 | static void stmp3xxx_pin_mask_irq(unsigned irq) | ||
410 | { | ||
411 | struct stmp3xxx_pinmux_bank *pm; | ||
412 | unsigned gpio; | ||
413 | |||
414 | stmp3xxx_irq_to_gpio(irq, &pm, &gpio); | ||
415 | __raw_writel(1 << gpio, pm->irqen + HW_STMP3xxx_CLR); | ||
416 | __raw_writel(1 << gpio, pm->pin2irq + HW_STMP3xxx_CLR); | ||
417 | } | ||
418 | |||
419 | static void stmp3xxx_pin_unmask_irq(unsigned irq) | ||
420 | { | ||
421 | struct stmp3xxx_pinmux_bank *pm; | ||
422 | unsigned gpio; | ||
423 | |||
424 | stmp3xxx_irq_to_gpio(irq, &pm, &gpio); | ||
425 | __raw_writel(1 << gpio, pm->irqen + HW_STMP3xxx_SET); | ||
426 | __raw_writel(1 << gpio, pm->pin2irq + HW_STMP3xxx_SET); | ||
427 | } | ||
428 | |||
429 | static inline | ||
430 | struct stmp3xxx_pinmux_bank *to_pinmux_bank(struct gpio_chip *chip) | ||
431 | { | ||
432 | return container_of(chip, struct stmp3xxx_pinmux_bank, chip); | ||
433 | } | ||
434 | |||
435 | static int stmp3xxx_gpio_to_irq(struct gpio_chip *chip, unsigned offset) | ||
436 | { | ||
437 | struct stmp3xxx_pinmux_bank *pm = to_pinmux_bank(chip); | ||
438 | return pm->virq + offset; | ||
439 | } | ||
440 | |||
441 | static int stmp3xxx_gpio_get(struct gpio_chip *chip, unsigned offset) | ||
442 | { | ||
443 | struct stmp3xxx_pinmux_bank *pm = to_pinmux_bank(chip); | ||
444 | unsigned v; | ||
445 | |||
446 | v = __raw_readl(pm->hw_gpio_read) & (1 << offset); | ||
447 | return v ? 1 : 0; | ||
448 | } | ||
449 | |||
450 | static void stmp3xxx_gpio_set(struct gpio_chip *chip, unsigned offset, int v) | ||
451 | { | ||
452 | struct stmp3xxx_pinmux_bank *pm = to_pinmux_bank(chip); | ||
453 | |||
454 | __raw_writel(1 << offset, v ? pm->hw_gpio_set : pm->hw_gpio_clr); | ||
455 | } | ||
456 | |||
457 | static int stmp3xxx_gpio_output(struct gpio_chip *chip, unsigned offset, int v) | ||
458 | { | ||
459 | struct stmp3xxx_pinmux_bank *pm = to_pinmux_bank(chip); | ||
460 | |||
461 | __raw_writel(1 << offset, pm->hw_gpio_doe + HW_STMP3xxx_SET); | ||
462 | stmp3xxx_gpio_set(chip, offset, v); | ||
463 | return 0; | ||
464 | } | ||
465 | |||
466 | static int stmp3xxx_gpio_input(struct gpio_chip *chip, unsigned offset) | ||
467 | { | ||
468 | struct stmp3xxx_pinmux_bank *pm = to_pinmux_bank(chip); | ||
469 | |||
470 | __raw_writel(1 << offset, pm->hw_gpio_doe + HW_STMP3xxx_CLR); | ||
471 | return 0; | ||
472 | } | ||
473 | |||
474 | static int stmp3xxx_gpio_request(struct gpio_chip *chip, unsigned offset) | ||
475 | { | ||
476 | return stmp3xxx_request_pin(chip->base + offset, PIN_GPIO, "gpio"); | ||
477 | } | ||
478 | |||
479 | static void stmp3xxx_gpio_free(struct gpio_chip *chip, unsigned offset) | ||
480 | { | ||
481 | stmp3xxx_release_pin(chip->base + offset, "gpio"); | ||
482 | } | ||
483 | |||
484 | static void stmp3xxx_gpio_irq(u32 irq, struct irq_desc *desc) | ||
485 | { | ||
486 | struct stmp3xxx_pinmux_bank *pm = get_irq_data(irq); | ||
487 | int gpio_irq = pm->virq; | ||
488 | u32 stat = __raw_readl(pm->irqstat); | ||
489 | |||
490 | while (stat) { | ||
491 | if (stat & 1) | ||
492 | irq_desc[gpio_irq].handle_irq(gpio_irq, | ||
493 | &irq_desc[gpio_irq]); | ||
494 | gpio_irq++; | ||
495 | stat >>= 1; | ||
496 | } | ||
497 | } | ||
498 | |||
499 | static struct irq_chip gpio_irq_chip = { | ||
500 | .ack = stmp3xxx_pin_ack_irq, | ||
501 | .mask = stmp3xxx_pin_mask_irq, | ||
502 | .unmask = stmp3xxx_pin_unmask_irq, | ||
503 | .set_type = stmp3xxx_set_irqtype, | ||
504 | }; | ||
505 | |||
506 | int __init stmp3xxx_pinmux_init(int virtual_irq_start) | ||
507 | { | ||
508 | int b, r = 0; | ||
509 | struct stmp3xxx_pinmux_bank *pm; | ||
510 | int virq; | ||
511 | |||
512 | for (b = 0; b < 3; b++) { | ||
513 | /* only banks 0,1,2 are allowed to GPIO */ | ||
514 | pm = pinmux_banks + b; | ||
515 | pm->chip.base = 32 * b; | ||
516 | pm->chip.ngpio = 32; | ||
517 | pm->chip.owner = THIS_MODULE; | ||
518 | pm->chip.can_sleep = 1; | ||
519 | pm->chip.exported = 1; | ||
520 | pm->chip.to_irq = stmp3xxx_gpio_to_irq; | ||
521 | pm->chip.direction_input = stmp3xxx_gpio_input; | ||
522 | pm->chip.direction_output = stmp3xxx_gpio_output; | ||
523 | pm->chip.get = stmp3xxx_gpio_get; | ||
524 | pm->chip.set = stmp3xxx_gpio_set; | ||
525 | pm->chip.request = stmp3xxx_gpio_request; | ||
526 | pm->chip.free = stmp3xxx_gpio_free; | ||
527 | pm->virq = virtual_irq_start + b * 32; | ||
528 | |||
529 | for (virq = pm->virq; virq < pm->virq; virq++) { | ||
530 | gpio_irq_chip.mask(virq); | ||
531 | set_irq_chip(virq, &gpio_irq_chip); | ||
532 | set_irq_handler(virq, handle_level_irq); | ||
533 | set_irq_flags(virq, IRQF_VALID); | ||
534 | } | ||
535 | r = gpiochip_add(&pm->chip); | ||
536 | if (r < 0) | ||
537 | break; | ||
538 | set_irq_chained_handler(pm->irq, stmp3xxx_gpio_irq); | ||
539 | set_irq_data(pm->irq, pm); | ||
540 | } | ||
541 | return r; | ||
542 | } | ||
543 | |||
544 | MODULE_AUTHOR("Vladislav Buzov"); | ||
545 | MODULE_LICENSE("GPL"); | ||
diff --git a/arch/arm/plat-stmp3xxx/timer.c b/arch/arm/plat-stmp3xxx/timer.c new file mode 100644 index 000000000000..c916068f0cab --- /dev/null +++ b/arch/arm/plat-stmp3xxx/timer.c | |||
@@ -0,0 +1,172 @@ | |||
1 | /* | ||
2 | * System timer for Freescale STMP37XX/STMP378X | ||
3 | * | ||
4 | * Embedded Alley Solutions, Inc <source@embeddedalley.com> | ||
5 | * | ||
6 | * Copyright 2008 Freescale Semiconductor, Inc. All Rights Reserved. | ||
7 | * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. | ||
8 | */ | ||
9 | |||
10 | /* | ||
11 | * The code contained herein is licensed under the GNU General Public | ||
12 | * License. You may obtain a copy of the GNU General Public License | ||
13 | * Version 2 or later at the following locations: | ||
14 | * | ||
15 | * http://www.opensource.org/licenses/gpl-license.html | ||
16 | * http://www.gnu.org/copyleft/gpl.html | ||
17 | */ | ||
18 | #include <linux/kernel.h> | ||
19 | #include <linux/init.h> | ||
20 | #include <linux/spinlock.h> | ||
21 | #include <linux/clocksource.h> | ||
22 | #include <linux/clockchips.h> | ||
23 | #include <linux/io.h> | ||
24 | #include <linux/irq.h> | ||
25 | #include <linux/interrupt.h> | ||
26 | |||
27 | #include <asm/mach/time.h> | ||
28 | #include <mach/stmp3xxx.h> | ||
29 | #include <mach/regs-timrot.h> | ||
30 | |||
31 | static irqreturn_t | ||
32 | stmp3xxx_timer_interrupt(int irq, void *dev_id) | ||
33 | { | ||
34 | struct clock_event_device *c = dev_id; | ||
35 | |||
36 | if (HW_TIMROT_TIMCTRLn_RD(0) & (1<<15)) { | ||
37 | HW_TIMROT_TIMCTRLn_CLR(0, (1<<15)); | ||
38 | c->event_handler(c); | ||
39 | } else if (HW_TIMROT_TIMCTRLn_RD(1) & (1<<15)) { | ||
40 | HW_TIMROT_TIMCTRLn_CLR(1, (1<<15)); | ||
41 | HW_TIMROT_TIMCTRLn_CLR(1, BM_TIMROT_TIMCTRLn_IRQ_EN); | ||
42 | HW_TIMROT_TIMCOUNTn_WR(1, 0xFFFF); | ||
43 | } | ||
44 | |||
45 | return IRQ_HANDLED; | ||
46 | } | ||
47 | |||
48 | static cycle_t stmp3xxx_clock_read(void) | ||
49 | { | ||
50 | return ~((HW_TIMROT_TIMCOUNTn_RD(1) & 0xFFFF0000) >> 16); | ||
51 | } | ||
52 | |||
53 | static int | ||
54 | stmp3xxx_timrot_set_next_event(unsigned long delta, | ||
55 | struct clock_event_device *dev) | ||
56 | { | ||
57 | HW_TIMROT_TIMCOUNTn_WR(0, delta); /* reload */ | ||
58 | return 0; | ||
59 | } | ||
60 | |||
61 | static void | ||
62 | stmp3xxx_timrot_set_mode(enum clock_event_mode mode, | ||
63 | struct clock_event_device *dev) | ||
64 | { | ||
65 | } | ||
66 | |||
67 | static struct clock_event_device ckevt_timrot = { | ||
68 | .name = "timrot", | ||
69 | .features = CLOCK_EVT_FEAT_ONESHOT, | ||
70 | .shift = 32, | ||
71 | .set_next_event = stmp3xxx_timrot_set_next_event, | ||
72 | .set_mode = stmp3xxx_timrot_set_mode, | ||
73 | }; | ||
74 | |||
75 | static struct clocksource cksrc_stmp3xxx = { | ||
76 | .name = "cksrc_stmp3xxx", | ||
77 | .rating = 250, | ||
78 | .read = stmp3xxx_clock_read, | ||
79 | .mask = CLOCKSOURCE_MASK(16), | ||
80 | .shift = 10, | ||
81 | .flags = CLOCK_SOURCE_IS_CONTINUOUS, | ||
82 | }; | ||
83 | |||
84 | static struct irqaction stmp3xxx_timer_irq = { | ||
85 | .name = "stmp3xxx_timer", | ||
86 | .flags = IRQF_DISABLED | IRQF_TIMER, | ||
87 | .handler = stmp3xxx_timer_interrupt, | ||
88 | .dev_id = &ckevt_timrot, | ||
89 | }; | ||
90 | |||
91 | |||
92 | /* | ||
93 | * Set up timer interrupt, and return the current time in seconds. | ||
94 | */ | ||
95 | static void __init stmp3xxx_init_timer(void) | ||
96 | { | ||
97 | cksrc_stmp3xxx.mult = clocksource_hz2mult(CLOCK_TICK_RATE, | ||
98 | cksrc_stmp3xxx.shift); | ||
99 | ckevt_timrot.mult = div_sc(CLOCK_TICK_RATE, NSEC_PER_SEC, | ||
100 | ckevt_timrot.shift); | ||
101 | ckevt_timrot.min_delta_ns = clockevent_delta2ns(2, &ckevt_timrot); | ||
102 | ckevt_timrot.max_delta_ns = clockevent_delta2ns(0xFFF, &ckevt_timrot); | ||
103 | ckevt_timrot.cpumask = cpumask_of(0); | ||
104 | |||
105 | HW_TIMROT_ROTCTRL_CLR(BM_TIMROT_ROTCTRL_SFTRST | | ||
106 | BM_TIMROT_ROTCTRL_CLKGATE); | ||
107 | HW_TIMROT_TIMCOUNTn_WR(0, 0); | ||
108 | HW_TIMROT_TIMCOUNTn_WR(1, 0); | ||
109 | |||
110 | HW_TIMROT_TIMCTRLn_WR(0, | ||
111 | (BF_TIMROT_TIMCTRLn_SELECT(8) | /* 32 kHz */ | ||
112 | BF_TIMROT_TIMCTRLn_PRESCALE(0) | | ||
113 | BM_TIMROT_TIMCTRLn_RELOAD | | ||
114 | BM_TIMROT_TIMCTRLn_UPDATE | | ||
115 | BM_TIMROT_TIMCTRLn_IRQ_EN)); | ||
116 | HW_TIMROT_TIMCTRLn_WR(1, | ||
117 | (BF_TIMROT_TIMCTRLn_SELECT(8) | /* 32 kHz */ | ||
118 | BF_TIMROT_TIMCTRLn_PRESCALE(0) | | ||
119 | BM_TIMROT_TIMCTRLn_RELOAD | | ||
120 | BM_TIMROT_TIMCTRLn_UPDATE)); | ||
121 | |||
122 | HW_TIMROT_TIMCOUNTn_WR(0, CLOCK_TICK_RATE / HZ - 1); | ||
123 | HW_TIMROT_TIMCOUNTn_WR(1, 0xFFFF); /* reload */ | ||
124 | |||
125 | setup_irq(IRQ_TIMER0, &stmp3xxx_timer_irq); | ||
126 | |||
127 | clocksource_register(&cksrc_stmp3xxx); | ||
128 | clockevents_register_device(&ckevt_timrot); | ||
129 | } | ||
130 | |||
131 | #ifdef CONFIG_PM | ||
132 | |||
133 | void stmp3xxx_suspend_timer(void) | ||
134 | { | ||
135 | HW_TIMROT_TIMCTRLn_CLR(0, BM_TIMROT_TIMCTRLn_IRQ_EN); | ||
136 | HW_TIMROT_TIMCTRLn_CLR(0, (1<<15)); | ||
137 | HW_TIMROT_ROTCTRL_SET(BM_TIMROT_ROTCTRL_CLKGATE); | ||
138 | } | ||
139 | |||
140 | void stmp3xxx_resume_timer(void) | ||
141 | { | ||
142 | HW_TIMROT_ROTCTRL_CLR(BM_TIMROT_ROTCTRL_SFTRST | | ||
143 | BM_TIMROT_ROTCTRL_CLKGATE); | ||
144 | |||
145 | |||
146 | HW_TIMROT_TIMCTRLn_WR(0, | ||
147 | (BF_TIMROT_TIMCTRLn_SELECT(8) | /* 32 kHz */ | ||
148 | BF_TIMROT_TIMCTRLn_PRESCALE(0) | | ||
149 | BM_TIMROT_TIMCTRLn_UPDATE | | ||
150 | BM_TIMROT_TIMCTRLn_IRQ_EN)); | ||
151 | HW_TIMROT_TIMCTRLn_WR(1, | ||
152 | (BF_TIMROT_TIMCTRLn_SELECT(8) | /* 32 kHz */ | ||
153 | BF_TIMROT_TIMCTRLn_PRESCALE(0) | | ||
154 | BM_TIMROT_TIMCTRLn_RELOAD | | ||
155 | BM_TIMROT_TIMCTRLn_UPDATE)); | ||
156 | |||
157 | HW_TIMROT_TIMCOUNTn_WR(0, CLOCK_TICK_RATE / HZ - 1); | ||
158 | HW_TIMROT_TIMCOUNTn_WR(1, 0xFFFF); /* reload */ | ||
159 | } | ||
160 | |||
161 | #else | ||
162 | |||
163 | #define stmp3xxx_suspend_timer NULL | ||
164 | #define stmp3xxx_resume_timer NULL | ||
165 | |||
166 | #endif /* CONFIG_PM */ | ||
167 | |||
168 | struct sys_timer stmp3xxx_timer = { | ||
169 | .init = stmp3xxx_init_timer, | ||
170 | .suspend = stmp3xxx_suspend_timer, | ||
171 | .resume = stmp3xxx_resume_timer, | ||
172 | }; | ||