--- /dev/null
+/*
+ * Parallel port to Walkera WK-0701 TX joystick
+ *
+ * Copyright (c) 2008 Peter Popovec
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+*/
+
+/*
+Cable: (walkera TX to parralel port)
+
+Walkera 0701 TX S-VIDEO connector:
+ (back side of TX)
+ __ __
+ / |_| \ connect pin 2 (signal) canon25
+ / O 4 3 O \ connect pin 3 (GND) LED NPN transistor
+ ( O 2 1 O ) / ---------------10
+ \ ___ / 2 o-----------------------|>|-----|
+ | [___] | V\
+ ------- 3 o --------------------------------v-------------- 25
+
+Frame format:
+Based on walkera WK-0701 PCM Format description by Shaul Eizikovich.
+
+Signal pulses:
+ (ANALOG)
+ SYNC BIN OCT
+ +---------+ +------+
+ | | | |
+--+ +------+ +---
+
+Frame:
+ SYNC , BIN1, OCT1, BIN2, OCT2 ... BIN24, OCT24, BIN25, next frame SYNC ..
+
+pulse length:
+ Binary values: Analog octal values:
+
+ 288 uS Binary 0 318 uS 000
+ 438 uS Binary 1 398 uS 001
+ 478 uS 010
+ 558 uS 011
+ 638 uS 100
+ 1306 uS SYNC 718 uS 101
+ 798 uS 110
+ 878 uS 111
+
+24 bin+oct values + 1 bin value = 24*4+1 bits = 97 bits
+
+all times in code in ns
+*/
+#define RESERVE 20000
+#define SYNC_PULSE 1306000
+#define BIN0_PULSE 288000
+#define BIN1_PULSE 438000
+
+#define ANALOG_MIN_PULSE 318000
+#define ANALOG_MAX_PULSE 878000
+#define ANALOG_DELTA 80000
+
+#define BIN_SAMPLE ((BIN0_PULSE + BIN1_PULSE) / 2)
+
+#define NO_SYNC 25
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/parport.h>
+#include <linux/input.h>
+#include <linux/hrtimer.h>
+
+#define DRIVER_DESC "Walkera WK-0701 TX as joystick"
+
+MODULE_AUTHOR("Peter Popovec <popovec@fei.tuke.sk>");
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
+
+static unsigned int walkera0701_pp_no;
+module_param_named(port, walkera0701_pp_no, int, 0);
+MODULE_PARM_DESC(port,
+ "Parallel port adapter for walkera WK-0701 TX (default is 0)");
+
+static unsigned char DATA[25];
+static unsigned char counter;
+static struct hrtimer walkera0701_timer;
+
+static u64 IRQtime, IRQlasttime;
+
+struct input_dev *input_dev;
+struct parport *walkera0701_parport;
+struct pardevice *walkera0701_pardevice;
+
+//TODO real paket parsing
+static inline void walkera0701_parse_frame(void)
+{
+ int i;
+ int val1, val2, val3, val4, val5, val6, val7, val8, magic;
+ int crc1, crc2;
+ for (crc1 = crc2 = i = 0; i < 10; i++) {
+ crc1 += DATA[i] & 7;
+ crc2 += (DATA[i] & 8) >> 3;
+ }
+ if ((DATA[10] & 7) != (crc1 & 7))
+ printk(KERN_INFO "Bad OCT 1-4 checksum\n");
+ if (((DATA[10] & 8) >> 3) != (((crc1 >> 3) + crc2) & 1))
+ printk(KERN_INFO "Bad BIN - OCT 1-4 checksum\n");
+
+ for (crc1 = crc2 = 0, i = 11; i < 23; i++) {
+ crc1 += DATA[i] & 7;
+ crc2 += (DATA[i] & 8) >> 3;
+ }
+ if ((DATA[23] & 7) != (crc1 & 7))
+ printk(KERN_INFO "Bad OCT 5-8 checksum\n");
+ if (((DATA[23] & 8) >> 3) != (((crc1 >> 3) + crc2) & 1))
+ printk(KERN_INFO "Bad BIN - OCT 5-8 checksum\n");
+
+ val1 = ((DATA[0] & 7) * 256 + DATA[1] * 16 + DATA[2]) >> 2;
+ val1 *= ((DATA[0] >> 2) & 2) - 1; //sign
+ val2 = (DATA[2] & 1) << 8 | (DATA[3] << 4) | DATA[4];
+ val2 *= (DATA[2] & 2) - 1; //sign
+ val3 = ((DATA[5] & 7) * 256 + DATA[6] * 16 + DATA[7]) >> 2;
+ val3 *= ((DATA[5] >> 2) & 2) - 1; //sign
+ val4 = (DATA[7] & 1) << 8 | (DATA[8] << 4) | DATA[9];
+ val4 *= (DATA[7] & 2) - 1; //sign
+ val5 = ((DATA[11] & 7) * 256 + DATA[12] * 16 + DATA[13]) >> 2;
+ val5 *= ((DATA[11] >> 2) & 2) - 1; //sign
+ val6 = (DATA[13] & 1) << 8 | (DATA[14] << 4) | DATA[15];
+ val6 *= (DATA[13] & 2) - 1; //sign
+ val7 = ((DATA[16] & 7) * 256 + DATA[17] * 16 + DATA[18]) >> 2;
+ val7 *= ((DATA[16] >> 2) & 2) - 1; //sign
+ val8 = (DATA[18] & 1) << 8 | (DATA[19] << 4) | DATA[20];
+ val8 *= (DATA[18] & 2) - 1; //sign
+
+ magic = (DATA[21] << 4) | DATA[22];
+/* printk(KERN_INFO "%4d %4d %4d %4d %4d %4d %4d %4d (magic %2x %d)\n",
+ val1, val2, val3, val4, val5, val6, val7, val8, magic,
+ (DATA[24] & 8) >> 3);
+*/
+ input_report_abs(input_dev, ABS_X, val2);
+ input_report_abs(input_dev, ABS_Y, val1);
+ input_report_abs(input_dev, ABS_THROTTLE, val3);
+ input_report_abs(input_dev, ABS_RUDDER, val4);
+ input_report_abs(input_dev, ABS_MISC, val7);
+ input_report_key(input_dev,BTN_GEAR_DOWN,val5 >0);
+}
+
+static int ACK;
+static inline int read_ack(void)
+{
+ return (parport_read_status(walkera0701_pardevice->port) & 0x40);
+}
+
+// falling edge, prepare to BIN value calculation
+static void walkera0701_irq_handler(void *dev_id)
+{
+ u64 pulseTime;
+ IRQtime = ktime_to_ns(ktime_get());
+ pulseTime = IRQtime - IRQlasttime;
+ IRQlasttime = IRQtime;
+ //cancel timer,if in handler or active do resync
+ if (unlikely(0 != hrtimer_try_to_cancel(&walkera0701_timer))) {
+ counter = NO_SYNC;
+ return;
+ }
+ if (unlikely(counter < NO_SYNC)) {
+ if (ACK) {
+ pulseTime -= BIN1_PULSE;
+ DATA[counter] = 8;
+ } else {
+ pulseTime -= BIN0_PULSE;
+ DATA[counter] = 0;
+ }
+ if (unlikely(counter == 24)) { //full frame
+ walkera0701_parse_frame();
+ counter = NO_SYNC;
+ if (likely(abs(pulseTime - SYNC_PULSE) < RESERVE)) //new frame sync
+ counter = 0;
+ } else {
+ if (likely((pulseTime > (ANALOG_MIN_PULSE - RESERVE))
+ && (pulseTime <
+ (ANALOG_MAX_PULSE + RESERVE)))) {
+ pulseTime -= (ANALOG_MIN_PULSE - RESERVE);
+ pulseTime = (u32) pulseTime / ANALOG_DELTA; //overtiping is safe, pulsetime < s32..
+ DATA[counter++] |= (pulseTime & 7);
+ } else
+ counter = NO_SYNC;
+ }
+ } else {
+ if (unlikely(abs(pulseTime - SYNC_PULSE - BIN0_PULSE) < (RESERVE + BIN1_PULSE - BIN0_PULSE))) //frame sync ..
+ counter = 0;
+ }
+ hrtimer_start(&walkera0701_timer, ktime_set(0, BIN_SAMPLE),
+ HRTIMER_MODE_REL);
+
+ return;
+}
+
+static enum hrtimer_restart walkera0701_timer_handler(struct hrtimer
+ *handle)
+{
+ ACK = read_ack();
+ return HRTIMER_NORESTART;
+}
+static int walkera0701_open(struct input_dev *dev)
+{
+ parport_enable_irq(walkera0701_parport);
+ return (0);
+}
+static void walkera0701_close(struct input_dev *dev)
+{
+ parport_disable_irq(walkera0701_parport);
+
+}
+static int walkera0701_connect(int parport)
+{
+ int err;
+ walkera0701_parport = parport_find_number(parport);
+ if (walkera0701_parport == NULL) {
+ printk(KERN_ERR "walkera0701: no such parport\n");
+ return -ENODEV;
+ }
+ if (walkera0701_parport->irq == -1) {
+ printk(KERN_ERR "walkera0701: parport without interrupt\n");
+ return -ENODEV;
+ }
+ walkera0701_pardevice =
+ parport_register_device(walkera0701_parport, "walkera0701",
+ NULL, NULL, walkera0701_irq_handler,
+ PARPORT_DEV_EXCL, NULL);
+ if (!walkera0701_pardevice) {
+ printk(KERN_ERR "walkera0701: parport busy\n");
+ return -EBUSY;
+ }
+ parport_negotiate(walkera0701_pardevice->port, IEEE1284_MODE_COMPAT);
+//TODO needed ?
+// parport_put_port(walkera0701_parport);
+ if (parport_claim(walkera0701_pardevice)) {
+ printk(KERN_ERR
+ "walkera0701: parport unable to claim, busy ?\n");
+ parport_unregister_device(walkera0701_pardevice);
+ return -EBUSY;
+ }
+
+ input_dev = input_allocate_device();
+ if (!input_dev) {
+ printk(KERN_ERR
+ "walkera0701 unable to allocate input device\n");
+ parport_release(walkera0701_pardevice);
+ parport_unregister_device(walkera0701_pardevice);
+ return -ENOMEM;
+ }
+
+ input_dev->name = "Walkera WK-0701 TX";
+ input_dev->phys = walkera0701_parport->name;
+ input_dev->id.bustype = BUS_PARPORT;
+ input_dev->id.vendor = 0x0001; //TODO
+ input_dev->id.product = 0x0001; //TODO
+ input_dev->id.version = 0x0100; //TODO
+ input_dev->open = walkera0701_open;
+ input_dev->close = walkera0701_close;
+
+ input_dev->evbit[0] = BIT(EV_ABS)|BIT_MASK(EV_KEY) ;
+ input_dev->keybit[BIT_WORD(BTN_GEAR_DOWN)]=BIT_MASK(BTN_GEAR_DOWN);
+
+ input_set_abs_params(input_dev, ABS_X, -512, 512, 0, 0);
+ input_set_abs_params(input_dev, ABS_Y, -512, 512, 0, 0);
+ input_set_abs_params(input_dev, ABS_THROTTLE, -512, 512, 0, 0);
+ input_set_abs_params(input_dev, ABS_RUDDER, -512, 512, 0, 0);
+ input_set_abs_params(input_dev, ABS_MISC, -512, 512, 0, 0);
+ err = input_register_device(input_dev);
+ if (err) {
+ input_free_device(input_dev);
+ parport_release(walkera0701_pardevice);
+ parport_unregister_device(walkera0701_pardevice);
+ parport_put_port(walkera0701_parport);
+ return (err);
+ }
+ parport_enable_irq(walkera0701_parport);
+ return 0;
+}
+
+static int __init walkera0701_init(void)
+{
+ hrtimer_init(&walkera0701_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ walkera0701_timer.function = walkera0701_timer_handler;
+ return walkera0701_connect(walkera0701_pp_no);
+}
+static void __exit walkera0701_exit(void)
+{
+ hrtimer_cancel(&walkera0701_timer);
+ input_unregister_device(input_dev);
+ input_free_device(input_dev);
+ if (walkera0701_pardevice) {
+ parport_release(walkera0701_pardevice);
+ parport_unregister_device(walkera0701_pardevice);
+ }
+}
+
+module_init(walkera0701_init);
+module_exit(walkera0701_exit);