diff options
author | Sascha Hauer <s.hauer@pengutronix.de> | 2009-06-04 11:12:26 -0400 |
---|---|---|
committer | Sascha Hauer <s.hauer@pengutronix.de> | 2009-11-12 02:32:32 -0500 |
commit | f8f9608d9b7a5b3349605284a4f3876e898413f1 (patch) | |
tree | 30f2525e4aa2a0822486d17bad356b90423d7478 /drivers/mtd/nand/mxc_nand.c | |
parent | d970a0730ba5598b711d42bac287a17db5b6268a (diff) |
mxc nand: use buffers
The NAND controller has some limitations how to access the
internal buffers. It only allows 32 bit accesses. The driver
used to work around this by having special alignment aware
copy routines.
We now copy the whole page to a buffer in memory and let the
access functions use this buffer. This simplifies the driver.
A bonnie++ test showed that this has no negative performance
impact on the driver.
Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
Diffstat (limited to 'drivers/mtd/nand/mxc_nand.c')
-rw-r--r-- | drivers/mtd/nand/mxc_nand.c | 310 |
1 files changed, 77 insertions, 233 deletions
diff --git a/drivers/mtd/nand/mxc_nand.c b/drivers/mtd/nand/mxc_nand.c index 8fcb33da8efc..03d20086170b 100644 --- a/drivers/mtd/nand/mxc_nand.c +++ b/drivers/mtd/nand/mxc_nand.c | |||
@@ -107,15 +107,17 @@ struct mxc_nand_host { | |||
107 | struct device *dev; | 107 | struct device *dev; |
108 | 108 | ||
109 | void __iomem *regs; | 109 | void __iomem *regs; |
110 | int spare_only; | ||
111 | int status_request; | 110 | int status_request; |
112 | int pagesize_2k; | 111 | int pagesize_2k; |
113 | uint16_t col_addr; | ||
114 | struct clk *clk; | 112 | struct clk *clk; |
115 | int clk_act; | 113 | int clk_act; |
116 | int irq; | 114 | int irq; |
117 | 115 | ||
118 | wait_queue_head_t irq_waitq; | 116 | wait_queue_head_t irq_waitq; |
117 | |||
118 | uint8_t *data_buf; | ||
119 | unsigned int buf_start; | ||
120 | int spare_len; | ||
119 | }; | 121 | }; |
120 | 122 | ||
121 | /* Define delays in microsec for NAND device operations */ | 123 | /* Define delays in microsec for NAND device operations */ |
@@ -227,23 +229,11 @@ static void send_addr(struct mxc_nand_host *host, uint16_t addr, int islast) | |||
227 | } | 229 | } |
228 | 230 | ||
229 | static void send_page(struct mxc_nand_host *host, uint8_t buf_id, | 231 | static void send_page(struct mxc_nand_host *host, uint8_t buf_id, |
230 | int spare_only, unsigned int ops) | 232 | unsigned int ops) |
231 | { | 233 | { |
232 | DEBUG(MTD_DEBUG_LEVEL3, "send_page (%d)\n", spare_only); | ||
233 | |||
234 | /* NANDFC buffer 0 is used for page read/write */ | 234 | /* NANDFC buffer 0 is used for page read/write */ |
235 | writew(buf_id, host->regs + NFC_BUF_ADDR); | 235 | writew(buf_id, host->regs + NFC_BUF_ADDR); |
236 | 236 | ||
237 | /* Configure spare or page+spare access */ | ||
238 | if (!host->pagesize_2k) { | ||
239 | uint16_t config1 = readw(host->regs + NFC_CONFIG1); | ||
240 | if (spare_only) | ||
241 | config1 |= NFC_SP_EN; | ||
242 | else | ||
243 | config1 &= ~(NFC_SP_EN); | ||
244 | writew(config1, host->regs + NFC_CONFIG1); | ||
245 | } | ||
246 | |||
247 | writew(ops, host->regs + NFC_CONFIG2); | 237 | writew(ops, host->regs + NFC_CONFIG2); |
248 | 238 | ||
249 | /* Wait for operation to complete */ | 239 | /* Wait for operation to complete */ |
@@ -278,6 +268,7 @@ static void send_read_id(struct mxc_nand_host *host) | |||
278 | writeb(readb(main_buf + 8), main_buf + 4); | 268 | writeb(readb(main_buf + 8), main_buf + 4); |
279 | writeb(readb(main_buf + 10), main_buf + 5); | 269 | writeb(readb(main_buf + 10), main_buf + 5); |
280 | } | 270 | } |
271 | memcpy(host->data_buf, host->regs + MAIN_AREA0, 16); | ||
281 | } | 272 | } |
282 | 273 | ||
283 | /* This function requests the NANDFC to perform a read of the | 274 | /* This function requests the NANDFC to perform a read of the |
@@ -363,32 +354,14 @@ static u_char mxc_nand_read_byte(struct mtd_info *mtd) | |||
363 | { | 354 | { |
364 | struct nand_chip *nand_chip = mtd->priv; | 355 | struct nand_chip *nand_chip = mtd->priv; |
365 | struct mxc_nand_host *host = nand_chip->priv; | 356 | struct mxc_nand_host *host = nand_chip->priv; |
366 | uint8_t ret = 0; | 357 | uint8_t ret; |
367 | uint16_t col, rd_word; | ||
368 | uint16_t __iomem *main_buf = host->regs + MAIN_AREA0; | ||
369 | uint16_t __iomem *spare_buf = host->regs + SPARE_AREA0; | ||
370 | 358 | ||
371 | /* Check for status request */ | 359 | /* Check for status request */ |
372 | if (host->status_request) | 360 | if (host->status_request) |
373 | return get_dev_status(host) & 0xFF; | 361 | return get_dev_status(host) & 0xFF; |
374 | 362 | ||
375 | /* Get column for 16-bit access */ | 363 | ret = *(uint8_t *)(host->data_buf + host->buf_start); |
376 | col = host->col_addr >> 1; | 364 | host->buf_start++; |
377 | |||
378 | /* If we are accessing the spare region */ | ||
379 | if (host->spare_only) | ||
380 | rd_word = readw(&spare_buf[col]); | ||
381 | else | ||
382 | rd_word = readw(&main_buf[col]); | ||
383 | |||
384 | /* Pick upper/lower byte of word from RAM buffer */ | ||
385 | if (host->col_addr & 0x1) | ||
386 | ret = (rd_word >> 8) & 0xFF; | ||
387 | else | ||
388 | ret = rd_word & 0xFF; | ||
389 | |||
390 | /* Update saved column address */ | ||
391 | host->col_addr++; | ||
392 | 365 | ||
393 | return ret; | 366 | return ret; |
394 | } | 367 | } |
@@ -397,33 +370,10 @@ static uint16_t mxc_nand_read_word(struct mtd_info *mtd) | |||
397 | { | 370 | { |
398 | struct nand_chip *nand_chip = mtd->priv; | 371 | struct nand_chip *nand_chip = mtd->priv; |
399 | struct mxc_nand_host *host = nand_chip->priv; | 372 | struct mxc_nand_host *host = nand_chip->priv; |
400 | uint16_t col, rd_word, ret; | 373 | uint16_t ret; |
401 | uint16_t __iomem *p; | ||
402 | 374 | ||
403 | DEBUG(MTD_DEBUG_LEVEL3, | 375 | ret = *(uint16_t *)(host->data_buf + host->buf_start); |
404 | "mxc_nand_read_word(col = %d)\n", host->col_addr); | 376 | host->buf_start += 2; |
405 | |||
406 | col = host->col_addr; | ||
407 | /* Adjust saved column address */ | ||
408 | if (col < mtd->writesize && host->spare_only) | ||
409 | col += mtd->writesize; | ||
410 | |||
411 | if (col < mtd->writesize) | ||
412 | p = (host->regs + MAIN_AREA0) + (col >> 1); | ||
413 | else | ||
414 | p = (host->regs + SPARE_AREA0) + ((col - mtd->writesize) >> 1); | ||
415 | |||
416 | if (col & 1) { | ||
417 | rd_word = readw(p); | ||
418 | ret = (rd_word >> 8) & 0xff; | ||
419 | rd_word = readw(&p[1]); | ||
420 | ret |= (rd_word << 8) & 0xff00; | ||
421 | |||
422 | } else | ||
423 | ret = readw(p); | ||
424 | |||
425 | /* Update saved column address */ | ||
426 | host->col_addr = col + 2; | ||
427 | 377 | ||
428 | return ret; | 378 | return ret; |
429 | } | 379 | } |
@@ -436,94 +386,14 @@ static void mxc_nand_write_buf(struct mtd_info *mtd, | |||
436 | { | 386 | { |
437 | struct nand_chip *nand_chip = mtd->priv; | 387 | struct nand_chip *nand_chip = mtd->priv; |
438 | struct mxc_nand_host *host = nand_chip->priv; | 388 | struct mxc_nand_host *host = nand_chip->priv; |
439 | int n, col, i = 0; | 389 | u16 col = host->buf_start; |
440 | 390 | int n = mtd->oobsize + mtd->writesize - col; | |
441 | DEBUG(MTD_DEBUG_LEVEL3, | ||
442 | "mxc_nand_write_buf(col = %d, len = %d)\n", host->col_addr, | ||
443 | len); | ||
444 | |||
445 | col = host->col_addr; | ||
446 | |||
447 | /* Adjust saved column address */ | ||
448 | if (col < mtd->writesize && host->spare_only) | ||
449 | col += mtd->writesize; | ||
450 | |||
451 | n = mtd->writesize + mtd->oobsize - col; | ||
452 | n = min(len, n); | ||
453 | |||
454 | DEBUG(MTD_DEBUG_LEVEL3, | ||
455 | "%s:%d: col = %d, n = %d\n", __func__, __LINE__, col, n); | ||
456 | |||
457 | while (n) { | ||
458 | void __iomem *p; | ||
459 | |||
460 | if (col < mtd->writesize) | ||
461 | p = host->regs + MAIN_AREA0 + (col & ~3); | ||
462 | else | ||
463 | p = host->regs + SPARE_AREA0 - | ||
464 | mtd->writesize + (col & ~3); | ||
465 | |||
466 | DEBUG(MTD_DEBUG_LEVEL3, "%s:%d: p = %p\n", __func__, | ||
467 | __LINE__, p); | ||
468 | |||
469 | if (((col | (int)&buf[i]) & 3) || n < 16) { | ||
470 | uint32_t data = 0; | ||
471 | |||
472 | if (col & 3 || n < 4) | ||
473 | data = readl(p); | ||
474 | |||
475 | switch (col & 3) { | ||
476 | case 0: | ||
477 | if (n) { | ||
478 | data = (data & 0xffffff00) | | ||
479 | (buf[i++] << 0); | ||
480 | n--; | ||
481 | col++; | ||
482 | } | ||
483 | case 1: | ||
484 | if (n) { | ||
485 | data = (data & 0xffff00ff) | | ||
486 | (buf[i++] << 8); | ||
487 | n--; | ||
488 | col++; | ||
489 | } | ||
490 | case 2: | ||
491 | if (n) { | ||
492 | data = (data & 0xff00ffff) | | ||
493 | (buf[i++] << 16); | ||
494 | n--; | ||
495 | col++; | ||
496 | } | ||
497 | case 3: | ||
498 | if (n) { | ||
499 | data = (data & 0x00ffffff) | | ||
500 | (buf[i++] << 24); | ||
501 | n--; | ||
502 | col++; | ||
503 | } | ||
504 | } | ||
505 | |||
506 | writel(data, p); | ||
507 | } else { | ||
508 | int m = mtd->writesize - col; | ||
509 | 391 | ||
510 | if (col >= mtd->writesize) | 392 | n = min(n, len); |
511 | m += mtd->oobsize; | ||
512 | 393 | ||
513 | m = min(n, m) & ~3; | 394 | memcpy(host->data_buf + col, buf, n); |
514 | 395 | ||
515 | DEBUG(MTD_DEBUG_LEVEL3, | 396 | host->buf_start += n; |
516 | "%s:%d: n = %d, m = %d, i = %d, col = %d\n", | ||
517 | __func__, __LINE__, n, m, i, col); | ||
518 | |||
519 | memcpy(p, &buf[i], m); | ||
520 | col += m; | ||
521 | i += m; | ||
522 | n -= m; | ||
523 | } | ||
524 | } | ||
525 | /* Update saved column address */ | ||
526 | host->col_addr = col; | ||
527 | } | 397 | } |
528 | 398 | ||
529 | /* Read the data buffer from the NAND Flash. To read the data from NAND | 399 | /* Read the data buffer from the NAND Flash. To read the data from NAND |
@@ -534,75 +404,14 @@ static void mxc_nand_read_buf(struct mtd_info *mtd, u_char *buf, int len) | |||
534 | { | 404 | { |
535 | struct nand_chip *nand_chip = mtd->priv; | 405 | struct nand_chip *nand_chip = mtd->priv; |
536 | struct mxc_nand_host *host = nand_chip->priv; | 406 | struct mxc_nand_host *host = nand_chip->priv; |
537 | int n, col, i = 0; | 407 | u16 col = host->buf_start; |
408 | int n = mtd->oobsize + mtd->writesize - col; | ||
538 | 409 | ||
539 | DEBUG(MTD_DEBUG_LEVEL3, | 410 | n = min(n, len); |
540 | "mxc_nand_read_buf(col = %d, len = %d)\n", host->col_addr, len); | ||
541 | |||
542 | col = host->col_addr; | ||
543 | |||
544 | /* Adjust saved column address */ | ||
545 | if (col < mtd->writesize && host->spare_only) | ||
546 | col += mtd->writesize; | ||
547 | |||
548 | n = mtd->writesize + mtd->oobsize - col; | ||
549 | n = min(len, n); | ||
550 | |||
551 | while (n) { | ||
552 | void __iomem *p; | ||
553 | |||
554 | if (col < mtd->writesize) | ||
555 | p = host->regs + MAIN_AREA0 + (col & ~3); | ||
556 | else | ||
557 | p = host->regs + SPARE_AREA0 - | ||
558 | mtd->writesize + (col & ~3); | ||
559 | |||
560 | if (((col | (int)&buf[i]) & 3) || n < 16) { | ||
561 | uint32_t data; | ||
562 | |||
563 | data = readl(p); | ||
564 | switch (col & 3) { | ||
565 | case 0: | ||
566 | if (n) { | ||
567 | buf[i++] = (uint8_t) (data); | ||
568 | n--; | ||
569 | col++; | ||
570 | } | ||
571 | case 1: | ||
572 | if (n) { | ||
573 | buf[i++] = (uint8_t) (data >> 8); | ||
574 | n--; | ||
575 | col++; | ||
576 | } | ||
577 | case 2: | ||
578 | if (n) { | ||
579 | buf[i++] = (uint8_t) (data >> 16); | ||
580 | n--; | ||
581 | col++; | ||
582 | } | ||
583 | case 3: | ||
584 | if (n) { | ||
585 | buf[i++] = (uint8_t) (data >> 24); | ||
586 | n--; | ||
587 | col++; | ||
588 | } | ||
589 | } | ||
590 | } else { | ||
591 | int m = mtd->writesize - col; | ||
592 | 411 | ||
593 | if (col >= mtd->writesize) | 412 | memcpy(buf, host->data_buf + col, len); |
594 | m += mtd->oobsize; | ||
595 | |||
596 | m = min(n, m) & ~3; | ||
597 | memcpy(&buf[i], p, m); | ||
598 | col += m; | ||
599 | i += m; | ||
600 | n -= m; | ||
601 | } | ||
602 | } | ||
603 | /* Update saved column address */ | ||
604 | host->col_addr = col; | ||
605 | 413 | ||
414 | host->buf_start += len; | ||
606 | } | 415 | } |
607 | 416 | ||
608 | /* Used by the upper layer to verify the data in NAND Flash | 417 | /* Used by the upper layer to verify the data in NAND Flash |
@@ -641,6 +450,36 @@ static void mxc_nand_select_chip(struct mtd_info *mtd, int chip) | |||
641 | } | 450 | } |
642 | } | 451 | } |
643 | 452 | ||
453 | /* | ||
454 | * Function to transfer data to/from spare area. | ||
455 | */ | ||
456 | static void copy_spare(struct mtd_info *mtd, bool bfrom) | ||
457 | { | ||
458 | struct nand_chip *this = mtd->priv; | ||
459 | struct mxc_nand_host *host = this->priv; | ||
460 | u16 i, j; | ||
461 | u16 n = mtd->writesize >> 9; | ||
462 | u8 *d = host->data_buf + mtd->writesize; | ||
463 | u8 *s = host->regs + SPARE_AREA0; | ||
464 | u16 t = host->spare_len; | ||
465 | |||
466 | j = (mtd->oobsize / n >> 1) << 1; | ||
467 | |||
468 | if (bfrom) { | ||
469 | for (i = 0; i < n - 1; i++) | ||
470 | memcpy(d + i * j, s + i * t, j); | ||
471 | |||
472 | /* the last section */ | ||
473 | memcpy(d + i * j, s + i * t, mtd->oobsize - i * j); | ||
474 | } else { | ||
475 | for (i = 0; i < n - 1; i++) | ||
476 | memcpy(&s[i * t], &d[i * j], j); | ||
477 | |||
478 | /* the last section */ | ||
479 | memcpy(&s[i * t], &d[i * j], mtd->oobsize - i * j); | ||
480 | } | ||
481 | } | ||
482 | |||
644 | static void mxc_do_addr_cycle(struct mtd_info *mtd, int column, int page_addr) | 483 | static void mxc_do_addr_cycle(struct mtd_info *mtd, int column, int page_addr) |
645 | { | 484 | { |
646 | struct nand_chip *nand_chip = mtd->priv; | 485 | struct nand_chip *nand_chip = mtd->priv; |
@@ -707,19 +546,18 @@ static void mxc_nand_command(struct mtd_info *mtd, unsigned command, | |||
707 | switch (command) { | 546 | switch (command) { |
708 | 547 | ||
709 | case NAND_CMD_STATUS: | 548 | case NAND_CMD_STATUS: |
710 | host->col_addr = 0; | 549 | host->buf_start = 0; |
711 | host->status_request = true; | 550 | host->status_request = true; |
712 | break; | 551 | break; |
713 | 552 | ||
714 | case NAND_CMD_READ0: | 553 | case NAND_CMD_READ0: |
715 | host->col_addr = column; | 554 | host->buf_start = column; |
716 | host->spare_only = false; | ||
717 | useirq = false; | 555 | useirq = false; |
718 | break; | 556 | break; |
719 | 557 | ||
720 | case NAND_CMD_READOOB: | 558 | case NAND_CMD_READOOB: |
721 | host->col_addr = column; | 559 | host->buf_start = column + mtd->writesize; |
722 | host->spare_only = true; | 560 | |
723 | useirq = false; | 561 | useirq = false; |
724 | if (host->pagesize_2k) | 562 | if (host->pagesize_2k) |
725 | command = NAND_CMD_READ0; /* only READ0 is valid */ | 563 | command = NAND_CMD_READ0; /* only READ0 is valid */ |
@@ -739,15 +577,13 @@ static void mxc_nand_command(struct mtd_info *mtd, unsigned command, | |||
739 | mxc_nand_command(mtd, NAND_CMD_READ0, 0, | 577 | mxc_nand_command(mtd, NAND_CMD_READ0, 0, |
740 | page_addr); | 578 | page_addr); |
741 | 579 | ||
742 | host->col_addr = column - mtd->writesize; | 580 | host->buf_start = column; |
743 | host->spare_only = true; | ||
744 | 581 | ||
745 | /* Set program pointer to spare region */ | 582 | /* Set program pointer to spare region */ |
746 | if (!host->pagesize_2k) | 583 | if (!host->pagesize_2k) |
747 | send_cmd(host, NAND_CMD_READOOB, false); | 584 | send_cmd(host, NAND_CMD_READOOB, false); |
748 | } else { | 585 | } else { |
749 | host->spare_only = false; | 586 | host->buf_start = column; |
750 | host->col_addr = column; | ||
751 | 587 | ||
752 | /* Set program pointer to page start */ | 588 | /* Set program pointer to page start */ |
753 | if (!host->pagesize_2k) | 589 | if (!host->pagesize_2k) |
@@ -757,13 +593,15 @@ static void mxc_nand_command(struct mtd_info *mtd, unsigned command, | |||
757 | break; | 593 | break; |
758 | 594 | ||
759 | case NAND_CMD_PAGEPROG: | 595 | case NAND_CMD_PAGEPROG: |
760 | send_page(host, 0, host->spare_only, NFC_INPUT); | 596 | memcpy(host->regs + MAIN_AREA0, host->data_buf, mtd->writesize); |
597 | copy_spare(mtd, false); | ||
598 | send_page(host, 0, NFC_INPUT); | ||
761 | 599 | ||
762 | if (host->pagesize_2k) { | 600 | if (host->pagesize_2k) { |
763 | /* data in 4 areas datas */ | 601 | /* data in 4 areas datas */ |
764 | send_page(host, 1, host->spare_only, NFC_INPUT); | 602 | send_page(host, 1, NFC_INPUT); |
765 | send_page(host, 2, host->spare_only, NFC_INPUT); | 603 | send_page(host, 2, NFC_INPUT); |
766 | send_page(host, 3, host->spare_only, NFC_INPUT); | 604 | send_page(host, 3, NFC_INPUT); |
767 | } | 605 | } |
768 | 606 | ||
769 | break; | 607 | break; |
@@ -789,16 +627,18 @@ static void mxc_nand_command(struct mtd_info *mtd, unsigned command, | |||
789 | /* send read confirm command */ | 627 | /* send read confirm command */ |
790 | send_cmd(host, NAND_CMD_READSTART, true); | 628 | send_cmd(host, NAND_CMD_READSTART, true); |
791 | /* read for each AREA */ | 629 | /* read for each AREA */ |
792 | send_page(host, 0, host->spare_only, NFC_OUTPUT); | 630 | send_page(host, 0, NFC_OUTPUT); |
793 | send_page(host, 1, host->spare_only, NFC_OUTPUT); | 631 | send_page(host, 1, NFC_OUTPUT); |
794 | send_page(host, 2, host->spare_only, NFC_OUTPUT); | 632 | send_page(host, 2, NFC_OUTPUT); |
795 | send_page(host, 3, host->spare_only, NFC_OUTPUT); | 633 | send_page(host, 3, NFC_OUTPUT); |
796 | } else | 634 | } else |
797 | send_page(host, 0, host->spare_only, NFC_OUTPUT); | 635 | send_page(host, 0, NFC_OUTPUT); |
636 | |||
637 | memcpy(host->data_buf, host->regs + MAIN_AREA0, mtd->writesize); | ||
638 | copy_spare(mtd, true); | ||
798 | break; | 639 | break; |
799 | 640 | ||
800 | case NAND_CMD_READID: | 641 | case NAND_CMD_READID: |
801 | host->col_addr = 0; | ||
802 | send_read_id(host); | 642 | send_read_id(host); |
803 | break; | 643 | break; |
804 | 644 | ||
@@ -824,10 +664,14 @@ static int __init mxcnd_probe(struct platform_device *pdev) | |||
824 | int err = 0, nr_parts = 0; | 664 | int err = 0, nr_parts = 0; |
825 | 665 | ||
826 | /* Allocate memory for MTD device structure and private data */ | 666 | /* Allocate memory for MTD device structure and private data */ |
827 | host = kzalloc(sizeof(struct mxc_nand_host), GFP_KERNEL); | 667 | host = kzalloc(sizeof(struct mxc_nand_host) + NAND_MAX_PAGESIZE + |
668 | NAND_MAX_OOBSIZE, GFP_KERNEL); | ||
828 | if (!host) | 669 | if (!host) |
829 | return -ENOMEM; | 670 | return -ENOMEM; |
830 | 671 | ||
672 | host->data_buf = (uint8_t *)(host + 1); | ||
673 | host->spare_len = 16; | ||
674 | |||
831 | host->dev = &pdev->dev; | 675 | host->dev = &pdev->dev; |
832 | /* structures must be linked */ | 676 | /* structures must be linked */ |
833 | this = &host->nand; | 677 | this = &host->nand; |