diff options
author | Markus Koch <markus@notsyncing.net> | 2019-07-21 13:20:28 -0400 |
---|---|---|
committer | Dmitry Torokhov <dmitry.torokhov@gmail.com> | 2019-07-22 00:35:24 -0400 |
commit | 5d4b45a1dd7b00feab57624035dcdbc1bab2e0f8 (patch) | |
tree | afd3c89c5f90da97e916ee6f477c562fec081b0e | |
parent | 771a081e44a9baa1991ef011cc453ef425591740 (diff) |
Input: add support for the FlySky FS-iA6B RC receiver
This patch adds support for the FlySky FS-iA6B RC receiver (serial IBUS).
It allows the usage of the FlySky FS-i6 and other AFHDS compliant remote
controls as a joystick input device.
To use it, a patch to inputattach which adds the FS-iA6B as a 115200 baud
serial device is required. I will upstream it after this patch is merged.
More information about the hardware can be found here:
https://notsyncing.net/?p=blog&b=2018.linux-fsia6b
Signed-off-by: Markus Koch <markus@notsyncing.net>
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
-rw-r--r-- | MAINTAINERS | 6 | ||||
-rw-r--r-- | drivers/input/joystick/Kconfig | 10 | ||||
-rw-r--r-- | drivers/input/joystick/Makefile | 5 | ||||
-rw-r--r-- | drivers/input/joystick/fsia6b.c | 231 | ||||
-rw-r--r-- | include/uapi/linux/serio.h | 1 |
5 files changed, 251 insertions, 2 deletions
diff --git a/MAINTAINERS b/MAINTAINERS index 677ef41cb012..cdd25b0a1218 100644 --- a/MAINTAINERS +++ b/MAINTAINERS | |||
@@ -12394,6 +12394,12 @@ S: Maintained | |||
12394 | F: Documentation/input/devices/pxrc.rst | 12394 | F: Documentation/input/devices/pxrc.rst |
12395 | F: drivers/input/joystick/pxrc.c | 12395 | F: drivers/input/joystick/pxrc.c |
12396 | 12396 | ||
12397 | FLYSKY FSIA6B RC RECEIVER | ||
12398 | M: Markus Koch <markus@notsyncing.net> | ||
12399 | L: linux-input@vger.kernel.org | ||
12400 | S: Maintained | ||
12401 | F: drivers/input/joystick/fsia6b.c | ||
12402 | |||
12397 | PHONET PROTOCOL | 12403 | PHONET PROTOCOL |
12398 | M: Remi Denis-Courmont <courmisch@gmail.com> | 12404 | M: Remi Denis-Courmont <courmisch@gmail.com> |
12399 | S: Supported | 12405 | S: Supported |
diff --git a/drivers/input/joystick/Kconfig b/drivers/input/joystick/Kconfig index 72b932901d00..312b854b5506 100644 --- a/drivers/input/joystick/Kconfig +++ b/drivers/input/joystick/Kconfig | |||
@@ -362,4 +362,14 @@ config JOYSTICK_PXRC | |||
362 | To compile this driver as a module, choose M here: the | 362 | To compile this driver as a module, choose M here: the |
363 | module will be called pxrc. | 363 | module will be called pxrc. |
364 | 364 | ||
365 | config JOYSTICK_FSIA6B | ||
366 | tristate "FlySky FS-iA6B RC Receiver" | ||
367 | select SERIO | ||
368 | help | ||
369 | Say Y here if you use a FlySky FS-i6 RC remote control along with the | ||
370 | FS-iA6B RC receiver as a joystick input device. | ||
371 | |||
372 | To compile this driver as a module, choose M here: the | ||
373 | module will be called fsia6b. | ||
374 | |||
365 | endif | 375 | endif |
diff --git a/drivers/input/joystick/Makefile b/drivers/input/joystick/Makefile index dd0492ebbed7..8656023f6ef5 100644 --- a/drivers/input/joystick/Makefile +++ b/drivers/input/joystick/Makefile | |||
@@ -12,6 +12,7 @@ obj-$(CONFIG_JOYSTICK_AS5011) += as5011.o | |||
12 | obj-$(CONFIG_JOYSTICK_ANALOG) += analog.o | 12 | obj-$(CONFIG_JOYSTICK_ANALOG) += analog.o |
13 | obj-$(CONFIG_JOYSTICK_COBRA) += cobra.o | 13 | obj-$(CONFIG_JOYSTICK_COBRA) += cobra.o |
14 | obj-$(CONFIG_JOYSTICK_DB9) += db9.o | 14 | obj-$(CONFIG_JOYSTICK_DB9) += db9.o |
15 | obj-$(CONFIG_JOYSTICK_FSIA6B) += fsia6b.o | ||
15 | obj-$(CONFIG_JOYSTICK_GAMECON) += gamecon.o | 16 | obj-$(CONFIG_JOYSTICK_GAMECON) += gamecon.o |
16 | obj-$(CONFIG_JOYSTICK_GF2K) += gf2k.o | 17 | obj-$(CONFIG_JOYSTICK_GF2K) += gf2k.o |
17 | obj-$(CONFIG_JOYSTICK_GRIP) += grip.o | 18 | obj-$(CONFIG_JOYSTICK_GRIP) += grip.o |
@@ -23,7 +24,7 @@ obj-$(CONFIG_JOYSTICK_JOYDUMP) += joydump.o | |||
23 | obj-$(CONFIG_JOYSTICK_MAGELLAN) += magellan.o | 24 | obj-$(CONFIG_JOYSTICK_MAGELLAN) += magellan.o |
24 | obj-$(CONFIG_JOYSTICK_MAPLE) += maplecontrol.o | 25 | obj-$(CONFIG_JOYSTICK_MAPLE) += maplecontrol.o |
25 | obj-$(CONFIG_JOYSTICK_PSXPAD_SPI) += psxpad-spi.o | 26 | obj-$(CONFIG_JOYSTICK_PSXPAD_SPI) += psxpad-spi.o |
26 | obj-$(CONFIG_JOYSTICK_PXRC) += pxrc.o | 27 | obj-$(CONFIG_JOYSTICK_PXRC) += pxrc.o |
27 | obj-$(CONFIG_JOYSTICK_SIDEWINDER) += sidewinder.o | 28 | obj-$(CONFIG_JOYSTICK_SIDEWINDER) += sidewinder.o |
28 | obj-$(CONFIG_JOYSTICK_SPACEBALL) += spaceball.o | 29 | obj-$(CONFIG_JOYSTICK_SPACEBALL) += spaceball.o |
29 | obj-$(CONFIG_JOYSTICK_SPACEORB) += spaceorb.o | 30 | obj-$(CONFIG_JOYSTICK_SPACEORB) += spaceorb.o |
@@ -32,7 +33,7 @@ obj-$(CONFIG_JOYSTICK_TMDC) += tmdc.o | |||
32 | obj-$(CONFIG_JOYSTICK_TURBOGRAFX) += turbografx.o | 33 | obj-$(CONFIG_JOYSTICK_TURBOGRAFX) += turbografx.o |
33 | obj-$(CONFIG_JOYSTICK_TWIDJOY) += twidjoy.o | 34 | obj-$(CONFIG_JOYSTICK_TWIDJOY) += twidjoy.o |
34 | obj-$(CONFIG_JOYSTICK_WARRIOR) += warrior.o | 35 | obj-$(CONFIG_JOYSTICK_WARRIOR) += warrior.o |
36 | obj-$(CONFIG_JOYSTICK_WALKERA0701) += walkera0701.o | ||
35 | obj-$(CONFIG_JOYSTICK_XPAD) += xpad.o | 37 | obj-$(CONFIG_JOYSTICK_XPAD) += xpad.o |
36 | obj-$(CONFIG_JOYSTICK_ZHENHUA) += zhenhua.o | 38 | obj-$(CONFIG_JOYSTICK_ZHENHUA) += zhenhua.o |
37 | obj-$(CONFIG_JOYSTICK_WALKERA0701) += walkera0701.o | ||
38 | 39 | ||
diff --git a/drivers/input/joystick/fsia6b.c b/drivers/input/joystick/fsia6b.c new file mode 100644 index 000000000000..e78c4c768990 --- /dev/null +++ b/drivers/input/joystick/fsia6b.c | |||
@@ -0,0 +1,231 @@ | |||
1 | // SPDX-License-Identifier: GPL-2.0 | ||
2 | /* | ||
3 | * FS-iA6B iBus RC receiver driver | ||
4 | * | ||
5 | * This driver provides all 14 channels of the FlySky FS-ia6B RC receiver | ||
6 | * as analog values. | ||
7 | * | ||
8 | * Additionally, the channels can be converted to discrete switch values. | ||
9 | * By default, it is configured for the offical FS-i6 remote control. | ||
10 | * If you use a different hardware configuration, you can configure it | ||
11 | * using the `switch_config` parameter. | ||
12 | */ | ||
13 | |||
14 | #include <linux/device.h> | ||
15 | #include <linux/input.h> | ||
16 | #include <linux/kernel.h> | ||
17 | #include <linux/module.h> | ||
18 | #include <linux/serio.h> | ||
19 | #include <linux/slab.h> | ||
20 | #include <linux/types.h> | ||
21 | |||
22 | #define DRIVER_DESC "FS-iA6B iBus RC receiver" | ||
23 | |||
24 | MODULE_AUTHOR("Markus Koch <markus@notsyncing.net>"); | ||
25 | MODULE_DESCRIPTION(DRIVER_DESC); | ||
26 | MODULE_LICENSE("GPL"); | ||
27 | |||
28 | #define IBUS_SERVO_COUNT 14 | ||
29 | |||
30 | static char *switch_config = "00000022320000"; | ||
31 | module_param(switch_config, charp, 0444); | ||
32 | MODULE_PARM_DESC(switch_config, | ||
33 | "Amount of switch positions per channel (14 characters, 0-3)"); | ||
34 | |||
35 | static int fsia6b_axes[IBUS_SERVO_COUNT] = { | ||
36 | ABS_X, ABS_Y, | ||
37 | ABS_Z, ABS_RX, | ||
38 | ABS_RY, ABS_RZ, | ||
39 | ABS_HAT0X, ABS_HAT0Y, | ||
40 | ABS_HAT1X, ABS_HAT1Y, | ||
41 | ABS_HAT2X, ABS_HAT2Y, | ||
42 | ABS_HAT3X, ABS_HAT3Y | ||
43 | }; | ||
44 | |||
45 | enum ibus_state { SYNC, COLLECT, PROCESS }; | ||
46 | |||
47 | struct ibus_packet { | ||
48 | enum ibus_state state; | ||
49 | |||
50 | int offset; | ||
51 | u16 ibuf; | ||
52 | u16 channel[IBUS_SERVO_COUNT]; | ||
53 | }; | ||
54 | |||
55 | struct fsia6b { | ||
56 | struct input_dev *dev; | ||
57 | struct ibus_packet packet; | ||
58 | |||
59 | char phys[32]; | ||
60 | }; | ||
61 | |||
62 | static irqreturn_t fsia6b_serio_irq(struct serio *serio, | ||
63 | unsigned char data, unsigned int flags) | ||
64 | { | ||
65 | struct fsia6b *fsia6b = serio_get_drvdata(serio); | ||
66 | int i; | ||
67 | int sw_state; | ||
68 | int sw_id = BTN_0; | ||
69 | |||
70 | fsia6b->packet.ibuf = (data << 8) | ((fsia6b->packet.ibuf >> 8) & 0xFF); | ||
71 | |||
72 | switch (fsia6b->packet.state) { | ||
73 | case SYNC: | ||
74 | if (fsia6b->packet.ibuf == 0x4020) | ||
75 | fsia6b->packet.state = COLLECT; | ||
76 | break; | ||
77 | |||
78 | case COLLECT: | ||
79 | fsia6b->packet.state = PROCESS; | ||
80 | break; | ||
81 | |||
82 | case PROCESS: | ||
83 | fsia6b->packet.channel[fsia6b->packet.offset] = | ||
84 | fsia6b->packet.ibuf; | ||
85 | fsia6b->packet.offset++; | ||
86 | |||
87 | if (fsia6b->packet.offset == IBUS_SERVO_COUNT) { | ||
88 | fsia6b->packet.offset = 0; | ||
89 | fsia6b->packet.state = SYNC; | ||
90 | for (i = 0; i < IBUS_SERVO_COUNT; ++i) { | ||
91 | input_report_abs(fsia6b->dev, fsia6b_axes[i], | ||
92 | fsia6b->packet.channel[i]); | ||
93 | |||
94 | sw_state = 0; | ||
95 | if (fsia6b->packet.channel[i] > 1900) | ||
96 | sw_state = 1; | ||
97 | else if (fsia6b->packet.channel[i] < 1100) | ||
98 | sw_state = 2; | ||
99 | |||
100 | switch (switch_config[i]) { | ||
101 | case '3': | ||
102 | input_report_key(fsia6b->dev, | ||
103 | sw_id++, | ||
104 | sw_state == 0); | ||
105 | /* fall-through */ | ||
106 | case '2': | ||
107 | input_report_key(fsia6b->dev, | ||
108 | sw_id++, | ||
109 | sw_state == 1); | ||
110 | /* fall-through */ | ||
111 | case '1': | ||
112 | input_report_key(fsia6b->dev, | ||
113 | sw_id++, | ||
114 | sw_state == 2); | ||
115 | } | ||
116 | } | ||
117 | input_sync(fsia6b->dev); | ||
118 | } else { | ||
119 | fsia6b->packet.state = COLLECT; | ||
120 | } | ||
121 | break; | ||
122 | } | ||
123 | |||
124 | return IRQ_HANDLED; | ||
125 | } | ||
126 | |||
127 | static int fsia6b_serio_connect(struct serio *serio, struct serio_driver *drv) | ||
128 | { | ||
129 | struct fsia6b *fsia6b; | ||
130 | struct input_dev *input_dev; | ||
131 | int err; | ||
132 | int i, j; | ||
133 | int sw_id = 0; | ||
134 | |||
135 | fsia6b = kzalloc(sizeof(*fsia6b), GFP_KERNEL); | ||
136 | if (!fsia6b) | ||
137 | return -ENOMEM; | ||
138 | |||
139 | fsia6b->packet.ibuf = 0; | ||
140 | fsia6b->packet.offset = 0; | ||
141 | fsia6b->packet.state = SYNC; | ||
142 | |||
143 | serio_set_drvdata(serio, fsia6b); | ||
144 | |||
145 | input_dev = input_allocate_device(); | ||
146 | if (!input_dev) { | ||
147 | err = -ENOMEM; | ||
148 | goto fail1; | ||
149 | } | ||
150 | fsia6b->dev = input_dev; | ||
151 | |||
152 | snprintf(fsia6b->phys, sizeof(fsia6b->phys), "%s/input0", serio->phys); | ||
153 | |||
154 | input_dev->name = DRIVER_DESC; | ||
155 | input_dev->phys = fsia6b->phys; | ||
156 | input_dev->id.bustype = BUS_RS232; | ||
157 | input_dev->id.vendor = SERIO_FSIA6B; | ||
158 | input_dev->id.product = serio->id.id; | ||
159 | input_dev->id.version = 0x0100; | ||
160 | input_dev->dev.parent = &serio->dev; | ||
161 | |||
162 | for (i = 0; i < IBUS_SERVO_COUNT; i++) | ||
163 | input_set_abs_params(input_dev, fsia6b_axes[i], | ||
164 | 1000, 2000, 2, 2); | ||
165 | |||
166 | /* Register switch configuration */ | ||
167 | for (i = 0; i < IBUS_SERVO_COUNT; i++) { | ||
168 | if (switch_config[i] < '0' || switch_config[i] > '3') { | ||
169 | dev_err(&fsia6b->dev->dev, | ||
170 | "Invalid switch configuration supplied for fsia6b.\n"); | ||
171 | err = -EINVAL; | ||
172 | goto fail2; | ||
173 | } | ||
174 | |||
175 | for (j = '1'; j <= switch_config[i]; j++) { | ||
176 | input_set_capability(input_dev, EV_KEY, BTN_0 + sw_id); | ||
177 | sw_id++; | ||
178 | } | ||
179 | } | ||
180 | |||
181 | err = serio_open(serio, drv); | ||
182 | if (err) | ||
183 | goto fail2; | ||
184 | |||
185 | err = input_register_device(fsia6b->dev); | ||
186 | if (err) | ||
187 | goto fail3; | ||
188 | |||
189 | return 0; | ||
190 | |||
191 | fail3: serio_close(serio); | ||
192 | fail2: input_free_device(input_dev); | ||
193 | fail1: serio_set_drvdata(serio, NULL); | ||
194 | kfree(fsia6b); | ||
195 | return err; | ||
196 | } | ||
197 | |||
198 | static void fsia6b_serio_disconnect(struct serio *serio) | ||
199 | { | ||
200 | struct fsia6b *fsia6b = serio_get_drvdata(serio); | ||
201 | |||
202 | serio_close(serio); | ||
203 | serio_set_drvdata(serio, NULL); | ||
204 | input_unregister_device(fsia6b->dev); | ||
205 | kfree(fsia6b); | ||
206 | } | ||
207 | |||
208 | static const struct serio_device_id fsia6b_serio_ids[] = { | ||
209 | { | ||
210 | .type = SERIO_RS232, | ||
211 | .proto = SERIO_FSIA6B, | ||
212 | .id = SERIO_ANY, | ||
213 | .extra = SERIO_ANY, | ||
214 | }, | ||
215 | { 0 } | ||
216 | }; | ||
217 | |||
218 | MODULE_DEVICE_TABLE(serio, fsia6b_serio_ids); | ||
219 | |||
220 | static struct serio_driver fsia6b_serio_drv = { | ||
221 | .driver = { | ||
222 | .name = "fsia6b" | ||
223 | }, | ||
224 | .description = DRIVER_DESC, | ||
225 | .id_table = fsia6b_serio_ids, | ||
226 | .interrupt = fsia6b_serio_irq, | ||
227 | .connect = fsia6b_serio_connect, | ||
228 | .disconnect = fsia6b_serio_disconnect | ||
229 | }; | ||
230 | |||
231 | module_serio_driver(fsia6b_serio_drv) | ||
diff --git a/include/uapi/linux/serio.h b/include/uapi/linux/serio.h index a0cac1d8670d..50e991952c97 100644 --- a/include/uapi/linux/serio.h +++ b/include/uapi/linux/serio.h | |||
@@ -82,5 +82,6 @@ | |||
82 | #define SERIO_EGALAX 0x3f | 82 | #define SERIO_EGALAX 0x3f |
83 | #define SERIO_PULSE8_CEC 0x40 | 83 | #define SERIO_PULSE8_CEC 0x40 |
84 | #define SERIO_RAINSHADOW_CEC 0x41 | 84 | #define SERIO_RAINSHADOW_CEC 0x41 |
85 | #define SERIO_FSIA6B 0x42 | ||
85 | 86 | ||
86 | #endif /* _UAPI_SERIO_H */ | 87 | #endif /* _UAPI_SERIO_H */ |