IMX6ULL 移植

1 INPUT 子系统

INPUT 子系统的核心代码在 drivers/input/input.c 文件当中。

我们要使用 INPUT 子系统,只需要注册一个 input 设备即可。

input_dev 结构体表示 input 设备。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
// https://elixir.bootlin.com/linux/v7.1-rc7/source/include/linux/input.h#L45

struct input_dev {
const char *name;
const char *phys;
const char *uniq;
struct input_id id;

unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)];

unsigned long evbit[BITS_TO_LONGS(EV_CNT)]; // 事件类型的位图
unsigned long keybit[BITS_TO_LONGS(KEY_CNT)]; // 按键值的位图
unsigned long relbit[BITS_TO_LONGS(REL_CNT)]; // 相对坐标的位图
unsigned long absbit[BITS_TO_LONGS(ABS_CNT)]; // 绝对坐标的位图
unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)]; // 杂项事件的位图
unsigned long ledbit[BITS_TO_LONGS(LED_CNT)]; // LED 相关的位图
unsigned long sndbit[BITS_TO_LONGS(SND_CNT)]; // sound 有关的位图
unsigned long ffbit[BITS_TO_LONGS(FF_CNT)]; // 压力反馈的位图
unsigned long swbit[BITS_TO_LONGS(SW_CNT)]; // 开关状态的位图

unsigned int hint_events_per_packet;

unsigned int keycodemax;
unsigned int keycodesize;
void *keycode;

int (*setkeycode)(struct input_dev *dev,
const struct input_keymap_entry *ke,
unsigned int *old_keycode);
int (*getkeycode)(struct input_dev *dev,
struct input_keymap_entry *ke);

struct ff_device *ff;

struct input_dev_poller *poller;

unsigned int repeat_key;
struct timer_list timer;

int rep[REP_CNT];

struct input_mt *mt;

struct input_absinfo *absinfo;

unsigned long key[BITS_TO_LONGS(KEY_CNT)];
unsigned long led[BITS_TO_LONGS(LED_CNT)];
unsigned long snd[BITS_TO_LONGS(SND_CNT)];
unsigned long sw[BITS_TO_LONGS(SW_CNT)];

int (*open)(struct input_dev *dev);
void (*close)(struct input_dev *dev);
int (*flush)(struct input_dev *dev, struct file *file);
int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value);

struct input_handle __rcu *grab;

spinlock_t event_lock;
struct mutex mutex;

unsigned int users;
bool going_away;

struct device dev;

struct list_head h_list;
struct list_head node;

unsigned int num_vals;
unsigned int max_vals;
struct input_value *vals;

bool devres_managed;

ktime_t timestamp[INPUT_CLK_MAX];

bool inhibited;
};

其中 evbit 表示输入事件的类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// https://elixir.bootlin.com/linux/v7.1-rc7/source/include/uapi/linux/input-event-codes.h#L52

#define EV_SYN 0x00 // 同步事件
#define EV_KEY 0x01 // 按键事件
#define EV_REL 0x02
#define EV_ABS 0x03
#define EV_MSC 0x04
#define EV_SW 0x05
#define EV_LED 0x11
#define EV_SND 0x12
#define EV_REP 0x14
#define EV_FF 0x15
#define EV_PWR 0x16
#define EV_FF_STATUS 0x17
#define EV_MAX 0x1f
#define EV_CNT (EV_MAX+1)

本章需要使用按键,因此需要注册 EV_KEY。

另外由于需要使用按键事件,我们也要使用到 keybit。

https://elixir.bootlin.com/linux/v7.1-rc7/source/include/uapi/linux/input-event-codes.h#L76

1.1 INPUT 子系统的 API

input_allocate_device:

函数原型如下:

struct input_dev *input_allocate_device(void);

函数参数和返回值含义如下:

  • 参数:无。
  • 返回值:申请到的 input_dev 指针;如果申请失败则返回 NULL。

该函数用于申请一个 input_dev 结构体,后续需要对该结构体进行初始化。

input_free_device:

如果不再使用前面申请到的 input_dev,就需要调用 input_free_device 将其释放。函数原型如下:

void input_free_device(struct input_dev *dev);

