wiki:gpio

General Purpose I/O

General Purpose I/O or GPIO refers to a digital signal from a processor or peripheral capable of sinking or sourcing current.

There are several details that are important when dealing with GPIOs:

  • voltage they drive to or are pulled up to
  • is there a pull-up or pull-down present
  • are they driving to a active-high or active-low state (totem-pole) or are they driving low and relying on a pull-up to go high (open collector)
  • how much current can they sink
  • how much current can they source
  • what bus is used to access them (which can affect when/where and how-fast you can read or write them)
  • does the gpio have an associated interrupt available (important if you want to use this as a low-overhead input)

Gateworks boards use General Purpose Input / Output (GPIO) pins commonly for things such as:

  • enabling/disabling of devices (ie serial drivers)
  • signal steering (ie steer USB bus between front-panel connector/header and PCI/PCIe slot)
  • device interrupt inputs (ie GSC host interrupt)
  • misc inputs (ie GPS PPS (pulse-per-second) input)
  • LED drivers (these are handled via led-class and not gpio-class)
  • pushbutton inputs (often handled via button-class and not gpio-class)

All off-board GPIO signals use 3.3V TTL voltage levels.

Power-on GPIO states

Usually these signals are used in such a way that the default power-on state requires no configuration of GPIO signals (ie, the power up state matches the desired power-on function).

As customer needs vary, power-on GPIO states may vary. There are a few options for configuring GPIO's that need to be changed from the 'hardware default':

  • bootloader configuration (occurs within milliseconds of board power-up)
    • Note the avila/cambria product families can configure ARM host GPIO0-5 according to non-volatile configuration stored in the board's EEPROM which can be manipulated via the RedBoot? bootloaders 'econfig' command
  • kernel configuration (occurs within seconds of board power-up)
  • userspace configuration (occurs within 10s of seconds of board power-up)
    • typically this would be done inside of an /etc/rc.local script or other init script. See OpenWrt/init and Yocto/services for more info.

Each of these options requires a different level of software complexity.

Fast vs Slow GPIO

A fast GPIO is one that is directly on the ARM SoC and requires memory mapped register reads and writes to access.

A slow GPIO is one that is provided by a peripheral device that hangs off the SoC on I2C, SPI, USB, or other type of bus that requires complicated bus access mechanisms. These GPIO's are also defined by the linux kernel as GPIO's that 'can sleep' and are not suitable for certain applications that require low latency or deterministic access times in the Linux kernel or in your application usage.

Gateworks products offer a bit of variety:

  • all GPIO's provided by the Gateworks System Controller (including the user pushbutton) are over I2C are slow gpio
  • Ventana boards have up to 4x DIO's to a connector that are ARM based fast GPIO's
  • GPIO's going to the miniPCI and miniPCIe connectors such as WDIS# and PERST# are ARM based fast GPIO's

You can look at the output of /sys/kernel/debug/gpio to see what GPIO's are available and from what controller. The controller name will indicate if its an I2C controller or not.

Linux GPIO

gpiolib and gpio class

The Linux kernel can provide gpio functionality to userspace via a gpio class accessed via sysfs (/sys/class/gpio/) implemented through gpiolib (CONFIG_GPIOLIB, drivers/gpio/gpiolib*). For userspace access this requires sysfs kernel support (CONFIG_SYSFS) and the sysfs filesystem mounted which is standard for most linux systems including the Gateworks BSP's. This framework allows boards to define GPIO configuration (direction, direction-changeable, user-friendly name, state, and userspace configurable).

Note that any gpio that is registered for use by a driver is not available for exporting for userspace control. You can cat /sys/kernel/debug/gpio to see what gpio's are current registered with the kernel.

