diff options
author | Greg Ungerer <gerg@snapgear.com> | 2006-06-25 20:33:10 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@g5.osdl.org> | 2006-06-25 20:43:33 -0400 |
commit | 3196cf83ad410d9549e78f69aa45db75540334b7 (patch) | |
tree | 55851337a1205e01fba6d36bb544cdd910d282ed /arch/m68knommu/platform | |
parent | b671b653da35cd38897c7b19e9baed64e7a9e1a1 (diff) |
[PATCH] m68knommu: ColdFire 532x CPU startup code
Add kernel startup code for the new Freescale 532x CPU family.
Signed-off-by: Greg Ungerer <gerg@uclinux.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
Diffstat (limited to 'arch/m68knommu/platform')
-rw-r--r-- | arch/m68knommu/platform/532x/config.c | 486 |
1 files changed, 486 insertions, 0 deletions
diff --git a/arch/m68knommu/platform/532x/config.c b/arch/m68knommu/platform/532x/config.c new file mode 100644 index 000000000000..ceef9bc181ea --- /dev/null +++ b/arch/m68knommu/platform/532x/config.c | |||
@@ -0,0 +1,486 @@ | |||
1 | /***************************************************************************/ | ||
2 | |||
3 | /* | ||
4 | * linux/arch/m68knommu/platform/532x/config.c | ||
5 | * | ||
6 | * Copyright (C) 1999-2002, Greg Ungerer (gerg@snapgear.com) | ||
7 | * Copyright (C) 2000, Lineo (www.lineo.com) | ||
8 | * Yaroslav Vinogradov yaroslav.vinogradov@freescale.com | ||
9 | * Copyright Freescale Semiconductor, Inc 2006 | ||
10 | * Copyright (c) 2006, emlix, Sebastian Hess <sh@emlix.com> | ||
11 | * | ||
12 | * This program is free software; you can redistribute it and/or modify | ||
13 | * it under the terms of the GNU General Public License as published by | ||
14 | * the Free Software Foundation; either version 2 of the License, or | ||
15 | * (at your option) any later version. | ||
16 | */ | ||
17 | |||
18 | /***************************************************************************/ | ||
19 | |||
20 | #include <linux/config.h> | ||
21 | #include <linux/kernel.h> | ||
22 | #include <linux/sched.h> | ||
23 | #include <linux/param.h> | ||
24 | #include <linux/init.h> | ||
25 | #include <linux/interrupt.h> | ||
26 | #include <asm/irq.h> | ||
27 | #include <asm/dma.h> | ||
28 | #include <asm/traps.h> | ||
29 | #include <asm/machdep.h> | ||
30 | #include <asm/coldfire.h> | ||
31 | #include <asm/mcftimer.h> | ||
32 | #include <asm/mcfsim.h> | ||
33 | #include <asm/mcfdma.h> | ||
34 | #include <asm/mcfwdebug.h> | ||
35 | |||
36 | /***************************************************************************/ | ||
37 | |||
38 | void coldfire_tick(void); | ||
39 | void coldfire_timer_init(irqreturn_t (*handler)(int, void *, struct pt_regs *)); | ||
40 | unsigned long coldfire_timer_offset(void); | ||
41 | void coldfire_trap_init(void); | ||
42 | void coldfire_reset(void); | ||
43 | |||
44 | extern unsigned int mcf_timervector; | ||
45 | extern unsigned int mcf_profilevector; | ||
46 | extern unsigned int mcf_timerlevel; | ||
47 | |||
48 | /***************************************************************************/ | ||
49 | |||
50 | /* | ||
51 | * DMA channel base address table. | ||
52 | */ | ||
53 | unsigned int dma_base_addr[MAX_M68K_DMA_CHANNELS] = { }; | ||
54 | unsigned int dma_device_address[MAX_M68K_DMA_CHANNELS]; | ||
55 | |||
56 | /***************************************************************************/ | ||
57 | |||
58 | void mcf_settimericr(unsigned int timer, unsigned int level) | ||
59 | { | ||
60 | volatile unsigned char *icrp; | ||
61 | unsigned int icr; | ||
62 | unsigned char irq; | ||
63 | |||
64 | if (timer <= 2) { | ||
65 | switch (timer) { | ||
66 | case 2: irq = 33; icr = MCFSIM_ICR_TIMER2; break; | ||
67 | default: irq = 32; icr = MCFSIM_ICR_TIMER1; break; | ||
68 | } | ||
69 | |||
70 | icrp = (volatile unsigned char *) (MCF_MBAR + icr); | ||
71 | *icrp = level; | ||
72 | mcf_enable_irq0(irq); | ||
73 | } | ||
74 | } | ||
75 | |||
76 | /***************************************************************************/ | ||
77 | |||
78 | int mcf_timerirqpending(int timer) | ||
79 | { | ||
80 | unsigned int imr = 0; | ||
81 | |||
82 | switch (timer) { | ||
83 | case 1: imr = 0x1; break; | ||
84 | case 2: imr = 0x2; break; | ||
85 | default: break; | ||
86 | } | ||
87 | return (mcf_getiprh() & imr); | ||
88 | } | ||
89 | |||
90 | /***************************************************************************/ | ||
91 | |||
92 | void config_BSP(char *commandp, int size) | ||
93 | { | ||
94 | mcf_setimr(MCFSIM_IMR_MASKALL); | ||
95 | |||
96 | #if defined(CONFIG_BOOTPARAM) | ||
97 | strncpy(commandp, CONFIG_BOOTPARAM_STRING, size); | ||
98 | commandp[size-1] = 0; | ||
99 | #else | ||
100 | /* Copy command line from FLASH to local buffer... */ | ||
101 | memcpy(commandp, (char *) 0x4000, 4); | ||
102 | if(strncmp(commandp, "kcl ", 4) == 0){ | ||
103 | memcpy(commandp, (char *) 0x4004, size); | ||
104 | commandp[size-1] = 0; | ||
105 | } else { | ||
106 | memset(commandp, 0, size); | ||
107 | } | ||
108 | #endif | ||
109 | |||
110 | mcf_timervector = 64+32; | ||
111 | mcf_profilevector = 64+33; | ||
112 | mach_sched_init = coldfire_timer_init; | ||
113 | mach_tick = coldfire_tick; | ||
114 | mach_gettimeoffset = coldfire_timer_offset; | ||
115 | mach_trap_init = coldfire_trap_init; | ||
116 | mach_reset = coldfire_reset; | ||
117 | |||
118 | #ifdef MCF_BDM_DISABLE | ||
119 | /* | ||
120 | * Disable the BDM clocking. This also turns off most of the rest of | ||
121 | * the BDM device. This is good for EMC reasons. This option is not | ||
122 | * incompatible with the memory protection option. | ||
123 | */ | ||
124 | wdebug(MCFDEBUG_CSR, MCFDEBUG_CSR_PSTCLK); | ||
125 | #endif | ||
126 | } | ||
127 | |||
128 | /***************************************************************************/ | ||
129 | /* Board initialization */ | ||
130 | |||
131 | /********************************************************************/ | ||
132 | /* | ||
133 | * PLL min/max specifications | ||
134 | */ | ||
135 | #define MAX_FVCO 500000 /* KHz */ | ||
136 | #define MAX_FSYS 80000 /* KHz */ | ||
137 | #define MIN_FSYS 58333 /* KHz */ | ||
138 | #define FREF 16000 /* KHz */ | ||
139 | |||
140 | |||
141 | #define MAX_MFD 135 /* Multiplier */ | ||
142 | #define MIN_MFD 88 /* Multiplier */ | ||
143 | #define BUSDIV 6 /* Divider */ | ||
144 | |||
145 | /* | ||
146 | * Low Power Divider specifications | ||
147 | */ | ||
148 | #define MIN_LPD (1 << 0) /* Divider (not encoded) */ | ||
149 | #define MAX_LPD (1 << 15) /* Divider (not encoded) */ | ||
150 | #define DEFAULT_LPD (1 << 1) /* Divider (not encoded) */ | ||
151 | |||
152 | #define SYS_CLK_KHZ 80000 | ||
153 | #define SYSTEM_PERIOD 12.5 | ||
154 | /* | ||
155 | * SDRAM Timing Parameters | ||
156 | */ | ||
157 | #define SDRAM_BL 8 /* # of beats in a burst */ | ||
158 | #define SDRAM_TWR 2 /* in clocks */ | ||
159 | #define SDRAM_CASL 2.5 /* CASL in clocks */ | ||
160 | #define SDRAM_TRCD 2 /* in clocks */ | ||
161 | #define SDRAM_TRP 2 /* in clocks */ | ||
162 | #define SDRAM_TRFC 7 /* in clocks */ | ||
163 | #define SDRAM_TREFI 7800 /* in ns */ | ||
164 | |||
165 | #define EXT_SRAM_ADDRESS (0xC0000000) | ||
166 | #define FLASH_ADDRESS (0x00000000) | ||
167 | #define SDRAM_ADDRESS (0x40000000) | ||
168 | |||
169 | #define NAND_FLASH_ADDRESS (0xD0000000) | ||
170 | |||
171 | int sys_clk_khz = 0; | ||
172 | int sys_clk_mhz = 0; | ||
173 | |||
174 | void wtm_init(void); | ||
175 | void scm_init(void); | ||
176 | void gpio_init(void); | ||
177 | void fbcs_init(void); | ||
178 | void sdramc_init(void); | ||
179 | int clock_pll (int fsys, int flags); | ||
180 | int clock_limp (int); | ||
181 | int clock_exit_limp (void); | ||
182 | int get_sys_clock (void); | ||
183 | |||
184 | asmlinkage void __init sysinit(void) | ||
185 | { | ||
186 | sys_clk_khz = clock_pll(0, 0); | ||
187 | sys_clk_mhz = sys_clk_khz/1000; | ||
188 | |||
189 | wtm_init(); | ||
190 | scm_init(); | ||
191 | gpio_init(); | ||
192 | fbcs_init(); | ||
193 | sdramc_init(); | ||
194 | } | ||
195 | |||
196 | void wtm_init(void) | ||
197 | { | ||
198 | /* Disable watchdog timer */ | ||
199 | MCF_WTM_WCR = 0; | ||
200 | } | ||
201 | |||
202 | #define MCF_SCM_BCR_GBW (0x00000100) | ||
203 | #define MCF_SCM_BCR_GBR (0x00000200) | ||
204 | |||
205 | void scm_init(void) | ||
206 | { | ||
207 | /* All masters are trusted */ | ||
208 | MCF_SCM_MPR = 0x77777777; | ||
209 | |||
210 | /* Allow supervisor/user, read/write, and trusted/untrusted | ||
211 | access to all slaves */ | ||
212 | MCF_SCM_PACRA = 0; | ||
213 | MCF_SCM_PACRB = 0; | ||
214 | MCF_SCM_PACRC = 0; | ||
215 | MCF_SCM_PACRD = 0; | ||
216 | MCF_SCM_PACRE = 0; | ||
217 | MCF_SCM_PACRF = 0; | ||
218 | |||
219 | /* Enable bursts */ | ||
220 | MCF_SCM_BCR = (MCF_SCM_BCR_GBR | MCF_SCM_BCR_GBW); | ||
221 | } | ||
222 | |||
223 | |||
224 | void fbcs_init(void) | ||
225 | { | ||
226 | MCF_GPIO_PAR_CS = 0x0000003E; | ||
227 | |||
228 | /* Latch chip select */ | ||
229 | MCF_FBCS1_CSAR = 0x10080000; | ||
230 | |||
231 | MCF_FBCS1_CSCR = 0x002A3780; | ||
232 | MCF_FBCS1_CSMR = (MCF_FBCS_CSMR_BAM_2M | MCF_FBCS_CSMR_V); | ||
233 | |||
234 | /* Initialize latch to drive signals to inactive states */ | ||
235 | *((u16 *)(0x10080000)) = 0xFFFF; | ||
236 | |||
237 | /* External SRAM */ | ||
238 | MCF_FBCS1_CSAR = EXT_SRAM_ADDRESS; | ||
239 | MCF_FBCS1_CSCR = (MCF_FBCS_CSCR_PS_16 | ||
240 | | MCF_FBCS_CSCR_AA | ||
241 | | MCF_FBCS_CSCR_SBM | ||
242 | | MCF_FBCS_CSCR_WS(1)); | ||
243 | MCF_FBCS1_CSMR = (MCF_FBCS_CSMR_BAM_512K | ||
244 | | MCF_FBCS_CSMR_V); | ||
245 | |||
246 | /* Boot Flash connected to FBCS0 */ | ||
247 | MCF_FBCS0_CSAR = FLASH_ADDRESS; | ||
248 | MCF_FBCS0_CSCR = (MCF_FBCS_CSCR_PS_16 | ||
249 | | MCF_FBCS_CSCR_BEM | ||
250 | | MCF_FBCS_CSCR_AA | ||
251 | | MCF_FBCS_CSCR_SBM | ||
252 | | MCF_FBCS_CSCR_WS(7)); | ||
253 | MCF_FBCS0_CSMR = (MCF_FBCS_CSMR_BAM_32M | ||
254 | | MCF_FBCS_CSMR_V); | ||
255 | } | ||
256 | |||
257 | void sdramc_init(void) | ||
258 | { | ||
259 | /* | ||
260 | * Check to see if the SDRAM has already been initialized | ||
261 | * by a run control tool | ||
262 | */ | ||
263 | if (!(MCF_SDRAMC_SDCR & MCF_SDRAMC_SDCR_REF)) { | ||
264 | /* SDRAM chip select initialization */ | ||
265 | |||
266 | /* Initialize SDRAM chip select */ | ||
267 | MCF_SDRAMC_SDCS0 = (0 | ||
268 | | MCF_SDRAMC_SDCS_BA(SDRAM_ADDRESS) | ||
269 | | MCF_SDRAMC_SDCS_CSSZ(MCF_SDRAMC_SDCS_CSSZ_32MBYTE)); | ||
270 | |||
271 | /* | ||
272 | * Basic configuration and initialization | ||
273 | */ | ||
274 | MCF_SDRAMC_SDCFG1 = (0 | ||
275 | | MCF_SDRAMC_SDCFG1_SRD2RW((int)((SDRAM_CASL + 2) + 0.5 )) | ||
276 | | MCF_SDRAMC_SDCFG1_SWT2RD(SDRAM_TWR + 1) | ||
277 | | MCF_SDRAMC_SDCFG1_RDLAT((int)((SDRAM_CASL*2) + 2)) | ||
278 | | MCF_SDRAMC_SDCFG1_ACT2RW((int)((SDRAM_TRCD ) + 0.5)) | ||
279 | | MCF_SDRAMC_SDCFG1_PRE2ACT((int)((SDRAM_TRP ) + 0.5)) | ||
280 | | MCF_SDRAMC_SDCFG1_REF2ACT((int)(((SDRAM_TRFC) ) + 0.5)) | ||
281 | | MCF_SDRAMC_SDCFG1_WTLAT(3)); | ||
282 | MCF_SDRAMC_SDCFG2 = (0 | ||
283 | | MCF_SDRAMC_SDCFG2_BRD2PRE(SDRAM_BL/2 + 1) | ||
284 | | MCF_SDRAMC_SDCFG2_BWT2RW(SDRAM_BL/2 + SDRAM_TWR) | ||
285 | | MCF_SDRAMC_SDCFG2_BRD2WT((int)((SDRAM_CASL+SDRAM_BL/2-1.0)+0.5)) | ||
286 | | MCF_SDRAMC_SDCFG2_BL(SDRAM_BL-1)); | ||
287 | |||
288 | |||
289 | /* | ||
290 | * Precharge and enable write to SDMR | ||
291 | */ | ||
292 | MCF_SDRAMC_SDCR = (0 | ||
293 | | MCF_SDRAMC_SDCR_MODE_EN | ||
294 | | MCF_SDRAMC_SDCR_CKE | ||
295 | | MCF_SDRAMC_SDCR_DDR | ||
296 | | MCF_SDRAMC_SDCR_MUX(1) | ||
297 | | MCF_SDRAMC_SDCR_RCNT((int)(((SDRAM_TREFI/(SYSTEM_PERIOD*64)) - 1) + 0.5)) | ||
298 | | MCF_SDRAMC_SDCR_PS_16 | ||
299 | | MCF_SDRAMC_SDCR_IPALL); | ||
300 | |||
301 | /* | ||
302 | * Write extended mode register | ||
303 | */ | ||
304 | MCF_SDRAMC_SDMR = (0 | ||
305 | | MCF_SDRAMC_SDMR_BNKAD_LEMR | ||
306 | | MCF_SDRAMC_SDMR_AD(0x0) | ||
307 | | MCF_SDRAMC_SDMR_CMD); | ||
308 | |||
309 | /* | ||
310 | * Write mode register and reset DLL | ||
311 | */ | ||
312 | MCF_SDRAMC_SDMR = (0 | ||
313 | | MCF_SDRAMC_SDMR_BNKAD_LMR | ||
314 | | MCF_SDRAMC_SDMR_AD(0x163) | ||
315 | | MCF_SDRAMC_SDMR_CMD); | ||
316 | |||
317 | /* | ||
318 | * Execute a PALL command | ||
319 | */ | ||
320 | MCF_SDRAMC_SDCR |= MCF_SDRAMC_SDCR_IPALL; | ||
321 | |||
322 | /* | ||
323 | * Perform two REF cycles | ||
324 | */ | ||
325 | MCF_SDRAMC_SDCR |= MCF_SDRAMC_SDCR_IREF; | ||
326 | MCF_SDRAMC_SDCR |= MCF_SDRAMC_SDCR_IREF; | ||
327 | |||
328 | /* | ||
329 | * Write mode register and clear reset DLL | ||
330 | */ | ||
331 | MCF_SDRAMC_SDMR = (0 | ||
332 | | MCF_SDRAMC_SDMR_BNKAD_LMR | ||
333 | | MCF_SDRAMC_SDMR_AD(0x063) | ||
334 | | MCF_SDRAMC_SDMR_CMD); | ||
335 | |||
336 | /* | ||
337 | * Enable auto refresh and lock SDMR | ||
338 | */ | ||
339 | MCF_SDRAMC_SDCR &= ~MCF_SDRAMC_SDCR_MODE_EN; | ||
340 | MCF_SDRAMC_SDCR |= (0 | ||
341 | | MCF_SDRAMC_SDCR_REF | ||
342 | | MCF_SDRAMC_SDCR_DQS_OE(0xC)); | ||
343 | } | ||
344 | } | ||
345 | |||
346 | void gpio_init(void) | ||
347 | { | ||
348 | /* Enable UART0 pins */ | ||
349 | MCF_GPIO_PAR_UART = ( 0 | ||
350 | | MCF_GPIO_PAR_UART_PAR_URXD0 | ||
351 | | MCF_GPIO_PAR_UART_PAR_UTXD0); | ||
352 | |||
353 | /* Initialize TIN3 as a GPIO output to enable the write | ||
354 | half of the latch */ | ||
355 | MCF_GPIO_PAR_TIMER = 0x00; | ||
356 | MCF_GPIO_PDDR_TIMER = 0x08; | ||
357 | MCF_GPIO_PCLRR_TIMER = 0x0; | ||
358 | |||
359 | } | ||
360 | |||
361 | int clock_pll(int fsys, int flags) | ||
362 | { | ||
363 | int fref, temp, fout, mfd; | ||
364 | u32 i; | ||
365 | |||
366 | fref = FREF; | ||
367 | |||
368 | if (fsys == 0) { | ||
369 | /* Return current PLL output */ | ||
370 | mfd = MCF_PLL_PFDR; | ||
371 | |||
372 | return (fref * mfd / (BUSDIV * 4)); | ||
373 | } | ||
374 | |||
375 | /* Check bounds of requested system clock */ | ||
376 | if (fsys > MAX_FSYS) | ||
377 | fsys = MAX_FSYS; | ||
378 | if (fsys < MIN_FSYS) | ||
379 | fsys = MIN_FSYS; | ||
380 | |||
381 | /* Multiplying by 100 when calculating the temp value, | ||
382 | and then dividing by 100 to calculate the mfd allows | ||
383 | for exact values without needing to include floating | ||
384 | point libraries. */ | ||
385 | temp = 100 * fsys / fref; | ||
386 | mfd = 4 * BUSDIV * temp / 100; | ||
387 | |||
388 | /* Determine the output frequency for selected values */ | ||
389 | fout = (fref * mfd / (BUSDIV * 4)); | ||
390 | |||
391 | /* | ||
392 | * Check to see if the SDRAM has already been initialized. | ||
393 | * If it has then the SDRAM needs to be put into self refresh | ||
394 | * mode before reprogramming the PLL. | ||
395 | */ | ||
396 | if (MCF_SDRAMC_SDCR & MCF_SDRAMC_SDCR_REF) | ||
397 | /* Put SDRAM into self refresh mode */ | ||
398 | MCF_SDRAMC_SDCR &= ~MCF_SDRAMC_SDCR_CKE; | ||
399 | |||
400 | /* | ||
401 | * Initialize the PLL to generate the new system clock frequency. | ||
402 | * The device must be put into LIMP mode to reprogram the PLL. | ||
403 | */ | ||
404 | |||
405 | /* Enter LIMP mode */ | ||
406 | clock_limp(DEFAULT_LPD); | ||
407 | |||
408 | /* Reprogram PLL for desired fsys */ | ||
409 | MCF_PLL_PODR = (0 | ||
410 | | MCF_PLL_PODR_CPUDIV(BUSDIV/3) | ||
411 | | MCF_PLL_PODR_BUSDIV(BUSDIV)); | ||
412 | |||
413 | MCF_PLL_PFDR = mfd; | ||
414 | |||
415 | /* Exit LIMP mode */ | ||
416 | clock_exit_limp(); | ||
417 | |||
418 | /* | ||
419 | * Return the SDRAM to normal operation if it is in use. | ||
420 | */ | ||
421 | if (MCF_SDRAMC_SDCR & MCF_SDRAMC_SDCR_REF) | ||
422 | /* Exit self refresh mode */ | ||
423 | MCF_SDRAMC_SDCR |= MCF_SDRAMC_SDCR_CKE; | ||
424 | |||
425 | /* Errata - workaround for SDRAM opeartion after exiting LIMP mode */ | ||
426 | MCF_SDRAMC_LIMP_FIX = MCF_SDRAMC_REFRESH; | ||
427 | |||
428 | /* wait for DQS logic to relock */ | ||
429 | for (i = 0; i < 0x200; i++) | ||
430 | ; | ||
431 | |||
432 | return fout; | ||
433 | } | ||
434 | |||
435 | int clock_limp(int div) | ||
436 | { | ||
437 | u32 temp; | ||
438 | |||
439 | /* Check bounds of divider */ | ||
440 | if (div < MIN_LPD) | ||
441 | div = MIN_LPD; | ||
442 | if (div > MAX_LPD) | ||
443 | div = MAX_LPD; | ||
444 | |||
445 | /* Save of the current value of the SSIDIV so we don't | ||
446 | overwrite the value*/ | ||
447 | temp = (MCF_CCM_CDR & MCF_CCM_CDR_SSIDIV(0xF)); | ||
448 | |||
449 | /* Apply the divider to the system clock */ | ||
450 | MCF_CCM_CDR = ( 0 | ||
451 | | MCF_CCM_CDR_LPDIV(div) | ||
452 | | MCF_CCM_CDR_SSIDIV(temp)); | ||
453 | |||
454 | MCF_CCM_MISCCR |= MCF_CCM_MISCCR_LIMP; | ||
455 | |||
456 | return (FREF/(3*(1 << div))); | ||
457 | } | ||
458 | |||
459 | int clock_exit_limp(void) | ||
460 | { | ||
461 | int fout; | ||
462 | |||
463 | /* Exit LIMP mode */ | ||
464 | MCF_CCM_MISCCR = (MCF_CCM_MISCCR & ~ MCF_CCM_MISCCR_LIMP); | ||
465 | |||
466 | /* Wait for PLL to lock */ | ||
467 | while (!(MCF_CCM_MISCCR & MCF_CCM_MISCCR_PLL_LOCK)) | ||
468 | ; | ||
469 | |||
470 | fout = get_sys_clock(); | ||
471 | |||
472 | return fout; | ||
473 | } | ||
474 | |||
475 | int get_sys_clock(void) | ||
476 | { | ||
477 | int divider; | ||
478 | |||
479 | /* Test to see if device is in LIMP mode */ | ||
480 | if (MCF_CCM_MISCCR & MCF_CCM_MISCCR_LIMP) { | ||
481 | divider = MCF_CCM_CDR & MCF_CCM_CDR_LPDIV(0xF); | ||
482 | return (FREF/(2 << divider)); | ||
483 | } | ||
484 | else | ||
485 | return ((FREF * MCF_PLL_PFDR) / (BUSDIV * 4)); | ||
486 | } | ||