函数参数和返回值含义如下:

  • dev:需要释放的 input_dev。
  • 返回值:无。

该函数一般用于申请成功后但尚未注册时,因后续初始化失败而进行资源释放。

input_register_device:

申请好一个 input_dev 以后,需要对其进行初始化。
初始化的内容通常包括事件类型 evbit 和事件值 keybit 等。
当 input_dev 初始化完成以后,就需要向 Linux 内核注册该 input 设备,此时需要用到 input_register_device 函数。其原型如下:

int input_register_device(struct input_dev *dev);

函数参数和返回值含义如下:

  • dev:要注册的 input_dev。
  • 返回值:
    • 0:input_dev 注册成功。
    • 负值:input_dev 注册失败。

input_unregister_device:

当驱动卸载时,需要将前面注册到内核中的 input_dev 注销掉,此时需要使用 input_unregister_device 函数。其原型如下:

void input_unregister_device(struct input_dev *dev);

函数参数和返回值含义如下:

  • dev:要注销的 input_dev。
  • 返回值:无。

1.1.1 使用流程总结

Linux input 设备的一般使用流程如下:

  1. 调用 input_allocate_device() 申请一个 input_dev。
  2. 初始化 input_dev 的相关成员变量,例如:
    • 设备名称
    • 事件类型 evbit
    • 按键值 keybit
  3. 调用 input_register_device() 将设备注册到内核。
  4. 当驱动卸载时:
    • 如果设备已经注册,则调用 input_unregister_device() 注销设备。
    • 如果设备尚未注册但已经申请,则调用 input_free_device() 释放设备。

1.1.2 说明

需要注意的是:

  • input_allocate_device() 与 input_free_device() 通常配套使用。
  • input_register_device() 与 input_unregister_device() 通常配套使用。
  • 如果 input_dev 已经成功注册,一般使用 input_unregister_device() 即可,不需要再单独调用 input_free_device()。

2 实例

开发板上的原理图。

alt text

首先修改设备树,承接上篇 LED 驱动。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
/*
* Copyright (C) 2016 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 "imx6ull-14x14-evk.dts"

/ {
alphaled {
compatible = "atk,test";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_alphaled>;
led-gpios = <&gpio1 3 GPIO_ACTIVE_LOW>;
status = "okay";
};

keyinput {
compatible = "atk,keyinput";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_keyinput>;
key-gpios = <&gpio1 18 GPIO_ACTIVE_LOW>;
linux,code = <KEY_0>;
status = "okay";
};
};

&tsc {
status = "disabled";
};

&iomuxc {
imx6ul-evk {
pinctrl_alphaled: alphaledgrp {
fsl,pins = <
MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0xb0
>;
};

pinctrl_keyinput: keyinputgrp {
fsl,pins = <
MX6UL_PAD_UART1_CTS_B__GPIO1_IO18 0x17059 /* 把 UART1_CTS_B -> GPIO1_IO18 */
>;
};
};
};

&usdhc2 {
pinctrl-names = "default", "state_100mhz", "state_200mhz";
pinctrl-0 = <&pinctrl_usdhc2_8bit>;
pinctrl-1 = <&pinctrl_usdhc2_8bit_100mhz>;
pinctrl-2 = <&pinctrl_usdhc2_8bit_200mhz>;
bus-width = <8>;
non-removable;
status = "okay";
};

按键的相关驱动。把 KEY0 作为 EV_KEY/KEY_0 上报,GPIO 低电平表示按下,使用中断加 10ms 消抖。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
#include <linux/device.h>
#include <linux/gpio/consumer.h>
#include <linux/input.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/workqueue.h>

#define KEYINPUT_NAME "atk-keyinput"
#define KEY_DEBOUNCE_MS 10

struct keyinput_dev {
struct gpio_desc *gpiod;
struct input_dev *input;
struct delayed_work work;
unsigned int keycode;
};

static void keyinput_report_work(struct work_struct *work)
{
struct keyinput_dev *keydev;
int value;

keydev = container_of(to_delayed_work(work), struct keyinput_dev, work);

value = gpiod_get_value_cansleep(keydev->gpiod);
if (value < 0)
return;

/* 上报按键事件 */
input_report_key(keydev->input, keydev->keycode, value);
/* 正式发送数据 */
input_sync(keydev->input);
}

