]> git.karo-electronics.de Git - mv-sheeva.git/blobdiff - drivers/mtd/onenand/samsung.c
Merge tag 'v2.6.37' of git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6
[mv-sheeva.git] / drivers / mtd / onenand / samsung.c
index a460f1b748c20fbcb29982925820b79e6a7bbd78..0de7a05e6de065ecfd90cd465939374c11b12090 100644 (file)
@@ -22,6 +22,7 @@
 #include <linux/mtd/onenand.h>
 #include <linux/mtd/partitions.h>
 #include <linux/dma-mapping.h>
+#include <linux/interrupt.h>
 
 #include <asm/mach/flash.h>
 #include <plat/regs-onenand.h>
@@ -58,7 +59,7 @@ enum soc_type {
 #define MAP_11                         (0x3)
 
 #define S3C64XX_CMD_MAP_SHIFT          24
-#define S5PC1XX_CMD_MAP_SHIFT          26
+#define S5PC100_CMD_MAP_SHIFT          26
 
 #define S3C6400_FBA_SHIFT              10
 #define S3C6400_FPA_SHIFT              4
@@ -81,6 +82,17 @@ enum soc_type {
 #define S5PC110_DMA_TRANS_CMD          0x418
 #define S5PC110_DMA_TRANS_STATUS       0x41C
 #define S5PC110_DMA_TRANS_DIR          0x420
+#define S5PC110_INTC_DMA_CLR           0x1004
+#define S5PC110_INTC_ONENAND_CLR       0x1008
+#define S5PC110_INTC_DMA_MASK          0x1024
+#define S5PC110_INTC_ONENAND_MASK      0x1028
+#define S5PC110_INTC_DMA_PEND          0x1044
+#define S5PC110_INTC_ONENAND_PEND      0x1048
+#define S5PC110_INTC_DMA_STATUS                0x1064
+#define S5PC110_INTC_ONENAND_STATUS    0x1068
+
+#define S5PC110_INTC_DMA_TD            (1 << 24)
+#define S5PC110_INTC_DMA_TE            (1 << 16)
 
 #define S5PC110_DMA_CFG_SINGLE         (0x0 << 16)
 #define S5PC110_DMA_CFG_4BURST         (0x2 << 16)
@@ -134,6 +146,7 @@ struct s3c_onenand {
        void __iomem    *dma_addr;
        struct resource *dma_res;
        unsigned long   phys_base;
+       struct completion       complete;
 #ifdef CONFIG_MTD_PARTITIONS
        struct mtd_partition *parts;
 #endif
@@ -191,7 +204,7 @@ static unsigned int s3c64xx_cmd_map(unsigned type, unsigned val)
 
 static unsigned int s5pc1xx_cmd_map(unsigned type, unsigned val)
 {
-       return (type << S5PC1XX_CMD_MAP_SHIFT) | val;
+       return (type << S5PC100_CMD_MAP_SHIFT) | val;
 }
 
 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,
        return 0;
 }
 
-static int s5pc110_dma_ops(void *dst, void *src, size_t count, int direction)
+static int (*s5pc110_dma_ops)(void *dst, void *src, size_t count, int direction);
+
+static int s5pc110_dma_poll(void *dst, void *src, size_t count, int direction)
 {
        void __iomem *base = onenand->dma_addr;
        int status;
+       unsigned long timeout;
 
        writel(src, base + S5PC110_DMA_SRC_ADDR);
        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)
 
        writel(S5PC110_DMA_TRANS_CMD_TR, base + S5PC110_DMA_TRANS_CMD);
 
+       /*
+        * There's no exact timeout values at Spec.
+        * In real case it takes under 1 msec.
+        * So 20 msecs are enough.
+        */
+       timeout = jiffies + msecs_to_jiffies(20);
+
        do {
                status = readl(base + S5PC110_DMA_TRANS_STATUS);
                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)
                                        base + S5PC110_DMA_TRANS_CMD);
                        return -EIO;
                }
-       } while (!(status & S5PC110_DMA_TRANS_STATUS_TD));
+       } while (!(status & S5PC110_DMA_TRANS_STATUS_TD) &&
+               time_before(jiffies, timeout));
 
        writel(S5PC110_DMA_TRANS_CMD_TDC, base + S5PC110_DMA_TRANS_CMD);
 
        return 0;
 }
 
