Skip to content
Advertisement

Linux input driver not working properly

I have written a small linux input driver which reads the state of a gpio. The driver registers OK and also the interrupt gets fired, however the events are not always displayed. Driver runs on Beagleboneblack with Android and kernel version 3.8.13 To test it I do either:

cat /dev/input/event2

Or run an user space app that I wrote. The app is able to retrieve the events properly from other input devices. To mention that “event2” is the right event in /dev/input, checked already.

The driver code:

#include <linux/module.h>                                                                                                                                                                                
#include <linux/interrupt.h>
#include <linux/platform_device.h>
...

struct button_drv {

·   int··   ·   ·   ·   btn_irq;
·   int ·   ·   ·   ·   btn_gpio;
·   struct device·  ·   *dev;
·   struct input_dev·   *btn_input;
};

static irqreturn_t btn_irq(int irq_nb, void *data) {

·   struct button_drv *btn_drv = (struct button_drv*)data;
·   struct input_dev *btn_input = btn_drv->btn_input;
·   int btn_gpio = btn_drv->btn_gpio;

·   dev_info(btn_drv->dev,"Inside button IRQ!n");
·   input_report_key(btn_input,BTN_0,gpio_get_value(btn_gpio));
·   input_sync(btn_input);

·   return IRQ_HANDLED;
}

static int btn_probe(struct platform_device *pdev) {

·   int ret,btn_irq,btn_gpio;
·   struct button_drv *btn_drv;
·   struct input_dev *btn_input;
·   struct device_node *node = pdev->dev.of_node;
·   
·   dev_info(&pdev->dev,"Inside btn probe driver!n");
·   btn_drv = devm_kzalloc(&pdev->dev,sizeof(struct button_drv),GFP_KERNEL);
·   if(!btn_drv)
·   {
·   ·   dev_err(&pdev->dev,"Failed to allocate memory for btn device!n");
·   ·   return -ENOMEM;
·   }

·   platform_set_drvdata(pdev,btn_drv);
·   btn_gpio = of_get_named_gpio(node,"btn-gpios",0);
·   
·   ret = devm_gpio_request_one(&pdev->dev,btn_gpio,GPIOF_DIR_IN,"btn");
·   if(IS_ERR_VALUE(ret))
    {
  ·   dev_err(&pdev->dev,"Failed to allocate btn gpio!n");
        ret = -ENODEV;
·   ·   goto err_mem;
   }
·   
·   btn_irq = gpio_to_irq(btn_gpio);
·   if(IS_ERR_VALUE(btn_irq))
·   {
   ·   dev_err(&pdev->dev,"Failed to get corresponding btn IRQ!n");
   ·   ret = -ENODEV;
   ·   goto err_out;
·   }

·   ret = devm_request_irq(&pdev->dev,btn_irq,btn_irq,
·   ·   ·   IRQF_TRIGGER_FALLING | IRQF_ONESHOT,pdev->name, btn_drv);
·   if(IS_ERR_VALUE(ret))
·   {
·   ·   dev_err(&pdev->dev,"Failed to map btn IRQ!n");
·   ·   ret = -EINVAL;
·   ·   goto err_out;
·   }

·   btn_input = input_allocate_device();
·   if(IS_ERR(btn_input))
·   {
·   ·   dev_err(&pdev->dev,"No memory for btn input device!");
·   ·   ret = -ENODEV;                                                                                                                                                                                   
·   ·   goto err_out;
·   }
·   btn_input->evbit[0] = BIT_MASK(EV_KEY);
·   btn_input->keybit[BIT_WORD(BTN_0)] = BIT_MASK(BTN_0);
·   btn_input->name = "button1";
    btn_input->phys = "button1/input0";
·   ret = input_register_device(btn_input);
·   if(IS_ERR_VALUE(ret))
·   {
·   ·   dev_err(&pdev->dev,"Failed to register btn input device!n");
·   ·   ret = -ENODEV;
·   ·   goto err_out;
·   }

·   btn_drv->btn_gpio · = btn_gpio;
·   btn_drv->btn_irq ·  = btn_irq;
·   btn_drv->btn_input ·= btn_input;
·   btn_drv->dev·   ·   = &pdev->dev;

·   dev_info(&pdev->dev,"Registered btn input device!");
·   
·   return 0;

err_out:
·   devm_gpio_free(&pdev->dev,btn_gpio);

err_mem:
·   platform_set_drvdata(pdev,NULL);
·   devm_kfree(&pdev->dev, btn_drv);
·   return ret;
}

