162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci *  Parallel port to Walkera WK-0701 TX joystick
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci *  Copyright (c) 2008 Peter Popovec
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci *  More about driver:  <file:Documentation/input/devices/walkera0701.rst>
862306a36Sopenharmony_ci */
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci
1162306a36Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
1262306a36Sopenharmony_ci
1362306a36Sopenharmony_ci#define RESERVE 20000
1462306a36Sopenharmony_ci#define SYNC_PULSE 1306000
1562306a36Sopenharmony_ci#define BIN0_PULSE 288000
1662306a36Sopenharmony_ci#define BIN1_PULSE 438000
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ci#define ANALOG_MIN_PULSE 318000
1962306a36Sopenharmony_ci#define ANALOG_MAX_PULSE 878000
2062306a36Sopenharmony_ci#define ANALOG_DELTA 80000
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_ci#define BIN_SAMPLE ((BIN0_PULSE + BIN1_PULSE) / 2)
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_ci#define NO_SYNC 25
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_ci#include <linux/kernel.h>
2762306a36Sopenharmony_ci#include <linux/module.h>
2862306a36Sopenharmony_ci#include <linux/parport.h>
2962306a36Sopenharmony_ci#include <linux/input.h>
3062306a36Sopenharmony_ci#include <linux/hrtimer.h>
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_ciMODULE_AUTHOR("Peter Popovec <popovec@fei.tuke.sk>");
3362306a36Sopenharmony_ciMODULE_DESCRIPTION("Walkera WK-0701 TX as joystick");
3462306a36Sopenharmony_ciMODULE_LICENSE("GPL");
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_cistatic unsigned int walkera0701_pp_no;
3762306a36Sopenharmony_cimodule_param_named(port, walkera0701_pp_no, int, 0);
3862306a36Sopenharmony_ciMODULE_PARM_DESC(port,
3962306a36Sopenharmony_ci		 "Parallel port adapter for Walkera WK-0701 TX (default is 0)");
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_ci/*
4262306a36Sopenharmony_ci * For now, only one device is supported, if somebody need more devices, code
4362306a36Sopenharmony_ci * can be expanded, one struct walkera_dev per device must be allocated and
4462306a36Sopenharmony_ci * set up by walkera0701_connect (release of device by walkera0701_disconnect)
4562306a36Sopenharmony_ci */
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_cistruct walkera_dev {
4862306a36Sopenharmony_ci	unsigned char buf[25];
4962306a36Sopenharmony_ci	u64 irq_time, irq_lasttime;
5062306a36Sopenharmony_ci	int counter;
5162306a36Sopenharmony_ci	int ack;
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_ci	struct input_dev *input_dev;
5462306a36Sopenharmony_ci	struct hrtimer timer;
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_ci	struct parport *parport;
5762306a36Sopenharmony_ci	struct pardevice *pardevice;
5862306a36Sopenharmony_ci};
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_cistatic struct walkera_dev w_dev;
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_cistatic inline void walkera0701_parse_frame(struct walkera_dev *w)
6362306a36Sopenharmony_ci{
6462306a36Sopenharmony_ci	int i;
6562306a36Sopenharmony_ci	int val1, val2, val3, val4, val5, val6, val7, val8;
6662306a36Sopenharmony_ci	int magic, magic_bit;
6762306a36Sopenharmony_ci	int crc1, crc2;
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_ci	for (crc1 = crc2 = i = 0; i < 10; i++) {
7062306a36Sopenharmony_ci		crc1 += w->buf[i] & 7;
7162306a36Sopenharmony_ci		crc2 += (w->buf[i] & 8) >> 3;
7262306a36Sopenharmony_ci	}
7362306a36Sopenharmony_ci	if ((w->buf[10] & 7) != (crc1 & 7))
7462306a36Sopenharmony_ci		return;
7562306a36Sopenharmony_ci	if (((w->buf[10] & 8) >> 3) != (((crc1 >> 3) + crc2) & 1))
7662306a36Sopenharmony_ci		return;
7762306a36Sopenharmony_ci	for (crc1 = crc2 = 0, i = 11; i < 23; i++) {
7862306a36Sopenharmony_ci		crc1 += w->buf[i] & 7;
7962306a36Sopenharmony_ci		crc2 += (w->buf[i] & 8) >> 3;
8062306a36Sopenharmony_ci	}
8162306a36Sopenharmony_ci	if ((w->buf[23] & 7) != (crc1 & 7))
8262306a36Sopenharmony_ci		return;
8362306a36Sopenharmony_ci	if (((w->buf[23] & 8) >> 3) != (((crc1 >> 3) + crc2) & 1))
8462306a36Sopenharmony_ci		return;
8562306a36Sopenharmony_ci	val1 = ((w->buf[0] & 7) * 256 + w->buf[1] * 16 + w->buf[2]) >> 2;
8662306a36Sopenharmony_ci	val1 *= ((w->buf[0] >> 2) & 2) - 1;	/* sign */
8762306a36Sopenharmony_ci	val2 = (w->buf[2] & 1) << 8 | (w->buf[3] << 4) | w->buf[4];
8862306a36Sopenharmony_ci	val2 *= (w->buf[2] & 2) - 1;	/* sign */
8962306a36Sopenharmony_ci	val3 = ((w->buf[5] & 7) * 256 + w->buf[6] * 16 + w->buf[7]) >> 2;
9062306a36Sopenharmony_ci	val3 *= ((w->buf[5] >> 2) & 2) - 1;	/* sign */
9162306a36Sopenharmony_ci	val4 = (w->buf[7] & 1) << 8 | (w->buf[8] << 4) | w->buf[9];
9262306a36Sopenharmony_ci	val4 *= (w->buf[7] & 2) - 1;	/* sign */
9362306a36Sopenharmony_ci	val5 = ((w->buf[11] & 7) * 256 + w->buf[12] * 16 + w->buf[13]) >> 2;
9462306a36Sopenharmony_ci	val5 *= ((w->buf[11] >> 2) & 2) - 1;	/* sign */
9562306a36Sopenharmony_ci	val6 = (w->buf[13] & 1) << 8 | (w->buf[14] << 4) | w->buf[15];
9662306a36Sopenharmony_ci	val6 *= (w->buf[13] & 2) - 1;	/* sign */
9762306a36Sopenharmony_ci	val7 = ((w->buf[16] & 7) * 256 + w->buf[17] * 16 + w->buf[18]) >> 2;
9862306a36Sopenharmony_ci	val7 *= ((w->buf[16] >> 2) & 2) - 1;	/*sign */
9962306a36Sopenharmony_ci	val8 = (w->buf[18] & 1) << 8 | (w->buf[19] << 4) | w->buf[20];
10062306a36Sopenharmony_ci	val8 *= (w->buf[18] & 2) - 1;	/*sign */
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_ci	magic = (w->buf[21] << 4) | w->buf[22];
10362306a36Sopenharmony_ci	magic_bit = (w->buf[24] & 8) >> 3;
10462306a36Sopenharmony_ci	pr_debug("%4d %4d %4d %4d  %4d %4d %4d %4d (magic %2x %d)\n",
10562306a36Sopenharmony_ci		 val1, val2, val3, val4, val5, val6, val7, val8,
10662306a36Sopenharmony_ci		 magic, magic_bit);
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_ci	input_report_abs(w->input_dev, ABS_X, val2);
10962306a36Sopenharmony_ci	input_report_abs(w->input_dev, ABS_Y, val1);
11062306a36Sopenharmony_ci	input_report_abs(w->input_dev, ABS_Z, val6);
11162306a36Sopenharmony_ci	input_report_abs(w->input_dev, ABS_THROTTLE, val3);
11262306a36Sopenharmony_ci	input_report_abs(w->input_dev, ABS_RUDDER, val4);
11362306a36Sopenharmony_ci	input_report_abs(w->input_dev, ABS_MISC, val7);
11462306a36Sopenharmony_ci	input_report_key(w->input_dev, BTN_GEAR_DOWN, val5 > 0);
11562306a36Sopenharmony_ci}
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_cistatic inline int read_ack(struct pardevice *p)
11862306a36Sopenharmony_ci{
11962306a36Sopenharmony_ci	return parport_read_status(p->port) & 0x40;
12062306a36Sopenharmony_ci}
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_ci/* falling edge, prepare to BIN value calculation */
12362306a36Sopenharmony_cistatic void walkera0701_irq_handler(void *handler_data)
12462306a36Sopenharmony_ci{
12562306a36Sopenharmony_ci	u64 pulse_time;
12662306a36Sopenharmony_ci	struct walkera_dev *w = handler_data;
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci	w->irq_time = ktime_to_ns(ktime_get());
12962306a36Sopenharmony_ci	pulse_time = w->irq_time - w->irq_lasttime;
13062306a36Sopenharmony_ci	w->irq_lasttime = w->irq_time;
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_ci	/* cancel timer, if in handler or active do resync */
13362306a36Sopenharmony_ci	if (unlikely(0 != hrtimer_try_to_cancel(&w->timer))) {
13462306a36Sopenharmony_ci		w->counter = NO_SYNC;
13562306a36Sopenharmony_ci		return;
13662306a36Sopenharmony_ci	}
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_ci	if (w->counter < NO_SYNC) {
13962306a36Sopenharmony_ci		if (w->ack) {
14062306a36Sopenharmony_ci			pulse_time -= BIN1_PULSE;
14162306a36Sopenharmony_ci			w->buf[w->counter] = 8;
14262306a36Sopenharmony_ci		} else {
14362306a36Sopenharmony_ci			pulse_time -= BIN0_PULSE;
14462306a36Sopenharmony_ci			w->buf[w->counter] = 0;
14562306a36Sopenharmony_ci		}
14662306a36Sopenharmony_ci		if (w->counter == 24) {	/* full frame */
14762306a36Sopenharmony_ci			walkera0701_parse_frame(w);
14862306a36Sopenharmony_ci			w->counter = NO_SYNC;
14962306a36Sopenharmony_ci			if (abs(pulse_time - SYNC_PULSE) < RESERVE)	/* new frame sync */
15062306a36Sopenharmony_ci				w->counter = 0;
15162306a36Sopenharmony_ci		} else {
15262306a36Sopenharmony_ci			if ((pulse_time > (ANALOG_MIN_PULSE - RESERVE)
15362306a36Sopenharmony_ci			     && (pulse_time < (ANALOG_MAX_PULSE + RESERVE)))) {
15462306a36Sopenharmony_ci				pulse_time -= (ANALOG_MIN_PULSE - RESERVE);
15562306a36Sopenharmony_ci				pulse_time = (u32) pulse_time / ANALOG_DELTA;	/* overtiping is safe, pulsetime < s32.. */
15662306a36Sopenharmony_ci				w->buf[w->counter++] |= (pulse_time & 7);
15762306a36Sopenharmony_ci			} else
15862306a36Sopenharmony_ci				w->counter = NO_SYNC;
15962306a36Sopenharmony_ci		}
16062306a36Sopenharmony_ci	} else if (abs(pulse_time - SYNC_PULSE - BIN0_PULSE) <
16162306a36Sopenharmony_ci				RESERVE + BIN1_PULSE - BIN0_PULSE)	/* frame sync .. */
16262306a36Sopenharmony_ci		w->counter = 0;
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_ci	hrtimer_start(&w->timer, BIN_SAMPLE, HRTIMER_MODE_REL);
16562306a36Sopenharmony_ci}
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_cistatic enum hrtimer_restart timer_handler(struct hrtimer
16862306a36Sopenharmony_ci					  *handle)
16962306a36Sopenharmony_ci{
17062306a36Sopenharmony_ci	struct walkera_dev *w;
17162306a36Sopenharmony_ci
17262306a36Sopenharmony_ci	w = container_of(handle, struct walkera_dev, timer);
17362306a36Sopenharmony_ci	w->ack = read_ack(w->pardevice);
17462306a36Sopenharmony_ci
17562306a36Sopenharmony_ci	return HRTIMER_NORESTART;
17662306a36Sopenharmony_ci}
17762306a36Sopenharmony_ci
17862306a36Sopenharmony_cistatic int walkera0701_open(struct input_dev *dev)
17962306a36Sopenharmony_ci{
18062306a36Sopenharmony_ci	struct walkera_dev *w = input_get_drvdata(dev);
18162306a36Sopenharmony_ci
18262306a36Sopenharmony_ci	if (parport_claim(w->pardevice))
18362306a36Sopenharmony_ci		return -EBUSY;
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_ci	parport_enable_irq(w->parport);
18662306a36Sopenharmony_ci	return 0;
18762306a36Sopenharmony_ci}
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_cistatic void walkera0701_close(struct input_dev *dev)
19062306a36Sopenharmony_ci{
19162306a36Sopenharmony_ci	struct walkera_dev *w = input_get_drvdata(dev);
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_ci	parport_disable_irq(w->parport);
19462306a36Sopenharmony_ci	hrtimer_cancel(&w->timer);
19562306a36Sopenharmony_ci
19662306a36Sopenharmony_ci	parport_release(w->pardevice);
19762306a36Sopenharmony_ci}
19862306a36Sopenharmony_ci
19962306a36Sopenharmony_cistatic void walkera0701_attach(struct parport *pp)
20062306a36Sopenharmony_ci{
20162306a36Sopenharmony_ci	struct pardev_cb walkera0701_parport_cb;
20262306a36Sopenharmony_ci	struct walkera_dev *w = &w_dev;
20362306a36Sopenharmony_ci
20462306a36Sopenharmony_ci	if (pp->number != walkera0701_pp_no) {
20562306a36Sopenharmony_ci		pr_debug("Not using parport%d.\n", pp->number);
20662306a36Sopenharmony_ci		return;
20762306a36Sopenharmony_ci	}
20862306a36Sopenharmony_ci
20962306a36Sopenharmony_ci	if (pp->irq == -1) {
21062306a36Sopenharmony_ci		pr_err("parport %d does not have interrupt assigned\n",
21162306a36Sopenharmony_ci			pp->number);
21262306a36Sopenharmony_ci		return;
21362306a36Sopenharmony_ci	}
21462306a36Sopenharmony_ci
21562306a36Sopenharmony_ci	w->parport = pp;
21662306a36Sopenharmony_ci
21762306a36Sopenharmony_ci	memset(&walkera0701_parport_cb, 0, sizeof(walkera0701_parport_cb));
21862306a36Sopenharmony_ci	walkera0701_parport_cb.flags = PARPORT_FLAG_EXCL;
21962306a36Sopenharmony_ci	walkera0701_parport_cb.irq_func = walkera0701_irq_handler;
22062306a36Sopenharmony_ci	walkera0701_parport_cb.private = w;
22162306a36Sopenharmony_ci
22262306a36Sopenharmony_ci	w->pardevice = parport_register_dev_model(pp, "walkera0701",
22362306a36Sopenharmony_ci						  &walkera0701_parport_cb, 0);
22462306a36Sopenharmony_ci
22562306a36Sopenharmony_ci	if (!w->pardevice) {
22662306a36Sopenharmony_ci		pr_err("failed to register parport device\n");
22762306a36Sopenharmony_ci		return;
22862306a36Sopenharmony_ci	}
22962306a36Sopenharmony_ci
23062306a36Sopenharmony_ci	if (parport_negotiate(w->pardevice->port, IEEE1284_MODE_COMPAT)) {
23162306a36Sopenharmony_ci		pr_err("failed to negotiate parport mode\n");
23262306a36Sopenharmony_ci		goto err_unregister_device;
23362306a36Sopenharmony_ci	}
23462306a36Sopenharmony_ci
23562306a36Sopenharmony_ci	hrtimer_init(&w->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
23662306a36Sopenharmony_ci	w->timer.function = timer_handler;
23762306a36Sopenharmony_ci
23862306a36Sopenharmony_ci	w->input_dev = input_allocate_device();
23962306a36Sopenharmony_ci	if (!w->input_dev) {
24062306a36Sopenharmony_ci		pr_err("failed to allocate input device\n");
24162306a36Sopenharmony_ci		goto err_unregister_device;
24262306a36Sopenharmony_ci	}
24362306a36Sopenharmony_ci
24462306a36Sopenharmony_ci	input_set_drvdata(w->input_dev, w);
24562306a36Sopenharmony_ci	w->input_dev->name = "Walkera WK-0701 TX";
24662306a36Sopenharmony_ci	w->input_dev->phys = w->parport->name;
24762306a36Sopenharmony_ci	w->input_dev->id.bustype = BUS_PARPORT;
24862306a36Sopenharmony_ci
24962306a36Sopenharmony_ci	/* TODO what id vendor/product/version ? */
25062306a36Sopenharmony_ci	w->input_dev->id.vendor = 0x0001;
25162306a36Sopenharmony_ci	w->input_dev->id.product = 0x0001;
25262306a36Sopenharmony_ci	w->input_dev->id.version = 0x0100;
25362306a36Sopenharmony_ci	w->input_dev->dev.parent = w->parport->dev;
25462306a36Sopenharmony_ci	w->input_dev->open = walkera0701_open;
25562306a36Sopenharmony_ci	w->input_dev->close = walkera0701_close;
25662306a36Sopenharmony_ci
25762306a36Sopenharmony_ci	w->input_dev->evbit[0] = BIT(EV_ABS) | BIT_MASK(EV_KEY);
25862306a36Sopenharmony_ci	w->input_dev->keybit[BIT_WORD(BTN_GEAR_DOWN)] = BIT_MASK(BTN_GEAR_DOWN);
25962306a36Sopenharmony_ci
26062306a36Sopenharmony_ci	input_set_abs_params(w->input_dev, ABS_X, -512, 512, 0, 0);
26162306a36Sopenharmony_ci	input_set_abs_params(w->input_dev, ABS_Y, -512, 512, 0, 0);
26262306a36Sopenharmony_ci	input_set_abs_params(w->input_dev, ABS_Z, -512, 512, 0, 0);
26362306a36Sopenharmony_ci	input_set_abs_params(w->input_dev, ABS_THROTTLE, -512, 512, 0, 0);
26462306a36Sopenharmony_ci	input_set_abs_params(w->input_dev, ABS_RUDDER, -512, 512, 0, 0);
26562306a36Sopenharmony_ci	input_set_abs_params(w->input_dev, ABS_MISC, -512, 512, 0, 0);
26662306a36Sopenharmony_ci
26762306a36Sopenharmony_ci	if (input_register_device(w->input_dev)) {
26862306a36Sopenharmony_ci		pr_err("failed to register input device\n");
26962306a36Sopenharmony_ci		goto err_free_input_dev;
27062306a36Sopenharmony_ci	}
27162306a36Sopenharmony_ci
27262306a36Sopenharmony_ci	return;
27362306a36Sopenharmony_ci
27462306a36Sopenharmony_cierr_free_input_dev:
27562306a36Sopenharmony_ci	input_free_device(w->input_dev);
27662306a36Sopenharmony_cierr_unregister_device:
27762306a36Sopenharmony_ci	parport_unregister_device(w->pardevice);
27862306a36Sopenharmony_ci}
27962306a36Sopenharmony_ci
28062306a36Sopenharmony_cistatic void walkera0701_detach(struct parport *port)
28162306a36Sopenharmony_ci{
28262306a36Sopenharmony_ci	struct walkera_dev *w = &w_dev;
28362306a36Sopenharmony_ci
28462306a36Sopenharmony_ci	if (!w->pardevice || w->parport->number != port->number)
28562306a36Sopenharmony_ci		return;
28662306a36Sopenharmony_ci
28762306a36Sopenharmony_ci	input_unregister_device(w->input_dev);
28862306a36Sopenharmony_ci	parport_unregister_device(w->pardevice);
28962306a36Sopenharmony_ci	w->parport = NULL;
29062306a36Sopenharmony_ci}
29162306a36Sopenharmony_ci
29262306a36Sopenharmony_cistatic struct parport_driver walkera0701_parport_driver = {
29362306a36Sopenharmony_ci	.name = "walkera0701",
29462306a36Sopenharmony_ci	.match_port = walkera0701_attach,
29562306a36Sopenharmony_ci	.detach = walkera0701_detach,
29662306a36Sopenharmony_ci	.devmodel = true,
29762306a36Sopenharmony_ci};
29862306a36Sopenharmony_ci
29962306a36Sopenharmony_cistatic int __init walkera0701_init(void)
30062306a36Sopenharmony_ci{
30162306a36Sopenharmony_ci	return parport_register_driver(&walkera0701_parport_driver);
30262306a36Sopenharmony_ci}
30362306a36Sopenharmony_ci
30462306a36Sopenharmony_cistatic void __exit walkera0701_exit(void)
30562306a36Sopenharmony_ci{
30662306a36Sopenharmony_ci	parport_unregister_driver(&walkera0701_parport_driver);
30762306a36Sopenharmony_ci}
30862306a36Sopenharmony_ci
30962306a36Sopenharmony_cimodule_init(walkera0701_init);
31062306a36Sopenharmony_cimodule_exit(walkera0701_exit);
311