Linux device driver development: The descriptor-based GPIO interface

Editor's Note: The embedded Linux kernel already play a vital role in embedded systems and stands to grow in importance in serving the diverse requirements of the Internet of Things (IoT). In turn, device drivers provide the critical link between applications and IoT devices themselves. In Linux Device Drivers Development, author John Madieu offers a comprehensive look at development of these drivers, combining detailed explanation with plenty of code samples.

This excerpt, Chapter 14 from the book, focuses pin control and GPIOs — an area of particular importance to embedded systems developers looking to interact with custom hardware devices. This installment continues this excerpt from Part 1 and Part 2.

One should use the following header to be able to use the new interface:

#include <linux/gpio/consumer.h>

With the descriptor-based interface, prior to allocating and taking the ownership of GPIOs, those GPIOs must have been mapped somewhere. By mapped, I mean they should be assigned to your device, whereas with the legacy integer-based interface, you just have to fetch a number anywhere and request it as a GPIO. Actually, there are three kinds of mapping in the kernel:

Platform data mapping: The mapping is done in the board file.

Device tree: The mapping is done in DT style, the same as discussed in the preceding sections. This is the mapping we will discuss in this book.

Advanced Configuration and Power Interface mapping (ACPI): The mapping is done in ACPI style. Generally used on x86-based systems.

GPIO descriptor mapping - the device tree

GPIO descriptor mappings are defined in the consumer device's node. The property that contains a GPIO descriptor mapping must be named <name>-gpios or <name>-gpio, where <name> is meaningful enough to describe the function for which those GPIOs will be used.

One should always suffix the property name with either -gpio or -gpios because every descriptor-based interface function relies on the gpio_suffixes[] variable, defined in drivers/gpio/gpiolib.h and shown as follows:

On error, these functions will return -ENOENT if no GPIO with the given function is assigned, or another error on which one can use the IS_ERR() macro. The first function returns the GPIO descriptor structure that corresponds to the GPIO at a given index, whereas the second function returns the GPIO at index 0 (useful for one-GPIO mapping). dev is the device to which the GPIO descriptor will belong. It is your device. con_id is the function within the GPIO consumer. It corresponds to the <name> prefix of the property name in the DT. idx is the index (starting from 0) of the GPIO for which one needs a descriptor. flags is an optional parameter that determines the GPIO initialization flags, to configure direction and/or output value. It is an instance of enum gpiod_flags, defined in include/linux/gpio/consumer.h:

The LED GPIOs will be active-high, while the power GPIO will be active-low (that is, gpiod_is_active_low(power) will be true). The reverse operation of allocation is done with the gpiod_put() function:

gpiod_put(struct gpio_desc *desc);

Let us see how one can release red and blue GPIO LEDs:

gpiod_put(blue); gpiod_put(red);

Before we go further, keep in mind that apart from the gpiod_get()/gpiod_get_index() and gpio_put() functions, which completely differ from gpio_request() and gpio_free(), one can perform API translation from integer-based interfaces to descriptor- based ones just by changing the gpio_ prefix into gpiod_.

That said, to change direction, one should use the gpiod_direction_input() and gpiod_direction_output() functions:

value is the state to apply to the GPIO once the direction is set to output. If the GPIO controller has this feature, one can set the debounce timeout of a given GPIO using its descriptor:

int gpiod_set_debounce(struct gpio_desc *desc, unsigned debounce);

In order to access a GPIO given its descriptor, the same attention must be paid as with the integer-based interface. In other words, one should take care whether one is in an atomic (cannot sleep) or non-atomic context, and then use the appropriate function:

John Madieu is an embedded Linux and kernel engineer living in France, in Paris. His main activities consist of developing drivers and Board Support Packages (BSP) for companies in domains such as automation, transport, healthcare, energy, and the military. John works at EXPEMB, a French company that is a pioneer in electronical board design based on computer-on-module, and in embedded Linux solutions. He is an open source and embedded systems enthusiast, convinced that it is only by sharing knowledge that one learns more.