15d4b45a1SMarkus Koch // SPDX-License-Identifier: GPL-2.0
25d4b45a1SMarkus Koch /*
35d4b45a1SMarkus Koch * FS-iA6B iBus RC receiver driver
45d4b45a1SMarkus Koch *
55d4b45a1SMarkus Koch * This driver provides all 14 channels of the FlySky FS-ia6B RC receiver
65d4b45a1SMarkus Koch * as analog values.
75d4b45a1SMarkus Koch *
85d4b45a1SMarkus Koch * Additionally, the channels can be converted to discrete switch values.
95d4b45a1SMarkus Koch * By default, it is configured for the offical FS-i6 remote control.
105d4b45a1SMarkus Koch * If you use a different hardware configuration, you can configure it
115d4b45a1SMarkus Koch * using the `switch_config` parameter.
125d4b45a1SMarkus Koch */
135d4b45a1SMarkus Koch
145d4b45a1SMarkus Koch #include <linux/device.h>
155d4b45a1SMarkus Koch #include <linux/input.h>
165d4b45a1SMarkus Koch #include <linux/kernel.h>
175d4b45a1SMarkus Koch #include <linux/module.h>
185d4b45a1SMarkus Koch #include <linux/serio.h>
195d4b45a1SMarkus Koch #include <linux/slab.h>
205d4b45a1SMarkus Koch #include <linux/types.h>
215d4b45a1SMarkus Koch
225d4b45a1SMarkus Koch #define DRIVER_DESC "FS-iA6B iBus RC receiver"
235d4b45a1SMarkus Koch
245d4b45a1SMarkus Koch MODULE_AUTHOR("Markus Koch <markus@notsyncing.net>");
255d4b45a1SMarkus Koch MODULE_DESCRIPTION(DRIVER_DESC);
265d4b45a1SMarkus Koch MODULE_LICENSE("GPL");
275d4b45a1SMarkus Koch
285d4b45a1SMarkus Koch #define IBUS_SERVO_COUNT 14
295d4b45a1SMarkus Koch
305d4b45a1SMarkus Koch static char *switch_config = "00000022320000";
315d4b45a1SMarkus Koch module_param(switch_config, charp, 0444);
325d4b45a1SMarkus Koch MODULE_PARM_DESC(switch_config,
335d4b45a1SMarkus Koch "Amount of switch positions per channel (14 characters, 0-3)");
345d4b45a1SMarkus Koch
355d4b45a1SMarkus Koch static int fsia6b_axes[IBUS_SERVO_COUNT] = {
365d4b45a1SMarkus Koch ABS_X, ABS_Y,
375d4b45a1SMarkus Koch ABS_Z, ABS_RX,
385d4b45a1SMarkus Koch ABS_RY, ABS_RZ,
395d4b45a1SMarkus Koch ABS_HAT0X, ABS_HAT0Y,
405d4b45a1SMarkus Koch ABS_HAT1X, ABS_HAT1Y,
415d4b45a1SMarkus Koch ABS_HAT2X, ABS_HAT2Y,
425d4b45a1SMarkus Koch ABS_HAT3X, ABS_HAT3Y
435d4b45a1SMarkus Koch };
445d4b45a1SMarkus Koch
455d4b45a1SMarkus Koch enum ibus_state { SYNC, COLLECT, PROCESS };
465d4b45a1SMarkus Koch
475d4b45a1SMarkus Koch struct ibus_packet {
485d4b45a1SMarkus Koch enum ibus_state state;
495d4b45a1SMarkus Koch
505d4b45a1SMarkus Koch int offset;
515d4b45a1SMarkus Koch u16 ibuf;
525d4b45a1SMarkus Koch u16 channel[IBUS_SERVO_COUNT];
535d4b45a1SMarkus Koch };
545d4b45a1SMarkus Koch
555d4b45a1SMarkus Koch struct fsia6b {
565d4b45a1SMarkus Koch struct input_dev *dev;
575d4b45a1SMarkus Koch struct ibus_packet packet;
585d4b45a1SMarkus Koch
595d4b45a1SMarkus Koch char phys[32];
605d4b45a1SMarkus Koch };
615d4b45a1SMarkus Koch
fsia6b_serio_irq(struct serio * serio,unsigned char data,unsigned int flags)625d4b45a1SMarkus Koch static irqreturn_t fsia6b_serio_irq(struct serio *serio,
635d4b45a1SMarkus Koch unsigned char data, unsigned int flags)
645d4b45a1SMarkus Koch {
655d4b45a1SMarkus Koch struct fsia6b *fsia6b = serio_get_drvdata(serio);
665d4b45a1SMarkus Koch int i;
675d4b45a1SMarkus Koch int sw_state;
685d4b45a1SMarkus Koch int sw_id = BTN_0;
695d4b45a1SMarkus Koch
705d4b45a1SMarkus Koch fsia6b->packet.ibuf = (data << 8) | ((fsia6b->packet.ibuf >> 8) & 0xFF);
715d4b45a1SMarkus Koch
725d4b45a1SMarkus Koch switch (fsia6b->packet.state) {
735d4b45a1SMarkus Koch case SYNC:
745d4b45a1SMarkus Koch if (fsia6b->packet.ibuf == 0x4020)
755d4b45a1SMarkus Koch fsia6b->packet.state = COLLECT;
765d4b45a1SMarkus Koch break;
775d4b45a1SMarkus Koch
785d4b45a1SMarkus Koch case COLLECT:
795d4b45a1SMarkus Koch fsia6b->packet.state = PROCESS;
805d4b45a1SMarkus Koch break;
815d4b45a1SMarkus Koch
825d4b45a1SMarkus Koch case PROCESS:
835d4b45a1SMarkus Koch fsia6b->packet.channel[fsia6b->packet.offset] =
845d4b45a1SMarkus Koch fsia6b->packet.ibuf;
855d4b45a1SMarkus Koch fsia6b->packet.offset++;
865d4b45a1SMarkus Koch
875d4b45a1SMarkus Koch if (fsia6b->packet.offset == IBUS_SERVO_COUNT) {
885d4b45a1SMarkus Koch fsia6b->packet.offset = 0;
895d4b45a1SMarkus Koch fsia6b->packet.state = SYNC;
905d4b45a1SMarkus Koch for (i = 0; i < IBUS_SERVO_COUNT; ++i) {
915d4b45a1SMarkus Koch input_report_abs(fsia6b->dev, fsia6b_axes[i],
925d4b45a1SMarkus Koch fsia6b->packet.channel[i]);
935d4b45a1SMarkus Koch
945d4b45a1SMarkus Koch sw_state = 0;
955d4b45a1SMarkus Koch if (fsia6b->packet.channel[i] > 1900)
965d4b45a1SMarkus Koch sw_state = 1;
975d4b45a1SMarkus Koch else if (fsia6b->packet.channel[i] < 1100)
985d4b45a1SMarkus Koch sw_state = 2;
995d4b45a1SMarkus Koch
1005d4b45a1SMarkus Koch switch (switch_config[i]) {
1015d4b45a1SMarkus Koch case '3':
1025d4b45a1SMarkus Koch input_report_key(fsia6b->dev,
1035d4b45a1SMarkus Koch sw_id++,
1045d4b45a1SMarkus Koch sw_state == 0);
105df561f66SGustavo A. R. Silva fallthrough;
1065d4b45a1SMarkus Koch case '2':
1075d4b45a1SMarkus Koch input_report_key(fsia6b->dev,
1085d4b45a1SMarkus Koch sw_id++,
1095d4b45a1SMarkus Koch sw_state == 1);
110df561f66SGustavo A. R. Silva fallthrough;
1115d4b45a1SMarkus Koch case '1':
1125d4b45a1SMarkus Koch input_report_key(fsia6b->dev,
1135d4b45a1SMarkus Koch sw_id++,
1145d4b45a1SMarkus Koch sw_state == 2);
1155d4b45a1SMarkus Koch }
1165d4b45a1SMarkus Koch }
1175d4b45a1SMarkus Koch input_sync(fsia6b->dev);
1185d4b45a1SMarkus Koch } else {
1195d4b45a1SMarkus Koch fsia6b->packet.state = COLLECT;
1205d4b45a1SMarkus Koch }
1215d4b45a1SMarkus Koch break;
1225d4b45a1SMarkus Koch }
1235d4b45a1SMarkus Koch
1245d4b45a1SMarkus Koch return IRQ_HANDLED;
1255d4b45a1SMarkus Koch }
1265d4b45a1SMarkus Koch
fsia6b_serio_connect(struct serio * serio,struct serio_driver * drv)1275d4b45a1SMarkus Koch static int fsia6b_serio_connect(struct serio *serio, struct serio_driver *drv)
1285d4b45a1SMarkus Koch {
1295d4b45a1SMarkus Koch struct fsia6b *fsia6b;
1305d4b45a1SMarkus Koch struct input_dev *input_dev;
1315d4b45a1SMarkus Koch int err;
1325d4b45a1SMarkus Koch int i, j;
1335d4b45a1SMarkus Koch int sw_id = 0;
1345d4b45a1SMarkus Koch
1355d4b45a1SMarkus Koch fsia6b = kzalloc(sizeof(*fsia6b), GFP_KERNEL);
1365d4b45a1SMarkus Koch if (!fsia6b)
1375d4b45a1SMarkus Koch return -ENOMEM;
1385d4b45a1SMarkus Koch
1395d4b45a1SMarkus Koch fsia6b->packet.ibuf = 0;
1405d4b45a1SMarkus Koch fsia6b->packet.offset = 0;
1415d4b45a1SMarkus Koch fsia6b->packet.state = SYNC;
1425d4b45a1SMarkus Koch
1435d4b45a1SMarkus Koch serio_set_drvdata(serio, fsia6b);
1445d4b45a1SMarkus Koch
1455d4b45a1SMarkus Koch input_dev = input_allocate_device();
1465d4b45a1SMarkus Koch if (!input_dev) {
1475d4b45a1SMarkus Koch err = -ENOMEM;
1485d4b45a1SMarkus Koch goto fail1;
1495d4b45a1SMarkus Koch }
1505d4b45a1SMarkus Koch fsia6b->dev = input_dev;
1515d4b45a1SMarkus Koch
1525d4b45a1SMarkus Koch snprintf(fsia6b->phys, sizeof(fsia6b->phys), "%s/input0", serio->phys);
1535d4b45a1SMarkus Koch
1545d4b45a1SMarkus Koch input_dev->name = DRIVER_DESC;
1555d4b45a1SMarkus Koch input_dev->phys = fsia6b->phys;
1565d4b45a1SMarkus Koch input_dev->id.bustype = BUS_RS232;
1575d4b45a1SMarkus Koch input_dev->id.vendor = SERIO_FSIA6B;
1585d4b45a1SMarkus Koch input_dev->id.product = serio->id.id;
1595d4b45a1SMarkus Koch input_dev->id.version = 0x0100;
1605d4b45a1SMarkus Koch input_dev->dev.parent = &serio->dev;
1615d4b45a1SMarkus Koch
1625d4b45a1SMarkus Koch for (i = 0; i < IBUS_SERVO_COUNT; i++)
1635d4b45a1SMarkus Koch input_set_abs_params(input_dev, fsia6b_axes[i],
1645d4b45a1SMarkus Koch 1000, 2000, 2, 2);
1655d4b45a1SMarkus Koch
1665d4b45a1SMarkus Koch /* Register switch configuration */
1675d4b45a1SMarkus Koch for (i = 0; i < IBUS_SERVO_COUNT; i++) {
1685d4b45a1SMarkus Koch if (switch_config[i] < '0' || switch_config[i] > '3') {
1695d4b45a1SMarkus Koch dev_err(&fsia6b->dev->dev,
1705d4b45a1SMarkus Koch "Invalid switch configuration supplied for fsia6b.\n");
1715d4b45a1SMarkus Koch err = -EINVAL;
1725d4b45a1SMarkus Koch goto fail2;
1735d4b45a1SMarkus Koch }
1745d4b45a1SMarkus Koch
1755d4b45a1SMarkus Koch for (j = '1'; j <= switch_config[i]; j++) {
1765d4b45a1SMarkus Koch input_set_capability(input_dev, EV_KEY, BTN_0 + sw_id);
1775d4b45a1SMarkus Koch sw_id++;
1785d4b45a1SMarkus Koch }
1795d4b45a1SMarkus Koch }
1805d4b45a1SMarkus Koch
1815d4b45a1SMarkus Koch err = serio_open(serio, drv);
1825d4b45a1SMarkus Koch if (err)
1835d4b45a1SMarkus Koch goto fail2;
1845d4b45a1SMarkus Koch
1855d4b45a1SMarkus Koch err = input_register_device(fsia6b->dev);
1865d4b45a1SMarkus Koch if (err)
1875d4b45a1SMarkus Koch goto fail3;
1885d4b45a1SMarkus Koch
1895d4b45a1SMarkus Koch return 0;
1905d4b45a1SMarkus Koch
1915d4b45a1SMarkus Koch fail3: serio_close(serio);
1925d4b45a1SMarkus Koch fail2: input_free_device(input_dev);
1935d4b45a1SMarkus Koch fail1: serio_set_drvdata(serio, NULL);
1945d4b45a1SMarkus Koch kfree(fsia6b);
1955d4b45a1SMarkus Koch return err;
1965d4b45a1SMarkus Koch }
1975d4b45a1SMarkus Koch
fsia6b_serio_disconnect(struct serio * serio)1985d4b45a1SMarkus Koch static void fsia6b_serio_disconnect(struct serio *serio)
1995d4b45a1SMarkus Koch {
2005d4b45a1SMarkus Koch struct fsia6b *fsia6b = serio_get_drvdata(serio);
2015d4b45a1SMarkus Koch
2025d4b45a1SMarkus Koch serio_close(serio);
2035d4b45a1SMarkus Koch serio_set_drvdata(serio, NULL);
2045d4b45a1SMarkus Koch input_unregister_device(fsia6b->dev);
2055d4b45a1SMarkus Koch kfree(fsia6b);
2065d4b45a1SMarkus Koch }
2075d4b45a1SMarkus Koch
2085d4b45a1SMarkus Koch static const struct serio_device_id fsia6b_serio_ids[] = {
2095d4b45a1SMarkus Koch {
2105d4b45a1SMarkus Koch .type = SERIO_RS232,
2115d4b45a1SMarkus Koch .proto = SERIO_FSIA6B,
2125d4b45a1SMarkus Koch .id = SERIO_ANY,
2135d4b45a1SMarkus Koch .extra = SERIO_ANY,
2145d4b45a1SMarkus Koch },
2155d4b45a1SMarkus Koch { 0 }
2165d4b45a1SMarkus Koch };
2175d4b45a1SMarkus Koch
2185d4b45a1SMarkus Koch MODULE_DEVICE_TABLE(serio, fsia6b_serio_ids);
2195d4b45a1SMarkus Koch
2205d4b45a1SMarkus Koch static struct serio_driver fsia6b_serio_drv = {
2215d4b45a1SMarkus Koch .driver = {
2225d4b45a1SMarkus Koch .name = "fsia6b"
2235d4b45a1SMarkus Koch },
2245d4b45a1SMarkus Koch .description = DRIVER_DESC,
2255d4b45a1SMarkus Koch .id_table = fsia6b_serio_ids,
2265d4b45a1SMarkus Koch .interrupt = fsia6b_serio_irq,
2275d4b45a1SMarkus Koch .connect = fsia6b_serio_connect,
2285d4b45a1SMarkus Koch .disconnect = fsia6b_serio_disconnect
2295d4b45a1SMarkus Koch };
2305d4b45a1SMarkus Koch
2315d4b45a1SMarkus Koch module_serio_driver(fsia6b_serio_drv)
232