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,除非您不害怕。

資源

應用筆記、資料表和參考手冊可在 ST 網站上找到 (STM32MP1)。

專門關注三個應用筆記 (AN5224, AN4031 & AN5001) 處理 STM32 DMAMUX、STM32 DMA 和 STM32 MDMA。

作者: