diff options
Diffstat (limited to 'drivers/mtd/onenand/samsung.c')
-rw-r--r-- | drivers/mtd/onenand/samsung.c | 133 |
1 files changed, 115 insertions, 18 deletions
diff --git a/drivers/mtd/onenand/samsung.c b/drivers/mtd/onenand/samsung.c index a460f1b748c2..0de7a05e6de0 100644 --- a/drivers/mtd/onenand/samsung.c +++ b/drivers/mtd/onenand/samsung.c | |||
@@ -22,6 +22,7 @@ | |||
22 | #include <linux/mtd/onenand.h> | 22 | #include <linux/mtd/onenand.h> |
23 | #include <linux/mtd/partitions.h> | 23 | #include <linux/mtd/partitions.h> |
24 | #include <linux/dma-mapping.h> | 24 | #include <linux/dma-mapping.h> |
25 | #include <linux/interrupt.h> | ||
25 | 26 | ||
26 | #include <asm/mach/flash.h> | 27 | #include <asm/mach/flash.h> |
27 | #include <plat/regs-onenand.h> | 28 | #include <plat/regs-onenand.h> |
@@ -58,7 +59,7 @@ enum soc_type { | |||
58 | #define MAP_11 (0x3) | 59 | #define MAP_11 (0x3) |
59 | 60 | ||
60 | #define S3C64XX_CMD_MAP_SHIFT 24 | 61 | #define S3C64XX_CMD_MAP_SHIFT 24 |
61 | #define S5PC1XX_CMD_MAP_SHIFT 26 | 62 | #define S5PC100_CMD_MAP_SHIFT 26 |
62 | 63 | ||
63 | #define S3C6400_FBA_SHIFT 10 | 64 | #define S3C6400_FBA_SHIFT 10 |
64 | #define S3C6400_FPA_SHIFT 4 | 65 | #define S3C6400_FPA_SHIFT 4 |
@@ -81,6 +82,17 @@ enum soc_type { | |||
81 | #define S5PC110_DMA_TRANS_CMD 0x418 | 82 | #define S5PC110_DMA_TRANS_CMD 0x418 |
82 | #define S5PC110_DMA_TRANS_STATUS 0x41C | 83 | #define S5PC110_DMA_TRANS_STATUS 0x41C |
83 | #define S5PC110_DMA_TRANS_DIR 0x420 | 84 | #define S5PC110_DMA_TRANS_DIR 0x420 |
85 | #define S5PC110_INTC_DMA_CLR 0x1004 | ||
86 | #define S5PC110_INTC_ONENAND_CLR 0x1008 | ||
87 | #define S5PC110_INTC_DMA_MASK 0x1024 | ||
88 | #define S5PC110_INTC_ONENAND_MASK 0x1028 | ||
89 | #define S5PC110_INTC_DMA_PEND 0x1044 | ||
90 | #define S5PC110_INTC_ONENAND_PEND 0x1048 | ||
91 | #define S5PC110_INTC_DMA_STATUS 0x1064 | ||
92 | #define S5PC110_INTC_ONENAND_STATUS 0x1068 | ||
93 | |||
94 | #define S5PC110_INTC_DMA_TD (1 << 24) | ||
95 | #define S5PC110_INTC_DMA_TE (1 << 16) | ||
84 | 96 | ||
85 | #define S5PC110_DMA_CFG_SINGLE (0x0 << 16) | 97 | #define S5PC110_DMA_CFG_SINGLE (0x0 << 16) |
86 | #define S5PC110_DMA_CFG_4BURST (0x2 << 16) | 98 | #define S5PC110_DMA_CFG_4BURST (0x2 << 16) |
@@ -134,6 +146,7 @@ struct s3c_onenand { | |||
134 | void __iomem *dma_addr; | 146 | void __iomem *dma_addr; |
135 | struct resource *dma_res; | 147 | struct resource *dma_res; |
136 | unsigned long phys_base; | 148 | unsigned long phys_base; |
149 | struct completion complete; | ||
137 | #ifdef CONFIG_MTD_PARTITIONS | 150 | #ifdef CONFIG_MTD_PARTITIONS |
138 | struct mtd_partition *parts; | 151 | struct mtd_partition *parts; |
139 | #endif | 152 | #endif |
@@ -191,7 +204,7 @@ static unsigned int s3c64xx_cmd_map(unsigned type, unsigned val) | |||
191 | 204 | ||
192 | static unsigned int s5pc1xx_cmd_map(unsigned type, unsigned val) | 205 | static unsigned int s5pc1xx_cmd_map(unsigned type, unsigned val) |
193 | { | 206 | { |
194 | return (type << S5PC1XX_CMD_MAP_SHIFT) | val; | 207 | return (type << S5PC100_CMD_MAP_SHIFT) | val; |
195 | } | 208 | } |
196 | 209 | ||
197 | static unsigned int s3c6400_mem_addr(int fba, int fpa, int fsa) | 210 | static unsigned int s3c6400_mem_addr(int fba, int fpa, int fsa) |
@@ -531,10 +544,13 @@ static int onenand_write_bufferram(struct mtd_info *mtd, int area, | |||
531 | return 0; | 544 | return 0; |
532 | } | 545 | } |
533 | 546 | ||
534 | static int s5pc110_dma_ops(void *dst, void *src, size_t count, int direction) | 547 | static int (*s5pc110_dma_ops)(void *dst, void *src, size_t count, int direction); |
548 | |||
549 | static int s5pc110_dma_poll(void *dst, void *src, size_t count, int direction) | ||
535 | { | 550 | { |
536 | void __iomem *base = onenand->dma_addr; | 551 | void __iomem *base = onenand->dma_addr; |
537 | int status; | 552 | int status; |
553 | unsigned long timeout; | ||
538 | 554 | ||
539 | writel(src, base + S5PC110_DMA_SRC_ADDR); | 555 | writel(src, base + S5PC110_DMA_SRC_ADDR); |
540 | writel(dst, base + S5PC110_DMA_DST_ADDR); | 556 | writel(dst, base + S5PC110_DMA_DST_ADDR); |
@@ -552,6 +568,13 @@ static int s5pc110_dma_ops(void *dst, void *src, size_t count, int direction) | |||
552 | 568 | ||
553 | writel(S5PC110_DMA_TRANS_CMD_TR, base + S5PC110_DMA_TRANS_CMD); | 569 | writel(S5PC110_DMA_TRANS_CMD_TR, base + S5PC110_DMA_TRANS_CMD); |
554 | 570 | ||
571 | /* | ||
572 | * There's no exact timeout values at Spec. | ||
573 | * In real case it takes under 1 msec. | ||
574 | * So 20 msecs are enough. | ||
575 | */ | ||
576 | timeout = jiffies + msecs_to_jiffies(20); | ||
577 | |||
555 | do { | 578 | do { |
556 | status = readl(base + S5PC110_DMA_TRANS_STATUS); | 579 | status = readl(base + S5PC110_DMA_TRANS_STATUS); |
557 | if (status & S5PC110_DMA_TRANS_STATUS_TE) { | 580 | if (status & S5PC110_DMA_TRANS_STATUS_TE) { |
@@ -559,13 +582,68 @@ static int s5pc110_dma_ops(void *dst, void *src, size_t count, int direction) | |||
559 | base + S5PC110_DMA_TRANS_CMD); | 582 | base + S5PC110_DMA_TRANS_CMD); |
560 | return -EIO; | 583 | return -EIO; |
561 | } | 584 | } |
562 | } while (!(status & S5PC110_DMA_TRANS_STATUS_TD)); | 585 | } while (!(status & S5PC110_DMA_TRANS_STATUS_TD) && |
586 | time_before(jiffies, timeout)); | ||
563 | 587 | ||
564 | writel(S5PC110_DMA_TRANS_CMD_TDC, base + S5PC110_DMA_TRANS_CMD); | 588 | writel(S5PC110_DMA_TRANS_CMD_TDC, base + S5PC110_DMA_TRANS_CMD); |
565 | 589 | ||
566 | return 0; | 590 | return 0; |
567 | } | 591 | } |
568 | 592 | ||
593 | static irqreturn_t s5pc110_onenand_irq(int irq, void *data) | ||
594 | { | ||
595 | void __iomem *base = onenand->dma_addr; | ||
596 | int status, cmd = 0; | ||
597 | |||
598 | status = readl(base + S5PC110_INTC_DMA_STATUS); | ||
599 | |||
600 | if (likely(status & S5PC110_INTC_DMA_TD)) | ||
601 | cmd = S5PC110_DMA_TRANS_CMD_TDC; | ||
602 | |||
603 | if (unlikely(status & S5PC110_INTC_DMA_TE)) | ||
604 | cmd = S5PC110_DMA_TRANS_CMD_TEC; | ||
605 | |||
606 | writel(cmd, base + S5PC110_DMA_TRANS_CMD); | ||
607 | writel(status, base + S5PC110_INTC_DMA_CLR); | ||
608 | |||
609 | if (!onenand->complete.done) | ||
610 | complete(&onenand->complete); | ||
611 | |||
612 | return IRQ_HANDLED; | ||
613 | } | ||
614 | |||
615 | static int s5pc110_dma_irq(void *dst, void *src, size_t count, int direction) | ||
616 | { | ||
617 | void __iomem *base = onenand->dma_addr; | ||
618 | int status; | ||
619 | |||
620 | status = readl(base + S5PC110_INTC_DMA_MASK); | ||
621 | if (status) { | ||
622 | status &= ~(S5PC110_INTC_DMA_TD | S5PC110_INTC_DMA_TE); | ||
623 | writel(status, base + S5PC110_INTC_DMA_MASK); | ||
624 | } | ||
625 | |||
626 | writel(src, base + S5PC110_DMA_SRC_ADDR); | ||
627 | writel(dst, base + S5PC110_DMA_DST_ADDR); | ||
628 | |||
629 | if (direction == S5PC110_DMA_DIR_READ) { | ||
630 | writel(S5PC110_DMA_SRC_CFG_READ, base + S5PC110_DMA_SRC_CFG); | ||
631 | writel(S5PC110_DMA_DST_CFG_READ, base + S5PC110_DMA_DST_CFG); | ||
632 | } else { | ||
633 | writel(S5PC110_DMA_SRC_CFG_WRITE, base + S5PC110_DMA_SRC_CFG); | ||
634 | writel(S5PC110_DMA_DST_CFG_WRITE, base + S5PC110_DMA_DST_CFG); | ||
635 | } | ||
636 | |||
637 | writel(count, base + S5PC110_DMA_TRANS_SIZE); | ||
638 | writel(direction, base + S5PC110_DMA_TRANS_DIR); | ||
639 | |||
640 | writel(S5PC110_DMA_TRANS_CMD_TR, base + S5PC110_DMA_TRANS_CMD); | ||
641 | |||
642 | wait_for_completion_timeout(&onenand->complete, msecs_to_jiffies(20)); | ||
643 | |||
644 | return 0; | ||
645 | } | ||
646 | |||
569 | static int s5pc110_read_bufferram(struct mtd_info *mtd, int area, | 647 | static int s5pc110_read_bufferram(struct mtd_info *mtd, int area, |
570 | unsigned char *buffer, int offset, size_t count) | 648 | unsigned char *buffer, int offset, size_t count) |
571 | { | 649 | { |
@@ -573,7 +651,8 @@ static int s5pc110_read_bufferram(struct mtd_info *mtd, int area, | |||
573 | void __iomem *p; | 651 | void __iomem *p; |
574 | void *buf = (void *) buffer; | 652 | void *buf = (void *) buffer; |
575 | dma_addr_t dma_src, dma_dst; | 653 | dma_addr_t dma_src, dma_dst; |
576 | int err; | 654 | int err, page_dma = 0; |
655 | struct device *dev = &onenand->pdev->dev; | ||
577 | 656 | ||
578 | p = this->base + area; | 657 | p = this->base + area; |
579 | if (ONENAND_CURRENT_BUFFERRAM(this)) { | 658 | if (ONENAND_CURRENT_BUFFERRAM(this)) { |
@@ -597,21 +676,27 @@ static int s5pc110_read_bufferram(struct mtd_info *mtd, int area, | |||
597 | page = vmalloc_to_page(buf); | 676 | page = vmalloc_to_page(buf); |
598 | if (!page) | 677 | if (!page) |
599 | goto normal; | 678 | goto normal; |
600 | buf = page_address(page) + ((size_t) buf & ~PAGE_MASK); | ||
601 | } | ||
602 | 679 | ||
603 | /* DMA routine */ | 680 | page_dma = 1; |
604 | dma_src = onenand->phys_base + (p - this->base); | 681 | /* DMA routine */ |
605 | dma_dst = dma_map_single(&onenand->pdev->dev, | 682 | dma_src = onenand->phys_base + (p - this->base); |
606 | buf, count, DMA_FROM_DEVICE); | 683 | dma_dst = dma_map_page(dev, page, 0, count, DMA_FROM_DEVICE); |
607 | if (dma_mapping_error(&onenand->pdev->dev, dma_dst)) { | 684 | } else { |
608 | dev_err(&onenand->pdev->dev, | 685 | /* DMA routine */ |
609 | "Couldn't map a %d byte buffer for DMA\n", count); | 686 | dma_src = onenand->phys_base + (p - this->base); |
687 | dma_dst = dma_map_single(dev, buf, count, DMA_FROM_DEVICE); | ||
688 | } | ||
689 | if (dma_mapping_error(dev, dma_dst)) { | ||
690 | dev_err(dev, "Couldn't map a %d byte buffer for DMA\n", count); | ||
610 | goto normal; | 691 | goto normal; |
611 | } | 692 | } |
612 | err = s5pc110_dma_ops((void *) dma_dst, (void *) dma_src, | 693 | err = s5pc110_dma_ops((void *) dma_dst, (void *) dma_src, |
613 | count, S5PC110_DMA_DIR_READ); | 694 | count, S5PC110_DMA_DIR_READ); |
614 | dma_unmap_single(&onenand->pdev->dev, dma_dst, count, DMA_FROM_DEVICE); | 695 | |
696 | if (page_dma) | ||
697 | dma_unmap_page(dev, dma_dst, count, DMA_FROM_DEVICE); | ||
698 | else | ||
699 | dma_unmap_single(dev, dma_dst, count, DMA_FROM_DEVICE); | ||
615 | 700 | ||
616 | if (!err) | 701 | if (!err) |
617 | return 0; | 702 | return 0; |
@@ -759,7 +844,6 @@ static void s3c_onenand_setup(struct mtd_info *mtd) | |||
759 | onenand->cmd_map = s5pc1xx_cmd_map; | 844 | onenand->cmd_map = s5pc1xx_cmd_map; |
760 | } else if (onenand->type == TYPE_S5PC110) { | 845 | } else if (onenand->type == TYPE_S5PC110) { |
761 | /* Use generic onenand functions */ | 846 | /* Use generic onenand functions */ |
762 | onenand->cmd_map = s5pc1xx_cmd_map; | ||
763 | this->read_bufferram = s5pc110_read_bufferram; | 847 | this->read_bufferram = s5pc110_read_bufferram; |
764 | this->chip_probe = s5pc110_chip_probe; | 848 | this->chip_probe = s5pc110_chip_probe; |
765 | return; | 849 | return; |
@@ -904,6 +988,20 @@ static int s3c_onenand_probe(struct platform_device *pdev) | |||
904 | } | 988 | } |
905 | 989 | ||
906 | onenand->phys_base = onenand->base_res->start; | 990 | onenand->phys_base = onenand->base_res->start; |
991 | |||
992 | s5pc110_dma_ops = s5pc110_dma_poll; | ||
993 | /* Interrupt support */ | ||
994 | r = platform_get_resource(pdev, IORESOURCE_IRQ, 0); | ||
995 | if (r) { | ||
996 | init_completion(&onenand->complete); | ||
997 | s5pc110_dma_ops = s5pc110_dma_irq; | ||
998 | err = request_irq(r->start, s5pc110_onenand_irq, | ||
999 | IRQF_SHARED, "onenand", &onenand); | ||
1000 | if (err) { | ||
1001 | dev_err(&pdev->dev, "failed to get irq\n"); | ||
1002 | goto scan_failed; | ||
1003 | } | ||
1004 | } | ||
907 | } | 1005 | } |
908 | 1006 | ||
909 | if (onenand_scan(mtd, 1)) { | 1007 | if (onenand_scan(mtd, 1)) { |
@@ -1000,7 +1098,7 @@ static int s3c_pm_ops_suspend(struct device *dev) | |||
1000 | struct onenand_chip *this = mtd->priv; | 1098 | struct onenand_chip *this = mtd->priv; |
1001 | 1099 | ||
1002 | this->wait(mtd, FL_PM_SUSPENDED); | 1100 | this->wait(mtd, FL_PM_SUSPENDED); |
1003 | return mtd->suspend(mtd); | 1101 | return 0; |
1004 | } | 1102 | } |
1005 | 1103 | ||
1006 | static int s3c_pm_ops_resume(struct device *dev) | 1104 | static int s3c_pm_ops_resume(struct device *dev) |
@@ -1009,7 +1107,6 @@ static int s3c_pm_ops_resume(struct device *dev) | |||
1009 | struct mtd_info *mtd = platform_get_drvdata(pdev); | 1107 | struct mtd_info *mtd = platform_get_drvdata(pdev); |
1010 | struct onenand_chip *this = mtd->priv; | 1108 | struct onenand_chip *this = mtd->priv; |
1011 | 1109 | ||
1012 | mtd->resume(mtd); | ||
1013 | this->unlock_all(mtd); | 1110 | this->unlock_all(mtd); |
1014 | return 0; | 1111 | return 0; |
1015 | } | 1112 | } |