Linux device driver development: The GPIO subsystem

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 begun in Part 1

Adapted from Linux Device Drivers Development, by John Madieu.


Chapter 14. Pin Control and GPIO Subsystem (Continued)
By John Madieu

The GPIO subsystem

From the hardware point of view, a GPIO is a functionality, a mode in which a pin can operate. From a software point of view, a GPIO is nothing but a digital line, which can operate as an input or output, and can have only two values: (1 for high or 0 for low). Kernel GPIO subsystems provide every function you can imagine to set up and handle GPIO line from within your driver:

  • Prior to using a GPIO from within the driver, one should claim it to the kernel. This is a way to take the ownership of the GPIO, preventing other drivers from accessing the same GPIO. After taking the ownership of the GPIO, one can:

    • Set the direction

    • Toggle its output state (driving line high or low) if used as output

    • Set the debounce-interval and read the state, if used as input. For GPIO lines mapped to IRQ, one can define at what edge/level the interrupt should be triggered, and register a handler that will be run whenever the interrupt occurs.

There are actually two different ways to deal with GPIO in the kernel, as follows:

  • The legacy and depreciated integer-based interface, where GPIOs are represented by integer

  • The new and recommended descriptor-based interface, where a GPIO is represented and described by an opaque structure, with a dedicated API

The integer-based GPIO interface: legacy

The integer-based interface is the most well-known. The GPIO is identified by an integer, which is used for every operation that needs to be performed on the GPIO. The following is the header that contains legacy GPIO access functions:

   #include

There are well known functions to handle GPIO in kernel.

Claiming and configuring the GPIO

One can allocate and take the ownership of a GPIO using the gpio_request() function:

   static int gpio_request(unsigned gpio, const char *label)

gpio represents the GPIO number we are interested in, and label is the label used by the kernel for the GPIO in sysfs, as we can see in /sys/kernel/debug/gpio . You have to check the value returned, where 0 mean success, and negative error code on error. Once done with the GPIO, it should be set free with the gpio_free() function:

   void gpio_free(unsigned int gpio)

If in doubt, one can use gpio_is_valid() function to check whether this GPIO number is valid on the system prior to allocate it:

   static bool gpio_is_valid(int number)

Once we own the GPIO, we can change its direction, depending on the need, and whether it should be an input or output, using the gpio_direction_input() or gpio_direction_output() functions:

   static int  gpio_direction_input(unsigned gpio)

   static int  gpio_direction_output(unsigned gpio, int value)

gpio is the GPIO number we need to set the direction. There is a second parameter when it comes to configuring the GPIO as output: value , which is the state the GPIO should be in once the output direction is effective. Here again, the return value is zero or a negative error number. These functions are internally mapped on top of lower level callback functions exposed by the driver of the GPIO controller that provides the GPIO we use. In the next Chapter 15, GPIO Controller Drivers – gpio_chip , dealing with GPIO controller drivers, we will see that a GPIO controller, through its struct gpio_chip structure, must expose a generic set of callback functions to use its GPIOs.

Some GPIO controllers offer the possibility to change the GPIO debounce-interval (this is only useful when the GPIO line is configured as input). This feature is platform-dependent. One can use int gpio_set_debounce() to achieve that:

   static int gpio_set_debounce(unsigned gpio, unsigned debounce)

where debounce is the debounce time in ms.

All the preceding functions should be called in a context that may sleep. It is a good practice to claim and configure GPIOs from within the driver's probe function.

Accessing the GPIO getting/setting the value

You should pay attention when accessing GPIO. In an atomic context, especially in an interrupt handler, one has to be sure the GPIO controller callback functions will not sleep. A well-designed controller driver should be able to inform other drivers (actually clients) whether call to its methods may sleep or not. This can be checked with gpio_cansleep() function.

None of the functions used to access GPIO return an error code. That is why you should pay attention and check return values during GPIO allocation and configuration.

In atomic context

There are GPIO controllers that can be accessed and managed through simple memory read/write operations. These are generally embedded in the SoC, and do not need to sleep. gpio_cansleep() will always return false for those controllers. For such GPIOs, you can get/set their value from within an IRQ handler, using the well-known gpio_get_value() or gpio_set_value() , depending on the GPIO line being configured as input or output :

   static int  gpio_get_value(unsigned gpio)

   void gpio_set_value(unsigned int gpio, int value);

gpio_get_value() should be used when the GPIO is configured as input (using gpio_direction_input() ), and return the actual value (state) of the GPIO. On the other hand, gpio_set_value() will affect the value of the GPIO, which should have been configured as an output using gpio_direction_output() . For both function, value can be considered as Boolean , where zero means low, and non-zero value mean high.

In a non-atomic context (that may sleep)

