Character device driver examples (LED, buttons, input input subsystem)

Table of contents

Objectives of this chapter

1. LED driver

2. Simple button driver based on interrupt

3. Button driver based on input subsystem


Objectives of this chapter


        This chapter combines the previous knowledge to implement common peripheral drivers for embedded systems, including LED, buttons, ADC, PWM and RTC. This chapter discusses the implementation of some drivers from an engineering perspective and a practical perspective. For example, if the LED device tree node is written, the device can be driven normally. But the button driver discusses interrupt-based and input subsystem-based ones respectively, and also specifically discusses the debouncing processing of the button. Not only that, this chapter also introduces some new knowledge, such as the kernel unified GPIO interface, clock subsystem, pinctrl subsystem, etc. Although this chapter is called "Character Device Driver Example", some devices are not implemented as character devices, but are operated through the sysfs file system interface.
 

1. LED driver


        An often-heard saying, "There is no move to win without a move" is used to describe martial arts practitioners who have reached the highest level of martial arts practice. Similar words include "Silence is better than sound" and "Great sound and sound. "Elephant's shape" and so on. In fact, the same is true for drivers. If you want to implement a device driver without having to go to great lengths or even write a line of driver code, does that mean that the level of driver developers has reached the highest level? It sounds like a fantasy, but it is not impossible, because kernel developers around the world are very enthusiastic. As long as they can write drivers, they have basically written them. If we can stand on the shoulders of these giants, then our work will be easier. The LED driver to be discussed next uses the driver that has been written by the kernel developer to achieve the functions we want. Before you write a driver, you should first check whether the kernel has implemented the driver. If so, then this will greatly improve our work efficiency. After all, getting a salary without typing a line of code is the ultimate goal pursued by every programmer, but this must not be told to the boss.
        Our LED is based on GPIO. For this reason, the kernel has two corresponding drivers, namely GPIO driver and LED driver. The GPIO-based LED driver calls the function exported by the GPIO driver. We don't care about the GPIO driver in this section (details will be explained later). Regarding the LED driver, the kernel document Documentation/leds/leds-class.txt has a simple description. It implements a leds class and controls LEDs through the sysfs interface, so it does not use the character device driver framework. Strictly speaking, The content of this section is inconsistent with the title of this chapter.

        For the driver implementation code, please see drivers/leds/leds-gpio.c

/*
 * LEDs driver for GPIOs
 *
 * Copyright (C) 2007 8D Technologies inc.
 * Raphael Assenat <[email protected]>
 * Copyright (C) 2008 Freescale Semiconductor, Inc.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 */
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/gpio.h>
#include <linux/leds.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/of_gpio.h>
#include <linux/slab.h>
#include <linux/workqueue.h>
#include <linux/module.h>
#include <linux/err.h>

struct gpio_led_data {
	struct led_classdev cdev;
	unsigned gpio;
	struct work_struct work;
	u8 new_level;
	u8 can_sleep;
	u8 active_low;
	u8 blinking;
	int (*platform_gpio_blink_set)(unsigned gpio, int state,
			unsigned long *delay_on, unsigned long *delay_off);
};

static void gpio_led_work(struct work_struct *work)
{
	struct gpio_led_data	*led_dat =
		container_of(work, struct gpio_led_data, work);

	if (led_dat->blinking) {
		led_dat->platform_gpio_blink_set(led_dat->gpio,
						 led_dat->new_level,
						 NULL, NULL);
		led_dat->blinking = 0;
	} else
		gpio_set_value_cansleep(led_dat->gpio, led_dat->new_level);
}

static void gpio_led_set(struct led_classdev *led_cdev,
	enum led_brightness value)
{
	struct gpio_led_data *led_dat =
		container_of(led_cdev, struct gpio_led_data, cdev);
	int level;

	if (value == LED_OFF)
		level = 0;
	else
		level = 1;

	if (led_dat->active_low)
		level = !level;

	/* Setting GPIOs with I2C/etc requires a task context, and we don't
	 * seem to have a reliable way to know if we're already in one; so
	 * let's just assume the worst.
	 */
	if (led_dat->can_sleep) {
		led_dat->new_level = level;
		schedule_work(&led_dat->work);
	} else {
		if (led_dat->blinking) {
			led_dat->platform_gpio_blink_set(led_dat->gpio, level,
							 NULL, NULL);
			led_dat->blinking = 0;
		} else
			gpio_set_value(led_dat->gpio, level);
	}
}

