Commit 81a8824f2e751f31249bc23e166c09d82e287887
1 parent
1775979a28
Exists in
master
and in
54 other branches
Initial revision
Showing 2 changed files with 1041 additions and 0 deletions Side-by-side Diff
common/cmd_i2c.c
1 | +/* | |
2 | + * (C) Copyright 2001 | |
3 | + * Gerald Van Baren, Custom IDEAS, vanbaren@cideas.com. | |
4 | + * | |
5 | + * See file CREDITS for list of people who contributed to this | |
6 | + * project. | |
7 | + * | |
8 | + * This program is free software; you can redistribute it and/or | |
9 | + * modify it under the terms of the GNU General Public License as | |
10 | + * published by the Free Software Foundation; either version 2 of | |
11 | + * the License, or (at your option) any later version. | |
12 | + * | |
13 | + * This program is distributed in the hope that it will be useful, | |
14 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
16 | + * GNU General Public License for more details. | |
17 | + * | |
18 | + * You should have received a copy of the GNU General Public License | |
19 | + * along with this program; if not, write to the Free Software | |
20 | + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, | |
21 | + * MA 02111-1307 USA | |
22 | + */ | |
23 | + | |
24 | +/* | |
25 | + * I2C Functions similar to the standard memory functions. | |
26 | + * | |
27 | + * There are several parameters in many of the commands that bear further | |
28 | + * explanations: | |
29 | + * | |
30 | + * Two of the commands (imm and imw) take a byte/word/long modifier | |
31 | + * (e.g. imm.w specifies the word-length modifier). This was done to | |
32 | + * allow manipulating word-length registers. It was not done on any other | |
33 | + * commands because it was not deemed useful. | |
34 | + * | |
35 | + * {i2c_chip} is the I2C chip address (the first byte sent on the bus). | |
36 | + * Each I2C chip on the bus has a unique address. On the I2C data bus, | |
37 | + * the address is the upper seven bits and the LSB is the "read/write" | |
38 | + * bit. Note that the {i2c_chip} address specified on the command | |
39 | + * line is not shifted up: e.g. a typical EEPROM memory chip may have | |
40 | + * an I2C address of 0x50, but the data put on the bus will be 0xA0 | |
41 | + * for write and 0xA1 for read. This "non shifted" address notation | |
42 | + * matches at least half of the data sheets :-/. | |
43 | + * | |
44 | + * {addr} is the address (or offset) within the chip. Small memory | |
45 | + * chips have 8 bit addresses. Large memory chips have 16 bit | |
46 | + * addresses. Other memory chips have 9, 10, or 11 bit addresses. | |
47 | + * Many non-memory chips have multiple registers and {addr} is used | |
48 | + * as the register index. Some non-memory chips have only one register | |
49 | + * and therefore don't need any {addr} parameter. | |
50 | + * | |
51 | + * The default {addr} parameter is one byte (.1) which works well for | |
52 | + * memories and registers with 8 bits of address space. | |
53 | + * | |
54 | + * You can specify the length of the {addr} field with the optional .0, | |
55 | + * .1, or .2 modifier (similar to the .b, .w, .l modifier). If you are | |
56 | + * manipulating a single register device which doesn't use an address | |
57 | + * field, use "0.0" for the address and the ".0" length field will | |
58 | + * suppress the address in the I2C data stream. This also works for | |
59 | + * successive reads using the I2C auto-incrementing memory pointer. | |
60 | + * | |
61 | + * If you are manipulating a large memory with 2-byte addresses, use | |
62 | + * the .2 address modifier, e.g. 210.2 addresses location 528 (decimal). | |
63 | + * | |
64 | + * Then there are the unfortunate memory chips that spill the most | |
65 | + * significant 1, 2, or 3 bits of address into the chip address byte. | |
66 | + * This effectively makes one chip (logically) look like 2, 4, or | |
67 | + * 8 chips. This is handled (awkwardly) by #defining | |
68 | + * CFG_I2C_EEPROM_ADDR_OVERFLOW and using the .1 modifier on the | |
69 | + * {addr} field (since .1 is the default, it doesn't actually have to | |
70 | + * be specified). Examples: given a memory chip at I2C chip address | |
71 | + * 0x50, the following would happen... | |
72 | + * imd 50 0 10 display 16 bytes starting at 0x000 | |
73 | + * On the bus: <S> A0 00 <E> <S> A1 <rd> ... <rd> | |
74 | + * imd 50 100 10 display 16 bytes starting at 0x100 | |
75 | + * On the bus: <S> A2 00 <E> <S> A3 <rd> ... <rd> | |
76 | + * imd 50 210 10 display 16 bytes starting at 0x210 | |
77 | + * On the bus: <S> A4 10 <E> <S> A5 <rd> ... <rd> | |
78 | + * This is awfully ugly. It would be nice if someone would think up | |
79 | + * a better way of handling this. | |
80 | + * | |
81 | + * Adapted from cmd_mem.c which is copyright Wolfgang Denk (wd@denx.de). | |
82 | + */ | |
83 | + | |
84 | +#include <common.h> | |
85 | +#include <command.h> | |
86 | +#include <cmd_i2c.h> | |
87 | +#include <i2c.h> | |
88 | +#include <asm/byteorder.h> | |
89 | + | |
90 | +#if (CONFIG_COMMANDS & CFG_CMD_I2C) | |
91 | + | |
92 | + | |
93 | +/* Display values from last command. | |
94 | + * Memory modify remembered values are different from display memory. | |
95 | + */ | |
96 | +static uchar i2c_dp_last_chip; | |
97 | +static uint i2c_dp_last_addr; | |
98 | +static uint i2c_dp_last_alen; | |
99 | +static uint i2c_dp_last_length = 0x10; | |
100 | + | |
101 | +static uchar i2c_mm_last_chip; | |
102 | +static uint i2c_mm_last_addr; | |
103 | +static uint i2c_mm_last_alen; | |
104 | + | |
105 | +#if defined(CFG_I2C_NOPROBES) | |
106 | +static uchar i2c_no_probes[] = CFG_I2C_NOPROBES; | |
107 | +#endif | |
108 | + | |
109 | +static int | |
110 | +mod_i2c_mem(cmd_tbl_t *cmdtp, int incrflag, int flag, int argc, char *argv[]); | |
111 | +extern int cmd_get_data_size(char* arg, int default_size); | |
112 | + | |
113 | +/* | |
114 | + * Syntax: | |
115 | + * imd {i2c_chip} {addr}{.0, .1, .2} {len} | |
116 | + */ | |
117 | +#define DISP_LINE_LEN 16 | |
118 | + | |
119 | +int do_i2c_md ( cmd_tbl_t *cmdtp, int flag, int argc, char *argv[]) | |
120 | +{ | |
121 | + u_char chip; | |
122 | + uint addr, alen, length; | |
123 | + int j, nbytes, linebytes; | |
124 | + | |
125 | + /* We use the last specified parameters, unless new ones are | |
126 | + * entered. | |
127 | + */ | |
128 | + chip = i2c_dp_last_chip; | |
129 | + addr = i2c_dp_last_addr; | |
130 | + alen = i2c_dp_last_alen; | |
131 | + length = i2c_dp_last_length; | |
132 | + | |
133 | + if (argc < 3) { | |
134 | + printf ("Usage:\n%s\n", cmdtp->usage); | |
135 | + return 1; | |
136 | + } | |
137 | + | |
138 | + if ((flag & CMD_FLAG_REPEAT) == 0) { | |
139 | + /* | |
140 | + * New command specified. | |
141 | + */ | |
142 | + alen = 1; | |
143 | + | |
144 | + /* | |
145 | + * I2C chip address | |
146 | + */ | |
147 | + chip = simple_strtoul(argv[1], NULL, 16); | |
148 | + | |
149 | + /* | |
150 | + * I2C data address within the chip. This can be 1 or | |
151 | + * 2 bytes long. Some day it might be 3 bytes long :-). | |
152 | + */ | |
153 | + addr = simple_strtoul(argv[2], NULL, 16); | |
154 | + alen = 1; | |
155 | + for(j = 0; j < 8; j++) { | |
156 | + if (argv[2][j] == '.') { | |
157 | + alen = argv[2][j+1] - '0'; | |
158 | + if (alen > 4) { | |
159 | + printf ("Usage:\n%s\n", cmdtp->usage); | |
160 | + return 1; | |
161 | + } | |
162 | + break; | |
163 | + } else if (argv[2][j] == '\0') { | |
164 | + break; | |
165 | + } | |
166 | + } | |
167 | + | |
168 | + /* | |
169 | + * If another parameter, it is the length to display. | |
170 | + * Length is the number of objects, not number of bytes. | |
171 | + */ | |
172 | + if (argc > 3) | |
173 | + length = simple_strtoul(argv[3], NULL, 16); | |
174 | + } | |
175 | + | |
176 | + /* | |
177 | + * Print the lines. | |
178 | + * | |
179 | + * We buffer all read data, so we can make sure data is read only | |
180 | + * once. | |
181 | + */ | |
182 | + nbytes = length; | |
183 | + do { | |
184 | + unsigned char linebuf[DISP_LINE_LEN]; | |
185 | + unsigned char *cp; | |
186 | + | |
187 | + linebytes = (nbytes > DISP_LINE_LEN) ? DISP_LINE_LEN : nbytes; | |
188 | + | |
189 | + if(i2c_read(chip, addr, alen, linebuf, linebytes) != 0) { | |
190 | + printf("Error reading the chip.\n"); | |
191 | + } else { | |
192 | + printf("%04x:", addr); | |
193 | + cp = linebuf; | |
194 | + for (j=0; j<linebytes; j++) { | |
195 | + printf(" %02x", *cp++); | |
196 | + addr++; | |
197 | + } | |
198 | + printf(" "); | |
199 | + cp = linebuf; | |
200 | + for (j=0; j<linebytes; j++) { | |
201 | + if ((*cp < 0x20) || (*cp > 0x7e)) | |
202 | + printf("."); | |
203 | + else | |
204 | + printf("%c", *cp); | |
205 | + cp++; | |
206 | + } | |
207 | + printf("\n"); | |
208 | + } | |
209 | + nbytes -= linebytes; | |
210 | + } while (nbytes > 0); | |
211 | + | |
212 | + i2c_dp_last_chip = chip; | |
213 | + i2c_dp_last_addr = addr; | |
214 | + i2c_dp_last_alen = alen; | |
215 | + i2c_dp_last_length = length; | |
216 | + | |
217 | + return 0; | |
218 | +} | |
219 | + | |
220 | +int do_i2c_mm ( cmd_tbl_t *cmdtp, int flag, int argc, char *argv[]) | |
221 | +{ | |
222 | + return mod_i2c_mem (cmdtp, 1, flag, argc, argv); | |
223 | +} | |
224 | + | |
225 | + | |
226 | +int do_i2c_nm ( cmd_tbl_t *cmdtp, int flag, int argc, char *argv[]) | |
227 | +{ | |
228 | + return mod_i2c_mem (cmdtp, 0, flag, argc, argv); | |
229 | +} | |
230 | + | |
231 | +/* Write (fill) memory | |
232 | + * | |
233 | + * Syntax: | |
234 | + * imw {i2c_chip} {addr}{.0, .1, .2} {data} [{count}] | |
235 | + */ | |
236 | +int do_i2c_mw ( cmd_tbl_t *cmdtp, int flag, int argc, char *argv[]) | |
237 | +{ | |
238 | + uchar chip; | |
239 | + ulong addr; | |
240 | + uint alen; | |
241 | + uchar byte; | |
242 | + int count; | |
243 | + int j; | |
244 | + | |
245 | + if ((argc < 4) || (argc > 5)) { | |
246 | + printf ("Usage:\n%s\n", cmdtp->usage); | |
247 | + return 1; | |
248 | + } | |
249 | + | |
250 | + /* | |
251 | + * Chip is always specified. | |
252 | + */ | |
253 | + chip = simple_strtoul(argv[1], NULL, 16); | |
254 | + | |
255 | + /* | |
256 | + * Address is always specified. | |
257 | + */ | |
258 | + addr = simple_strtoul(argv[2], NULL, 16); | |
259 | + alen = 1; | |
260 | + for(j = 0; j < 8; j++) { | |
261 | + if (argv[2][j] == '.') { | |
262 | + alen = argv[2][j+1] - '0'; | |
263 | + if(alen > 4) { | |
264 | + printf ("Usage:\n%s\n", cmdtp->usage); | |
265 | + return 1; | |
266 | + } | |
267 | + break; | |
268 | + } else if (argv[2][j] == '\0') { | |
269 | + break; | |
270 | + } | |
271 | + } | |
272 | + | |
273 | + /* | |
274 | + * Value to write is always specified. | |
275 | + */ | |
276 | + byte = simple_strtoul(argv[3], NULL, 16); | |
277 | + | |
278 | + /* | |
279 | + * Optional count | |
280 | + */ | |
281 | + if(argc == 5) { | |
282 | + count = simple_strtoul(argv[4], NULL, 16); | |
283 | + } else { | |
284 | + count = 1; | |
285 | + } | |
286 | + | |
287 | + while (count-- > 0) { | |
288 | + if(i2c_write(chip, addr++, alen, &byte, 1) != 0) { | |
289 | + printf("Error writing the chip.\n"); | |
290 | + } | |
291 | + /* | |
292 | + * Wait for the write to complete. The write can take | |
293 | + * up to 10mSec (we allow a little more time). | |
294 | + * | |
295 | + * On some chips, while the write is in progress, the | |
296 | + * chip doesn't respond. This apparently isn't a | |
297 | + * universal feature so we don't take advantage of it. | |
298 | + */ | |
299 | + udelay(11000); | |
300 | +#if 0 | |
301 | + for(timeout = 0; timeout < 10; timeout++) { | |
302 | + udelay(2000); | |
303 | + if(i2c_probe(chip) == 0) | |
304 | + break; | |
305 | + } | |
306 | +#endif | |
307 | + } | |
308 | + | |
309 | + return (0); | |
310 | +} | |
311 | + | |
312 | + | |
313 | +/* Calculate a CRC on memory | |
314 | + * | |
315 | + * Syntax: | |
316 | + * icrc32 {i2c_chip} {addr}{.0, .1, .2} {count} | |
317 | + */ | |
318 | +int do_i2c_crc (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[]) | |
319 | +{ | |
320 | + uchar chip; | |
321 | + ulong addr; | |
322 | + uint alen; | |
323 | + int count; | |
324 | + uchar byte; | |
325 | + ulong crc; | |
326 | + ulong err; | |
327 | + int j; | |
328 | + | |
329 | + if (argc < 4) { | |
330 | + printf ("Usage:\n%s\n", cmdtp->usage); | |
331 | + return 1; | |
332 | + } | |
333 | + | |
334 | + /* | |
335 | + * Chip is always specified. | |
336 | + */ | |
337 | + chip = simple_strtoul(argv[1], NULL, 16); | |
338 | + | |
339 | + /* | |
340 | + * Address is always specified. | |
341 | + */ | |
342 | + addr = simple_strtoul(argv[2], NULL, 16); | |
343 | + alen = 1; | |
344 | + for(j = 0; j < 8; j++) { | |
345 | + if (argv[2][j] == '.') { | |
346 | + alen = argv[2][j+1] - '0'; | |
347 | + if(alen > 4) { | |
348 | + printf ("Usage:\n%s\n", cmdtp->usage); | |
349 | + return 1; | |
350 | + } | |
351 | + break; | |
352 | + } else if (argv[2][j] == '\0') { | |
353 | + break; | |
354 | + } | |
355 | + } | |
356 | + | |
357 | + /* | |
358 | + * Count is always specified | |
359 | + */ | |
360 | + count = simple_strtoul(argv[3], NULL, 16); | |
361 | + | |
362 | + printf ("CRC32 for %08lx ... %08lx ==> ", addr, addr + count - 1); | |
363 | + /* | |
364 | + * CRC a byte at a time. This is going to be slooow, but hey, the | |
365 | + * memories are small and slow too so hopefully nobody notices. | |
366 | + */ | |
367 | + crc = 0; | |
368 | + err = 0; | |
369 | + while(count-- > 0) { | |
370 | + if(i2c_read(chip, addr, alen, &byte, 1) != 0) { | |
371 | + err++; | |
372 | + } | |
373 | + crc = crc32 (crc, &byte, 1); | |
374 | + addr++; | |
375 | + } | |
376 | + if(err > 0) | |
377 | + { | |
378 | + printf("Error reading the chip,\n"); | |
379 | + } else { | |
380 | + printf ("%08lx\n", crc); | |
381 | + } | |
382 | + | |
383 | + return 0; | |
384 | +} | |
385 | + | |
386 | + | |
387 | +/* Modify memory. | |
388 | + * | |
389 | + * Syntax: | |
390 | + * imm{.b, .w, .l} {i2c_chip} {addr}{.0, .1, .2} | |
391 | + * inm{.b, .w, .l} {i2c_chip} {addr}{.0, .1, .2} | |
392 | + */ | |
393 | + | |
394 | +static int | |
395 | +mod_i2c_mem(cmd_tbl_t *cmdtp, int incrflag, int flag, int argc, char *argv[]) | |
396 | +{ | |
397 | + uchar chip; | |
398 | + ulong addr; | |
399 | + uint alen; | |
400 | + ulong data; | |
401 | + int size = 1; | |
402 | + int nbytes; | |
403 | + int j; | |
404 | + extern char console_buffer[]; | |
405 | + | |
406 | + if (argc != 3) { | |
407 | + printf ("Usage:\n%s\n", cmdtp->usage); | |
408 | + return 1; | |
409 | + } | |
410 | + | |
411 | +#ifdef CONFIG_BOOT_RETRY_TIME | |
412 | + reset_cmd_timeout(); /* got a good command to get here */ | |
413 | +#endif | |
414 | + /* | |
415 | + * We use the last specified parameters, unless new ones are | |
416 | + * entered. | |
417 | + */ | |
418 | + chip = i2c_mm_last_chip; | |
419 | + addr = i2c_mm_last_addr; | |
420 | + alen = i2c_mm_last_alen; | |
421 | + | |
422 | + if ((flag & CMD_FLAG_REPEAT) == 0) { | |
423 | + /* | |
424 | + * New command specified. Check for a size specification. | |
425 | + * Defaults to byte if no or incorrect specification. | |
426 | + */ | |
427 | + size = cmd_get_data_size(argv[0], 1); | |
428 | + | |
429 | + /* | |
430 | + * Chip is always specified. | |
431 | + */ | |
432 | + chip = simple_strtoul(argv[1], NULL, 16); | |
433 | + | |
434 | + /* | |
435 | + * Address is always specified. | |
436 | + */ | |
437 | + addr = simple_strtoul(argv[2], NULL, 16); | |
438 | + alen = 1; | |
439 | + for(j = 0; j < 8; j++) { | |
440 | + if (argv[2][j] == '.') { | |
441 | + alen = argv[2][j+1] - '0'; | |
442 | + if(alen > 4) { | |
443 | + printf ("Usage:\n%s\n", cmdtp->usage); | |
444 | + return 1; | |
445 | + } | |
446 | + break; | |
447 | + } else if (argv[2][j] == '\0') { | |
448 | + break; | |
449 | + } | |
450 | + } | |
451 | + } | |
452 | + | |
453 | + /* | |
454 | + * Print the address, followed by value. Then accept input for | |
455 | + * the next value. A non-converted value exits. | |
456 | + */ | |
457 | + do { | |
458 | + printf("%08lx:", addr); | |
459 | + if(i2c_read(chip, addr, alen, (char *)&data, size) != 0) { | |
460 | + printf("\nError reading the chip,\n"); | |
461 | + } else { | |
462 | + data = cpu_to_be32(data); | |
463 | + if(size == 1) { | |
464 | + printf(" %02lx", (data >> 24) & 0x000000FF); | |
465 | + } else if(size == 2) { | |
466 | + printf(" %04lx", (data >> 16) & 0x0000FFFF); | |
467 | + } else { | |
468 | + printf(" %08lx", data); | |
469 | + } | |
470 | + } | |
471 | + | |
472 | + nbytes = readline (" ? "); | |
473 | + if (nbytes == 0) { | |
474 | + /* | |
475 | + * <CR> pressed as only input, don't modify current | |
476 | + * location and move to next. | |
477 | + */ | |
478 | + if (incrflag) | |
479 | + addr += size; | |
480 | + nbytes = size; | |
481 | +#ifdef CONFIG_BOOT_RETRY_TIME | |
482 | + reset_cmd_timeout(); /* good enough to not time out */ | |
483 | +#endif | |
484 | + } | |
485 | +#ifdef CONFIG_BOOT_RETRY_TIME | |
486 | + else if (nbytes == -2) { | |
487 | + break; /* timed out, exit the command */ | |
488 | + } | |
489 | +#endif | |
490 | + else { | |
491 | + char *endp; | |
492 | + | |
493 | + data = simple_strtoul(console_buffer, &endp, 16); | |
494 | + if(size == 1) { | |
495 | + data = data << 24; | |
496 | + } else if(size == 2) { | |
497 | + data = data << 16; | |
498 | + } | |
499 | + data = be32_to_cpu(data); | |
500 | + nbytes = endp - console_buffer; | |
501 | + if (nbytes) { | |
502 | +#ifdef CONFIG_BOOT_RETRY_TIME | |
503 | + /* | |
504 | + * good enough to not time out | |
505 | + */ | |
506 | + reset_cmd_timeout(); | |
507 | +#endif | |
508 | + if(i2c_write(chip, addr, alen, (char *)&data, size) != 0) { | |
509 | + printf("Error writing the chip.\n"); | |
510 | + } | |
511 | + if (incrflag) | |
512 | + addr += size; | |
513 | + } | |
514 | + } | |
515 | + } while (nbytes); | |
516 | + | |
517 | + chip = i2c_mm_last_chip; | |
518 | + addr = i2c_mm_last_addr; | |
519 | + alen = i2c_mm_last_alen; | |
520 | + | |
521 | + return 0; | |
522 | +} | |
523 | + | |
524 | +/* | |
525 | + * Syntax: | |
526 | + * iprobe {addr}{.0, .1, .2} | |
527 | + */ | |
528 | +int do_i2c_probe (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[]) | |
529 | +{ | |
530 | + int j; | |
531 | +#if defined(CFG_I2C_NOPROBES) | |
532 | + int k, skip; | |
533 | +#endif | |
534 | + | |
535 | + printf("Valid chip addresses:"); | |
536 | + for(j = 0; j < 128; j++) { | |
537 | +#if defined(CFG_I2C_NOPROBES) | |
538 | + skip = 0; | |
539 | + for (k = 0; k < sizeof(i2c_no_probes); k++){ | |
540 | + if (j == i2c_no_probes[k]){ | |
541 | + skip = 1; | |
542 | + break; | |
543 | + } | |
544 | + } | |
545 | + if (skip) | |
546 | + continue; | |
547 | +#endif | |
548 | + if(i2c_probe(j) == 0) { | |
549 | + printf(" %02X", j); | |
550 | + } | |
551 | + } | |
552 | + printf("\n"); | |
553 | + | |
554 | +#if defined(CFG_I2C_NOPROBES) | |
555 | + puts ("Excluded chip addresses:"); | |
556 | + for( k = 0; k < sizeof(i2c_no_probes); k++ ) | |
557 | + printf(" %02X", i2c_no_probes[k] ); | |
558 | + puts ("\n"); | |
559 | +#endif | |
560 | + | |
561 | + return 0; | |
562 | +} | |
563 | + | |
564 | + | |
565 | +/* | |
566 | + * Syntax: | |
567 | + * iloop {i2c_chip} {addr}{.0, .1, .2} [{length}] [{delay}] | |
568 | + * {length} - Number of bytes to read | |
569 | + * {delay} - A DECIMAL number and defaults to 1000 uSec | |
570 | + */ | |
571 | +int do_i2c_loop(cmd_tbl_t *cmdtp, int flag, int argc, char *argv[]) | |
572 | +{ | |
573 | + u_char chip; | |
574 | + ulong alen; | |
575 | + uint addr; | |
576 | + uint length; | |
577 | + u_char bytes[16]; | |
578 | + int delay; | |
579 | + int j; | |
580 | + | |
581 | + if (argc < 3) { | |
582 | + printf ("Usage:\n%s\n", cmdtp->usage); | |
583 | + return 1; | |
584 | + } | |
585 | + | |
586 | + /* | |
587 | + * Chip is always specified. | |
588 | + */ | |
589 | + chip = simple_strtoul(argv[1], NULL, 16); | |
590 | + | |
591 | + /* | |
592 | + * Address is always specified. | |
593 | + */ | |
594 | + addr = simple_strtoul(argv[2], NULL, 16); | |
595 | + alen = 1; | |
596 | + for(j = 0; j < 8; j++) { | |
597 | + if (argv[2][j] == '.') { | |
598 | + alen = argv[2][j+1] - '0'; | |
599 | + if (alen > 4) { | |
600 | + printf ("Usage:\n%s\n", cmdtp->usage); | |
601 | + return 1; | |
602 | + } | |
603 | + break; | |
604 | + } else if (argv[2][j] == '\0') { | |
605 | + break; | |
606 | + } | |
607 | + } | |
608 | + | |
609 | + /* | |
610 | + * Length is the number of objects, not number of bytes. | |
611 | + */ | |
612 | + length = 1; | |
613 | + length = simple_strtoul(argv[3], NULL, 16); | |
614 | + if(length > sizeof(bytes)) { | |
615 | + length = sizeof(bytes); | |
616 | + } | |
617 | + | |
618 | + /* | |
619 | + * The delay time (uSec) is optional. | |
620 | + */ | |
621 | + delay = 1000; | |
622 | + if (argc > 3) { | |
623 | + delay = simple_strtoul(argv[4], NULL, 10); | |
624 | + } | |
625 | + /* | |
626 | + * Run the loop... | |
627 | + */ | |
628 | + while(1) { | |
629 | + if(i2c_read(chip, addr, alen, bytes, length) != 0) { | |
630 | + printf("Error reading the chip.\n"); | |
631 | + } | |
632 | + udelay(delay); | |
633 | + } | |
634 | + | |
635 | + /* NOTREACHED */ | |
636 | + return 0; | |
637 | +} | |
638 | + | |
639 | + | |
640 | +/* | |
641 | + * The SDRAM command is separately configured because many | |
642 | + * (most?) embedded boards don't use SDRAM DIMMs. | |
643 | + */ | |
644 | +#if (CONFIG_COMMANDS & CFG_CMD_SDRAM) | |
645 | + | |
646 | +/* | |
647 | + * Syntax: | |
648 | + * sdram {i2c_chip} | |
649 | + */ | |
650 | +int do_sdram ( cmd_tbl_t *cmdtp, int flag, int argc, char *argv[]) | |
651 | +{ | |
652 | + u_char chip; | |
653 | + u_char data[128]; | |
654 | + u_char cksum; | |
655 | + int j; | |
656 | + | |
657 | + if (argc < 2) { | |
658 | + printf ("Usage:\n%s\n", cmdtp->usage); | |
659 | + return 1; | |
660 | + } | |
661 | + /* | |
662 | + * Chip is always specified. | |
663 | + */ | |
664 | + chip = simple_strtoul(argv[1], NULL, 16); | |
665 | + | |
666 | + if(i2c_read(chip, 0, 1, data, sizeof(data)) != 0) { | |
667 | + printf("No SDRAM Serial Presence Detect found.\n"); | |
668 | + return 1; | |
669 | + } | |
670 | + | |
671 | + cksum = 0; | |
672 | + for (j = 0; j < 63; j++) { | |
673 | + cksum += data[j]; | |
674 | + } | |
675 | + if(cksum != data[63]) { | |
676 | + printf ("WARNING: Configuration data checksum failure:\n" | |
677 | + " is 0x%02x, calculated 0x%02x\n", | |
678 | + data[63], cksum); | |
679 | + } | |
680 | + printf("SPD data revision %d.%d\n", | |
681 | + (data[62] >> 4) & 0x0F, data[62] & 0x0F); | |
682 | + printf("Bytes used 0x%02X\n", data[0]); | |
683 | + printf("Serial memory size 0x%02X\n", 1 << data[1]); | |
684 | + printf("Memory type "); | |
685 | + switch(data[2]) { | |
686 | + case 2: printf("EDO\n"); break; | |
687 | + case 4: printf("SDRAM\n"); break; | |
688 | + default: printf("unknown\n"); break; | |
689 | + } | |
690 | + printf("Row address bits "); | |
691 | + if((data[3] & 0x00F0) == 0) { | |
692 | + printf("%d\n", data[3] & 0x0F); | |
693 | + } else { | |
694 | + printf("%d/%d\n", data[3] & 0x0F, (data[3] >> 4) & 0x0F); | |
695 | + } | |
696 | + printf("Column address bits "); | |
697 | + if((data[4] & 0x00F0) == 0) { | |
698 | + printf("%d\n", data[4] & 0x0F); | |
699 | + } else { | |
700 | + printf("%d/%d\n", data[4] & 0x0F, (data[4] >> 4) & 0x0F); | |
701 | + } | |
702 | + printf("Module rows %d\n", data[5]); | |
703 | + printf("Module data width %d bits\n", (data[7] << 8) | data[6]); | |
704 | + printf("Interface signal levels "); | |
705 | + switch(data[8]) { | |
706 | + case 0: printf("5.0v/TTL\n"); break; | |
707 | + case 1: printf("LVTTL\n"); break; | |
708 | + case 2: printf("HSTL 1.5\n"); break; | |
709 | + case 3: printf("SSTL 3.3\n"); break; | |
710 | + case 4: printf("SSTL 2.5\n"); break; | |
711 | + default: printf("unknown\n"); break; | |
712 | + } | |
713 | + printf("SDRAM cycle time %d.%d nS\n", | |
714 | + (data[9] >> 4) & 0x0F, data[9] & 0x0F); | |
715 | + printf("SDRAM access time %d.%d nS\n", | |
716 | + (data[10] >> 4) & 0x0F, data[10] & 0x0F); | |
717 | + printf("EDC configuration "); | |
718 | + switch(data[11]) { | |
719 | + case 0: printf("None\n"); break; | |
720 | + case 1: printf("Parity\n"); break; | |
721 | + case 2: printf("ECC\n"); break; | |
722 | + default: printf("unknown\n"); break; | |
723 | + } | |
724 | + if((data[12] & 0x80) == 0) { | |
725 | + printf("No self refresh, rate "); | |
726 | + } else { | |
727 | + printf("Self refresh, rate "); | |
728 | + } | |
729 | + switch(data[12] & 0x7F) { | |
730 | + case 0: printf("15.625uS\n"); break; | |
731 | + case 1: printf("3.9uS\n"); break; | |
732 | + case 2: printf("7.8uS\n"); break; | |
733 | + case 3: printf("31.3uS\n"); break; | |
734 | + case 4: printf("62.5uS\n"); break; | |
735 | + case 5: printf("125uS\n"); break; | |
736 | + default: printf("unknown\n"); break; | |
737 | + } | |
738 | + printf("SDRAM width (primary) %d\n", data[13] & 0x7F); | |
739 | + if((data[13] & 0x80) != 0) { | |
740 | + printf(" (second bank) %d\n", | |
741 | + 2 * (data[13] & 0x7F)); | |
742 | + } | |
743 | + if(data[14] != 0) { | |
744 | + printf("EDC width %d\n", | |
745 | + data[14] & 0x7F); | |
746 | + if((data[14] & 0x80) != 0) { | |
747 | + printf(" (second bank) %d\n", | |
748 | + 2 * (data[14] & 0x7F)); | |
749 | + } | |
750 | + } | |
751 | + printf("Min clock delay, back-to-back random column addresses %d\n", | |
752 | + data[15]); | |
753 | + printf("Burst length(s) "); | |
754 | + if(data[16] & 0x80) printf(" Page"); | |
755 | + if(data[16] & 0x08) printf(" 8"); | |
756 | + if(data[16] & 0x04) printf(" 4"); | |
757 | + if(data[16] & 0x02) printf(" 2"); | |
758 | + if(data[16] & 0x01) printf(" 1"); | |
759 | + printf("\n"); | |
760 | + printf("Number of banks %d\n", data[17]); | |
761 | + printf("CAS latency(s) "); | |
762 | + if(data[18] & 0x80) printf(" TBD"); | |
763 | + if(data[18] & 0x40) printf(" 7"); | |
764 | + if(data[18] & 0x20) printf(" 6"); | |
765 | + if(data[18] & 0x10) printf(" 5"); | |
766 | + if(data[18] & 0x08) printf(" 4"); | |
767 | + if(data[18] & 0x04) printf(" 3"); | |
768 | + if(data[18] & 0x02) printf(" 2"); | |
769 | + if(data[18] & 0x01) printf(" 1"); | |
770 | + printf("\n"); | |
771 | + printf("CS latency(s) "); | |
772 | + if(data[19] & 0x80) printf(" TBD"); | |
773 | + if(data[19] & 0x40) printf(" 6"); | |
774 | + if(data[19] & 0x20) printf(" 5"); | |
775 | + if(data[19] & 0x10) printf(" 4"); | |
776 | + if(data[19] & 0x08) printf(" 3"); | |
777 | + if(data[19] & 0x04) printf(" 2"); | |
778 | + if(data[19] & 0x02) printf(" 1"); | |
779 | + if(data[19] & 0x01) printf(" 0"); | |
780 | + printf("\n"); | |
781 | + printf("WE latency(s) "); | |
782 | + if(data[20] & 0x80) printf(" TBD"); | |
783 | + if(data[20] & 0x40) printf(" 6"); | |
784 | + if(data[20] & 0x20) printf(" 5"); | |
785 | + if(data[20] & 0x10) printf(" 4"); | |
786 | + if(data[20] & 0x08) printf(" 3"); | |
787 | + if(data[20] & 0x04) printf(" 2"); | |
788 | + if(data[20] & 0x02) printf(" 1"); | |
789 | + if(data[20] & 0x01) printf(" 0"); | |
790 | + printf("\n"); | |
791 | + printf("Module attributes:\n"); | |
792 | + if(!data[21]) printf(" (none)\n"); | |
793 | + if(data[21] & 0x80) printf(" TBD (bit 7)\n"); | |
794 | + if(data[21] & 0x40) printf(" Redundant row address\n"); | |
795 | + if(data[21] & 0x20) printf(" Differential clock input\n"); | |
796 | + if(data[21] & 0x10) printf(" Registerd DQMB inputs\n"); | |
797 | + if(data[21] & 0x08) printf(" Buffered DQMB inputs\n"); | |
798 | + if(data[21] & 0x04) printf(" On-card PLL\n"); | |
799 | + if(data[21] & 0x02) printf(" Registered address/control lines\n"); | |
800 | + if(data[21] & 0x01) printf(" Buffered address/control lines\n"); | |
801 | + printf("Device attributes:\n"); | |
802 | + if(data[22] & 0x80) printf(" TBD (bit 7)\n"); | |
803 | + if(data[22] & 0x40) printf(" TBD (bit 6)\n"); | |
804 | + if(data[22] & 0x20) printf(" Upper Vcc tolerance 5%%\n"); | |
805 | + else printf(" Upper Vcc tolerance 10%%\n"); | |
806 | + if(data[22] & 0x10) printf(" Lower Vcc tolerance 5%%\n"); | |
807 | + else printf(" Lower Vcc tolerance 10%%\n"); | |
808 | + if(data[22] & 0x08) printf(" Supports write1/read burst\n"); | |
809 | + if(data[22] & 0x04) printf(" Supports precharge all\n"); | |
810 | + if(data[22] & 0x02) printf(" Supports auto precharge\n"); | |
811 | + if(data[22] & 0x01) printf(" Supports early RAS# precharge\n"); | |
812 | + printf("SDRAM cycle time (2nd highest CAS latency) %d.%d nS\n", | |
813 | + (data[23] >> 4) & 0x0F, data[23] & 0x0F); | |
814 | + printf("SDRAM access from clock (2nd highest CAS latency) %d.%d nS\n", | |
815 | + (data[24] >> 4) & 0x0F, data[24] & 0x0F); | |
816 | + printf("SDRAM cycle time (3rd highest CAS latency) %d.%d nS\n", | |
817 | + (data[25] >> 4) & 0x0F, data[25] & 0x0F); | |
818 | + printf("SDRAM access from clock (3rd highest CAS latency) %d.%d nS\n", | |
819 | + (data[26] >> 4) & 0x0F, data[26] & 0x0F); | |
820 | + printf("Minimum row precharge %d nS\n", data[27]); | |
821 | + printf("Row active to row active min %d nS\n", data[28]); | |
822 | + printf("RAS to CAS delay min %d nS\n", data[29]); | |
823 | + printf("Minimum RAS pulse width %d nS\n", data[30]); | |
824 | + printf("Density of each row "); | |
825 | + if(data[31] & 0x80) printf(" 512MByte"); | |
826 | + if(data[31] & 0x40) printf(" 256MByte"); | |
827 | + if(data[31] & 0x20) printf(" 128MByte"); | |
828 | + if(data[31] & 0x10) printf(" 64MByte"); | |
829 | + if(data[31] & 0x08) printf(" 32MByte"); | |
830 | + if(data[31] & 0x04) printf(" 16MByte"); | |
831 | + if(data[31] & 0x02) printf(" 8MByte"); | |
832 | + if(data[31] & 0x01) printf(" 4MByte"); | |
833 | + printf("\n"); | |
834 | + printf("Command and Address setup %c%d.%d nS\n", | |
835 | + (data[32] & 0x80) ? '-' : '+', | |
836 | + (data[32] >> 4) & 0x07, data[32] & 0x0F); | |
837 | + printf("Command and Address hold %c%d.%d nS\n", | |
838 | + (data[33] & 0x80) ? '-' : '+', | |
839 | + (data[33] >> 4) & 0x07, data[33] & 0x0F); | |
840 | + printf("Data signal input setup %c%d.%d nS\n", | |
841 | + (data[34] & 0x80) ? '-' : '+', | |
842 | + (data[34] >> 4) & 0x07, data[34] & 0x0F); | |
843 | + printf("Data signal input hold %c%d.%d nS\n", | |
844 | + (data[35] & 0x80) ? '-' : '+', | |
845 | + (data[35] >> 4) & 0x07, data[35] & 0x0F); | |
846 | + printf("Manufacturer's JEDEC ID "); | |
847 | + for(j = 64; j <= 71; j++) | |
848 | + printf("%02X ", data[j]); | |
849 | + printf("\n"); | |
850 | + printf("Manufacturing Location %02X\n", data[72]); | |
851 | + printf("Manufacturer's Part Number "); | |
852 | + for(j = 73; j <= 90; j++) | |
853 | + printf("%02X ", data[j]); | |
854 | + printf("\n"); | |
855 | + printf("Revision Code %02X %02X\n", data[91], data[92]); | |
856 | + printf("Manufacturing Date %02X %02X\n", data[93], data[94]); | |
857 | + printf("Assembly Serial Number "); | |
858 | + for(j = 95; j <= 98; j++) | |
859 | + printf("%02X ", data[j]); | |
860 | + printf("\n"); | |
861 | + printf("Speed rating PC%d\n", | |
862 | + data[126] == 0x66 ? 66 : data[126]); | |
863 | + | |
864 | + return 0; | |
865 | +} | |
866 | +#endif /* CFG_CMD_SDRAM */ | |
867 | + | |
868 | +#endif /* CFG_CMD_I2C */ |
include/configs/smdk2410.h
1 | +/* | |
2 | + * (C) Copyright 2002 | |
3 | + * Sysgo Real-Time Solutions, GmbH <www.elinos.com> | |
4 | + * Marius Groeger <mgroeger@sysgo.de> | |
5 | + * Gary Jennejohn <gj@denx.de> | |
6 | + * David Mueller <d.mueller@elsoft.ch> | |
7 | + * | |
8 | + * Configuation settings for the SAMSUNG SMDK2410 board. | |
9 | + * | |
10 | + * See file CREDITS for list of people who contributed to this | |
11 | + * project. | |
12 | + * | |
13 | + * This program is free software; you can redistribute it and/or | |
14 | + * modify it under the terms of the GNU General Public License as | |
15 | + * published by the Free Software Foundation; either version 2 of | |
16 | + * the License, or (at your option) any later version. | |
17 | + * | |
18 | + * This program is distributed in the hope that it will be useful, | |
19 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
20 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
21 | + * GNU General Public License for more details. | |
22 | + * | |
23 | + * You should have received a copy of the GNU General Public License | |
24 | + * along with this program; if not, write to the Free Software | |
25 | + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, | |
26 | + * MA 02111-1307 USA | |
27 | + */ | |
28 | + | |
29 | +#ifndef __CONFIG_H | |
30 | +#define __CONFIG_H | |
31 | + | |
32 | +/* | |
33 | + * If we are developing, we might want to start armboot from ram | |
34 | + * so we MUST NOT initialize critical regs like mem-timing ... | |
35 | + */ | |
36 | +#define CONFIG_INIT_CRITICAL /* undef for developing */ | |
37 | + | |
38 | +/* | |
39 | + * High Level Configuration Options | |
40 | + * (easy to change) | |
41 | + */ | |
42 | +#define CONFIG_ARM920T 1 /* This is an ARM920T Core */ | |
43 | +#define CONFIG_S3C2410 1 /* in a SAMSUNG S3C2410 SoC */ | |
44 | +#define CONFIG_SMDK2410 1 /* on a SAMSUNG SMDK2410 Board */ | |
45 | + | |
46 | +/* input clock of PLL */ | |
47 | +#define CONFIG_PLL_INPUT_FREQ 12000000/* the SMDK2410 has 12MHz input clock */ | |
48 | + | |
49 | + | |
50 | +#define USE_920T_MMU 1 | |
51 | +#undef CONFIG_USE_IRQ /* we don't need IRQ/FIQ stuff */ | |
52 | + | |
53 | +/* | |
54 | + * Size of malloc() pool | |
55 | + */ | |
56 | +#define CONFIG_MALLOC_SIZE (CFG_ENV_SIZE + 128*1024) | |
57 | + | |
58 | +/* | |
59 | + * Hardware drivers | |
60 | + */ | |
61 | +#define CONFIG_DRIVER_CS8900 1 /* we have a CS8900 on-board */ | |
62 | +#define CS8900_BASE 0x19000300 | |
63 | +#define CS8900_BUS16 1 /* the Linux driver does accesses as shorts */ | |
64 | + | |
65 | +/* | |
66 | + * select serial console configuration | |
67 | + */ | |
68 | +#define CONFIG_SERIAL1 1 /* we use SERIAL 1 on SMDK2410 */ | |
69 | + | |
70 | +/* allow to overwrite serial and ethaddr */ | |
71 | +#define CONFIG_ENV_OVERWRITE | |
72 | + | |
73 | +#define CONFIG_BAUDRATE 115200 | |
74 | + | |
75 | +#ifndef USE_920T_MMU | |
76 | +#define CONFIG_COMMANDS (CONFIG_CMD_DFL & ~CFG_CMD_CACHE) | |
77 | +#else | |
78 | +#define CONFIG_COMMANDS (CONFIG_CMD_DFL) | |
79 | +#endif | |
80 | + | |
81 | +/* this must be included AFTER the definition of CONFIG_COMMANDS (if any) */ | |
82 | +#include <cmd_confdefs.h> | |
83 | + | |
84 | +#define CONFIG_BOOTDELAY 3 | |
85 | +/*#define CONFIG_BOOTARGS "root=ramfs devfs=mount console=ttySA0,9600" */ | |
86 | +/*#define CONFIG_ETHADDR 08:00:3e:26:0a:5b */ | |
87 | +#define CONFIG_NETMASK 255.255.255.0 | |
88 | +#define CONFIG_IPADDR 10.0.0.110 | |
89 | +#define CONFIG_SERVERIP 10.0.0.1 | |
90 | +/*#define CONFIG_BOOTFILE "elinos-lart" */ | |
91 | +/*#define CONFIG_BOOTCOMMAND "tftp; bootm" */ | |
92 | + | |
93 | +#if (CONFIG_COMMANDS & CFG_CMD_KGDB) | |
94 | +#define CONFIG_KGDB_BAUDRATE 115200 /* speed to run kgdb serial port */ | |
95 | +/* what's this ? it's not used anywhere */ | |
96 | +#define CONFIG_KGDB_SER_INDEX 1 /* which serial port to use */ | |
97 | +#endif | |
98 | + | |
99 | +/* | |
100 | + * Miscellaneous configurable options | |
101 | + */ | |
102 | +#define CFG_LONGHELP /* undef to save memory */ | |
103 | +#define CFG_PROMPT "SMDK2410 # " /* Monitor Command Prompt */ | |
104 | +#define CFG_CBSIZE 256 /* Console I/O Buffer Size */ | |
105 | +#define CFG_PBSIZE (CFG_CBSIZE+sizeof(CFG_PROMPT)+16) /* Print Buffer Size */ | |
106 | +#define CFG_MAXARGS 16 /* max number of command args */ | |
107 | +#define CFG_BARGSIZE CFG_CBSIZE /* Boot Argument Buffer Size */ | |
108 | + | |
109 | +#define CFG_MEMTEST_START 0x30000000 /* memtest works on */ | |
110 | +#define CFG_MEMTEST_END 0x33F00000 /* 63 MB in DRAM */ | |
111 | + | |
112 | +#undef CFG_CLKS_IN_HZ /* everything, incl board info, in Hz */ | |
113 | + | |
114 | +#define CFG_LOAD_ADDR 0x33000000 /* default load address */ | |
115 | + | |
116 | +/* the PWM TImer 4 uses a counter of 15625 for 10 ms, so we need */ | |
117 | +/* it to wrap 100 times (total 1562500) to get 1 sec. */ | |
118 | +#define CFG_HZ 1562500 | |
119 | + | |
120 | +/* valid baudrates */ | |
121 | +#define CFG_BAUDRATE_TABLE { 9600, 19200, 38400, 57600, 115200 } | |
122 | + | |
123 | +/*----------------------------------------------------------------------- | |
124 | + * Stack sizes | |
125 | + * | |
126 | + * The stack sizes are set up in start.S using the settings below | |
127 | + */ | |
128 | +#define CONFIG_STACKSIZE (128*1024) /* regular stack */ | |
129 | +#ifdef CONFIG_USE_IRQ | |
130 | +#define CONFIG_STACKSIZE_IRQ (4*1024) /* IRQ stack */ | |
131 | +#define CONFIG_STACKSIZE_FIQ (4*1024) /* FIQ stack */ | |
132 | +#endif | |
133 | + | |
134 | +/*----------------------------------------------------------------------- | |
135 | + * Physical Memory Map | |
136 | + */ | |
137 | +#define CONFIG_NR_DRAM_BANKS 1 /* we have 1 bank of DRAM */ | |
138 | +#define PHYS_SDRAM_1 0x30000000 /* SDRAM Bank #1 */ | |
139 | +#define PHYS_SDRAM_1_SIZE 0x04000000 /* 64 MB */ | |
140 | + | |
141 | +#define PHYS_FLASH_1 0x00000000 /* Flash Bank #1 */ | |
142 | + | |
143 | +#define CFG_FLASH_BASE PHYS_FLASH_1 | |
144 | + | |
145 | +/*----------------------------------------------------------------------- | |
146 | + * FLASH and environment organization | |
147 | + */ | |
148 | + | |
149 | +#define CONFIG_AMD_LV400 1 /* uncomment this if you have a LV400 flash */ | |
150 | +#if 0 | |
151 | +#define CONFIG_AMD_LV800 1 /* uncomment this if you have a LV800 flash */ | |
152 | +#endif | |
153 | + | |
154 | +#define CFG_MAX_FLASH_BANKS 1 /* max number of memory banks */ | |
155 | +#ifdef CONFIG_AMD_LV800 | |
156 | +#define PHYS_FLASH_SIZE 0x00100000 /* 1MB */ | |
157 | +#define CFG_MAX_FLASH_SECT (19) /* max number of sectors on one chip */ | |
158 | +#define CFG_ENV_ADDR (CFG_FLASH_BASE + 0x0F0000) /* addr of environment */ | |
159 | +#endif | |
160 | +#ifdef CONFIG_AMD_LV400 | |
161 | +#define PHYS_FLASH_SIZE 0x00080000 /* 512KB */ | |
162 | +#define CFG_MAX_FLASH_SECT (11) /* max number of sectors on one chip */ | |
163 | +#define CFG_ENV_ADDR (CFG_FLASH_BASE + 0x070000) /* addr of environment */ | |
164 | +#endif | |
165 | + | |
166 | +/* timeout values are in ticks */ | |
167 | +#define CFG_FLASH_ERASE_TOUT (5*CFG_HZ) /* Timeout for Flash Erase */ | |
168 | +#define CFG_FLASH_WRITE_TOUT (5*CFG_HZ) /* Timeout for Flash Write */ | |
169 | + | |
170 | +#define CFG_ENV_IS_IN_FLASH 1 | |
171 | +#define CFG_ENV_SIZE 0x10000 /* Total Size of Environment Sector */ | |
172 | + | |
173 | +#endif /* __CONFIG_H */ |