static irqreturn_t keyinput_irq_handler(int irq, void *dev_id)
{
struct keyinput_dev *keydev = dev_id;

/* 这里采用延迟工作来消抖 */
mod_delayed_work(system_wq, &keydev->work,
msecs_to_jiffies(KEY_DEBOUNCE_MS));

return IRQ_HANDLED;
}

static int keyinput_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct keyinput_dev *keydev;
struct input_dev *input;
u32 keycode = KEY_0;
int irq;
int ret;

keydev = devm_kzalloc(dev, sizeof(*keydev), GFP_KERNEL);
if (!keydev)
return -ENOMEM;

/* 会去设备树当中查看 key-gpios 的属性 */
keydev->gpiod = devm_gpiod_get(dev, "key", GPIOD_IN);
if (IS_ERR(keydev->gpiod)) {
dev_err(dev, "failed to get key gpio\n");
return PTR_ERR(keydev->gpiod);
}

/* 景观我们初始化为 KEY_0,这里依然可以默认使用设备树当中的属性 */
of_property_read_u32(dev->of_node, "linux,code", &keycode);
keydev->keycode = keycode;

input = input_allocate_device();
if (!input)
return -ENOMEM;

keydev->input = input;
input->name = KEYINPUT_NAME;
input->phys = "atk-keyinput/input0";
input->id.bustype = BUS_HOST;
input->dev.parent = dev;

input_set_capability(input, EV_KEY, keydev->keycode);

ret = input_register_device(input);
if (ret) {
input_free_device(input);
return ret;
}

/* 延迟工作机制 */
INIT_DELAYED_WORK(&keydev->work, keyinput_report_work);

irq = gpiod_to_irq(keydev->gpiod);
if (irq < 0) {
ret = irq;
goto err_unregister_input;
}

/* 申请中断机制 */
ret = devm_request_irq(dev, irq, keyinput_irq_handler,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
dev_name(dev), keydev);
if (ret) {
dev_err(dev, "failed to request irq\n");
goto err_unregister_input;
}

platform_set_drvdata(pdev, keydev);
dev_info(dev, "registered key input device, keycode=%u\n", keydev->keycode);

return 0;

err_unregister_input:
cancel_delayed_work_sync(&keydev->work);
input_unregister_device(input);
return ret;
}

static int keyinput_remove(struct platform_device *pdev)
{
struct keyinput_dev *keydev = platform_get_drvdata(pdev);

cancel_delayed_work_sync(&keydev->work);
input_unregister_device(keydev->input);

return 0;
}

static const struct of_device_id keyinput_of_match[] = {
{ .compatible = "atk,keyinput" },
{ }
};
MODULE_DEVICE_TABLE(of, keyinput_of_match);

static struct platform_driver keyinput_driver = {
.probe = keyinput_probe,
.remove = keyinput_remove,
.driver = {
.name = KEYINPUT_NAME,
.of_match_table = keyinput_of_match,
},
};

