]>
Commit | Line | Data |
---|---|---|
71f95118 WD |
1 | /* |
2 | * (C) Copyright 2003 | |
3 | * Kyle Harris, Nexus Technologies, Inc. [email protected] | |
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 | #include <config.h> | |
25 | #include <common.h> | |
26 | #include <mmc.h> | |
27 | #include <asm/errno.h> | |
28 | #include <asm/arch/hardware.h> | |
7205e407 | 29 | #include <part.h> |
71f95118 WD |
30 | |
31 | #ifdef CONFIG_MMC | |
32 | ||
33 | extern int | |
7205e407 WD |
34 | fat_register_device(block_dev_desc_t *dev_desc, int part_no); |
35 | ||
36 | static block_dev_desc_t mmc_dev; | |
37 | ||
38 | block_dev_desc_t * mmc_get_dev(int dev) | |
39 | { | |
40 | return ((block_dev_desc_t *)&mmc_dev); | |
41 | } | |
71f95118 | 42 | |
8bde7f77 | 43 | /* |
71f95118 WD |
44 | * FIXME needs to read cid and csd info to determine block size |
45 | * and other parameters | |
46 | */ | |
47 | static uchar mmc_buf[MMC_BLOCK_SIZE]; | |
48 | static mmc_csd_t mmc_csd; | |
49 | static int mmc_ready = 0; | |
50 | ||
51 | ||
8bde7f77 | 52 | static uchar * |
71f95118 WD |
53 | /****************************************************/ |
54 | mmc_cmd(ushort cmd, ushort argh, ushort argl, ushort cmdat) | |
55 | /****************************************************/ | |
56 | { | |
57 | static uchar resp[20]; | |
58 | ulong status; | |
59 | int words, i; | |
60 | ||
61 | debug("mmc_cmd %x %x %x %x\n", cmd, argh, argl, cmdat); | |
62 | MMC_STRPCL = MMC_STRPCL_STOP_CLK; | |
63 | MMC_I_MASK = ~MMC_I_MASK_CLK_IS_OFF; | |
64 | while (!(MMC_I_REG & MMC_I_REG_CLK_IS_OFF)); | |
8bde7f77 | 65 | MMC_CMD = cmd; |
71f95118 WD |
66 | MMC_ARGH = argh; |
67 | MMC_ARGL = argl; | |
68 | MMC_CMDAT = cmdat; | |
69 | MMC_I_MASK = ~MMC_I_MASK_END_CMD_RES; | |
70 | MMC_STRPCL = MMC_STRPCL_START_CLK; | |
71 | while (!(MMC_I_REG & MMC_I_REG_END_CMD_RES)); | |
72 | ||
73 | status = MMC_STAT; | |
74 | debug("MMC status %x\n", status); | |
75 | if (status & MMC_STAT_TIME_OUT_RESPONSE) | |
76 | { | |
77 | return 0; | |
78 | } | |
79 | ||
80 | switch (cmdat & 0x3) | |
81 | { | |
82 | case MMC_CMDAT_R1: | |
83 | case MMC_CMDAT_R3: | |
8bde7f77 | 84 | words = 3; |
71f95118 WD |
85 | break; |
86 | ||
87 | case MMC_CMDAT_R2: | |
8bde7f77 | 88 | words = 8; |
71f95118 WD |
89 | break; |
90 | ||
91 | default: | |
92 | return 0; | |
93 | } | |
94 | for (i = words-1; i >= 0; i--) | |
95 | { | |
96 | ulong res_fifo = MMC_RES; | |
97 | int offset = i << 1; | |
98 | ||
99 | resp[offset] = ((uchar *)&res_fifo)[0]; | |
100 | resp[offset+1] = ((uchar *)&res_fifo)[1]; | |
101 | } | |
102 | #ifdef MMC_DEBUG | |
103 | for (i=0; i<words*2; i += 2) | |
104 | { | |
105 | printf("MMC resp[%d] = %02x\n", i, resp[i]); | |
106 | printf("MMC resp[%d] = %02x\n", i+1, resp[i+1]); | |
107 | } | |
108 | #endif | |
109 | return resp; | |
110 | } | |
111 | ||
112 | int | |
113 | /****************************************************/ | |
114 | mmc_block_read(uchar *dst, ulong src, ulong len) | |
115 | /****************************************************/ | |
116 | { | |
117 | uchar *resp; | |
118 | ushort argh, argl; | |
119 | ulong status; | |
120 | ||
121 | if (len == 0) | |
122 | { | |
123 | return 0; | |
124 | } | |
125 | ||
126 | debug("mmc_block_rd dst %lx src %lx len %d\n", (ulong)dst, src, len); | |
127 | ||
128 | argh = len >> 16; | |
129 | argl = len & 0xffff; | |
130 | ||
131 | /* set block len */ | |
132 | resp = mmc_cmd(MMC_CMD_SET_BLOCKLEN, argh, argl, MMC_CMDAT_R1); | |
133 | ||
134 | /* send read command */ | |
135 | argh = src >> 16; | |
136 | argl = src & 0xffff; | |
137 | MMC_STRPCL = MMC_STRPCL_STOP_CLK; | |
138 | MMC_RDTO = 0xffff; | |
139 | MMC_NOB = 1; | |
140 | MMC_BLKLEN = len; | |
8bde7f77 | 141 | resp = mmc_cmd(MMC_CMD_READ_BLOCK, argh, argl, |
71f95118 WD |
142 | MMC_CMDAT_R1|MMC_CMDAT_READ|MMC_CMDAT_BLOCK|MMC_CMDAT_DATA_EN); |
143 | ||
8bde7f77 | 144 | |
71f95118 WD |
145 | MMC_I_MASK = ~MMC_I_MASK_RXFIFO_RD_REQ; |
146 | while (len) | |
147 | { | |
148 | if (MMC_I_REG & MMC_I_REG_RXFIFO_RD_REQ) | |
149 | { | |
150 | *dst++ = MMC_RXFIFO; | |
151 | len--; | |
152 | } | |
153 | status = MMC_STAT; | |
154 | if (status & MMC_STAT_ERRORS) | |
155 | { | |
156 | printf("MMC_STAT error %lx\n", status); | |
157 | return -1; | |
158 | } | |
159 | } | |
160 | MMC_I_MASK = ~MMC_I_MASK_DATA_TRAN_DONE; | |
161 | while (!(MMC_I_REG & MMC_I_REG_DATA_TRAN_DONE)); | |
162 | status = MMC_STAT; | |
163 | if (status & MMC_STAT_ERRORS) | |
164 | { | |
165 | printf("MMC_STAT error %lx\n", status); | |
166 | return -1; | |
167 | } | |
168 | return 0; | |
169 | } | |
170 | ||
171 | int | |
172 | /****************************************************/ | |
173 | mmc_block_write(ulong dst, uchar *src, int len) | |
174 | /****************************************************/ | |
175 | { | |
176 | uchar *resp; | |
177 | ushort argh, argl; | |
178 | ulong status; | |
179 | ||
180 | if (len == 0) | |
181 | { | |
182 | return 0; | |
183 | } | |
184 | ||
185 | debug("mmc_block_wr dst %lx src %lx len %d\n", dst, (ulong)src, len); | |
186 | ||
187 | argh = len >> 16; | |
188 | argl = len & 0xffff; | |
189 | ||
190 | /* set block len */ | |
191 | resp = mmc_cmd(MMC_CMD_SET_BLOCKLEN, argh, argl, MMC_CMDAT_R1); | |
192 | ||
193 | /* send write command */ | |
194 | argh = dst >> 16; | |
195 | argl = dst & 0xffff; | |
196 | MMC_STRPCL = MMC_STRPCL_STOP_CLK; | |
197 | MMC_NOB = 1; | |
198 | MMC_BLKLEN = len; | |
8bde7f77 | 199 | resp = mmc_cmd(MMC_CMD_WRITE_BLOCK, argh, argl, |
71f95118 | 200 | MMC_CMDAT_R1|MMC_CMDAT_WRITE|MMC_CMDAT_BLOCK|MMC_CMDAT_DATA_EN); |
8bde7f77 | 201 | |
71f95118 WD |
202 | MMC_I_MASK = ~MMC_I_MASK_TXFIFO_WR_REQ; |
203 | while (len) | |
204 | { | |
205 | if (MMC_I_REG & MMC_I_REG_TXFIFO_WR_REQ) | |
206 | { | |
207 | int i, bytes = min(32,len); | |
208 | ||
209 | for (i=0; i<bytes; i++) | |
210 | { | |
211 | MMC_TXFIFO = *src++; | |
212 | } | |
213 | if (bytes < 32) | |
214 | { | |
215 | MMC_PRTBUF = MMC_PRTBUF_BUF_PART_FULL; | |
216 | } | |
217 | len -= bytes; | |
218 | } | |
219 | status = MMC_STAT; | |
220 | if (status & MMC_STAT_ERRORS) | |
221 | { | |
222 | printf("MMC_STAT error %lx\n", status); | |
223 | return -1; | |
224 | } | |
225 | } | |
226 | MMC_I_MASK = ~MMC_I_MASK_DATA_TRAN_DONE; | |
227 | while (!(MMC_I_REG & MMC_I_REG_DATA_TRAN_DONE)); | |
228 | MMC_I_MASK = ~MMC_I_MASK_PRG_DONE; | |
229 | while (!(MMC_I_REG & MMC_I_REG_PRG_DONE)); | |
230 | status = MMC_STAT; | |
231 | if (status & MMC_STAT_ERRORS) | |
232 | { | |
233 | printf("MMC_STAT error %lx\n", status); | |
234 | return -1; | |
235 | } | |
236 | return 0; | |
237 | } | |
238 | ||
239 | ||
240 | int | |
241 | /****************************************************/ | |
242 | mmc_read(ulong src, uchar *dst, int size) | |
243 | /****************************************************/ | |
244 | { | |
245 | ulong end, part_start, part_end, part_len, aligned_start, aligned_end; | |
246 | ulong mmc_block_size, mmc_block_address; | |
247 | ||
248 | if (size == 0) | |
249 | { | |
250 | return 0; | |
251 | } | |
252 | ||
253 | if (!mmc_ready) | |
254 | { | |
255 | printf("Please initial the MMC first\n"); | |
256 | return -1; | |
257 | } | |
258 | ||
259 | mmc_block_size = MMC_BLOCK_SIZE; | |
260 | mmc_block_address = ~(mmc_block_size - 1); | |
261 | ||
262 | src -= CFG_MMC_BASE; | |
263 | end = src + size; | |
264 | part_start = ~mmc_block_address & src; | |
265 | part_end = ~mmc_block_address & end; | |
266 | aligned_start = mmc_block_address & src; | |
267 | aligned_end = mmc_block_address & end; | |
268 | ||
269 | /* all block aligned accesses */ | |
270 | debug("src %lx dst %lx end %lx pstart %lx pend %lx astart %lx aend %lx\n", | |
271 | src, (ulong)dst, end, part_start, part_end, aligned_start, aligned_end); | |
272 | if (part_start) | |
273 | { | |
274 | part_len = mmc_block_size - part_start; | |
275 | debug("ps src %lx dst %lx end %lx pstart %lx pend %lx astart %lx aend %lx\n", | |
276 | src, (ulong)dst, end, part_start, part_end, aligned_start, aligned_end); | |
277 | if ((mmc_block_read(mmc_buf, aligned_start, mmc_block_size)) < 0) | |
278 | { | |
279 | return -1; | |
280 | } | |
281 | memcpy(dst, mmc_buf+part_start, part_len); | |
282 | dst += part_len; | |
283 | src += part_len; | |
284 | } | |
285 | debug("src %lx dst %lx end %lx pstart %lx pend %lx astart %lx aend %lx\n", | |
286 | src, (ulong)dst, end, part_start, part_end, aligned_start, aligned_end); | |
287 | for (; src < aligned_end; src += mmc_block_size, dst += mmc_block_size) | |
288 | { | |
289 | debug("al src %lx dst %lx end %lx pstart %lx pend %lx astart %lx aend %lx\n", | |
290 | src, (ulong)dst, end, part_start, part_end, aligned_start, aligned_end); | |
291 | if ((mmc_block_read((uchar *)(dst), src, mmc_block_size)) < 0) | |
292 | { | |
293 | return -1; | |
294 | } | |
295 | } | |
296 | debug("src %lx dst %lx end %lx pstart %lx pend %lx astart %lx aend %lx\n", | |
297 | src, (ulong)dst, end, part_start, part_end, aligned_start, aligned_end); | |
298 | if (part_end && src < end) | |
299 | { | |
300 | debug("pe src %lx dst %lx end %lx pstart %lx pend %lx astart %lx aend %lx\n", | |
301 | src, (ulong)dst, end, part_start, part_end, aligned_start, aligned_end); | |
302 | if ((mmc_block_read(mmc_buf, aligned_end, mmc_block_size)) < 0) | |
303 | { | |
304 | return -1; | |
305 | } | |
306 | memcpy(dst, mmc_buf, part_end); | |
307 | } | |
308 | return 0; | |
309 | } | |
310 | ||
311 | int | |
312 | /****************************************************/ | |
313 | mmc_write(uchar *src, ulong dst, int size) | |
314 | /****************************************************/ | |
315 | { | |
316 | ulong end, part_start, part_end, part_len, aligned_start, aligned_end; | |
317 | ulong mmc_block_size, mmc_block_address; | |
318 | ||
319 | if (size == 0) | |
320 | { | |
321 | return 0; | |
322 | } | |
323 | ||
324 | if (!mmc_ready) | |
325 | { | |
326 | printf("Please initial the MMC first\n"); | |
327 | return -1; | |
328 | } | |
329 | ||
330 | mmc_block_size = MMC_BLOCK_SIZE; | |
331 | mmc_block_address = ~(mmc_block_size - 1); | |
332 | ||
333 | dst -= CFG_MMC_BASE; | |
334 | end = dst + size; | |
335 | part_start = ~mmc_block_address & dst; | |
336 | part_end = ~mmc_block_address & end; | |
337 | aligned_start = mmc_block_address & dst; | |
338 | aligned_end = mmc_block_address & end; | |
339 | ||
340 | /* all block aligned accesses */ | |
341 | debug("src %lx dst %lx end %lx pstart %lx pend %lx astart %lx aend %lx\n", | |
342 | src, (ulong)dst, end, part_start, part_end, aligned_start, aligned_end); | |
343 | if (part_start) | |
344 | { | |
345 | part_len = mmc_block_size - part_start; | |
346 | debug("ps src %lx dst %lx end %lx pstart %lx pend %lx astart %lx aend %lx\n", | |
347 | (ulong)src, dst, end, part_start, part_end, aligned_start, aligned_end); | |
348 | if ((mmc_block_read(mmc_buf, aligned_start, mmc_block_size)) < 0) | |
349 | { | |
350 | return -1; | |
351 | } | |
352 | memcpy(mmc_buf+part_start, src, part_len); | |
353 | if ((mmc_block_write(aligned_start, mmc_buf, mmc_block_size)) < 0) | |
354 | { | |
355 | return -1; | |
356 | } | |
357 | dst += part_len; | |
358 | src += part_len; | |
359 | } | |
360 | debug("src %lx dst %lx end %lx pstart %lx pend %lx astart %lx aend %lx\n", | |
361 | src, (ulong)dst, end, part_start, part_end, aligned_start, aligned_end); | |
362 | for (; dst < aligned_end; src += mmc_block_size, dst += mmc_block_size) | |
363 | { | |
364 | debug("al src %lx dst %lx end %lx pstart %lx pend %lx astart %lx aend %lx\n", | |
365 | src, (ulong)dst, end, part_start, part_end, aligned_start, aligned_end); | |
366 | if ((mmc_block_write(dst, (uchar *)src, mmc_block_size)) < 0) | |
367 | { | |
368 | return -1; | |
369 | } | |
370 | } | |
371 | debug("src %lx dst %lx end %lx pstart %lx pend %lx astart %lx aend %lx\n", | |
372 | src, (ulong)dst, end, part_start, part_end, aligned_start, aligned_end); | |
373 | if (part_end && dst < end) | |
374 | { | |
375 | debug("pe src %lx dst %lx end %lx pstart %lx pend %lx astart %lx aend %lx\n", | |
376 | src, (ulong)dst, end, part_start, part_end, aligned_start, aligned_end); | |
377 | if ((mmc_block_read(mmc_buf, aligned_end, mmc_block_size)) < 0) | |
378 | { | |
379 | return -1; | |
380 | } | |
381 | memcpy(mmc_buf, src, part_end); | |
382 | if ((mmc_block_write(aligned_end, mmc_buf, mmc_block_size)) < 0) | |
383 | { | |
384 | return -1; | |
385 | } | |
386 | } | |
387 | return 0; | |
388 | } | |
389 | ||
7205e407 | 390 | ulong |
71f95118 | 391 | /****************************************************/ |
7205e407 | 392 | mmc_bread(int dev_num, ulong blknr, ulong blkcnt, ulong *dst) |
71f95118 WD |
393 | /****************************************************/ |
394 | { | |
395 | int mmc_block_size = MMC_BLOCK_SIZE; | |
396 | ulong src = blknr * mmc_block_size + CFG_MMC_BASE; | |
397 | ||
398 | mmc_read(src, (uchar *)dst, blkcnt*mmc_block_size); | |
399 | return blkcnt; | |
400 | } | |
401 | ||
402 | int | |
403 | /****************************************************/ | |
404 | mmc_init(int verbose) | |
405 | /****************************************************/ | |
406 | { | |
407 | int retries, rc = -ENODEV; | |
408 | uchar *resp; | |
409 | ||
410 | #ifdef CONFIG_LUBBOCK | |
411 | set_GPIO_mode( GPIO6_MMCCLK_MD ); | |
412 | set_GPIO_mode( GPIO8_MMCCS0_MD ); | |
413 | #endif | |
414 | CKEN |= CKEN12_MMC; /* enable MMC unit clock */ | |
415 | ||
416 | mmc_csd.c_size = 0; | |
417 | ||
418 | MMC_CLKRT = MMC_CLKRT_0_3125MHZ; | |
8bde7f77 | 419 | MMC_RESTO = MMC_RES_TO_MAX; |
71f95118 WD |
420 | MMC_SPI = MMC_SPI_DISABLE; |
421 | ||
422 | /* reset */ | |
423 | retries = 10; | |
424 | resp = mmc_cmd(0, 0, 0, 0); | |
425 | resp = mmc_cmd(1, 0x00ff, 0xc000, MMC_CMDAT_INIT|MMC_CMDAT_BUSY|MMC_CMDAT_R3); | |
426 | while (retries-- && resp && !(resp[4] & 0x80)) | |
427 | { | |
428 | debug("resp %x %x\n", resp[0], resp[1]); | |
429 | udelay(50); | |
430 | resp = mmc_cmd(1, 0x00ff, 0xff00, MMC_CMDAT_BUSY|MMC_CMDAT_R3); | |
431 | } | |
432 | ||
433 | /* try to get card id */ | |
434 | resp = mmc_cmd(2, 0, 0, MMC_CMDAT_R2); | |
435 | if (resp) | |
436 | { | |
437 | /* TODO configure mmc driver depending on card attributes */ | |
438 | mmc_cid_t *cid = (mmc_cid_t *)resp; | |
439 | if (verbose) | |
440 | { | |
441 | printf("MMC found. Card desciption is:\n"); | |
8bde7f77 | 442 | printf("Manufacturer ID = %02x%02x%02x\n", |
71f95118 WD |
443 | cid->id[0], cid->id[1], cid->id[2]); |
444 | printf("HW/FW Revision = %x %x\n",cid->hwrev, cid->fwrev); | |
445 | cid->hwrev = cid->fwrev = 0; /* null terminate string */ | |
446 | printf("Product Name = %s\n",cid->name); | |
8bde7f77 | 447 | printf("Serial Number = %02x%02x%02x\n", |
71f95118 WD |
448 | cid->sn[0], cid->sn[1], cid->sn[2]); |
449 | printf("Month = %d\n",cid->month); | |
450 | printf("Year = %d\n",1997 + cid->year); | |
451 | } | |
7205e407 WD |
452 | /* fill in device description */ |
453 | mmc_dev.if_type = IF_TYPE_MMC; | |
454 | mmc_dev.dev = 0; | |
455 | mmc_dev.lun = 0; | |
456 | mmc_dev.type = 0; | |
457 | /* FIXME fill in the correct size (is set to 32MByte) */ | |
458 | mmc_dev.blksz = 512; | |
459 | mmc_dev.lba = 0x10000; | |
460 | sprintf(mmc_dev.vendor,"Man %02x%02x%02x Snr %02x%02x%02x", | |
461 | cid->id[0], cid->id[1], cid->id[2], | |
462 | cid->sn[0], cid->sn[1], cid->sn[2]); | |
463 | sprintf(mmc_dev.product,"%s",cid->name); | |
464 | sprintf(mmc_dev.revision,"%x %x",cid->hwrev, cid->fwrev); | |
465 | mmc_dev.removable = 0; | |
466 | mmc_dev.block_read = mmc_bread; | |
71f95118 WD |
467 | |
468 | /* MMC exists, get CSD too */ | |
469 | resp = mmc_cmd(MMC_CMD_SET_RCA, MMC_DEFAULT_RCA, 0, MMC_CMDAT_R1); | |
470 | resp = mmc_cmd(MMC_CMD_SEND_CSD, MMC_DEFAULT_RCA, 0, MMC_CMDAT_R2); | |
471 | if (resp) | |
472 | { | |
473 | mmc_csd_t *csd = (mmc_csd_t *)resp; | |
474 | memcpy(&mmc_csd, csd, sizeof(csd)); | |
475 | rc = 0; | |
476 | mmc_ready = 1; | |
477 | /* FIXME add verbose printout for csd */ | |
478 | } | |
479 | } | |
480 | ||
481 | MMC_CLKRT = 0; /* 20 MHz */ | |
482 | resp = mmc_cmd(7, MMC_DEFAULT_RCA, 0, MMC_CMDAT_R1); | |
483 | ||
42d1f039 | 484 | fat_register_device(&mmc_dev,1); /* partitions start counting with 1 */ |
71f95118 WD |
485 | |
486 | return rc; | |
487 | } | |
488 | ||
489 | int | |
490 | mmc_ident(block_dev_desc_t *dev) | |
491 | { | |
492 | return 0; | |
493 | } | |
494 | ||
495 | int | |
496 | mmc2info(ulong addr) | |
497 | { | |
498 | /* FIXME hard codes to 32 MB device */ | |
499 | if (addr >= CFG_MMC_BASE && addr < CFG_MMC_BASE + 0x02000000) | |
500 | { | |
501 | return 1; | |
502 | } | |
503 | return 0; | |
504 | } | |
505 | ||
506 | #endif |