summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
authorVojtech Pavlik <vojtech@suse.cz>2002-07-11 20:49:03 -0700
committerGreg Kroah-Hartman <greg@kroah.com>2002-07-11 20:49:03 -0700
commit450903303e6464a8cee7e23ea5febc6d103a4204 (patch)
tree4072584f1948ba25afb30b6995a2d083a35c2787 /drivers
parent774921a2d9f462a63aff7cc9bf5150eba7bc60cb (diff)
[PATCH] Big HID update
This cset is update of the HID drivers to the latest version, as a part of the Input merge. It finally includes ForceFeedback support by Johann Deneux, enabling ForceFeedback on new Logitech and Microsoft devices.
Diffstat (limited to 'drivers')
-rw-r--r--drivers/usb/input/Config.help31
-rw-r--r--drivers/usb/input/Config.in9
-rw-r--r--drivers/usb/input/Makefile17
-rw-r--r--drivers/usb/input/fixp-arith.h84
-rw-r--r--drivers/usb/input/hid-core.c69
-rw-r--r--drivers/usb/input/hid-ff.c90
-rw-r--r--drivers/usb/input/hid-input.c61
-rw-r--r--drivers/usb/input/hid-lg3dff.c444
-rw-r--r--drivers/usb/input/hid-lgff.c495
-rw-r--r--drivers/usb/input/hid.h26
-rw-r--r--drivers/usb/input/hiddev.c57
-rw-r--r--drivers/usb/input/pid.c330
-rw-r--r--drivers/usb/input/pid.h62
-rw-r--r--drivers/usb/input/powermate.c360
14 files changed, 2081 insertions, 54 deletions
diff --git a/drivers/usb/input/Config.help b/drivers/usb/input/Config.help
index aece24054cfe..6ff790e150d8 100644
--- a/drivers/usb/input/Config.help
+++ b/drivers/usb/input/Config.help
@@ -23,6 +23,23 @@ CONFIG_USB_HIDINPUT
If unsure, say Y.
+CONFIG_HID_FF
+ Say Y here is you want force feedback support for a few hid devices. See
+ below for a list of supported devices.
+ See Documentation/input/ff.txt for a description of the force feedback API.
+
+ If unsure, say N.
+
+CONFIG_LOGITECH_RUMBLE
+ Say Y here if you have a Logitech WingMan Cordless rumble pad and if you
+ want to enable force feedback. Note: if you say N here, this device will
+ still be supported, but without force feedback.
+
+CONFIG_HID_PID
+ Say Y yes if you have a PID-compliant joystick and wish to enable force
+ feedback for it. The Microsoft Sidewinder Force Feedback 2 is one such
+ device.
+
CONFIG_USB_HIDDEV
Say Y here if you want to support HID devices (from the USB
specification standpoint) that aren't strictly user interface
@@ -83,3 +100,17 @@ CONFIG_USB_WACOM
inserted in and removed from the running kernel whenever you want).
The module will be called wacom.o. If you want to compile it as a
module, say M here and read <file:Documentation/modules.txt>.
+
+CONFIG_USB_POWERMATE
+ Say Y here if you want to use Griffin PowerMate or Contour Jog devices.
+ These are stainless steel dials which can measure clockwise and
+ anticlockwise rotation. The dial also acts as a pushbutton. The base
+ contains an LED which can be instructed to pulse or to switch to a
+ particular intensity.
+
+ You can download userspace tools from http://sowerbutts.com/powermate/
+
+ This driver is also available as a module ( = code which can be
+ inserted in and removed from the running kernel whenever you want).
+ The module will be called powermate.o. If you want to compile it as a
+ module, say M here and read <file:Documentation/modules.txt>.
diff --git a/drivers/usb/input/Config.in b/drivers/usb/input/Config.in
index 5ae2242107a0..4b775b95bd42 100644
--- a/drivers/usb/input/Config.in
+++ b/drivers/usb/input/Config.in
@@ -3,10 +3,16 @@
#
comment 'USB Human Interface Devices (HID)'
dep_tristate ' USB Human Interface Device (full HID) support' CONFIG_USB_HID $CONFIG_USB
+
if [ "$CONFIG_INPUT" = "n" ]; then
comment ' Input core support is needed for USB HID input layer or HIDBP support'
fi
+
dep_mbool ' HID input layer support' CONFIG_USB_HIDINPUT $CONFIG_INPUT $CONFIG_USB_HID
+dep_mbool ' Force feedback support' CONFIG_HID_FF $CONFIG_USB_HIDINPUT
+dep_mbool ' PID Devices' CONFIG_HID_PID $CONFIG_USB_HID $CONFIG_HID_FF
+dep_mbool ' Logitech RumblePad support' CONFIG_LOGITECH_RUMBLE $CONFIG_USB_HID $CONFIG_HID_FF
+dep_mbool ' Logitech WingMan Force 3D support' CONFIG_LOGITECH_3D $CONFIG_USB_HID $CONFIG_HID_FF
dep_mbool ' /dev/hiddev raw HID device support' CONFIG_USB_HIDDEV $CONFIG_USB_HID
if [ "$CONFIG_USB_HID" != "y" ]; then
@@ -16,4 +22,5 @@ fi
dep_tristate ' Aiptek 6000U/8000U tablet support' CONFIG_USB_AIPTEK $CONFIG_USB $CONFIG_INPUT
dep_tristate ' Wacom Intuos/Graphire tablet support' CONFIG_USB_WACOM $CONFIG_USB $CONFIG_INPUT
-
+dep_tristate ' Griffin PowerMate and Contour Jog support' CONFIG_USB_POWERMATE $CONFIG_USB $CONFIG_INPUT
+
diff --git a/drivers/usb/input/Makefile b/drivers/usb/input/Makefile
index 19927fb85c8b..387b89f25641 100644
--- a/drivers/usb/input/Makefile
+++ b/drivers/usb/input/Makefile
@@ -12,12 +12,27 @@ endif
ifeq ($(CONFIG_USB_HIDINPUT),y)
hid-objs += hid-input.o
endif
+ifeq ($(CONFIG_HID_PID),y)
+ hid-objs += pid.o
+endif
+
+ifeq ($(CONFIG_LOGITECH_RUMBLE),y)
+ hid-objs += hid-lgff.o
+endif
+
+ifeq ($(CONFIG_LOGITECH_3D),y)
+ hid-objs += hid-lg3dff.o
+endif
+
+ifeq ($(CONFIG_HID_FF),y)
+ hid-objs += hid-ff.o
+endif
obj-$(CONFIG_USB_AIPTEK) += aiptek.o
obj-$(CONFIG_USB_HID) += hid.o
obj-$(CONFIG_USB_KBD) += usbkbd.o
obj-$(CONFIG_USB_MOUSE) += usbmouse.o
obj-$(CONFIG_USB_WACOM) += wacom.o
-
+obj-$(CONFIG_USB_POWERMATE) += powermate.o
include $(TOPDIR)/Rules.make
diff --git a/drivers/usb/input/fixp-arith.h b/drivers/usb/input/fixp-arith.h
new file mode 100644
index 000000000000..1b3066daea1f
--- /dev/null
+++ b/drivers/usb/input/fixp-arith.h
@@ -0,0 +1,84 @@
+#ifndef _FIXP_ARITH_H
+#define _FIXP_ARITH_H
+
+/*
+ * $$
+ *
+ * Simplistic fixed-point arithmetics.
+ * Hmm, I'm probably duplicating some code :(
+ *
+ * Copyright (c) 2002 Johann Deneux
+ */
+
+/*
+ * 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Should you need to contact me, the author, you can do so by
+ * e-mail - mail your message to <deneux@ifrance.com>
+ */
+
+#include <linux/types.h>
+
+// The type representing fixed-point values
+typedef s16 fixp_t;
+
+#define FRAC_N 8
+#define FRAC_MASK ((1<<FRAC_N)-1)
+
+// Not to be used directly. Use fixp_{cos,sin}
+fixp_t cos_table[45] = {
+ 0x0100, 0x00FF, 0x00FF, 0x00FE, 0x00FD, 0x00FC, 0x00FA, 0x00F8,
+ 0x00F6, 0x00F3, 0x00F0, 0x00ED, 0x00E9, 0x00E6, 0x00E2, 0x00DD,
+ 0x00D9, 0x00D4, 0x00CF, 0x00C9, 0x00C4, 0x00BE, 0x00B8, 0x00B1,
+ 0x00AB, 0x00A4, 0x009D, 0x0096, 0x008F, 0x0087, 0x0080, 0x0078,
+ 0x0070, 0x0068, 0x005F, 0x0057, 0x004F, 0x0046, 0x003D, 0x0035,
+ 0x002C, 0x0023, 0x001A, 0x0011, 0x0008
+};
+
+
+/* a: 123 -> 123.0 */
+inline fixp_t fixp_new(s16 a)
+{
+ return a<<FRAC_N;
+}
+
+/* a: 0xFFFF -> -1.0
+ 0x8000 -> 1.0
+ 0x0000 -> 0.0
+*/
+inline fixp_t fixp_new16(s16 a)
+{
+ return ((s32)a)>>(16-FRAC_N);
+}
+
+inline fixp_t fixp_cos(unsigned int degrees)
+{
+ int quadrant = (degrees / 90) & 3;
+ unsigned int i = (degrees % 90) >> 1;
+
+ return (quadrant == 1 || quadrant == 2)? -cos_table[i] : cos_table[i];
+}
+
+inline fixp_t fixp_sin(unsigned int degrees)
+{
+ return -fixp_cos(degrees + 90);
+}
+
+inline fixp_t fixp_mult(fixp_t a, fixp_t b)
+{
+ return ((s32)(a*b))>>FRAC_N;
+}
+
+#endif
diff --git a/drivers/usb/input/hid-core.c b/drivers/usb/input/hid-core.c
index cbe2a13fc967..ca7b3f198390 100644
--- a/drivers/usb/input/hid-core.c
+++ b/drivers/usb/input/hid-core.c
@@ -1,5 +1,5 @@
/*
- * $Id: hid-core.c,v 1.42 2002/01/27 00:22:46 vojtech Exp $
+ * $Id: hid-core.c,v 1.6 2002/06/09 17:34:55 jdeneux Exp $
*
* Copyright (c) 1999 Andreas Gal
* Copyright (c) 2000-2001 Vojtech Pavlik
@@ -108,11 +108,10 @@ static struct hid_field *hid_register_field(struct hid_report *report, unsigned
memset(field, 0, sizeof(struct hid_field) + usages * sizeof(struct hid_usage)
+ values * sizeof(unsigned));
- report->field[report->maxfield] = field;
+ report->field[report->maxfield++] = field;
field->usage = (struct hid_usage *)(field + 1);
field->value = (unsigned *)(field->usage + usages);
field->report = report;
- field->index = report->maxfield++;
return field;
}
@@ -518,6 +517,8 @@ static void hid_free_device(struct hid_device *device)
{
unsigned i,j;
+ hid_ff_exit(device);
+
for (i = 0; i < HID_REPORT_TYPES; i++) {
struct hid_report_enum *report_enum = device->report_enum + i;
@@ -1171,8 +1172,8 @@ int hid_wait_io(struct hid_device *hid)
set_current_state(TASK_UNINTERRUPTIBLE);
add_wait_queue(&hid->wait, &wait);
- while (timeout && test_bit(HID_CTRL_RUNNING, &hid->iofl) &&
- test_bit(HID_OUT_RUNNING, &hid->iofl))
+ while (timeout && (test_bit(HID_CTRL_RUNNING, &hid->iofl) ||
+ test_bit(HID_OUT_RUNNING, &hid->iofl)))
timeout = schedule_timeout(timeout);
set_current_state(TASK_RUNNING);
@@ -1223,6 +1224,7 @@ void hid_init_reports(struct hid_device *hid)
struct hid_report *report;
struct list_head *list;
int len;
+ int err, ret;
report_enum = hid->report_enum + HID_INPUT_REPORT;
list = report_enum->report_list.next;
@@ -1240,7 +1242,16 @@ void hid_init_reports(struct hid_device *hid)
list = list->next;
}
- if (hid_wait_io(hid)) {
+ err = 0;
+ while ((ret = hid_wait_io(hid))) {
+ err |= ret;
+ if (test_bit(HID_CTRL_RUNNING, &hid->iofl))
+ usb_unlink_urb(hid->urbctrl);
+ if (test_bit(HID_OUT_RUNNING, &hid->iofl))
+ usb_unlink_urb(hid->urbout);
+ }
+
+ if (err) {
warn("timeout initializing reports\n");
return;
}
@@ -1299,7 +1310,7 @@ static struct hid_device *usb_hid_configure(struct usb_device *dev, int ifnum)
struct hid_descriptor *hdesc;
struct hid_device *hid;
unsigned quirks = 0, rsize = 0;
- char *buf;
+ char *buf, *rdesc;
int n;
for (n = 0; hid_blacklist[n].idVendor; n++)
@@ -1325,27 +1336,31 @@ static struct hid_device *usb_hid_configure(struct usb_device *dev, int ifnum)
return NULL;
}
- {
- __u8 rdesc[rsize];
+ if (!(rdesc = kmalloc(rsize, GFP_KERNEL))) {
+ dbg("couldn't allocate rdesc memory");
+ return NULL;
+ }
- if ((n = hid_get_class_descriptor(dev, interface->bInterfaceNumber, HID_DT_REPORT, rdesc, rsize)) < 0) {
- dbg("reading report descriptor failed");
- return NULL;
- }
+ if ((n = hid_get_class_descriptor(dev, interface->bInterfaceNumber, HID_DT_REPORT, rdesc, rsize)) < 0) {
+ dbg("reading report descriptor failed");
+ kfree(rdesc);
+ return NULL;
+ }
#ifdef DEBUG_DATA
- printk(KERN_DEBUG __FILE__ ": report descriptor (size %u, read %d) = ", rsize, n);
- for (n = 0; n < rsize; n++)
- printk(" %02x", (unsigned) rdesc[n]);
- printk("\n");
+ printk(KERN_DEBUG __FILE__ ": report descriptor (size %u, read %d) = ", rsize, n);
+ for (n = 0; n < rsize; n++)
+ printk(" %02x", (unsigned) rdesc[n]);
+ printk("\n");
#endif
- if (!(hid = hid_parse_report(rdesc, rsize))) {
- dbg("parsing report descriptor failed");
- return NULL;
- }
+ if (!(hid = hid_parse_report(rdesc, rsize))) {
+ dbg("parsing report descriptor failed");
+ kfree(rdesc);
+ return NULL;
}
+ kfree(rdesc);
hid->quirks = quirks;
for (n = 0; n < interface->bNumEndpoints; n++) {
@@ -1439,6 +1454,8 @@ static void* hid_probe(struct usb_device *dev, unsigned int ifnum,
hid_init_reports(hid);
hid_dump_device(hid);
+ hid_ff_init(hid);
+
if (!hidinput_connect(hid))
hid->claimed |= HID_CLAIMED_INPUT;
if (!hiddev_connect(hid))
@@ -1477,20 +1494,20 @@ static void hid_disconnect(struct usb_device *dev, void *ptr)
{
struct hid_device *hid = ptr;
- dbg("cleanup called");
usb_unlink_urb(hid->urbin);
usb_unlink_urb(hid->urbout);
usb_unlink_urb(hid->urbctrl);
+ if (hid->claimed & HID_CLAIMED_INPUT)
+ hidinput_disconnect(hid);
+ if (hid->claimed & HID_CLAIMED_HIDDEV)
+ hiddev_disconnect(hid);
+
usb_free_urb(hid->urbin);
usb_free_urb(hid->urbctrl);
if (hid->urbout)
usb_free_urb(hid->urbout);
- if (hid->claimed & HID_CLAIMED_INPUT)
- hidinput_disconnect(hid);
- if (hid->claimed & HID_CLAIMED_HIDDEV)
- hiddev_disconnect(hid);
hid_free_device(hid);
}
diff --git a/drivers/usb/input/hid-ff.c b/drivers/usb/input/hid-ff.c
new file mode 100644
index 000000000000..875776b86bfa
--- /dev/null
+++ b/drivers/usb/input/hid-ff.c
@@ -0,0 +1,90 @@
+/*
+ * $Id: hid-ff.c,v 1.3 2002/06/09 11:06:38 jdeneux Exp $
+ *
+ * Force feedback support for hid devices.
+ * Not all hid devices use the same protocol. For example, some use PID,
+ * other use their own proprietary procotol.
+ *
+ * Copyright (c) 2002 Johann Deneux
+ */
+
+/*
+ * 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Should you need to contact me, the author, you can do so by
+ * e-mail - mail your message to <deneux@ifrance.com>
+ */
+
+#include <linux/input.h>
+
+#define DEBUG
+#include <linux/usb.h>
+
+#include "hid.h"
+
+/* Drivers' initializing functions */
+extern int hid_lgff_init(struct hid_device* hid);
+extern int hid_lg3d_init(struct hid_device* hid);
+extern int hid_pid_init(struct hid_device* hid);
+
+/*
+ * This table contains pointers to initializers. To add support for new
+ * devices, you need to add the USB vendor and product ids here.
+ */
+struct hid_ff_initializer {
+ __u16 idVendor;
+ __u16 idProduct;
+ int (*init)(struct hid_device*);
+};
+
+static struct hid_ff_initializer inits[] = {
+#ifdef CONFIG_LOGITECH_RUMBLE
+ {0x46d, 0xc211, hid_lgff_init},
+#endif
+#ifdef CONFIG_LOGITECH_3D
+ {0x46d, 0xc283, hid_lg3d_init},
+#endif
+#ifdef CONFIG_HID_PID
+ {0x45e, 0x001b, hid_pid_init},
+#endif
+ {0, 0, NULL} /* Terminating entry */
+};
+
+static struct hid_ff_initializer *hid_get_ff_init(__u16 idVendor,
+ __u16 idProduct)
+{
+ struct hid_ff_initializer *init;
+ for (init = inits;
+ init->idVendor
+ && !(init->idVendor == idVendor
+ && init->idProduct == idProduct);
+ init++);
+
+ return init->idVendor? init : NULL;
+}
+
+int hid_ff_init(struct hid_device* hid)
+{
+ struct hid_ff_initializer *init;
+
+ init = hid_get_ff_init(hid->dev->descriptor.idVendor,
+ hid->dev->descriptor.idProduct);
+
+ if (!init) {
+ warn("hid_ff_init could not find initializer");
+ return -ENOSYS;
+ }
+ return init->init(hid);
+}
diff --git a/drivers/usb/input/hid-input.c b/drivers/usb/input/hid-input.c
index b45938b18cc8..37f12424cb6f 100644
--- a/drivers/usb/input/hid-input.c
+++ b/drivers/usb/input/hid-input.c
@@ -1,5 +1,5 @@
/*
- * $Id: hid-input.c,v 1.18 2001/11/07 09:01:18 vojtech Exp $
+ * $Id: hid-input.c,v 1.2 2002/04/23 00:59:25 rdamazio Exp $
*
* Copyright (c) 2000-2001 Vojtech Pavlik
*
@@ -273,9 +273,53 @@ static void hidinput_configure_usage(struct hid_device *device, struct hid_field
usage->type = EV_KEY; bit = input->keybit; max = KEY_MAX;
break;
-
+
+ case HID_UP_PID:
+
+ usage->type = EV_FF; bit = input->ffbit; max = FF_MAX;
+
+ switch(usage->hid & HID_USAGE) {
+ case 0x26: set_bit(FF_CONSTANT, input->ffbit); break;
+ case 0x27: set_bit(FF_RAMP, input->ffbit); break;
+ case 0x28: set_bit(FF_CUSTOM, input->ffbit); break;
+ case 0x30: set_bit(FF_SQUARE, input->ffbit);
+ set_bit(FF_PERIODIC, input->ffbit); break;
+ case 0x31: set_bit(FF_SINE, input->ffbit);
+ set_bit(FF_PERIODIC, input->ffbit); break;
+ case 0x32: set_bit(FF_TRIANGLE, input->ffbit);
+ set_bit(FF_PERIODIC, input->ffbit); break;
+ case 0x33: set_bit(FF_SAW_UP, input->ffbit);
+ set_bit(FF_PERIODIC, input->ffbit); break;
+ case 0x34: set_bit(FF_SAW_DOWN, input->ffbit);
+ set_bit(FF_PERIODIC, input->ffbit); break;
+ case 0x40: set_bit(FF_SPRING, input->ffbit); break;
+ case 0x41: set_bit(FF_DAMPER, input->ffbit); break;
+ case 0x42: set_bit(FF_INERTIA , input->ffbit); break;
+ case 0x43: set_bit(FF_FRICTION, input->ffbit); break;
+ case 0x7e: usage->code = FF_GAIN; break;
+ case 0x83: /* Simultaneous Effects Max */
+ input->ff_effects_max = (field->value[0]);
+ dbg("Maximum Effects - %d",input->ff_effects_max);
+ break;
+ case 0x98: /* Device Control */
+ usage->code = FF_AUTOCENTER; break;
+ case 0xa4: /* Safety Switch */
+ usage->code = BTN_DEAD;
+ bit = input->keybit;
+ usage->type = EV_KEY;
+ max = KEY_MAX;
+ dbg("Safety Switch Report\n");
+ break;
+ case 0x9f: /* Device Paused */
+ case 0xa0: /* Actuators Enabled */
+ dbg("Not telling the input API about ");
+ resolv_usage(usage->hid);
+ return;
+ }
+ break;
default:
unknown:
+ resolv_usage(usage->hid);
if (field->report_size == 1) {
@@ -365,6 +409,16 @@ void hidinput_hid_event(struct hid_device *hid, struct hid_field *field, struct
input_event(input, EV_KEY, BTN_TOUCH, value > a + ((b - a) >> 3));
}
+ if (usage->hid == (HID_UP_PID | 0x83UL)) { /* Simultaneous Effects Max */
+ input->ff_effects_max = value;
+ dbg("Maximum Effects - %d",input->ff_effects_max);
+ return;
+ }
+ if (usage->hid == (HID_UP_PID | 0x7fUL)) {
+ dbg("PID Pool Report\n");
+ return;
+ }
+
if((usage->type == EV_KEY) && (usage->code == 0)) /* Key 0 is "unassigned", not KEY_UKNOWN */
return;
@@ -380,6 +434,9 @@ static int hidinput_input_event(struct input_dev *dev, unsigned int type, unsign
struct hid_field *field = NULL;
int offset;
+ if (type == EV_FF)
+ return hid_ff_event(hid, dev, type, code, value);
+
if ((offset = hid_find_field(hid, type, code, &field)) == -1) {
warn("event field not found");
return -1;
diff --git a/drivers/usb/input/hid-lg3dff.c b/drivers/usb/input/hid-lg3dff.c
new file mode 100644
index 000000000000..b9f5636dd614
--- /dev/null
+++ b/drivers/usb/input/hid-lg3dff.c
@@ -0,0 +1,444 @@
+/*
+ * $$
+ *
+ * Force feedback support for hid-compliant devices of the Logitech *3D family
+ *
+ * Copyright (c) 2002 Johann Deneux
+ */
+
+/*
+ * 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Should you need to contact me, the author, you can do so by
+ * e-mail - mail your message to <deneux@ifrance.com>
+ */
+
+#include <linux/input.h>
+#include <linux/sched.h>
+
+#define DEBUG
+#include <linux/usb.h>
+
+#include <linux/circ_buf.h>
+
+#include "hid.h"
+#include "fixp-arith.h"
+
+#define RUN_AT(t) (jiffies + (t))
+
+/* Periodicity of the update */
+#define PERIOD (HZ/10)
+
+/* Effect status: lg3d_effect::flags */
+#define EFFECT_STARTED 0 /* Effect is going to play after some time
+ (ff_replay.delay) */
+#define EFFECT_PLAYING 1 /* Effect is being played */
+#define EFFECT_USED 2
+
+/* Check that the current process can access an effect */
+#define CHECK_OWNERSHIP(i, l) \
+ (i>=0 && i<N_EFFECTS \
+ && test_bit(EFFECT_USED, l->effects[i].flags) \
+ && (current->pid == 0 \
+ || l->effects[i].owner == current->pid))
+
+#define N_EFFECTS 8
+
+struct lg3d_effect {
+ pid_t owner;
+
+ struct ff_effect effect; /* Description of the effect */
+
+ unsigned int count; /* Number of times left to play */
+ unsigned long flags[1];
+
+ unsigned long started_at; /* When the effect started to play */
+};
+
+// For lg3d_device::flags
+#define DEVICE_USB_XMIT 0 /* An URB is being sent */
+#define DEVICE_CLOSING 1 /* The driver is being unitialised */
+
+struct lg3d_device {
+ struct hid_device* hid;
+
+ struct urb* urbffout; /* Output URB used to send ff commands */
+ struct usb_ctrlrequest ffcr; /* ff commands use control URBs */
+ char buf[8];
+
+ struct lg3d_effect effects[N_EFFECTS];
+ spinlock_t lock; /* device-level lock. Having locks on
+ a per-effect basis could be nice, but
+ isn't really necessary */
+ struct timer_list timer;
+ unsigned long last_time; /* Last time the timer handler was
+ executed */
+
+ unsigned long flags[1]; /* Contains various information about the
+ state of the driver for this device */
+};
+
+static void hid_lg3d_ctrl_out(struct urb *urb);
+static void hid_lg3d_exit(struct hid_device* hid);
+static int hid_lg3d_event(struct hid_device *hid, struct input_dev *input,
+ unsigned int type, unsigned int code, int value);
+static int hid_lg3d_flush(struct input_dev *input, struct file *file);
+static int hid_lg3d_upload_effect(struct input_dev *input,
+ struct ff_effect *effect);
+static int hid_lg3d_erase(struct input_dev *input, int id);
+static void hid_lg3d_timer(unsigned long timer_data);
+
+
+int hid_lg3d_init(struct hid_device* hid)
+{
+ struct lg3d_device *private;
+
+ /* Private data */
+ private = kmalloc(sizeof(struct lg3d_device), GFP_KERNEL);
+ if (!private) return -1;
+
+ memset(private, 0, sizeof(struct lg3d_device));
+
+ hid->ff_private = private;
+
+ private->hid = hid;
+ spin_lock_init(&private->lock);
+
+ /* Timer for the periodic update task */
+ init_timer(&private->timer);
+ private->timer.data = (unsigned long)private;
+ private->timer.function = hid_lg3d_timer;
+
+ /* Event and exit callbacks */
+ hid->ff_exit = hid_lg3d_exit;
+ hid->ff_event = hid_lg3d_event;
+
+ /* USB init */
+ if (!(private->urbffout = usb_alloc_urb(0, GFP_KERNEL))) {
+ kfree(hid->ff_private);
+ return -1;
+ }
+
+ usb_fill_control_urb(private->urbffout, hid->dev, 0,
+ (void*) &private->ffcr, private->buf, 8,
+ hid_lg3d_ctrl_out, hid);
+ dbg("Created ff output control urb");
+
+ /* Input init */
+ hid->input.upload_effect = hid_lg3d_upload_effect;
+ hid->input.flush = hid_lg3d_flush;
+ set_bit(FF_CONSTANT, hid->input.ffbit);
+ set_bit(EV_FF, hid->input.evbit);
+ hid->input.ff_effects_max = N_EFFECTS;
+
+ printk(KERN_INFO "Force feedback for Logitech *3D devices by Johann Deneux <deneux@ifrance.com>\n");
+
+ /* Start the update task */
+ private->timer.expires = RUN_AT(PERIOD);
+ add_timer(&private->timer); /*TODO: only run the timer when at least
+ one effect is playing */
+
+ return 0;
+}
+
+static void hid_lg3d_exit(struct hid_device* hid)
+{
+ struct lg3d_device *lg3d = hid->ff_private;
+ unsigned long flags;
+
+ spin_lock_irqsave(&lg3d->lock, flags);
+ set_bit(DEVICE_CLOSING, lg3d->flags);
+ spin_unlock_irqrestore(&lg3d->lock, flags);
+
+ del_timer_sync(&lg3d->timer);
+
+ if (lg3d->urbffout) {
+ usb_unlink_urb(lg3d->urbffout);
+ usb_free_urb(lg3d->urbffout);
+ }
+
+ kfree(lg3d);
+}
+
+static int hid_lg3d_event(struct hid_device *hid, struct input_dev* input,
+ unsigned int type, unsigned int code, int value)
+{
+ struct lg3d_device *lg3d = hid->ff_private;
+ struct lg3d_effect *effect = lg3d->effects + code;
+ unsigned long flags;
+
+ if (type != EV_FF) return -EINVAL;
+ if (!CHECK_OWNERSHIP(code, lg3d)) return -EACCES;
+ if (value < 0) return -EINVAL;
+
+ spin_lock_irqsave(&lg3d->lock, flags);
+
+ if (value > 0) {
+ if (test_bit(EFFECT_STARTED, effect->flags)) {
+ spin_unlock_irqrestore(&lg3d->lock, flags);
+ return -EBUSY;
+ }
+ if (test_bit(EFFECT_PLAYING, effect->flags)) {
+ spin_unlock_irqrestore(&lg3d->lock, flags);
+ return -EBUSY;
+ }
+
+ effect->count = value;
+
+ if (effect->effect.replay.delay) {
+ set_bit(EFFECT_STARTED, effect->flags);
+ } else {
+ set_bit(EFFECT_PLAYING, effect->flags);
+ }
+ effect->started_at = jiffies;
+ }
+ else { /* value == 0 */
+ clear_bit(EFFECT_STARTED, effect->flags);
+ clear_bit(EFFECT_PLAYING, effect->flags);
+ }
+
+ spin_unlock_irqrestore(&lg3d->lock, flags);
+
+ return 0;
+}
+
+/* Erase all effects this process owns */
+static int hid_lg3d_flush(struct input_dev *dev, struct file *file)
+{
+ struct hid_device *hid = dev->private;
+ struct lg3d_device *lg3d = hid->ff_private;
+ int i;
+
+ for (i=0; i<dev->ff_effects_max; ++i) {
+
+ /*NOTE: no need to lock here. The only times EFFECT_USED is
+ modified is when effects are uploaded or when an effect is
+ erased. But a process cannot close its dev/input/eventX fd
+ and perform ioctls on the same fd all at the same time */
+ if ( current->pid == lg3d->effects[i].owner
+ && test_bit(EFFECT_USED, lg3d->effects[i].flags)) {
+
+ if (hid_lg3d_erase(dev, i))
+ warn("erase effect %d failed", i);
+ }
+
+ }
+
+ return 0;
+}
+
+static int hid_lg3d_erase(struct input_dev *dev, int id)
+{
+ struct hid_device *hid = dev->private;
+ struct lg3d_device *lg3d = hid->ff_private;
+ unsigned long flags;
+
+ if (!CHECK_OWNERSHIP(id, lg3d)) return -EACCES;
+
+ spin_lock_irqsave(&lg3d->lock, flags);
+ lg3d->effects[id].flags[0] = 0;
+ spin_unlock_irqrestore(&lg3d->lock, flags);
+
+ return 0;
+}
+
+static int hid_lg3d_upload_effect(struct input_dev* input,
+ struct ff_effect* effect)
+{
+ struct hid_device *hid = input->private;
+ struct lg3d_device *lg3d = hid->ff_private;
+ struct lg3d_effect new;
+ int id;
+ unsigned long flags;
+
+ dbg("ioctl upload");
+
+ if (!test_bit(effect->type, input->ffbit)) return -EINVAL;
+
+ if (effect->type != FF_CONSTANT) return -EINVAL;
+
+ spin_lock_irqsave(&lg3d->lock, flags);
+
+ if (effect->id == -1) {
+ int i;
+
+ for (i=0; i<N_EFFECTS && test_bit(EFFECT_USED, lg3d->effects[i].flags); ++i);
+ if (i >= N_EFFECTS) {
+ spin_unlock_irqrestore(&lg3d->lock, flags);
+ return -ENOSPC;
+ }
+
+ effect->id = i;
+ lg3d->effects[i].owner = current->pid;
+ lg3d->effects[i].flags[0] = 0;
+ set_bit(EFFECT_USED, lg3d->effects[i].flags);
+ }
+ else if (!CHECK_OWNERSHIP(effect->id, lg3d)) {
+ spin_unlock_irqrestore(&lg3d->lock, flags);
+ return -EACCES;
+ }
+
+ id = effect->id;
+ new = lg3d->effects[id];
+
+ new.effect = *effect;
+ new.effect.replay = effect->replay;
+
+ if (test_bit(EFFECT_STARTED, lg3d->effects[id].flags)
+ || test_bit(EFFECT_STARTED, lg3d->effects[id].flags)) {
+
+ /* Changing replay parameters is not allowed (for the time
+ being) */
+ if (new.effect.replay.delay != lg3d->effects[id].effect.replay.delay
+ || new.effect.replay.length != lg3d->effects[id].effect.replay.length) {
+ spin_unlock_irqrestore(&lg3d->lock, flags);
+ return -ENOSYS;
+ }
+
+ lg3d->effects[id] = new;
+
+ } else {
+ lg3d->effects[id] = new;
+ }
+
+ spin_unlock_irqrestore(&lg3d->lock, flags);
+ return 0;
+}
+
+static void hid_lg3d_ctrl_out(struct urb *urb)
+{
+ struct hid_device *hid = urb->context;
+ struct lg3d_device *lg3d = hid->ff_private;
+ unsigned long flags;
+
+ spin_lock_irqsave(&lg3d->lock, flags);
+
+ if (urb->status)
+ warn("hid_irq_ffout status %d received", urb->status);
+ clear_bit(DEVICE_USB_XMIT, lg3d->flags);
+ dbg("xmit = 0");
+
+ spin_unlock_irqrestore(&lg3d->lock, flags);
+}
+
+static void hid_lg3d_timer(unsigned long timer_data)
+{
+ struct lg3d_device *lg3d = (struct lg3d_device*)timer_data;
+ struct hid_device *hid = lg3d->hid;
+ unsigned long flags;
+ int x, y;
+ int i;
+ int err;
+
+ spin_lock_irqsave(&lg3d->lock, flags);
+
+ if (test_bit(DEVICE_USB_XMIT, lg3d->flags)) {
+ if (lg3d->urbffout->status != -EINPROGRESS) {
+ warn("xmit *not* in progress");
+ }
+ else {
+ dbg("xmit in progress");
+ }
+
+ spin_unlock_irqrestore(&lg3d->lock, flags);
+
+ lg3d->timer.expires = RUN_AT(PERIOD);
+ add_timer(&lg3d->timer);
+
+ return;
+ }
+
+ x = 0x7f;
+ y = 0x7f;
+
+ for (i=0; i<N_EFFECTS; ++i) {
+ struct lg3d_effect* effect = lg3d->effects +i;
+
+ if (test_bit(EFFECT_PLAYING, effect->flags)) {
+
+ if (effect->effect.type == FF_CONSTANT) {
+ //TODO: handle envelopes
+ int degrees = effect->effect.direction * 360 >> 16;
+ x += fixp_mult(fixp_sin(degrees),
+ fixp_new16(effect->effect.u.constant.level));
+ y += fixp_mult(-fixp_cos(degrees),
+ fixp_new16(effect->effect.u.constant.level));
+ }
+
+ /* One run of the effect is finished playing */
+ if (time_after(jiffies,
+ effect->started_at
+ + effect->effect.replay.delay*HZ/1000
+ + effect->effect.replay.length*HZ/1000)) {
+ dbg("Finished playing once");
+ if (--effect->count <= 0) {
+ dbg("Stopped");
+ clear_bit(EFFECT_PLAYING, effect->flags);
+ }
+ else {
+ dbg("Start again");
+ if (effect->effect.replay.length != 0) {
+ clear_bit(EFFECT_PLAYING, effect->flags);
+ set_bit(EFFECT_STARTED, effect->flags);
+ }
+ effect->started_at = jiffies;
+ }
+ }
+
+ } else if (test_bit(EFFECT_STARTED, lg3d->effects[i].flags)) {
+ dbg("Started");
+ /* Check if we should start playing the effect */
+ if (time_after(jiffies,
+ lg3d->effects[i].started_at
+ + lg3d->effects[i].effect.replay.delay*HZ/1000)) {
+ dbg("Now playing");
+ clear_bit(EFFECT_STARTED, lg3d->effects[i].flags);
+ set_bit(EFFECT_PLAYING, lg3d->effects[i].flags);
+ }
+ }
+ }
+
+ if (x < 0) x = 0;
+ if (x > 0xff) x = 0xff;
+ if (y < 0) y = 0;
+ if (y > 0xff) y = 0xff;
+
+ lg3d->urbffout->pipe = usb_sndctrlpipe(hid->dev, 0);
+ lg3d->ffcr.bRequestType = USB_TYPE_CLASS | USB_DIR_OUT | USB_RECIP_INTERFACE;
+ lg3d->urbffout->transfer_buffer_length = lg3d->ffcr.wLength = 8;
+ lg3d->ffcr.bRequest = 9;
+ lg3d->ffcr.wValue = 0x0200; /*NOTE: Potential problem with
+ little/big endian */
+ lg3d->ffcr.wIndex = 0;
+
+ lg3d->urbffout->dev = hid->dev;
+
+ lg3d->buf[0] = 0x51;
+ lg3d->buf[1] = 0x08;
+ lg3d->buf[2] = x;
+ lg3d->buf[3] = y;
+
+ if ((err=usb_submit_urb(lg3d->urbffout, GFP_ATOMIC)))
+ warn("usb_submit_urb returned %d", err);
+ else
+ set_bit(DEVICE_USB_XMIT, lg3d->flags);
+
+ if (!test_bit(DEVICE_CLOSING, lg3d->flags)) {
+ lg3d->timer.expires = RUN_AT(PERIOD);
+ add_timer(&lg3d->timer);
+ }
+
+ spin_unlock_irqrestore(&lg3d->lock, flags);
+}
diff --git a/drivers/usb/input/hid-lgff.c b/drivers/usb/input/hid-lgff.c
new file mode 100644
index 000000000000..8ae5cf004cfa
--- /dev/null
+++ b/drivers/usb/input/hid-lgff.c
@@ -0,0 +1,495 @@
+/*
+ * $$
+ *
+ * Force feedback support for hid-compliant for some of the devices from
+ * Logitech, namely:
+ * - WingMan Cordless RumblePad
+ *
+ * Copyright (c) 2002 Johann Deneux
+ */
+
+/*
+ * 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Should you need to contact me, the author, you can do so by
+ * e-mail - mail your message to <deneux@ifrance.com>
+ */
+
+#include <linux/input.h>
+#include <linux/sched.h>
+
+#define DEBUG
+#include <linux/usb.h>
+
+#include <linux/circ_buf.h>
+
+#include "hid.h"
+
+#define RUN_AT(t) (jiffies + (t))
+
+/* Transmition state */
+#define XMIT_RUNNING 0
+
+/* Effect status */
+#define EFFECT_STARTED 0 /* Effect is going to play after some time
+ (ff_replay.delay) */
+#define EFFECT_PLAYING 1 /* Effect is being played */
+#define EFFECT_USED 2
+
+/* Check that the current process can access an effect */
+#define CHECK_OWNERSHIP(effect) (current->pid == 0 \
+ || effect.owner == current->pid)
+
+/* **************************************************************************/
+/* Implements the protocol used by the Logitech WingMan Cordless RumblePad */
+/* **************************************************************************/
+
+#define LGFF_CHECK_OWNERSHIP(i, l) \
+ (i>=0 && i<LGFF_EFFECTS \
+ && test_bit(EFFECT_USED, l->effects[i].flags) \
+ && CHECK_OWNERSHIP(l->effects[i]))
+
+#define LGFF_BUFFER_SIZE 64
+#define LGFF_EFFECTS 8
+
+struct lgff_magnitudes {
+ unsigned char left;
+ unsigned char right;
+};
+
+struct lgff_effect {
+ int id;
+ struct hid_ff_logitech* lgff;
+
+ pid_t owner;
+ unsigned char left; /* Magnitude of vibration for left motor */
+ unsigned char right; /* Magnitude of vibration for right motor */
+ struct ff_replay replay;
+ unsigned int count; /* Number of times to play */
+ struct timer_list timer;
+ unsigned long flags[1];
+};
+
+struct hid_ff_logitech {
+ struct hid_device* hid;
+
+ struct urb* urbffout; /* Output URB used to send ff commands */
+ struct usb_ctrlrequest ffcr; /* ff commands use control URBs */
+ char buf[8];
+
+ spinlock_t xmit_lock;
+ unsigned int xmit_head, xmit_tail;
+ struct lgff_magnitudes xmit_data[LGFF_BUFFER_SIZE];
+ long xmit_flags[1];
+
+ struct lgff_effect effects[LGFF_EFFECTS];
+ spinlock_t lock; /* device-level lock. Having locks on
+ a per-effect basis could be nice, but
+ isn't really necessary */
+};
+
+static void hid_lgff_ctrl_out(struct urb *urb);
+static void hid_lgff_exit(struct hid_device* hid);
+static int hid_lgff_event(struct hid_device *hid, struct input_dev *input,
+ unsigned int type, unsigned int code, int value);
+static void hid_lgff_make_rumble(struct hid_device* hid);
+
+static int hid_lgff_flush(struct input_dev *input, struct file *file);
+static int hid_lgff_upload_effect(struct input_dev *input,
+ struct ff_effect *effect);
+static int hid_lgff_erase(struct input_dev *input, int id);
+static void hid_lgff_ctrl_playback(struct hid_device* hid, struct lgff_effect*,
+ int play);
+static void hid_lgff_timer(unsigned long timer_data);
+
+
+int hid_lgff_init(struct hid_device* hid)
+{
+ struct hid_ff_logitech *private;
+ int i;
+
+ /* Private data */
+ private = kmalloc(sizeof(struct hid_ff_logitech), GFP_KERNEL);
+ if (!private) return -1;
+
+ memset(private, 0, sizeof(struct hid_ff_logitech));
+
+ hid->ff_private = private;
+
+ private->hid = hid;
+ spin_lock_init(&private->lock);
+ spin_lock_init(&private->xmit_lock);
+
+ private->buf[0] = 0x03;
+ private->buf[1] = 0x42;
+
+ for (i=0; i<LGFF_EFFECTS; ++i) {
+ struct lgff_effect* effect = &private->effects[i];
+ struct timer_list* timer = &effect->timer;
+
+ init_timer(timer);
+ effect->id = i;
+ effect->lgff = private;
+ timer->data = (unsigned long)effect;
+ timer->function = hid_lgff_timer;
+ }
+
+ /* Event and exit callbacks */
+ hid->ff_exit = hid_lgff_exit;
+ hid->ff_event = hid_lgff_event;
+
+ /* USB init */
+ if (!(private->urbffout = usb_alloc_urb(0, GFP_KERNEL))) {
+ kfree(hid->ff_private);
+ return -1;
+ }
+
+ usb_fill_control_urb(private->urbffout, hid->dev, 0,
+ (void*) &private->ffcr, private->buf, 8,
+ hid_lgff_ctrl_out, hid);
+ dbg("Created ff output control urb");
+
+ /* Input init */
+ hid->input.upload_effect = hid_lgff_upload_effect;
+ hid->input.flush = hid_lgff_flush;
+ set_bit(FF_RUMBLE, hid->input.ffbit);
+ set_bit(EV_FF, hid->input.evbit);
+ hid->input.ff_effects_max = LGFF_EFFECTS;
+
+ printk(KERN_INFO "Force feedback for Logitech rumble devices by Johann Deneux <deneux@ifrance.com>\n");
+
+ return 0;
+}
+
+static void hid_lgff_exit(struct hid_device* hid)
+{
+ struct hid_ff_logitech *lgff = hid->ff_private;
+
+ if (lgff->urbffout) {
+ usb_unlink_urb(lgff->urbffout);
+ usb_free_urb(lgff->urbffout);
+ }
+}
+
+static int hid_lgff_event(struct hid_device *hid, struct input_dev* input,
+ unsigned int type, unsigned int code, int value)
+{
+ struct hid_ff_logitech *lgff = hid->ff_private;
+ struct lgff_effect *effect = lgff->effects + code;
+ unsigned long flags;
+
+ if (type != EV_FF) return -EINVAL;
+ if (!LGFF_CHECK_OWNERSHIP(code, lgff)) return -EACCES;
+ if (value < 0) return -EINVAL;
+
+ spin_lock_irqsave(&lgff->lock, flags);
+
+ if (value > 0) {
+ if (test_bit(EFFECT_STARTED, effect->flags)) {
+ spin_unlock_irqrestore(&lgff->lock, flags);
+ return -EBUSY;
+ }
+ if (test_bit(EFFECT_PLAYING, effect->flags)) {
+ spin_unlock_irqrestore(&lgff->lock, flags);
+ return -EBUSY;
+ }
+
+ effect->count = value;
+
+ if (effect->replay.delay) {
+ set_bit(EFFECT_STARTED, effect->flags);
+ effect->timer.expires = RUN_AT(effect->replay.delay * HZ / 1000);
+ } else {
+ hid_lgff_ctrl_playback(hid, effect, value);
+ effect->timer.expires = RUN_AT(effect->replay.length * HZ / 1000);
+ }
+
+ add_timer(&effect->timer);
+ }
+ else { /* value == 0 */
+ if (test_and_clear_bit(EFFECT_STARTED, effect->flags)) {
+ del_timer(&effect->timer);
+
+ } else if (test_and_clear_bit(EFFECT_PLAYING, effect->flags)) {
+ del_timer(&effect->timer);
+ hid_lgff_ctrl_playback(hid, effect, value);
+ }
+
+ if (test_bit(EFFECT_PLAYING, effect->flags))
+ warn("Effect %d still playing", code);
+ }
+
+ spin_unlock_irqrestore(&lgff->lock, flags);
+
+ return 0;
+
+}
+
+/* Erase all effects this process owns */
+static int hid_lgff_flush(struct input_dev *dev, struct file *file)
+{
+ struct hid_device *hid = dev->private;
+ struct hid_ff_logitech *lgff = hid->ff_private;
+ int i;
+
+ for (i=0; i<dev->ff_effects_max; ++i) {
+
+ /*NOTE: no need to lock here. The only times EFFECT_USED is
+ modified is when effects are uploaded or when an effect is
+ erased. But a process cannot close its dev/input/eventX fd
+ and perform ioctls on the same fd all at the same time */
+ if ( current->pid == lgff->effects[i].owner
+ && test_bit(EFFECT_USED, lgff->effects[i].flags)) {
+
+ if (hid_lgff_erase(dev, i))
+ warn("erase effect %d failed", i);
+ }
+
+ }
+
+ return 0;
+}
+
+static int hid_lgff_erase(struct input_dev *dev, int id)
+{
+ struct hid_device *hid = dev->private;
+ struct hid_ff_logitech *lgff = hid->ff_private;
+ unsigned long flags;
+
+ if (!LGFF_CHECK_OWNERSHIP(id, lgff)) return -EACCES;
+
+ spin_lock_irqsave(&lgff->lock, flags);
+ hid_lgff_ctrl_playback(hid, lgff->effects + id, 0);
+ lgff->effects[id].flags[0] = 0;
+ spin_unlock_irqrestore(&lgff->lock, flags);
+
+ return 0;
+}
+
+static int hid_lgff_upload_effect(struct input_dev* input,
+ struct ff_effect* effect)
+{
+ struct hid_device *hid = input->private;
+ struct hid_ff_logitech *lgff = hid->ff_private;
+ struct lgff_effect new;
+ int id;
+ unsigned long flags;
+
+ dbg("ioctl rumble");
+
+ if (!test_bit(effect->type, input->ffbit)) return -EINVAL;
+
+ if (effect->type != FF_RUMBLE) return -EINVAL;
+
+ spin_lock_irqsave(&lgff->lock, flags);
+
+ if (effect->id == -1) {
+ int i;
+
+ for (i=0; i<LGFF_EFFECTS && test_bit(EFFECT_USED, lgff->effects[i].flags); ++i);
+ if (i >= LGFF_EFFECTS) {
+ spin_unlock_irqrestore(&lgff->lock, flags);
+ return -ENOSPC;
+ }
+
+ effect->id = i;
+ lgff->effects[i].owner = current->pid;
+ lgff->effects[i].flags[0] = 0;
+ set_bit(EFFECT_USED, lgff->effects[i].flags);
+ }
+ else if (!LGFF_CHECK_OWNERSHIP(effect->id, lgff)) {
+ spin_unlock_irqrestore(&lgff->lock, flags);
+ return -EACCES;
+ }
+
+ id = effect->id;
+ new = lgff->effects[id];
+
+ new.right = effect->u.rumble.strong_magnitude >> 9;
+ new.left = effect->u.rumble.weak_magnitude >> 9;
+ new.replay = effect->replay;
+
+ /* If we updated an effect that was being played, we need to remake
+ the rumble effect */
+ if (test_bit(EFFECT_STARTED, lgff->effects[id].flags)
+ || test_bit(EFFECT_STARTED, lgff->effects[id].flags)) {
+
+ /* Changing replay parameters is not allowed (for the time
+ being) */
+ if (new.replay.delay != lgff->effects[id].replay.delay
+ || new.replay.length != lgff->effects[id].replay.length) {
+ spin_unlock_irqrestore(&lgff->lock, flags);
+ return -ENOSYS;
+ }
+
+ lgff->effects[id] = new;
+ hid_lgff_make_rumble(hid);
+
+ } else {
+ lgff->effects[id] = new;
+ }
+
+ spin_unlock_irqrestore(&lgff->lock, flags);
+ return 0;
+}
+
+static void hid_lgff_xmit(struct hid_device* hid)
+{
+ struct hid_ff_logitech *lgff = hid->ff_private;
+ int err;
+ int tail;
+ unsigned long flags;
+
+ spin_lock_irqsave(&lgff->xmit_lock, flags);
+
+ tail = lgff->xmit_tail;
+ if (lgff->xmit_head == tail) {
+ clear_bit(XMIT_RUNNING, lgff->xmit_flags);
+ spin_unlock_irqrestore(&lgff->xmit_lock, flags);
+ return;
+ }
+ lgff->buf[3] = lgff->xmit_data[tail].left;
+ lgff->buf[4] = lgff->xmit_data[tail].right;
+ tail++; tail &= LGFF_BUFFER_SIZE -1;
+ lgff->xmit_tail = tail;
+
+ spin_unlock_irqrestore(&lgff->xmit_lock, flags);
+
+ lgff->urbffout->pipe = usb_sndctrlpipe(hid->dev, 0);
+ lgff->ffcr.bRequestType = USB_TYPE_CLASS | USB_DIR_OUT | USB_RECIP_INTERFACE;
+ lgff->urbffout->transfer_buffer_length = lgff->ffcr.wLength = 8;
+ lgff->ffcr.bRequest = 9;
+ lgff->ffcr.wValue = 0x0203; /*NOTE: Potential problem with
+ little/big endian */
+ lgff->ffcr.wIndex = 0;
+
+ lgff->urbffout->dev = hid->dev;
+
+ if ((err=usb_submit_urb(lgff->urbffout, GFP_ATOMIC)))
+ warn("usb_submit_urb returned %d", err);
+}
+
+static void hid_lgff_make_rumble(struct hid_device* hid)
+{
+ struct hid_ff_logitech *lgff = hid->ff_private;
+ int left = 0, right = 0;
+ int i;
+ int head, tail;
+ unsigned long flags;
+
+ for (i=0; i<LGFF_EFFECTS; ++i) {
+ if (test_bit(EFFECT_USED, lgff->effects[i].flags)
+ && test_bit(EFFECT_PLAYING, lgff->effects[i].flags)) {
+ left += lgff->effects[i].left;
+ right += lgff->effects[i].right;
+ }
+ }
+
+ spin_lock_irqsave(&lgff->xmit_lock, flags);
+
+ head = lgff->xmit_head;
+ tail = lgff->xmit_tail;
+
+ if (CIRC_SPACE(head, tail, LGFF_BUFFER_SIZE) < 1) {
+ warn("not enough space in xmit buffer to send new packet");
+ spin_unlock_irqrestore(&lgff->xmit_lock, flags);
+ return;
+ }
+
+ lgff->xmit_data[head].left = left > 0x7f ? 0x7f : left;
+ lgff->xmit_data[head].right = right > 0x7f ? 0x7f : right;
+ head++; head &= LGFF_BUFFER_SIZE -1;
+ lgff->xmit_head = head;
+
+ if (test_and_set_bit(XMIT_RUNNING, lgff->xmit_flags))
+ spin_unlock_irqrestore(&lgff->xmit_lock, flags);
+ else {
+ spin_unlock_irqrestore(&lgff->xmit_lock, flags);
+ hid_lgff_xmit(hid);
+ }
+}
+
+static void hid_lgff_ctrl_out(struct urb *urb)
+{
+ struct hid_device *hid = urb->context;
+
+ if (urb->status)
+ warn("hid_irq_ffout status %d received", urb->status);
+
+ hid_lgff_xmit(hid);
+}
+
+/* Lock must be held by caller */
+static void hid_lgff_ctrl_playback(struct hid_device *hid,
+ struct lgff_effect *effect, int play)
+{
+ if (play) {
+ set_bit(EFFECT_PLAYING, effect->flags);
+ hid_lgff_make_rumble(hid);
+
+ } else {
+ clear_bit(EFFECT_PLAYING, effect->flags);
+ hid_lgff_make_rumble(hid);
+ }
+}
+
+static void hid_lgff_timer(unsigned long timer_data)
+{
+ struct lgff_effect *effect = (struct lgff_effect*) timer_data;
+ struct hid_ff_logitech* lgff = effect->lgff;
+ int id = effect->id;
+
+ unsigned long flags;
+
+ dbg("in hid_lgff_timer");
+
+ if (id < 0 || id >= LGFF_EFFECTS) {
+ warn("Bad effect id %d", id);
+ return;
+ }
+
+ effect = lgff->effects + id;
+
+ spin_lock_irqsave(&lgff->lock, flags);
+
+ if (!test_bit(EFFECT_USED, effect->flags)) {
+ warn("Unused effect id %d", id);
+
+ } else if (test_bit(EFFECT_STARTED, effect->flags)) {
+ clear_bit(EFFECT_STARTED, effect->flags);
+ set_bit(EFFECT_PLAYING, effect->flags);
+ hid_lgff_ctrl_playback(lgff->hid, effect, 1);
+ effect->timer.expires = RUN_AT(effect->replay.length * HZ / 1000);
+ add_timer(&effect->timer);
+
+ dbg("Effect %d starts playing", id);
+ } else if (test_bit(EFFECT_PLAYING, effect->flags)) {
+ clear_bit(EFFECT_PLAYING, effect->flags);
+ hid_lgff_ctrl_playback(lgff->hid, effect, 0);
+ if (--effect->count > 0) {
+ /*TODO: check that replay.delay is non-null */
+ set_bit(EFFECT_STARTED, effect->flags);
+ effect->timer.expires = RUN_AT(effect->replay.delay * HZ / 1000);
+ add_timer(&effect->timer);
+ dbg("Effect %d restarted", id);
+ } else {
+ dbg("Effect %d stopped", id);
+ }
+ } else {
+ warn("Effect %d is not started nor playing", id);
+ }
+
+ spin_unlock_irqrestore(&lgff->lock, flags);
+}
diff --git a/drivers/usb/input/hid.h b/drivers/usb/input/hid.h
index 357e31bd3814..7862be448938 100644
--- a/drivers/usb/input/hid.h
+++ b/drivers/usb/input/hid.h
@@ -359,6 +359,11 @@ struct hid_device { /* device report descriptor */
char name[128]; /* Device name */
char phys[64]; /* Device physical location */
char uniq[64]; /* Device unique identifier (serial #) */
+
+ void *ff_private; /* Private data for the force-feedback driver */
+ void (*ff_exit)(struct hid_device*); /* Called by hid_exit_ff(hid) */
+ int (*ff_event)(struct hid_device *hid, struct input_dev *input,
+ unsigned int type, unsigned int code, int value);
};
#define HID_GLOBAL_STACK_SIZE 4
@@ -395,6 +400,7 @@ struct hid_descriptor {
#define hid_dump_input(a,b) do { } while (0)
#define hid_dump_device(c) do { } while (0)
#define hid_dump_field(a,b) do { } while (0)
+#define resolv_usage(a) do { } while (0)
#endif
#endif
@@ -419,3 +425,23 @@ int hid_find_field(struct hid_device *, unsigned int, unsigned int, struct hid_f
int hid_set_field(struct hid_field *, unsigned, __s32);
void hid_submit_report(struct hid_device *, struct hid_report *, unsigned char dir);
void hid_init_reports(struct hid_device *hid);
+int hid_find_report_by_usage(struct hid_device *hid, __u32 wanted_usage, struct hid_report **report, int type);
+
+
+#ifdef CONFIG_HID_FF
+int hid_ff_init(struct hid_device *hid);
+#else
+static inline int hid_ff_init(struct hid_device *hid) { return -1; }
+#endif
+static inline void hid_ff_exit(struct hid_device *hid)
+{
+ if (hid->ff_exit)
+ hid->ff_exit(hid);
+}
+static inline int hid_ff_event(struct hid_device *hid, struct input_dev *input,
+ unsigned int type, unsigned int code, int value)
+{
+ if (hid->ff_event)
+ return hid->ff_event(hid, input, type, code, value);
+ return -ENOSYS;
+}
diff --git a/drivers/usb/input/hiddev.c b/drivers/usb/input/hiddev.c
index 51b7808d7472..8b93b2f0a1bc 100644
--- a/drivers/usb/input/hiddev.c
+++ b/drivers/usb/input/hiddev.c
@@ -389,9 +389,7 @@ static int hiddev_ioctl(struct inode *inode, struct file *file,
dinfo.product = dev->descriptor.idProduct;
dinfo.version = dev->descriptor.bcdDevice;
dinfo.num_applications = hid->maxapplication;
- if (copy_to_user((void *) arg, &dinfo, sizeof(dinfo)))
- return -EFAULT;
- return 0;
+ return copy_to_user((void *) arg, &dinfo, sizeof(dinfo));
}
case HIDIOCGFLAG:
@@ -482,9 +480,7 @@ static int hiddev_ioctl(struct inode *inode, struct file *file,
rinfo.num_fields = report->maxfield;
- if (copy_to_user((void *) arg, &rinfo, sizeof(rinfo)))
- return -EFAULT;
- return 0;
+ return copy_to_user((void *) arg, &rinfo, sizeof(rinfo));
case HIDIOCGFIELDINFO:
{
@@ -516,9 +512,7 @@ static int hiddev_ioctl(struct inode *inode, struct file *file,
finfo.unit_exponent = field->unit_exponent;
finfo.unit = field->unit;
- if (copy_to_user((void *) arg, &finfo, sizeof(finfo)))
- return -EFAULT;
- return 0;
+ return copy_to_user((void *) arg, &finfo, sizeof(finfo));
}
case HIDIOCGUCODE:
@@ -539,17 +533,39 @@ static int hiddev_ioctl(struct inode *inode, struct file *file,
uref.usage_code = field->usage[uref.usage_index].hid;
- if (copy_to_user((void *) arg, &uref, sizeof(uref)))
- return -EFAULT;
- return 0;
+ return copy_to_user((void *) arg, &uref, sizeof(uref));
case HIDIOCGUSAGE:
+ if (copy_from_user(&uref, (void *) arg, sizeof(uref)))
+ return -EFAULT;
+
+ if (uref.report_id == HID_REPORT_ID_UNKNOWN) {
+ field = hiddev_lookup_usage(hid, &uref);
+ if (field == NULL)
+ return -EINVAL;
+ } else {
+ rinfo.report_type = uref.report_type;
+ rinfo.report_id = uref.report_id;
+ if ((report = hiddev_lookup_report(hid, &rinfo)) == NULL)
+ return -EINVAL;
+
+ if (uref.field_index >= report->maxfield)
+ return -EINVAL;
+
+ field = report->field[uref.field_index];
+ if (uref.usage_index >= field->maxusage)
+ return -EINVAL;
+ }
+
+ uref.value = field->value[uref.usage_index];
+
+ return copy_to_user((void *) arg, &uref, sizeof(uref));
+
case HIDIOCSUSAGE:
if (copy_from_user(&uref, (void *) arg, sizeof(uref)))
return -EFAULT;
- if (cmd == HIDIOCSUSAGE &&
- uref.report_type != HID_REPORT_TYPE_OUTPUT)
+ if (uref.report_type == HID_REPORT_TYPE_INPUT)
return -EINVAL;
if (uref.report_id == HID_REPORT_ID_UNKNOWN) {
@@ -570,14 +586,7 @@ static int hiddev_ioctl(struct inode *inode, struct file *file,
return -EINVAL;
}
- if (cmd == HIDIOCGUSAGE) {
- uref.value = field->value[uref.usage_index];
- if (copy_to_user((void *) arg, &uref, sizeof(uref)))
- return -EFAULT;
- return 0;
- } else {
- field->value[uref.usage_index] = uref.value;
- }
+ field->value[uref.usage_index] = uref.value;
return 0;
@@ -626,9 +635,9 @@ int hiddev_connect(struct hid_device *hid)
if (i == hid->maxapplication)
return -1;
- retval = usb_register_dev (&hiddev_fops, HIDDEV_MINOR_BASE, 1, &minor);
+ retval = usb_register_dev(&hiddev_fops, HIDDEV_MINOR_BASE, 1, &minor);
if (retval) {
- err ("Not able to get a minor for this device.");
+ err("Not able to get a minor for this device.");
return -1;
}
diff --git a/drivers/usb/input/pid.c b/drivers/usb/input/pid.c
new file mode 100644
index 000000000000..d9865cd03fbb
--- /dev/null
+++ b/drivers/usb/input/pid.c
@@ -0,0 +1,330 @@
+/*
+ * PID Force feedback support for hid devices.
+ *
+ * Copyright (c) 2002 Rodrigo Damazio.
+ * Portions by Johann Deneux and Bjorn Augustson
+ */
+
+/*
+ * 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Should you need to contact me, the author, you can do so by
+ * e-mail - mail your message to <rdamazio@lsi.usp.br>
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/mm.h>
+#include <linux/smp_lock.h>
+#include <linux/spinlock.h>
+#include <linux/input.h>
+#include <linux/usb.h>
+#include "hid.h"
+#include "pid.h"
+
+#define DEBUG
+
+MODULE_AUTHOR("Rodrigo Damazio <rdamazio@lsi.usp.br>");
+MODULE_DESCRIPTION("USB PID(Physical Interface Device) Driver");
+MODULE_LICENSE("GPL");
+
+#define CHECK_OWNERSHIP(i, hid_pid) \
+ ((i) < FF_EFFECTS_MAX && i >= 0 && \
+ test_bit(FF_PID_FLAGS_USED, &hid_pid->effects[(i)].flags) && \
+ (current->pid == 0 || \
+ (hid_pid)->effects[(i)].owner == current->pid))
+
+/* Called when a transfer is completed */
+static void hid_pid_ctrl_out(struct urb *u)
+{
+#ifdef DEBUG
+ printk("hid_pid_ctrl_out - Transfer Completed\n");
+#endif
+}
+
+static void hid_pid_exit(struct hid_device* hid)
+{
+ struct hid_ff_pid *private = hid->ff_private;
+
+ if (private->urbffout) {
+ usb_unlink_urb(private->urbffout);
+ usb_free_urb(private->urbffout);
+ }
+}
+
+static int pid_upload_periodic(struct hid_ff_pid *pid, struct ff_effect *effect, int is_update) {
+
+ printk("Requested periodic force upload\n");
+ return 0;
+}
+
+static int pid_upload_constant(struct hid_ff_pid *pid, struct ff_effect *effect, int is_update) {
+ printk("Requested constant force upload\n");
+ return 0;
+}
+
+static int pid_upload_condition(struct hid_ff_pid *pid, struct ff_effect *effect, int is_update) {
+ printk("Requested Condition force upload\n");
+ return 0;
+}
+
+static int pid_upload_ramp(struct hid_ff_pid *pid, struct ff_effect *effect, int is_update) {
+ printk("Request ramp force upload\n");
+ return 0;
+}
+
+static int hid_pid_event(struct hid_device *hid, struct input_dev *input,
+ unsigned int type, unsigned int code, int value)
+{
+#ifdef DEBUG
+ printk ("PID event received: type=%d,code=%d,value=%d.\n",type,code,value);
+#endif
+
+ if (type != EV_FF)
+ return -1;
+
+
+
+ return 0;
+}
+
+/* Lock must be held by caller */
+static void hid_pid_ctrl_playback(struct hid_device *hid,
+ struct hid_pid_effect *effect, int play)
+{
+ if (play) {
+ set_bit(FF_PID_FLAGS_PLAYING, &effect->flags);
+
+ } else {
+ clear_bit(FF_PID_FLAGS_PLAYING, &effect->flags);
+ }
+}
+
+
+static int hid_pid_erase(struct input_dev *dev, int id)
+{
+ struct hid_device *hid = dev->private;
+ struct hid_field* field;
+ struct hid_report* report;
+ struct hid_ff_pid *pid = hid->ff_private;
+ unsigned long flags;
+ unsigned wanted_report = HID_UP_PID | FF_PID_USAGE_BLOCK_FREE; /* PID Block Free Report */
+ int ret;
+
+ if (!CHECK_OWNERSHIP(id, pid)) return -EACCES;
+
+ /* Find report */
+ ret = hid_find_report_by_usage(hid, wanted_report, &report, HID_OUTPUT_REPORT);
+ if(!ret) {
+ printk("Couldn't find report\n");
+ return ret;
+ }
+
+ /* Find field */
+ field = (struct hid_field *) kmalloc(sizeof(struct hid_field), GFP_KERNEL);
+ ret = hid_set_field(field, ret, pid->effects[id].device_id);
+ if(!ret) {
+ printk("Couldn't set field\n");
+ return ret;
+ }
+
+ hid_submit_report(hid, report, USB_DIR_OUT);
+
+ spin_lock_irqsave(&pid->lock, flags);
+ hid_pid_ctrl_playback(hid, pid->effects + id, 0);
+ pid->effects[id].flags = 0;
+ spin_unlock_irqrestore(&pid->lock, flags);
+
+ return ret;
+}
+
+/* Erase all effects this process owns */
+static int hid_pid_flush(struct input_dev *dev, struct file *file)
+{
+ struct hid_device *hid = dev->private;
+ struct hid_ff_pid *pid = hid->ff_private;
+ int i;
+
+ /*NOTE: no need to lock here. The only times EFFECT_USED is
+ modified is when effects are uploaded or when an effect is
+ erased. But a process cannot close its dev/input/eventX fd
+ and perform ioctls on the same fd all at the same time */
+ for (i=0; i<dev->ff_effects_max; ++i)
+ if ( current->pid == pid->effects[i].owner
+ && test_bit(FF_PID_FLAGS_USED, &pid->effects[i].flags))
+ if (hid_pid_erase(dev, i))
+ warn("erase effect %d failed", i);
+
+ return 0;
+}
+
+
+static int hid_pid_upload_effect(struct input_dev *dev,
+ struct ff_effect *effect)
+{
+ struct hid_ff_pid* pid_private = (struct hid_ff_pid*)(dev->private);
+ int ret;
+ int is_update;
+ int flags=0;
+
+#ifdef DEBUG
+ printk("Upload effect called: effect_type=%x\n",effect->type);
+#endif
+ /* Check this effect type is supported by this device */
+ if (!test_bit(effect->type, dev->ffbit)) {
+#ifdef DEBUG
+ printk("Invalid kind of effect requested.\n");
+#endif
+ return -EINVAL;
+ }
+
+ /*
+ * If we want to create a new effect, get a free id
+ */
+ if (effect->id == -1) {
+ int id=0;
+
+ // Spinlock so we don`t get a race condition when choosing IDs
+ spin_lock_irqsave(&pid_private->lock,flags);
+
+ while(id < FF_EFFECTS_MAX)
+ if (!test_and_set_bit(FF_PID_FLAGS_USED, &pid_private->effects[id++].flags))
+ break;
+
+ if ( id == FF_EFFECTS_MAX) {
+// TEMP - We need to get ff_effects_max correctly first: || id >= dev->ff_effects_max) {
+#ifdef DEBUG
+ printk("Not enough device memory\n");
+#endif
+ return -ENOMEM;
+ }
+
+ effect->id = id;
+#ifdef DEBUG
+ printk("Effect ID is %d\n.",id);
+#endif
+ pid_private->effects[id].owner = current->pid;
+ pid_private->effects[id].flags = (1<<FF_PID_FLAGS_USED);
+ spin_unlock_irqrestore(&pid_private->lock,flags);
+
+ is_update = FF_PID_FALSE;
+ }
+ else {
+ /* We want to update an effect */
+ if (!CHECK_OWNERSHIP(effect->id, pid_private)) return -EACCES;
+
+ /* Parameter type cannot be updated */
+ if (effect->type != pid_private->effects[effect->id].effect.type)
+ return -EINVAL;
+
+ /* Check the effect is not already being updated */
+ if (test_bit(FF_PID_FLAGS_UPDATING, &pid_private->effects[effect->id].flags)) {
+ return -EAGAIN;
+ }
+
+ is_update = FF_PID_TRUE;
+ }
+
+ /*
+ * Upload the effect
+ */
+ switch (effect->type) {
+ case FF_PERIODIC:
+ ret = pid_upload_periodic(pid_private, effect, is_update);
+ break;
+
+ case FF_CONSTANT:
+ ret = pid_upload_constant(pid_private, effect, is_update);
+ break;
+
+ case FF_SPRING:
+ case FF_FRICTION:
+ case FF_DAMPER:
+ case FF_INERTIA:
+ ret = pid_upload_condition(pid_private, effect, is_update);
+ break;
+
+ case FF_RAMP:
+ ret = pid_upload_ramp(pid_private, effect, is_update);
+ break;
+
+ default:
+#ifdef DEBUG
+ printk("Invalid type of effect requested - %x.\n", effect->type);
+#endif
+ return -EINVAL;
+ }
+ /* If a packet was sent, forbid new updates until we are notified
+ * that the packet was updated
+ */
+ if (ret == 0)
+ set_bit(FF_PID_FLAGS_UPDATING, &pid_private->effects[effect->id].flags);
+ pid_private->effects[effect->id].effect = *effect;
+ return ret;
+}
+
+int hid_pid_init(struct hid_device *hid)
+{
+ struct hid_ff_pid *private;
+
+ private = hid->ff_private = kmalloc(sizeof(struct hid_ff_pid), GFP_KERNEL);
+ if (!private) return -1;
+
+ memset(private,0,sizeof(struct hid_ff_pid));
+
+ hid->ff_private = private; /* 'cause memset can move the block away */
+
+ private->hid = hid;
+
+ hid->ff_exit = hid_pid_exit;
+ hid->ff_event = hid_pid_event;
+
+ /* Open output URB */
+ if (!(private->urbffout = usb_alloc_urb(0, GFP_KERNEL))) {
+ kfree(private);
+ return -1;
+ }
+
+ usb_fill_control_urb(private->urbffout, hid->dev,0,(void *) &private->ffcr,private->ctrl_buffer,8,hid_pid_ctrl_out,hid);
+ hid->input.upload_effect = hid_pid_upload_effect;
+ hid->input.flush = hid_pid_flush;
+ hid->input.ff_effects_max = 8; // A random default
+ set_bit(EV_FF, hid->input.evbit);
+ set_bit(EV_FF_STATUS, hid->input.evbit);
+
+ spin_lock_init(&private->lock);
+
+ printk(KERN_INFO "Force feedback driver for PID devices by Rodrigo Damazio <rdamazio@lsi.usp.br>.\n");
+
+ return 0;
+}
+
+static int __init hid_pid_modinit(void)
+{
+ return 0;
+}
+
+static void __exit hid_pid_modexit(void)
+{
+
+}
+
+module_init(hid_pid_modinit);
+module_exit(hid_pid_modexit);
+
+
diff --git a/drivers/usb/input/pid.h b/drivers/usb/input/pid.h
new file mode 100644
index 000000000000..a27e48b8026d
--- /dev/null
+++ b/drivers/usb/input/pid.h
@@ -0,0 +1,62 @@
+/*
+ * PID Force feedback support for hid devices.
+ *
+ * Copyright (c) 2002 Rodrigo Damazio.
+ */
+
+/*
+ * 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Should you need to contact me, the author, you can do so by
+ * e-mail - mail your message to <rdamazio@lsi.usp.br>
+ */
+
+#define FF_EFFECTS_MAX 64
+
+#define FF_PID_FLAGS_USED 1 /* If the effect exists */
+#define FF_PID_FLAGS_UPDATING 2 /* If the effect is being updated */
+#define FF_PID_FLAGS_PLAYING 3 /* If the effect is currently being played */
+
+#define FF_PID_FALSE 0
+#define FF_PID_TRUE 1
+
+struct hid_pid_effect {
+ unsigned long flags;
+ pid_t owner;
+ unsigned int device_id; // The device-assigned ID
+ struct ff_effect effect;
+};
+
+struct hid_ff_pid {
+ struct hid_device *hid;
+ unsigned long int gain;
+
+ struct urb *urbffout;
+ struct usb_ctrlrequest ffcr;
+ spinlock_t lock;
+
+ char ctrl_buffer[8];
+
+ struct hid_pid_effect effects[FF_EFFECTS_MAX];
+};
+
+/*
+ * Constants from the PID usage table (still far from complete)
+ */
+
+#define FF_PID_USAGE_BLOCK_LOAD 0x89UL
+#define FF_PID_USAGE_BLOCK_FREE 0x90UL
+#define FF_PID_USAGE_NEW_EFFECT 0xABUL
+#define FF_PID_USAGE_POOL_REPORT 0x7FUL
diff --git a/drivers/usb/input/powermate.c b/drivers/usb/input/powermate.c
new file mode 100644
index 000000000000..378cfa3c0717
--- /dev/null
+++ b/drivers/usb/input/powermate.c
@@ -0,0 +1,360 @@
+/*
+ * A driver for the Griffin Technology, Inc. "PowerMate" USB controller dial.
+ *
+ * v1.0, (c)2002 William R Sowerbutts <will@sowerbutts.com>
+ *
+ * This device is a stainless steel knob which connects over USB. It can measure
+ * clockwise and anticlockwise rotation. The dial also acts as a pushbutton with
+ * a spring for automatic release. The base contains a pair of LEDs which illuminate
+ * the translucent base. It rotates without limit and reports its relative rotation
+ * back to the host when polled by the USB controller.
+ *
+ * Testing with the knob I have has shown that it measures approximately 94 "clicks"
+ * for one full rotation. Testing with my High Speed Rotation Actuator (ok, it was
+ * a variable speed cordless electric drill) has shown that the device can measure
+ * speeds of up to 7 clicks either clockwise or anticlockwise between pollings from
+ * the host. If it counts more than 7 clicks before it is polled, it will wrap back
+ * to zero and start counting again. This was at quite high speed, however, almost
+ * certainly faster than the human hand could turn it.
+ *
+ * The device's microcontroller can be programmed to set the LED to either a constant
+ * intensity, or to a rhythmic pulsing. Several patterns and speeds are available.
+ *
+ * Griffin were very happy to provide documentation and free hardware for development.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/input.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/usb.h>
+
+#define POWERMATE_VENDOR 0x077d /* Griffin Technology, Inc. */
+#define POWERMATE_PRODUCT_NEW 0x0410 /* Griffin PowerMate */
+#define POWERMATE_PRODUCT_OLD 0x04AA /* Griffin soundKnob */
+
+#define CONTOUR_VENDOR 0x05f3 /* Contour Design, Inc. */
+#define CONTOUR_JOG 0x0240 /* Jog and Shuttle */
+
+/* these are the command codes we send to the device */
+#define SET_STATIC_BRIGHTNESS 0x01
+#define SET_PULSE_ASLEEP 0x02
+#define SET_PULSE_AWAKE 0x03
+#define SET_PULSE_MODE 0x04
+
+/* these refer to bits in the powermate_device's requires_update field. */
+#define UPDATE_STATIC_BRIGHTNESS (1<<0)
+#define UPDATE_PULSE_ASLEEP (1<<1)
+#define UPDATE_PULSE_AWAKE (1<<2)
+#define UPDATE_PULSE_MODE (1<<3)
+
+#define POWERMATE_PAYLOAD_SIZE 3
+struct powermate_device {
+ signed char data[POWERMATE_PAYLOAD_SIZE];
+ struct urb irq, config;
+ struct usb_ctrlrequest configcr;
+ struct usb_device *udev;
+ struct input_dev input;
+ struct semaphore lock;
+ int static_brightness;
+ int pulse_speed;
+ int pulse_table;
+ int pulse_asleep;
+ int pulse_awake;
+ int requires_update; // physical settings which are out of sync
+ char phys[64];
+};
+
+static char pm_name_powermate[] = "Griffin PowerMate";
+static char pm_name_soundknob[] = "Griffin SoundKnob";
+
+static void powermate_config_complete(struct urb *urb); /* forward declararation of callback */
+
+/* Callback for data arriving from the PowerMate over the USB interrupt pipe */
+static void powermate_irq(struct urb *urb)
+{
+ struct powermate_device *pm = urb->context;
+
+ if(urb->status)
+ return;
+
+ /* handle updates to device state */
+ input_report_key(&pm->input, BTN_0, pm->data[0] & 0x01);
+ input_report_rel(&pm->input, REL_DIAL, pm->data[1]);
+}
+
+/* Decide if we need to issue a control message and do so. Must be called with pm->lock down */
+static void powermate_sync_state(struct powermate_device *pm)
+{
+ if(pm->requires_update == 0)
+ return; /* no updates are required */
+ if(pm->config.status == -EINPROGRESS)
+ return; /* an update is already in progress; it'll issue this update when it completes */
+
+ if(pm->requires_update & UPDATE_STATIC_BRIGHTNESS){
+ pm->configcr.wValue = cpu_to_le16( SET_STATIC_BRIGHTNESS );
+ pm->configcr.wIndex = cpu_to_le16( pm->static_brightness );
+ pm->requires_update &= ~UPDATE_STATIC_BRIGHTNESS;
+ }else if(pm->requires_update & UPDATE_PULSE_ASLEEP){
+ pm->configcr.wValue = cpu_to_le16( SET_PULSE_ASLEEP );
+ pm->configcr.wIndex = cpu_to_le16( pm->pulse_asleep ? 1 : 0 );
+ pm->requires_update &= ~UPDATE_PULSE_ASLEEP;
+ }else if(pm->requires_update & UPDATE_PULSE_AWAKE){
+ pm->configcr.wValue = cpu_to_le16( SET_PULSE_AWAKE );
+ pm->configcr.wIndex = cpu_to_le16( pm->pulse_awake ? 1 : 0 );
+ pm->requires_update &= ~UPDATE_PULSE_AWAKE;
+ }else if(pm->requires_update & UPDATE_PULSE_MODE){
+ int op, arg;
+ /* the powermate takes an operation and an argument for its pulse algorithm.
+ the operation can be:
+ 0: divide the speed
+ 1: pulse at normal speed
+ 2: multiply the speed
+ the argument only has an effect for operations 0 and 2, and ranges between
+ 1 (least effect) to 255 (maximum effect).
+
+ thus, several states are equivalent and are coalesced into one state.
+
+ we map this onto a range from 0 to 510, with:
+ 0 -- 254 -- use divide (0 = slowest)
+ 255 -- use normal speed
+ 256 -- 510 -- use multiple (510 = fastest).
+
+ Only values of 'arg' quite close to 255 are particularly useful/spectacular.
+ */
+ if(pm->pulse_speed < 255){
+ op = 0; // divide
+ arg = 255 - pm->pulse_speed;
+ }else if(pm->pulse_speed > 255){
+ op = 2; // multiply
+ arg = pm->pulse_speed - 255;
+ }else{
+ op = 1; // normal speed
+ arg = 0; // can be any value
+ }
+ pm->configcr.wValue = cpu_to_le16( (pm->pulse_table << 8) | SET_PULSE_MODE );
+ pm->configcr.wIndex = cpu_to_le16( (arg << 8) | op );
+ pm->requires_update &= ~UPDATE_PULSE_MODE;
+ }else{
+ printk(KERN_ERR "powermate: unknown update required");
+ pm->requires_update = 0; /* fudge the bug */
+ return;
+ }
+
+/* printk("powermate: %04x %04x\n", pm->configcr.wValue, pm->configcr.wIndex); */
+
+ pm->config.dev = pm->udev; /* is this necessary? */
+ pm->configcr.bRequestType = 0x41; /* vendor request */
+ pm->configcr.bRequest = 0x01;
+ pm->configcr.wLength = 0;
+
+ FILL_CONTROL_URB(&pm->config, pm->udev, usb_sndctrlpipe(pm->udev, 0),
+ (void*)&pm->configcr, 0, 0, powermate_config_complete, pm);
+
+ if(usb_submit_urb(&pm->config, GFP_ATOMIC))
+ printk(KERN_ERR "powermate: usb_submit_urb(config) failed");
+}
+
+/* Called when our asynchronous control message completes. We may need to issue another immediately */
+static void powermate_config_complete(struct urb *urb)
+{
+ struct powermate_device *pm = urb->context;
+
+ if(urb->status)
+ printk(KERN_ERR "powermate: config urb returned %d\n", urb->status);
+
+ down(&pm->lock);
+ powermate_sync_state(pm);
+ up(&pm->lock);
+}
+
+/* Set the LED up as described and begin the sync with the hardware if required */
+static void powermate_pulse_led(struct powermate_device *pm, int static_brightness, int pulse_speed,
+ int pulse_table, int pulse_asleep, int pulse_awake)
+{
+ if(pulse_speed < 0)
+ pulse_speed = 0;
+ if(pulse_table < 0)
+ pulse_table = 0;
+ if(pulse_speed > 510)
+ pulse_speed = 510;
+ if(pulse_table > 2)
+ pulse_table = 2;
+
+ pulse_asleep = !!pulse_asleep;
+ pulse_awake = !!pulse_awake;
+
+ down(&pm->lock);
+
+ /* mark state updates which are required */
+ if(static_brightness != pm->static_brightness){
+ pm->static_brightness = static_brightness;
+ pm->requires_update |= UPDATE_STATIC_BRIGHTNESS;
+ }
+ if(pulse_asleep != pm->pulse_asleep){
+ pm->pulse_asleep = pulse_asleep;
+ pm->requires_update |= UPDATE_PULSE_ASLEEP;
+ }
+ if(pulse_awake != pm->pulse_awake){
+ pm->pulse_awake = pulse_awake;
+ pm->requires_update |= UPDATE_PULSE_AWAKE;
+ }
+ if(pulse_speed != pm->pulse_speed || pulse_table != pm->pulse_table){
+ pm->pulse_speed = pulse_speed;
+ pm->pulse_table = pulse_table;
+ pm->requires_update |= UPDATE_PULSE_MODE;
+ }
+
+ powermate_sync_state(pm);
+
+ up(&pm->lock);
+}
+
+/* Callback from the Input layer when an event arrives from userspace to configure the LED */
+static int powermate_input_event(struct input_dev *dev, unsigned int type, unsigned int code, int _value)
+{
+ unsigned int command = (unsigned int)_value;
+ struct powermate_device *pm = dev->private;
+
+ if(type == EV_MSC && code == MSC_PULSELED){
+ /*
+ bits 0- 7: 8 bits: LED brightness
+ bits 8-16: 9 bits: pulsing speed modifier (0 ... 510); 0-254 = slower, 255 = standard, 256-510 = faster.
+ bits 17-18: 2 bits: pulse table (0, 1, 2 valid)
+ bit 19: 1 bit : pulse whilst asleep?
+ bit 20: 1 bit : pulse constantly?
+ */
+ int static_brightness = command & 0xFF; // bits 0-7
+ int pulse_speed = (command >> 8) & 0x1FF; // bits 8-16
+ int pulse_table = (command >> 17) & 0x3; // bits 17-18
+ int pulse_asleep = (command >> 19) & 0x1; // bit 19
+ int pulse_awake = (command >> 20) & 0x1; // bit 20
+
+ powermate_pulse_led(pm, static_brightness, pulse_speed, pulse_table, pulse_asleep, pulse_awake);
+ }
+
+ return 0;
+}
+
+/* Called whenever a USB device matching one in our supported devices table is connected */
+static void *powermate_probe(struct usb_device *udev, unsigned int ifnum, const struct usb_device_id *id)
+{
+ struct usb_interface_descriptor *interface;
+ struct usb_endpoint_descriptor *endpoint;
+ struct powermate_device *pm;
+ int pipe, maxp;
+ char path[64];
+
+ interface = udev->config[0].interface[ifnum].altsetting + 0;
+ endpoint = interface->endpoint + 0;
+ if (!(endpoint->bEndpointAddress & 0x80)) return NULL;
+ if ((endpoint->bmAttributes & 3) != 3) return NULL;
+
+ usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+ 0x0a, USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+ 0, interface->bInterfaceNumber, NULL, 0,
+ HZ * USB_CTRL_SET_TIMEOUT);
+
+ if (!(pm = kmalloc(sizeof(struct powermate_device), GFP_KERNEL)))
+ return NULL;
+
+ memset(pm, 0, sizeof(struct powermate_device));
+ pm->udev = udev;
+
+ init_MUTEX(&pm->lock);
+
+ /* get a handle to the interrupt data pipe */
+ pipe = usb_rcvintpipe(udev, endpoint->bEndpointAddress);
+ maxp = usb_maxpacket(udev, pipe, usb_pipeout(pipe));
+
+ if(maxp != POWERMATE_PAYLOAD_SIZE)
+ printk("powermate: Expected payload of %d bytes, found %d bytes!\n", POWERMATE_PAYLOAD_SIZE, maxp);
+
+ FILL_INT_URB(&pm->irq, udev, pipe, pm->data, POWERMATE_PAYLOAD_SIZE, powermate_irq, pm, endpoint->bInterval);
+
+ /* register our interrupt URB with the USB system */
+ if(usb_submit_urb(&pm->irq, GFP_KERNEL)) {
+ kfree(pm);
+ return NULL; /* failure */
+ }
+
+ switch (udev->descriptor.idProduct) {
+ case POWERMATE_PRODUCT_NEW: pm->input.name = pm_name_powermate; break;
+ case POWERMATE_PRODUCT_OLD: pm->input.name = pm_name_soundknob; break;
+ default:
+ pm->input.name = pm_name_soundknob;
+ printk(KERN_WARNING "powermate: unknown product id %04x\n", udev->descriptor.idProduct);
+ }
+
+ pm->input.private = pm;
+ pm->input.evbit[0] = BIT(EV_KEY) | BIT(EV_REL) | BIT(EV_MSC);
+ pm->input.keybit[LONG(BTN_0)] = BIT(BTN_0);
+ pm->input.relbit[LONG(REL_DIAL)] = BIT(REL_DIAL);
+ pm->input.mscbit[LONG(MSC_PULSELED)] = BIT(MSC_PULSELED);
+ pm->input.idbus = BUS_USB;
+ pm->input.idvendor = udev->descriptor.idVendor;
+ pm->input.idproduct = udev->descriptor.idProduct;
+ pm->input.idversion = udev->descriptor.bcdDevice;
+ pm->input.event = powermate_input_event;
+
+ input_register_device(&pm->input);
+
+ usb_make_path(udev, path, 64);
+ snprintf(pm->phys, 64, "%s/input0", path);
+ printk(KERN_INFO "input: %s on %s\n", pm->input.name, pm->input.phys);
+
+ /* force an update of everything */
+ pm->requires_update = UPDATE_PULSE_ASLEEP | UPDATE_PULSE_AWAKE | UPDATE_PULSE_MODE | UPDATE_STATIC_BRIGHTNESS;
+ powermate_pulse_led(pm, 0x80, 255, 0, 1, 0); // set default pulse parameters
+
+ return pm;
+}
+
+/* Called when a USB device we've accepted ownership of is removed */
+static void powermate_disconnect(struct usb_device *dev, void *ptr)
+{
+ struct powermate_device *pm = ptr;
+ down(&pm->lock);
+ pm->requires_update = 0;
+ usb_unlink_urb(&pm->irq);
+ input_unregister_device(&pm->input);
+
+ kfree(pm);
+}
+
+static struct usb_device_id powermate_devices [] = {
+ { USB_DEVICE(POWERMATE_VENDOR, POWERMATE_PRODUCT_NEW) },
+ { USB_DEVICE(POWERMATE_VENDOR, POWERMATE_PRODUCT_OLD) },
+ { USB_DEVICE(CONTOUR_VENDOR, CONTOUR_JOG) },
+ { } /* Terminating entry */
+};
+
+MODULE_DEVICE_TABLE (usb, powermate_devices);
+
+static struct usb_driver powermate_driver = {
+ name: "powermate",
+ probe: powermate_probe,
+ disconnect: powermate_disconnect,
+ id_table: powermate_devices,
+};
+
+int powermate_init(void)
+{
+ if (usb_register(&powermate_driver) < 0)
+ return -1;
+ return 0;
+}
+
+void powermate_cleanup(void)
+{
+ usb_deregister(&powermate_driver);
+}
+
+module_init(powermate_init);
+module_exit(powermate_cleanup);
+
+MODULE_AUTHOR( "William R Sowerbutts" );
+MODULE_DESCRIPTION( "Griffin Technology, Inc PowerMate driver" );
+MODULE_LICENSE("GPL");