diff --git a/rt-thread-version/rt-thread-smart/device/device-tree/device-tree.md b/rt-thread-version/rt-thread-smart/device/device-tree/device-tree.md index 6e28c552ef366a65fe8c0358df4e106ce7ee0684..a051725ab22faa7eb2f3250d997637499a0acaaf 100644 --- a/rt-thread-version/rt-thread-smart/device/device-tree/device-tree.md +++ b/rt-thread-version/rt-thread-smart/device/device-tree/device-tree.md @@ -12,14 +12,14 @@ dtc -I dts -O dtb -o mydtb.dtb mydts.dts ### 使用 qemu -在 qemu 环境下,可以使用`-dtb mydtb.dtb`来指定使用的 dtb 文件,也可以不指定,使用默认的 dtb 文件。aarch64 下,内核启动时可从 x0 寄存器读到设备树文件地址。 +在 qemu 环境下,可以使用 `-dtb mydtb.dtb` 来指定使用的 dtb 文件,也可以不指定,使用默认的 dtb 文件。aarch64 下,内核启动时可从 x0 寄存器读到设备树文件地址。 ### 使用 uboot uboot 的 bootcmd 环境变量添加加载设备树命令,并设置环境变量 fdtaddr,例如(具体需要根据设备情况而定): ``` -// bootcmd 中加入加载设备树的命令,这里以从 mmc 加载为例,${fdt_addr}替换为加载到内存的地址 +// bootcmd 中加入加载设备树的命令,这里以从 mmc 加载为例,${fdt_addr} 替换为加载到内存的地址 setenv bootcmd "load mmc 0:1 ${fdt_addr} mydtb.dtb; [other cmd]" setenv fdtaddr ${fdt_addr} ``` @@ -37,7 +37,7 @@ aarch64 下,内核启动时可从 x0 寄存器读到设备树文件地址。 - 对启动参数 bootargs 进行处理,包括挂载根文件系统,设置早期控制台等; - 初始化 directfs,从而可以在正常运行时获得设备信息; - 解压缩设备树,方便之后对设备树的访问; -- 根据/cpus 结点信息对 cpu 进行初始化; +- 根据 /cpus 结点信息对 cpu 进行初始化; - 根据中断控制器结点进行中断初始化; - 在设备初始化完毕后设置正常运行下的控制台; @@ -63,18 +63,10 @@ aarch64 下,内核启动时可从 x0 寄存器读到设备树文件地址。 - 给设备树解析得到的各设备分配一个平台设备 rt_platform_device,并将其挂载在平台总线上; - 当平台驱动和平台设备相符(兼容属性或者名称相同)时,两者相匹配,并调用驱动的 probe 函数进行初始化; -### 正常运行下 - -系统正常运行时可以通过在 msh 中查看 directFS(挂载在 /direct 上)来了解平台设备树信息,该文件系统中与设备树相关的文件及目录如下: - -- /direct/firmware/devicetree:以目录结构展示了设备树的结构,该目录对应了根结点,以子目录代表子结点,文件代表结点下属性,可通过 cat、ls 等命令查看; -- /direct/firmware/fdt:读取该文件内容则会读取到设备树编译后的二进制文件; -- /direct/bus/platform:读取该目录可了解系统平台总线上的设备和驱动; - -同时设备树运行时保留在内存中,应用程序仍可以通过访问设备树来了解系统信息。 - ## rt-thread 中设备树编写 +在 rt-thread 中使用设备树,首先需要为平台编写设备树文件。 + ### 设备树系统结点编写 设备树的系统结点用于 rt-thread 的系统初始化过程以及提供对设备结点解析所需的部分参数。 @@ -87,7 +79,7 @@ aarch64 下,内核启动时可从 x0 寄存器读到设备树文件地址。 | --- | --- | | #size-cells | 子结点使用多少个 32 位整数来表示大小属性,rt-thread 中如未指定使用默认值 1 | | #address-cells | 子结点使用多少个 32 位整数表示地址属性,rt-thread 中如未指定使用默认值 1 | -| compatible | 指定平台名称,格式为"<厂商>,<型号>",用于选择正确平台特定程序来初始化设备或判断设备是否有某些特性 | +| compatible | 指定平台名称,格式为 "<厂商>,<型号>",用于选择正确平台特定程序来初始化设备或判断设备是否有某些特性 | | model | 设备型号,该属性仅用于为用户提供设备描述,不用于设备匹配 | #### /aliases 结点 @@ -101,13 +93,13 @@ aarch64 下,内核启动时可从 x0 寄存器读到设备树文件地址。 | 属性 | 解释 | | --- | --- | | device_type | 属性值必须为 "memory" | -| reg | 指定内存范围,格式为<地址 大小>,地址和大小的表示需满足根节点指定的 #address-cells 和 #size-cells | +| reg | 指定内存范围,格式为 <地址 大小>,地址和大小的表示需满足根节点指定的 #address-cells 和 #size-cells | | initial-mapped-area | 指定初始化映射区域,目前 rt-thread 不支持该属性 | | hotpluggable | 指定该内存区域可以热插拔,目前 rt-thread 不支持该属性 | 注意事项: -- 该类型结点必须位于根节点/之下; +- 该类型结点必须位于根节点 / 之下; - 该类型结点必须指定属性名 device_type 的值为 "memory"; - 该类型结点必须使用 reg 属性指定内存范围; - 该类型结点命名方式一般为 memory@start-address; @@ -118,8 +110,8 @@ aarch64 下,内核启动时可从 x0 寄存器读到设备树文件地址。 | 属性 | 解释 | | --- | --- | -| #size-cells | 必须指定为根节点的#size-cells | -| #address-cells | 必须指定为根节点的#address-cells | +| #size-cells | 必须指定为根节点的 #size-cells | +| #address-cells | 必须指定为根节点的 #address-cells | | ranges | 指定 reserved-memory 结点下地址空间如何向 cpu 物理地址空间转换 | - 以上三个属性均必须明确指定; @@ -164,18 +156,18 @@ rt-thread 中 bootargs 支持的参数如下: | 属性 | 解释 | | --- | --- | | #address-cells | cpu 子结点使用多少个 32 位整数来表示大小属性 | -| #size-cells | 该值应为 0,cpu 的 reg 没有 size 属性 | +| #size-cells | 该值必须为 0,cpu 的 reg 没有 size 属性 | -##### /cpus/cpu *结点 +##### /cpus/cpu* 结点 该结点描述一个具体的 cpu 实例,对应属性编写如下: | 属性 | 解释 | | --- | --- | -| device_type | 必须指定为"cpu" | +| device_type | 必须指定为 "cpu" | | reg | 这里表示 cpu 的硬件编号,用一个 32 位无符号整型数表示 | -| enable-method | 指定禁用状态 cpu 的启动方法,rt-thread 支持"psci"和"spin-table"两种方式 | -| cpu-release-addr | 如果 enable-method 属性指定为"spin-table",则需要该属性,其值指定了一个 spin-table 条目的物理地址,用于解除辅助 CPU 的阻塞状态,以便其可以继续执行任务 | +| enable-method | 指定禁用状态 cpu 的启动方法,rt-thread 支持 "psci" 和 "spin-table" 两种方式 | +| cpu-release-addr | 如果 enable-method 属性指定为 "spin-table",则需要该属性,其值指定了一个 spin-table 条目的物理地址,用于解除辅助 CPU 的阻塞状态,以便其可以继续执行任务 | ### rt-thread 中设备结点编写 @@ -212,7 +204,7 @@ rt-thread 中 bootargs 支持的参数如下: | 属性 | 解释 | | --- | --- | | interrupt-parent | 该设备向哪个中断控制器发送中断,值为 phandle 或中断控制器路径 | -| interrupts | 为设备描述中断信息,一个中断所使用的单元格用#interrupt-cells 属性描述 | +| interrupts | 为设备描述中断信息,一个中断所使用的单元格用 #interrupt-cells 属性描述 | | interrupts-extended | 为设备描述中断信息,该设备会向不同中断控制器发送中断 | | interrupt-map | 用于描述多个设备之间的中断映射关系 | | interrupt-mask | 用于对中断号进行过滤、匹配、多路复用 | @@ -230,8 +222,104 @@ rt-thread 中 bootargs 支持的参数如下: | ranges | 通常用于总线类型设备,描述总线结点和父节点的地址空间如何映射 | | memory-region | 所使用的预留内存区域,目前暂无 API 处理该属性 | +### 示例 + +```dts +/ { + // 表示子结点的地址相关值占一个 32 位整数的 cell + #address-cells = <1>; + // 表示子结点的大小相关值占一个 32 位整数的 cell + #size-cells = <1>; + // 表示设备兼容 "my-hardware" 的特性 + compatible = "my-hardware"; + // 设备模型,仅用于为用户提供设备信息 + model = "My Hardware Platform"; + + // 使用 ttyS0 作为控制台输入输出设备,波特率 115200,并将 "/dev/mmcblk0p2" 块设备上的 VFAT 文件系统挂载到根目录。 + chosen { + bootargs = "console=ttyS0,115200 root=/dev/mmcblk0p2 rw rootfstype=vfat"; + }; + + // 指定可用物理内存范围 + memory@0x0 { + // 必须显式指定该属性值为 "memory" + device_type = "memory"; + reg = <0x0 0x10000000>; // 1GB of RAM starting at address 0x0 + }; + + cpus { + // cpus 的该属性必须是 0x0 + #size-cells = <0x0>; + #address-cells = <0x1>; + + cpu@0 { + reg = <0x0>; + enable-method = "psci"; + compatible = "arm,cortex-a53"; + // cpu 节点必须指定 device_type 为 "cpu" + device_type = "cpu"; + }; + + cpu@1 { + reg = <0x1>; + enable-method = "psci"; + compatible = "arm,cortex-a53"; + device_type = "cpu"; + }; + }; + + reserved-memory { + // 指定地址单元格的数量 + #address-cells = <1>; + // 指定大小单元格的数量 + #size-cells = <1>; + // 表示将 reserved-memory 结点下以 0x0 开始的地址长度 0x100 映射到 cpu 的 0x10000 地址 + ranges = <0x0 0x10000 0x100>; + + // 内存区域的名称 + framebuffer@30000000 { + compatible = "my_reserved_memory"; + // 内存区域的地址和大小。根据父结点的 ranges,其映射到 cpu 地址空间的 0x10000 + reg = <0x0 0x100>; + }; + }; + + // 指定上面的内存结点别名为 mem + aliases { + mem = &/memory@0x0; + }; + + // 其他结点如果想引用内存结点,可以不必提供完整路径 + other-region { + memory-device = <&mem> + } + + intc@8000000 { + // 中断控制器使用的内存 + reg = <0x0 0x8000000 0x0 0x10000 0x0 0x8010000 0x0 0x10000>; + compatible = "arm,cortex-a15-gic"; + interrupt-controller; + #interrupt-cells = <0x3>; + }; + + // 一个设备示例 + my-device { + // 用于与驱动配对 + compatible = "my-device"; + // IO 资源 + reg = <0x0 0x1000>; + // 向该中断控制器发送中断请求 + interrupt-parent = <&/intc@8000000>; + // 中断资源,格式由对应中断控制器的 #interrupt-cells 控制 + interrupts = <0x0 0x1 0x2>; + }; +}; +``` + ## rt-thread 下驱动程序编写 +为了使 rt-thread 能够使用设备树指定的设备,需要编写能够驱动设备的驱动程序。 + ### 驱动兼容性 设备需要指定其可以与何种设备相匹配。以 pl061 驱动为例,指定方法如下: @@ -240,25 +328,25 @@ rt-thread 中 bootargs 支持的参数如下: // 定义 rt_ofw_node_id 结构体,其中指定了可以兼容的设备应有的 compatible 属性 static const struct rt_ofw_node_id pl061_ofw_ids[] = { - { .compatible = "arm,pl061" }, - { /* sentinel */ } + {.compatible = "arm,pl061"}, + {/* sentinel */} }; static struct rt_platform_driver pl061_driver = -{ - // 驱动名称,假如设备名是"pin-pl061"也可以匹配 +{ + // 驱动名称,假如设备名是 "pin-pl061" 也可以匹配 .name = "pin-pl061", // 指定了该驱动的 ids,可判断其可以与何种设备相匹配 .ids = pl061_ofw_ids, - // probe 函数,在驱动探测到相匹配的设备时对其调用的函数 + // probe 函数,在驱动探测到相匹配的设备时对其调用的函数,代码见下方 probe 函数编写介绍 .probe = pl061_probe, }; // 在系统启动时注册该平台驱动 RT_PLATFORM_DRIVER_EXPORT(pl061_driver); ``` -- 如上所示,pl061_driver 可以与设备名为"pin-pl061"或 compatible 属性含有"arm,pl061"的设备相匹配; +- 如上所示,pl061_driver 可以与设备名为 "pin-pl061" 或 compatible 属性含有 "arm,pl061" 的设备相匹配; ### probe 函数编写 @@ -285,13 +373,13 @@ probe 函数是 driver 探测到相匹配的设备时对其调用的函数。具 int rt_ofw_irq_cells(struct rt_ofw_node *np) ``` -该函数获取设备结点#interrupt-cells 属性 +该函数获取设备结点 #interrupt-cells 属性 | 参数 | 描述 | |:------------------|:------------------------------------| | np | 设备结点 | -| **返回** | **描述** | -| int | 设备结点对应的#interrupt-cells 属性值 | +| ** 返回 ** | ** 描述 ** | +| int | 设备结点对应的 #interrupt-cells 属性值 | | -RT_EEMPTY | 设备结点没有该属性 | | -RT_EINVAL | 设备不合法 | @@ -305,7 +393,7 @@ rt_err_t rt_ofw_parse_irq_map(struct rt_ofw_node *np, struct rt_ofw_cell_args *i |:------------------|:------------------------------------| | np | 设备结点 | | irq_args | 传递参数及存放解析 interrupt-map 属性结果 | -| **返回** | **描述** | +| ** 返回 ** | ** 描述 ** | | 0 | 解析成功 | | 错误码 | 解析失败 | @@ -320,7 +408,7 @@ rt_err_t rt_ofw_parse_irq_cells(struct rt_ofw_node *np, int index, struct rt_ofw | np | 设备结点 | | index | 设备第几个中断资源 | | out_irq_args | 存储获得的中断信息 | -| **返回** | **描述** | +| ** 返回 ** | ** 描述 ** | | 0 | 获取中断信息成功 | | 错误码 | 获取失败 | @@ -333,8 +421,8 @@ struct rt_ofw_node *rt_ofw_find_irq_parent(struct rt_ofw_node *np, int *out_inte | 参数 | 描述 | |:------------------|:------------------------------------| | np | 设备结点 | -| out_interrupt_cells | 中断控制器的#interrupt-cells 属性 | -| **返回** | **描述** | +| out_interrupt_cells | 中断控制器的 #interrupt-cells 属性 | +| ** 返回 ** | ** 描述 ** | | rt_ofw_node 结点 | 获取的中断控制器结点 | | RT_NULL | 找不到对应的中断控制器结点 | @@ -347,7 +435,7 @@ int rt_ofw_map_irq(struct rt_ofw_cell_args *irq_args) | 参数 | 描述 | |:------------------|:------------------------------------| | irq_args | 获取到的设备中断信息 | -| **返回** | **描述** | +| ** 返回 ** | ** 描述 ** | | 大于 0 整数 | 设置的中断编号 | | 错误码 | 执行错误 | @@ -360,7 +448,7 @@ int rt_ofw_get_irq_count(struct rt_ofw_node *np) | 参数 | 描述 | |:------------------|:------------------------------------| | np | 设备结点 | -| **返回** | **描述** | +| ** 返回 ** | ** 描述 ** | | 一个非负整数 | 设备中断资源数目 | | -RT_EINVAL | 传入的 np 不合法 | @@ -374,7 +462,7 @@ int rt_ofw_get_irq(struct rt_ofw_node *np, int index) |:------------------|:------------------------------------| | np | 设备结点 | | index | 需要处理的设备中断资源编号 | -| **返回** | **描述** | +| ** 返回 ** | ** 描述 ** | | 一个非负整数 | 指定中断资源的中断号 | | 错误码 | 执行错误 | @@ -388,7 +476,7 @@ int rt_ofw_get_irq_by_name(struct rt_ofw_node *np, const char *name) |:------------------|:------------------------------------| | np | 设备结点 | | name | 需要处理的设备中断名称 | -| **返回** | **描述** | +| ** 返回 ** | ** 描述 ** | | 一个非负整数 | 指定中断资源的中断号 | | 错误码 | 执行错误 | @@ -396,7 +484,7 @@ int rt_ofw_get_irq_by_name(struct rt_ofw_node *np, const char *name) rt_isr_handler_t rt_hw_interrupt_install(int vector, rt_isr_handler_t handler, void *param, const char *name) ``` -该函数设置中断向量表,用于指定某中断号对应的中断服务例程。需要包含头文件`` +该函数设置中断向量表,用于指定某中断号对应的中断服务例程。需要包含头文件 `` | 参数 | 描述 | |:------------------|:------------------------------------| @@ -404,7 +492,7 @@ rt_isr_handler_t rt_hw_interrupt_install(int vector, rt_isr_handler_t handler, v | handler | 需要设置成的中断服务例程 | | param | 中断服务例程参数 | | name | 中断服务例程名称 | -| **返回** | **描述** | +| ** 返回 ** | ** 描述 ** | | RT_NULL | 该中断号原来无中断服务例程 | | old_handler | 之前该中断号对应的中断服务例程 | @@ -422,52 +510,52 @@ rt_isr_handler_t rt_hw_interrupt_install(int vector, rt_isr_handler_t handler, v int rt_ofw_bus_addr_cells(struct rt_ofw_node *np) ``` -获取总线设备的#address-cells 属性。它会从总线设备结点遍历 parent 结点,获取到第一个#address-cells 则返回。如果未获得则使用默认值 1。 +获取总线设备的 #address-cells 属性。它会从总线设备结点遍历 parent 结点,获取到第一个 #address-cells 则返回。如果未获得则使用默认值 1。 | 参数 | 描述 | |:------------------|:------------------------------------| | np | 设备结点 | -| **返回** | **描述** | -| 一个正整数 | 设备的#address-cells 属性 | +| ** 返回 ** | ** 描述 ** | +| 一个正整数 | 设备的 #address-cells 属性 | | -RT_EINVAL | np 不合法 | ```c int rt_ofw_bus_size_cells(struct rt_ofw_node *np) ``` -获取总线设备的#size-cells 属性。它会从总线设备结点遍历 parent 结点,获取到第一个#size-cells 则返回。如果未获得则使用默认值 1。 +获取总线设备的 #size-cells 属性。它会从总线设备结点遍历 parent 结点,获取到第一个 #size-cells 则返回。如果未获得则使用默认值 1。 | 参数 | 描述 | |:------------------|:------------------------------------| | np | 设备结点 | -| **返回** | **描述** | -| 一个正整数 | 设备的#size-cells 属性 | +| ** 返回 ** | ** 描述 ** | +| 一个正整数 | 设备的 #size-cells 属性 | | -RT_EINVAL | np 不合法 | ```c int rt_ofw_io_addr_cells(struct rt_ofw_node *np) ``` -获取普通设备的#address-cells 属性。其优先获取所在总线(即其父节点)的该属性。如果未获得则使用默认值 1。 +获取普通设备的 #address-cells 属性。其优先获取所在总线(即其父节点)的该属性。如果未获得则使用默认值 1。 | 参数 | 描述 | |:------------------|:------------------------------------| | np | 设备结点 | -| **返回** | **描述** | -| 一个正整数 | 设备的#address-cells 属性 | +| ** 返回 ** | ** 描述 ** | +| 一个正整数 | 设备的 #address-cells 属性 | | -RT_EINVAL | np 不合法 | ```c int rt_ofw_io_size_cells(struct rt_ofw_node *np) ``` -获取普通设备的#size-cells 属性。其优先获取所在总线(即其父节点)的该属性。如果未获得则使用默认值 1。 +获取普通设备的 #size-cells 属性。其优先获取所在总线(即其父节点)的该属性。如果未获得则使用默认值 1。 | 参数 | 描述 | |:------------------|:------------------------------------| | np | 设备结点 | -| **返回** | **描述** | -| 一个正整数 | 设备的#size-cells 属性 | +| ** 返回 ** | ** 描述 ** | +| 一个正整数 | 设备的 #size-cells 属性 | | -RT_EINVAL | np 不合法 | ```c @@ -479,7 +567,7 @@ int rt_ofw_get_address_count(struct rt_ofw_node *np) | 参数 | 描述 | |:------------------|:------------------------------------| | np | 设备结点 | -| **返回** | **描述** | +| ** 返回 ** | ** 描述 ** | | 一个非负整数 | 设备结点的 reg 属性中地址的数量 | | -RT_EINVAL | np 不合法 | @@ -495,7 +583,7 @@ rt_err_t rt_ofw_get_address(struct rt_ofw_node *np, int index, rt_uint64_t *out_ | index | 需要获得的 address-size 对的编号 | | out_address | 存储获得的 address 转换到 cpu 地址空间的值 | | out_size | 存储获得的 size 值 | -| **返回** | **描述** | +| ** 返回 ** | ** 描述 ** | | RT_EOK | 执行成功 | | -RT_EINVAL | 参数不合法 | @@ -511,7 +599,7 @@ rt_err_t rt_ofw_get_address_by_name(struct rt_ofw_node *np, const char *name, rt | name | 需要获得的 IO 资源名称 | | out_address | 存储获得的 address 转换到 cpu 地址空间的值 | | out_size | 存储获得的 size 值 | -| **返回** | **描述** | +| ** 返回 ** | ** 描述 ** | | RT_EOK | 执行成功 | | -RT_EINVAL | 参数不合法 | @@ -526,7 +614,7 @@ int rt_ofw_get_address_array(struct rt_ofw_node *np, int nr, rt_uint64_t *out_re | np | 设备结点 | | nr | 需要获得的 IO 资源个数 | | out_regs | 存储获得的以 address-size 对表示的 IO 资源 | -| **返回** | **描述** | +| ** 返回 ** | ** 描述 ** | | 一个非负整数 | 实际获得的 IO 资源个数 | | -RT_EINVAL | 参数不合法 | @@ -539,9 +627,9 @@ rt_uint64_t rt_ofw_translate_address(struct rt_ofw_node *np, const char *range_t | 参数 | 描述 | |:------------------|:------------------------------------| | np | 设备结点 | -| range_type | 映射类型,不指定时默认为"ranges",该函数从该属性来获得映射方式 | +| range_type | 映射类型,不指定时默认为 "ranges",该函数从该属性来获得映射方式 | | address | 设备所属总线下的某一地址 | -| **返回** | **描述** | +| ** 返回 ** | ** 描述 ** | | 最大 64 位正整数 | 转换失败 | | 一个合法物理地址 | 设备映射到的物理地址 | @@ -555,7 +643,7 @@ void *rt_ofw_iomap(struct rt_ofw_node *np, int index) |:------------------|:------------------------------------| | np | 设备结点 | | index | 指定的 IO 资源编号 | -| **返回** | **描述** | +| ** 返回 ** | ** 描述 ** | | RT_NULL | 执行失败 | | 一个合法的内核虚拟地址 | 设备映射到的内核虚拟地址 | @@ -569,13 +657,106 @@ void *rt_ofw_iomap_by_name(struct rt_ofw_node *np, const char *name) |:------------------|:------------------------------------| | np | 设备结点 | | name | 指定的 IO 资源名称 | -| **返回** | **描述** | +| ** 返回 ** | ** 描述 ** | | RT_NULL | 执行失败 | | 一个合法的内核虚拟地址 | 设备映射到的内核虚拟地址 | #### 其它属性处理 -在`components/drivers/include/drivers/ofw.h`中定义了一系列可以处理不同属性值格式的函数,驱动程序可以调用这些函数来获得并处理这些属性。 +在 `components/drivers/include/drivers/ofw.h` 中定义了一系列可以处理不同属性值格式的函数,驱动程序可以调用这些函数来获得并处理这些属性。 + +#### probe 函数示例 + +下面仍以 pl061 设备为例,介绍 probe 函数的编写。 + +```c +static rt_err_t pl061_ofw_init(struct rt_platform_device *pdev, struct pl061 *pl061) +{ + rt_err_t err = RT_EOK; + struct rt_ofw_node *np = pdev->parent.ofw_node; + + // 对 pl061 的 0 号内存资源初始化 + pl061->base = rt_ofw_iomap(np, 0); + + if (pl061->base) + { + // 获取 pl061 的 0 号中断资源 + pl061->irq = rt_ofw_get_irq(np, 0); + + if (pl061->irq < 0) + { + err = -RT_ERROR; + } + else + { + // 设置 ofw 结点中的设备信息 + rt_ofw_data(np) = &pl061->parent; + } + } + else + { + err = -RT_EIO; + } + + return err; +} + +static rt_err_t pl061_probe(struct rt_platform_device *pdev) +{ + rt_err_t err = RT_EOK; + struct pl061 *pl061 = rt_calloc(1, sizeof(*pl061)); + + if (pl061) + { + // 根据设备树的信息对 pl061 进行初始化 + err = pl061_ofw_init(pdev, pl061); + } + else + { + err = -RT_ENOMEM; + } + + if (!err) + { + // 设备特定初始化 + rt_spin_lock_init(&pl061->spinlock); + + pl061->parent.irqchip.irq = pl061->irq; + pl061->parent.irqchip.pin_range[0] = 0; + pl061->parent.irqchip.pin_range[1] = PL061_GPIO_NR - 1; + pl061->parent.ops = &pl061_pin_ops; + pin_pic_init(&pl061->parent); + + // 注册设备 + rt_device_pin_register("gpio", &pl061_pin_ops, pl061); + + // 设置中断服务例程 + rt_hw_interrupt_install(pl061->irq, pl061_isr, pl061, "gpio-pl061"); + rt_hw_interrupt_umask(pl061->irq); + } + else + { + rt_free(pl061); + } + + return err; +} +``` + +由上程序也可知,设备树中 pl061 设备结点编写需要提供一个中断资源及一个 IO 资源,下面的示例结点可以被 pl061 驱动处理: + +```dts +/pl061{ + compatible = "arm,pl061"; + reg = <0x40000 20>; + // 指定中断控制器,需要定义 interrupt-controller 结点 + interrupt-parent = <&interrupt-controller>; + interrupts = <20 0>; +} +``` + +- reg 的格式由其父结点的 #address-cells 和 #size-cells 指定,这里假定均为 1。 +- interrupts 格式由其指定的中断控制器 (这里是 interrupt-controller) 的 #interrupt-cells 指定,这里假定其为 2。 ### 说明编写