Advertisement

Linux device driver development: The descriptor-based GPIO interface

John Madieu

October 09, 2018

John MadieuOctober 09, 2018

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.  

Adapted from Linux Device Drivers Development, by John Madieu.


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

The descriptor-based GPIO interface: the new and recommended way

With the new descriptor-based GPIO interface, a GPIO is characterized by a coherent struct gpio_desc structure:

  struct gpio_desc {
      struct gpio_chip  *chip;
      unsigned long flags;
      const char *label;
};


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:

   /* gpio suffixes used for ACPI and device tree lookup */
   static const char * const gpio_suffixes[] = { "gpios", "gpio" };

Let us see how by having a look at the function used to look for GPIO descriptors mappings in devices in DT:

  static struct gpio_desc *of_find_gpio(struct device *dev,
                                       const char *con_id,
                                      unsigned int idx,
                                      enum gpio_lookup_flags *flags)
   {
      char prop_name[32]; /* 32 is max size of property name */
      enum of_gpio_flags of_flags;
      struct gpio_desc *desc;
      unsigned int i;
      for (i = 0; i < ARRAY_SIZE(gpio_suffixes); i++) {
            if (con_id)
                  snprintf(prop_name, sizeof(prop_name), "%s-%s",
                          con_id,
                         gpio_suffixes[i]);
            else
                  snprintf(prop_name, sizeof(prop_name), "%s",
                         gpio_suffixes[i]);
            desc = of_get_named_gpiod_flags(dev->of_node,
                                             prop_name, idx,
                                    &of_flags);
            if (!IS_ERR(desc) || (PTR_ERR(desc) == -EPROBE_DEFER))
                        break;
            }

if (IS_ERR(desc))                   return desc;             if (of_flags & OF_GPIO_ACTIVE_LOW)                   *flags |= GPIO_ACTIVE_LOW;             return desc;    }

Now, let us consider the following node, which is an excerpt of Documentation/gpio/board.txt :

  foo_device {
      compatible = "acme,foo";
      [...]
      led-gpios = <&gpio 15 GPIO_ACTIVE_HIGH>, /* red */
                  <&gpio 16 GPIO_ACTIVE_HIGH>, /* green */
                  <&gpio 17 GPIO_ACTIVE_HIGH>; /* blue */
      power-gpios = <&gpio 1 GPIO_ACTIVE_LOW>;
      reset-gpios = <&gpio 1 GPIO_ACTIVE_LOW>;
   };


This is what a mapping should look like, with meaningful name.

Allocating and using GPIO

One can use either gpiog_get() or gpiod_get_index() to allocate a GPIO descriptor:

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

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:

  enum gpiod_flags {
       GPIOD_ASIS = 0,
       GPIOD_IN = GPIOD_FLAGS_BIT_DIR_SET,
       GPIOD_OUT_LOW = GPIOD_FLAGS_BIT_DIR_SET |
                       GPIOD_FLAGS_BIT_DIR_OUT,
       GPIOD_OUT_HIGH = GPIOD_FLAGS_BIT_DIR_SET |
                        GPIOD_FLAGS_BIT_DIR_OUT |
                        GPIOD_FLAGS_BIT_DIR_VAL,
};


Now let us allocate GPIO descriptors for mappings defined in the preceding DT:

  struct gpio_desc *red, *green, *blue, *power;
   red = gpiod_get_index(dev, "led", 0, GPIOD_OUT_HIGH);
   green = gpiod_get_index(dev, "led", 1, GPIOD_OUT_HIGH);
   blue = gpiod_get_index(dev, "led", 2, GPIOD_OUT_HIGH);
   power = gpiod_get(dev, "power", GPIOD_OUT_HIGH);


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:

   int gpiod_direction_input(struct gpio_desc *desc);
   int gpiod_direction_output(struct gpio_desc *desc, int value);

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:

  int gpiod_cansleep(const struct gpio_desc *desc);
   /* Value get/set from sleeping context */
   int gpiod_get_value_cansleep(const struct gpio_desc *desc);
   void gpiod_set_value_cansleep(struct gpio_desc *desc, int value);
   /* Value get/set from non-sleeping context */
   int gpiod_get_value(const struct gpio_desc *desc);
   void gpiod_set_value(struct gpio_desc *desc, int value);