+static irqreturn_t s5pc110_onenand_irq(int irq, void *data)
+{
+       void __iomem *base = onenand->dma_addr;
+       int status, cmd = 0;
+
+       status = readl(base + S5PC110_INTC_DMA_STATUS);
+
+       if (likely(status & S5PC110_INTC_DMA_TD))
+               cmd = S5PC110_DMA_TRANS_CMD_TDC;
+
+       if (unlikely(status & S5PC110_INTC_DMA_TE))
+               cmd = S5PC110_DMA_TRANS_CMD_TEC;
+
+       writel(cmd, base + S5PC110_DMA_TRANS_CMD);
+       writel(status, base + S5PC110_INTC_DMA_CLR);
+
+       if (!onenand->complete.done)
+               complete(&onenand->complete);
+
+       return IRQ_HANDLED;
+}
+
+static int s5pc110_dma_irq(void *dst, void *src, size_t count, int direction)
+{
+       void __iomem *base = onenand->dma_addr;
+       int status;
+
+       status = readl(base + S5PC110_INTC_DMA_MASK);
+       if (status) {
+               status &= ~(S5PC110_INTC_DMA_TD | S5PC110_INTC_DMA_TE);
+               writel(status, base + S5PC110_INTC_DMA_MASK);
+       }
+
+       writel(src, base + S5PC110_DMA_SRC_ADDR);
+       writel(dst, base + S5PC110_DMA_DST_ADDR);
+
+       if (direction == S5PC110_DMA_DIR_READ) {
+               writel(S5PC110_DMA_SRC_CFG_READ, base + S5PC110_DMA_SRC_CFG);
+               writel(S5PC110_DMA_DST_CFG_READ, base + S5PC110_DMA_DST_CFG);
+       } else {
+               writel(S5PC110_DMA_SRC_CFG_WRITE, base + S5PC110_DMA_SRC_CFG);
+               writel(S5PC110_DMA_DST_CFG_WRITE, base + S5PC110_DMA_DST_CFG);
+       }
+
+       writel(count, base + S5PC110_DMA_TRANS_SIZE);
+       writel(direction, base + S5PC110_DMA_TRANS_DIR);
+
+       writel(S5PC110_DMA_TRANS_CMD_TR, base + S5PC110_DMA_TRANS_CMD);
+
+       wait_for_completion_timeout(&onenand->complete, msecs_to_jiffies(20));
+
+       return 0;
+}
+
 static int s5pc110_read_bufferram(struct mtd_info *mtd, int area,
                unsigned char *buffer, int offset, size_t count)
 {
@@ -573,7 +651,8 @@ static int s5pc110_read_bufferram(struct mtd_info *mtd, int area,
        void __iomem *p;
        void *buf = (void *) buffer;
        dma_addr_t dma_src, dma_dst;
-       int err;
+       int err, page_dma = 0;
+       struct device *dev = &onenand->pdev->dev;
 
        p = this->base + area;
        if (ONENAND_CURRENT_BUFFERRAM(this)) {
@@ -597,21 +676,27 @@ static int s5pc110_read_bufferram(struct mtd_info *mtd, int area,
                page = vmalloc_to_page(buf);
                if (!page)
                        goto normal;
-               buf = page_address(page) + ((size_t) buf & ~PAGE_MASK);
-       }
 
-       /* DMA routine */
-       dma_src = onenand->phys_base + (p - this->base);
-       dma_dst = dma_map_single(&onenand->pdev->dev,
-                       buf, count, DMA_FROM_DEVICE);
-       if (dma_mapping_error(&onenand->pdev->dev, dma_dst)) {
-               dev_err(&onenand->pdev->dev,
-                       "Couldn't map a %d byte buffer for DMA\n", count);
+               page_dma = 1;
+               /* DMA routine */
+               dma_src = onenand->phys_base + (p - this->base);
+               dma_dst = dma_map_page(dev, page, 0, count, DMA_FROM_DEVICE);
+       } else {
+               /* DMA routine */
+               dma_src = onenand->phys_base + (p - this->base);
+               dma_dst = dma_map_single(dev, buf, count, DMA_FROM_DEVICE);
+       }
+       if (dma_mapping_error(dev, dma_dst)) {
+               dev_err(dev, "Couldn't map a %d byte buffer for DMA\n", count);
                goto normal;
        }
        err = s5pc110_dma_ops((void *) dma_dst, (void *) dma_src,
                        count, S5PC110_DMA_DIR_READ);
-       dma_unmap_single(&onenand->pdev->dev, dma_dst, count, DMA_FROM_DEVICE);
+
+       if (page_dma)
+               dma_unmap_page(dev, dma_dst, count, DMA_FROM_DEVICE);
+       else
+               dma_unmap_single(dev, dma_dst, count, DMA_FROM_DEVICE);
 
        if (!err)
                return 0;
@@ -759,7 +844,6 @@ static void s3c_onenand_setup(struct mtd_info *mtd)
                onenand->cmd_map = s5pc1xx_cmd_map;
        } else if (onenand->type == TYPE_S5PC110) {
                /* Use generic onenand functions */
-               onenand->cmd_map = s5pc1xx_cmd_map;
                this->read_bufferram = s5pc110_read_bufferram;
                this->chip_probe = s5pc110_chip_probe;
                return;
@@ -904,6 +988,20 @@ static int s3c_onenand_probe(struct platform_device *pdev)
                }
 
                onenand->phys_base = onenand->base_res->start;
+
+               s5pc110_dma_ops = s5pc110_dma_poll;
+               /* Interrupt support */
+               r = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+               if (r) {
+                       init_completion(&onenand->complete);
+                       s5pc110_dma_ops = s5pc110_dma_irq;
+                       err = request_irq(r->start, s5pc110_onenand_irq,
+                                       IRQF_SHARED, "onenand", &onenand);
+                       if (err) {
+                               dev_err(&pdev->dev, "failed to get irq\n");
+                               goto scan_failed;
+                       }
+               }
        }
 
        if (onenand_scan(mtd, 1)) {
@@ -1000,7 +1098,7 @@ static int s3c_pm_ops_suspend(struct device *dev)
        struct onenand_chip *this = mtd->priv;
 
        this->wait(mtd, FL_PM_SUSPENDED);
-       return mtd->suspend(mtd);
+       return 0;
 }
 
 static  int s3c_pm_ops_resume(struct device *dev)
@@ -1009,7 +1107,6 @@ static  int s3c_pm_ops_resume(struct device *dev)
        struct mtd_info *mtd = platform_get_drvdata(pdev);
        struct onenand_chip *this = mtd->priv;
 
-       mtd->resume(mtd);
        this->unlock_all(mtd);
        return 0;
 }