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.
pull/3/head
Nirenjan Krishnan 2012-10-08 16:21:24 -07:00
parent a217fa486e
commit a4ef90d3c1
4 changed files with 323 additions and 14 deletions

View File

@ -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)

View File

@ -19,6 +19,8 @@
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/usb.h>
#include <linux/input.h>
#include <linux/usb/input.h>
#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);

View File

@ -13,6 +13,7 @@
#define X52JOY_COMMON_H
#include <linux/usb.h>
#include <linux/input.h>
#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 */

View File

@ -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 <linux/input.h>
#include <linux/usb/input.h>
#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);
}