From a4ef90d3c112d2ece4d2d8038bf6e376d1513918 Mon Sep 17 00:00:00 2001 From: Nirenjan Krishnan Date: Mon, 8 Oct 2012 16:21:24 -0700 Subject: [PATCH] Support for input devices - still experimental This change adds an input device for the Saitek X52 pro flight control system. However, with this change, there is some bug which causes a kernel crash. Still debugging, but committing so that I don't lose data. --- driver/Makefile | 2 +- driver/x52joy.c | 77 +++++++++++--- driver/x52joy_common.h | 22 +++- driver/x52joy_input.c | 236 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 323 insertions(+), 14 deletions(-) create mode 100644 driver/x52joy_input.c diff --git a/driver/Makefile b/driver/Makefile index 587a4ea..adf2fea 100644 --- a/driver/Makefile +++ b/driver/Makefile @@ -1,5 +1,5 @@ obj-m := saitek_x52.o -saitek_x52-objs := x52joy.o x52joy_commands.o +saitek_x52-objs := x52joy.o x52joy_commands.o x52joy_input.o KDIR := /lib/modules/$(shell uname -r)/build PWD := $(shell pwd) diff --git a/driver/x52joy.c b/driver/x52joy.c index 3a3be77..dc79f4e 100644 --- a/driver/x52joy.c +++ b/driver/x52joy.c @@ -19,6 +19,8 @@ #include #include #include +#include +#include #include "x52joy_commands.h" #include "x52joy_common.h" @@ -27,16 +29,6 @@ #define DRIVER_DESC "Saitek X52Pro HOTAS Driver" #define DRIVER_VERSION "1.0" -#define X52TYPE_X52 1 -#define X52TYPE_X52PRO 2 -#define X52TYPE_UNKNOWN 0 - -#define VENDOR_ID_SAITEK 0x06a3 -#define PRODUCT_ID_X52_PRO 0x0762 - -#define X52FLAGS_SUPPORTS_MFD (1 << 0) -#define X52FLAGS_SUPPORTS_LED (1 << 1) - static const struct x52_device { u16 idVendor; u16 idProduct; @@ -85,7 +77,8 @@ static ssize_t show_text_line(struct device *dev, char *buf, u8 line) line--; /* Convert to 0-based line number */ if (joy->feat_mfd) { - return sprintf(buf, "%s\n", joy->line[line].text); + //return sprintf(buf, "%s\n", joy->line[line].text); + return sprintf(buf, "%s\n", joy->phys); } else { sprintf(buf, "\n"); return -EOPNOTSUPP; @@ -393,6 +386,8 @@ static int x52_probe(struct usb_interface *intf, { struct usb_device *udev = interface_to_usbdev(intf); struct x52_joy *joy = NULL; + struct input_dev *idev = NULL; + struct usb_endpoint_descriptor *ep_irq_in; int retval = -ENOMEM; int i; @@ -405,12 +400,60 @@ static int x52_probe(struct usb_interface *intf, joy = kzalloc(sizeof(*joy), GFP_KERNEL); if (joy == NULL) { - dev_err(&intf->dev, "Out of memory\n"); + dev_err(&intf->dev, "Out of memory: Cannot create joystick\n"); goto error; } + idev = input_allocate_device(); + if (idev == NULL) { + dev_err(&intf->dev, "Out of memory: Cannot create input\n"); + goto error; + } + + joy->idata = usb_alloc_coherent(udev, X52_PACKET_LEN, GFP_KERNEL, + &joy->idata_dma); + if (!joy->idata) { + dev_err(&intf->dev, "Out of memory: Cannot alloc coherent buffer\n"); + goto error; + } + + joy->irq_in = usb_alloc_urb(0, GFP_KERNEL); + if (!joy->irq_in) { + dev_err(&intf->dev, "Out of memory: Cannot alloc IRQ URB\n"); + goto error1; + } + joy->udev = usb_get_dev(udev); + joy->idev = idev; + usb_make_path(udev, joy->phys, sizeof(joy->phys)); + strlcat(joy->phys, "/input0", sizeof(joy->phys)); + + idev->name = x52_devices[i].name; + idev->phys = joy->phys; + usb_to_input_id(udev, &idev->id); + idev->dev.parent = &intf->dev; + + input_set_drvdata(idev, joy); + + idev->open = x52_open; + idev->close = x52_close; + + x52_setup_input(idev); + + ep_irq_in = &intf->cur_altsetting->endpoint[0].desc; + usb_fill_int_urb(joy->irq_in, udev, + usb_rcvintpipe(udev, ep_irq_in->bEndpointAddress), + joy->idata, X52_PACKET_LEN, x52_irq_handler, + joy, ep_irq_in->bInterval); + joy->irq_in->transfer_dma = joy->idata_dma; + joy->irq_in->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + + retval = input_register_device(joy->idev); + if (retval) { + goto error2; + } + /* Set the feature bits */ joy->feat_mfd = !!(x52_devices[i].flags & X52FLAGS_SUPPORTS_MFD); joy->feat_led = !!(x52_devices[i].flags & X52FLAGS_SUPPORTS_LED); @@ -457,7 +500,12 @@ static int x52_probe(struct usb_interface *intf, dev_info(&intf->dev, "X52 device now attached\n"); return 0; +error2: + usb_free_urb(joy->irq_in); +error1: + usb_free_coherent(udev, X52_PACKET_LEN, joy->idata, joy->idata_dma); error: + input_free_device(idev); kfree(joy); return retval; } @@ -469,6 +517,11 @@ static void x52_disconnect(struct usb_interface *intf) joy = usb_get_intfdata(intf); usb_set_intfdata(intf, NULL); + usb_kill_urb(joy->irq_in); + usb_free_urb(joy->irq_in); + usb_free_coherent(joy->udev, X52_PACKET_LEN, joy->idata, joy->idata_dma); + input_free_device(joy->idev); + if (joy->feat_mfd) { device_remove_file(&intf->dev, &dev_attr_mfd_line1); device_remove_file(&intf->dev, &dev_attr_mfd_line2); diff --git a/driver/x52joy_common.h b/driver/x52joy_common.h index 9d2a855..f385ed7 100644 --- a/driver/x52joy_common.h +++ b/driver/x52joy_common.h @@ -13,6 +13,7 @@ #define X52JOY_COMMON_H #include +#include #include "x52joy_map.h" @@ -20,7 +21,9 @@ struct x52_joy { struct usb_device *udev; struct urb *irq_in; unsigned char *idata; - struct input_dev *dev; + dma_addr_t idata_dma; + struct input_dev *idev; + char phys[64]; u32 led_status; struct x52_mfd_line line[X52_MFD_LINES]; @@ -49,6 +52,18 @@ struct x52_joy { u8 :5; }; +#define X52_PACKET_LEN 16 + +#define X52TYPE_X52 1 +#define X52TYPE_X52PRO 2 +#define X52TYPE_UNKNOWN 0 + +#define VENDOR_ID_SAITEK 0x06a3 +#define PRODUCT_ID_X52_PRO 0x0762 + +#define X52FLAGS_SUPPORTS_MFD (1 << 0) +#define X52FLAGS_SUPPORTS_LED (1 << 1) + int set_text(struct x52_joy *joy, u8 line_no); int set_brightness(struct x52_joy *joy, u8 target); int set_led(struct x52_joy *joy, u8 target); @@ -56,4 +71,9 @@ int set_date(struct x52_joy *joy); int set_shift(struct x52_joy *joy); int set_blink(struct x52_joy *joy); +void x52_irq_handler(struct urb *urb); +void x52_setup_input(struct input_dev *idev); +int x52_open (struct input_dev *idev); +void x52_close (struct input_dev *idev); + #endif /* !defined X52JOY_COMMON_H */ diff --git a/driver/x52joy_input.c b/driver/x52joy_input.c new file mode 100644 index 0000000..11ea0e9 --- /dev/null +++ b/driver/x52joy_input.c @@ -0,0 +1,236 @@ +/* + * Saitek X52 Pro HOTAS driver + * + * Copyright (C) 2012 Nirenjan Krishnan (nirenjan@nirenjan.org) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2. + * + */ + +#ifdef CONFIG_USB_DEBUG + #define DEBUG 1 +#endif + +#include +#include + +#include "x52joy_commands.h" +#include "x52joy_common.h" + +#define X52PRO_X_SHIFT 0 +#define X52PRO_X_MASK 0x3ff +#define X52PRO_Y_SHIFT 10 +#define X52PRO_Y_MASK 0x3ff +#define X52PRO_RZ_SHIFT 20 +#define X52PRO_RZ_MASK 0x3ff + +static void x52pro_decode_urb(struct x52_joy *joy, unsigned char *data) +{ + struct input_dev *idev = joy->idev; + u32 stick_axis = le32_to_cpu(&data[0]); + + input_report_key(idev, BTN_TRIGGER_HAPPY1, data[8] & 0x01); + input_report_key(idev, BTN_TRIGGER_HAPPY2, data[8] & 0x02); + input_report_key(idev, BTN_TRIGGER_HAPPY3, data[8] & 0x04); + input_report_key(idev, BTN_TRIGGER_HAPPY4, data[8] & 0x08); + input_report_key(idev, BTN_TRIGGER_HAPPY5, data[8] & 0x10); + input_report_key(idev, BTN_TRIGGER_HAPPY6, data[8] & 0x20); + input_report_key(idev, BTN_TRIGGER_HAPPY7, data[8] & 0x40); + input_report_key(idev, BTN_TRIGGER_HAPPY8, data[8] & 0x80); + + input_report_key(idev, BTN_TRIGGER_HAPPY9, data[9] & 0x01); + input_report_key(idev, BTN_TRIGGER_HAPPY10, data[9] & 0x02); + input_report_key(idev, BTN_TRIGGER_HAPPY11, data[9] & 0x04); + input_report_key(idev, BTN_TRIGGER_HAPPY12, data[9] & 0x08); + input_report_key(idev, BTN_TRIGGER_HAPPY13, data[9] & 0x10); + input_report_key(idev, BTN_TRIGGER_HAPPY14, data[9] & 0x20); + input_report_key(idev, BTN_TRIGGER_HAPPY15, data[9] & 0x40); + input_report_key(idev, BTN_TRIGGER_HAPPY16, data[9] & 0x80); + + input_report_key(idev, BTN_TRIGGER_HAPPY17, data[10] & 0x01); + input_report_key(idev, BTN_TRIGGER_HAPPY18, data[10] & 0x02); + input_report_key(idev, BTN_TRIGGER_HAPPY19, data[10] & 0x04); + input_report_key(idev, BTN_TRIGGER_HAPPY20, data[10] & 0x08); + input_report_key(idev, BTN_TRIGGER_HAPPY21, data[10] & 0x10); + input_report_key(idev, BTN_TRIGGER_HAPPY22, data[10] & 0x20); + input_report_key(idev, BTN_TRIGGER_HAPPY23, data[10] & 0x40); + input_report_key(idev, BTN_TRIGGER_HAPPY24, data[10] & 0x80); + + input_report_key(idev, BTN_TRIGGER_HAPPY25, data[11] & 0x01); + input_report_key(idev, BTN_TRIGGER_HAPPY26, data[11] & 0x02); + input_report_key(idev, BTN_TRIGGER_HAPPY27, data[11] & 0x04); + input_report_key(idev, BTN_TRIGGER_HAPPY28, data[11] & 0x08); + input_report_key(idev, BTN_TRIGGER_HAPPY29, data[11] & 0x10); + input_report_key(idev, BTN_TRIGGER_HAPPY30, data[11] & 0x20); + input_report_key(idev, BTN_TRIGGER_HAPPY31, data[11] & 0x40); + input_report_key(idev, BTN_TRIGGER_HAPPY32, data[11] & 0x80); + + input_report_key(idev, BTN_TRIGGER_HAPPY33, data[12] & 0x01); + input_report_key(idev, BTN_TRIGGER_HAPPY34, data[12] & 0x02); + input_report_key(idev, BTN_TRIGGER_HAPPY35, data[12] & 0x04); + input_report_key(idev, BTN_TRIGGER_HAPPY36, data[12] & 0x08); + input_report_key(idev, BTN_TRIGGER_HAPPY37, data[12] & 0x10); + input_report_key(idev, BTN_TRIGGER_HAPPY38, data[12] & 0x20); + input_report_key(idev, BTN_TRIGGER_HAPPY39, data[12] & 0x40); + + input_report_abs(idev, ABS_X, + (stick_axis >> X52PRO_X_SHIFT) & X52PRO_X_MASK); + input_report_abs(idev, ABS_Y, + (stick_axis >> X52PRO_Y_SHIFT) & X52PRO_Y_MASK); + input_report_abs(idev, ABS_RZ, + (stick_axis >> X52PRO_RZ_SHIFT) & X52PRO_RZ_MASK); + + input_report_abs(idev, ABS_THROTTLE, data[4]); + input_report_abs(idev, ABS_RX, data[5]); + input_report_abs(idev, ABS_RY, data[6]); + input_report_abs(idev, ABS_Z, data[7]); + + input_report_abs(idev, ABS_TILT_X, data[14] & 0xF); + input_report_abs(idev, ABS_TILT_Y, data[14] >> 4); + + switch(data[13]) { + case 0x00: + input_report_abs(idev, ABS_HAT0X, 0); + input_report_abs(idev, ABS_HAT0Y, 0); + break; + case 0x10: + input_report_abs(idev, ABS_HAT0X, 0); + input_report_abs(idev, ABS_HAT0Y, -1); + break; + case 0x20: + input_report_abs(idev, ABS_HAT0X, 1); + input_report_abs(idev, ABS_HAT0Y, -1); + break; + case 0x30: + input_report_abs(idev, ABS_HAT0X, 1); + input_report_abs(idev, ABS_HAT0Y, 0); + break; + case 0x40: + input_report_abs(idev, ABS_HAT0X, 1); + input_report_abs(idev, ABS_HAT0Y, 1); + break; + case 0x50: + input_report_abs(idev, ABS_HAT0X, 0); + input_report_abs(idev, ABS_HAT0Y, 1); + break; + case 0x60: + input_report_abs(idev, ABS_HAT0X, -1); + input_report_abs(idev, ABS_HAT0Y, 1); + break; + case 0x70: + input_report_abs(idev, ABS_HAT0X, -1); + input_report_abs(idev, ABS_HAT0Y, 0); + break; + case 0x80: + input_report_abs(idev, ABS_HAT0X, -1); + input_report_abs(idev, ABS_HAT0Y, -1); + break; + } + + input_sync(idev); +} + +void x52_irq_handler(struct urb *urb) +{ + struct x52_joy *joy = urb->context; + int retval, status; + + status = urb->status; + + switch (status) { + case 0: + /* success */ + break; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + /* This URB is terminated, clean up */ + dbg("%s - URB shutting down with status: %d", + __func__, status); + return; + default: + dbg("%s - nonzero URB status received: %d", + __func__, status); + goto error; + } + + switch (joy->type) { + case X52TYPE_X52PRO: + x52pro_decode_urb(joy, joy->idata); + break; + case X52TYPE_X52: + break; + default: + break; + } + +error: + retval = usb_submit_urb(urb, GFP_ATOMIC); + if (retval) + err ("%s - usb_submit_urb failed with result %d", + __func__, retval); + +} + +void x52_setup_input(struct input_dev *idev) +{ + int i; + + if (!idev) { + return; + } + + /* + * Enable event inputs. + * + * EV_KEY for buttons (and keyboard events in the future) + * EV_ABS for the axis + * EV_REL for future mouse support by the mouse stick + */ + idev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + + /* For now, map the buttons directly */ + for (i = BTN_TRIGGER_HAPPY1; i <= BTN_TRIGGER_HAPPY39; i++) { + __set_bit(i, idev->keybit); + } + + /* Map the axes */ + input_set_abs_params(idev, ABS_X, 0, 1023, 16, 512); + input_set_abs_params(idev, ABS_Y, 0, 1023, 16, 512); + input_set_abs_params(idev, ABS_RZ, 0, 1023, 16, 512); + + input_set_abs_params(idev, ABS_THROTTLE, 0, 255, 0, 0); + input_set_abs_params(idev, ABS_RX, 0, 255, 16, 128); + input_set_abs_params(idev, ABS_RY, 0, 255, 16, 128); + input_set_abs_params(idev, ABS_Z, 0, 255, 0, 0); + + /* Mouse stick */ + input_set_abs_params(idev, ABS_TILT_X, 0, 15, 0, 8); + input_set_abs_params(idev, ABS_TILT_Y, 0, 15, 0, 8); + + /* Hat switch */ + input_set_abs_params(idev, ABS_HAT0X, -1, 1, 0, 0); + input_set_abs_params(idev, ABS_HAT0Y, -1, 1, 0, 0); +} + +int x52_open (struct input_dev *idev) +{ + struct x52_joy *joy = input_get_drvdata(idev); + + joy->irq_in->dev = joy->udev; + if (usb_submit_urb(joy->irq_in, GFP_KERNEL)) { + return -EIO; + } + + return 0; +} + +void x52_close (struct input_dev *idev) +{ + struct x52_joy *joy = input_get_drvdata(idev); + + usb_kill_urb(joy->irq_in); +} +