OSDN Git Service

dmaengine: omap-dma: move reading of dma position to omap-dma.c
[android-x86/kernel.git] / drivers / dma / omap-dma.c
index 362e7c4..323eae2 100644 (file)
@@ -5,6 +5,7 @@
  * it under the terms of the GNU General Public License version 2 as
  * published by the Free Software Foundation.
  */
+#include <linux/delay.h>
 #include <linux/dmaengine.h>
 #include <linux/dma-mapping.h>
 #include <linux/err.h>
@@ -26,11 +27,13 @@ struct omap_dmadev {
        spinlock_t lock;
        struct tasklet_struct task;
        struct list_head pending;
+       struct omap_system_dma_plat_info *plat;
 };
 
 struct omap_chan {
        struct virt_dma_chan vc;
        struct list_head node;
+       struct omap_system_dma_plat_info *plat;
 
        struct dma_slave_config cfg;
        unsigned dma_sig;
@@ -58,6 +61,7 @@ struct omap_desc {
        uint8_t sync_mode;      /* OMAP_DMA_SYNC_xxx */
        uint8_t sync_type;      /* OMAP_DMA_xxx_SYNC* */
        uint8_t periph_port;    /* Peripheral port */
+       uint16_t cicr;          /* CICR value */
 
        unsigned sglen;
        struct omap_sg sg[0];
@@ -93,28 +97,137 @@ static void omap_dma_desc_free(struct virt_dma_desc *vd)
        kfree(container_of(vd, struct omap_desc, vd));
 }
 
+static void omap_dma_start(struct omap_chan *c, struct omap_desc *d)
+{
+       struct omap_dmadev *od = to_omap_dma_dev(c->vc.chan.device);
+       uint32_t val;
+
+       if (__dma_omap15xx(od->plat->dma_attr))
+               c->plat->dma_write(0, CPC, c->dma_ch);
+       else
+               c->plat->dma_write(0, CDAC, c->dma_ch);
+
+       if (!__dma_omap15xx(od->plat->dma_attr) && c->cyclic) {
+               val = c->plat->dma_read(CLNK_CTRL, c->dma_ch);
+
+               if (dma_omap1())
+                       val &= ~(1 << 14);
+
+               val |= c->dma_ch | 1 << 15;
+
+               c->plat->dma_write(val, CLNK_CTRL, c->dma_ch);
+       } else if (od->plat->errata & DMA_ERRATA_PARALLEL_CHANNELS)
+               c->plat->dma_write(c->dma_ch, CLNK_CTRL, c->dma_ch);
+
+       /* Clear CSR */
+       if (dma_omap1())
+               c->plat->dma_read(CSR, c->dma_ch);
+       else
+               c->plat->dma_write(~0, CSR, c->dma_ch);
+
+       /* Enable interrupts */
+       c->plat->dma_write(d->cicr, CICR, c->dma_ch);
+
+       val = c->plat->dma_read(CCR, c->dma_ch);
+       if (od->plat->errata & DMA_ERRATA_IFRAME_BUFFERING)
+               val |= OMAP_DMA_CCR_BUFFERING_DISABLE;
+       val |= OMAP_DMA_CCR_EN;
+       mb();
+       c->plat->dma_write(val, CCR, c->dma_ch);
+}
+
+static void omap_dma_stop(struct omap_chan *c)
+{
+       struct omap_dmadev *od = to_omap_dma_dev(c->vc.chan.device);
+       uint32_t val;
+
+       /* disable irq */
+       c->plat->dma_write(0, CICR, c->dma_ch);
+
+       /* Clear CSR */
+       if (dma_omap1())
+               c->plat->dma_read(CSR, c->dma_ch);
+       else
+               c->plat->dma_write(~0, CSR, c->dma_ch);
+
+       val = c->plat->dma_read(CCR, c->dma_ch);
+       if (od->plat->errata & DMA_ERRATA_i541 &&
+           val & OMAP_DMA_CCR_SEL_SRC_DST_SYNC) {
+               uint32_t sysconfig;
+               unsigned i;
+
+               sysconfig = c->plat->dma_read(OCP_SYSCONFIG, c->dma_ch);
+               val = sysconfig & ~DMA_SYSCONFIG_MIDLEMODE_MASK;
+               val |= DMA_SYSCONFIG_MIDLEMODE(DMA_IDLEMODE_NO_IDLE);
+               c->plat->dma_write(val, OCP_SYSCONFIG, c->dma_ch);
+
+               val = c->plat->dma_read(CCR, c->dma_ch);
+               val &= ~OMAP_DMA_CCR_EN;
+               c->plat->dma_write(val, CCR, c->dma_ch);
+
+               /* Wait for sDMA FIFO to drain */
+               for (i = 0; ; i++) {
+                       val = c->plat->dma_read(CCR, c->dma_ch);
+                       if (!(val & (OMAP_DMA_CCR_RD_ACTIVE | OMAP_DMA_CCR_WR_ACTIVE)))
+                               break;
+
+                       if (i > 100)
+                               break;
+
+                       udelay(5);
+               }
+
+               if (val & (OMAP_DMA_CCR_RD_ACTIVE | OMAP_DMA_CCR_WR_ACTIVE))
+                       dev_err(c->vc.chan.device->dev,
+                               "DMA drain did not complete on lch %d\n",
+                               c->dma_ch);
+
+               c->plat->dma_write(sysconfig, OCP_SYSCONFIG, c->dma_ch);
+       } else {
+               val &= ~OMAP_DMA_CCR_EN;
+               c->plat->dma_write(val, CCR, c->dma_ch);
+       }
+
+       mb();
+
+       if (!__dma_omap15xx(od->plat->dma_attr) && c->cyclic) {
+               val = c->plat->dma_read(CLNK_CTRL, c->dma_ch);
+
+               if (dma_omap1())
+                       val |= 1 << 14; /* set the STOP_LNK bit */
+               else
+                       val &= ~(1 << 15); /* Clear the ENABLE_LNK bit */
+
+               c->plat->dma_write(val, CLNK_CTRL, c->dma_ch);
+       }
+}
+
 static void omap_dma_start_sg(struct omap_chan *c, struct omap_desc *d,
        unsigned idx)
 {
        struct omap_sg *sg = d->sg + idx;
 
-       if (d->dir == DMA_DEV_TO_MEM)
-               omap_set_dma_dest_params(c->dma_ch, OMAP_DMA_PORT_EMIFF,
-                       OMAP_DMA_AMODE_POST_INC, sg->addr, 0, 0);
-       else
-               omap_set_dma_src_params(c->dma_ch, OMAP_DMA_PORT_EMIFF,
-                       OMAP_DMA_AMODE_POST_INC, sg->addr, 0, 0);
+       if (d->dir == DMA_DEV_TO_MEM) {
+               c->plat->dma_write(sg->addr, CDSA, c->dma_ch);
+               c->plat->dma_write(0, CDEI, c->dma_ch);
+               c->plat->dma_write(0, CDFI, c->dma_ch);
+       } else {
+               c->plat->dma_write(sg->addr, CSSA, c->dma_ch);
+               c->plat->dma_write(0, CSEI, c->dma_ch);
+               c->plat->dma_write(0, CSFI, c->dma_ch);
+       }
 
-       omap_set_dma_transfer_params(c->dma_ch, d->es, sg->en, sg->fn,
-               d->sync_mode, c->dma_sig, d->sync_type);
+       c->plat->dma_write(sg->en, CEN, c->dma_ch);
+       c->plat->dma_write(sg->fn, CFN, c->dma_ch);
 
-       omap_start_dma(c->dma_ch);
+       omap_dma_start(c, d);
 }
 
 static void omap_dma_start_desc(struct omap_chan *c)
 {
        struct virt_dma_desc *vd = vchan_next_desc(&c->vc);
        struct omap_desc *d;
+       uint32_t val;
 
        if (!vd) {
                c->desc = NULL;
@@ -126,12 +239,87 @@ static void omap_dma_start_desc(struct omap_chan *c)
        c->desc = d = to_omap_dma_desc(&vd->tx);
        c->sgidx = 0;
 
-       if (d->dir == DMA_DEV_TO_MEM)
-               omap_set_dma_src_params(c->dma_ch, d->periph_port,
-                       OMAP_DMA_AMODE_CONSTANT, d->dev_addr, 0, d->fi);
-       else
-               omap_set_dma_dest_params(c->dma_ch, d->periph_port,
-                       OMAP_DMA_AMODE_CONSTANT, d->dev_addr, 0, d->fi);
+       if (d->dir == DMA_DEV_TO_MEM) {
+               if (dma_omap1()) {
+                       val = c->plat->dma_read(CSDP, c->dma_ch);
+                       val &= ~(0x1f << 9 | 0x1f << 2);
+                       val |= OMAP_DMA_PORT_EMIFF << 9;
+                       val |= d->periph_port << 2;
+                       c->plat->dma_write(val, CSDP, c->dma_ch);
+               }
+
+               val = c->plat->dma_read(CCR, c->dma_ch);
+               val &= ~(0x03 << 14 | 0x03 << 12);
+               val |= OMAP_DMA_AMODE_POST_INC << 14;
+               val |= OMAP_DMA_AMODE_CONSTANT << 12;
+               c->plat->dma_write(val, CCR, c->dma_ch);
+
+               c->plat->dma_write(d->dev_addr, CSSA, c->dma_ch);
+               c->plat->dma_write(0, CSEI, c->dma_ch);
+               c->plat->dma_write(d->fi, CSFI, c->dma_ch);
+       } else {
+               if (dma_omap1()) {
+                       val = c->plat->dma_read(CSDP, c->dma_ch);
+                       val &= ~(0x1f << 9 | 0x1f << 2);
+                       val |= d->periph_port << 9;
+                       val |= OMAP_DMA_PORT_EMIFF << 2;
+                       c->plat->dma_write(val, CSDP, c->dma_ch);
+               }
+
+               val = c->plat->dma_read(CCR, c->dma_ch);
+               val &= ~(0x03 << 12 | 0x03 << 14);
+               val |= OMAP_DMA_AMODE_CONSTANT << 14;
+               val |= OMAP_DMA_AMODE_POST_INC << 12;
+               c->plat->dma_write(val, CCR, c->dma_ch);
+
+               c->plat->dma_write(d->dev_addr, CDSA, c->dma_ch);
+               c->plat->dma_write(0, CDEI, c->dma_ch);
+               c->plat->dma_write(d->fi, CDFI, c->dma_ch);
+       }
+
+       val = c->plat->dma_read(CSDP, c->dma_ch);
+       val &= ~0x03;
+       val |= d->es;
+       c->plat->dma_write(val, CSDP, c->dma_ch);
+
+       if (dma_omap1()) {
+               val = c->plat->dma_read(CCR, c->dma_ch);
+               val &= ~(1 << 5);
+               if (d->sync_mode == OMAP_DMA_SYNC_FRAME)
+                       val |= 1 << 5;
+               c->plat->dma_write(val, CCR, c->dma_ch);
+
+               val = c->plat->dma_read(CCR2, c->dma_ch);
+               val &= ~(1 << 2);
+               if (d->sync_mode == OMAP_DMA_SYNC_BLOCK)
+                       val |= 1 << 2;
+               c->plat->dma_write(val, CCR2, c->dma_ch);
+       } else if (c->dma_sig) {
+               val = c->plat->dma_read(CCR, c->dma_ch);
+
+               /* DMA_SYNCHRO_CONTROL_UPPER depends on the channel number */
+               val &= ~(1 << 24 | 1 << 23 | 3 << 19 | 1 << 18 | 1 << 5 | 0x1f);
+               val |= (c->dma_sig & ~0x1f) << 14;
+               val |= c->dma_sig & 0x1f;
+
+               if (d->sync_mode & OMAP_DMA_SYNC_FRAME)
+                       val |= 1 << 5;
+
+               if (d->sync_mode & OMAP_DMA_SYNC_BLOCK)
+                       val |= 1 << 18;
+
+               switch (d->sync_type) {
+               case OMAP_DMA_DST_SYNC_PREFETCH:/* dest synch */
+                       val |= 1 << 23;         /* Prefetch */
+                       break;
+               case 0:
+                       break;
+               default:
+                       val |= 1 << 24;         /* source synch */
+                       break;
+               }
+               c->plat->dma_write(val, CCR, c->dma_ch);
+       }
 
        omap_dma_start_sg(c, d, 0);
 }
@@ -239,6 +427,68 @@ static size_t omap_dma_desc_size_pos(struct omap_desc *d, dma_addr_t addr)
        return size;
 }
 
+static dma_addr_t omap_dma_get_src_pos(struct omap_chan *c)
+{
+       struct omap_dmadev *od = to_omap_dma_dev(c->vc.chan.device);
+       dma_addr_t addr;
+
+       if (__dma_omap15xx(od->plat->dma_attr))
+               addr = c->plat->dma_read(CPC, c->dma_ch);
+       else
+               addr = c->plat->dma_read(CSAC, c->dma_ch);
+
+       if (od->plat->errata & DMA_ERRATA_3_3 && addr == 0)
+               addr = c->plat->dma_read(CSAC, c->dma_ch);
+
+       if (!__dma_omap15xx(od->plat->dma_attr)) {
+               /*
+                * CDAC == 0 indicates that the DMA transfer on the channel has
+                * not been started (no data has been transferred so far).
+                * Return the programmed source start address in this case.
+                */
+               if (c->plat->dma_read(CDAC, c->dma_ch))
+                       addr = c->plat->dma_read(CSAC, c->dma_ch);
+               else
+                       addr = c->plat->dma_read(CSSA, c->dma_ch);
+       }
+
+       if (dma_omap1())
+               addr |= c->plat->dma_read(CSSA, c->dma_ch) & 0xffff0000;
+
+       return addr;
+}
+
+static dma_addr_t omap_dma_get_dst_pos(struct omap_chan *c)
+{
+       struct omap_dmadev *od = to_omap_dma_dev(c->vc.chan.device);
+       dma_addr_t addr;
+
+       if (__dma_omap15xx(od->plat->dma_attr))
+               addr = c->plat->dma_read(CPC, c->dma_ch);
+       else
+               addr = c->plat->dma_read(CDAC, c->dma_ch);
+
+       /*
+        * omap 3.2/3.3 erratum: sometimes 0 is returned if CSAC/CDAC is
+        * read before the DMA controller finished disabling the channel.
+        */
+       if (!__dma_omap15xx(od->plat->dma_attr) && addr == 0) {
+               addr = c->plat->dma_read(CDAC, c->dma_ch);
+               /*
+                * CDAC == 0 indicates that the DMA transfer on the channel has
+                * not been started (no data has been transferred so far).
+                * Return the programmed destination start address in this case.
+                */
+               if (addr == 0)
+                       addr = c->plat->dma_read(CDSA, c->dma_ch);
+       }
+
+       if (dma_omap1())
+               addr |= c->plat->dma_read(CDSA, c->dma_ch) & 0xffff0000;
+
+       return addr;
+}
+
 static enum dma_status omap_dma_tx_status(struct dma_chan *chan,
        dma_cookie_t cookie, struct dma_tx_state *txstate)
 {
@@ -260,9 +510,9 @@ static enum dma_status omap_dma_tx_status(struct dma_chan *chan,
                dma_addr_t pos;
 
                if (d->dir == DMA_MEM_TO_DEV)
-                       pos = omap_get_dma_src_pos(c->dma_ch);
+                       pos = omap_dma_get_src_pos(c);
                else if (d->dir == DMA_DEV_TO_MEM)
-                       pos = omap_get_dma_dst_pos(c->dma_ch);
+                       pos = omap_dma_get_dst_pos(c);
                else
                        pos = 0;
 
@@ -353,6 +603,12 @@ static struct dma_async_tx_descriptor *omap_dma_prep_slave_sg(
        d->sync_mode = OMAP_DMA_SYNC_FRAME;
        d->sync_type = sync_type;
        d->periph_port = OMAP_DMA_PORT_TIPB;
+       d->cicr = OMAP_DMA_DROP_IRQ | OMAP_DMA_BLOCK_IRQ;
+
+       if (dma_omap1())
+               d->cicr |= OMAP1_DMA_TOUT_IRQ;
+       else
+               d->cicr |= OMAP2_DMA_MISALIGNED_ERR_IRQ | OMAP2_DMA_TRANS_ERR_IRQ;
 
        /*
         * Build our scatterlist entries: each contains the address,
@@ -382,6 +638,7 @@ static struct dma_async_tx_descriptor *omap_dma_prep_dma_cyclic(
        size_t period_len, enum dma_transfer_direction dir, unsigned long flags,
        void *context)
 {
+       struct omap_dmadev *od = to_omap_dma_dev(chan->device);
        struct omap_chan *c = to_omap_dma_chan(chan);
        enum dma_slave_buswidth dev_width;
        struct omap_desc *d;
@@ -438,20 +695,34 @@ static struct dma_async_tx_descriptor *omap_dma_prep_dma_cyclic(
        d->sg[0].en = period_len / es_bytes[es];
        d->sg[0].fn = buf_len / period_len;
        d->sglen = 1;
+       d->cicr = OMAP_DMA_DROP_IRQ;
+       if (flags & DMA_PREP_INTERRUPT)
+               d->cicr |= OMAP_DMA_FRAME_IRQ;
+
+       if (dma_omap1())
+               d->cicr |= OMAP1_DMA_TOUT_IRQ;
+       else
+               d->cicr |= OMAP2_DMA_MISALIGNED_ERR_IRQ | OMAP2_DMA_TRANS_ERR_IRQ;
 
        if (!c->cyclic) {
                c->cyclic = true;
-               omap_dma_link_lch(c->dma_ch, c->dma_ch);
 
-               if (flags & DMA_PREP_INTERRUPT)
-                       omap_enable_dma_irq(c->dma_ch, OMAP_DMA_FRAME_IRQ);
+               if (__dma_omap15xx(od->plat->dma_attr)) {
+                       uint32_t val;
 
-               omap_disable_dma_irq(c->dma_ch, OMAP_DMA_BLOCK_IRQ);
+                       val = c->plat->dma_read(CCR, c->dma_ch);
+                       val |= 3 << 8;
+                       c->plat->dma_write(val, CCR, c->dma_ch);
+               }
        }
 
        if (dma_omap2plus()) {
-               omap_set_dma_src_burst_mode(c->dma_ch, OMAP_DMA_DATA_BURST_16);
-               omap_set_dma_dest_burst_mode(c->dma_ch, OMAP_DMA_DATA_BURST_16);
+               uint32_t val;
+
+               val = c->plat->dma_read(CSDP, c->dma_ch);
+               val |= 0x03 << 7; /* src burst mode 16 */
+               val |= 0x03 << 14; /* dst burst mode 16 */
+               c->plat->dma_write(val, CSDP, c->dma_ch);
        }
 
        return vchan_tx_prep(&c->vc, &d->vd, flags);
@@ -483,20 +754,27 @@ static int omap_dma_terminate_all(struct omap_chan *c)
 
        /*
         * Stop DMA activity: we assume the callback will not be called
-        * after omap_stop_dma() returns (even if it does, it will see
+        * after omap_dma_stop() returns (even if it does, it will see
         * c->desc is NULL and exit.)
         */
        if (c->desc) {
                c->desc = NULL;
                /* Avoid stopping the dma twice */
                if (!c->paused)
-                       omap_stop_dma(c->dma_ch);
+                       omap_dma_stop(c);
        }
 
        if (c->cyclic) {
                c->cyclic = false;
                c->paused = false;
-               omap_dma_unlink_lch(c->dma_ch, c->dma_ch);
+
+               if (__dma_omap15xx(od->plat->dma_attr)) {
+                       uint32_t val;
+
+                       val = c->plat->dma_read(CCR, c->dma_ch);
+                       val &= ~(3 << 8);
+                       c->plat->dma_write(val, CCR, c->dma_ch);
+               }
        }
 
        vchan_get_all_descriptors(&c->vc, &head);
@@ -513,7 +791,7 @@ static int omap_dma_pause(struct omap_chan *c)
                return -EINVAL;
 
        if (!c->paused) {
-               omap_stop_dma(c->dma_ch);
+               omap_dma_stop(c);
                c->paused = true;
        }
 
@@ -527,7 +805,7 @@ static int omap_dma_resume(struct omap_chan *c)
                return -EINVAL;
 
        if (c->paused) {
-               omap_start_dma(c->dma_ch);
+               omap_dma_start(c, c->desc);
                c->paused = false;
        }
 
@@ -573,6 +851,7 @@ static int omap_dma_chan_init(struct omap_dmadev *od, int dma_sig)
        if (!c)
                return -ENOMEM;
 
+       c->plat = od->plat;
        c->dma_sig = dma_sig;
        c->vc.desc_free = omap_dma_desc_free;
        vchan_init(&c->vc, &od->ddev);
@@ -594,7 +873,6 @@ static void omap_dma_free(struct omap_dmadev *od)
                tasklet_kill(&c->vc.task);
                kfree(c);
        }
-       kfree(od);
 }
 
 static int omap_dma_probe(struct platform_device *pdev)
@@ -602,10 +880,14 @@ static int omap_dma_probe(struct platform_device *pdev)
        struct omap_dmadev *od;
        int rc, i;
 
-       od = kzalloc(sizeof(*od), GFP_KERNEL);
+       od = devm_kzalloc(&pdev->dev, sizeof(*od), GFP_KERNEL);
        if (!od)
                return -ENOMEM;
 
+       od->plat = omap_get_plat_info();
+       if (!od->plat)
+               return -EPROBE_DEFER;
+
        dma_cap_set(DMA_SLAVE, od->ddev.cap_mask);
        dma_cap_set(DMA_CYCLIC, od->ddev.cap_mask);
        od->ddev.device_alloc_chan_resources = omap_dma_alloc_chan_resources;