【问题标题】:PCM DMA Engine Using AXI-DMA IP on Xilinx Zynq Based Platform在基于 Xilinx Zynq 的平台上使用 AXI-DMA IP 的 PCM DMA 引擎
【发布时间】:2017-07-14 09:55:30
【问题描述】:

我正在尝试在基于 Zynq-7000 的平台上使用 DMA 引擎将 PCM 流传输到 Zynq PL 中的自定义 I2S 控制器。我的 I2S 控制器连接到外部放大器。我想通过 AXI-DMA 控制器使用 DMA。这是目前我的数据路径:

我在 Zynq PS 上使用 Linux 4.10 内核。我使用 Linux ASoC 子系统生成 pcm 流并控制我的外部音频放大器。我有 512MB 的 DDR RAM 连接到 Zynq。我想使用这个 RAM 的一部分来运行我的 DMA 引擎。我的 I2S 控制器通过 AXI-Lite 控制接口运行,并使用 AXI4-Stream 接口进行音频流传输。此 IP 已经过测试,可以假定可以很好地与这些接口配合使用。

过去,我在 Zynq PS 中使用 PL330 来驱动 DMA 引擎。我的 I2S 控制器过去在其 AXI-Lite 寄存器空间中内置了一个 FIFO,因此所有 DMA 传输都通过 AXI-Lite 接口。我只是将 DMA 引擎指向这个 FIFO 地址,如下所示:

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, &i2s->playback_dma_data,
        &i2s->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(&pdev->dev, sizeof(*i2s), GFP_KERNEL);
    if (!i2s)
        return -ENOMEM;

    platform_set_drvdata(pdev, i2s);

    i2s->playback_dma_data.addr = I2S_BASE_ADDRESS + TX_FIFO_OFFSET;
    i2s->playback_dma_data.addr_width = 4;
    i2s->playback_dma_data.maxburst = 1;

    i2s->capture_dma_data.addr = I2S_BASE_ADDRESS + RX_FIFO_OFFSET;
    i2s->capture_dma_data.addr_width = 4;
    i2s->capture_dma_data.maxburst = 1;

    devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0);

    return 0;
}

设备树:

dmac_s: dmac@f8003000 {
    compatible = "arm,pl330", "arm,primecell";
    reg = <0xf8003000 0x1000>;
    interrupt-parent = <&intc>;
    interrupt-names = "abort", "dma0", "dma1", "dma2", "dma3",
        "dma4", "dma5", "dma6", "dma7";
    interrupts = <0 13 4>,
        <0 14 4>, <0 15 4>,
        <0 16 4>, <0 17 4>,
        <0 40 4>, <0 41 4>,
        <0 42 4>, <0 43 4>;
    #dma-cells = <1>;
    #dma-channels = <8>;
    #dma-requests = <4>;
    clocks = <&clkc 27>;
    clock-names = "apb_pclk";
};

axi_i2s@0x43C00000 {
    #sound-dai-cells = <1>;
    compatible = "my,driver";
    reg = <0x43C00000 0x10000>;
    clocks = <&clkc 15>;
    clock-names = "axi";
    dmas = <&dmac_s 0>, <&dmac_s 1>;
    dma-names = "tx", "rx";
    xlnx,dma-type = <0x1>;
};

新设置:

/* AXI DMA */
axi_dma_0: axidma@40400000 {
    compatible = "xlnx,axi-dma-1.00.a";
    #dma-cells = <1>;
    reg = < 0x40400000 0x10000 >;
    xlnx,addrwidth = <0x20>;
    clocks = <&clkc 15>, <&clkc 15>, <&clkc 15>, <&clkc 15>;
    clock-names = "s_axi_lite_aclk", "m_axi_sg_aclk", "m_axi_mm2s_aclk", "m_axi_s2mm_aclk";
    interrupt-parent = <&intc>;
    interrupts = < 0 33 4  0 34 4>;
    dma-ranges = <0x00000000 0x00000000 0x20000000>;
    //xlnx,include-sg ;
    dma-channel@40400000 {
        compatible = "xlnx,axi-dma-mm2s-channel";
        dma-channels = <0x1>;
        interrupts = < 0 33 4 >;
        xlnx,datawidth = <0x20>;
        xlnx,device-id = <0x0>;
        //xlnx,include-dre ;
    } ;
    dma-channel@40400030 {
        compatible = "xlnx,axi-dma-s2mm-channel";
        dma-channels = <0x1>;
        interrupts = < 0 34 4 >;
        xlnx,datawidth = <0x20>;
        xlnx,device-id = <0x0>;
        //xlnx,include-dre ;
    } ;
};

/* New stream version */
axi_i2s@0x43C10000 {
    #sound-dai-cells = <1>;
    compatible = "my,driver";
    reg = <0x43C10000 0x10000>;
    clocks = <&clkc 15>;
    clock-names = "axi";
    dmas = <&axi_dma_0 0
        &axi_dma_0 1>;
    dma-names = "axidma0", "axidma1";
    xlnx,dma-type = <0x1>;
};

显然,有些细节被遗漏了,但这些都是相关的部分。

现在,我不太清楚如何使用 AXI-DMA IP 而不是 PL330 将此驱动程序更改为 DMA。由于 DMA 传输将在没有 FIFO 的不同内存区域中完成,我如何设置 snd_dmaengine_dai_dma_data 结构以写入 AXI-DMA 内存?特别是本节:

i2s->playback_dma_data.addr = I2S_BASE_ADDRESS + TX_FIFO_OFFSET;
i2s->playback_dma_data.addr_width = 4;
i2s->playback_dma_data.maxburst = 1;

i2s->capture_dma_data.addr = I2S_BASE_ADDRESS + RX_FIFO_OFFSET;
i2s->capture_dma_data.addr_width = 4;
i2s->capture_dma_data.maxburst = 1;

AXI-DMA IP 可以访问我所有 512MB 的 DDR,但我不知道内核会在哪里为我的 DMA 传输分配内存。

【问题讨论】:

    标签: linux xilinx alsa pcm dma


    【解决方案1】:

    您将需要一个 linux 内核驱动程序来分配将用作 DMA 缓冲区的内存。如果你需要一个大的连续缓冲区,你需要在你的 linux 内核中启用 CMA。在您的驱动程序中,您可以使用 kmalloc 来分配内存。

    作为内核驱动程序的参考,我建议使用 udmabuf (https://github.com/ikwzm/udmabuf)。

    udmabuf 在 /sys/class/udmabuf 中显示为设备,并且可以在用户空间中读取 DMA 缓冲区的物理地址。将此地址传递到您的 AXI DMA 缓冲区,作为将数据推送到的目标区域。

    更多关于 Zynq 上 linux 中 DMA 的信息:https://forums.xilinx.com/xlnx/attachments/xlnx/ELINUX/10658/1/drivers-session4-dma-4public.pdf

    编辑: 您可以在此处找到 AXI DMA 的示例 linux 驱动程序 https://forums.xilinx.com/xlnx/attachments/xlnx/ELINUX/10658/2/axidma.c.golden

    【讨论】:

      猜你喜欢
      • 2022-01-13
      • 2016-09-04
      • 2016-03-31
      • 1970-01-01
      • 2014-10-15
      • 2013-08-05
      • 2021-05-07
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多