关于 PCA9555 扩展器上#interrupt-cells 配置的混淆

Confusion regarding #interrupt-cells configuration on PCA9555 expander

我第一次尝试在我的自定义平台上设置设备树源文件。电路板上有一个 NXP PCA9555 gpio 扩展器。我正在尝试为设备设置节点,但有点困惑。

这是我在 dts 文件中的节点位置:

ioexp0: gpio-exp@21 {
        compatible = "nxp,pca9555";
        reg = <21>;

        interrupt-parent = <&gpio>;
        interrupts = <8 0>;

        gpio-controller;
        #gpio-cells = <2>;

        /*I don't understand the following two lines*/
        interrupt-controller;
        #interrupt-cells = <2>;
};

我通过使用 armada-388-gp.dts 源作为指南达到了这一点。

我的困惑在于处理 #interrupt-cells 属性 的代码。 bindings documentation 对这个芯片一点帮助都没有,因为它没有说明任何关于中断单元解释的内容。

查看 pca9555 驱动程序 source code 中的 pca953x_irq_setup 函数 - 我没有看到 #interrupt-cells 属性 被处理的任何地方。这是在 linux 中断处理代码中处理的吗?我只是对我应该如何知道两个中断单元的含义感到困惑。

pca953x_irq_setup 为了您的方便:

static int pca953x_irq_setup(struct pca953x_chip *chip,
                 int irq_base)
{
    struct i2c_client *client = chip->client;
    int ret, i;

    if (client->irq && irq_base != -1
            && (chip->driver_data & PCA_INT)) {
        ret = pca953x_read_regs(chip,
                    chip->regs->input, chip->irq_stat);
        if (ret)
            return ret;

        /*
         * There is no way to know which GPIO line generated the
         * interrupt.  We have to rely on the previous read for
         * this purpose.
         */
        for (i = 0; i < NBANK(chip); i++)
            chip->irq_stat[i] &= chip->reg_direction[i];
        mutex_init(&chip->irq_lock);

        ret = devm_request_threaded_irq(&client->dev,
                    client->irq,
                       NULL,
                       pca953x_irq_handler,
                       IRQF_TRIGGER_LOW | IRQF_ONESHOT |
                           IRQF_SHARED,
                       dev_name(&client->dev), chip);
        if (ret) {
            dev_err(&client->dev, "failed to request irq %d\n",
                client->irq);
            return ret;
        }

        ret =  gpiochip_irqchip_add_nested(&chip->gpio_chip,
                           &pca953x_irq_chip,
                           irq_base,
                           handle_simple_irq,
                           IRQ_TYPE_NONE);
        if (ret) {
            dev_err(&client->dev,
                "could not connect irqchip to gpiochip\n");
            return ret;
        }

        gpiochip_set_nested_irqchip(&chip->gpio_chip,
                        &pca953x_irq_chip,
                        client->irq);
    }

    return 0;
}

这是我第一次使用设备树,所以我希望它是我遗漏的明显东西。

在查看了所有评论后,我进行了一些补充阅读并找到了我的答案。

我现在明白我误解了设备树的某些属性。我以前的印象是驱动程序必须指定如何处理所有属性。我现在看到 linux 实际上会处理许多通用属性,例如 gpiosinterrupts(这很有意义)。

关于实际 interrupts binding 的文档很有帮助,但不是设备驱动程序的文档。

这里更详细地解释了从 intspec 到 IRQ_TYPE* 的转换是如何发生的:

函数of_irq_parse_one将中断说明符整数复制到struct of_phandle_argshere. This arg is then passed to irq_create_of_mapping via a consumer function (e.g. of_irq_get). This function then maps these args to a struct irq_fwspec via of_phandle_args_to_fwspec and passes it's fwspec data to irq_create_fwspec_mapping. These functions are all found in irqdomain.c. At this point the irq will belong to an irq_domain or use the irq_default_domain. As far I can tell - the pca853x driver uses the default domain. This domain is often setup by platform specific code. I found mine by searching for irq_domain_ops on cross reference。其中很多似乎通过 irq_domain_translateintspec[1] & IRQ_TYPE_SENSE_MASK 简单复制到 irq_create_fwspec_mapping 中的 type 变量。从这里开始,类型通过 irqd_set_trigger_type.

设置为 irq 的 irq_data

of_irq_parse_one:

/**
 * of_irq_parse_one - Resolve an interrupt for a device
 * @device: the device whose interrupt is to be resolved
 * @index: index of the interrupt to resolve
 * @out_irq: structure of_irq filled by this function
 *
 * This function resolves an interrupt for a node by walking the interrupt tree,
 * finding which interrupt controller node it is attached to, and returning the
 * interrupt specifier that can be used to retrieve a Linux IRQ number.
 */
