Signed-off-by: Alim Akhtar <alim.akh...@samsung.com>
---
 drivers/dma/amba-pl08x.c |  135 ++++++++++++++++++++++++++++++++++++++--------
 1 files changed, 112 insertions(+), 23 deletions(-)

diff --git a/drivers/dma/amba-pl08x.c b/drivers/dma/amba-pl08x.c
index cd8df7f..501540f 100644
--- a/drivers/dma/amba-pl08x.c
+++ b/drivers/dma/amba-pl08x.c
@@ -66,8 +66,25 @@
  *    after the final transfer signalled by LBREQ or LSREQ.  The DMAC
  *    will then move to the next LLI entry.
  *
- * Global TODO:
- * - Break out common code from arch/arm/mach-s3c64xx and share
+ * Samsung S3C64xx SoCs uses a variant of PL080 DMAC. It contains an extra
+ * control register to hold the TransferSize. Below is the LLI structure
+ * and offsets of S3C64xx DMAC.
+ *     -----------------------------------------------------------------
+ *     |       Offset          |       Contents                        |
+ *     -----------------------------------------------------------------
+ *     | Next LLI Address      | Source Address for Next xfer          |
+ *     -----------------------------------------------------------------
+ *     | Next LLI Address+0x04 | Destination Address for Next xfer     |
+ *     -----------------------------------------------------------------
+ *     | Next LLI Address+0x08 | Next LLI address for next xfer        |
+ *     -----------------------------------------------------------------
+ *     | Next LLI Address+0x0c | DMACCxControl0 data for next xfer     |
+ *     -----------------------------------------------------------------
+ *     | Next LLI Address+0x10 | DMACCxControl1 xfer size for next xfer|
+ *     -----------------------------------------------------------------
+ * Also S3C64XX has a config register at offset 0x14
+ * Have a look at arch/arm/include/asm/hardware/pl080.h for complete register
+ * details.
  */
 #include <linux/amba/bus.h>
 #include <linux/amba/pl08x.h>
@@ -97,6 +114,8 @@ static struct amba_driver pl08x_amba_driver;
 struct vendor_data {
        u8 channels;
        bool dualmaster;
+       /* To identify samsung DMAC */
+       bool is_pl080_s3c;
 };
 
 /*
@@ -110,6 +129,11 @@ struct pl08x_lli {
        u32 dst;
        u32 lli;
        u32 cctl;
+       /*
+        * Samsung pl080 DMAC has one exrta control register
+        * which is used to hold the transfer_size
+        */
+       u32 cctl1;
 };
 
 /**
@@ -171,9 +195,20 @@ static inline struct pl08x_txd *to_pl08x_txd(struct 
dma_async_tx_descriptor *tx)
 /* Whether a certain channel is busy or not */
 static int pl08x_phy_channel_busy(struct pl08x_phy_chan *ch)
 {
+       struct pl08x_dma_chan *plchan = ch->serving;
+       struct pl08x_driver_data *pl08x;
        unsigned int val;
 
-       val = readl(ch->base + PL080_CH_CONFIG);
+       if (plchan == NULL)
+               return false;
+
+       pl08x = plchan->host;
+
+       if (pl08x->vd->is_pl080_s3c)
+               val = readl(ch->base + PL080S_CH_CONFIG);
+       else
+               val = readl(ch->base + PL080_CH_CONFIG);
+
        return val & PL080_CONFIG_ACTIVE;
 }
 