static int gpio_blink_set(struct led_classdev *led_cdev,
	unsigned long *delay_on, unsigned long *delay_off)
{
	struct gpio_led_data *led_dat =
		container_of(led_cdev, struct gpio_led_data, cdev);

	led_dat->blinking = 1;
	return led_dat->platform_gpio_blink_set(led_dat->gpio, GPIO_LED_BLINK,
						delay_on, delay_off);
}

static int create_gpio_led(const struct gpio_led *template,
	struct gpio_led_data *led_dat, struct device *parent,
	int (*blink_set)(unsigned, int, unsigned long *, unsigned long *))
{
	int ret, state;

	led_dat->gpio = -1;

	/* skip leds that aren't available */
	if (!gpio_is_valid(template->gpio)) {
		dev_info(parent, "Skipping unavailable LED gpio %d (%s)\n",
				template->gpio, template->name);
		return 0;
	}

	ret = devm_gpio_request(parent, template->gpio, template->name);
	if (ret < 0)
		return ret;

	led_dat->cdev.name = template->name;
	led_dat->cdev.default_trigger = template->default_trigger;
	led_dat->gpio = template->gpio;
	led_dat->can_sleep = gpio_cansleep(template->gpio);
	led_dat->active_low = template->active_low;
	led_dat->blinking = 0;
	if (blink_set) {
		led_dat->platform_gpio_blink_set = blink_set;
		led_dat->cdev.blink_set = gpio_blink_set;
	}
	led_dat->cdev.brightness_set = gpio_led_set;
	if (template->default_state == LEDS_GPIO_DEFSTATE_KEEP)
		state = !!gpio_get_value_cansleep(led_dat->gpio) ^ led_dat->active_low;
	else
		state = (template->default_state == LEDS_GPIO_DEFSTATE_ON);
	led_dat->cdev.brightness = state ? LED_FULL : LED_OFF;
	if (!template->retain_state_suspended)
		led_dat->cdev.flags |= LED_CORE_SUSPENDRESUME;

	ret = gpio_direction_output(led_dat->gpio, led_dat->active_low ^ state);
	if (ret < 0)
		return ret;

	INIT_WORK(&led_dat->work, gpio_led_work);

	ret = led_classdev_register(parent, &led_dat->cdev);
	if (ret < 0)
		return ret;

	return 0;
}

static void delete_gpio_led(struct gpio_led_data *led)
{
	if (!gpio_is_valid(led->gpio))
		return;
	led_classdev_unregister(&led->cdev);
	cancel_work_sync(&led->work);
}

struct gpio_leds_priv {
	int num_leds;
	struct gpio_led_data leds[];
};

static inline int sizeof_gpio_leds_priv(int num_leds)
{
	return sizeof(struct gpio_leds_priv) +
		(sizeof(struct gpio_led_data) * num_leds);
}

/* Code to create from OpenFirmware platform devices */
#ifdef CONFIG_OF_GPIO
static struct gpio_leds_priv *gpio_leds_create_of(struct platform_device *pdev)
{
	struct device_node *np = pdev->dev.of_node, *child;
	struct gpio_leds_priv *priv;
	int count, ret;

	/* count LEDs in this device, so we know how much to allocate */
	count = of_get_available_child_count(np);
	if (!count)
		return ERR_PTR(-ENODEV);

	for_each_available_child_of_node(np, child)
		if (of_get_gpio(child, 0) == -EPROBE_DEFER)
			return ERR_PTR(-EPROBE_DEFER);

	priv = devm_kzalloc(&pdev->dev, sizeof_gpio_leds_priv(count),
			GFP_KERNEL);
	if (!priv)
		return ERR_PTR(-ENOMEM);

	for_each_available_child_of_node(np, child) {
		struct gpio_led led = {};
		enum of_gpio_flags flags;
		const char *state;

		led.gpio = of_get_gpio_flags(child, 0, &flags);
		led.active_low = flags & OF_GPIO_ACTIVE_LOW;
		led.name = of_get_property(child, "label", NULL) ? : child->name;
		led.default_trigger =
			of_get_property(child, "linux,default-trigger", NULL);
		state = of_get_property(child, "default-state", NULL);
		if (state) {
			if (!strcmp(state, "keep"))
				led.default_state = LEDS_GPIO_DEFSTATE_KEEP;
			else if (!strcmp(state, "on"))
				led.default_state = LEDS_GPIO_DEFSTATE_ON;
			else
				led.default_state = LEDS_GPIO_DEFSTATE_OFF;
		}

		ret = create_gpio_led(&led, &priv->leds[priv->num_leds++],
				      &pdev->dev, NULL);
		if (ret < 0) {
			of_node_put(child);
			goto err;
		}
	}