int of_irq_parse_one(struct device_node *device, int index, struct of_phandle_args *out_irq)
{
    struct device_node *p;
    const __be32 *intspec, *tmp, *addr;
    u32 intsize, intlen;
    int i, res;

pr_debug("of_irq_parse_one: dev=%s, index=%d\n", of_node_full_name(device), index);

/* OldWorld mac stuff is "special", handle out of line */
if (of_irq_workarounds & OF_IMAP_OLDWORLD_MAC)
    return of_irq_parse_oldworld(device, index, out_irq);

/* Get the reg property (if any) */
addr = of_get_property(device, "reg", NULL);

/* Try the new-style interrupts-extended first */
res = of_parse_phandle_with_args(device, "interrupts-extended",
                "#interrupt-cells", index, out_irq);
if (!res)
    return of_irq_parse_raw(addr, out_irq);

/* Get the interrupts property */
intspec = of_get_property(device, "interrupts", &intlen);
if (intspec == NULL)
    return -EINVAL;

intlen /= sizeof(*intspec);

pr_debug(" intspec=%d intlen=%d\n", be32_to_cpup(intspec), intlen);

/* Look for the interrupt parent. */
p = of_irq_find_parent(device);
if (p == NULL)
    return -EINVAL;

/* Get size of interrupt specifier */
tmp = of_get_property(p, "#interrupt-cells", NULL);
if (tmp == NULL) {
    res = -EINVAL;
    goto out;
}
intsize = be32_to_cpu(*tmp);

pr_debug(" intsize=%d intlen=%d\n", intsize, intlen);

/* Check index */
if ((index + 1) * intsize > intlen) {
    res = -EINVAL;
    goto out;
}

/* Copy intspec into irq structure */
intspec += index * intsize;
out_irq->np = p;
out_irq->args_count = intsize;
for (i = 0; i < intsize; i++)
    out_irq->args[i] = be32_to_cpup(intspec++);

/* Check if there are any interrupt-map translations to process */
res = of_irq_parse_raw(addr, out_irq);
 out:
    of_node_put(p);
    return res;
}
EXPORT_SYMBOL_GPL(of_irq_parse_one)

irq_create_fwspec_mapping:

unsigned int irq_create_fwspec_mapping(struct irq_fwspec *fwspec)
{
    struct irq_domain *domain;
    struct irq_data *irq_data;
    irq_hw_number_t hwirq;
    unsigned int type = IRQ_TYPE_NONE;
    int virq;

    if (fwspec->fwnode) {
        domain = irq_find_matching_fwspec(fwspec, DOMAIN_BUS_WIRED);
        if (!domain)
            domain = irq_find_matching_fwspec(fwspec, DOMAIN_BUS_ANY);
    } else {
        domain = irq_default_domain;
    }

    if (!domain) {
        pr_warn("no irq domain found for %s !\n",
            of_node_full_name(to_of_node(fwspec->fwnode)));
        return 0;
    }

    if (irq_domain_translate(domain, fwspec, &hwirq, &type))
        return 0;

    /*
     * WARN if the irqchip returns a type with bits
     * outside the sense mask set and clear these bits.
     */
    if (WARN_ON(type & ~IRQ_TYPE_SENSE_MASK))
        type &= IRQ_TYPE_SENSE_MASK;

    /*
     * If we've already configured this interrupt,
     * don't do it again, or hell will break loose.
     */
    virq = irq_find_mapping(domain, hwirq);
    if (virq) {
        /*
         * If the trigger type is not specified or matches the
         * current trigger type then we are done so return the
         * interrupt number.
         */
        if (type == IRQ_TYPE_NONE || type == irq_get_trigger_type(virq))
            return virq;

        /*
         * If the trigger type has not been set yet, then set
         * it now and return the interrupt number.
         */
        if (irq_get_trigger_type(virq) == IRQ_TYPE_NONE) {
            irq_data = irq_get_irq_data(virq);
            if (!irq_data)
                return 0;

            irqd_set_trigger_type(irq_data, type);
            return virq;
        }

        pr_warn("type mismatch, failed to map hwirq-%lu for %s!\n",
            hwirq, of_node_full_name(to_of_node(fwspec->fwnode)));
        return 0;
    }

    if (irq_domain_is_hierarchy(domain)) {
        virq = irq_domain_alloc_irqs(domain, 1, NUMA_NO_NODE, fwspec);
        if (virq <= 0)
            return 0;
    } else {
        /* Create mapping */
        virq = irq_create_mapping(domain, hwirq);
        if (!virq)
            return virq;
    }

    irq_data = irq_get_irq_data(virq);
    if (!irq_data) {
        if (irq_domain_is_hierarchy(domain))
            irq_domain_free_irqs(virq, 1);
        else
            irq_dispose_mapping(virq);
        return 0;
    }

    /* Store trigger type */
    irqd_set_trigger_type(irq_data, type);

    return virq;
}
EXPORT_SYMBOL_GPL(irq_create_fwspec_mapping);