For a GPIO descriptor mapped to IRQ, one can use gpiod_to_irq() in order to get the IRQ number that corresponds to the given GPIO descriptor, which can be used with the request_irq() function:

   int gpiod_to_irq(const struct gpio_desc *desc);

At any given time in the code, one can switch from the descriptor-based interface to the legacy integer-based interface and vice versa, using the desc_to_gpio() or gpio_to_desc() functions:

   /* Convert between the old gpio_ and new gpiod_ interfaces */
   struct gpio_desc *gpio_to_desc(unsigned gpio);
   int desc_to_gpio(const struct gpio_desc *desc);

 

Putting it all together

The driver bellows summarizes the concepts introduced in descriptor-based interfaces. The principle is the same, as are the GPIOs:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>      /* For platform devices */
#include <linux/gpio/consumer.h>        /* For GPIO Descriptor interface */
#include <linux/interrupt.h>            /* For IRQ */
#include <linux/of.h>                   /* For DT*/
/*
 * Let us consider the bellow mapping
 *
 *    foo_device {
 *       compatible = "packt,gpio-descriptor-sample";
 *       led-gpios = <&gpio2 15 GPIO_ACTIVE_HIGH>, // red
 *                   <&gpio2 16 GPIO_ACTIVE_HIGH>, // green
 *
 *       btn1-gpios = <&gpio2 1 GPIO_ACTIVE_LOW>;
 *       btn2-gpios = <&gpio2 1 GPIO_ACTIVE_LOW>;
 *   };
 */
static struct gpio_desc *red, *green, *btn1, *btn2;
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 = gpiod_get_value(btn2);
    gpiod_set_value(red, state);
    gpiod_set_value(green, state);
    pr_info("btn1 interrupt: Interrupt! btn2 state is %d)\n", state);
    return IRQ_HANDLED;
}
static const struct of_device_id gpiod_dt_ids[] = {
    { .compatible = "packt,gpio-descriptor-sample", },
    { /* sentinel */ }
};
static int my_pdrv_probe (struct platform_device *pdev)
{
    int retval;
    struct device *dev = &pdev->dev;
    /*
     * We use gpiod_get/gpiod_get_index() along with the flags
     * in order to configure the GPIO direction and an initial
     * value in a single function call.
     *
     * One could have used:
     *  red = gpiod_get_index(dev, "led", 0);
     *  gpiod_direction_output(red, 0);
     */
    red = gpiod_get_index(dev, "led", 0, GPIOD_OUT_LOW);
    green = gpiod_get_index(dev, "led", 1, GPIOD_OUT_LOW);
    /*
     * Configure Button GPIOs as input
     *
     * After this, one can call gpiod_set_debounce()
     * only if the controller has the feature
     * For example, to debounce  a button with a delay of 200ms
     *  gpiod_set_debounce(btn1, 200);
     */
    btn1 = gpiod_get(dev, "btn1", GPIOD_IN);
    btn2 = gpiod_get(dev, "btn2", GPIOD_IN);
    irq = gpiod_to_irq(btn1);
    retval = request_threaded_irq(irq, NULL,
                            btn1_pushed_irq_handler,
                            IRQF_TRIGGER_LOW | IRQF_ONESHOT,
                            "gpio-descriptor-sample", NULL);
    pr_info("Hello! device probed!\n");
    return 0;
}
static int my_pdrv_remove(struct platform_device *pdev)
{
    free_irq(irq, NULL);
    gpiod_put(red);
    gpiod_put(green);
    gpiod_put(btn1);
    gpiod_put(btn2);
    pr_info("good bye reader!\n");
    return 0;
}
static struct platform_driver mypdrv = {
    .probe      = my_pdrv_probe,
    .remove     = my_pdrv_remove,
    .driver     = {
        .name     = "gpio_descriptor_sample",
        .of_match_table = of_match_ptr(gpiod_dt_ids), 
        .owner    = THIS_MODULE,
    },
};
module_platform_driver(mypdrv);
MODULE_AUTHOR("John Madieu <john.madieu@gmail.com>");
MODULE_LICENSE("GPL");


The next installment will discuss the GPIO interface and device tree.

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.

 

 

Loading comments...