Here are some example use cases for using gpio-class via sysfs:

  • to see gpio names, config (direction), and current state: (requires debugfs kernel support and fs mounted)
    cat /sys/kernel/debug/gpio
    
  • to export a GPIO that is available to userspace (ie provided by a gpio controller, not in-use by a kernel driver, and not already exported):
    echo 9 > /sys/class/gpio/export # export gpio5 (will create /sys/class/gpio/gpio9 and configure as input)
    echo out > /sys/class/gpio/gpio5/direction # make it an output
    echo 1 > /sys/class/gpio/gpio5/value # set its value to high
    
  • to see the direction of a gpio via sysfs (only allowed for GPIO's configured as bi-directional)
    cat /sys/class/gpio/gpio5/direction  ;# in/out
    
  • to set the direction of a gpio via sysfs (only allowed for GPIO's configured as bi-directional)
    echo out > /sys/class/gpio/gpio5/direction ;# set as output
    echo in > /sys/class/gpio/gpio5/direction ;# set as input
    
  • to see the state of a gpio via sysfs
    cat /sys/class/gpio/gpio5/value  ;# show current state (0=low, any other value than 0=high)
    
  • to set the state of a gpio via sysfs
    echo 0 > /sys/class/gpio/gpio5/value  ;# assert low
    echo 1 > /sys/class/gpio/gpio5/value  ;# assert high
    

GPIO mapping

The various product hardware manuals will call out GPIO devices on the board and provide a table mapping the GPIO to a GPIO host controller, a function, and a connector pinout (if going off-board).

This hardware mapping is defined in the Linux kernel device-tree for Ventana (ie arch/arm/boot/dts/imx6qdl-gw54xx.dtsi) or in the kernel board-support file for other product families (file, ie arch/arm/mach-cns3xxx/laguna.c).

If debugfs is enabled in the kernel (as it is on Gateworks BSP's) you can see a nice table of GPIO mapping via /sys/kernel/debug/gpio. Some examples:

  • GW5400:
    # cat /sys/kernel/debug/gpio 
    GPIOs 0-31, platform/209c000.gpio, 209c000.gpio:
     gpio-26  (pps-gpio            ) in  lo    
     gpio-29  (PCIe reset          ) out lo    
     gpio-30  (phy-reset           ) out hi    
    
    GPIOs 32-63, platform/20a0000.gpio, 20a0000.gpio:
    
    GPIOs 64-95, platform/20a4000.gpio, 20a4000.gpio:
     gpio-86  (usb_otg_vbus        ) out lo    
    
    GPIOs 96-127, platform/20a8000.gpio, 20a8000.gpio:
     gpio-102 (user1               ) out lo    
     gpio-103 (user2               ) out lo    
     gpio-111 (user3               ) out lo    
    
    GPIOs 128-159, platform/20ac000.gpio, 20ac000.gpio:
    
    GPIOs 160-191, platform/20b0000.gpio, 20b0000.gpio:
    
    GPIOs 192-223, platform/20b4000.gpio, 20b4000.gpio:
     gpio-192 (2198000.usdhc cd    ) in  hi    
    
    GPIOs 240-255, i2c/0-0023, pca9555, can sleep:
     gpio-240 (user_pb             ) in  hi 
    
    • Note that this does not show GPIO's that are not used by Linux drivers such as USB HUB reset, Ethernet PHY reset, etc.
  • GW2388:
    # cat /sys/kernel/debug/gpio 
    GPIOs 0-31, cns3xxx_gpio0:
     gpio-0   (GPS_PPS             ) in  hi
     gpio-3   (USB_FAULT#          ) in  hi
     gpio-6   (USB_VBUS_EN         ) out hi
     gpio-7   (GSM_SEL0            ) out lo
     gpio-8   (GSM_SEL1            ) out lo
     gpio-9   (FP_SER_EN           ) out lo
    
    GPIOs 32-63, cns3xxx_gpio1:
    
    GPIOs 100-115, i2c/0-0023, pca9555, can sleep:
     gpio-100 (USER_PB#            ) in  hi
     gpio-114 (user2               ) out hi
     gpio-115 (user1               ) out hi
    
  • GW2387:
    # cat /sys/kernel/debug/gpio 
    GPIOs 0-31, cns3xxx_gpio0:
     gpio-0   (GPS_PPS             ) in  hi
     gpio-2   (USB_FAULT#          ) in  hi
     gpio-5   (USB_PCI_SEL         ) out lo
     gpio-6   (USB_VBUS_EN         ) out hi
     gpio-7   (GSM_SEL0            ) out lo
     gpio-8   (GSM_SEL1            ) out lo
     gpio-9   (FP_SER_EN           ) out lo
    
    GPIOs 32-63, cns3xxx_gpio1:
    
    GPIOs 100-115, i2c/0-0023, pca9555, can sleep:
     gpio-100 (USER_PB#            ) in  hi
     gpio-114 (user2               ) out hi
     gpio-115 (user1               ) out hi
    

Product Family specific notes:

  • Ventana:
    • IMX6 GPIOs: The IMX6 has 7 32bit GPIO controllers for a possible 224 fast ARM based GPIO's
      • GPIO1_IO[0-31]: gpio0-gpio31
      • GPIO2_IO[0-31]: gpio32-gpio63
      • GPIO3_IO[0-31]: gpio64-gpio95
      • GPIO4_IO[0-31]: gpio96-gpio127
      • GPIO5_IO[0-31]: gpio128-gpio159
      • GPIO6_IO[0-31]: gpio160-gpio191
      • GPIO7_IO[0-31]: gpio192-gpio223
    • GSC GPIOs:
      • GSC_GPIO_P0[0-7]: gpio240-gpio247
      • GSC_GPIO_P1[0-7]: gpio247-gpio255
    • additional i2c based GPIO port-expanders present on some boards would begin at gpio116
  • Laguna:
    • cns3xxx GPIOs: The CNS3xxx has 2 32bit GPIO controllers for a possible 64 fast ARM based GPIO's
    • GSC GPIOs:
      • GSC_GPIO_P0[0-7]: gpio100-gpio107
      • GSC_GPIO_P1[0-7]: gpio108-gpio115
    • additional i2c based GPIO port-expanders present on some boards would begin at gpio116

When referring to Gateworks board user hardware manuals you will need to translate the hardware pin name to a gpio using the information above. Some examples:

  • Laguna GPIOA8 would be the 9th IO on the GPIOA controller (the first is GPIOA0) thus map to gpio 0+8 = gpio-8
  • Laguna GPIOB1 would be the 2nd IO on the GPIOB controller thus map to gpio 32+2 = gpio-34
  • Ventana GPIO[1]:DIO16 is GPIO block 1 IO 16 thus maps to gpio0 + 16 (or 0*32+16) = gpio-16
  • Ventana GPIO[2]:DIO9 is GPIO block 2 IO 9 thus maps to gpio32 + 9 (or 1*32+9) = gpio-41
  • Equation: Ventana GPIO<x>IO<y> is GPIO block x IO y thus maps to: gpio-((x-1)*32)+y

See also:

catching a GPIO change from userspace without polling using an interrupt

A gpio that has an interrupt associated with it (which depends on the gpio controller and how its hooked up to the processor) can be used with a blocking poll system call. This may seem non-intuitive that you use a 'poll' call to block until an interrupt has occurred, but that is exactly what will happen when you use poll on the sysfs 'value' file representing the value of the gpio in question. Note that even if you are not interested in the value files contents (e.g. counting the total number of interrupts caused by a gpio) you must use both an lseek and a read after poll to consume the interrupt. Otherwise the previous interrupt will cause future poll calls to immediately return.

Note that you can only do this with an GPIO that has interrupt support which means when its exported to userspace there will be an 'edge' file in this /sys/class/gpio directory.

Example code:

/*
 * gpio-poll.c - demonstrate catching a GPIO change event without polling
 *               ironically using the poll(2) system call
 *
 * Author: Tim Harvey <tharvey@gateworks.com>
 */

#include <fcntl.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#ifdef __ANDROID__
#include <cutils/android_reboot.h>
#else
#include <sys/reboot.h>
#endif
#include <sys/types.h>
#include <sys/stat.h>

/* seconds to block waiting for edge
 *  - this also defines the interval between calls if button is held down
 */
#define BLOCK_TIME 5
/* seconds before press-and-release count is cleared */
#define MIN_REST_TIME 2

/* called when a gpio changes value
 * @value - 0 is low, 1 is high
 *
 * The GSC pushbutton has an internal pullup, thus a 'button' should be
 * normally open, and when pressed should short to ground. Therefore a 0
 * is 'down', and a 1 is 'up'
 */
time_t held_time = 0; /* num seconds button held down */
time_t last = 0; /* last event time */
int num_releases = 0; /* number of successive press-and-release events */
void gpio_change(int gpio, int value)
{
        time_t now = time(NULL);

        printf("gpio%d: %d last=%ld\n", gpio, value, (long)(now - last));
        if (!value) { /* down */
                long held;
                if (!held_time)
                        held_time = now;

                held = (long)(now - held_time);
                printf("held=%ld\n", held);

                /* if held for more than 5 seconds */
                if (held >= 5) {
                        printf("Powering system down\n");
#ifdef __ANDROID__
                        /* will perform a safe unmount prior to shutdown */
                        android_reboot(ANDROID_RB_POWEROFF, 0, 0);
#else
                        reboot(RB_POWER_OFF);
#endif
                        while (1);
                }
        } else { /* up */
                /* cancel held time counter */
                held_time = 0;
                /* increment or reset num_releases */
                if (now - last < MIN_REST_TIME) {
                        num_releases++;
                        printf("count=%d\n", num_releases);

                        /* perform something on number of presses? */
                } else {
                        printf("reset count\n");
                        num_releases = 0;
                }

                last = time(NULL);
        }
}


int main(int argc, char **argv)
{
        struct pollfd fdset;
        char path[256];
        char buf[32];
        int gpio;
        int fd, rz, c;

        if (argc < 3) {
                fprintf(stderr, "usage: %s <gpio> <rising|falling|both>\n",
                        argv[0]);
                exit (-1);
        }

        gpio = atoi(argv[1]);

        /* configure gpio trigger:
         *   edge trigger of 'both' (falling and rising) allows catching
         *   changes in both directions vs level triggerd.
         */
        sprintf(path, "/sys/class/gpio/gpio%d/edge", gpio);
        if ((fd = open(path, O_WRONLY)) < 0) {
                perror("open() failed\n");
                fprintf(stderr, "non-exported or non-input gpio: %s\n", path);
                exit (-1);
        }
        write(fd, argv[2], strlen(argv[2]));
        close(fd);

        /* open gpio sysfs value for reading in blocking mode */
        sprintf(path, "/sys/class/gpio/gpio%d/value", gpio);
        if ((fd = open(path, O_RDONLY | O_NONBLOCK)) < 0) {
                perror("open() failed\n");
                fprintf(stderr, "invalid or non-exported gpio: %s\n", path);
                exit (-1);
        }

        printf("monitoring %s for interrupt using poll()\n", path);
        while (1) {
                /* use poll(2) to block for 10s or until interrupt occurs */
                fdset.fd = fd;
                fdset.events = POLLPRI;
                fdset.revents = 0;
                if ((c = poll(&fdset, 1, 10000)) < 0) {
                        perror("poll() failed");
                        break;
                }

                if (fdset.revents & POLLPRI) {
                        /* show gpio value */
                        /* Note that both an lseek and read are necessary
                         * in order to clear the interrupt detected by poll
                         */
                        lseek(fdset.fd, 0, SEEK_SET);
                        rz = read(fdset.fd, buf, sizeof(buf));
                        buf[rz ? rz - 1 : 0] = 0;
                        gpio_change(gpio, atoi(buf));
                }
        }
        close(fd);

        return 0;
}

Example usage (block until interrupt rising and falling edge interrupt occurs on gpio-100):

root@OpenWrt:/# ./gpio-poll 100 both
monitoring /sys/class/gpio/gpio100/value for interrupt using poll()
gpio100: 1
gpio100: 0
gpio100: 1
gpio100: 0
gpio100: 1

using GPIO's as buttons in Linux

Often users want to use a GPIO as a button. The best way to do this in Linux is to use the gpio-keys (KEYBOARD_GPIO) or gpio-keys-polled (KEYBOARD_GPIO_POLLED) Linux input driver which allows you to assign Linux gpio's to Linux input key events which will be fired when the button is pressed and released.

The gpio-keys driver is for gpio's that are interrupt capable and the gpio-keys-polled driver is for gpio's that are not interrupt capable and thus must be polled periodically.

As an example of how to configure the gpio-keys driver we can look at the GW51xx which has DIO0-DIO4 mapped to ARM gpio's. The imx6qdl-gw51xx.dtsi device-tree can have the following section added:

       /* define linux keyboard gpio-keys driver named 'gpio-button' */
       gpio: gpio-button {
               compatible = "gpio-keys";
               #address-cells = <1>;
               #size-cells = <0>;
               autorepeat;

               /* map GW51xx DIO0 IMX6 GPIO1_IO16 to linux keycode 103 (KEY_UP) */
               button@1 {
                        label = "GPIO Key Up";
                        linux,code = <103>;
                        debounce-interval = <100>;
                        gpios = <&gpio1 16 GPIO_ACTIVE_LOW>;
               };

               /* map GW51xx DIO1 IMX6 GPIO1__IO19 to linux keycode 109 (KEY_DOWN) */
               button@2 {
                        label = "GPIO Key Down";
                        linux,code = <108>;
                        debounce-interval = <100>;
                        gpios = <&gpio1 19 GPIO_ACTIVE_LOW>;
               };

               /* map GW51xx DIO2 IMX6 GPIO1__IO17 to linux keycode 106 (KEY_RIGHT) */
               button@3 {
                        label = "GPIO Key Right";
                        linux,code = <106>;
                        debounce-interval = <100>;
                        gpios = <&gpio1 17 GPIO_ACTIVE_LOW>;
               };

               /* map GW51xx DIO3 IMX6 GPIO1__IO18 to linux keycode 105 (KEY_LEFT) */
               button@4 {
                        label = "GPIO Key Left";
                        linux,code = <105>;
                        debounce-interval = <100>;
                        gpios = <&gpio1 18 GPIO_ACTIVE_LOW>;
               };
        };

  • The gpio node needs to be at the top level and its placement is arbitrary however its customary to put it in alphabetical order (thus between the chosen node and the leds node)

BSP Specific notes:

  • OpenWrt replaces the gpio-keys and gpio-keys-polled Linux driver with their out-of-tree driver called gpio-button-hotplug which instead of emiting linux input events emits uevent messages to the button subsystem which tie into the OpenWrt hotplug daemon. See the OpenWrt/gpio page for more details.
  • Android can map Linux input events to Android key events via a KeyLayout file - see here for details.

References:

Linux LED class for GPIO (and PWM) controlled LEDs

see wiki:linux/led

Other References

Here are some other useful references from our wiki:

Last modified 14 months ago Last modified on 06/30/16 09:07:52