	return priv;

err:
	for (count = priv->num_leds - 2; count >= 0; count--)
		delete_gpio_led(&priv->leds[count]);
	return ERR_PTR(-ENODEV);
}

static const struct of_device_id of_gpio_leds_match[] = {
	{ .compatible = "gpio-leds", },
	{},
};
#else /* CONFIG_OF_GPIO */
static struct gpio_leds_priv *gpio_leds_create_of(struct platform_device *pdev)
{
	return ERR_PTR(-ENODEV);
}
#endif /* CONFIG_OF_GPIO */


static int gpio_led_probe(struct platform_device *pdev)
{
	struct gpio_led_platform_data *pdata = dev_get_platdata(&pdev->dev);
	struct gpio_leds_priv *priv;
	int i, ret = 0;


	if (pdata && pdata->num_leds) {
		priv = devm_kzalloc(&pdev->dev,
				sizeof_gpio_leds_priv(pdata->num_leds),
					GFP_KERNEL);
		if (!priv)
			return -ENOMEM;

		priv->num_leds = pdata->num_leds;
		for (i = 0; i < priv->num_leds; i++) {
			ret = create_gpio_led(&pdata->leds[i],
					      &priv->leds[i],
					      &pdev->dev, pdata->gpio_blink_set);
			if (ret < 0) {
				/* On failure: unwind the led creations */
				for (i = i - 1; i >= 0; i--)
					delete_gpio_led(&priv->leds[i]);
				return ret;
			}
		}
	} else {
		priv = gpio_leds_create_of(pdev);
		if (IS_ERR(priv))
			return PTR_ERR(priv);
	}

	platform_set_drvdata(pdev, priv);

	return 0;
}

static int gpio_led_remove(struct platform_device *pdev)
{
	struct gpio_leds_priv *priv = platform_get_drvdata(pdev);
	int i;

	for (i = 0; i < priv->num_leds; i++)
		delete_gpio_led(&priv->leds[i]);

	return 0;
}

static struct platform_driver gpio_led_driver = {
	.probe		= gpio_led_probe,
	.remove		= gpio_led_remove,
	.driver		= {
		.name	= "leds-gpio",
		.owner	= THIS_MODULE,
		.of_match_table = of_match_ptr(of_gpio_leds_match),
	},
};

module_platform_driver(gpio_led_driver);

MODULE_AUTHOR("Raphael Assenat <[email protected]>, Trent Piepho <[email protected]>");
MODULE_DESCRIPTION("GPIO LED driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:leds-gpio");


        Now that the driver has been implemented, how do we make it work? First, we must configure the kernel and ensure that the driver is selected. Run the make ARCH=arm menuconfig command under the kernel source code and select the options below.


After selecting the driver, save the configuration, use the following command to recompile the kernel, and then copy it to the directory specified by the TFTP server.

make ARCH=arm uImage


        After the driver is configured, device nodes should be added to the device tree. For how to write device nodes, please refer to the kernel documentation Documentation/devicetree/bindings/leds/leds-gpio.txt (Before writing device nodes, search in the kernel documentation. Corresponding instructions, this is a good habit). Modify arch/arm/boot/dts/exynos4412-fs4412.dts, delete the previously added LED device node, and add the following device node.

	leds{
		compatible = "gpio-leds";
		
		led2 {
			label = "led2";
			gpios = <&gpx2 7 0>;
			default-state = "off";
		};
		
		led3 {
			label = "led3";
			gpios = <&gpx1 0 0>;
			default-state = "off";
		};

		led4 {
			label = "led4";
			gpios = <&gpf3 4 0>;
			default-state = "off";
		};

		led5 {
			label = "led5";
		    gpios = <&gpf3 5 0>;
			default-state = "off";
		};
	};

        The compatible attribute is gpio-leds, which can match the LED driver. The label in each led node is the name of the subdirectory that appears in the sys directory. gpios specifies the GPIO port to which the LED is connected. The third value is 0, which means that the high level lights up the LED light, and 1 means that the low level lights up the LED light. The value of the default-state attribute is off, which means the LED light is off by default, and if it is on, it is on by default. After modifying the device tree source file, use the following command to compile the device tree, and then copy it to the specified directory.

Restart the development board and use the following command to see the corresponding device directory.

led2, led3, led4, and led5 respectively correspond to 4 LED lights. There is a brightness file in each directory. The current brightness of the LED light can be obtained by reading this file, and the brightness of the LED light can be modified by writing this file. Because these LED lights are connected to the GPIO port, the brightness is only 0 and 1, 0 means off, 1 means on, the command is as follows.

Of course, you can also write an application to control the LED lights on and off. The application layer test code is as follows

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>

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

