Input: add joystick driver for Walkera WK-0701 RC transmitter

Signed-off-by: Peter Popovec <popovec@fei.tuke.sk>
Signed-off-by: Dmitry Torokhov <dtor@mail.ru>
This commit is contained in:
Peter Popovec 2008-11-11 14:46:14 -05:00 committed by Dmitry Torokhov
parent 59bdb43769
commit cec87e38e9
4 changed files with 414 additions and 0 deletions

View File

@ -0,0 +1,109 @@
Walkera WK-0701 transmitter is supplied with a ready to fly Walkera
helicopters such as HM36, HM37, HM60. The walkera0701 module enables to use
this transmitter as joystick
Devel homepage and download:
http://zub.fei.tuke.sk/walkera-wk0701/
or use cogito:
cg-clone http://zub.fei.tuke.sk/GIT/walkera0701-joystick
Connecting to PC:
At back side of transmitter S-video connector can be found. Modulation
pulses from processor to HF part can be found at pin 2 of this connector,
pin 3 is GND. Between pin 3 and CPU 5k6 resistor can be found. To get
modulation pulses to PC, signal pulses must be amplified.
Cable: (walkera TX to parport)
Walkera WK-0701 TX S-VIDEO connector:
(back side of TX)
__ __ S-video: canon25
/ |_| \ pin 2 (signal) NPN parport
/ O 4 3 O \ pin 3 (GND) LED ________________ 10 ACK
( O 2 1 O ) | C
\ ___ / 2 ________________________|\|_____|/
| [___] | |/| B |\
------- 3 __________________________________|________________ 25 GND
E
I use green LED and BC109 NPN transistor.
Software:
Build kernel with walkera0701 module. Module walkera0701 need exclusive
access to parport, modules like lp must be unloaded before loading
walkera0701 module, check dmesg for error messages. Connect TX to PC by
cable and run jstest /dev/input/js0 to see values from TX. If no value can
be changed by TX "joystick", check output from /proc/interrupts. Value for
(usually irq7) parport must increase if TX is on.
Technical details:
Driver use interrupt from parport ACK input bit to measure pulse length
using hrtimers.
Frame format:
Based on walkera WK-0701 PCM Format description by Shaul Eizikovich.
(downloaded from http://www.smartpropoplus.com/Docs/Walkera_Wk-0701_PCM.pdf)
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
(Warning, pulses on ACK ar inverted by transistor, irq is rised up on sync
to bin change or octal value to bin change).
Binary data representations:
One binary and octal value can be grouped to nibble. 24 nibbles + one binary
values can be sampled between sync pulses.
Values for first four channels (analog joystick values) can be found in
first 10 nibbles. Analog value is represented by one sign bit and 9 bit
absolute binary value. (10 bits per channel). Next nibble is checksum for
first ten nibbles.
Next nibbles 12 .. 21 represents four channels (not all channels can be
directly controlled from TX). Binary representations ar the same as in first
four channels. In nibbles 22 and 23 is a special magic number. Nibble 24 is
checksum for nibbles 12..23.
After last octal value for nibble 24 and next sync pulse one additional
binary value can be sampled. This bit and magic number is not used in
software driver. Some details about this magic numbers can be found in
Walkera_Wk-0701_PCM.pdf.
Checksum calculation:
Summary of octal values in nibbles must be same as octal value in checksum
nibble (only first 3 bits are used). Binary value for checksum nibble is
calculated by sum of binary values in checked nibbles + sum of octal values
in checked nibbles divided by 8. Only bit 0 of this sum is used.

View File

@ -294,4 +294,16 @@ config JOYSTICK_XPAD_LEDS
This option enables support for the LED which surrounds the Big X on
XBox 360 controller.
config JOYSTICK_WALKERA0701
tristate "Walkera WK-0701 RC transmitter"
depends on HIGH_RES_TIMERS && PARPORT
help
Say Y or M here if you have a Walkera WK-0701 transmitter which is
supplied with a ready to fly Walkera helicopters such as HM36,
HM37, HM60 and want to use it via parport as a joystick. More
information is available: <file:Documentation/input/walkera0701.txt>
To compile this driver as a module, choose M here: the
module will be called walkera0701.
endif

View File

@ -29,4 +29,5 @@ obj-$(CONFIG_JOYSTICK_TWIDJOY) += twidjoy.o
obj-$(CONFIG_JOYSTICK_WARRIOR) += warrior.o
obj-$(CONFIG_JOYSTICK_XPAD) += xpad.o
obj-$(CONFIG_JOYSTICK_ZHENHUA) += zhenhua.o
obj-$(CONFIG_JOYSTICK_WALKERA0701) += walkera0701.o

View File

@ -0,0 +1,292 @@
/*
* Parallel port to Walkera WK-0701 TX joystick
*
* Copyright (c) 2008 Peter Popovec
*
* More about driver: <file:Documentation/input/walkera0701.txt>
*/
/*
* 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.
*/
/* #define WK0701_DEBUG */
#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>
MODULE_AUTHOR("Peter Popovec <popovec@fei.tuke.sk>");
MODULE_DESCRIPTION("Walkera WK-0701 TX as joystick");
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)");
/*
* For now, only one device is supported, if somebody need more devices, code
* can be expanded, one struct walkera_dev per device must be allocated and
* set up by walkera0701_connect (release of device by walkera0701_disconnect)
*/
struct walkera_dev {
unsigned char buf[25];
u64 irq_time, irq_lasttime;
int counter;
int ack;
struct input_dev *input_dev;
struct hrtimer timer;
struct parport *parport;
struct pardevice *pardevice;
};
static struct walkera_dev w_dev;
static inline void walkera0701_parse_frame(struct walkera_dev *w)
{
int i;
int val1, val2, val3, val4, val5, val6, val7, val8;
int crc1, crc2;
for (crc1 = crc2 = i = 0; i < 10; i++) {
crc1 += w->buf[i] & 7;
crc2 += (w->buf[i] & 8) >> 3;
}
if ((w->buf[10] & 7) != (crc1 & 7))
return;
if (((w->buf[10] & 8) >> 3) != (((crc1 >> 3) + crc2) & 1))
return;
for (crc1 = crc2 = 0, i = 11; i < 23; i++) {
crc1 += w->buf[i] & 7;
crc2 += (w->buf[i] & 8) >> 3;
}
if ((w->buf[23] & 7) != (crc1 & 7))
return;
if (((w->buf[23] & 8) >> 3) != (((crc1 >> 3) + crc2) & 1))
return;
val1 = ((w->buf[0] & 7) * 256 + w->buf[1] * 16 + w->buf[2]) >> 2;
val1 *= ((w->buf[0] >> 2) & 2) - 1; /* sign */
val2 = (w->buf[2] & 1) << 8 | (w->buf[3] << 4) | w->buf[4];
val2 *= (w->buf[2] & 2) - 1; /* sign */
val3 = ((w->buf[5] & 7) * 256 + w->buf[6] * 16 + w->buf[7]) >> 2;
val3 *= ((w->buf[5] >> 2) & 2) - 1; /* sign */
val4 = (w->buf[7] & 1) << 8 | (w->buf[8] << 4) | w->buf[9];
val4 *= (w->buf[7] & 2) - 1; /* sign */
val5 = ((w->buf[11] & 7) * 256 + w->buf[12] * 16 + w->buf[13]) >> 2;
val5 *= ((w->buf[11] >> 2) & 2) - 1; /* sign */
val6 = (w->buf[13] & 1) << 8 | (w->buf[14] << 4) | w->buf[15];
val6 *= (w->buf[13] & 2) - 1; /* sign */
val7 = ((w->buf[16] & 7) * 256 + w->buf[17] * 16 + w->buf[18]) >> 2;
val7 *= ((w->buf[16] >> 2) & 2) - 1; /*sign */
val8 = (w->buf[18] & 1) << 8 | (w->buf[19] << 4) | w->buf[20];
val8 *= (w->buf[18] & 2) - 1; /*sign */
#ifdef WK0701_DEBUG
{
int magic, magic_bit;
magic = (w->buf[21] << 4) | w->buf[22];
magic_bit = (w->buf[24] & 8) >> 3;
printk(KERN_DEBUG
"walkera0701: %4d %4d %4d %4d %4d %4d %4d %4d (magic %2x %d)\n",
val1, val2, val3, val4, val5, val6, val7, val8, magic,
magic_bit);
}
#endif
input_report_abs(w->input_dev, ABS_X, val2);
input_report_abs(w->input_dev, ABS_Y, val1);
input_report_abs(w->input_dev, ABS_Z, val6);
input_report_abs(w->input_dev, ABS_THROTTLE, val3);
input_report_abs(w->input_dev, ABS_RUDDER, val4);
input_report_abs(w->input_dev, ABS_MISC, val7);
input_report_key(w->input_dev, BTN_GEAR_DOWN, val5 > 0);
}
static inline int read_ack(struct pardevice *p)
{
return parport_read_status(p->port) & 0x40;
}
/* falling edge, prepare to BIN value calculation */
static void walkera0701_irq_handler(void *handler_data)
{
u64 pulse_time;
struct walkera_dev *w = handler_data;
w->irq_time = ktime_to_ns(ktime_get());
pulse_time = w->irq_time - w->irq_lasttime;
w->irq_lasttime = w->irq_time;
/* cancel timer, if in handler or active do resync */
if (unlikely(0 != hrtimer_try_to_cancel(&w->timer))) {
w->counter = NO_SYNC;
return;
}
if (w->counter < NO_SYNC) {
if (w->ack) {
pulse_time -= BIN1_PULSE;
w->buf[w->counter] = 8;
} else {
pulse_time -= BIN0_PULSE;
w->buf[w->counter] = 0;
}
if (w->counter == 24) { /* full frame */
walkera0701_parse_frame(w);
w->counter = NO_SYNC;
if (abs(pulse_time - SYNC_PULSE) < RESERVE) /* new frame sync */
w->counter = 0;
} else {
if ((pulse_time > (ANALOG_MIN_PULSE - RESERVE)
&& (pulse_time < (ANALOG_MAX_PULSE + RESERVE)))) {
pulse_time -= (ANALOG_MIN_PULSE - RESERVE);
pulse_time = (u32) pulse_time / ANALOG_DELTA; /* overtiping is safe, pulsetime < s32.. */
w->buf[w->counter++] |= (pulse_time & 7);
} else
w->counter = NO_SYNC;
}
} else if (abs(pulse_time - SYNC_PULSE - BIN0_PULSE) <
RESERVE + BIN1_PULSE - BIN0_PULSE) /* frame sync .. */
w->counter = 0;
hrtimer_start(&w->timer, ktime_set(0, BIN_SAMPLE), HRTIMER_MODE_REL);
}
static enum hrtimer_restart timer_handler(struct hrtimer
*handle)
{
struct walkera_dev *w;
w = container_of(handle, struct walkera_dev, timer);
w->ack = read_ack(w->pardevice);
return HRTIMER_NORESTART;
}
static int walkera0701_open(struct input_dev *dev)
{
struct walkera_dev *w = input_get_drvdata(dev);
parport_enable_irq(w->parport);
return 0;
}
static void walkera0701_close(struct input_dev *dev)
{
struct walkera_dev *w = input_get_drvdata(dev);
parport_disable_irq(w->parport);
}
static int walkera0701_connect(struct walkera_dev *w, int parport)
{
int err = -ENODEV;
w->parport = parport_find_number(parport);
if (w->parport == NULL)
return -ENODEV;
if (w->parport->irq == -1) {
printk(KERN_ERR "walkera0701: parport without interrupt\n");
goto init_err;
}
err = -EBUSY;
w->pardevice = parport_register_device(w->parport, "walkera0701",
NULL, NULL, walkera0701_irq_handler,
PARPORT_DEV_EXCL, w);
if (!w->pardevice)
goto init_err;
if (parport_negotiate(w->pardevice->port, IEEE1284_MODE_COMPAT))
goto init_err1;
if (parport_claim(w->pardevice))
goto init_err1;
w->input_dev = input_allocate_device();
if (!w->input_dev)
goto init_err2;
input_set_drvdata(w->input_dev, w);
w->input_dev->name = "Walkera WK-0701 TX";
w->input_dev->phys = w->parport->name;
w->input_dev->id.bustype = BUS_PARPORT;
/* TODO what id vendor/product/version ? */
w->input_dev->id.vendor = 0x0001;
w->input_dev->id.product = 0x0001;
w->input_dev->id.version = 0x0100;
w->input_dev->open = walkera0701_open;
w->input_dev->close = walkera0701_close;
w->input_dev->evbit[0] = BIT(EV_ABS) | BIT_MASK(EV_KEY);
w->input_dev->keybit[BIT_WORD(BTN_GEAR_DOWN)] = BIT_MASK(BTN_GEAR_DOWN);
input_set_abs_params(w->input_dev, ABS_X, -512, 512, 0, 0);
input_set_abs_params(w->input_dev, ABS_Y, -512, 512, 0, 0);
input_set_abs_params(w->input_dev, ABS_Z, -512, 512, 0, 0);
input_set_abs_params(w->input_dev, ABS_THROTTLE, -512, 512, 0, 0);
input_set_abs_params(w->input_dev, ABS_RUDDER, -512, 512, 0, 0);
input_set_abs_params(w->input_dev, ABS_MISC, -512, 512, 0, 0);
err = input_register_device(w->input_dev);
if (err)
goto init_err3;
hrtimer_init(&w->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
w->timer.function = timer_handler;
return 0;
init_err3:
input_free_device(w->input_dev);
init_err2:
parport_release(w->pardevice);
init_err1:
parport_unregister_device(w->pardevice);
init_err:
parport_put_port(w->parport);
return err;
}
static void walkera0701_disconnect(struct walkera_dev *w)
{
hrtimer_cancel(&w->timer);
input_unregister_device(w->input_dev);
parport_release(w->pardevice);
parport_unregister_device(w->pardevice);
parport_put_port(w->parport);
}
static int __init walkera0701_init(void)
{
return walkera0701_connect(&w_dev, walkera0701_pp_no);
}
static void __exit walkera0701_exit(void)
{
walkera0701_disconnect(&w_dev);
}
module_init(walkera0701_init);
module_exit(walkera0701_exit);