linux-device-driverinterruptgpioled

Linux GPIO driver only handles 2 IRQs instead of 4 that were requested


I am learning to write Linux drivers with the book 'Linux-Device-Drivers 3rd Edition', and I wanted to implement a simple driver for a RaspberryPi Model 4 B to control some LEDs and buttons on a breadboard that are connected to GPIO pins.

I have 4 LEDs and 4 buttons, one for each.

This is my DTS

/dts-v1/;
/plugin/;

/ {
    fragment@0 {
        target-path = "/";
        __overlay__ {
            led {
                compatible = "led";
                label = "led";
                status = "okay";
                led-gpios = <&gpio 17 0>, // blue
                            <&gpio 27 0>, // red
                            <&gpio 22 0>, // green
                            <&gpio 23 0>; // yellow

                btn-gpios = <&gpio 5 1>, // blue-btn
                            <&gpio 6 1>, // red-btn
                            <&gpio 26 1>, // green-btn
                            <&gpio 24 1>; // yellow-btn
            };
        };
    };
};

led.h

#include <linux/gpio/consumer.h>

#define DATA_BUFFER_SIZE 1096

struct led_device {
    struct gpio_desc *blue, 
              *red, 
              *green, 
              *yellow, 
              *btn_0, 
              *btn_1, 
              *btn_2, 
              *btn_3;
    int irq_btn_0, 
        irq_btn_1, 
        irq_btn_2, 
        irq_btn_3;
    unsigned long btn_0_last_press_jiffies,
              btn_1_last_press_jiffies,
              btn_2_last_press_jiffies,
              btn_3_last_press_jiffies;
    struct proc_dir_entry *proc_file;
    char data_buffer[DATA_BUFFER_SIZE];
};

This is my driver code led.c

#include <linux/module.h>
#include <linux/init.h>
#include <linux/mod_devicetable.h>
#include <linux/property.h>
#include <linux/platform_device.h>
#include <linux/of_device.h>
#include <linux/proc_fs.h>
#include <linux/interrupt.h>

#include <linux/jiffies.h>
extern unsigned long volatile jiffies;

#include "led.h"

MODULE_AUTHOR("******");
MODULE_LICENSE("GPL v2");

static struct led_device led_dev = {
    .blue = NULL,
    .red = NULL,
    .green = NULL,
    .yellow = NULL,
    .btn_0 = NULL,
    .btn_1 = NULL,
    .btn_2 = NULL,
    .btn_3 = NULL,
    .irq_btn_0 = 0,
    .irq_btn_1 = 0,
    .irq_btn_2 = 0,
    .irq_btn_3 = 0,
    .btn_0_last_press_jiffies = 0,
    .btn_1_last_press_jiffies = 0,
    .btn_2_last_press_jiffies = 0,
    .btn_3_last_press_jiffies = 0,
    .proc_file = NULL,
};

#define LED_BLUE_INDEX 0
#define LED_RED_INDEX 1
#define LED_GREEN_INDEX 2
#define LED_YELLOW_INDEX 3

#define BTN_0_INDEX 0
#define BTN_1_INDEX 1
#define BTN_2_INDEX 2
#define BTN_3_INDEX 3

static int dt_probe(struct platform_device *pdev);
static int dt_remove(struct platform_device *pdev);

