xref: /openbmc/linux/drivers/input/joystick/fsia6b.c (revision df561f66)
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