xref: /OK3568_Linux_fs/kernel/drivers/input/joystick/fsia6b.c (revision 4882a59341e53eb6f0b4789bf948001014eff981)
1*4882a593Smuzhiyun // SPDX-License-Identifier: GPL-2.0
2*4882a593Smuzhiyun /*
3*4882a593Smuzhiyun  * FS-iA6B iBus RC receiver driver
4*4882a593Smuzhiyun  *
5*4882a593Smuzhiyun  * This driver provides all 14 channels of the FlySky FS-ia6B RC receiver
6*4882a593Smuzhiyun  * as analog values.
7*4882a593Smuzhiyun  *
8*4882a593Smuzhiyun  * Additionally, the channels can be converted to discrete switch values.
9*4882a593Smuzhiyun  * By default, it is configured for the offical FS-i6 remote control.
10*4882a593Smuzhiyun  * If you use a different hardware configuration, you can configure it
11*4882a593Smuzhiyun  * using the `switch_config` parameter.
12*4882a593Smuzhiyun  */
13*4882a593Smuzhiyun 
14*4882a593Smuzhiyun #include <linux/device.h>
15*4882a593Smuzhiyun #include <linux/input.h>
16*4882a593Smuzhiyun #include <linux/kernel.h>
17*4882a593Smuzhiyun #include <linux/module.h>
18*4882a593Smuzhiyun #include <linux/serio.h>
19*4882a593Smuzhiyun #include <linux/slab.h>
20*4882a593Smuzhiyun #include <linux/types.h>
21*4882a593Smuzhiyun 
22*4882a593Smuzhiyun #define DRIVER_DESC		"FS-iA6B iBus RC receiver"
23*4882a593Smuzhiyun 
24*4882a593Smuzhiyun MODULE_AUTHOR("Markus Koch <markus@notsyncing.net>");
25*4882a593Smuzhiyun MODULE_DESCRIPTION(DRIVER_DESC);
26*4882a593Smuzhiyun MODULE_LICENSE("GPL");
27*4882a593Smuzhiyun 
28*4882a593Smuzhiyun #define IBUS_SERVO_COUNT	14
29*4882a593Smuzhiyun 
30*4882a593Smuzhiyun static char *switch_config = "00000022320000";
31*4882a593Smuzhiyun module_param(switch_config, charp, 0444);
32*4882a593Smuzhiyun MODULE_PARM_DESC(switch_config,
33*4882a593Smuzhiyun 		 "Amount of switch positions per channel (14 characters, 0-3)");
34*4882a593Smuzhiyun 
35*4882a593Smuzhiyun static int fsia6b_axes[IBUS_SERVO_COUNT] = {
36*4882a593Smuzhiyun 	ABS_X, ABS_Y,
37*4882a593Smuzhiyun 	ABS_Z, ABS_RX,
38*4882a593Smuzhiyun 	ABS_RY, ABS_RZ,
39*4882a593Smuzhiyun 	ABS_HAT0X, ABS_HAT0Y,
40*4882a593Smuzhiyun 	ABS_HAT1X, ABS_HAT1Y,
41*4882a593Smuzhiyun 	ABS_HAT2X, ABS_HAT2Y,
42*4882a593Smuzhiyun 	ABS_HAT3X, ABS_HAT3Y
43*4882a593Smuzhiyun };
44*4882a593Smuzhiyun 
45*4882a593Smuzhiyun enum ibus_state { SYNC, COLLECT, PROCESS };
46*4882a593Smuzhiyun 
47*4882a593Smuzhiyun struct ibus_packet {
48*4882a593Smuzhiyun 	enum ibus_state state;
49*4882a593Smuzhiyun 
50*4882a593Smuzhiyun 	int offset;
51*4882a593Smuzhiyun 	u16 ibuf;
52*4882a593Smuzhiyun 	u16 channel[IBUS_SERVO_COUNT];
53*4882a593Smuzhiyun };
54*4882a593Smuzhiyun 
55*4882a593Smuzhiyun struct fsia6b {
56*4882a593Smuzhiyun 	struct input_dev *dev;
57*4882a593Smuzhiyun 	struct ibus_packet packet;
58*4882a593Smuzhiyun 
59*4882a593Smuzhiyun 	char phys[32];
60*4882a593Smuzhiyun };
61*4882a593Smuzhiyun 
fsia6b_serio_irq(struct serio * serio,unsigned char data,unsigned int flags)62*4882a593Smuzhiyun static irqreturn_t fsia6b_serio_irq(struct serio *serio,
63*4882a593Smuzhiyun 				    unsigned char data, unsigned int flags)
64*4882a593Smuzhiyun {
65*4882a593Smuzhiyun 	struct fsia6b *fsia6b = serio_get_drvdata(serio);
66*4882a593Smuzhiyun 	int i;
67*4882a593Smuzhiyun 	int sw_state;
68*4882a593Smuzhiyun 	int sw_id = BTN_0;
69*4882a593Smuzhiyun 
70*4882a593Smuzhiyun 	fsia6b->packet.ibuf = (data << 8) | ((fsia6b->packet.ibuf >> 8) & 0xFF);
71*4882a593Smuzhiyun 
72*4882a593Smuzhiyun 	switch (fsia6b->packet.state) {
73*4882a593Smuzhiyun 	case SYNC:
74*4882a593Smuzhiyun 		if (fsia6b->packet.ibuf == 0x4020)
75*4882a593Smuzhiyun 			fsia6b->packet.state = COLLECT;
76*4882a593Smuzhiyun 		break;
77*4882a593Smuzhiyun 
78*4882a593Smuzhiyun 	case COLLECT:
79*4882a593Smuzhiyun 		fsia6b->packet.state = PROCESS;
80*4882a593Smuzhiyun 		break;
81*4882a593Smuzhiyun 
82*4882a593Smuzhiyun 	case PROCESS:
83*4882a593Smuzhiyun 		fsia6b->packet.channel[fsia6b->packet.offset] =
84*4882a593Smuzhiyun 				fsia6b->packet.ibuf;
85*4882a593Smuzhiyun 		fsia6b->packet.offset++;
86*4882a593Smuzhiyun 
87*4882a593Smuzhiyun 		if (fsia6b->packet.offset == IBUS_SERVO_COUNT) {
88*4882a593Smuzhiyun 			fsia6b->packet.offset = 0;
89*4882a593Smuzhiyun 			fsia6b->packet.state = SYNC;
90*4882a593Smuzhiyun 			for (i = 0; i < IBUS_SERVO_COUNT; ++i) {
91*4882a593Smuzhiyun 				input_report_abs(fsia6b->dev, fsia6b_axes[i],
92*4882a593Smuzhiyun 						 fsia6b->packet.channel[i]);
93*4882a593Smuzhiyun 
94*4882a593Smuzhiyun 				sw_state = 0;
95*4882a593Smuzhiyun 				if (fsia6b->packet.channel[i] > 1900)
96*4882a593Smuzhiyun 					sw_state = 1;
97*4882a593Smuzhiyun 				else if (fsia6b->packet.channel[i] < 1100)
98*4882a593Smuzhiyun 					sw_state = 2;
99*4882a593Smuzhiyun 
100*4882a593Smuzhiyun 				switch (switch_config[i]) {
101*4882a593Smuzhiyun 				case '3':
102*4882a593Smuzhiyun 					input_report_key(fsia6b->dev,
103*4882a593Smuzhiyun 							 sw_id++,
104*4882a593Smuzhiyun 							 sw_state == 0);
105*4882a593Smuzhiyun 					fallthrough;
106*4882a593Smuzhiyun 				case '2':
107*4882a593Smuzhiyun 					input_report_key(fsia6b->dev,
108*4882a593Smuzhiyun 							 sw_id++,
109*4882a593Smuzhiyun 							 sw_state == 1);
110*4882a593Smuzhiyun 					fallthrough;
111*4882a593Smuzhiyun 				case '1':
112*4882a593Smuzhiyun 					input_report_key(fsia6b->dev,
113*4882a593Smuzhiyun 							 sw_id++,
114*4882a593Smuzhiyun 							 sw_state == 2);
115*4882a593Smuzhiyun 				}
116*4882a593Smuzhiyun 			}
117*4882a593Smuzhiyun 			input_sync(fsia6b->dev);
118*4882a593Smuzhiyun 		} else {
119*4882a593Smuzhiyun 			fsia6b->packet.state = COLLECT;
120*4882a593Smuzhiyun 		}
121*4882a593Smuzhiyun 		break;
122*4882a593Smuzhiyun 	}
123*4882a593Smuzhiyun 
124*4882a593Smuzhiyun 	return IRQ_HANDLED;
125*4882a593Smuzhiyun }
126*4882a593Smuzhiyun 
fsia6b_serio_connect(struct serio * serio,struct serio_driver * drv)127*4882a593Smuzhiyun static int fsia6b_serio_connect(struct serio *serio, struct serio_driver *drv)
128*4882a593Smuzhiyun {
129*4882a593Smuzhiyun 	struct fsia6b *fsia6b;
130*4882a593Smuzhiyun 	struct input_dev *input_dev;
131*4882a593Smuzhiyun 	int err;
132*4882a593Smuzhiyun 	int i, j;
133*4882a593Smuzhiyun 	int sw_id = 0;
134*4882a593Smuzhiyun 
135*4882a593Smuzhiyun 	fsia6b = kzalloc(sizeof(*fsia6b), GFP_KERNEL);
136*4882a593Smuzhiyun 	if (!fsia6b)
137*4882a593Smuzhiyun 		return -ENOMEM;
138*4882a593Smuzhiyun 
139*4882a593Smuzhiyun 	fsia6b->packet.ibuf = 0;
140*4882a593Smuzhiyun 	fsia6b->packet.offset = 0;
141*4882a593Smuzhiyun 	fsia6b->packet.state = SYNC;
142*4882a593Smuzhiyun 
143*4882a593Smuzhiyun 	serio_set_drvdata(serio, fsia6b);
144*4882a593Smuzhiyun 
145*4882a593Smuzhiyun 	input_dev = input_allocate_device();
146*4882a593Smuzhiyun 	if (!input_dev) {
147*4882a593Smuzhiyun 		err = -ENOMEM;
148*4882a593Smuzhiyun 		goto fail1;
149*4882a593Smuzhiyun 	}
150*4882a593Smuzhiyun 	fsia6b->dev = input_dev;
151*4882a593Smuzhiyun 
152*4882a593Smuzhiyun 	snprintf(fsia6b->phys, sizeof(fsia6b->phys), "%s/input0", serio->phys);
153*4882a593Smuzhiyun 
154*4882a593Smuzhiyun 	input_dev->name = DRIVER_DESC;
155*4882a593Smuzhiyun 	input_dev->phys = fsia6b->phys;
156*4882a593Smuzhiyun 	input_dev->id.bustype = BUS_RS232;
157*4882a593Smuzhiyun 	input_dev->id.vendor = SERIO_FSIA6B;
158*4882a593Smuzhiyun 	input_dev->id.product = serio->id.id;
159*4882a593Smuzhiyun 	input_dev->id.version = 0x0100;
160*4882a593Smuzhiyun 	input_dev->dev.parent = &serio->dev;
161*4882a593Smuzhiyun 
162*4882a593Smuzhiyun 	for (i = 0; i < IBUS_SERVO_COUNT; i++)
163*4882a593Smuzhiyun 		input_set_abs_params(input_dev, fsia6b_axes[i],
164*4882a593Smuzhiyun 				     1000, 2000, 2, 2);
165*4882a593Smuzhiyun 
166*4882a593Smuzhiyun 	/* Register switch configuration */
167*4882a593Smuzhiyun 	for (i = 0; i < IBUS_SERVO_COUNT; i++) {
168*4882a593Smuzhiyun 		if (switch_config[i] < '0' || switch_config[i] > '3') {
169*4882a593Smuzhiyun 			dev_err(&fsia6b->dev->dev,
170*4882a593Smuzhiyun 				"Invalid switch configuration supplied for fsia6b.\n");
171*4882a593Smuzhiyun 			err = -EINVAL;
172*4882a593Smuzhiyun 			goto fail2;
173*4882a593Smuzhiyun 		}
174*4882a593Smuzhiyun 
175*4882a593Smuzhiyun 		for (j = '1'; j <= switch_config[i]; j++) {
176*4882a593Smuzhiyun 			input_set_capability(input_dev, EV_KEY, BTN_0 + sw_id);
177*4882a593Smuzhiyun 			sw_id++;
178*4882a593Smuzhiyun 		}
179*4882a593Smuzhiyun 	}
180*4882a593Smuzhiyun 
181*4882a593Smuzhiyun 	err = serio_open(serio, drv);
182*4882a593Smuzhiyun 	if (err)
183*4882a593Smuzhiyun 		goto fail2;
184*4882a593Smuzhiyun 
185*4882a593Smuzhiyun 	err = input_register_device(fsia6b->dev);
186*4882a593Smuzhiyun 	if (err)
187*4882a593Smuzhiyun 		goto fail3;
188*4882a593Smuzhiyun 
189*4882a593Smuzhiyun 	return 0;
190*4882a593Smuzhiyun 
191*4882a593Smuzhiyun fail3:	serio_close(serio);
192*4882a593Smuzhiyun fail2:	input_free_device(input_dev);
193*4882a593Smuzhiyun fail1:	serio_set_drvdata(serio, NULL);
194*4882a593Smuzhiyun 	kfree(fsia6b);
195*4882a593Smuzhiyun 	return err;
196*4882a593Smuzhiyun }
197*4882a593Smuzhiyun 
fsia6b_serio_disconnect(struct serio * serio)198*4882a593Smuzhiyun static void fsia6b_serio_disconnect(struct serio *serio)
199*4882a593Smuzhiyun {
200*4882a593Smuzhiyun 	struct fsia6b *fsia6b = serio_get_drvdata(serio);
201*4882a593Smuzhiyun 
202*4882a593Smuzhiyun 	serio_close(serio);
203*4882a593Smuzhiyun 	serio_set_drvdata(serio, NULL);
204*4882a593Smuzhiyun 	input_unregister_device(fsia6b->dev);
205*4882a593Smuzhiyun 	kfree(fsia6b);
206*4882a593Smuzhiyun }
207*4882a593Smuzhiyun 
208*4882a593Smuzhiyun static const struct serio_device_id fsia6b_serio_ids[] = {
209*4882a593Smuzhiyun 	{
210*4882a593Smuzhiyun 		.type	= SERIO_RS232,
211*4882a593Smuzhiyun 		.proto	= SERIO_FSIA6B,
212*4882a593Smuzhiyun 		.id	= SERIO_ANY,
213*4882a593Smuzhiyun 		.extra	= SERIO_ANY,
214*4882a593Smuzhiyun 	},
215*4882a593Smuzhiyun 	{ 0 }
216*4882a593Smuzhiyun };
217*4882a593Smuzhiyun 
218*4882a593Smuzhiyun MODULE_DEVICE_TABLE(serio, fsia6b_serio_ids);
219*4882a593Smuzhiyun 
220*4882a593Smuzhiyun static struct serio_driver fsia6b_serio_drv = {
221*4882a593Smuzhiyun 	.driver		= {
222*4882a593Smuzhiyun 		.name	= "fsia6b"
223*4882a593Smuzhiyun 	},
224*4882a593Smuzhiyun 	.description	= DRIVER_DESC,
225*4882a593Smuzhiyun 	.id_table	= fsia6b_serio_ids,
226*4882a593Smuzhiyun 	.interrupt	= fsia6b_serio_irq,
227*4882a593Smuzhiyun 	.connect	= fsia6b_serio_connect,
228*4882a593Smuzhiyun 	.disconnect	= fsia6b_serio_disconnect
229*4882a593Smuzhiyun };
230*4882a593Smuzhiyun 
231*4882a593Smuzhiyun module_serio_driver(fsia6b_serio_drv)
232