3.8.3. DMA 驱动

此处描述的 DMA 是 ZX 平台上的系统 DMA。一些硬件 IP 内部自带的 DMA 不在这里描述的范围。

3.8.3.1. 驱动框架

U-Boot 驱动模型支持 DMA,ZX 平台中 DMA 驱动基于该框架进行实现。 相关配置为:

  • CONFIG_DMA

  • CONFIG_ZX_DMA

相关源码有:

  • include/dma.h

  • drivers/dma/dma-uclass.c

  • drivers/dma/zx_dma.c

3.8.3.2. 驱动接口

常用接口

int dma_enable(struct dma *dma);
int dma_disable(struct dma *dma);
int dma_request(struct udevice *dev, struct dma *dma);
int dma_free(struct dma *dma);
int dma_memcpy(void *dst, void *src, size_t len);
int dma_prepare_rcv_buf(struct dma *dma, void *dst, size_t size);
int dma_receive(struct dma *dma, void **dst, void *metadata);
int dma_send(struct dma *dma, void *src, size_t len, void *metadata);

3.8.3.3. 实现说明

ZX 平台上有一个系统 DMA,其支持8个通道同时工作。如规格书所定义,DMA 可以在不同的硬件 IP 之间搬运数据,系统为各硬件 IP 分配了固定的数据端口号。 使用 DMA 时,软件需要先申请到一个空闲的 DMA 通道,并将源数据端口和目标数据端口等信息配置给 DMA 通道,然后启动 DMA 进行工作。

然而上述的描述和使用方式并不能直接对应到 DTS 的配置方式以及 U-Boot 中的 DMA 表示方式, 中间需要做一些转换和说明。

在 DTS 中,可以描述某个控制器是否支持 DMA,并且配置所使用的 DMA ID 号。 在 ZX 平台中,实际只有一个 DMA,各硬件 IP 共享使用。在配置 DTS 时, 使用设备对应的 DMA 数据端口号作为 DMA ID,在运行时再给该 ID 分配可用的 DMA 通道。

如下面的示例:

spi0: spi@10400000 {
    compatible = "zx,aic-spi-v1.0";
    reg = <0x0 0x10400000 0x0 0x1000>;
    interrupts-extended = <&plic0 44 IRQ_TYPE_LEVEL_HIGH>;
    clocks = <&cmu CLK_SPI0>;
    resets = <&rst RESET_SPI0>;
    dmas = <&dma DMA_SPI0>, <&dma DMA_SPI0>;
    dma-names = "rx", "tx";
    #address-cells = <1>;
    #size-cells = <0>;
    spi-max-frequency = <24000000>;
};

这里的 DMA 配置中,描述了 SPI0 控制器使用两个 DMA,分别是 “rx”, “tx”,它们的 DMA ID 都是 SPI0 对应的 DMA 数据端口号 10。

上述的配置对应到 U-Boot 的 DMA 驱动实现时,会有一些问题。U-Boot DMA 在运行时使用下面的结构体表示:

struct dma {
        struct udevice *dev;
        unsigned long id;
};

DMA 的实例化在 DMA 驱动框架 dma-uclass.c 中进行,其中的 id 值即为 DTS 中配置的 DMA ID。这里两个 DMA 使用了相同的 ID 号,如果直接使用,无法区分不同 DMA 所映射的 DMA 通道。

ZX 平台上通过对 DMA 结构体中的 id 进行了扩展,以方便区分实际使用的不同 DMA, 如下所示:

  • bit[15:0] 表示 IP 端口号

  • bit[31:16] 表示 DMA 通道号

在 DMA 创建时赋值端口号区域,DMA request 时赋值通道号区域。由于上述两个动作是在一个调用中完成的, 因此不会有问题:

dma_get_by_name(bus, "tx", &priv->tx_dma); // drivers/dma/dma-uclass.c
|-> dma_get_by_index(dev, index, dma); // drivers/dma/dma-uclass.c
    |-> dma_of_xlate_default(dma, &args);
    |   |-> dma->id = args->args[0];
    |
    |-> dma_request(dev_dma, dma); // drivers/dma/dma-uclass.c
        |-> aic_dma_request(dma); // drivers/dma/zx_dma.c
            |-> phy_ch = aic_dma_phy_request(ud);
            |-> dma->id |= (phy_ch << AIC_DMA_PHY_CH_OFF);

U-Boot 对 struct dma 结构体中 id 的定义是唯一标识,只要能够做 DMA 区分即可, 因此上述扩展不会造成其他问题。

3.8.3.4. 初始化流程

DMA 驱动的初始化,在 DMA 第一次被使用时触发进行。

情况1: DRAM DMA 数据传输

dma_memcpy(dst_buf, src_buf, len);// drivers/dma/dma-uclass.c
|-> dma_get_device(DMA_SUPPORTS_MEM_TO_MEM, &dev);
    |-> uclass_first_device(UCLASS_DMA, &dev)
        |-> uclass_find_first_device(id, index, &dev);
        |-> uclass_get_device_tail(dev, ret, devp);
            |-> device_probe(dev); // drivers/core/device.c
                |-> drv->probe(dev);
                    aic_dma_probe(dev); // drivers/dma/zx_dma.c

情况2: 根据 DTS 配置申请 DMA

dma_get_by_name(bus, "tx", &priv->tx_dma); // drivers/dma/dma-uclass.c
|-> dma_get_by_index(dev, index, dma); // drivers/dma/dma-uclass.c
    |-> uclass_get_device_by_ofnode(UCLASS_DMA, args.node, &dev_dma);
        |   // drivers/core/uclass.c
        |-> uclass_find_device_by_ofnode(id, node, &dev);
        |-> uclass_get_device_tail(dev, ret, devp);
            |-> device_probe(dev); // drivers/core/device.c
                |-> drv->probe(dev);
                    aic_dma_probe(dev); // drivers/dma/zx_dma.c