Implementation of input subsystem (GPIO keys) driven by linux

1. overview

GPIO keys is a general key driver based on input subsystem. The driver also conforms to the linux driver implementation model, that is, the separation model of driver and device. General key drivers are developed based on GPIO keys

2. GPIO keys code analysis (based on linux 4.14.40)

(1) as a whole, it is divided into the following four steps

static int gpio_keys_probe(struct platform_device *pdev)
{
 
    ...........
    ...........
    /*First, get the button from the device tree, i.e. gpio related control methods and attributes*/
	pdata = gpio_keys_get_devtree_pdata(dev);
    ...........
    ...........
    /*Second, assign an input device*/
	input = devm_input_allocate_device(dev);
    ...........
    ...........
    /*Third, register gpio related information and allocate resources*/
	for (i = 0; i < pdata->nbuttons; i++) {
		error = gpio_keys_setup_key(pdev, input, ddata,
					    button, i, child);
    }
    ...........
    ...........
    /*Fourth, register the input device to the kernel*/
	error = input_register_device(input);
    ...........
    ...........
	return 0;
}

(2) from the perspective of registering an input device, step 2 and step 4 are a common step. Step 3 and step 1 are the key steps. First, step 3

static int gpio_keys_setup_key(struct platform_device *pdev,
				struct input_dev *input,
				struct gpio_keys_drvdata *ddata,
				const struct gpio_keys_button *button,
				int idx,
				struct fwnode_handle *child)
{

    .............
    .............
    /*First, request gpio and obtain irq resources*/
	error = devm_gpio_request_one(dev, button->gpio, flags, desc);

	irq = gpiod_to_irq(bdata->gpiod);
    .............
    .............
    /*Second, register a delayed work task. The purpose of this task is to
    Sending messages through the input subsystem may result in sleep or schedule, so a
    delay work Go and finish it
    */
    INIT_DELAYED_WORK(&bdata->work, gpio_keys_gpio_work_func);
	isr = gpio_keys_gpio_isr;
	irqflags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING;}
    .............
    .............
    /*Third, registration interruption*/
	error = devm_request_any_context_irq(dev, bdata->irq, isr, irqflags,
					     desc, bdata);

	return 0;
}

(3) let's see what interrupt and delay work have done

static irqreturn_t gpio_keys_gpio_isr(int irq, void *dev_id)
{
	struct gpio_button_data *bdata = dev_id;
    ...............
    ...............
    /*In the interrupt, the only thing to do is to add the delay work to the system "WQ queue. There is also a delay
    The purpose of this delay is to remove jitter*/
	mod_delayed_work(system_wq,
			 &bdata->work,
			 msecs_to_jiffies(bdata->software_debounce));

	return IRQ_HANDLED;
}

static void gpio_keys_gpio_work_func(struct work_struct *work)
{
    /*Call the escalation message and look down*/
    ...........
	gpio_keys_gpio_report_event(bdata);
    ...........
}

static void gpio_keys_gpio_report_event(struct gpio_button_data *bdata)
{
    ............
    /*The first step is to obtain gpio status*/
	state = gpiod_get_value_cansleep(bdata->gpiod);
	............
    /*Send message to input*/
    input_event(input, type, *bdata->code, state);
	input_sync(input);
    ............
}

(4) please see the first step and the configuration of the device tree

(red is necessary)

  • label: alias of a key
  • linux,code: key value as the unique identification number of the key
  • Linux, input type: input type (EV? Key, ev? ABS (relative coordinate), ev? Rel (absolute coordinate)...) defaults to EV? Key
  • Wakeup source: related to pm, default to disable
  • Linux, can disable: whether to share interrupt line ((default) 0:shared, 1: not shared)
  • Debounce interval: debounce delay
  • gpios: information about gpio s
/*There are many applications and initializations for gpio resources*/
gpio_keys_pins_default: gpio_keys_pins_default {
      pinctrl-single,pins = <
      AM4372_IOPAD(0x9a0, PIN_INPUT | MUX_MODE9) /* (L23) mcasp0_aclkr.gpio0[18] */
      >;
};     
/*Create GPIO keys device description*/
 gpio_keys: gpio_keys {
                compatible = "gpio-keys";
                pinctrl-names = "default";
                pinctrl-0 = <&gpio_keys_pins_default>;
                #address-cells = <1>;
                #size-cells = <0>;
/*Add a button for system reset*/
                button0 {
                        label = "reset";
                        linux,code = <0x198>; /*KEY_RESTART*/
                        gpios = <&gpio0 18 GPIO_ACTIVE_LOW>;
                };
   };

3. cat /proc/bus/input/devices 

Through this, you can see which handler receives the input event

$: cat  /proc/bus/input/devices 
I: Bus=0019 Vendor=0001 Product=0001 Version=0100
N: Name="gpio_keys"
P: Phys=gpio-keys/input0
S: Sysfs=/devices/platform/gpio_keys/input/input0
U: Uniq=
H: Handlers=event0 
B: PROP=0
B: EV=3
B: KEY=1000000 0 0 0 0 0 0 0 0 0 0 0 0

4. How to receive key messages? The following routine is for reference

#include <stdio.h>
#include <string.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include <time.h>
#include <sys/time.h>
#include <sys/types.h> 
#include <unistd.h>

#define KEY_DEVICE_FILE "/dev/input/event0"

struct input_event {
	struct timeval time;
	unsigned short type;
	unsigned short code;
	int value;
};

int main()
{
	int fd_key = -1;
	struct input_event key_evt;
	int ret = 0;

	fd_key= open(KEY_DEVICE_FILE, O_RDONLY);

	if(fd_key< 0) {
           return fd_key;
	}

monitor_key:

	ret = read(fd_key, (char*)&key_evt, sizeof(struct input_event));
	if(ret < 0)
	{
		printf("read key event failed:%d\n", ret);
	}else{
		if(key_evt.code == 408 && key_evt.value == 1)
		{
			printf("The press of reset button is detected\n");
			return 0;
		}
#if 0
		printf("%lld seconds %ld microseconds", key_evt.time.tv_sec,
							key_evt.time.tv_usec);
		printf("type:%d code:%d value:%d\n", key_evt.type, key_evt.code, key_evt.value);	
#endif
	}
	goto monitor_key;
	return 0;

}

 

Keywords: Linux

Added by AndyBG on Sat, 30 Nov 2019 16:45:03 +0200