Skip to content

Day11 - Multi-GPIO IRQ Driver

Overview

This lab demonstrates how to build a Linux platform driver that:

  • controls multiple GPIO outputs (LEDs)
  • reads an input GPIO (button)
  • handles button events using IRQ
  • exposes a character device interface (/dev/mygpio)

Quick Start

make
sudo insmod mygpio.ko
sudo dtoverlay mygpio-irq

dmesg -w

Test:

echo led1=1 | sudo tee /dev/mygpio
echo led2=1 | sudo tee /dev/mygpio
echo all=0 | sudo tee /dev/mygpio

cat /dev/mygpio

Press the button and observe IRQ logs.


Hardware Mapping

Function GPIO
LED1 GPIO17
LED2 GPIO27
BTN GPIO22

Architecture Overview

User Space
   |
   |  read/write
   v
/dev/mygpio  (char device)
   |
   v
mygpio driver (platform driver)
   |        \
   |         -> IRQ handler (button)
   |
   +--> LED1 GPIO (output)
   +--> LED2 GPIO (output)
   +--> BTN GPIO  (input -> IRQ)

Device Tree Overlay

/dts-v1/;
/plugin/;

/ {
    compatible = "brcm,bcm2712";

    fragment@0 {
        target-path = "/";

        __overlay__ {
            mygpio: mygpio@0 {
                compatible = "myvendor,mygpio-irq";

                led1-gpios = <&gpio 17 GPIO_ACTIVE_HIGH>;
                led2-gpios = <&gpio 27 GPIO_ACTIVE_HIGH>;
                btn-gpios  = <&gpio 22 GPIO_ACTIVE_LOW>;
            };
        };
    };
};

Driver Design

Private Data Structure

/**
 * @brief Private runtime data for mygpio device
 */
struct mygpio_dev {
    struct device *dev;

    struct gpio_desc *led1_gpiod;
    struct gpio_desc *led2_gpiod;
    struct gpio_desc *btn_gpiod;

    int btn_irq;
    int button_state;

    struct mutex lock;

    dev_t devt;
    struct cdev cdev;
    struct class *class;
};

IRQ Flow

GPIO (BTN)
   -> gpiod_to_irq()
   -> devm_request_irq()
   -> ISR (mygpio_btn_isr)
   -> update button_state

IRQ Handler

static irqreturn_t mygpio_btn_isr(int irq, void *data)
{
    struct mygpio_dev *mydev = data;
    int value;

    /* Keep ISR short and non-blocking */
    value = gpiod_get_value(mydev->btn_gpiod);
    if (value < 0) {
        dev_err(mydev->dev, "IRQ read failed: %d\n", value);
        return IRQ_HANDLED;
    }

    mydev->button_state = value;

    dev_info(mydev->dev, "Button IRQ: state=%d\n", value);

    return IRQ_HANDLED;
}

Probe Flow

1. Allocate device structure (devm_kzalloc)
2. Request GPIOs (devm_gpiod_get)
3. Read initial button state
4. Convert GPIO to IRQ (gpiod_to_irq)
5. Register IRQ (devm_request_irq)
6. Create char device
7. Save driver data (platform_set_drvdata)

Read Interface

Returns current device state:

led1=0 led2=1 btn=0

Write Interface

Supported commands:

led1=0
led1=1
led2=0
led2=1
all=0
all=1

Build

make

Load Driver

sudo insmod mygpio.ko

Load Overlay

sudo dtoverlay mygpio-irq

Verify

dmesg | tail

Expected:

mygpio Day 11 probed successfully, btn_irq=XX

Test LED Control

echo led1=1 | sudo tee /dev/mygpio
echo led2=1 | sudo tee /dev/mygpio
echo all=0 | sudo tee /dev/mygpio

Test Read

cat /dev/mygpio

Example:

led1=1 led2=0 btn=1

Test IRQ

dmesg -w

Press button:

Button IRQ: state=1
Button IRQ: state=0

Common Issues

1. gpiod_to_irq() fails

  • overlay not loaded
  • wrong GPIO number
  • invalid pin

2. No IRQ triggered

  • wiring issue
  • missing pull-up
  • wrong polarity
  • wrong trigger edge

3. Button state inverted

Check:

btn-gpios = <&gpio 22 GPIO_ACTIVE_LOW>;

Key Concepts Learned

  • Multiple GPIO handling in one driver
  • Device Tree GPIO mapping
  • Interrupt handling in Linux kernel
  • devm_* resource management
  • Character device + IRQ integration

Next Steps

  • Debounce handling
  • Toggle LED on button press
  • Blocking read (wait queue)
  • poll/select support
  • threaded IRQ

File Structure

docs/labs/day11-multi-gpio-irq/
├── mygpio.c
├── mygpio-irq.dts
├── Makefile
└── day11-multi-gpio-irq.md