title: PCM DMA Engine Using AXI-DMA IP on Xilinx Zynq Based Platform
body: <p>I am trying to use a DMA engine on a Zynq-7000 based platform to transfer a PCM stream to a custom I2S controller in the Zynq PL. My I2S controller interfaces to an external amp. I want to use DMA through an AXI-DMA Controller. This is currently my datapath:</p>
  
  <p><a href="https://i.stack.imgur.com/j4xGo.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/j4xGo.png" alt="axi_dma_path"></a>
  I am using a Linux 4.10 kernel on the Zynq PS. I use Linux ASoC subsystem to generate pcm streams and control my external audio amp. I have 512MB of DDR RAM connected to Zynq. I would like to use a section of this RAM to run my DMA engine. My I2S controller runs off an AXI-Lite control interface and uses an AXI4-Stream interface for audio streaming. This IP has been tested and can be assumed to work well with these interfaces.</p>
  
  <p>In the past, I have used the PL330 in the Zynq PS to drive the DMA engine. My I2S controller used to have a FIFO built into it's AXI-Lite register space, so all DMA transfers went through AXI-Lite interface. I simply pointed the DMA engine to this FIFO address like so:</p>
  
  <pre><code>struct axi_i2s {
      struct snd_dmaengine_dai_dma_data playback_dma_data;
      struct snd_dmaengine_dai_dma_data capture_dma_data;
  };
  
  static int axi_i2s_dai_probe(struct snd_soc_dai *dai)
  {
      struct axi_i2s *i2s = snd_soc_dai_get_drvdata(dai);
  
      snd_soc_dai_init_dma_data(dai, &amp;i2s-&gt;playback_dma_data,
          &amp;i2s-&gt;capture_dma_data);
  
      return 0;
  }
  
  static struct snd_soc_dai_driver axi_i2s_dai = {
      .probe = axi_i2s_dai_probe,
      .playback = {
          .channels_min = 1,
          .channels_max = 8,
          .rates = I2S_RATES,
          .formats = SNDRV_PCM_FMTBIT_S16_LE |
                  SNDRV_PCM_FMTBIT_S24_LE |
                  SNDRV_PCM_FMTBIT_S32_LE,
      },
  };
  
  static int axi_i2s_probe(struct platform_device *pdev)
  {
      axi_i2s *i2s;
  
      i2s = devm_kzalloc(&amp;pdev-&gt;dev, sizeof(*i2s), GFP_KERNEL);
      if (!i2s)
          return -ENOMEM;
  
      platform_set_drvdata(pdev, i2s);
  
      i2s-&gt;playback_dma_data.addr = I2S_BASE_ADDRESS + TX_FIFO_OFFSET;
      i2s-&gt;playback_dma_data.addr_width = 4;
      i2s-&gt;playback_dma_data.maxburst = 1;
  
      i2s-&gt;capture_dma_data.addr = I2S_BASE_ADDRESS + RX_FIFO_OFFSET;
      i2s-&gt;capture_dma_data.addr_width = 4;
      i2s-&gt;capture_dma_data.maxburst = 1;
  
      devm_snd_dmaengine_pcm_register(&amp;pdev-&gt;dev, NULL, 0);
  
      return 0;
  }
  </code></pre>
  
  <p>Devicetree:</p>
  
  <pre><code>dmac_s: dmac@f8003000 {
      compatible = "arm,pl330", "arm,primecell";
      reg = &lt;0xf8003000 0x1000&gt;;
      interrupt-parent = &lt;&amp;intc&gt;;
      interrupt-names = "abort", "dma0", "dma1", "dma2", "dma3",
          "dma4", "dma5", "dma6", "dma7";
      interrupts = &lt;0 13 4&gt;,
          &lt;0 14 4&gt;, &lt;0 15 4&gt;,
          &lt;0 16 4&gt;, &lt;0 17 4&gt;,
          &lt;0 40 4&gt;, &lt;0 41 4&gt;￼,
          &lt;0 42 4&gt;, &lt;0 43 4&gt;;￼
      #dma-cells = &lt;1&gt;;
      #dma-channels = &lt;8&gt;;￼
      #dma-requests = &lt;4&gt;;
      clocks = &lt;&amp;clkc 27&gt;;
      clock-names = "apb_pclk";
  };
  
  axi_i2s@0x43C00000 {
      #sound-dai-cells = &lt;1&gt;;
      compatible = "my,driver";
      reg = &lt;0x43C00000 0x10000&gt;;
      clocks = &lt;&amp;clkc 15&gt;;
      clock-names = "axi";
      dmas = &lt;&amp;dmac_s 0&gt;, &lt;&amp;dmac_s 1&gt;;
      dma-names = "tx", "rx";
      xlnx,dma-type = &lt;0x1&gt;;
  };
  </code></pre>
  
  <p>New set up:</p>
  
  <pre><code>/* AXI DMA */
  axi_dma_0: axidma@40400000 {
      compatible = "xlnx,axi-dma-1.00.a";
      #dma-cells = &lt;1&gt;;
      reg = &lt; 0x40400000 0x10000 &gt;;
      xlnx,addrwidth = &lt;0x20&gt;;
      clocks = &lt;&amp;clkc 15&gt;, &lt;&amp;clkc 15&gt;, &lt;&amp;clkc 15&gt;, &lt;&amp;clkc 15&gt;;
      clock-names = "s_axi_lite_aclk", "m_axi_sg_aclk", "m_axi_mm2s_aclk", "m_axi_s2mm_aclk";
      interrupt-parent = &lt;&amp;intc&gt;;
      interrupts = &lt; 0 33 4  0 34 4&gt;;
      dma-ranges = &lt;0x00000000 0x00000000 0x20000000&gt;;
      //xlnx,include-sg ;
      dma-channel@40400000 {
          compatible = "xlnx,axi-dma-mm2s-channel";
          dma-channels = &lt;0x1&gt;;
          interrupts = &lt; 0 33 4 &gt;;
          xlnx,datawidth = &lt;0x20&gt;;
          xlnx,device-id = &lt;0x0&gt;;
          //xlnx,include-dre ;
      } ;
      dma-channel@40400030 {
          compatible = "xlnx,axi-dma-s2mm-channel";
          dma-channels = &lt;0x1&gt;;
          interrupts = &lt; 0 34 4 &gt;;
          xlnx,datawidth = &lt;0x20&gt;;
          xlnx,device-id = &lt;0x0&gt;;
          //xlnx,include-dre ;
      } ;
  };
  
  /* New stream version */
  axi_i2s@0x43C10000 {
      #sound-dai-cells = &lt;1&gt;;
      compatible = "my,driver";
      reg = &lt;0x43C10000 0x10000&gt;;
      clocks = &lt;&amp;clkc 15&gt;;
      clock-names = "axi";
      dmas = &lt;&amp;axi_dma_0 0
          &amp;axi_dma_0 1&gt;;
      dma-names = "axidma0", "axidma1";
      xlnx,dma-type = &lt;0x1&gt;;
  };
  </code></pre>
  
  <p>Obviously, some details are left out, but these are the relevant bits.</p>
  
  <p>Now, I can't quite figure out how to change this driver to DMA using the AXI-DMA IP instead of the PL330. Since the DMA transfers will be done in a different memory region without a FIFO, how do I set up the snd_dmaengine_dai_dma_data structs to write to the AXI-DMA memory? Specifically this section:</p>
  
  <pre><code>i2s-&gt;playback_dma_data.addr = I2S_BASE_ADDRESS + TX_FIFO_OFFSET;
  i2s-&gt;playback_dma_data.addr_width = 4;
  i2s-&gt;playback_dma_data.maxburst = 1;
  
  i2s-&gt;capture_dma_data.addr = I2S_BASE_ADDRESS + RX_FIFO_OFFSET;
  i2s-&gt;capture_dma_data.addr_width = 4;
  i2s-&gt;capture_dma_data.maxburst = 1;
  </code></pre>
  
  <p>The AXI-DMA IP has access to all 512MB of my DDR, but I don't know where the kernel will allocate memory for my DMA transfers.</p>