关于PCA9555扩展器上#interrupt-cells配置的困惑

时间:2017-06-13 17:04:08

标签: c linux linux-kernel interrupt device-tree

我尝试在自定义平台上首次设置设备树源文件。板上是恩智浦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;
}

这是我第一次使用设备树,所以我希望它显而易见,我只是缺席。

1 个答案:

答案 0 :(得分:1)

在查看了所有评论后,我做了一些额外的阅读,并找到了答案。

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

实际interrupts binding的文档非常有用,而不是设备驱动程序的文档。

以下是对从intspec到IRQ_TYPE*的转换如何发生的详细说明:

函数of_irq_parse_one将中断说明符整数复制到struct of_phandle_args here。然后通过消费者函数(例如irq_create_of_mapping)将此arg传递给of_irq_get。然后,此函数将这些参数通过struct irq_fwspec映射到of_phandle_args_to_fwspec,并将其fwspec数据传递给irq_create_fwspec_mapping。这些功能都在irqdomain.c中找到。此时,irq将属于irq_domain或使用irq_default_domain。据我所知 - pca853x驱动程序使用默认域。此域通常由特定于平台的代码设置。我通过搜索irq_domain_ops on cross reference找到了我的。其中很多似乎只是通过intspec[1] & IRQ_TYPE_SENSE_MASKtype简单地复制到irq_create_fwspec_mapping中的irq_domain_translate变量。从这里开始,类型通过irq_data设置为irq的irqd_set_trigger_type

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);