module_platform_driver(keyinput_driver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Jvle");
MODULE_DESCRIPTION("KEY0 input subsystem driver for i.MX6ULL");

给 LED 的 test 驱动添加了 read 接口,app 可以先读取当前灯状态,再做切换。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/gpio/consumer.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/uaccess.h>

#define TESTDEV_NAME "test"

struct test_dev {
dev_t devid;
struct cdev cdev;
struct class *class;
struct device *device;
struct gpio_desc *gpiod;
};

static int test_open(struct inode *inode, struct file *file)
{
struct test_dev *led;

led = container_of(inode->i_cdev, struct test_dev, cdev);
file->private_data = led;

return 0;
}

static ssize_t test_write(struct file *file, const char __user *buf,
size_t count, loff_t *ppos)
{
struct test_dev *led = file->private_data;
char kbuf[16];
size_t len;
int ret, value;

if (!count)
return 0;

len = min(count, sizeof(kbuf) - 1);
if (copy_from_user(kbuf, buf, len))
return -EFAULT;

kbuf[len] = '\0';

ret = kstrtoint(kbuf, 0, &value);
if (ret)
return ret;

gpiod_set_value_cansleep(led->gpiod, !!value);

return count;
}

static ssize_t test_read(struct file *file, char __user *buf,
size_t count, loff_t *ppos)
{
struct test_dev *led = file->private_data;
char kbuf[4];
int value, len;

value = gpiod_get_value_cansleep(led->gpiod);
if (value < 0)
return value;

len = scnprintf(kbuf, sizeof(kbuf), "%d\n", value ? 1 : 0);

return simple_read_from_buffer(buf, count, ppos, kbuf, len);
}

static const struct file_operations test_fops = {
.owner = THIS_MODULE,
.open = test_open,
.read = test_read,
.write = test_write,
};

static int test_probe(struct platform_device *pdev)
{
struct test_dev *led;
int ret;

led = devm_kzalloc(&pdev->dev, sizeof(*led), GFP_KERNEL);
if (!led)
return -ENOMEM;

led->gpiod = devm_gpiod_get(&pdev->dev, "led", GPIOD_OUT_HIGH);
if (IS_ERR(led->gpiod)) {
dev_err(&pdev->dev, "failed to get led gpio\n");
return PTR_ERR(led->gpiod);
}

ret = alloc_chrdev_region(&led->devid, 0, 1, TESTDEV_NAME);
if (ret)
return ret;

cdev_init(&led->cdev, &test_fops);
led->cdev.owner = THIS_MODULE;

ret = cdev_add(&led->cdev, led->devid, 1);
if (ret)
goto err_unregister_chrdev;

led->class = class_create(THIS_MODULE, TESTDEV_NAME);
if (IS_ERR(led->class)) {
ret = PTR_ERR(led->class);
goto err_cdev_del;
}

led->device = device_create(led->class, NULL, led->devid, NULL,
TESTDEV_NAME);
if (IS_ERR(led->device)) {
ret = PTR_ERR(led->device);
goto err_class_destroy;
}

platform_set_drvdata(pdev, led);
dev_info(&pdev->dev, "test probe ok\n");

return 0;

err_class_destroy:
class_destroy(led->class);
err_cdev_del:
cdev_del(&led->cdev);
err_unregister_chrdev:
unregister_chrdev_region(led->devid, 1);
return ret;
}

static int test_remove(struct platform_device *pdev)
{
struct test_dev *led = platform_get_drvdata(pdev);

device_destroy(led->class, led->devid);
class_destroy(led->class);
cdev_del(&led->cdev);
unregister_chrdev_region(led->devid, 1);

return 0;
}

static const struct of_device_id test_of_match[] = {
{ .compatible = "atk,test" },
{ }
};
MODULE_DEVICE_TABLE(of, test_of_match);

static struct platform_driver test_driver = {
.probe = test_probe,
.remove = test_remove,
.driver = {
.name = TESTDEV_NAME,
.of_match_table = test_of_match,
},
};

module_platform_driver(test_driver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Jvle");
MODULE_DESCRIPTION("GPIO descriptor based test driver");

新增代码文件 key_led_app.c,监听 /dev/input/event* 中名为 atk-keyinput 的输入设备。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
#include <errno.h>
#include <fcntl.h>
#include <linux/input.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>

#define DEFAULT_LED_DEV "/dev/test"
#define DEFAULT_INPUT_NAME "atk-keyinput"

static void usage(const char *prog)
{
fprintf(stderr, "Usage: %s [led_device] [input_event_device]\n", prog);
fprintf(stderr, "Example: %s /dev/test /dev/input/event0\n", prog);
}

static int write_led_state(int fd, int state)
{
const char *value = state ? "1" : "0";
ssize_t ret;

ret = write(fd, value, 1);
if (ret != 1)
return -1;

return 0;
}

static int read_led_state(int fd)
{
char buf[16];
ssize_t ret;

ret = read(fd, buf, sizeof(buf) - 1);
if (ret <= 0)
return -1;

buf[ret] = '\0';
return atoi(buf) ? 1 : 0;
}

static int open_input_device(const char *path)
{
char name[64];
char devpath[32];
int fd;
int i;

if (path)
return open(path, O_RDONLY);

for (i = 0; i < 32; i++) {
snprintf(devpath, sizeof(devpath), "/dev/input/event%d", i);
fd = open(devpath, O_RDONLY);
if (fd < 0)
continue;

memset(name, 0, sizeof(name));
if (ioctl(fd, EVIOCGNAME(sizeof(name)), name) >= 0 &&
strcmp(name, DEFAULT_INPUT_NAME) == 0)
return fd;

close(fd);
}

errno = ENODEV;
return -1;
}

int main(int argc, char *argv[])
{
const char *led_dev = DEFAULT_LED_DEV;
const char *input_dev = NULL;
struct input_event ev;
int input_fd;
int led_fd;
int led_state;
ssize_t ret;

if (argc > 3) {
usage(argv[0]);
return 1;
}

if (argc >= 2)
led_dev = argv[1];

if (argc == 3)
input_dev = argv[2];

led_fd = open(led_dev, O_RDWR);
if (led_fd < 0) {
fprintf(stderr, "open %s failed: %s\n", led_dev, strerror(errno));
return 1;
}

led_state = read_led_state(led_fd);
if (led_state < 0)
led_state = 0;

input_fd = open_input_device(input_dev);
if (input_fd < 0) {
fprintf(stderr, "open input device failed: %s\n", strerror(errno));
close(led_fd);
return 1;
}

printf("listen KEY0 and toggle LED, current state: %s\n",
led_state ? "on" : "off");
fflush(stdout);

for (;;) {
ret = read(input_fd, &ev, sizeof(ev));
if (ret < 0) {
if (errno == EINTR)
continue;

fprintf(stderr, "read input event failed: %s\n",
strerror(errno));
break;
}

if (ret != sizeof(ev))
continue;

if (ev.type == EV_KEY && ev.code == KEY_0 && ev.value == 1) {
led_state = !led_state;
if (write_led_state(led_fd, led_state) < 0) {
fprintf(stderr, "write led failed: %s\n",
strerror(errno));
break;
}

printf("KEY0 pressed, LED %s\n",
led_state ? "on" : "off");
fflush(stdout);
}
}

close(input_fd);
close(led_fd);
return 1;
}

Makefile 当中新增对应的关键字,同时编译这些文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
KDIR ?= ../linux-imx-rel_imx_4.1.15_2.1.0_ga
ARCH ?= arm
CROSS_COMPILE ?= ../gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabihf-

obj-m += test.o
obj-m += keyinput.o

all:
$(MAKE) -C $(KDIR) M=$(CURDIR) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) modules
$(CROSS_COMPILE)gcc -Wall -static -O2 -o test_app test_app.c
$(CROSS_COMPILE)gcc -Wall -static -O2 -o key_led_app key_led_app.c

clean:
$(MAKE) -C $(KDIR) M=$(CURDIR) clean
rm -f test_app key_led_app

观察结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
[imx6ull:/]# insmod /lib/modules/4.1.15/extra/keyinput.ko 
input: atk-keyinput as /devices/platform/keyinput/input/input1
atk-keyinput keyinput: registered key input device, keycode=11
[imx6ull:/]# key_led_app
listen KEY0 and toggle LED, current state: on
KEY0 pressed, LED off
KEY0 pressed, LED on
KEY0 pressed, LED off
KEY0 pressed, LED on
KEY0 pressed, LED off
KEY0 pressed, LED on
KEY0 pressed, LED off
KEY0 pressed, LED on
random: nonblocking pool is initialized
KEY0 pressed, LED off
KEY0 pressed, LED on
KEY0 pressed, LED off
KEY0 pressed, LED on
KEY0 pressed, LED off
KEY0 pressed, LED on
KEY0 pressed, LED off
KEY0 pressed, LED on
KEY0 pressed, LED off
KEY0 pressed, LED on
KEY0 pressed, LED off
KEY0 pressed, LED on
KEY0 pressed, LED off
KEY0 pressed, LED on
KEY0 pressed, LED off
^C
[imx6ull:/]# lsmod
keyinput 2415 0 - Live 0x7f004000 (O)
test 2580 0 - Live 0x7f000000 (O)