设备树里中断节点的语法
参考文档:内核Documentation\devicetree\bindings\interrupt-controller\interrupts.txt
1. 设备树里的中断控制器
中断硬件框图如下:
需要三个属性来描述
- compatible
- interrupt-controller
- interrupt-cell
在硬件上,中断控制器只有GIC
这一个,但是我们在软件上也可以把上图中的GPIO
称为中断控制器。很多芯片有多个GPIO
模块,比如GPIO1、GPIO2等等。所以软件上的中断控制器
就有很多个:GIC
、GPIO1
、GPIO2
等等。
GPIO1 连接到GIC,GPIO2连接到GIC,所以GPIO1的父亲是GIC,GPIO2的父亲是GIC。
假设GPIO1有32 个中断源,但是它把其中的16个汇聚起来向GIC发出一个中断,把另外16个汇聚起来向GIC发出另一个中断。这就意味着GPIO1会用到GIC的两个中断,会涉及GIC里的 2 个hwirq。
这些层级关系、中断号(hwirq),都会在设备树中有所体现。
在设备树中,中断控制器节点中必须有一个属性:interrupt-controller
,表明它是“中断控制器”。还必须有一个属性: #interrupt-cells
,表明引用这个中断控制器的话需要多少个 cell。
#interrupt-cells 的值一般有如下的取值
- #interrupt-cells = <1>
别的节点要使用这个中断控制器时,只需要一个cell
来表明使用哪一个中断。 - #interrupt-cells = <2>
别的节点要使用这个中断控制器时,需要一个 cell 来表明使用“哪一个中断”;还需要另一个 cell 来描述中断,一般是表明触发类型:
第 2 个 cell 的 bits[3:0] 用来表示中断触发类型(trigger type and level flags):
1 = low-to-high edge triggered,上升沿触发
2 = high-to-low edge triggered,下降沿触发
4 = active high level-sensitive,高电平触发
8 = active low level-sensitive,低电平触发
示例如下:
vic: intc@10140000 {
compatible = "arm,versatile-vic";
interrupt-controller;
#interrupt-cells = <1>;
reg = <0x10140000 0x1000>;
};
如果中断控制器有级联关系,下级的中断控制器还需要表明它的“interrupt-parent”是谁,用了interrupt-parent ” 中的哪一个“ interrupts”。
2 设备树里使用中断
一个外设,它的中断信号接到哪个“中断控制器”的哪个“中断引脚”,这个中断的触发方式是怎样的?这3 个问题,在设备树里使用中断时,都要有所体现。
- interrupt-parent=<&xxxx>
你要用哪一个中断器里的中断? - interrupts
你要用哪一个中断?interrupts
里要用几个cell
,由interrupt-parent
对应的中断控制器决定。在中断控制器里有#interrupt-cells
属性,它指明了要用几个cell
来描述中断
i2c@7000c000 {
gpioext: gpio-adnp@41 {
compatible = "ad,gpio-adnp";
interrupt-parent = <&gpio>;
interrupts = <160 1>;
gpio-controller;
#gpio-cells = <1>;
interrupt-controller;
#interrupt-cells = <2>;
};
......
};
- 新写法:interrupts-extended
一个interrupts-extended
属性就可以既指定interrupt-parent
,也指定interrupt-parent
,也指定interrupts
,比如
interrupts-extended = <&intc1 5 1>, <&intc2 1 0>;
&intc1和&intc2:这些是中断控制器的引用,它们是设备树中定义的节点,负责管理中断请求。
5和1:这些是中断号,它们是中断控制器分配给特定设备的中断请求号。
1和0:这些是中断的触发类型。
设备树里中断节点的示例
以100ASK_IMX6ULL开发板为例,在 arch/arm/boot/dts 目录下可以看到 2 个文件: imx6ull.dtsi、 100ask_imx6ull-14x14.dts,把里面有关中断的部分内容抽取出来。
从设备树反推 IMX6ULL 的中断体系,如下,比之前的框图多了一个“ GPC INTC”:
GPC INTC
的英文是:General Power Controller, interrupt Controller
。它提供中断屏蔽,中断状态查询功能,实际上这些功能在 GIC
里也实现了,个人觉得有点多余。除此之外,它还提供唤醒功能,这才是保留它的原因。
在代码中获得中断
之前提到过,设备树中的节点有些能被转换为内核里的 platform_device
,有些不能,回顾如下:
- 根节点下含有compatile属性的子节点,会转换为 platform_device
- 含有特定 compatile 属性的节点的子节点,会转换为 platform_device,如果一个节点的compatile 属性,它的值是这 4 者之一: “simplebus”,“simple-mfd”,“isa”,“arm,amba-bus”,那么它的子结点(需含 compatile 属性)也可以转换为 platform_device。
- 总线 I2C、 SPI 节点下的子节点: 不转换为 platform_device,某个总线下到子节点, 应该交给对应的总线驱动程序来处理, 它们不应该被转换为 platform_device。
1.对于platform_device
一个节点能被转换为 platform_device
,如果它的设备树里指定了中断属性,那么可以从 platform_device
中获得中断资源,函数如下,可以使用下列函数获得 IORESOURCE_IRQ
资源,即中断号:
/**
* platform_get_resource - get a resource for a device
* @dev: platform device
* @type: resource type // 取哪类资源? IORESOURCE_MEM、 IORESOURCE_REG
* // IORESOURCE_IRQ 等
* @num: resource index // 这类资源中的哪一个?
*/
struct resource *platform_get_resource(struct platform_device *dev,
unsigned int type,
unsigned int num);
2. 对于I2C 设备、SPI 设备
对于I2C 设备节点,I2C 总线驱动在处理设备树里的 I2C子节点时,也会处理其中的中断信息。一个 I2C 设备会被转换为一个 i2c_client
结构体,中断号也会保存在 i2c_client
的irq 成员里,代码如下 (drivers/i2c/i2c-core-base.c
):
static int i2c_device_probe(struct device *dev)
{
struct i2c_client *client = i2c_verify_client(dev);
struct i2c_driver *driver;
int status;
if (!client)
return 0;
driver = to_i2c_driver(dev->driver);
client->irq = client->init_irq;
if (!client->irq && !driver->disable_i2c_core_irq_mapping) {
int irq = -ENOENT;
if (client->flags & I2C_CLIENT_HOST_NOTIFY) {
dev_dbg(dev, "Using Host Notify IRQ\n");
/* Keep adapter active when Host Notify is required */
pm_runtime_get_sync(&client->adapter->dev);
irq = i2c_smbus_host_notify_to_irq(client);
} else if (dev->of_node) {
irq = of_irq_get_byname(dev->of_node, "irq");
if (irq == -EINVAL || irq == -ENODATA)
irq = of_irq_get(dev->of_node, 0);
} else if (ACPI_COMPANION(dev)) {
irq = i2c_acpi_get_irq(client);
}
if (irq == -EPROBE_DEFER)
return irq;
if (irq < 0)
irq = 0;
client->irq = irq;
}
/*
* An I2C ID table is not mandatory, if and only if, a suitable OF
* or ACPI ID table is supplied for the probing device.
*/
if (!driver->id_table &&
!i2c_acpi_match_device(dev->driver->acpi_match_table, client) &&
!i2c_of_match_device(dev->driver->of_match_table, client))
return -ENODEV;
if (client->flags & I2C_CLIENT_WAKE) {
int wakeirq;
wakeirq = of_irq_get_byname(dev->of_node, "wakeup");
if (wakeirq == -EPROBE_DEFER)
return wakeirq;
device_init_wakeup(&client->dev, true);
if (wakeirq > 0 && wakeirq != client->irq)
status = dev_pm_set_dedicated_wake_irq(dev, wakeirq);
else if (client->irq > 0)
status = dev_pm_set_wake_irq(dev, client->irq);
else
status = 0;
if (status)
dev_warn(&client->dev, "failed to set up wakeup irq\n");
}
dev_dbg(dev, "probe\n");
status = of_clk_set_defaults(dev->of_node, false);
if (status < 0)
goto err_clear_wakeup_irq;
status = dev_pm_domain_attach(&client->dev, true);
if (status)
goto err_clear_wakeup_irq;
/*
* When there are no more users of probe(),
* rename probe_new to probe.
*/
if (driver->probe_new)
status = driver->probe_new(client);
else if (driver->probe)
status = driver->probe(client,
i2c_match_id(driver->id_table, client));
else
status = -EINVAL;
if (status)
goto err_detach_pm_domain;
return 0;
err_detach_pm_domain:
dev_pm_domain_detach(&client->dev, true);
err_clear_wakeup_irq:
dev_pm_clear_wake_irq(&client->dev);
device_init_wakeup(&client->dev, false);
return status;
}
对于SPI设备节点, SPI总线驱动在处理设备树里的 SPI 子节点时,也会处理其中的中断信息。一个SPI 设备会被转换为一个 spi_device 结构体,中断号会保存在 spi_device 的 irq 成员里,代码如下(drivers/spi/spi.c):
static int spi_drv_probe(struct device *dev)
{
const struct spi_driver *sdrv = to_spi_driver(dev->driver);
struct spi_device *spi = to_spi_device(dev);
int ret;
ret = of_clk_set_defaults(dev->of_node, false);
if (ret)
return ret;
if (dev->of_node) {
spi->irq = of_irq_get(dev->of_node, 0);
if (spi->irq == -EPROBE_DEFER)
return -EPROBE_DEFER;
if (spi->irq < 0)
spi->irq = 0;
}
ret = dev_pm_domain_attach(dev, true);
if (ret)
return ret;
ret = sdrv->probe(spi);
if (ret)
dev_pm_domain_detach(dev, true);
return ret;
}
3.调用 of_irq_get 获得中断号
如果你的设备节点既不能转换为 platform_device
,它也不是 I2C
设备,不是 SPI
设备,那么在驱动程序中可以自行调用 of_irq_get
函数去解析设备树,得到中断号。
4. 对于GPIO
参考: drivers/input/keyboard/gpio_keys.c
可以使用 gpio_to_irq
或 gpiod_to_irq
获得中断号。
假如设备树中有如下节点:
gpio-keys {
compatible = "gpio-keys";
pinctrl-names = "default";
user {
label = "User Button";
gpios = <&gpio5 1 GPIO_ACTIVE_HIGH>;
gpio-key,wakeup;
linux,code = <KEY_1>;
};
};
那么可以使用下面的函数获得引脚和 flag
button->gpio = of_get_gpio_flags(pp, 0, &flags);
bdata->gpiod = gpio_to_desc(button->gpio);
再去使用 gpiod_to_irq
获得中断号
irq = gpiod_to_irq(bdata->gpiod);