IMX6-SOLOX GPIO Interrupt Handling In Kernel Space
IMX6-SOLOX GPIO Interrupt Handling In Kernel Space

This post is to explain the handling of GPIO interrupts in Linux with IMX6-SOLOX. I will use Udoo neo full development board which is based on NXP IMX6-SOLOX SOC. In Udoo neo full there are 32 GPIOs for user access. Out of 32, two GPIOs are connected to LEDs, and one GPIO connected to the push button.

In Kernel Space

In IMX6 each pin supports multiple functionalities the mechanism by which one chooses the mode a pin should work on is called pin muxing. The system responsible for pin muxing is called the pin controller subsystem.

The pin controller driver is responsible for parsing pin descriptions in the DT and applying their configuration to the chip. The driver usually needs a set of two nested nodes to describe a group of pins configurations.

  • pinctrl-<ID>: It is a list of phandles, each of which points to a pin configuration node. Pin configuration is applying electronic properties of pins such as the pull-up, pull-down, driver strength, debounce period, and so on
  • pinctrl-name: This allows for giving a name to each state in a list. List entry 0 defines the name for integer state ID 0. The state ID 0 is commonly given the name default.
int_key{
            compatible = "led-key";         
            led-gpios = <&gpio1 22 GPIO_ACTIVE_HIGH>, 
                        <&gpio1 14 GPIO_ACTIVE_HIGH>;   
            key-gpios = <&gpio1 25 GPIO_ACTIVE_HIGH>;       
            pinctrl-names = "default";      
            pinctrl-0 = <&pinctrl_key_gpio>;
            interrupt-parent = <&gpio1>;    
            interrupts = <25 IRQ_TYPE_EDGE_RISING>;
        };

