Path: blob/master/drivers/input/joystick/walkera0701.c
15111 views
/*1* Parallel port to Walkera WK-0701 TX joystick2*3* Copyright (c) 2008 Peter Popovec4*5* More about driver: <file:Documentation/input/walkera0701.txt>6*/78/*9* This program is free software; you can redistribute it and/or modify it10* under the terms of the GNU General Public License version 2 as published by11* the Free Software Foundation.12*/1314/* #define WK0701_DEBUG */1516#define RESERVE 2000017#define SYNC_PULSE 130600018#define BIN0_PULSE 28800019#define BIN1_PULSE 4380002021#define ANALOG_MIN_PULSE 31800022#define ANALOG_MAX_PULSE 87800023#define ANALOG_DELTA 800002425#define BIN_SAMPLE ((BIN0_PULSE + BIN1_PULSE) / 2)2627#define NO_SYNC 252829#include <linux/kernel.h>30#include <linux/module.h>31#include <linux/parport.h>32#include <linux/input.h>33#include <linux/hrtimer.h>3435MODULE_AUTHOR("Peter Popovec <[email protected]>");36MODULE_DESCRIPTION("Walkera WK-0701 TX as joystick");37MODULE_LICENSE("GPL");3839static unsigned int walkera0701_pp_no;40module_param_named(port, walkera0701_pp_no, int, 0);41MODULE_PARM_DESC(port,42"Parallel port adapter for Walkera WK-0701 TX (default is 0)");4344/*45* For now, only one device is supported, if somebody need more devices, code46* can be expanded, one struct walkera_dev per device must be allocated and47* set up by walkera0701_connect (release of device by walkera0701_disconnect)48*/4950struct walkera_dev {51unsigned char buf[25];52u64 irq_time, irq_lasttime;53int counter;54int ack;5556struct input_dev *input_dev;57struct hrtimer timer;5859struct parport *parport;60struct pardevice *pardevice;61};6263static struct walkera_dev w_dev;6465static inline void walkera0701_parse_frame(struct walkera_dev *w)66{67int i;68int val1, val2, val3, val4, val5, val6, val7, val8;69int crc1, crc2;7071for (crc1 = crc2 = i = 0; i < 10; i++) {72crc1 += w->buf[i] & 7;73crc2 += (w->buf[i] & 8) >> 3;74}75if ((w->buf[10] & 7) != (crc1 & 7))76return;77if (((w->buf[10] & 8) >> 3) != (((crc1 >> 3) + crc2) & 1))78return;79for (crc1 = crc2 = 0, i = 11; i < 23; i++) {80crc1 += w->buf[i] & 7;81crc2 += (w->buf[i] & 8) >> 3;82}83if ((w->buf[23] & 7) != (crc1 & 7))84return;85if (((w->buf[23] & 8) >> 3) != (((crc1 >> 3) + crc2) & 1))86return;87val1 = ((w->buf[0] & 7) * 256 + w->buf[1] * 16 + w->buf[2]) >> 2;88val1 *= ((w->buf[0] >> 2) & 2) - 1; /* sign */89val2 = (w->buf[2] & 1) << 8 | (w->buf[3] << 4) | w->buf[4];90val2 *= (w->buf[2] & 2) - 1; /* sign */91val3 = ((w->buf[5] & 7) * 256 + w->buf[6] * 16 + w->buf[7]) >> 2;92val3 *= ((w->buf[5] >> 2) & 2) - 1; /* sign */93val4 = (w->buf[7] & 1) << 8 | (w->buf[8] << 4) | w->buf[9];94val4 *= (w->buf[7] & 2) - 1; /* sign */95val5 = ((w->buf[11] & 7) * 256 + w->buf[12] * 16 + w->buf[13]) >> 2;96val5 *= ((w->buf[11] >> 2) & 2) - 1; /* sign */97val6 = (w->buf[13] & 1) << 8 | (w->buf[14] << 4) | w->buf[15];98val6 *= (w->buf[13] & 2) - 1; /* sign */99val7 = ((w->buf[16] & 7) * 256 + w->buf[17] * 16 + w->buf[18]) >> 2;100val7 *= ((w->buf[16] >> 2) & 2) - 1; /*sign */101val8 = (w->buf[18] & 1) << 8 | (w->buf[19] << 4) | w->buf[20];102val8 *= (w->buf[18] & 2) - 1; /*sign */103104#ifdef WK0701_DEBUG105{106int magic, magic_bit;107magic = (w->buf[21] << 4) | w->buf[22];108magic_bit = (w->buf[24] & 8) >> 3;109printk(KERN_DEBUG110"walkera0701: %4d %4d %4d %4d %4d %4d %4d %4d (magic %2x %d)\n",111val1, val2, val3, val4, val5, val6, val7, val8, magic,112magic_bit);113}114#endif115input_report_abs(w->input_dev, ABS_X, val2);116input_report_abs(w->input_dev, ABS_Y, val1);117input_report_abs(w->input_dev, ABS_Z, val6);118input_report_abs(w->input_dev, ABS_THROTTLE, val3);119input_report_abs(w->input_dev, ABS_RUDDER, val4);120input_report_abs(w->input_dev, ABS_MISC, val7);121input_report_key(w->input_dev, BTN_GEAR_DOWN, val5 > 0);122}123124static inline int read_ack(struct pardevice *p)125{126return parport_read_status(p->port) & 0x40;127}128129/* falling edge, prepare to BIN value calculation */130static void walkera0701_irq_handler(void *handler_data)131{132u64 pulse_time;133struct walkera_dev *w = handler_data;134135w->irq_time = ktime_to_ns(ktime_get());136pulse_time = w->irq_time - w->irq_lasttime;137w->irq_lasttime = w->irq_time;138139/* cancel timer, if in handler or active do resync */140if (unlikely(0 != hrtimer_try_to_cancel(&w->timer))) {141w->counter = NO_SYNC;142return;143}144145if (w->counter < NO_SYNC) {146if (w->ack) {147pulse_time -= BIN1_PULSE;148w->buf[w->counter] = 8;149} else {150pulse_time -= BIN0_PULSE;151w->buf[w->counter] = 0;152}153if (w->counter == 24) { /* full frame */154walkera0701_parse_frame(w);155w->counter = NO_SYNC;156if (abs(pulse_time - SYNC_PULSE) < RESERVE) /* new frame sync */157w->counter = 0;158} else {159if ((pulse_time > (ANALOG_MIN_PULSE - RESERVE)160&& (pulse_time < (ANALOG_MAX_PULSE + RESERVE)))) {161pulse_time -= (ANALOG_MIN_PULSE - RESERVE);162pulse_time = (u32) pulse_time / ANALOG_DELTA; /* overtiping is safe, pulsetime < s32.. */163w->buf[w->counter++] |= (pulse_time & 7);164} else165w->counter = NO_SYNC;166}167} else if (abs(pulse_time - SYNC_PULSE - BIN0_PULSE) <168RESERVE + BIN1_PULSE - BIN0_PULSE) /* frame sync .. */169w->counter = 0;170171hrtimer_start(&w->timer, ktime_set(0, BIN_SAMPLE), HRTIMER_MODE_REL);172}173174static enum hrtimer_restart timer_handler(struct hrtimer175*handle)176{177struct walkera_dev *w;178179w = container_of(handle, struct walkera_dev, timer);180w->ack = read_ack(w->pardevice);181182return HRTIMER_NORESTART;183}184185static int walkera0701_open(struct input_dev *dev)186{187struct walkera_dev *w = input_get_drvdata(dev);188189parport_enable_irq(w->parport);190return 0;191}192193static void walkera0701_close(struct input_dev *dev)194{195struct walkera_dev *w = input_get_drvdata(dev);196197parport_disable_irq(w->parport);198}199200static int walkera0701_connect(struct walkera_dev *w, int parport)201{202int err = -ENODEV;203204w->parport = parport_find_number(parport);205if (w->parport == NULL)206return -ENODEV;207208if (w->parport->irq == -1) {209printk(KERN_ERR "walkera0701: parport without interrupt\n");210goto init_err;211}212213err = -EBUSY;214w->pardevice = parport_register_device(w->parport, "walkera0701",215NULL, NULL, walkera0701_irq_handler,216PARPORT_DEV_EXCL, w);217if (!w->pardevice)218goto init_err;219220if (parport_negotiate(w->pardevice->port, IEEE1284_MODE_COMPAT))221goto init_err1;222223if (parport_claim(w->pardevice))224goto init_err1;225226w->input_dev = input_allocate_device();227if (!w->input_dev)228goto init_err2;229230input_set_drvdata(w->input_dev, w);231w->input_dev->name = "Walkera WK-0701 TX";232w->input_dev->phys = w->parport->name;233w->input_dev->id.bustype = BUS_PARPORT;234235/* TODO what id vendor/product/version ? */236w->input_dev->id.vendor = 0x0001;237w->input_dev->id.product = 0x0001;238w->input_dev->id.version = 0x0100;239w->input_dev->open = walkera0701_open;240w->input_dev->close = walkera0701_close;241242w->input_dev->evbit[0] = BIT(EV_ABS) | BIT_MASK(EV_KEY);243w->input_dev->keybit[BIT_WORD(BTN_GEAR_DOWN)] = BIT_MASK(BTN_GEAR_DOWN);244245input_set_abs_params(w->input_dev, ABS_X, -512, 512, 0, 0);246input_set_abs_params(w->input_dev, ABS_Y, -512, 512, 0, 0);247input_set_abs_params(w->input_dev, ABS_Z, -512, 512, 0, 0);248input_set_abs_params(w->input_dev, ABS_THROTTLE, -512, 512, 0, 0);249input_set_abs_params(w->input_dev, ABS_RUDDER, -512, 512, 0, 0);250input_set_abs_params(w->input_dev, ABS_MISC, -512, 512, 0, 0);251252err = input_register_device(w->input_dev);253if (err)254goto init_err3;255256hrtimer_init(&w->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);257w->timer.function = timer_handler;258return 0;259260init_err3:261input_free_device(w->input_dev);262init_err2:263parport_release(w->pardevice);264init_err1:265parport_unregister_device(w->pardevice);266init_err:267parport_put_port(w->parport);268return err;269}270271static void walkera0701_disconnect(struct walkera_dev *w)272{273hrtimer_cancel(&w->timer);274input_unregister_device(w->input_dev);275parport_release(w->pardevice);276parport_unregister_device(w->pardevice);277parport_put_port(w->parport);278}279280static int __init walkera0701_init(void)281{282return walkera0701_connect(&w_dev, walkera0701_pp_no);283}284285static void __exit walkera0701_exit(void)286{287walkera0701_disconnect(&w_dev);288}289290module_init(walkera0701_init);291module_exit(walkera0701_exit);292293294