Linux 4.5 GPIO Interrupt через Devicetree на платформе Xilinx Zynq

Я использую специальную плату для разработки с Zynq XC72010, используемую для запуска ядра Linux 4.5. Я разрабатываю драйвер устройства для микросхемы, которую мы тестируем самостоятельно, и у меня много проблем с попыткой привязать линию GPIO к программному IRQ. Пока что я испробовал несколько методов и исчерпал весь поиск в Google, который мог придумать. Соответствующие части моей конфигурации дерева устройств:

/ {
    compatible = "xlnx,zynq-7000";

    amba {
        compatible = "simple-bus";
        #address-cells = <1>;
        #size-cells = <1>;
        interrupt-parent = <&intc>;
        ranges;

        intc: interrupt-controller@f8f01000 {
            compatible = "arm,cortex-a9-gic";
            #interrupt-cells = <3>;
            interrupt-controller;
            reg = <0xF8F01000 0x1000>,
                  <0xF8F00100 0x100>;
        };

        i2c0: i2c@e0004000 {
            compatible = "cdns,i2c-r1p10";
            status = "disabled";
            clocks = <&clkc 38>;
            interrupt-parent = <&intc>;
            interrupts = <0 25 4>;
            reg = <0xe0004000 0x1000>;
            #address-cells = <1>;
            #size-cells = <0>;

            // I WANT INTERRUPT TO TRIGGER
            // ON THIS DEVICE (axi_gpio_0, pin 2)
            device: device@48 {
                compatible = "device,name";
                reg = <0x48>;
                reset-gpios = <&axi_gpio_0 1 0>;
                interrupt-parent = <&axi_gpio_0>;
                interrupt-gpios = <&axi_gpio_0 2 0>;
            };
        };
    };

    amba_pl {
        #address-cells = <1>;
        #size-cells = <1>;
        compatible = "simple-bus";
        ranges ;
        axi_gpio_0: gpio@41200000 {
            #gpio-cells = <2>;
            compatible = "xlnx,xps-gpio-1.00.a";
            gpio-controller;
            interrupt-parent = <&intc>;
            interrupts = <0 31 4>;
            reg = <0x41200000 0x10000>;
            xlnx,all-inputs = <0x0>;
            xlnx,all-inputs-2 = <0x0>;
            xlnx,all-outputs = <0x0>;
            xlnx,all-outputs-2 = <0x0>;
            xlnx,dout-default = <0x00000000>;
            xlnx,dout-default-2 = <0x00000000>;
            xlnx,gpio-width = <0x10>;
            xlnx,gpio2-width = <0x20>;
            xlnx,interrupt-present = <0x1>;
            xlnx,is-dual = <0x0>;
            xlnx,tri-default = <0xFFFFFFFF>;
            xlnx,tri-default-2 = <0xFFFFFFFF>;
    };
};

Я пытаюсь назначить прерывание контакту 2 «axi_gpio_0» внутри «устройства».

Просмотр Google дал 3 распространенных метода привязки прерывания в коде драйвера:

/* Method 1 */
device->interrupt_gpio = devm_gpiod_get_optional(&i2c_client->dev,
                                        "interrupt", GPIOD_IN);

if(IS_ERR(device->interrupt_gpio))
    return PTR_ERR(device->interrupt_gpio);

printk("device: Interrupt GPIO = %d\n",desc_to_gpio(device->interrupt_gpio));

irq = gpiod_to_irq(device->interrupt_gpio);
printk("device: IRQ = %d\n",irq);

ret = devm_request_threaded_irq(&i2c_client->dev, irq,
    NULL, device_irq_thread, IRQF_ONESHOT | IRQF_TRIGGER_HIGH,
    "device", device);
if (ret != 0)
    dev_err(&i2c_client->dev, "Failed to request IRQ: %d\n", ret);



/* Method 2 */
device->interrupt_gpio = devm_gpiod_get_optional(&i2c_client->dev,
    "interrupt", GPIOD_ASIS);

if (IS_ERR(device->interrupt_gpio))
    return PTR_ERR(device->interrupt_gpio);


if (device->interrupt_gpio) {
    dev_info(&i2c_client->dev, "Found interrupt GPIO: %d\n",desc_to_gpio(device->interrupt_gpio));
    dev_info(&i2c_client->dev, "IRQ Number: %d\n",gpiod_to_irq(device->interrupt_gpio));

    gpio_request(desc_to_gpio(device->interrupt_gpio), "DEVICE_INT");    // Request a GPIO pin from the driver
    gpio_direction_input(desc_to_gpio(device->interrupt_gpio));           // Set GPIO as input
    gpio_set_debounce(desc_to_gpio(device->interrupt_gpio), 50);          // Set a 50ms debounce, adjust to your needs
    gpio_export(desc_to_gpio(device->interrupt_gpio), false);                    // The GPIO will appear in /sys/class/gpio

    ret = request_irq(gpiod_to_irq(device->interrupt_gpio),             // requested interrupt
                   (irq_handler_t) irqHandler,  // pointer to handler function
                   IRQF_TRIGGER_RISING,         // interrupt mode flag
                   "DEVICE_IRQ_HANDLER",        // used in /proc/interrupts
                   NULL);                       // the *dev_id shared interrupt lines, NULL is okay
    if (ret != 0) {
        dev_err(&i2c_client->dev,
            "Failed to request IRQ: %d\n", ret);
    }
}
else {
    dev_err(&i2c_client->dev, "Failed to get interrupt GPIO pin\n");
}



/* Method 3 */
dev_info(&i2c_client->dev, "IRQ requested: %d\n", i2c_client->irq);

ret = devm_request_threaded_irq(&i2c_client->dev, i2c_client->irq,
NULL, device_irq_thread, IRQF_ONESHOT | IRQF_TRIGGER_LOW,
"device", device);
if (ret != 0)
    dev_err(&i2c_client->dev, "Failed to request IRQ: %d\n", ret);

Я пробовал комбинации всех этих методов и различных конфигураций дерева устройств, но ни один из них не достиг нужной мне функциональности.

Метод 1 приводит к следующему результату:

device: Interrupt GPIO = 892
device: IRQ = -6
device 0-0048: Failed to request IRQ: -22

Метод 2 приводит к следующему результату:

device 0-0048: Found interrupt GPIO: 892
device 0-0048: IRQ Number: -6
device 0-0048: Failed to request IRQ: -22

Итак, попытка использовать дескриптор GPIO и старый GPIO api не увенчались успехом в привязке прерывания.

Чтобы попробовать метод 3, я настроил дерево устройств:

device: device@48 {
    compatible = "device,name";
    reg = <0x48>;
    interrupt-parent = <&axi_gpio_0>; // or <&intc>?
    interrupts = <0 2 0x02>; // trying to grab pin 2
};

Метод 3 приводит к следующему результату:

genirq: Setting trigger mode 2 for irq 168 failed (gic_set_type+0x0/0x48)
device 0-0048: IRQ requested: 168
genirq: Setting trigger mode 8 for irq 168 failed (gic_set_type+0x0/0x48)
device 0-0048: Failed to request IRQ: -22

Похоже, проблема заключается в назначении программного прерывания конкретному GPIO в Linux. Я не вижу, что мне здесь не хватает. Любой совет приветствуется.

РЕДАКТИРОВАТЬ 1:

Я обнаружил, что Linux по какой-то причине не любит низкоуровневые прерывания. Изменение метода 3 на:

device: device@48 {
    compatible = "device,name";
    reg = <0x48>;
    interrupt-parent = <&axi_gpio_0>;
    interrupts = <0 2 0x04>;
};

И код драйвера для:

dev_info(&i2c_client->dev, "IRQ requested: %d\n", i2c_client->irq);

ret = devm_request_threaded_irq(&i2c_client->dev, i2c_client->irq,
NULL, device_irq_thread, IRQF_ONESHOT | IRQF_TRIGGER_HIGH,
"device", device);
if (ret != 0)
    dev_err(&i2c_client->dev, "Failed to request IRQ: %d\n", ret);

позволяет мне успешно запросить IRQ. Однако у меня активный низкий уровень сигнала, так что это мне особо не помогает. Кроме того, я не уверен, что этот метод ссылается на axi_gpio_0 контакт 2 как сигнал прерывания. Я могу использовать как intc, так и axi_gpio_0 как interrupt-parent, и он соответствует одному и тому же номеру IRQ (я вижу это из cat /proc/interrupts). Итак, игнорируя полярность сигнала, как мне убедиться, что мое зарегистрированное прерывание запускается на основе переключения axi_gpio_0 контакта 2?

РЕДАКТИРОВАТЬ 2:

Я отследил проблему с запросом прерывания с активным низким уровнем у драйвера для контроллера прерываний: kernel/drivers/irqchip/irq-gic.c. Этот фрагмент кода является причиной проблемы:

static int gic_set_type(struct irq_data *d, unsigned int type)
{
    void __iomem *base = gic_dist_base(d);
    unsigned int gicirq = gic_irq(d);

    /* Interrupt configuration for SGIs can't be changed */
    if (gicirq < 16)
        return -EINVAL;

    /* SPIs have restrictions on the supported types */
    if (gicirq >= 32 && type != IRQ_TYPE_LEVEL_HIGH &&
                type != IRQ_TYPE_EDGE_RISING)
        return -EINVAL;

    return gic_configure_irq(gicirq, type, base, NULL);
}

Взламывать ядро ​​- это совсем не то, чем я хочу заниматься, а комментирую:

/* SPIs have restrictions on the supported types */
/*if (gicirq >= 32 && type != IRQ_TYPE_LEVEL_HIGH &&
            type != IRQ_TYPE_EDGE_RISING)
return -EINVAL;*/

позволяет мне запросить прерывание с низким уровнем активности. В целях тестирования это должно временно работать.

ОБНОВИТЬ:

Я успешно создал IRQ на контакте GPIO. Моя проблема была в контроллере GPIO, который я использовал. Контроллер представлял собой IP-блок Xilinx внутри блока Zynq Programmable Logic, и этот контроллер не может запускать прерывания на выводах GPIO (по неизвестным мне причинам). Я припаял контакт прерывания на плате, над которой я работал, к контакту GPIO на другом, более общем контроллере, и теперь Linux отлично со мной играет.

Подводя итог, можно сказать, что контроллер GPIO, соответствующий compatible = "xlnx,xps-gpio-1.00.a";, не может связываться с программными прерываниями в Linux. Если у вас возникла эта проблема, ИСПОЛЬЗУЙТЕ ДРУГОЙ КОНТРОЛЛЕР GPIO.

Спасибо всем за помощь.


person James Schulman    schedule 29.08.2016    source источник
comment
-22 - -EINVAL (недопустимый аргумент) для Meth 1/2. Кроме того, номера IRQ должны быть ›= 0, поэтому либо printk неверен, либо номер IRQ является отрицательным (что соответствует EINVAL, IMO).   -  person Craig Estey    schedule 29.08.2016
comment
ya -22 возникает из-за запроса отрицательного IRQ (gpiod_to_irq дает мне отрицательное число, -ENXIO). Таким образом, может показаться, что gpiod_to_irq не может найти номер IRQ для GPIO, который я указываю в devicetree.   -  person James Schulman    schedule 29.08.2016
comment
Откуда взялось название свойства interrupt-gpios? Вы придумали это имя? Как ваш драйвер получает значение этого свойства?   -  person sawdust    schedule 30.08.2016
comment
Узел дерева устройств, который вы используете для метода 3, является правильным. Я не думаю, что свойство interrupt-gpios существует (как указано в комментарии выше).   -  person Longfield    schedule 30.08.2016
comment
interrupt-gpios - это свойство, которое я использовал для указания вывода GPIO в axi_gpio_0. Я получаю значение с помощью device->interrupt_gpio = devm_gpiod_get_optional(&i2c_client->dev, "interrupt", GPIOD_IN);, где device->interrupt_gpio - это поле в структуре, которую я определил в другом месте.   -  person James Schulman    schedule 30.08.2016


Ответы (1)


Используя узел дерева устройств метода 3, вы сможете получить IRQ с помощью irq_of_parse_and_map(i2c_client->dev.of_node, 0).

Полученное IRQ можно затем запросить, как вы это делали с devm_request_threaded_irq().

person Longfield    schedule 30.08.2016
comment
Спасибо за ответ. Ваше предложение сработало, и в моем драйвере зарегистрировано прерывание. Я обнаружил, что по какой-то неизвестной причине Linux не позволяет мне регистрировать прерывание по низкому уровню или по падающему фронту, но вполне устраивает высокоуровневый или нарастающий фронт. Это было причиной двух разных genirq: Setting trigger mode ошибок в методе 3. Однако у меня низкий активный сигнал, поэтому мне нужно найти способ инвертировать логику сигнала. - person James Schulman; 30.08.2016
comment
Я выяснил, почему Linux рявкнул на мое прерывание при низком уровне активности. См. ИЗМЕНИТЬ 2 выше. - person James Schulman; 30.08.2016
comment
Я думаю, что свойство ìnterrupts = <0 2 0x4> неверно, и каким-то образом это приводит к тому, что прерывание регистрируется GIC, а не контроллером прерываний GPIO. Должно получиться interrupts = <2 0x4>. - person Longfield; 31.08.2016