#define LED_DEV_PATH	"/sys/class/leds/led%d/brightness"
#define ON		1
#define OFF		0

int fs4412_set_led(unsigned int lednum, unsigned int mode)
{
	int fd;
	int ret;
	char devpath[128];
	char *on = "1\n";
	char *off = "0\n";
	char *m = NULL;

	snprintf(devpath, sizeof(devpath), LED_DEV_PATH, lednum);
	fd = open(devpath, O_WRONLY);
	if (fd == -1) {
		perror("fsled->open");
		return -1;
	}

	if (mode == ON)
		m = on;
	else
		m = off;

	ret = write(fd, m, strlen(m));
	if (ret == -1) {
		perror("fsled->write");
		close(fd);
		return -1;
	}

	close(fd);
	return 0;
}

int main(int argc, char *argv[])
{
	unsigned int lednum = 2;

	while (1) {
		fs4412_set_led(lednum, ON);
		usleep(500000);
		fs4412_set_led(lednum, OFF);
		usleep(500000);

		lednum++;
		if (lednum > 5)
			lednum = 2;
	}
}

 The effect is like a running water lamp. Try the resource binding function. If it succeeds, you can download it and take a look.


        The code is relatively simple and will not be explained here. It should be noted that when operating files in the sys directory, pay attention to the file location pointer. The simple method is to reopen it each time and then close it after use. The compilation and testing commands are as follows. The four LED lights will flash in sequence like the previous example. I used my convenient little script, which has been polished by more than 30 driver experiments and is now very useful. When I have time later, I will use scripts to manage kernel compilation and device tree compilation, and make a small SDK. (Quietly: Let’s see if we can sell it to Huaqing, hahahaha)


2. Simple button driver based on interrupt


There are three buttons on the FS4412, and their related schematics are shown in the figure.


        K2 and K3 can be used for general key input, and K4 is used for power management. Further combined with the core board schematic diagram, it can be seen that K2 and K3 are connected to the GPXL. and GPX1.2 pins respectively, and these two pins can also be used as external interrupt input pins, and the interrupt numbers are EINT9 and EINT10 respectively. K2 and K3 are normally open key switches, so these two pins are usually at high level. When the button is pressed, the pins are directly connected to ground and are at low level. That is to say, a falling edge will be generated when the button is pressed, and a rising edge will be generated when the button is released. We can set the pin to a falling edge trigger, so that it can respond to the user's key input in a timely manner, or it can be set to a double edge trigger, so that an interrupt is generated when the key is pressed and lifted.

        Next, we first add nodes to the device tree. In order to describe how these two pins work in interrupt mode, we need to refer to the kernel document Documentation/devicetree/bindings/interrupt-controller/interruptstxt. This document illustrates the interrupt attribute as an example. How to set it up.

interrupt-parent = <&intcl>;
interrupts=<5 0>,<6 0>;


        interrupt-parent specifies the interrupt controller node connected to the interrupt line. If the value of interrupts has two cells, then the first cell is the index of the interrupt line in the interrupt controller, and the second cell cell refers to the interrupt triggering method, 0 means not specified.
        In order to give the node of the key device, we need to view the contents of its interrupt controller node. Because they are connected to the GPX1 group of pins, according to the gpx1 keyword we can see its definition in the arch/arm/boot/dts/exynos4x12-pinctrl.dtsi file.

This also explains why we did not specify the physical address when setting up the LED device tree earlier. It only shows that the GPIO name can still find and control the LED because it has been defined in other files.

        It can be seen that gpx1 is indeed an interrupt controller and uses two cells. It has a total of 8 interrupt lines, which just correspond to the pins GPX1.0 to GPX1.7. The pins we use are GPX1.1 and GPX1.2, so the indexes are naturally 1 and 2. This can also be determined by the parent node gic of gpxl. The meaning of the three cells of interrupts is described in the Documentation/devicetreebindings/arm/gic.txt kernel document. The first cell is 0, which represents the SPI interrupt; the second Cell is the SPI interrupt number: the third cell is the interrupt triggering method, and 0 means it is not specified. Looking at the interrupt controller section of the Exynos4412 chip manual, as shown in the figure, we can see that the SPI interrupt numbers corresponding to the EINT9 and EINT10 interrupts are exactly 25 and 26.

With this information, we can give the device node of the button, the content is as follows,
 

