dmaengine: s3c24xx-dma: Add cyclic transfer support
authorVasily Khoruzhick <anarsoul@gmail.com>
Tue, 20 May 2014 20:23:02 +0000 (23:23 +0300)
committerVinod Koul <vinod.koul@intel.com>
Sun, 1 Jun 2014 16:52:51 +0000 (22:22 +0530)
Many audio interface drivers require support of cyclic transfers to work
correctly, for example Samsung ASoC DMA driver. This patch adds support
for cyclic transfers to the s3c24xx-dma driver

Signed-off-by: Vasily Khoruzhick <anarsoul@gmail.com>
Reviewed-by: Heiko Stuebner <heiko@sntech.de>
Signed-off-by: Vinod Koul <vinod.koul@intel.com>
drivers/dma/s3c24xx-dma.c

index 6528eeda1575df33a930faff8823048e66ec00a7..012520c9fd79174a44392fa3e2a448db2bccdce7 100644 (file)
@@ -164,6 +164,7 @@ struct s3c24xx_sg {
  * @disrcc: value for source control register
  * @didstc: value for destination control register
  * @dcon: base value for dcon register
+ * @cyclic: indicate cyclic transfer
  */
 struct s3c24xx_txd {
        struct virt_dma_desc vd;
@@ -173,6 +174,7 @@ struct s3c24xx_txd {
        u32 disrcc;
        u32 didstc;
        u32 dcon;
+       bool cyclic;
 };
 
 struct s3c24xx_dma_chan;
@@ -669,8 +671,10 @@ static irqreturn_t s3c24xx_dma_irq(int irq, void *data)
                /* when more sg's are in this txd, start the next one */
                if (!list_is_last(txd->at, &txd->dsg_list)) {
                        txd->at = txd->at->next;
+                       if (txd->cyclic)
+                               vchan_cyclic_callback(&txd->vd);
                        s3c24xx_dma_start_next_sg(s3cchan, txd);
-               } else {
+               } else if (!txd->cyclic) {
                        s3cchan->at = NULL;
                        vchan_cookie_complete(&txd->vd);
 
@@ -682,6 +686,12 @@ static irqreturn_t s3c24xx_dma_irq(int irq, void *data)
                                s3c24xx_dma_start_next_txd(s3cchan);
                        else
                                s3c24xx_dma_phy_free(s3cchan);
+               } else {
+                       vchan_cyclic_callback(&txd->vd);
+
+                       /* Cyclic: reset at beginning */
+                       txd->at = txd->dsg_list.next;
+                       s3c24xx_dma_start_next_sg(s3cchan, txd);
                }
        }
        spin_unlock(&s3cchan->vc.lock);
@@ -877,6 +887,104 @@ static struct dma_async_tx_descriptor *s3c24xx_dma_prep_memcpy(
        return vchan_tx_prep(&s3cchan->vc, &txd->vd, flags);
 }
 
+static struct dma_async_tx_descriptor *s3c24xx_dma_prep_dma_cyclic(
+       struct dma_chan *chan, dma_addr_t addr, size_t size, size_t period,
+       enum dma_transfer_direction direction, unsigned long flags,
+       void *context)
+{
+       struct s3c24xx_dma_chan *s3cchan = to_s3c24xx_dma_chan(chan);
+       struct s3c24xx_dma_engine *s3cdma = s3cchan->host;
+       const struct s3c24xx_dma_platdata *pdata = s3cdma->pdata;
+       struct s3c24xx_dma_channel *cdata = &pdata->channels[s3cchan->id];
+       struct s3c24xx_txd *txd;
+       struct s3c24xx_sg *dsg;
+       unsigned sg_len;
+       dma_addr_t slave_addr;
+       u32 hwcfg = 0;
+       int i;
+
+       dev_dbg(&s3cdma->pdev->dev,
+               "prepare cyclic transaction of %zu bytes with period %zu from %s\n",
+               size, period, s3cchan->name);
+
+       if (!is_slave_direction(direction)) {
+               dev_err(&s3cdma->pdev->dev,
+                       "direction %d unsupported\n", direction);
+               return NULL;
+       }
+
+       txd = s3c24xx_dma_get_txd();
+       if (!txd)
+               return NULL;
+
+       txd->cyclic = 1;
+
+       if (cdata->handshake)
+               txd->dcon |= S3C24XX_DCON_HANDSHAKE;
+
+       switch (cdata->bus) {
+       case S3C24XX_DMA_APB:
+               txd->dcon |= S3C24XX_DCON_SYNC_PCLK;
+               hwcfg |= S3C24XX_DISRCC_LOC_APB;
+               break;
+       case S3C24XX_DMA_AHB:
+               txd->dcon |= S3C24XX_DCON_SYNC_HCLK;
+               hwcfg |= S3C24XX_DISRCC_LOC_AHB;
+               break;
+       }
+
+       /*
+        * Always assume our peripheral desintation is a fixed
+        * address in memory.
+        */
+       hwcfg |= S3C24XX_DISRCC_INC_FIXED;
+
+       /*
+        * Individual dma operations are requested by the slave,
+        * so serve only single atomic operations (S3C24XX_DCON_SERV_SINGLE).
+        */
+       txd->dcon |= S3C24XX_DCON_SERV_SINGLE;
+
+       if (direction == DMA_MEM_TO_DEV) {
+               txd->disrcc = S3C24XX_DISRCC_LOC_AHB |
+                             S3C24XX_DISRCC_INC_INCREMENT;
+               txd->didstc = hwcfg;
+               slave_addr = s3cchan->cfg.dst_addr;
+               txd->width = s3cchan->cfg.dst_addr_width;
+       } else {
+               txd->disrcc = hwcfg;
+               txd->didstc = S3C24XX_DIDSTC_LOC_AHB |
+                             S3C24XX_DIDSTC_INC_INCREMENT;
+               slave_addr = s3cchan->cfg.src_addr;
+               txd->width = s3cchan->cfg.src_addr_width;
+       }
+
+       sg_len = size / period;
+
+       for (i = 0; i < sg_len; i++) {
+               dsg = kzalloc(sizeof(*dsg), GFP_NOWAIT);
+               if (!dsg) {
+                       s3c24xx_dma_free_txd(txd);
+                       return NULL;
+               }
+               list_add_tail(&dsg->node, &txd->dsg_list);
+
+               dsg->len = period;
+               /* Check last period length */
+               if (i == sg_len - 1)
+                       dsg->len = size - period * i;
+               if (direction == DMA_MEM_TO_DEV) {
+                       dsg->src_addr = addr + period * i;
+                       dsg->dst_addr = slave_addr;
+               } else { /* DMA_DEV_TO_MEM */
+                       dsg->src_addr = slave_addr;
+                       dsg->dst_addr = addr + period * i;
+               }
+       }
+
+       return vchan_tx_prep(&s3cchan->vc, &txd->vd, flags);
+}
+
 static struct dma_async_tx_descriptor *s3c24xx_dma_prep_slave_sg(
                struct dma_chan *chan, struct scatterlist *sgl,
                unsigned int sg_len, enum dma_transfer_direction direction,
@@ -1197,6 +1305,7 @@ static int s3c24xx_dma_probe(struct platform_device *pdev)
 
        /* Initialize slave engine for SoC internal dedicated peripherals */
        dma_cap_set(DMA_SLAVE, s3cdma->slave.cap_mask);
+       dma_cap_set(DMA_CYCLIC, s3cdma->slave.cap_mask);
        dma_cap_set(DMA_PRIVATE, s3cdma->slave.cap_mask);
        s3cdma->slave.dev = &pdev->dev;
        s3cdma->slave.device_alloc_chan_resources =
@@ -1206,6 +1315,7 @@ static int s3c24xx_dma_probe(struct platform_device *pdev)
        s3cdma->slave.device_tx_status = s3c24xx_dma_tx_status;
        s3cdma->slave.device_issue_pending = s3c24xx_dma_issue_pending;
        s3cdma->slave.device_prep_slave_sg = s3c24xx_dma_prep_slave_sg;
+       s3cdma->slave.device_prep_dma_cyclic = s3c24xx_dma_prep_dma_cyclic;
        s3cdma->slave.device_control = s3c24xx_dma_control;
 
        /* Register as many memcpy channels as there are physical channels */