驱动支持
1. Clock 驱动
本章节描述 ArtInChip 平台的 U-Boot 时钟配置相关内容。
1.1. 驱动框架
U-Boot 驱动模型支持 Clock,ArtInChip 平台中 Clock 驱动基于该框架进行实现。 相关配置为:
- CONFIG_CLK
- CONFIG_CLK_ARTINCHIP
- CONFIG_CLK_ARTINCHIP_CMU
- CONFIG_SPL_CLK_ARTINCHIP
- CONFIG_SPL_CLK_ARTINCHIP_CMU
相关源码有:
include/clk.h
include/clk-uclass.h
drivers/clk/clk.c
drivers/clk/clk-uclass.c
drivers/clk/artinchip/clk-aic.h
drivers/clk/artinchip/clk-artinchip.c
drivers/clk/artinchip/clk-cmu.c
1.2. 驱动接口
相关的 Clock 驱动接口有:
int clk_get_by_index_platdata(struct udevice *dev, int index,
struct phandle_1_arg *cells, struct clk *clk);
int clk_get_by_index(struct udevice *dev, int index, struct clk *clk);
int clk_get_by_index_nodev(ofnode node, int index, struct clk *clk);
int clk_get_bulk(struct udevice *dev, struct clk_bulk *bulk);
int clk_get_by_name(struct udevice *dev, const char *name, struct clk *clk);
int clk_release_all(struct clk *clk, int count);
int clk_enable(struct clk *clk);
int clk_disable(struct clk *clk);
ulong clk_set_rate(struct clk *clk, ulong rate);
ulong clk_get_rate(struct clk *clk);
1.3. 初始化和使用
通常硬件设备初始化时,需要配置对应的时钟。Clock 驱动的 probe 在时钟设备第一次被获取时触发。
clk_get_by_index(); // drivers/clk/clk-uclass.c
|-> clk_get_by_index_tail();
|-> uclass_get_device_by_ofnode(UCLASS_CLK, args->node, &dev_clk);
|-> uclass_find_device_by_ofnode(id, node, &dev);
|-> uclass_get_device_tail(dev, ret, devp); // drivers/core/uclass.c
|-> device_probe(dev); // drivers/core/device.c
|-> drv->probe(dev);
aic_clk_probe(dev);
// drivers/clk/artinchip/clk-cmu.c
设备使用的时钟通过时钟树进行管理。在时钟树中,每一个时钟都被分配一个具体的 ID, 并且在 DTS 中配置给需要的硬件设备。设备初始化时,通过 FDT 获取对应的时钟设备。
DTS 中时钟配置示例:
dma: dma-controller@10000000 {
compatible = "artinchip,aic-dma";
...
clocks = <&ccu CLK_DMA>;
...
};
相关 ID 定义可参考:
include/dt-bindings/clock/artinchip,aic-cmu.h
获取时钟设备的流程:
clk_get_by_index(dev, index, clk); // drivers/clk/clk-uclass.c
| // 此处 index 是 DTS 中配置给该设备的第几个时钟
|
|-> clk_get_by_index_tail();
|-> uclass_get_device_by_ofnode(UCLASS_CLK, args->node, &dev_clk);
|-> clk_of_xlate_default(clk, args);
|-> clk->id = args->args[0]; // 获取到具体的时钟 ID
需要设置和获取相关时钟信息时,通过 clk->id
访问时钟树。
clk_set_rate(clk, rate); // drivers/clk/clk-uclass.c
|-> ops->set_rate(clk, rate);
artinchip_clk_set_rate(clk, rate); // drivers/clk/artinchip/clk-artinchip.c
|-> aic_get_clk_info(priv->tree, clk->id, &index);
// 驱动内部,使用 clk-id 获取对应的时钟节点
2. Reset 驱动
本章节描述 ArtInChip 平台的 U-Boot 复位驱动相关内容。
2.1. 驱动框架
U-Boot 驱动模型支持 Reset,ArtInChip 平台中 Reset 驱动基于该框架进行实现。 相关配置为:
- CONFIG_DM_RESET
- CONFIG_RESET_ARTINCHIP
相关源码有:
include/reset.h
include/reset-uclass.h
drivers/reset/reset-uclass.c
drivers/reset/reset-artinchip.c
2.2. 驱动接口
相关的复位驱动接口有:
int reset_get_by_index(struct udevice *dev, int index,
struct reset_ctl *reset_ctl);
int reset_get_by_name(struct udevice *dev, const char *name,
struct reset_ctl *reset_ctl);
int reset_get_by_index_nodev(ofnode node, int index,
struct reset_ctl *reset_ctl);
int reset_get_bulk(struct udevice *dev, struct reset_ctl_bulk *bulk);
int reset_request(struct reset_ctl *reset_ctl);
int reset_free(struct reset_ctl *reset_ctl);
int reset_assert(struct reset_ctl *reset_ctl);
int reset_deassert(struct reset_ctl *reset_ctl);
2.3. 初始化和使用
通常硬件设备初始化时,需要对其进行一次复位。Reset 驱动的 probe 在复位控制器第一次被获取时触发。
reset_get_by_index(); // drivers/reset/reset-uclass.c
|-> reset_get_by_index_tail(ret, dev_ofnode(dev), &args, "resets",
| index > 0, reset_ctl);
|-> uclass_get_device_by_ofnode(UCLASS_RESET, args->node, &dev_reset);
|-> 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);
artinchip_reset_probe(dev);
// drivers/reset/reset-artinchip.c
系统给每一个设备的复位控制器分配了一个 ID,并且在设备的 DTS 配置中将 ID 分配到具体的设备。 设备初始化时,通过 FDT 的配置获取相应的复位控制设备。
DTS 中复位控制器配置示例:
dma: dma-controller@10000000 {
compatible = "artinchip,aic-dma";
...
resets = <&rst RESET_DMA>;
...
};
相关 ID 定义可参考:
include/dt-bindings/reset/artinchip,aic-reset.h
获取复位控制器的流程:
reset_get_by_index(dev, index, reset_ctl); // drivers/reset/reset-uclass.c
| // 此处 index 是 DTS 中配置给该设备的第几个复位控制设备
|
|-> reset_get_by_index_tail();
|-> uclass_get_device_by_ofnode(UCLASS_RESET, args->node, &dev_reset);
|-> resetof_xlate_default(reset_ctl, args);
|-> reset_ctl->id = args->args[0]; // 获取到具体的复位控制器 ID
需要对设备进行复位时,通过 reset_ctl->id
进行访问和设置硬件。
reset_assert(reset_ctl); // drivers/reset/reset-uclass.c
|-> ops->rst_assert(reset_ctl);
artinchip_reset_assert(reset_ctrl); // drivers/reset/reset-artinchip.c
3. DMA 驱动
此处描述的 DMA 是 ArtInChip 平台上的系统 DMA。一些硬件 IP 内部自带的 DMA 不在这里描述的范围。
3.1. 驱动框架
U-Boot 驱动模型支持 DMA,ArtInChip 平台中 DMA 驱动基于该框架进行实现。 相关配置为:
- CONFIG_DMA
- CONFIG_ARTINCHIP_DMA
相关源码有:
include/dma.h
drivers/dma/dma-uclass.c
drivers/dma/artinchip_dma.c
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.3. 实现说明
ArtInChip 平台上有一个系统 DMA,其支持8个通道同时工作。如规格书所定义,DMA 可以在不同的硬件 IP 之间搬运数据,系统为各硬件 IP 分配了固定的数据端口号。 使用 DMA 时,软件需要先申请到一个空闲的 DMA 通道,并将源数据端口和目标数据端口等信息配置给 DMA 通道,然后启动 DMA 进行工作。
然而上述的描述和使用方式并不能直接对应到 DTS 的配置方式以及 U-Boot 中的 DMA 表示方式, 中间需要做一些转换和说明。
在 DTS 中,可以描述某个控制器是否支持 DMA,并且配置所使用的 DMA ID 号。 在 ArtInChip 平台中,实际只有一个 DMA,各硬件 IP 共享使用。在配置 DTS 时, 使用设备对应的 DMA 数据端口号作为 DMA ID,在运行时再给该 ID 分配可用的 DMA 通道。
如下面的示例:
spi0: spi@10400000 {
compatible = "artinchip,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 通道。
ArtInChip 平台上通过对 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/artinchip_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.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/artinchip_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/artinchip_dma.c
4. SPI 驱动
SPI 在 U-Boot 中主要用于支持 SPI NAND/NOR 存储设备。目前 ArtInChip 平台上 SPI 的实现只支持半双工模式(Half-duplex)。
4.1. 驱动框架
U-Boot 驱动模型支持 SPI,ArtInChip 平台中 SPI 驱动基于该框架进行实现。 相关配置为:
- CONFIG_DM_SPI
- CONFIG_SPI
- CONFIG_SPL_SPI_SUPPORT
- CONFIG_ARTINCHIP_SPI
相关源码有:
drivers/spi/spi-uclass.c
include/spi.h
drivers/spi/artinchip_spi.c