static int btn_remove(struct platform_device *pdev) {

·.......

·   return 0;
}                                                      


static struct platform_driver btn_drv = {
·   .probe· ·   = btn_probe,
·   .remove··   = btn_remove,
...
};


static int __init btn_init(void) {

·   return platform_driver_register(&btn_drv);
}
module_init(btn_init);

static void __exit btn_exit(void) {

·   platform_driver_unregister(&btn_drv);
}
module_exit(btn_exit);


MODULE_DESCRIPTION("Button driver");
MODULE_AUTHOR("Test");
MODULE_LICENSE("GPL");        

I stripped out some lines, to make it smaller. It matches against an entry in DTS where I defined the GPIO number. The OF_ related stuff is removed here.

The app code:

#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <linux/input.h>
#include <string.h>
#include <stdio.h>
#include <sys/select.h>

int main(int argc, char * argv[]) {

·   char* device;
·   int dev_fd,ret;
·   fd_set readfds;

·   if(argc < 2)
·   {
·   ·   printf("No input device specified!n");
·   ·   exit(1);                                                                                                                                                                                         
·   }

·   device = argv[1];
·   dev_fd = open(device,O_RDONLY);
·   if(dev_fd<0)
·   {
·   ·   printf("Failed to oped device: %s with error: %sn",device,strerror(errno));
·   ·   exit(2);
·   }
·   
·   do {
·   ·   FD_ZERO(&readfds);
·   ·   FD_SET(dev_fd,&readfds);

·   ·   ret = select(dev_fd+1,&readfds,NULL,NULL,NULL);
·   ·   if(ret < 0)
·   ·   {
·   ·   ·   printf("Select returned error: %sn",strerror(errno));
·   ·   ·   exit(3);
·   ·   }
   ·    
        if(FD_ISSET(dev_fd,&readfds))
·   ·   {
·   ·   ·   int nb;
·   ·   ·   struct input_event ev;

·   ·   ·   nb = read(dev_fd,&ev,sizeof(ev));
·   ·   ·   if(nb < 0)
·   ·   ·   {
·   ·   ·   ·   printf("Failed to read from input device, err: %sn",strerror(errno));
·   ·   ·   ·   exit(4);
·   ·   ·   }

·   ·   ·   printf("Read for sizeof-ev:%lu returned nb:%d, Type:%d, Code:%d, Value:%dn",
·   ·   ·   ·   ·   sizeof(ev), nb, ev.type, ev.code, ev.value);
·   ·   }
·   }while(1);

·   close(dev_fd);
    exit(0);
}

When doing cat /dev/input/event2 sometimes I get some characters, most of the times I don’t. This matches with the output of my app when reading the input device:

shell@beagleboneblack:/data/user/bbone/01.BtnInput # ./input_read /dev/input/event2
[  158.419312] btn-drv btn.9: Inside  button IRQ!
[  158.425230] btn-drv btn.9: Inside  button IRQ!
[  159.125057] btn-drv btn.9: Inside  button IRQ!
[  159.130966] btn-drv btn.9: Inside  button IRQ!
Read for sizeof-ev:16 returned nb:16, Type:1, Code:256, Value:1
Read for sizeof-ev:16 returned nb:16, Type:0, Code:0, Value:0
Read for sizeof-ev:16 returned nb:16, Type:1, Code:256, Value:0
Read for sizeof-ev:16 returned nb:16, Type:0, Code:0, Value:0
[  161.441807] btn-drv btn.9: Inside  button IRQ!
[  161.447731] btn-drv btn.9: Inside  button IRQ!
[  161.453645] btn-drv btn.9: Inside  button IRQ!
[  161.571151] btn-drv btn.9: Inside  button IRQ!
[  161.593890] btn-drv btn.9: Inside  button IRQ!
......
[  164.519528] btn-drv btn.9: Inside  button IRQ!
[  164.525427] btn-drv btn.9: Inside  button IRQ!
[  165.023885] btn-drv btn.9: Inside  button IRQ!
[  165.029780] btn-drv btn.9: Inside  button IRQ!
[  165.416927] btn-drv btn.9: Inside  button IRQ!
[  165.444170] btn-drv btn.9: Inside  button IRQ!
[  165.450079] btn-drv btn.9: Inside  button IRQ!
Read for sizeof-ev:16 returned nb:16, Type:1, Code:256, Value:1
Read for sizeof-ev:16 returned nb:16, Type:0, Code:0, Value:0
[  166.075658] btn-drv btn.9: Inside  button IRQ!
[  166.081639] btn-drv btn.9: Inside  button IRQ!
Read for sizeof-ev:16 returned nb:16, Type:1, Code:256, Value:0
Read for sizeof-ev:16 returned nb:16, Type:0, Code:0, Value:0
[  166.272010] btn-drv btn.9: Inside  button IRQ!
[  166.277938] btn-drv btn.9: Inside  button IRQ!
[  166.547894] btn-drv btn.9: Inside  button IRQ!

I placed a dev_info in my irq routine to see whenever the input_report_key is called. Sometimes I get more events sometimes more IRQs, and sometimes I get only IRQs messages.

So I’m not missing any IRQ, seems to be something with the key report event. I’m thinking maybe the IRQs are overlapping if they are fired close to each other when button is pressed and this can affect input flow..?

Will try to change the code to force reporting the key once each 10ms, just to see if it makes any difference. I have no other idea. Any help much appreciated.

Update: so, i changed the code to report 1 time each 10ms and 1 time per second, no difference, must be something else.

Any idea?

Thank you, Daniel

Advertisement

Answer

Seems I found the issue. When setting up the interrupt, it should be triggered on both edges, as the linux input system reports only the change of event value, therefore when the same edge triggers the IRQ, most of the time the event will be the same, which in my case was “low”. That means switch released, but with actual code the switch was never seen as pressed (unless the level on that gpio was not yet stable and could read high instead of low). Seems those were the only cases when I was getting an event reported.

With following changes the driver works as expected:

In btn_probe replace:

ret = devm_request_irq(&pdev->dev,btn_irq,btn_irq,
        IRQF_TRIGGER_FALLING | IRQF_ONESHOT,pdev->name, btn_drv);

with

ret = devm_request_irq(&pdev->dev,btn_irq,btn_irq,
        IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING | IRQF_ONESHOT,pdev->name, btn_drv);

And the IRQ rewritten to avoid multiple IRQs within short period of time:

static irqreturn_t btn_irq(int irq_nb, void *data) {

·   struct button_drv *btn_drv = (struct button_drv*)data;
    struct input_dev *btn_input = btn_drv->btn_input;
·   int btn_gpio = btn_drv.btn_gpio;
·   int val;

·   if(time_before(jiffies,btn_drv.new_time))
·   {
·   ·   btn_drv.scan_disc++;
·   ·   return IRQ_HANDLED;
·   }

·   if(!gpio_get_value(btn_gpio))
·   {
·   ·   pr_info("btn-drv: switch pressed!n");
·   ·   val = 1;
·   }
·   else
·   {
·   ·   pr_info("btn-drv: switch released!n");
·   ·   val = 0;
·   }

·   input_report_key(btn_input,BTN_0,val);
·   input_sync(btn_input);

·   btn_drv.new_time = jiffies + SCAN_DELTA;

·   return IRQ_HANDLED;
}

With:

#define SCAN_DELTA∙ msecs_to_jiffies(100)∙ //msecs

And new_time and scan_disc new entries in btn_drv struct.

User contributions licensed under: CC BY-SA
8 People found this is helpful
Advertisement