static struct of_device_id led_ids[] = {
    {
        .compatible = "led",
    }, { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, led_ids);

static struct platform_driver led_driver = {
    .probe = dt_probe,
    .remove = dt_remove,
    .driver = {
        .name = "led",
        .of_match_table = led_ids,
    },
};

static irqreturn_t handle_irq(int irq, void *dev_id) {
    static unsigned long flags = 0;

    local_irq_save(flags);
    if (irq == led_dev.irq_btn_0) {
        pr_info("Button 0 was pushed\n");
    } else if (irq == led_dev.irq_btn_1) {
        pr_info("Button 1 was pushed\n");
    } else if (irq == led_dev.irq_btn_2) {
        pr_info("Button 2 was pushed\n");
    } else if (irq == led_dev.irq_btn_3) {
        pr_info("Button 3 was pushed\n");
    }
    local_irq_restore(flags);

    return IRQ_HANDLED;
};

static ssize_t led_write(struct file *filp, const char *user_buffer, size_t count, loff_t *offset) {
    int led, action;
    struct gpio_desc *gpio;

    memset(led_dev.data_buffer, 0, sizeof(led_dev.data_buffer));

    count = min(count, sizeof(led_dev.data_buffer));

    if (copy_from_user(led_dev.data_buffer, user_buffer, count)) {
        pr_err("[led] Failed to copy data from user\n");
        return -EFAULT;
    }

    if (sscanf(led_dev.data_buffer, "%d,%d", &led, &action) != 2) {
        pr_err("[led] Invalid command\n");
        return -EINVAL;
    }

    switch (led) {
        case LED_BLUE_INDEX:
            gpio = led_dev.blue;
            break;
        case LED_RED_INDEX:
            gpio = led_dev.red;
            break;
        case LED_GREEN_INDEX:
            gpio = led_dev.green;
            break;
        case LED_YELLOW_INDEX:
            gpio = led_dev.yellow;
            break;
        default:
            return count;
    }

    gpiod_set_value(gpio, !!action);
    return count;
}

static ssize_t led_read(struct file *filp, char __user *user_buffer, size_t count, loff_t *offset) {
    return 0;
};

static struct proc_ops fops = {
    .proc_write = led_write,
    .proc_read = led_read,
};

static int setup_leds_gpios(struct device *dev) {
    int ret = 0;

    led_dev.blue = gpiod_get_index(dev, "led", LED_BLUE_INDEX, GPIOD_OUT_LOW);
    if (IS_ERR(led_dev.blue)) {
        pr_err("[led] Failed to setup blue LED\n");
        return PTR_ERR(led_dev.blue);
    }

    led_dev.red = gpiod_get_index(dev, "led", LED_RED_INDEX, GPIOD_OUT_LOW);
    if (IS_ERR(led_dev.red)) {
        pr_err("[led] Failed to setup red LED\n");
        ret = PTR_ERR(led_dev.red);
        goto error_red;
    }

    led_dev.green = gpiod_get_index(dev, "led", LED_GREEN_INDEX, GPIOD_OUT_LOW);
    if (IS_ERR(led_dev.green)) {
        pr_err("[led] Failed to setup green LED\n");
        ret = PTR_ERR(led_dev.green);
        goto error_green;
    }

    led_dev.yellow = gpiod_get_index(dev, "led", LED_YELLOW_INDEX, GPIOD_OUT_LOW);
    if (IS_ERR(led_dev.yellow) {
        pr_err("[led] Failed to setup yellow LED\n");
        ret = PTR_ERR(led_dev.yellow);
        goto error_yellow;
    }

    return ret; 

error_yellow:
    gpiod_put(led_dev.green);
error_green:
    gpiod_put(led_dev.red);
error_red:
    gpiod_put(led_dev.blue);
    return ret;
};

static int setup_btns_gpios(struct device *dev) {
    int ret = 0;

    led_dev.btn_0 = gpiod_get_index(dev, "btn", BTN_0_INDEX, GPIOD_IN);
    if (IS_ERR(led_dev.btn_0)) {
        pr_err("[led] Failed to setup button 0\n");
        return PTR_ERR(led_dev.btn_0);
    }

    led_dev.btn_1 = gpiod_get_index(dev, "btn", BTN_1_INDEX, GPIOD_IN);
    if (IS_ERR(led_dev.btn_1)) {
        printk("[led] Failed to setup button 1\n");
        ret = PTR_ERR(led_dev.btn_1);
        goto error_btn_1;
    }

    led_dev.btn_2 = gpiod_get_index(dev, "btn", BTN_2_INDEX, GPIOD_IN);
    if (IS_ERR(led_dev.btn_2)) {
        printk("[led] Failed to setup button 2\n");
        ret = PTR_ERR(led_dev.btn_2);
        goto error_btn_2;
    }

    led_dev.btn_3 = gpiod_get_index(dev, "btn", BTN_3_INDEX, GPIOD_IN);
    if (IS_ERR(led_dev.btn_3)) {
        printk("[led] Failed to setup button 3\n");
        ret = PTR_ERR(led_dev.btn_3);
        goto error_btn_3;
    }

    return ret;

error_btn_3:
    gpiod_put(led_dev.btn_2);
error_btn_2:
    gpiod_put(led_dev.btn_1);
error_btn_1:
    gpiod_put(led_dev.btn_0);
    return ret;
};

static int setup_btns_irqs(void) {
    int ret = 0;

    led_dev.irq_btn_0 = gpiod_to_irq(led_dev.btn_0);
    ret = request_irq(led_dev.irq_btn_0, 
                      handle_irq,
                      IRQF_TRIGGER_FALLING,
                      "led-btn-0", 
                      NULL);
    if (ret) {
        pr_err("[led] Failed to register IRQ for button 0\n");
        return ret;
    }

    led_dev.irq_btn_1 = gpiod_to_irq(led_dev.btn_1);
    ret = request_irq(led_dev.irq_btn_1, 
                      handle_irq,
                      IRQF_TRIGGER_FALLING,
                      "led-btn-1", 
                      NULL);
    if (ret) {
        pr_err("[led] Failed to register IRQ for button 1\n");
        goto btn_1_irq_error;
    }

    led_dev.irq_btn_2 = gpiod_to_irq(led_dev.btn_2);
    ret = request_irq(led_dev.irq_btn_2, 
                      handle_irq,
                      IRQF_TRIGGER_FALLING,
                      "led-btn-2", 
                      NULL);
    if (ret) {
        pr_err("[led] Failed to register IRQ for button 2\n");
        goto btn_2_irq_error;
    }

    led_dev.irq_btn_3 = gpiod_to_irq(led_dev.btn_3);
    ret = request_irq(led_dev.irq_btn_3, 
                      handle_irq,
                      IRQF_TRIGGER_FALLING,
                      "led-btn-3", 
                      NULL);
    if (ret) {
        pr_err("[led] Failed to register IRQ for button 3\n");
        goto btn_3_irq_error;
    }

    return ret;

btn_3_irq_error:
    free_irq(led_dev.irq_btn_2, NULL);
btn_2_irq_error:
    free_irq(led_dev.irq_btn_1, NULL);
btn_1_irq_error:
    free_irq(led_dev.irq_btn_0, NULL);
    return ret;
};

static void free_btns_gpios(void) {
    if (led_dev.btn_0) {
             gpiod_put(led_dev.btn_0);
        led_dev.btn_0 = NULL;
    }

    if (led_dev.btn_1) {
        gpiod_put(led_dev.btn_1);
        led_dev.btn_1 = NULL;
    }

    if (led_dev.btn_2) {
        gpiod_put(led_dev.btn_2);
        led_dev.btn_2 = NULL;
    }

    if (led_dev.btn_3) {
        gpiod_put(led_dev.btn_3);
        led_dev.btn_3 = NULL;
    }
};

static void free_leds_gpios(void) {
    if (led_dev.blue) {
        gpiod_put(led_dev.blue);
        led_dev.blue = NULL;
    }

    if (led_dev.red) {
        gpiod_put(led_dev.red);
        led_dev.red = NULL;
    }

    if (led_dev.green) {
        gpiod_put(led_dev.green);
        led_dev.green = NULL;
    }

    if (led_dev.yellow) {
        gpiod_put(led_dev.yellow);
        led_dev.yellow = NULL;
    }
};

static void free_btns_irqs(void) {
    if (led_dev.irq_btn_0) {
        free_irq(led_dev.irq_btn_0, NULL);
        led_dev.irq_btn_0 = 0;
    }

    if (led_dev.irq_btn_1) {
        free_irq(led_dev.irq_btn_1, NULL);
        led_dev.irq_btn_1 = 0;
    }

    if (led_dev.irq_btn_2) {
        free_irq(led_dev.irq_btn_2, NULL);
        led_dev.irq_btn_2 = 0;
    }

    if (led_dev.irq_btn_3) {
        free_irq(led_dev.irq_btn_3, NULL);
        led_dev.irq_btn_3 = 0;
    }
};

static int dt_probe(struct platform_device *pdev) {
    struct device *dev = &pdev->dev;
    int ret = 0;
    
    if ((ret = setup_leds_gpios(dev))) {
        return ret;
    }

    if ((ret = setup_btns_gpios(dev))) {
        goto error_btns;
    }

    if ((ret = setup_btns_irqs())) {
        goto error_btn_irqs;
    }
    
    led_dev.proc_file = proc_create("led", 0666, NULL, &fops);
    if (led_dev.proc_file == NULL) {
        pr_err("[led] Failed to create /proc/led\n");
        ret = -ENOMEM;
        goto error_proc_file;
    }

    pr_info("[led] Successfully setup GPIO driver\n");

    return ret;

error_proc_file:
    free_btns_irqs();
error_btn_irqs:
    free_btns_gpios();
error_btns:
    free_leds_gpios();

    return ret;
}

static int dt_remove(struct platform_device *pdev) {
    pr_info("[led] Removing device\n");

    free_btns_irqs();
    free_btns_gpios();
    free_leds_gpios();
    proc_remove(led_dev.proc_file);

    return 0;
}

static int __init led_init(void) {
    pr_info("[led] Loading LED driver\n");
    if (platform_driver_register(&led_driver)) {
        pr_err("[led] Failed to register driver\n");
        return -1;
    }
    return 0;
};

static void __exit led_cleanup(void) {
    pr_info("[led] Cleaning up LED driver\n");
    platform_driver_unregister(&led_driver);
};

module_init(led_init);
module_exit(led_cleanup);

All of the buttons' circuits are wired - GPIO IN pin -> button -> GND. I followed the instructions on the RaspberryPi website for connecting the buttons.

My problem is when I load my module and press the buttons, only the IRQs of btn_0 and btn_1 run.

This is the output of dmesg after pressing all of the buttons one after the other in order

[led] Successfully setup GPIO driver
Button 0 was pushed
Button 1 was pushed

This is the content of /proc/interrupts

 69:          1          0          0          0  pinctrl-bcm2835   5 Edge      led-btn-0
 70:          1          0          0          0  pinctrl-bcm2835   6 Edge      led-btn-1
 71:          0          0          0          0  pinctrl-bcm2835  26 Edge      led-btn-2
 72:          0          0          0          0  pinctrl-bcm2835  24 Edge      led-btn-3

I can't seem to understand why the IRQs for btn_2 and btn_3 aren't triggering no matter how many times I press the buttons.

Edit:

From what I read online, it seems that since my pins are connected to GND, I need to set a pull-up resistor so that when the circuit is open, the pin will read HIGH, I'm just not sure on how to do that from within the kernel module.


Solution

  • As @0andiry mentions in their comments, the issue really was with the pull bias. Turns out the pull bias for GPIO pins 24 and 26 is low. I made sure of that with the command

    sudo cat /sys/kernel/debug/gpio
    

    The output shows that the pull bias is low, as indicated by the lo part

    gpio-24  (GPIO24              |btn                 ) in  lo IRQ ACTIVE LOW
    gpio-26  (GPIO24              |btn                 ) in  lo IRQ ACTIVE LOW
    

    To fix that, I changed the pull bias to high in my device tree file in the &gpio node. This is the working DTS

    /dts-v1/;
    /plugin/;
    
    / {
        compatible = "brcm,bcm2835", "brcm,bcm2708", "brcm,bcm2709", "brcm,bcm2710", "brcm,bcm2711";
        fragment@0 {
            target = <&gpio>;
            __overlay__ {
                btn_pins: btn_pins {
                    brcm,pins = <5 6 26 24>; // gpio no.
                    brcm,function = <0>;     // 0:in, 1:out
                    brcm,pull = <2>;         // 2:up 1:down 0:none
                };
            };
        };
        fragment@1 {
            target-path = "/";
            __overlay__ {
                led {
                    compatible = "led";
                    label = "led";
                    status = "okay";
                    pinctrl-0 = <&btn_pins>;
                    pinctrl-names = "default";
                    led-gpios = <&gpio 17 0>, // led-0
                                <&gpio 27 0>, // led-1
                                <&gpio 22 0>, // led-2
                                <&gpio 23 0>; // led-3
    
                    btn-gpios = <&gpio 5 1>,  // btn-0
                                <&gpio 6 1>,  // btn-1
                                <&gpio 26 1>, // btn-2
                                <&gpio 24 1>; // btn-3
                };
            };
        };
    };
    

    Checking /sys/kernel/debug/gpio after rebuilding my driver and DTS and loading them, it shows the following

    gpio-24  (GPIO24              |btn                 ) in  hi IRQ ACTIVE LOW
    gpio-26  (GPIO24              |btn                 ) in  hi IRQ ACTIVE LOW