Linux GPIO Subsystem: Legacy vs. Descriptor-Based APIs

Legacy GPIO Interface

Kernel API

/* Device-tree helper */
int of_get_gpio(struct device_node *np, int idx);

/* Pin life-cycle */
int  gpio_request(unsigned gpio, const char *label);
void gpio_free(unsigned gpio);

/* Direction */
int  gpio_direction_input(unsigned gpio);
int  gpio_direction_output(unsigned gpio, int value);

/* Value access */
int  gpio_get_value(unsigned gpio);
void gpio_set_value(unsigned gpio, int value);

Device-tree snippet

leds_legacy: user_leds@0 {
    compatible = "vendor,leds-legacy";
    label      = "user_leds";
    /* Property must be named "gpios" */
    gpios = <&gpioc 3 GPIO_ACTIVE_HIGH>,
            <&gpioc 4 GPIO_ACTIVE_HIGH>,
            <&gpioc 5 GPIO_ACTIVE_HIGH>;
};

Minimal driver

#define LED_COUNT 3
static int led_gpio[LED_COUNT];

static int legacy_led_probe(struct platform_device *pdev)
{
    struct device_node *np = pdev->dev.of_node;
    int i, ret;

    for (i = 0; i < LED_COUNT; i++) {
        led_gpio[i] = of_get_gpio(np, i);
        if (led_gpio[i] < 0)
            return led_gpio[i];

        ret = devm_gpio_request(&pdev->dev, led_gpio[i], "user_led");
        if (ret)
            return ret;

        gpio_direction_output(led_gpio[i], 0);
    }
    return 0;
}

static const struct of_device_id legacy_led_of_match[] = {
    { .compatible = "vendor,leds-legacy" },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, legacy_led_of_match);

static struct platform_driver legacy_led_driver = {
    .probe  = legacy_led_probe,
    .driver = {
        .name           = "legacy_leds",
        .of_match_table = legacy_led_of_match,
    },
};
module_platform_driver(legacy_led_driver);
MODULE_LICENSE("GPL");

Modern Descriptor Interface

Kernel API

/* Acquisition */
struct gpio_desc *gpiod_get(struct device *dev, const char *con_id,
                            enum gpiod_flags flags);
struct gpio_desc *gpiod_get_index(struct device *dev, const char *con_id,
                                  unsigned int idx, enum gpiod_flags flags);

/* Managed variants (auto-cleanup) */
struct gpio_desc *devm_gpiod_get(struct device *dev, const char *con_id,
                                 enum gpiod_flags flags);
struct gpio_desc *devm_gpiod_get_index(struct device *dev, const char *con_id,
                                       unsigned int idx, enum gpiod_flags flags);

/* Release (only for non-managed) */
void gpiod_put(struct gpio_desc *desc);
void devm_gpiod_put(struct device *dev, struct gpio_desc *desc);

/* Direction & value */
int  gpiod_direction_input(struct gpio_desc *desc);
int  gpiod_direction_output(struct gpio_desc *desc, int value);
int  gpiod_get_value(const struct gpio_desc *desc);
void gpiod_set_value(struct gpio_desc *desc, int value);

Device-tree flexibility

leds_new: user_leds@1 {
    compatible = "vendor,leds-new";
    label      = "user_leds";
    /* Any of these names are accepted */
    led-gpios = <&gpioc 3 GPIO_ACTIVE_HIGH>,
                <&gpioc 4 GPIO_ACTIVE_HIGH>,
                <&gpioc 5 GPIO_ACTIVE_HIGH>;
};

Modern driver

#define LED_COUNT 3
static struct gpio_desc *led_desc[LED_COUNT];

static int new_led_probe(struct platform_device *pdev)
{
    int i;

    for (i = 0; i < LED_COUNT; i++) {
        led_desc[i] = devm_gpiod_get_index(&pdev->dev, "led", i,
                                           GPIOD_OUT_LOW);
        if (IS_ERR(led_desc[i]))
            return PTR_ERR(led_desc[i]);
    }
    return 0;
}

static const struct of_device_id new_led_of_match[] = {
    { .compatible = "vendor,leds-new" },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, new_led_of_match);

static struct platform_driver new_led_driver = {
    .probe  = new_led_probe,
    .driver = {
        .name           = "new_leds",
        .of_match_table = new_led_of_match,
    },
};
module_platform_driver(new_led_driver);
MODULE_LICENSE("GPL");

Migratino hints

  • Replace of_get_gpio()devm_gpiod_get_index()
  • Replace gpio_request()/gpio_free() with managed variants
  • Use struct gpio_desc * instead of raw integers
  • Property names in DT can now be *-gpios, *-gpio, gpios, or gpio

Tags: Linux gpio device-tree kernel-module embedded

Posted on Thu, 07 May 2026 08:47:11 +0000 by cdorob