keys {
    compatible="fs4412,fskey";
    interrupt-parent = <&gpx1>;
    interrupts=<1 2>,<2 2>;
};

        The second cell of the interrupts attribute is 2, indicating a falling edge. Add the above code to the device tree file arch/arm/boot/dts/exynos4412-fs4412.dts, recompile the device tree, and then copy it to the directory specified by the TFTP server.



        Combining the previous knowledge of interrupt programming and Linux device model, we can easily write this simple button driver code as follows

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>

#include <linux/fs.h>
#include <linux/cdev.h>

#include <linux/slab.h>
#include <linux/ioctl.h>
#include <linux/uaccess.h>

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

struct resource *key2_res;
struct resource *key3_res;

static irqreturn_t fskey_handler(int irq, void *dev_id)
{
	if (irq == key2_res->start)
		printk("K2 pressed\n");
	else
		printk("K3 pressed\n");

	return IRQ_HANDLED;
}

static int fskey_probe(struct platform_device *pdev)
{
	int ret;

	key2_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
	key3_res = platform_get_resource(pdev, IORESOURCE_IRQ, 1);

	if (!key2_res || !key3_res) {
		ret = -ENOENT;
		goto res_err;
	}

	ret = request_irq(key2_res->start, fskey_handler, key2_res->flags & IRQF_TRIGGER_MASK, "key2", NULL);
	if (ret)
		goto key2_err;
	ret = request_irq(key3_res->start, fskey_handler, key3_res->flags & IRQF_TRIGGER_MASK, "key3", NULL);
	if (ret)
		goto key3_err;

	return 0;

key3_err:
	free_irq(key2_res->start, NULL);
key2_err:
res_err:
	return ret;
}

static int fskey_remove(struct platform_device *pdev)
{
	free_irq(key3_res->start, NULL);
	free_irq(key2_res->start, NULL);

	return 0;
}

static const struct of_device_id fskey_of_matches[] = {
	{ .compatible = "fs4412,fskey", },
	{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, fskey_of_matches);

struct platform_driver fskey_drv = { 
	.driver = { 
		.name    = "fskey",
		.owner   = THIS_MODULE,
		.of_match_table = of_match_ptr(fskey_of_matches),
	},  
	.probe   = fskey_probe,
	.remove  = fskey_remove,
};

module_platform_driver(fskey_drv);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("name <e-mail>");
MODULE_DESCRIPTION("A simple device driver for Keys on FS4412 board");

        Lines 33 and 34 of the code use platform_get_resouree to obtain these two interrupt resources respectively. Lines 41 and 44 of the code register these two interrupts respectively. The starting interrupt number is the one in the resource. start member, the handler functions of both interrupts are fskey_handler. In the skey_handler function, determine which 9 keys were pressed based on the interrupt number irq. This driver is not a character device driver, it is just a simple key interrupt test module. The corresponding compilation and testing commands are as follows.

        After the driver is loaded, pressing the button will print on the console. We will find that the key may be printed several times after being pressed only once. The reason for this result is the familiar key jitter. Due to some mechanical characteristics, the waveform generated when pressing and releasing the button is not perfect. The actual waveform is roughly as shown in the figure

 

        In the above waveform, we see that the falling edge occurs several times, which causes the interrupt to be triggered several times. In order to solve this problem, we must debounce it. The simple way is to use a timer to calculate the time interval between two interrupts. If it is too small, ignore the subsequent interrupts. However, there is still a problem using the interrupt method. Just imagine, what happens when you press and hold a key on the computer keyboard? Under normal circumstances, we should be able to report three events: press, lift, and long press of the key. It is relatively friendly. For this reason, we use the method in the next section to implement a more practical key device driver.


3. Button driver based on input subsystem


        If we want our keys to work as cool as a keyboard, then we have to take advantage of the input subsystem in the kernel. Simply put, the input subsystem is a subsystem that provides a unified interface to the upper layer for all input devices. Common input devices include keyboards, mice, handwriting input pads, joysticks, and touch pads. The input subsystem has defined corresponding standards for it, such as stipulating the representation of input events, key values ​​and relative coordinates of the mouse. The approximate hierarchy of the input subsystem is shown in the figure.


        The driver is used to obtain input data from the input hardware in real time. The event processing layer is used to process the data reported by the driver and provide standard event information to the upper layer. The input core layer is used to manage the two layers and establish a communication bridge. This input subsystem is still relatively complex, but if we are just writing an input device driver, we only need to know an important data structure struct input_dev and several commonly used APIs.

struct input_dev {  
    /* 设备名称 */  
    const char *name;  
  
    /* 设备驱动程序标识符 */  
    const char *phys;  
  
    /* 设备总线类型 */  
    const char *id;  
    struct input_id id_table[MAX_ID];  
  
    /* 设备支持的事件类型 */  
    unsigned long evbit[NBITS(EV_MAX)];  
  
    /* 事件处理回调函数 */  
    void (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value);  
  
    /* 设备索引 */  
    int devindex;  
  
    /* 设备支持的按键映射 */  
    unsigned long keybit[NBITS(KEY_MAX)];  
    /* ... */  
};


        An input device is represented by a structinput dev structure object. The meanings of the main members are as follows

        name: Enter the name of the device.
        Phys: The physical path of the device in the system hierarchy.
        id: Enter the id of the device, including bus type, manufacturer ID, product ID, version number, etc. evbit: the event type that the device can report, such as EV KEY means that the device can report key events EV REL Indicates that the device can report relative coordinate events.
        keybit: the key value that the device can report
        keycodemax: the size of the key coding table
        keycodesize: each code in the key coding table The size of
        keycode: key code table.
        Several important APIs of input device drivers are as follows

struct input_dev *input_allocate_device(void);
void input_free_device(struct input_dev *dev);
int input_register_device(struct input_dev *dev);
void input_unregister_device(struct input_dev *dev);
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value);
void input_report_key(struct input_dev *dev, unsigned int code, int value);
void input_sync(struct input_dev *dev);


        input_allocate_device: Dynamically allocate a struct input_dev structure object, and return the address of the object NULL to indicate failure.
        input free_device: Release the input device dev.
        Input register_device: Register input device dev.
        input unregister_device: Unregister the input device dev.
        Input_event: Report an event, dev is the input device for reporting the event, type is the event type, code is the encoding, and value is the specific value, which varies according to the event type.
        input report_key: encapsulation of input_event, the reported event type is EV_KEY
        input sync: synchronization event.