On the other hand, there are GPIO controllers wired on buses such as SPI and I2C. Since functions accessing those buses may lead to sleep, the gpio_cansleep() function should always return true (it is up to the GPIO controller to take of returning true). In this case, you should not access those GPIOs from within the IRQ handled, at least not in the top half (the hard IRQ). Moreover, the accessors you have to use as your general-purpose access should be suffixed with _cansleep .

   static int gpio_get_value_cansleep(unsigned gpio);

   void gpio_set_value_cansleep(unsigned gpio, int value);

They behave exactly like accessors without the _cansleep() name suffix, with the only difference being that they prevent the kernel from printing warnings when the GPIOs are accessed.

GPIOs mapped to IRQ

Input GPIOs can often be used as IRQ signals. Such IRQs can be edge-triggered or level- triggered. The configuration depends on your needs. The GPIO controller is responsible for providing the mapping between the GPIO and its IRQ. One can use goio_to_irq() to map a given GPIO number to its IRQ number:

   int gpio_to_irq(unsigned gpio);

The return value is the IRQ number, on which one can call request_irq() (or the threaded version request_threaded_irq()) in order to register a handler for this IRQ:

  static irqreturn_t my_interrupt_handler(int irq, void *dev_id)   {      [...]      return IRQ_HANDLED;   }   [...]   int gpio_int = of_get_gpio(np, 0);   int irq_num = gpio_to_irq(gpio_int);   int error = devm_request_threaded_irq(&client->dev, irq_num,                                  NULL, my_interrupt_handler,                                  IRQF_TRIGGER_RISING | IRQF_ONESHOT,                                  input_dev->name, my_data_struct);   if (error) {       dev_err(&client->dev, "irq %d requested failed, %dn",           client->irq, error);       return error;}

Putting it all together

The following code is a summary putting into practice all the concepts discussed regarding integer-based interfaces. This driver manages four GPIOs: two buttons (btn1 and btn2), and two LEDs (green and red). Btn1 is mapped to an IRQ, and whenever its state changes to LOW, the state of btn2 is applied to LEDs. For example, if the state of btn1 goes LOW while btn2 is high, GREEN and RED led will be driven to HIGH:

#include <linux/init.h>#include <linux/module.h>#include <linux/kernel.h>#include <linux/gpio.h>                 /* For Legacy integer based GPIO */#include <linux/interrupt.h>            /* For IRQ *//* * Please choose values that are free on your system */static unsigned int GPIO_LED_RED = 49;static unsigned int GPIO_BTN1 = 115;static unsigned int GPIO_BTN2 = 116;static unsigned int GPIO_LED_GREEN = 120;static int irq;static irqreturn_t btn1_pushed_irq_handler(int irq, void *dev_id){    int state;    /* read the button value and change the led state */    state = gpio_get_value(GPIO_BTN2);    gpio_set_value(GPIO_LED_RED, state);    gpio_set_value(GPIO_LED_GREEN, state);    pr_info("GPIO_BTN1 interrupt: Interrupt! GPIO_BTN2 state is %d)n", state);    return IRQ_HANDLED;}static int __init hellowolrd_init(void){    int retval;    /*     * One could have checked whether the GPIO is valid on the controller or not,     * using gpio_is_valid() function.     * Ex:     *  if (!gpio_is_valid(GPIO_LED_RED)) {     *       pr_infor("Invalid Red LEDn");     *       return -ENODEV;     *   }     */    gpio_request(GPIO_LED_GREEN, "green-led");    gpio_request(GPIO_LED_RED, "red-led");    gpio_request(GPIO_BTN1, "button-1");    gpio_request(GPIO_BTN2, "button-2");    /*     * Configure Button GPIOs as input     *     * After this, one can call gpio_set_debounce()     * only if the controller has the feature     *     * For example, to debounce  a button with a delay of 200ms     *  gpio_set_debounce(GPIO_BTN1, 200);     */    gpio_direction_input(GPIO_BTN1);    gpio_direction_input(GPIO_BTN2);    /*     * Set LED GPIOs as output, with their initial values set to 0     */    gpio_direction_output(GPIO_LED_RED, 0);    gpio_direction_output(GPIO_LED_GREEN, 0);    irq = gpio_to_irq(GPIO_BTN1);    retval = request_threaded_irq(irq, NULL,                            btn1_pushed_irq_handler,                             IRQF_TRIGGER_LOW | IRQF_ONESHOT,                             "device-name", NULL);    pr_info("Hello world!n");    return 0;}static void __exit hellowolrd_exit(void){    free_irq(irq, NULL);    gpio_free(GPIO_LED_RED);    gpio_free(GPIO_LED_GREEN);    gpio_free(GPIO_BTN1);    gpio_free(GPIO_BTN2);    pr_info("End of the worldn");}module_init(hellowolrd_init);module_exit(hellowolrd_exit);MODULE_AUTHOR("John Madieu <john.madieu@gmail.com>");MODULE_LICENSE("GPL");


The next installment will discuss the descriptor-based GPIO interface.

Reprinted with permission from Packt Publishing. Copyright © 2017 Packt Publishing


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.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.