preface
Gpio – keys. C in the Linux kernel (driver/input/the rid_device_info_keyboard/gpio – keys. C) united all about key driver is implemented. Its good code architecture can be compatible with almost all platforms on the key processing process. If you need to implement a keystroke driver on the target platform, you can use the driver directly, with little to no code to implement yourself.
The test platform
The code described in this article was tested on the following platforms:
- Host: Ubuntu14.04
- Target: are – rk3288
- Compiler:arm-linux-android-gcc
architecture
The gPIO-keys driver is based on the Input subsystem of the Linux kernel, and the device driver is registered in the system in the form of platform_device. The driver realizes the key based on the interrupt processing mode, and reports the key event to the application layer through the input subsystem, and supplies the application program to parse the use.
DTS configuration
Located in the Documentation/devicetree/bindings/gpio gpio – keys. TXT for gpio – keys are introduced the Device driver – Tree bingdings. The supported properties are defined as follows, and a summary of the basic SYNTAX of DTS can be found.
Required properties
- – compatible = “gpio-keys”;
This property defines device compatibility.
Optional properties
- – Autorepeat: Boolean, enable the Auto repeat feature of the Input subsystem.
Subnode Properties Each button(key) corresponds to a child node of gPIO-keys. The attributes of the child node include:
- – gPIOS: device-tree GPIO specification attribute.
- – label: specifies the descriptive name of the key.
- -Linux,code: Key code defined by the input subsystem. See include/ dt-Bindings /input/input.h for code definitions of keys and buttons.
Optional subnode-properties
- – Linux,input-type: defines the event type(input subsystem definition) on which the key/button depends. The default value is 1 == EV_KEY.
- – debecos-interval: defines the shake removal interval for the key/button. The default value is 5ms.
- -gpio-key,wakeup:Boolean: indicates that the key can wakeup the system, for example, power-key of the Android operating system.
Example nodes:
gpio_keys_test {
compatible = "gpio-keys";
#address-cells = <1>;
#size-cells = <0>;
autorepeat;
powerkey {
label = "power key";
linux,code = <116>;
gpios = <&gpio0 GPIO_A5 GPIO_ACTIVE_LOW>;
gpio-key,wakeup;
debounce-interval = <5>;
};
};
Copy the code
Basic data structure
*/ struct gpio_keys_button {unsigned int code; /* input event code (KEY_*, SW_*) */ int gpio; /* -1 if this key does not support gpio */ int active_low; const char *desc; unsigned int type; /* input event type (EV_KEY, EV_SW, EV_ABS) */ int wakeup; /* configure the button as a wake-up source */ int debounce_interval; /* debounce ticks interval in msecs */ bool can_disable; int value; /* axis value for EV_ABS */ unsigned int irq; /* Irq number in case of interrupt keys */ }; */ struct gpio_button_data {const struct gpio_keys_button *button; struct input_dev *input; struct timer_list timer; struct work_struct work; unsigned int timer_debounce; /* in msecs */ unsigned int irq; spinlock_t lock; bool disabled; bool key_pressed; }; /*key/button platform configuration */ struct gpio_keys_platform_data {struct gpio_keys_button *buttons; int nbuttons; unsigned int poll_interval; /* polling interval in msecs - for polling driver only */ unsigned int rep:1; /* enable input subsystem auto repeat */ int (*enable)(struct device *dev); void (*disable)(struct device *dev); const char *name; /* input device name */ }; /*key/button plaform_device data */ struct gpio_keys_drvdata {const struct gpio_keys_platform_data *pdata; struct input_dev *input; struct mutex disable_lock; struct gpio_button_data data[0]; };Copy the code
Equipment registration
The gPIO-keys driver is registered with the system as platform_driver, so it needs to define the platfrom_driver structure as follows:
Static struct platform_driver gpio_keys_device_driver = {. Probe = gpio_keys_probe,//gpio-keys driver initialization function. Remove = Driver = {. Name = "gpio-keys",.owner = THIS_MODULE,.pm = &gpio_keys_pM_ops, .of_match_table = of_match_ptr(gPIo_keys_of_match),// Define the driver compatibility attributes as follows:}}; static struct of_device_id gpio_keys_of_match[] = { { .compatible = "gpio-keys", }, { }, };Copy the code
Device Probe Process
The following mainly analyzes the main probe process of the driver. Please refer to the kernel code for more details.
static int gpio_keys_probe(struct platform_device *pdev) { ... . if (! pdata) { pdata = gpio_keys_get_devtree_pdata(dev); ------------------------------------------->(1) if (IS_ERR(pdata)) return PTR_ERR(pdata); } ddata = kzalloc(sizeof(struct gpio_keys_drvdata) + pdata->nbuttons * sizeof(struct gpio_button_data), GFP_KERNEL); input = input_allocate_device(); --------------------------------------------------------(2) if (! ddata || ! input) { dev_err(dev, "failed to allocate state\n"); error = -ENOMEM; goto fail1; } platform_set_drvdata(pdev, ddata); input_set_drvdata(input, ddata); input->name = pdata->name ? : pdev->name; input->phys = "gpio-keys/input0"; input->dev.parent = &pdev->dev; input->open = gpio_keys_open; input->close = gpio_keys_close; . . /* Enable auto repeat feature of Linux input subsystem */ if (pdata->rep) __set_bit(EV_REP, input->evbit); for (i = 0; i < pdata->nbuttons; I++) {-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - (3) the const struct gpio_keys_button * button = & pdata buttons - > [I]; struct gpio_button_data *bdata = &ddata->data[i]; error = gpio_keys_setup_key(pdev, input, bdata, button); if (error) goto fail2; if (button->wakeup) wakeup = 1; } error = sysfs_create_group(&pdev->dev.kobj, &gpio_keys_attr_group); ----------------(4) if (error) { dev_err(dev, "Unable to export keys/switches, error: %d\n", error); goto fail2; } error = input_register_device(input); -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- (5) if (error) {dev_err (dev, "Unable to register the input device, the error: %d\n", error); goto fail3; } device_init_wakeup(&pdev->dev, wakeup); return 0; . . }Copy the code
- (1) Parse DTS attribute definition of GPIo-keys, create and initialize gPIo_keys_platform_data.
- (2) Allocate and initialize the INPUT device.
- (3) Go through all keys/buttons and register the resources required by key/buton (GPIO, IRQ, etc.).
- Register the attributes of the gpio-keys access interface in the SYS file system. The path of the GPIo-keys device in the sys file system is /sys/devices/gpio_keys_test.32, where gPIo_keys_test is the device node name in the DTS.
- (5) Register the INPUT device.
Device Resource Parsing
The gPIo_keys_get_devtree_pdata function translates the device attributes of the DTS node into the gPIo_keys_platform_data structure.
gpio_keys_get_devtree_pdata(struct device *dev) { ... . nbuttons = of_get_child_count(node); -----------------------------------------------(1) if (nbuttons == 0) { error = -ENODEV; goto err_out; } pdata = kzalloc(sizeof(*pdata) + nbuttons * (sizeof *button), GFP_KERNEL); if (! pdata) { error = -ENOMEM; goto err_out; } pdata->buttons = (struct gpio_keys_button *)(pdata + 1); pdata->nbuttons = nbuttons; pdata->rep = !! of_get_property(node, "autorepeat", NULL); i = 0; for_each_child_of_node(node, pp) {------------------------------------------------(2) int gpio; enum of_gpio_flags flags; if (! of_find_property(pp, "gpios", NULL)) { pdata->nbuttons--; dev_warn(dev, "Found button without gpios\n"); continue; } gpio = of_get_gpio_flags(pp, 0, &flags); if (gpio < 0) { error = gpio; if (error ! = -EPROBE_DEFER) dev_err(dev, "Failed to get gpio flags, error: %d\n", error); goto err_free_pdata; } button = &pdata->buttons[i++]; button->gpio = gpio; button->active_low = flags & OF_GPIO_ACTIVE_LOW; if (of_property_read_u32(pp, "linux,code", &button->code)) { dev_err(dev, "Button without keycode: 0x%x\n", button->gpio); error = -EINVAL; goto err_free_pdata; } button->desc = of_get_property(pp, "label", NULL); if (of_property_read_u32(pp, "linux,input-type", &button->type)) button->type = EV_KEY; button->wakeup = !! of_get_property(pp, "gpio-key,wakeup", NULL); if (of_property_read_u32(pp, "debounce-interval", &button->debounce_interval)) button->debounce_interval = 5; } if (pdata->nbuttons == 0) { error = -EINVAL; goto err_free_pdata; } return pdata; }Copy the code
- (1) Obtain the number of keys/button nodes and initialize the autorepeat attribute of the input system.
- (2) Pass through all child nodes of DTS, read key/button gPIOS, Flags, Linux, code, Linux,input-type, gPIo-key,wakeup, debecos-interval attribute fields in sequence.
Button to register
Gpio_keys_setup_key Applies for and configures GPIO and irQ associated with GPIO. The process is as follows:
static int gpio_keys_setup_key(struct platform_device *pdev, struct input_dev *input, struct gpio_button_data *bdata, const struct gpio_keys_button *button) { ...... if (gpio_is_valid(button->gpio)) { error = gpio_request_one(button->gpio, GPIOF_IN, desc); ----------------------------->(1) if (error < 0) { dev_err(dev, "Failed to request GPIO %d, error %d\n", button->gpio, error); return error; } if (button->debounce_interval) {---------------------------------------------------->(2) error = gpio_set_debounce(button->gpio, button->debounce_interval * 1000); /* use timer if gpiolib doesn't provide debounce */ if (error < 0) bdata->timer_debounce = button->debounce_interval; } irq = gpio_to_irq(button->gpio); --------------------------------------------------->(3) if (irq < 0) { error = irq; dev_err(dev, "Unable to get irq number for GPIO %d, error %d\n", button->gpio, error); goto fail; } bdata->irq = irq; INIT_WORK(&bdata->work, gpio_keys_gpio_work_func); ------------------------------>(4) setup_timer(&bdata->timer, gpio_keys_gpio_timer, (unsigned long)bdata); isr = gpio_keys_gpio_isr; irqflags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING; } else { ... . } input_set_capability(input, button->type ? : EV_KEY, button->code); /* * If platform has specified that the button can be disabled, * we don't want it to share the interrupt line. */ if (! button->can_disable) irqflags |= IRQF_SHARED; error = request_any_context_irq(bdata->irq, isr, irqflags, desc, bdata); ------------>(5) if (error < 0) { dev_err(dev, "Unable to claim irq %d; error %d\n", bdata->irq, error); goto fail; }... . }Copy the code
- Gpio_request_one (key/button) : GPIOF_IN (key/button) : GPIOF_IN (key/button) : GPIO_REQUEST_ONE (key/button) : GPIOF_IN (key/button)
- (2) Initialize the timer required for key/button shaking. Note that gpio_set_debounce may fail. If this fails, the setup_timer of (4) will complete the key/button shaking function.
- (3) Obtain the IRQ corresponding to GPIO, which is the handle parameter of all operations related to the GPIO interruption maintained by the system.
- Gpio_keys_gpio_timer is the timer timeout processing function, this function is very simple, Its call schedule_work (& bdata – > work); To schedule the interrupted workqueue. Initialize the interrupt trigger mode is: IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, namely the edge trigger.
- (5) Apply for interruption. Request /_any/_context/_irq requests resources for interrupt processing and activates the interrupt line. Note that this function selects interrupt top level handling in Hartirq or threaded mode, depending on the interrupt configuration described.
Interrupt handling
Interrupt processing -top level
The upper part of the GPIO-keys driver is very simple. The process is as follows
static irqreturn_t gpio_keys_gpio_isr(int irq, void *dev_id) { struct gpio_button_data *bdata = dev_id; BUG_ON(irq ! = bdata->irq); if (bdata->button->wakeup)------------------------------------------------>(1) pm_stay_awake(bdata->input->dev.parent); if (bdata->timer_debounce) mod_timer(&bdata->timer, jiffies + msecs_to_jiffies(bdata->timer_debounce)); --------------->(2) else schedule_work(&bdata->work); return IRQ_HANDLED; } static void gpio_keys_gpio_timer(unsigned long _data) { struct gpio_button_data *bdata = (struct gpio_button_data *)_data; schedule_work(&bdata->work); -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- > (3)}Copy the code
- (1) If the key/button has the system wake-up function, call the power-related processing process.
- (2) The timer_debounce value of the key/button must be greater than 0, so call mod_timer to start the shake processing timer.
- (3) The timer timeout processing function gPIo_keys_gPIo_timer will be called after the timeout of the shake off timer. The implementation of this function is very simple, and it only does one thing, that is, scheduling the workqueue of key/button.
Interrupt handling -bottom level
As mentioned above, the lower part of gPIO-keys interrupt is processed as workqueue. If the shake off timer of the upper part of gPIO-keys interrupt times out, the workqueue will be triggered, and the workqueue will be executed at an appropriate time point. The following is the workqueue processing flow.
static void gpio_keys_gpio_work_func(struct work_struct *work) { struct gpio_button_data *bdata = container_of(work, struct gpio_button_data, work); gpio_keys_gpio_report_event(bdata); -------------------------------------------------->(1) if (bdata->button->wakeup) pm_relax(bdata->input->dev.parent); } static void gpio_keys_gpio_report_event(struct gpio_button_data *bdata) { const struct gpio_keys_button *button = bdata->button; struct input_dev *input = bdata->input; unsigned int type = button->type ? : EV_KEY; int state = (gpio_get_value_cansleep(button->gpio) ? 1 : 0) ^ button->active_low; --->(2) if (type == EV_ABS) { if (state) input_event(input, type, button->code, button->value); } else { input_event(input, type, button->code, !! state); --------------------------------->(3) } input_sync(input); }Copy the code
- (1) Report the GPIO status of key/button.
- (2) Read THE I/O state of GPIO, and convert it to the state of key/button according to the active_low state of key/button.
- (3) Key events of key/button are reported through the input subsystem.
Application testing
Here is an example of how to configure the GPIO-keys driver through DTS and how to monitor key/button keystroke events through the application.
Device DTS configuration
` gpio_keys_test { compatible = "gpio-keys"; #address-cells = <1>; #size-cells = <0>; autorepeat; powerkey { label = "power key"; linux,code = <116>; gpios = <&gpio0 GPIO_A5 GPIO_ACTIVE_LOW>; gpio-key,wakeup; debounce-interval = <5>; }; }; `Copy the code
The DTS above is configured with a gPIo0 GPIO_A5 as a key. Configure key events and enable the wake up function.
Gpio-keys driver is enabled
The configuration path for enabling the gpio-keys driver is as follows:
Device Driver--->
Input device support--->
Keyboards------------->
GPIO buttons
Copy the code
Save the kernel configuration, recompile the kernel, and download the DTB and zImage files to the development board.
Keystroke event application test
After the above configuration, after the system starts up, we will see the device file corresponding to the device in the /dev/input directory, which in this case is event2. To read device keystroke events, use the following application:
#include <linux/input.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <stdlib.h> #include <fcntl.h> #include <stdio.h> #define INPUT_DEV "/dev/input/event2" int main(int argc, char * const argv[]) { int fd = 0; struct input_event event; int ret = 0; fd = open(INPUT_DEV, O_RDONLY); while(1){ ret = read(fd, &event, sizeof(event)); if(ret == -1) { perror("Failed to read.\n"); exit(1); } if(event.type ! = EV_SYN) { printf("type:%d, code:%d, value:%d\n", event.type, event.code, event.value); } } return 0; }Copy the code
The output of the program is as follows:
//type:EV_KEY, code:116:power key, value:1, Key press Type :1, code:116, value:0 Type :1, code:116, value:1 Type :1, code:116, value:0 type:1, code:116, value:1 type:1, code:116, value:0Copy the code
conclusion
The GPIO-keys driver basically unified all key related driver modes in Linux system, and we can directly configure and use the driver when developing the key driver. In addition, the driver interacts with user-space applications through the Input subsystem, eliminating the need to write file system-specific interfaces (eliminating the configuration of the File_operations structure, which the Input subsystem already does). The driver can focus on the processing of key/button events, simplifying the driver processing process.