The steps to implement an input device driver are generally as follows.
(1) Use input_allocate_device to allocate an input device object.
(2) Initialize the input device object, including name, path, ID, event types that can be reported and content related to the coding table, etc.
(3) Use input_register_device to register the input device.
(4) Use input event to report the event when the input device generates an event.
(5) Use input sync to synchronize the event. (6) Use input_unregister_device to log out the device when the input device is not needed, and use input free_device
        After introducing the input subsystem and its related programming steps, let’s discuss the key value acquisition and debounce processing . Release its memory. First, we use scanning to obtain key values ​​instead of interrupts. That is to say, configure the pin as an input, then read the level of the pin regularly, and judge the status of the button based on the level. Secondly, the processing of de-shaking can be carried out as shown in the figure below.

 


        Assume that the driver scans the keys every 50ms. Each scan needs to obtain the key level values ​​multiple times. When the key level values ​​read three times in a row are consistent, the acquisition is considered successful. The value of the button is obtained before reporting. In Figure 9.5, the level read at the beginning is high. When reading continues, the level is low, so the count is cleared. When reading again, the level becomes high again, and the count is cleared again. This continues until until the count reaches 2. The next scan is with the mirror pressed all the way, no jitter, so the count reaches 2 smoothly. In the next scan, jitter also occurred, and the count value was continuously cleared until it stabilized to a high level. The scanning interval, the interval between each sampling, and the count value can be determined according to the specific hardware device.
        We said before that the kernel has written a GPIO framework code specifically for GPIO hardware, which makes it easier for us to program GPIO. Let’s take a look at these main APIs first.

int gpio_request(unsigned gpio,const char *label);
int gpio_request_array(const struct gpio *array; size_t num);
void gpio_free(unsigned gpio);\
void gpio_free_array(const struct gpio *array,size_t num);
int gpio direction_input(unsigned gpio);
int gpio direction_output(unsigned gpio,int value);
int gpio get_value(unsigned gpio);
void gpio set_value(unsigned gpio,int value);
int of_get_gpio(struct device_node *np,int index);


        gpio_request: Apply for a GPIO and name it label. Return 0 to indicate success.

        gpio_request_array: Apply for a set of GPIO, return 0 to indicate success.
        gpio_free: Release a GPIO.
        gpio_free_array: Release a group of GPIO.
        gpio_direction_input: Set the GPIO pin as input
        gpio_direction_output: Set the GPIO pin as output
        gpio_get_value: Get the input GPIO pin status.
        gpio_set_value: Set the output level of the output GPIO pin
        of_get_gpio: Get the index GPIO from the device node np, return the GPIO number if successful, and return one if failed negative number.
        Using the above API to program GPIO usually includes the following steps.
        (1) Use of_get_gpio to obtain the number corresponding to the GPIO described in the device node.
        (2) Use gpio_request to apply for permission to use the GPIO pin. If it has been applied for by another driver first, the application again will fail. This usage is similar to our previous usage of I/O memory. it's the same.
        (3) Use gpio_direction input or gpio direction output to configure the GPIO pin as input or output.

        (4) Use gpio_get_value or gpio set_value to get the status of the input GPIO pin or set the output level of the output GPIO pin.
        (5) If the GPIO pin is no longer used, you should use gpio free to release the GPIO pin. With the above knowledge, we can write a key driver based on the input subsystem. The code is as follows
 

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>

