驱动程序的主要目的是处理和管理设备,大多数时候将它们的功能暴露给用户空间。这里的目标是收集设备的配置参数,特别是资源(内存区域、中断线、DMA通道、时钟等)。
下面是我们将在本文中使用的设备节点。它是i.MX6 UART设备的节点,定义在arch/arm/boot/dts/imx6qdl.dtsi中:
uart1: serial@02020000 { compatible = "fsl,imx6q-uart", "fsl,imx21-uart"; reg = <0x02020000 0x4000>; interrupts = <0 26 IRQ_TYPE_LEVEL_HIGH>; clocks = <&clks IMX6QDL_CLK_UART_IPG>, <&clks IMX6QDL_CLK_UART_SERIAL>; clock-names = "ipg", "per"; dmas = <&sdma 25 4 0>, <&sdma 26 4 0>; dma-names = "rx", "tx"; status = "disabled"; };
命名资源的概念
当驱动程序需要一个特定类型的资源列表时,你不能保证列表是按照驱动程序所期望的方式排序的,因为编写板级设备树的人通常不是编写驱动程序的人。例如,一个驱动程序可能期望它的设备节点有两条IRQ线,一条用于索引0的Tx事件,另一条用于索引1的Rx事件。如果不遵守命令会发生什么? 驱动会有不受欢迎的行为。为了避免这种不匹配,引入了命名资源(时钟、irq、dma、reg)的概念。它包括定义我们的资源列表并命名它们,因此无论它们的索引是什么,给定的名称总是与资源匹配。
资源命名对应的属性如下:
- reg-names: 这是针对reg属性中的内存区域列表。
- clock-names: 这是在clocks属性中的命名时钟。
- interrupt-names: 这将在interrupts属性中为每个中断提供一个名称。
- dma-names: 这是为了dma属性。
我们创建一个假设备节点条目来解释:
fake_device { compatible = "packt,fake-device"; reg = <0x02020000 0x4000>, <0x4a064800 0x200>, <0x4a064c00 0x200>; reg-names = "config", "ohci", "ehci"; interrupts = <0 66 IRQ_TYPE_LEVEL_HIGH>, <0 67 IRQ_TYPE_LEVEL_HIGH>; interrupt-names = "ohci", "ehci"; clocks = <&clks IMX6QDL_CLK_UART_IPG>, <&clks IMX6QDL_CLK_UART_SERIAL>; clock-names = "ipg", "per"; dmas = <&sdma 25 4 0>, <&sdma 26 4 0>; dma-names = "rx", "tx"; };
驱动程序中提取每个命名资源的代码如下所示:
struct resource *res1, *res2; res1 = platform_get_resource_byname(pdev, IORESOURCE_MEM, "ohci"); res2 = platform_get_resource_byname(pdev, IORESOURCE_MEM, "config");
struct dma_chan *dma_chan_rx, *dma_chan_tx; dma_chan_rx = dma_request_slave_channel(&pdev->dev, "rx"); dma_chan_tx = dma_request_slave_channel(&pdev->dev, "tx");
int txirq, rxirq; txirq = platform_get_irq_byname(pdev, "ohci");
rxirq = platform_get_irq_byname(pdev, "ehci");
struct clk *clck_per, *clk_ipg;
clk_ipg = devm_clk_get(&pdev->dev, "ipg");
clk_ipg = devm_clk_get(&pdev->dev, "pre");
通过这种方式,您可以确保将正确的名称映射到正确的资源,而不需要再使用索引。
访问寄存器
在这里,驱动程序将获得内存区域的所有权,并将其映射到虚拟地址空间。
struct resource *res; void __iomem *base;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0); /* * Here one can request and map the memory region * using request_mem_region(res->start, resource_size(res), pdev->name) * and ioremap(iores->start, resource_size(iores) * */ base = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(base)) return PTR_ERR(base);
platform_get_resource()将根据第一个(索引0)reg分配中的内存区域设置struct res的开始和结束字段。请记住platform_get_resource()的最后一个参数表示资源索引。在前面的示例中,索引0意味着该资源类型的第一个值,以防设备在DT节点中被分配多个内存区域。在我们的示例中,它是reg = <0x02020000 0x4000>,这意味着分配的区域从物理地址0x02020000开始,大小为0x4000字节。platform_get_resource()将设置 res.start = 0x02020000 和 res.end = 0x02023fff。
处理中断
中断接口实际上分为两部分——消费者端和控制器端。DT中有四个属性用来描述中断连接:
控制器是向消费者公开IRQ线路的设备。在控制器侧,设备具有以下属性:
- interrupt-controller: 一个空(布尔值)属性,你应该定义它来标记设备为中断控制器。
- #interrupt-cells: 这是中断控制器的一个属性。它表示使用了多少个cell为该中断控制器指定一个中断。
消费者是生成IRQ的设备。消费者绑定需要以下属性:
- interrupt-parent: 对于产生中断的设备节点,它是一个包含指向设备所连接的控制节点中断的phandle指针的属性。如果省略,则设备从其父节点继承该属性。
- interrupts:这是中断说明符。
中断binding和中断说明符绑定到中断控制器设备。用于定义中断输入的cell数量取决于中断控制器,它是通过其唯一的#interrupt-cells属性决定的。例如在i.MX6中,中断控制器是一个全局中断控制器(GIC)。它的binding在Documentation/devicetree/bindings/arm/gic.txt中有很好的解释。
中断处理程序
这包括从DT获取IRQ号,并将其映射到Linux IRQ,从而为它注册一个函数回调。这样做的驱动程序代码如下:
int irq = platform_get_irq(pdev, 0); ret = request_irq(irq, imx_rxint, 0, dev_name(&pdev->dev), sport);
platform_get_irq()调用将返回irq号;这个数字可以被devm_request_irq()使用(irq在/proc/interrupts中可见)。
第二个参数0表示我们需要在设备节点中指定的第一个中断。如果有多个中断,我们可以根据需要的中断更改这个索引,或者只使用命名的资源。
在前面的例子中,设备节点包含一个中断说明符,如下所示:
interrupts = <0 66 IRQ_TYPE_LEVEL_HIGH>;
- 根据ARM GIC,第一个单元告诉我们中断类型:
- 0: 共享外围中断(Shared peripheral interrupt, SPI),表示核间共享的中断信号,可以由GIC路由到任何核
- 1: 私有外围中断(PPI),用于私有于单个核心的中断信号
文档可以在http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0407e/CCHDBEBE.html找到。
- 第二个cell保存中断号。这个数字取决于中断线是PPI还是SPI。
- 第三个cell,在本例中是IRQ_TYPE_LEVEL_HIGH,表示触发方式(IRQ_TYPE_LEVEL_HIGH表示高电平触发)。所有可用的检测级别都定义在include/linux/irq.h中。
中断控制器代码
interrupt-controller属性用于将一个设备声明为中断控制器。#interrupt-cells属性定义了定义单个中断线必须使用多少个cell。
提取特定于应用程序的数据
特定于应用程序的数据是超出公共属性的数据(既不是resources也不是gpio、调节器等等)。这些是可以分配给设备的任意属性和子节点。这样的属性名通常以制造商code作为前缀。这些可以是任何字符串、布尔值或整数值,以及它们在Linux源代码的drivers/of/base.c中定义的API。下面我们讨论的例子并不详尽。现在让我们重用前面定义的节点:
node_label: nodename@reg{
string-property = ""a string"";
string-list = ""red fish"", ""blue fish"";
one-int-property = <197>; /* One cell in this property */
int-list-property = <0xbeef 123 0xabcd4>;/* each number (cell) is 32 a
* bit integer(uint32). There
* are 3 cells in this property
*/
mixed-list-property = "a string", <0xadbcd45>, <35>, [0x01 0x23 0x45]
byte-array-property = [0x01 0x23 0x45 0x67];
one-cell-property = <197>;
boolean-property;
};
文本字符串
下面是一个字符串属性:
string-property = "a string";
回到驱动程序中,您应该使用of_property_read_string()来读取字符串值。其原型定义如下:
int of_property_read_string(const struct device_node *np, const char *propname, const char **out_string)
下面的代码展示了如何使用它:
const char *my_string = NULL;
of_property_read_string(pdev->dev.of_node, "string-property", &my_string);
Cells和32位无符号整数
以下是我们的int属性:
one-int-property = <197>;
int-list-property = <1350000 0x54dae47 1250000 1200000>;
你应该使用of_property_read_u32()来读取cell值。其原型定义如下:
int of_property_read_u32(const struct device_node *np, const char *propname, u32 *out_value)
回到驱动程序中,编写以下代码:
unsigned int number;
of_property_read_u32(pdev->dev.of_node, "one-int-property", &number);
可以使用of_property_read_u32_array读取cell列表。其原型如下:
int of_property_read_u32_array(const struct device_node *np, const char *propname, u32 *out_values, size_t sz);
这里,sz是要读取的数组元素的个数。看一下drivers/of/base.c,看看如何解释它的返回值:
unsigned int cells_array[4];
if (of_property_read_u32_array(pdev->dev.of_node, "int-list-property", cells_array, 4)) {
dev_err(&pdev->dev, "list of cells not specified\n");
return -EINVAL;
}
布尔值
你应该使用of_property_read_bool()来读取在函数的第二个参数中给出的布尔属性:
bool my_bool = of_property_read_bool(pdev->dev.of_node, "booleanproperty");
if(my_bool){
/* boolean is true */
} else
/* Bolean is false */
}
提取和解析子节点
您可以在设备节点中添加任何子节点。给定一个表示闪存设备的节点,分区可以表示为子节点。对于处理一组输入和输出GPIO的设备,每一组都可以表示为一个子节点。示例节点如下所示:
eeprom: ee24lc512@55 {
compatible = "microchip,24xx512";
reg = <0x55>;
partition1 {
read-only;
part-name = "private";
offset = <0>;
size = <1024>;
};
partition2 {
part-name = "data";
offset = <1024>;
size = <64512>;
};
};
可以使用for_each_child_of_node()遍历给定节点的子节点:
struct device_node *np = pdev->dev.of_node;
struct device_node *sub_np;
for_each_child_of_node(np, sub_np) {
/* sub_np will point successively to each sub-node */
[...]
int size;
of_property_read_u32(client->dev.of_node, "size", &size);
...
}
标签:Handling,中断,dev,int,pdev,property,节点,resources,设备 From: https://www.cnblogs.com/wanglouxiaozi/p/17262487.html