This guide describes how to implement a GPIO interrupt driver on the Nitrogen93 platform using Ezurio's Yocto BSP. The driver uses the Linux GPIO descriptor API and is matched to a device tree node through a custom "compatible" string.
Overview
The driver configures a specific GPIO pin as an interrupt source. It requests the GPIO using the device tree, converts it into an IRQ, and registers a handler. The implementation uses devm-managed functions for automatic resource cleanup and integrates with Linux runtime power management. This approach is designed for use with Nitrogen93 SMARC hardware under Ezurio's Yocto BSP.
Device Tree Setup
Add the following node to your carrier board’s device tree overlay or directly into the DTS file used (e.g., "imx93-nitrogen-smarc.dts"):
gpioirq@0 {
compatible = "ezurio,nitrogen93-gpioirq";
gpio-irq-gpios = <&gpio1 3 GPIO_ACTIVE_LOW>;
};Kernel Driver Source Code
Create a file namedgpio-irq-demo.c and save it under your Yocto layer at:meta-yourlayer/recipes-kernel/gpio-irq-demo/files/gpio-irq-demo.c/*
* Simple GPIO IRQ demo platform driver.
* Requests a GPIO via the "irq-gpios" DT property, maps it to an IRQ,
* and logs a message each time the interrupt fires.
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/gpio/consumer.h> /* gpiod_get/put, descriptor helpers */
#include <linux/of.h> /* DeviceTree matching */
#include <linux/platform_device.h> /* platform_driver/probe/remove */
#include <linux/interrupt.h> /* request_irq, irq handler types */
#include <linux/pm_runtime.h> /* runtime PM helpers */
/* Per-device driver data */
struct gpio_irq_dev {
struct gpio_desc *gpiod; /* GPIO descriptor obtained from DT */
int irq; /* Linux IRQ number mapped from GPIO */
};
/* Top-half interrupt handler: keep it fast/minimal */
static irqreturn_t gpio_irq_handler(int irq, void *dev_id) {
struct gpio_irq_dev *dev = dev_id;
/* Print which GPIO fired (for demo/logging only) */
pr_info("GPIO IRQ: triggered on GPIO %d\n", desc_to_gpio(dev->gpiod));
return IRQ_HANDLED;
}
/* Probe is called when a matching DT node binds to this driver */
static int gpio_irq_probe(struct platform_device *pdev) {
struct gpio_irq_dev *dev;
int irq_flags, ret;
/* Allocate zeroed per-device data tied to device lifecycle */
dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL);
if (!dev)
return -ENOMEM;
/*
* Get the GPIO from DeviceTree using the "irq-gpios" property.
* Example DT:
* irq: gpio-irq@0 {
* compatible = "ezurio,nitrogen93-gpioirq";
* irq-gpios = <&gpioX Y GPIO_ACTIVE_LOW>;
* };
*
* In code, the consumer name "irq" maps to "<name>-gpios" => "irq-gpios".
*/
dev->gpiod = devm_gpiod_get(&pdev->dev, "irq", GPIOD_IN);
if (IS_ERR(dev->gpiod))
return dev_err_probe(&pdev->dev, PTR_ERR(dev->gpiod),
"Failed to get GPIO\n");
/* Translate the GPIO to a Linux IRQ number */
dev->irq = gpiod_to_irq(dev->gpiod);
if (dev->irq < 0)
return dev_err_probe(&pdev->dev, dev->irq,
"Failed to get IRQ\n");
/*
* If DT or firmware specified an IRQ trigger, keep it; otherwise,
* default to falling edge for the demo.
*/
irq_flags = irq_get_trigger_type(dev->irq);
if (!irq_flags)
irq_flags = IRQF_TRIGGER_FALLING;
/* Make dev available to remove()/PM/etc via pdev->dev.driver_data */
platform_set_drvdata(pdev, dev);
/*
* Request the IRQ with a managed (devm_) lifetime.
* The handler receives our 'dev' pointer as its dev_id.
*/
ret = devm_request_irq(&pdev->dev, dev->irq, gpio_irq_handler,
irq_flags, dev_name(&pdev->dev), dev);
if (ret)
return dev_err_probe(&pdev->dev, ret, "IRQ request failed\n");
/* Enable runtime PM in case the platform benefits from it */
pm_runtime_enable(&pdev->dev);
dev_info(&pdev->dev, "GPIO IRQ driver initialized (irq=%d, flags=0x%x)\n",
dev->irq, irq_flags);
return 0;
}
/* Remove is called on driver unbind; devm_ resources auto-free */
static int gpio_irq_remove(struct platform_device *pdev) {
pm_runtime_disable(&pdev->dev);
return 0;
}
/* Match table for of_platform binding */
static const struct of_device_id gpio_irq_of_match[] = {
{ .compatible = "ezurio,nitrogen93-gpioirq" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, gpio_irq_of_match);
/* Platform driver glue */
static struct platform_driver gpio_irq_driver = {
.probe = gpio_irq_probe,
.remove = gpio_irq_remove,
.driver = {
.name = "gpio-irq-demo",
.of_match_table = gpio_irq_of_match,
},
};
/* Register init/exit boilerplate for a platform_driver */
module_platform_driver(gpio_irq_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Ezurio");
MODULE_DESCRIPTION("GPIO IRQ driver for Nitrogen93");Yocto Integration
Create the BitBake recipe at:meta-yourlayer/recipes-kernel/gpio-irq-demo/gpio-irq-demo_0.1.bbDESCRIPTION = "GPIO IRQ kernel module for Nitrogen93"
LICENSE = "GPLv2"
LIC_FILES_CHKSUM = "file://gpio-irq-demo.c;beginline=1;endline=10;md5=<FIXME>"
SRC_URI = "file://gpio-irq-demo.c"
S = "${WORKDIR}"
inherit module
KERNEL_MODULE_AUTOLOAD += "gpio-irq-demo"To include the driver in your image, add this line to your image recipe or .bbappend file (for Ezurio use "ezurio-core-image-base"):
IMAGE_INSTALL:append = " gpio-irq-demo"Build and Test
Rebuild and flash the image with Ezurio's image target:
bitbake gpio-irq-demo
bitbake ezurio-core-image-baseAfter booting, check that the driver initialized:
dmesg | grep "GPIO IRQ driver initialized"Trigger the GPIO to verify the interrupt:GPIO IRQ: triggered on GPIO NIf nothing is logged, confirm:
- Device tree overlay is loaded
- GPIO routing and electrical level are correct
- IRQ edge polarity matches hardware
- Module is present:
lsmod | grep gpio_irq_demo
/filters:background_color(white)/2025-02/Nitrogen%2093%20NX611%20front-1port.png)