#include <linux/slab.h>
#include <linux/delay.h>

#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/gpio.h>
#include <linux/input.h>
#include <linux/input-polldev.h>
#include <linux/platform_device.h>

#define MAX_KEYS_NUM		(8)
#define SCAN_INTERVAL		(50)	/* ms */
#define KB_ACTIVATE_DELAY	(20)	/* us */
#define KBDSCAN_STABLE_COUNT	(3)

struct fskey_dev {
	unsigned int count;
	unsigned int kstate[MAX_KEYS_NUM];
	unsigned int kcount[MAX_KEYS_NUM];
	unsigned char keycode[MAX_KEYS_NUM];
	int gpio[MAX_KEYS_NUM];
	struct input_polled_dev *polldev;
};

static void fskey_poll(struct input_polled_dev *dev)
{
	unsigned int index;
	unsigned int kstate;
	struct fskey_dev *fskey = dev->private;

	for (index = 0; index < fskey->count; index++)
		fskey->kcount[index] = 0;

	index = 0;
	do {
		udelay(KB_ACTIVATE_DELAY);
		kstate = gpio_get_value(fskey->gpio[index]);
		if (kstate != fskey->kstate[index]) {
			fskey->kstate[index] = kstate;
			fskey->kcount[index] = 0;
		} else {
			if (++fskey->kcount[index] >= KBDSCAN_STABLE_COUNT) {
				input_report_key(dev->input, fskey->keycode[index], !kstate);
				index++;
			}
		}
	} while (index < fskey->count);

	input_sync(dev->input);
}

static int fskey_probe(struct platform_device *pdev)
{
	int ret;
	int index;
	struct fskey_dev *fskey;

	fskey = kzalloc(sizeof(struct fskey_dev), GFP_KERNEL);
	if (!fskey)
		return -ENOMEM;

	platform_set_drvdata(pdev, fskey);

	for (index = 0; index < MAX_KEYS_NUM; index++) {
		ret = of_get_gpio(pdev->dev.of_node, index);
		if (ret < 0)
			break;
		else
			fskey->gpio[index] = ret;
	}

	if (!index)
		goto gpio_err;
	else
		fskey->count = index;

	for (index = 0; index < fskey->count; index++) {
		ret = gpio_request(fskey->gpio[index], "KEY");
		if (ret)
			goto req_err;

		gpio_direction_input(fskey->gpio[index]);
		fskey->keycode[index] = KEY_2 + index;
		fskey->kstate[index] = 1;
	}

	fskey->polldev = input_allocate_polled_device();
	if (!fskey->polldev) {
		ret = -ENOMEM;
		goto req_err;
	}

	fskey->polldev->private = fskey;
	fskey->polldev->poll = fskey_poll;
	fskey->polldev->poll_interval = SCAN_INTERVAL;

	fskey->polldev->input->name = "FS4412 Keyboard";
	fskey->polldev->input->phys = "fskbd/input0";
	fskey->polldev->input->id.bustype = BUS_HOST;
	fskey->polldev->input->id.vendor = 0x0001;
	fskey->polldev->input->id.product = 0x0001;
	fskey->polldev->input->id.version = 0x0100;
	fskey->polldev->input->dev.parent = &pdev->dev;

	fskey->polldev->input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
	fskey->polldev->input->keycode = fskey->keycode;
	fskey->polldev->input->keycodesize = sizeof(unsigned char);
	fskey->polldev->input->keycodemax = index;

	for (index = 0; index < fskey->count; index++)
		__set_bit(fskey->keycode[index], fskey->polldev->input->keybit);
	__clear_bit(KEY_RESERVED, fskey->polldev->input->keybit);

	ret = input_register_polled_device(fskey->polldev);
	if (ret)
		goto reg_err;

	return 0;

reg_err:
	input_free_polled_device(fskey->polldev);
req_err:
	for (index--; index >= 0; index--)
		gpio_free(fskey->gpio[index]);
gpio_err:
	kfree(fskey);
	return ret;
}

