STM32 DMA-MDMA 链接¶
简介¶
本文档描述了 STM32 DMA-MDMA 链接功能。但在深入探讨之前,让我们先介绍一下涉及的外设。
为了将数据传输从 CPU 上卸载,STM32 微处理器 (MPU) 嵌入了直接内存访问控制器 (DMA)。
STM32MP1 SoC 同时嵌入了 STM32 DMA 和 STM32 MDMA 控制器。STM32 DMA 请求路由功能通过 DMA 请求复用器 (STM32 DMAMUX) 增强。
STM32 DMAMUX
STM32 DMAMUX 将来自给定外设的任何 DMA 请求路由到任何 STM32 DMA 控制器(STM32MP1 计数为两个 STM32 DMA 控制器)通道。
STM32 DMA
STM32 DMA 主要用于为不同的外设实现中央数据缓冲区存储(通常在系统 SRAM 中)。它可以访问外部 RAM,但无法生成方便的突发传输,从而确保 AXI 的最佳负载。
STM32 MDMA
STM32 MDMA (主 DMA) 主要用于管理 RAM 数据缓冲区之间的直接数据传输,而无需 CPU 干预。它也可以用在分层结构中,该结构使用 STM32 DMA 作为 AHB 外设的第一级数据缓冲区接口,而 STM32 MDMA 则充当具有更好性能的第二级 DMA。作为 AXI/AHB 主设备,STM32 MDMA 可以控制 AXI/AHB 总线。
原理¶
STM32 DMA-MDMA 链接功能依赖于 STM32 DMA 和 STM32 MDMA 控制器的优势。
STM32 DMA 具有循环双缓冲区模式 (DBM)。在每次事务结束时(当 DMA 数据计数器 - DMA_SxNDTR - 达到 0 时),内存指针(配置为 DMA_SxSM0AR 和 DMA_SxM1AR)会被交换,并且 DMA 数据计数器会自动重新加载。这允许 SW 或 STM32 MDMA 处理一个内存区域,而第二个内存区域正由 STM32 DMA 传输填充/使用。
使用 STM32 MDMA 链表模式,单个请求会启动要传输的数据数组(节点集合),直到通道的链表指针为空。除非第一个和最后一个节点相互链接,否则最后一个节点的通道传输完成是传输的结束,在这种情况下,链表循环创建循环 MDMA 传输。
STM32 MDMA 与 STM32 DMA 有直接连接。这实现了外设之间的自主通信和同步,从而节省了 CPU 资源和总线拥塞。STM32 DMA 通道的传输完成信号可以触发 STM32 MDMA 传输。STM32 MDMA 可以通过写入其中断清除寄存器(其地址存储在 MDMA_CxMAR 中,位掩码在 MDMA_CxMDR 中)来清除由 STM32 DMA 生成的请求。
STM32 MDMA 与 STM32 DMA 的互连表¶ STM32 DMAMUX 通道
STM32 DMA 通道
STM32 DMA 传输完成信号
STM32 MDMA 请求
通道 0
DMA1 通道 0
dma1_tcf0
0x00
通道 1
DMA1 通道 1
dma1_tcf1
0x01
通道 2
DMA1 通道 2
dma1_tcf2
0x02
通道 3
DMA1 通道 3
dma1_tcf3
0x03
通道 4
DMA1 通道 4
dma1_tcf4
0x04
通道 5
DMA1 通道 5
dma1_tcf5
0x05
通道 6
DMA1 通道 6
dma1_tcf6
0x06
通道 7
DMA1 通道 7
dma1_tcf7
0x07
通道 8
DMA2 通道 0
dma2_tcf0
0x08
通道 9
DMA2 通道 1
dma2_tcf1
0x09
通道 10
DMA2 通道 2
dma2_tcf2
0x0A
通道 11
DMA2 通道 3
dma2_tcf3
0x0B
通道 12
DMA2 通道 4
dma2_tcf4
0x0C
通道 13
DMA2 通道 5
dma2_tcf5
0x0D
通道 14
DMA2 通道 6
dma2_tcf6
0x0E
通道 15
DMA2 通道 7
dma2_tcf7
0x0F
STM32 DMA-MDMA 链接功能然后使用 SRAM 缓冲区。STM32MP1 SoC 嵌入了三个快速访问的静态内部 RAM,它们具有不同的大小,用于数据存储。由于 STM32 DMA 的历史遗留问题(在微控制器中),STM32 DMA 在 DDR 上的性能较差,而在 SRAM 上的性能最佳。因此,STM32 DMA 和 STM32 MDMA 之间使用了 SRAM 缓冲区。此缓冲区被分成两个相等的周期,STM32 DMA 使用一个周期,而 STM32 MDMA 同时使用另一个周期。
dma[1:2]-tcf[0:7] .----------------. ____________ ' _________ V____________ | STM32 DMA | / __|>_ \ | STM32 MDMA | |------------| | / \ | |------------| | DMA_SxM0AR |<=>| | SRAM | |<=>| []-[]...[] | | DMA_SxM1AR | | \_____/ | | | |____________| \___<|____/ |____________|STM32 DMA-MDMA 链接使用 (struct dma_slave_config).peripheral_config 交换配置 MDMA 所需的参数。这些参数被收集到一个包含三个值的 u32 数组中
STM32 MDMA 请求(实际上是 DMAMUX 通道 ID),
用于清除传输完成中断标志的 STM32 DMA 寄存器的地址,
STM32 DMA 通道的传输完成中断标志的掩码。
STM32 DMA-MDMA 链接支持的设备树更新¶
1. 分配一个 SRAM 缓冲区
SRAM 设备树节点在 SoC 设备树中定义。您可以在板设备树中引用它来定义您的 SRAM 池。
&sram { my_foo_device_dma_pool: dma-sram@0 { reg = <0x0 0x1000>; }; };请注意起始索引,以防有其他 SRAM 使用者。策略性地定义您的池大小:为了优化链接,思路是 STM32 DMA 和 STM32 MDMA 可以在 SRAM 的每个缓冲区上同时工作。如果 SRAM 周期大于预期的 DMA 传输,那么 STM32 DMA 和 STM32 MDMA 将按顺序工作,而不是同时工作。这不是功能问题,但不是最佳的。
不要忘记在您的设备节点中引用您的 SRAM 池。您需要定义一个新属性。
&my_foo_device { ... my_dma_pool = &my_foo_device_dma_pool; };然后在您的 foo 驱动程序中获取此 SRAM 池并分配您的 SRAM 缓冲区。
2. 分配一个 STM32 DMA 通道和一个 STM32 MDMA 通道
除了您已经为“经典” DMA 操作拥有的通道之外,您还需要在您的设备树节点中定义一个额外的通道。
这个新通道必须取自 STM32 MDMA 通道,因此,要使用的 DMA 控制器的句柄是 MDMA 控制器的句柄。
&my_foo_device { [...] my_dma_pool = &my_foo_device_dma_pool; dmas = <&dmamux1 ...>, // STM32 DMA channel <&mdma1 0 0x3 0x1200000a 0 0>; // + STM32 MDMA channel };关于 STM32 MDMA 绑定
1. 请求行号:无论这里的值是什么,它都会被 MDMA 驱动程序使用通过 (struct dma_slave_config).peripheral_config 传递的 STM32 DMAMUX 通道 ID 覆盖
2. 优先级:选择非常高 (0x3),以便您的通道在请求仲裁期间优先于其他通道
3. 一个 32 位掩码,指定 DMA 通道配置:源地址和目标地址增量,每个单次传输的块传输为 128 字节
4. 一个 32 位值,指定用于确认请求的寄存器:它将被 MDMA 驱动程序覆盖,并使用通过 (struct dma_slave_config).peripheral_config 传递的 DMA 通道中断标志清除寄存器地址
5. 一个 32 位掩码,指定要写入以确认请求的值:它将被 MDMA 驱动程序覆盖,并使用通过 (struct dma_slave_config).peripheral_config 传递的 DMA 通道传输完成标志
foo 驱动程序中 STM32 DMA-MDMA 链接支持的驱动程序更新¶
0. (可选)如果 dmaengine_prep_slave_sg(),则重构原始 sg_table
如果使用 dmaengine_prep_slave_sg(),则原始 sg_table 不能按原样使用。必须从原始 sg_table 创建两个新的 sg_table。一个用于 STM32 DMA 传输(其中内存地址现在指向 SRAM 缓冲区而不是 DDR 缓冲区),另一个用于 STM32 MDMA 传输(其中内存地址指向 DDR 缓冲区)。
新的 sg_list 项必须适合 SRAM 周期长度。这是一个 DMA_DEV_TO_MEM 的示例
/* * Assuming sgl and nents, respectively the initial scatterlist and its * length. * Assuming sram_dma_buf and sram_period, respectively the memory * allocated from the pool for DMA usage, and the length of the period, * which is half of the sram_buf size. */ struct sg_table new_dma_sgt, new_mdma_sgt; struct scatterlist *s, *_sgl; dma_addr_t ddr_dma_buf; u32 new_nents = 0, len; int i; /* Count the number of entries needed */ for_each_sg(sgl, s, nents, i) if (sg_dma_len(s) > sram_period) new_nents += DIV_ROUND_UP(sg_dma_len(s), sram_period); else new_nents++; /* Create sg table for STM32 DMA channel */ ret = sg_alloc_table(&new_dma_sgt, new_nents, GFP_ATOMIC); if (ret) dev_err(dev, "DMA sg table alloc failed\n"); for_each_sg(new_dma_sgt.sgl, s, new_dma_sgt.nents, i) { _sgl = sgl; sg_dma_len(s) = min(sg_dma_len(_sgl), sram_period); /* Targets the beginning = first half of the sram_buf */ s->dma_address = sram_buf; /* * Targets the second half of the sram_buf * for odd indexes of the item of the sg_list */ if (i & 1) s->dma_address += sram_period; } /* Create sg table for STM32 MDMA channel */ ret = sg_alloc_table(&new_mdma_sgt, new_nents, GFP_ATOMIC); if (ret) dev_err(dev, "MDMA sg_table alloc failed\n"); _sgl = sgl; len = sg_dma_len(sgl); ddr_dma_buf = sg_dma_address(sgl); for_each_sg(mdma_sgt.sgl, s, mdma_sgt.nents, i) { size_t bytes = min_t(size_t, len, sram_period); sg_dma_len(s) = bytes; sg_dma_address(s) = ddr_dma_buf; len -= bytes; if (!len && sg_next(_sgl)) { _sgl = sg_next(_sgl); len = sg_dma_len(_sgl); ddr_dma_buf = sg_dma_address(_sgl); } else { ddr_dma_buf += bytes; } }不要忘记在使用 dmaengine_prep_slave_sg() 获取描述符后释放这些新的 sg_table。
1. 设置控制器特定参数
首先,使用 dmaengine_slave_config() 和 struct dma_slave_config 来配置 STM32 DMA 通道。您只需要注意 DMA 地址,内存地址(取决于传输方向)必须指向您的 SRAM 缓冲区,并且设置 (struct dma_slave_config).peripheral_size != 0。
STM32 DMA 驱动程序将检查 (struct dma_slave_config).peripheral_size 以确定是否正在使用链接。如果使用,则 STM32 DMA 驱动程序将 (struct dma_slave_config).peripheral_config 填充一个包含三个 u32 的数组:第一个包含 STM32 DMAMUX 通道 ID,第二个包含通道中断标志清除寄存器地址,第三个包含通道传输完成标志掩码。
然后,使用 dmaengine_slave_config 和另一个 struct dma_slave_config 来配置 STM32 MDMA 通道。注意 DMA 地址,设备地址(取决于传输方向)必须指向您的 SRAM 缓冲区,并且内存地址必须指向最初用于“经典” DMA 操作的缓冲区。使用先前由 STM32 DMA 驱动程序更新的 (struct dma_slave_config).peripheral_size 和 .peripheral_config,来设置用于配置 STM32 MDMA 通道的 struct dma_slave_config 的 (struct dma_slave_config).peripheral_size 和 .peripheral_config。
struct dma_slave_config dma_conf; struct dma_slave_config mdma_conf; memset(&dma_conf, 0, sizeof(dma_conf)); [...] config.direction = DMA_DEV_TO_MEM; config.dst_addr = sram_dma_buf; // SRAM buffer config.peripheral_size = 1; // peripheral_size != 0 => chaining dmaengine_slave_config(dma_chan, &dma_config); memset(&mdma_conf, 0, sizeof(mdma_conf)); config.direction = DMA_DEV_TO_MEM; mdma_conf.src_addr = sram_dma_buf; // SRAM buffer mdma_conf.dst_addr = rx_dma_buf; // original memory buffer mdma_conf.peripheral_size = dma_conf.peripheral_size; // <- dma_conf mdma_conf.peripheral_config = dma_config.peripheral_config; // <- dma_conf dmaengine_slave_config(mdma_chan, &mdma_conf);2. 获取 STM32 DMA 通道事务的描述符
以与获取“经典” DMA 操作的描述符相同的方式,您只需将原始 sg_list(如果使用 dmaengine_prep_slave_sg())替换为使用 SRAM 缓冲区的新 sg_list,或者将原始缓冲区地址、长度和周期(如果使用 dmaengine_prep_dma_cyclic())替换为新的 SRAM 缓冲区。
3. 获取 STM32 MDMA 通道事务的描述符
如果您先前使用以下方式获取描述符(用于 STM32 DMA):
dmaengine_prep_slave_sg(),则对 STM32 MDMA 使用 dmaengine_prep_slave_sg();
dmaengine_prep_dma_cyclic(),则对 STM32 MDMA 使用 dmaengine_prep_dma_cyclic()。
使用使用 SRAM 缓冲区的新 sg_list(如果使用 dmaengine_prep_slave_sg()),或者,取决于传输方向,要么使用原始 DDR 缓冲区(如果使用 DMA_DEV_TO_MEM),要么使用 SRAM 缓冲区(如果使用 DMA_MEM_TO_DEV),源地址先前已使用 dmaengine_slave_config() 设置。
4. 提交两个事务
在提交您的事务之前,您可能需要在传输结束时(dmaengine_prep_slave_sg())或周期(dmaengine_prep_dma_cyclic())完成时定义要在哪个描述符上调用回调。取决于方向,在完成整个传输的描述符上设置回调
DMA_DEV_TO_MEM:在“MDMA”描述符上设置回调
DMA_MEM_TO_DEV:在“DMA”描述符上设置回调
然后,无论顺序如何,使用 dmaengine_tx_submit() 提交描述符。
5. 发出挂起的请求(并等待回调通知)
由于 STM32 MDMA 通道传输由 STM32 DMA 触发,因此您必须在 STM32 DMA 通道之前发出 STM32 MDMA 通道。
如果存在,您的回调将被调用以警告您有关整个传输或周期完成的结束。
不要忘记终止两个通道。STM32 DMA 通道配置为循环双缓冲区模式,因此它不会被 HW 禁用,您需要终止它。在 sg 传输的情况下,STM32 MDMA 通道将被 HW 停止,但在循环传输的情况下则不会。无论传输类型如何,您都可以终止它。
STM32 DMA-MDMA 链接 DMA_MEM_TO_DEV 特殊情况
DMA_MEM_TO_DEV 中的 STM32 DMA-MDMA 链接是一种特殊情况。实际上,STM32 MDMA 使用 DDR 数据馈送 SRAM 缓冲区,并且 STM32 DMA 从 SRAM 缓冲区读取数据。因此,当 STM32 DMA 开始读取时,一些数据(第一个周期)必须复制到 SRAM 缓冲区中。
一个技巧可能是暂停 STM32 DMA 通道(这将引发传输完成信号,触发 STM32 MDMA 通道),但是 STM32 DMA 读取的第一个数据可能是“错误的”。正确的方法是使用 dmaengine_prep_dma_memcpy() 准备第一个 SRAM 周期。然后应该从 sg 或循环传输中“删除”此第一个周期。
由于这种复杂性,最好将 STM32 DMA-MDMA 链接用于 DMA_DEV_TO_MEM,并保持“经典” DMA 使用于 DMA_MEM_TO_DEV,除非您不害怕。
资源¶
- 作者:
Amelie Delaunay <amelie.delaunay@foss.st.com>