# This is a BitKeeper generated patch for the following project: # Project Name: Linux kernel tree # This patch format is intended for GNU patch command version 2.5 or higher. # This patch includes the following deltas: # ChangeSet 1.636 -> 1.637 # drivers/usb/input/hid.h 1.14 -> 1.15 # drivers/usb/input/Makefile 1.6 -> 1.7 # drivers/usb/input/hid-input.c 1.5 -> 1.6 # drivers/usb/input/hiddev.c 1.14 -> 1.15 # drivers/usb/input/hid-core.c 1.25 -> 1.26 # drivers/usb/input/Config.help 1.3 -> 1.4 # drivers/usb/input/Config.in 1.4 -> 1.5 # (new) -> 1.1 drivers/usb/input/hid-lgff.c # (new) -> 1.1 drivers/usb/input/hid-lg3dff.c # (new) -> 1.1 drivers/usb/input/powermate.c # (new) -> 1.1 drivers/usb/input/fixp-arith.h # (new) -> 1.1 drivers/usb/input/pid.c # (new) -> 1.1 drivers/usb/input/pid.h # (new) -> 1.1 drivers/usb/input/hid-ff.c # # The following is the BitKeeper ChangeSet Log # -------------------------------------------- # 02/07/12 vojtech@suse.cz 1.637 # [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. # -------------------------------------------- # diff -Nru a/drivers/usb/input/Config.help b/drivers/usb/input/Config.help --- a/drivers/usb/input/Config.help Fri Jul 12 11:45:38 2002 +++ b/drivers/usb/input/Config.help Fri Jul 12 11:45:38 2002 @@ -23,6 +23,23 @@ 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 @@ -82,4 +99,18 @@ 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 wacom.o. If you want to compile it as a + module, say M here and read . + +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 . diff -Nru a/drivers/usb/input/Config.in b/drivers/usb/input/Config.in --- a/drivers/usb/input/Config.in Fri Jul 12 11:45:38 2002 +++ b/drivers/usb/input/Config.in Fri Jul 12 11:45:38 2002 @@ -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 @@ 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 -Nru a/drivers/usb/input/Makefile b/drivers/usb/input/Makefile --- a/drivers/usb/input/Makefile Fri Jul 12 11:45:38 2002 +++ b/drivers/usb/input/Makefile Fri Jul 12 11:45:38 2002 @@ -12,12 +12,27 @@ 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 -Nru a/drivers/usb/input/fixp-arith.h b/drivers/usb/input/fixp-arith.h --- /dev/null Wed Dec 31 16:00:00 1969 +++ b/drivers/usb/input/fixp-arith.h Fri Jul 12 11:45:38 2002 @@ -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 + */ + +#include + +// The type representing fixed-point values +typedef s16 fixp_t; + +#define FRAC_N 8 +#define FRAC_MASK ((1< 123.0 */ +inline fixp_t fixp_new(s16 a) +{ + return a< -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 -Nru a/drivers/usb/input/hid-core.c b/drivers/usb/input/hid-core.c --- a/drivers/usb/input/hid-core.c Fri Jul 12 11:45:38 2002 +++ b/drivers/usb/input/hid-core.c Fri Jul 12 11:45:38 2002 @@ -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 @@ 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 @@ { 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ { 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 -Nru a/drivers/usb/input/hid-ff.c b/drivers/usb/input/hid-ff.c --- /dev/null Wed Dec 31 16:00:00 1969 +++ b/drivers/usb/input/hid-ff.c Fri Jul 12 11:45:38 2002 @@ -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 + */ + +#include + +#define DEBUG +#include + +#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 -Nru a/drivers/usb/input/hid-input.c b/drivers/usb/input/hid-input.c --- a/drivers/usb/input/hid-input.c Fri Jul 12 11:45:38 2002 +++ b/drivers/usb/input/hid-input.c Fri Jul 12 11:45:38 2002 @@ -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 @@ 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 @@ 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; @@ -379,6 +433,9 @@ struct hid_device *hid = dev->private; 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"); diff -Nru a/drivers/usb/input/hid-lg3dff.c b/drivers/usb/input/hid-lg3dff.c --- /dev/null Wed Dec 31 16:00:00 1969 +++ b/drivers/usb/input/hid-lg3dff.c Fri Jul 12 11:45:38 2002 @@ -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 + */ + +#include +#include + +#define DEBUG +#include + +#include + +#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 && ieffects[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 \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; iff_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; ieffects[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; ieffects +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 -Nru a/drivers/usb/input/hid-lgff.c b/drivers/usb/input/hid-lgff.c --- /dev/null Wed Dec 31 16:00:00 1969 +++ b/drivers/usb/input/hid-lgff.c Fri Jul 12 11:45:38 2002 @@ -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 + */ + +#include +#include + +#define DEBUG +#include + +#include + +#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 && ieffects[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; ieffects[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 \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; iff_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; ieffects[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; ieffects[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 -Nru a/drivers/usb/input/hid.h b/drivers/usb/input/hid.h --- a/drivers/usb/input/hid.h Fri Jul 12 11:45:38 2002 +++ b/drivers/usb/input/hid.h Fri Jul 12 11:45:38 2002 @@ -359,6 +359,11 @@ 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 @@ #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_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 -Nru a/drivers/usb/input/hiddev.c b/drivers/usb/input/hiddev.c --- a/drivers/usb/input/hiddev.c Fri Jul 12 11:45:38 2002 +++ b/drivers/usb/input/hiddev.c Fri Jul 12 11:45:38 2002 @@ -389,9 +389,7 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 -Nru a/drivers/usb/input/pid.c b/drivers/usb/input/pid.c --- /dev/null Wed Dec 31 16:00:00 1969 +++ b/drivers/usb/input/pid.c Fri Jul 12 11:45:38 2002 @@ -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 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "hid.h" +#include "pid.h" + +#define DEBUG + +MODULE_AUTHOR("Rodrigo Damazio "); +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; iff_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<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 .\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 -Nru a/drivers/usb/input/pid.h b/drivers/usb/input/pid.h --- /dev/null Wed Dec 31 16:00:00 1969 +++ b/drivers/usb/input/pid.h Fri Jul 12 11:45:38 2002 @@ -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 + */ + +#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 -Nru a/drivers/usb/input/powermate.c b/drivers/usb/input/powermate.c --- /dev/null Wed Dec 31 16:00:00 1969 +++ b/drivers/usb/input/powermate.c Fri Jul 12 11:45:38 2002 @@ -0,0 +1,360 @@ +/* + * A driver for the Griffin Technology, Inc. "PowerMate" USB controller dial. + * + * v1.0, (c)2002 William R Sowerbutts + * + * 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 +#include +#include +#include +#include +#include + +#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");