static int fskey_remove(struct platform_device *pdev)
{
	unsigned int index;
	struct fskey_dev *fskey = platform_get_drvdata(pdev);

	input_unregister_polled_device(fskey->polldev);
	input_free_polled_device(fskey->polldev);
	for (index = 0; index < fskey->count; index++)
		gpio_free(fskey->gpio[index]);
	kfree(fskey);

	return 0;
}

static const struct of_device_id fskey_of_matches[] = {
	{ .compatible = "fs4412,fskey", },
	{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, fskey_of_matches);

struct platform_driver fskey_drv = { 
	.driver = { 
		.name    = "fskey",
		.owner   = THIS_MODULE,
		.of_match_table = of_match_ptr(fskey_of_matches),
	},  
	.probe   = fskey_probe,
	.remove  = fskey_remove,
};

module_platform_driver(fskey_drv);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Kevin Jiang <name<e-mail>");
MODULE_DESCRIPTION("A simple device driver for Keys on FS4412 board");


        Lines 20 to 27 of the code define a structure type struct fskey_dev. The count member indicates how many keys there are: kstate is used to record the last state of the key; kcount is used to record how many consecutive times the same sample has been obtained. status, used to eliminate jitter; keycode is the key code reported to the upper layer; gpio is used to record the GPIO number: polldev is a polldev input device, specifically refers to a device that obtains the input data of the input device through polling, equivalent to struct input_dev The subclass mainly adds a poll function to poll the input device.
        Line 62 of the code dynamically allocates the structure object mentioned above. Lines 68 to 79 are used to get the number corresponding to the GPIO described in the device tree, and get the total number of GPIO pins. Lines 81 to 89 apply for the right to use these GPIOs, configure them as input, and set the key encoding to the value of numeric key 2 and subsequent sequences. The status is 1, indicating that the key is released.
        Line 91 of the code dynamically allocates a struct input_polled_dev structure object. Line 97 saves fskey in the private member so that subsequent functions can obtain fskey through the struct input_polled_dev structure object. Line 98 specifies the polling function fskey_poll. In this function, the status of the key will be detected and the key value will be reported. poll_interval is the polling interval, specified here as 50ms. Lines 101 to 107 are the initialization of the input device structure object, including name, path and ID. Line 109 specifies that the input device can report EV_KEY times, and EV_REP indicates that the driver supports long key press detection. keycode, keycodesize and keycodemax are initialization related to the key coding table. Lines 114 to 116 set the key encodings that the input device can report. Recall the previous keybit description. KEY_RESERVED is the key numbered 0. Use __clear_bit to clear it, which means that the key will not be reported. Line 118 of the code registers the input device.
        In the fskey_poll function, the algorithm introduced earlier is used to debounce, and the interval of each sampling is set to 20us. When the status of three consecutive samples is consistent, imput_report_key is used to report the key value, and then continues to scan the next key. Finally, use input_sync to synchronize, that is, synchronize the previously reported key values ​​to the upper layer. Additionally, the key value reported by !kstate indicates whether the key was pressed or released.
        To compile this driver, you must ensure that the kernel supports polled input devices. Use the make ARCH=arm menuconfig command to make the following configurations in the configuration interface.


        It is important to note that in the arch/arm/boot/dts/exynos4412-fs4412,dts device tree file, the above two GPIOs have been used in other nodes, but the actual hardware has not been used in other nodes. Defined functions, so to ensure that our driver can successfully apply for GPIO, those nodes must be modified or deleted. Select delete here. The nodes involved are regulators, pinctrl@11000000 and keypad@100A0000. After modifying the device tree source file, compile it according to the previous method. Finally, copy the newly generated file to the directory specified by the TFTP server.

There is a fatal bug here. There is an io used when mmc reads registers. After this is deconfigured, the compilation fails. I missed a semicolon and vomited


The application layer code for testing is as follows. Because the code is very simple, it will not be explained here.
 

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>

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

#include <linux/input.h>


#define KEY_DEV_PATH "/dev/input/event0"

int fs4412_get_key(void)
{
	int fd; 
	struct input_event event;

	fd = open(KEY_DEV_PATH, O_RDONLY);
	if(fd == -1) {
		perror("fskey->open");
		return -1;
	}

	while (1) {
		if(read(fd, &event, sizeof(event)) == sizeof(event)) {
			if (event.type == EV_KEY && event.value == 1) {
				close(fd);
				return event.code;
			} else
				continue;
		} else {
			close(fd);
			fprintf(stderr, "fskey->read: read failed\n");
			return -1;
		}
	}
}

int main(int argc, char *argv[])
{
	while (1)
		printf("key value: %d\n", fs4412_get_key());
}

Guess you like

Origin blog.csdn.net/qq_52479948/article/details/132261759