@@ -207,7 +242,12 @@ static void pl08x_start_txd(struct pl08x_dma_chan *plchan,
        writel(lli->dst, phychan->base + PL080_CH_DST_ADDR);
        writel(lli->lli, phychan->base + PL080_CH_LLI);
        writel(lli->cctl, phychan->base + PL080_CH_CONTROL);
-       writel(txd->ccfg, phychan->base + PL080_CH_CONFIG);
+
+       if (pl08x->vd->is_pl080_s3c) {
+               writel(txd->ccfg, phychan->base + PL080S_CH_CONFIG);
+               writel(lli->cctl1, phychan->base + PL080S_CH_CONTROL2);
+       } else
+               writel(txd->ccfg, phychan->base + PL080_CH_CONFIG);
 
        /* Enable the DMA channel */
        /* Do not access config register until channel shows as disabled */
@@ -215,11 +255,23 @@ static void pl08x_start_txd(struct pl08x_dma_chan *plchan,
                cpu_relax();
 
        /* Do not access config register until channel shows as inactive */
-       val = readl(phychan->base + PL080_CH_CONFIG);
-       while ((val & PL080_CONFIG_ACTIVE) || (val & PL080_CONFIG_ENABLE))
+       if (pl08x->vd->is_pl080_s3c) {
+               val = readl(phychan->base + PL080S_CH_CONFIG);
+               while ((val & PL080_CONFIG_ACTIVE) ||
+                       (val & PL080_CONFIG_ENABLE))
+                       val = readl(phychan->base + PL080S_CH_CONFIG);
+
+               writel(val | PL080_CONFIG_ENABLE,
+                       phychan->base + PL080S_CH_CONFIG);
+       } else {
                val = readl(phychan->base + PL080_CH_CONFIG);
+                       while ((val & PL080_CONFIG_ACTIVE) ||
+                               (val & PL080_CONFIG_ENABLE))
+                               val = readl(phychan->base + PL080_CH_CONFIG);
 
-       writel(val | PL080_CONFIG_ENABLE, phychan->base + PL080_CH_CONFIG);
+               writel(val | PL080_CONFIG_ENABLE,
+                       phychan->base + PL080_CH_CONFIG);
+       }
 }
 
 /*
@@ -236,12 +288,19 @@ static void pl08x_pause_phy_chan(struct pl08x_phy_chan 
*ch)
 {
        u32 val;
        int timeout;
+       struct pl08x_dma_chan *plchan = ch->serving;
+       struct pl08x_driver_data *pl08x = plchan->host;
 
        /* Set the HALT bit and wait for the FIFO to drain */
-       val = readl(ch->base + PL080_CH_CONFIG);
-       val |= PL080_CONFIG_HALT;
-       writel(val, ch->base + PL080_CH_CONFIG);
-
+       if (pl08x->vd->is_pl080_s3c) {
+               val = readl(ch->base + PL080S_CH_CONFIG);
+               val |= PL080_CONFIG_HALT;
+               writel(val, ch->base + PL080S_CH_CONFIG);
+       } else {
+               val = readl(ch->base + PL080_CH_CONFIG);
+               val |= PL080_CONFIG_HALT;
+               writel(val, ch->base + PL080_CH_CONFIG);
+       }
        /* Wait for channel inactive */
        for (timeout = 1000; timeout; timeout--) {
                if (!pl08x_phy_channel_busy(ch))
@@ -255,11 +314,19 @@ static void pl08x_pause_phy_chan(struct pl08x_phy_chan 
*ch)
 static void pl08x_resume_phy_chan(struct pl08x_phy_chan *ch)
 {
        u32 val;
+       struct pl08x_dma_chan *plchan = ch->serving;
+       struct pl08x_driver_data *pl08x = plchan->host;
 
        /* Clear the HALT bit */
-       val = readl(ch->base + PL080_CH_CONFIG);
-       val &= ~PL080_CONFIG_HALT;
-       writel(val, ch->base + PL080_CH_CONFIG);
+       if (pl08x->vd->is_pl080_s3c) {
+               val = readl(ch->base + PL080S_CH_CONFIG);
+               val &= ~PL080_CONFIG_HALT;
+               writel(val, ch->base + PL080S_CH_CONFIG);
+       } else {
+               val = readl(ch->base + PL080_CH_CONFIG);
+               val &= ~PL080_CONFIG_HALT;
+               writel(val, ch->base + PL080_CH_CONFIG);
+       }
 }
 
 /*
@@ -271,12 +338,17 @@ static void pl08x_resume_phy_chan(struct pl08x_phy_chan 
*ch)
 static void pl08x_terminate_phy_chan(struct pl08x_driver_data *pl08x,
        struct pl08x_phy_chan *ch)
 {
-       u32 val = readl(ch->base + PL080_CH_CONFIG);
-
-       val &= ~(PL080_CONFIG_ENABLE | PL080_CONFIG_ERR_IRQ_MASK |
-                PL080_CONFIG_TC_IRQ_MASK);
-
-       writel(val, ch->base + PL080_CH_CONFIG);
+       if (pl08x->vd->is_pl080_s3c) {
+               u32 val = readl(ch->base + PL080S_CH_CONFIG);
+               val &= ~(PL080_CONFIG_ENABLE | PL080_CONFIG_ERR_IRQ_MASK |
+                               PL080_CONFIG_TC_IRQ_MASK);
+               writel(val, ch->base + PL080S_CH_CONFIG);
+       } else {
+               u32 val = readl(ch->base + PL080_CH_CONFIG);
+               val &= ~(PL080_CONFIG_ENABLE | PL080_CONFIG_ERR_IRQ_MASK |
+                               PL080_CONFIG_TC_IRQ_MASK);
+               writel(val, ch->base + PL080_CH_CONFIG);
+       }
 
        writel(1 << ch->id, pl08x->base + PL080_ERR_CLEAR);
        writel(1 << ch->id, pl08x->base + PL080_TC_CLEAR);
@@ -569,6 +641,7 @@ static int pl08x_fill_llis_for_desc(struct 
pl08x_driver_data *pl08x,
        u32 cctl, early_bytes = 0;
        size_t max_bytes_per_lli, total_bytes = 0;
        struct pl08x_lli *llis_va;
+       size_t lli_len = 0, target_len, tsize, odd_bytes;
 
        txd->llis_va = dma_pool_alloc(pl08x->pool, GFP_NOWAIT, &txd->llis_bus);
        if (!txd->llis_va) {
@@ -700,7 +773,7 @@ static int pl08x_fill_llis_for_desc(struct 
pl08x_driver_data *pl08x,
                 * width left
                 */
                while (bd.remainder > (mbus->buswidth - 1)) {
-                       size_t lli_len, tsize, width;
+                       size_t width;
 
                        /*
                         * If enough left try to send max possible,
@@ -759,6 +832,9 @@ static int pl08x_fill_llis_for_desc(struct 
pl08x_driver_data *pl08x,
        llis_va[num_llis - 1].lli = 0;
        /* The final LLI element shall also fire an interrupt. */
        llis_va[num_llis - 1].cctl |= PL080_CONTROL_TC_IRQ_EN;
+       /* Keep the TransferSize seperate to fill samsung specific register */
+       if (pl08x->vd->is_pl080_s3c)
+               llis_va[num_llis - 1].cctl1 |= lli_len;
 
 #ifdef VERBOSE_DEBUG
        {
@@ -771,8 +847,8 @@ static int pl08x_fill_llis_for_desc(struct 
pl08x_driver_data *pl08x,
                        dev_vdbg(&pl08x->adev->dev,
                                 "%3d @%p: 0x%08x 0x%08x 0x%08x 0x%08x\n",
                                 i, &llis_va[i], llis_va[i].src,
-                                llis_va[i].dst, llis_va[i].lli, llis_va[i].cctl
-                               );
+                                llis_va[i].dst, llis_va[i].lli,
+                                llis_va[i].cctl);
                }
        }
 #endif
@@ -1979,6 +2055,12 @@ static struct vendor_data vendor_pl081 = {
        .dualmaster = false,
 };
 
+static struct vendor_data vendor_pl080_s3c = {
+       .channels = 8,
+       .dualmaster = true,
+       .is_pl080_s3c = true,
+};
+
 static struct amba_id pl08x_ids[] = {
        /* PL080 */
        {
@@ -1998,6 +2080,13 @@ static struct amba_id pl08x_ids[] = {
                .mask   = 0x00ffffff,
                .data   = &vendor_pl080,
        },
+       /* Samsung DMAC is PL080 variant*/
+       {
+               .id     = 0x00041082,
+               .mask   = 0x000fffff,
+               .data   = &vendor_pl080_s3c,
+
+       },
        { 0, 0 },
 };
 
-- 
1.7.2.3

--
To unsubscribe from this list: send the line "unsubscribe linux-samsung-soc" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to