&iomuxc {
...
            pinctrl_key_gpio: key_gpio {
                fsl,pins = <MX6SX_PAD_CSI_VSYNC__GPIO1_IO_25 0x3079>;
        };

A pin configuration MX6SX_PAD_CSI_VSYNC__GPIO1_IO_25 represents the pin function, which is GPIO in this case, and 0x3079 represents the pin settings. These macros are defined in arch/arm/boot/dts/imx6sx-pinfunc.h for IMX6 SOC.

The GPIO subsystem

Kernel GPIO subsystems provide every function to set up and handle the GPIO line from within your driver.

Steps to access the GPIO line from kernel

  1. Prior to using a GPIO from within the driver, one should claim it to the kernel. This is a way to take ownership of the GPIO, preventing other drivers from accessing the same GPIO. After taking ownership of the GPIO.
  2. Set the direction
  3. Toggle its output state if used as output
  4. Read the state, if used as input.
  5. The interrupt can be mapped for GPIO input pin with IRQ number.
Two ways to deal with GPIO in the kernel
  1. Integer-based interface, where GPIOs are represented by integer — depreciated
  2. Descriptor-based interface.

In this post, I will use descriptor-based method to access the GPIOs. The device tree for GPIO descriptor mapping is defined in 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. In the above code block, the device tree node for GPIO is shown. In <led-gpios> two GPIO pins are defined for LEDs and in <key-gpios> one GPIO is defined for push-button.

Kernel driver to access the GPIOs from kernel space

To access the GPIOs from kernel driver we can use GPIO Descriptor Consumer Interface framework. GPIOLIB library should be enabled from kernel Kconfig to access the functions.

Functions gpiod_get() or gpiod_get_index() used to allocate a GPIO descriptor structure that corresponds to the GPIO at a given index. devm_gpiod_get_index() is just an wrapper which is called as managed gpiod_get_index(). In the device tree, each GPIO property should be suffixed with either -gpio or -gpios because every descriptor-based interface function relies on that suffix, and each GPIOs are differentiated with the corresponding prefix and its index in device tree.

Once the GPIOs are allocated with there descriptor, then GPIOs can be controlled by functions gpiod_direction_output(), gpiod_direction_input(), gpiod_set_value() etc.

    red_led = devm_gpiod_get_index(dev,"led",1,GPIOD_OUT_LOW);
    if(IS_ERR(red_led)){
		pr_err("devm_gpiod_get failed\n");
		return PTR_ERR(red_led);
    }

    key = devm_gpiod_get(dev,"key",GPIOD_IN);
    if(IS_ERR(key)){
		pr_err("devm_gpiod_get failed\n");
		return PTR_ERR(key);
    }

In this post, a simple miscellaneous driver is written to access the GPIO interrupt to the userspace. The miscellaneous driver is used when you cannot classify your peripheral. This means, if you don’t want to use the major number, then you can write this misc driver, and also if you want to write a simple driver, then you can choose a misc driver instead of choosing a character driver.

To allocate an interrupt line, devm_request_irq() is used. This function allocates interrupt resources and enables the interrupt line and IRQ handling. In the device tree

  • interrupt-parent: For the device node that generates interrupt
  • interrupts: It is the interrupt specifier.

The IRQ line is the GPIO#25 that belongs to the GPIO controller node gpio1. This consists of using a GPIO as an interrupt source, so that whenever the GPIO changes to a given state, the interrupt is raised, thus triggering the event, and the interrupt handler is executed.

struct gpio_desc *key,*gren_led,*red_led;

static int ledkey_probe(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev;
    int irq,ret;
    

    pr_info("ledkey_probe called\n");

    gren_led = devm_gpiod_get_index(dev,"led",0,GPIOD_OUT_LOW);
    if(IS_ERR(gren_led)){
		pr_err("devm_gpiod_get failed\n");
		return PTR_ERR(gren_led);
    }
    
    red_led = devm_gpiod_get_index(dev,"led",1,GPIOD_OUT_LOW);
    if(IS_ERR(red_led)){
		pr_err("devm_gpiod_get failed\n");
		return PTR_ERR(red_led);
    }

    key = devm_gpiod_get(dev,"key",GPIOD_IN);
    if(IS_ERR(key)){
		pr_err("devm_gpiod_get failed\n");
		return PTR_ERR(key);
    }
    
    gpiod_direction_output(gren_led,0);
    gpiod_direction_output(red_led,0);
    gpiod_set_debounce(key,200);
    
    irq = gpiod_to_irq(key);
    if(irq < 0){
		pr_err("gpiod_to_irq error\n");
		return irq;
    }
    pr_info("irq = %d\n",irq);

    ret = devm_request_irq(dev,irq,key_isr,IRQF_TRIGGER_RISING,"Keys",dev);
    if(ret){
		pr_info("devm_request_irq failed\n");
		return ret;
    }
    
    ret = misc_register(&key_miscdevice);
    if(ret){
		pr_err("Could not register misc device\n");
		return ret;
    }
    
    init_waitqueue_head(&wait_queue_key_press);
    pr_info("key-led-dev got minor number %d\n",key_miscdevice.minor);
    return 0;
}

static const struct of_device_id ledkeyof_ids[] = {
    {.compatible = "led-key"},
    {},
};

MODULE_DEVICE_TABLE(of,ledkeyof_ids);

static struct platform_driver ledkey_driver = {
    .probe = ledkey_probe,
    .remove = ledkey_remove,
    .driver = {
	.name = "led-key",
	.of_match_table = ledkeyof_ids,
	.owner = THIS_MODULE,
    }
};
module_platform_driver(ledkey_driver);

Simple user-space application can open the device for reading. While reading the application blocks until the button press. The driver declares generic file operations to interact with user-space. File operations such as open, close, read, poll. The read method is implemented such that when ever the button press occurs it will send “Button press” string from kernel space to userspace.

Poll method is used to implement passive wait whenever the userspace program performs a select() or poll() system calls. A wait queue is declared for read operation to put tasks in sleep when there is no data to read.

poll_wait(filp,&wait_queue_key_press,wait);

Notify the wait queue when there is new data from the interrupt isr

wake_up(&wait_queue_key_press);
uint8_t key_pressed=false,ledd = 0;
DECLARE_WAIT_QUEUE_HEAD(wait_queue_key_press);

static irqreturn_t key_isr(int irq,void *data)
{
    pr_info("Button pressed\n");

    wake_up(&wait_queue_key_press);

    key_pressed = true;

    if(ledd == 0x00){
		gpiod_set_value(gren_led,1);
		gpiod_set_value(red_led,0);
		ledd = 0x01;
    }
    else if(ledd == 0x01){
		gpiod_set_value(gren_led,0);
		gpiod_set_value(red_led,1);
		ledd = 0x00;
    }
    return IRQ_HANDLED;

}

ssize_t key_read(struct file *filp,char __user *buff,size_t count,loff_t *offset)
{
    pr_info("key_read\n");

    if(copy_to_user(buff,"Key-pressed",12) > 0){
		pr_info("Copy to user fail\n");
    }

    return count;
}

static unsigned int key_poll(struct file *filp,poll_table *wait)
{
    unsigned int reval_mask = 0;

    pr_info("key_poll \n");
    poll_wait(filp,&wait_queue_key_press,wait);
    
    if(key_pressed == true){
		key_pressed = false;
		reval_mask = (POLLIN | POLLRDNORM);
    }
    return reval_mask;
}

User space program to interact with the kernel driver

User program will open the device file and sets up the poll for read method so until the device file has some data, the user process is put for sleep. When the button is pressed, the string “Button-pressed” is written into the device file and the user process wakes up and prints the string on terminal.

int main(void)
{
        int fd,ret;
        struct pollfd pfd;

        fd = open(dev,O_RDWR|O_NONBLOCK);
        if(fd < 0){
            return -1;
        }
        pfd.fd = fd;
        pfd.events = (POLLIN | POLLRDNORM);
        while(1)
        {
            ret = poll(&pfd,(unsigned long)1,-1);
            read(fd,rbuf,sizeof(rbuf));     
            printf("From kernel = %s\n",rbuf);
        }
        return 0;
}

Driver and user space application can be compiled from the makefile present in the project.

Driver install
root@jagguudoo:~# insmod led-key.ko                                                                                                           
root@jagguudoo:~# dmesg
[ 1171.022374] ledkey_probe called
[ 1171.022577] irq = 88
[ 1171.025656] key-led-dev got minor number 56
Userspace application
root@jagguudoo:~# ./appLedKey 
From kernel = Key-pressed
From kernel = Key-pressed
From kernel = Key-pressed

The kernel driver and the user space program is available in the git repository https://github.com/jaggu777/led-button/tree/master