Commit 45090330 authored by Vojtech Pavlik's avatar Vojtech Pavlik Committed by Greg Kroah-Hartman

[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.
parent 774921a2
...@@ -23,6 +23,23 @@ CONFIG_USB_HIDINPUT ...@@ -23,6 +23,23 @@ CONFIG_USB_HIDINPUT
If unsure, say Y. 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 CONFIG_USB_HIDDEV
Say Y here if you want to support HID devices (from the USB Say Y here if you want to support HID devices (from the USB
specification standpoint) that aren't strictly user interface specification standpoint) that aren't strictly user interface
...@@ -83,3 +100,17 @@ CONFIG_USB_WACOM ...@@ -83,3 +100,17 @@ CONFIG_USB_WACOM
inserted in and removed from the running kernel whenever you want). 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 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>. 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>.
...@@ -3,10 +3,16 @@ ...@@ -3,10 +3,16 @@
# #
comment 'USB Human Interface Devices (HID)' comment 'USB Human Interface Devices (HID)'
dep_tristate ' USB Human Interface Device (full HID) support' CONFIG_USB_HID $CONFIG_USB dep_tristate ' USB Human Interface Device (full HID) support' CONFIG_USB_HID $CONFIG_USB
if [ "$CONFIG_INPUT" = "n" ]; then if [ "$CONFIG_INPUT" = "n" ]; then
comment ' Input core support is needed for USB HID input layer or HIDBP support' comment ' Input core support is needed for USB HID input layer or HIDBP support'
fi fi
dep_mbool ' HID input layer support' CONFIG_USB_HIDINPUT $CONFIG_INPUT $CONFIG_USB_HID 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 dep_mbool ' /dev/hiddev raw HID device support' CONFIG_USB_HIDDEV $CONFIG_USB_HID
if [ "$CONFIG_USB_HID" != "y" ]; then if [ "$CONFIG_USB_HID" != "y" ]; then
...@@ -16,4 +22,5 @@ fi ...@@ -16,4 +22,5 @@ fi
dep_tristate ' Aiptek 6000U/8000U tablet support' CONFIG_USB_AIPTEK $CONFIG_USB $CONFIG_INPUT 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 ' 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
...@@ -12,12 +12,27 @@ endif ...@@ -12,12 +12,27 @@ endif
ifeq ($(CONFIG_USB_HIDINPUT),y) ifeq ($(CONFIG_USB_HIDINPUT),y)
hid-objs += hid-input.o hid-objs += hid-input.o
endif 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_AIPTEK) += aiptek.o
obj-$(CONFIG_USB_HID) += hid.o obj-$(CONFIG_USB_HID) += hid.o
obj-$(CONFIG_USB_KBD) += usbkbd.o obj-$(CONFIG_USB_KBD) += usbkbd.o
obj-$(CONFIG_USB_MOUSE) += usbmouse.o obj-$(CONFIG_USB_MOUSE) += usbmouse.o
obj-$(CONFIG_USB_WACOM) += wacom.o obj-$(CONFIG_USB_WACOM) += wacom.o
obj-$(CONFIG_USB_POWERMATE) += powermate.o
include $(TOPDIR)/Rules.make include $(TOPDIR)/Rules.make
#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
/* /*
* $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) 1999 Andreas Gal
* Copyright (c) 2000-2001 Vojtech Pavlik * Copyright (c) 2000-2001 Vojtech Pavlik
...@@ -108,11 +108,10 @@ static struct hid_field *hid_register_field(struct hid_report *report, unsigned ...@@ -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) memset(field, 0, sizeof(struct hid_field) + usages * sizeof(struct hid_usage)
+ values * sizeof(unsigned)); + values * sizeof(unsigned));
report->field[report->maxfield] = field; report->field[report->maxfield++] = field;
field->usage = (struct hid_usage *)(field + 1); field->usage = (struct hid_usage *)(field + 1);
field->value = (unsigned *)(field->usage + usages); field->value = (unsigned *)(field->usage + usages);
field->report = report; field->report = report;
field->index = report->maxfield++;
return field; return field;
} }
...@@ -518,6 +517,8 @@ static void hid_free_device(struct hid_device *device) ...@@ -518,6 +517,8 @@ static void hid_free_device(struct hid_device *device)
{ {
unsigned i,j; unsigned i,j;
hid_ff_exit(device);
for (i = 0; i < HID_REPORT_TYPES; i++) { for (i = 0; i < HID_REPORT_TYPES; i++) {
struct hid_report_enum *report_enum = device->report_enum + i; struct hid_report_enum *report_enum = device->report_enum + i;
...@@ -1171,8 +1172,8 @@ int hid_wait_io(struct hid_device *hid) ...@@ -1171,8 +1172,8 @@ int hid_wait_io(struct hid_device *hid)
set_current_state(TASK_UNINTERRUPTIBLE); set_current_state(TASK_UNINTERRUPTIBLE);
add_wait_queue(&hid->wait, &wait); add_wait_queue(&hid->wait, &wait);
while (timeout && test_bit(HID_CTRL_RUNNING, &hid->iofl) && while (timeout && (test_bit(HID_CTRL_RUNNING, &hid->iofl) ||
test_bit(HID_OUT_RUNNING, &hid->iofl)) test_bit(HID_OUT_RUNNING, &hid->iofl)))
timeout = schedule_timeout(timeout); timeout = schedule_timeout(timeout);
set_current_state(TASK_RUNNING); set_current_state(TASK_RUNNING);
...@@ -1223,6 +1224,7 @@ void hid_init_reports(struct hid_device *hid) ...@@ -1223,6 +1224,7 @@ void hid_init_reports(struct hid_device *hid)
struct hid_report *report; struct hid_report *report;
struct list_head *list; struct list_head *list;
int len; int len;
int err, ret;
report_enum = hid->report_enum + HID_INPUT_REPORT; report_enum = hid->report_enum + HID_INPUT_REPORT;
list = report_enum->report_list.next; list = report_enum->report_list.next;
...@@ -1240,7 +1242,16 @@ void hid_init_reports(struct hid_device *hid) ...@@ -1240,7 +1242,16 @@ void hid_init_reports(struct hid_device *hid)
list = list->next; 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"); warn("timeout initializing reports\n");
return; return;
} }
...@@ -1299,7 +1310,7 @@ static struct hid_device *usb_hid_configure(struct usb_device *dev, int ifnum) ...@@ -1299,7 +1310,7 @@ static struct hid_device *usb_hid_configure(struct usb_device *dev, int ifnum)
struct hid_descriptor *hdesc; struct hid_descriptor *hdesc;
struct hid_device *hid; struct hid_device *hid;
unsigned quirks = 0, rsize = 0; unsigned quirks = 0, rsize = 0;
char *buf; char *buf, *rdesc;
int n; int n;
for (n = 0; hid_blacklist[n].idVendor; n++) for (n = 0; hid_blacklist[n].idVendor; n++)
...@@ -1325,11 +1336,14 @@ static struct hid_device *usb_hid_configure(struct usb_device *dev, int ifnum) ...@@ -1325,11 +1336,14 @@ static struct hid_device *usb_hid_configure(struct usb_device *dev, int ifnum)
return NULL; return NULL;
} }
{ if (!(rdesc = kmalloc(rsize, GFP_KERNEL))) {
__u8 rdesc[rsize]; dbg("couldn't allocate rdesc memory");
return NULL;
}
if ((n = hid_get_class_descriptor(dev, interface->bInterfaceNumber, HID_DT_REPORT, rdesc, rsize)) < 0) { if ((n = hid_get_class_descriptor(dev, interface->bInterfaceNumber, HID_DT_REPORT, rdesc, rsize)) < 0) {
dbg("reading report descriptor failed"); dbg("reading report descriptor failed");
kfree(rdesc);
return NULL; return NULL;
} }
...@@ -1342,10 +1356,11 @@ static struct hid_device *usb_hid_configure(struct usb_device *dev, int ifnum) ...@@ -1342,10 +1356,11 @@ static struct hid_device *usb_hid_configure(struct usb_device *dev, int ifnum)
if (!(hid = hid_parse_report(rdesc, rsize))) { if (!(hid = hid_parse_report(rdesc, rsize))) {
dbg("parsing report descriptor failed"); dbg("parsing report descriptor failed");
kfree(rdesc);
return NULL; return NULL;
} }
}
kfree(rdesc);
hid->quirks = quirks; hid->quirks = quirks;
for (n = 0; n < interface->bNumEndpoints; n++) { for (n = 0; n < interface->bNumEndpoints; n++) {
...@@ -1439,6 +1454,8 @@ static void* hid_probe(struct usb_device *dev, unsigned int ifnum, ...@@ -1439,6 +1454,8 @@ static void* hid_probe(struct usb_device *dev, unsigned int ifnum,
hid_init_reports(hid); hid_init_reports(hid);
hid_dump_device(hid); hid_dump_device(hid);
hid_ff_init(hid);
if (!hidinput_connect(hid)) if (!hidinput_connect(hid))
hid->claimed |= HID_CLAIMED_INPUT; hid->claimed |= HID_CLAIMED_INPUT;
if (!hiddev_connect(hid)) if (!hiddev_connect(hid))
...@@ -1477,20 +1494,20 @@ static void hid_disconnect(struct usb_device *dev, void *ptr) ...@@ -1477,20 +1494,20 @@ static void hid_disconnect(struct usb_device *dev, void *ptr)
{ {
struct hid_device *hid = ptr; struct hid_device *hid = ptr;
dbg("cleanup called");
usb_unlink_urb(hid->urbin); usb_unlink_urb(hid->urbin);
usb_unlink_urb(hid->urbout); usb_unlink_urb(hid->urbout);
usb_unlink_urb(hid->urbctrl); 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->urbin);
usb_free_urb(hid->urbctrl); usb_free_urb(hid->urbctrl);
if (hid->urbout) if (hid->urbout)
usb_free_urb(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); hid_free_device(hid);
} }
......
/*
* $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);
}
/* /*
* $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 * Copyright (c) 2000-2001 Vojtech Pavlik
* *
...@@ -274,8 +274,52 @@ static void hidinput_configure_usage(struct hid_device *device, struct hid_field ...@@ -274,8 +274,52 @@ static void hidinput_configure_usage(struct hid_device *device, struct hid_field
usage->type = EV_KEY; bit = input->keybit; max = KEY_MAX; usage->type = EV_KEY; bit = input->keybit; max = KEY_MAX;
break; 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: default:
unknown: unknown:
resolv_usage(usage->hid);
if (field->report_size == 1) { if (field->report_size == 1) {
...@@ -365,6 +409,16 @@ void hidinput_hid_event(struct hid_device *hid, struct hid_field *field, struct ...@@ -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)); 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 */ if((usage->type == EV_KEY) && (usage->code == 0)) /* Key 0 is "unassigned", not KEY_UKNOWN */
return; return;
...@@ -380,6 +434,9 @@ static int hidinput_input_event(struct input_dev *dev, unsigned int type, unsign ...@@ -380,6 +434,9 @@ static int hidinput_input_event(struct input_dev *dev, unsigned int type, unsign
struct hid_field *field = NULL; struct hid_field *field = NULL;
int offset; 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) { if ((offset = hid_find_field(hid, type, code, &field)) == -1) {
warn("event field not found"); warn("event field not found");
return -1; return -1;
......
/*
* $$
*
* 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);
}
/*
* $$
*
* 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);
}
...@@ -359,6 +359,11 @@ struct hid_device { /* device report descriptor */ ...@@ -359,6 +359,11 @@ struct hid_device { /* device report descriptor */
char name[128]; /* Device name */ char name[128]; /* Device name */
char phys[64]; /* Device physical location */ char phys[64]; /* Device physical location */
char uniq[64]; /* Device unique identifier (serial #) */ 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 #define HID_GLOBAL_STACK_SIZE 4
...@@ -395,6 +400,7 @@ struct hid_descriptor { ...@@ -395,6 +400,7 @@ struct hid_descriptor {
#define hid_dump_input(a,b) do { } while (0) #define hid_dump_input(a,b) do { } while (0)
#define hid_dump_device(c) do { } while (0) #define hid_dump_device(c) do { } while (0)
#define hid_dump_field(a,b) do { } while (0) #define hid_dump_field(a,b) do { } while (0)
#define resolv_usage(a) do { } while (0)
#endif #endif
#endif #endif
...@@ -419,3 +425,23 @@ int hid_find_field(struct hid_device *, unsigned int, unsigned int, struct hid_f ...@@ -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); int hid_set_field(struct hid_field *, unsigned, __s32);
void hid_submit_report(struct hid_device *, struct hid_report *, unsigned char dir); void hid_submit_report(struct hid_device *, struct hid_report *, unsigned char dir);
void hid_init_reports(struct hid_device *hid); 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;
}
...@@ -389,9 +389,7 @@ static int hiddev_ioctl(struct inode *inode, struct file *file, ...@@ -389,9 +389,7 @@ static int hiddev_ioctl(struct inode *inode, struct file *file,
dinfo.product = dev->descriptor.idProduct; dinfo.product = dev->descriptor.idProduct;
dinfo.version = dev->descriptor.bcdDevice; dinfo.version = dev->descriptor.bcdDevice;
dinfo.num_applications = hid->maxapplication; dinfo.num_applications = hid->maxapplication;
if (copy_to_user((void *) arg, &dinfo, sizeof(dinfo))) return copy_to_user((void *) arg, &dinfo, sizeof(dinfo));
return -EFAULT;
return 0;
} }
case HIDIOCGFLAG: case HIDIOCGFLAG:
...@@ -482,9 +480,7 @@ static int hiddev_ioctl(struct inode *inode, struct file *file, ...@@ -482,9 +480,7 @@ static int hiddev_ioctl(struct inode *inode, struct file *file,
rinfo.num_fields = report->maxfield; rinfo.num_fields = report->maxfield;
if (copy_to_user((void *) arg, &rinfo, sizeof(rinfo))) return copy_to_user((void *) arg, &rinfo, sizeof(rinfo));
return -EFAULT;
return 0;
case HIDIOCGFIELDINFO: case HIDIOCGFIELDINFO:
{ {
...@@ -516,9 +512,7 @@ static int hiddev_ioctl(struct inode *inode, struct file *file, ...@@ -516,9 +512,7 @@ static int hiddev_ioctl(struct inode *inode, struct file *file,
finfo.unit_exponent = field->unit_exponent; finfo.unit_exponent = field->unit_exponent;
finfo.unit = field->unit; finfo.unit = field->unit;
if (copy_to_user((void *) arg, &finfo, sizeof(finfo))) return copy_to_user((void *) arg, &finfo, sizeof(finfo));
return -EFAULT;
return 0;
} }
case HIDIOCGUCODE: case HIDIOCGUCODE:
...@@ -539,19 +533,12 @@ static int hiddev_ioctl(struct inode *inode, struct file *file, ...@@ -539,19 +533,12 @@ static int hiddev_ioctl(struct inode *inode, struct file *file,
uref.usage_code = field->usage[uref.usage_index].hid; uref.usage_code = field->usage[uref.usage_index].hid;
if (copy_to_user((void *) arg, &uref, sizeof(uref))) return copy_to_user((void *) arg, &uref, sizeof(uref));
return -EFAULT;
return 0;
case HIDIOCGUSAGE: case HIDIOCGUSAGE:
case HIDIOCSUSAGE:
if (copy_from_user(&uref, (void *) arg, sizeof(uref))) if (copy_from_user(&uref, (void *) arg, sizeof(uref)))
return -EFAULT; return -EFAULT;
if (cmd == HIDIOCSUSAGE &&
uref.report_type != HID_REPORT_TYPE_OUTPUT)
return -EINVAL;
if (uref.report_id == HID_REPORT_ID_UNKNOWN) { if (uref.report_id == HID_REPORT_ID_UNKNOWN) {
field = hiddev_lookup_usage(hid, &uref); field = hiddev_lookup_usage(hid, &uref);
if (field == NULL) if (field == NULL)
...@@ -570,15 +557,37 @@ static int hiddev_ioctl(struct inode *inode, struct file *file, ...@@ -570,15 +557,37 @@ static int hiddev_ioctl(struct inode *inode, struct file *file,
return -EINVAL; return -EINVAL;
} }
if (cmd == HIDIOCGUSAGE) {
uref.value = field->value[uref.usage_index]; uref.value = field->value[uref.usage_index];
if (copy_to_user((void *) arg, &uref, sizeof(uref)))
return copy_to_user((void *) arg, &uref, sizeof(uref));
case HIDIOCSUSAGE:
if (copy_from_user(&uref, (void *) arg, sizeof(uref)))
return -EFAULT; return -EFAULT;
return 0;
if (uref.report_type == HID_REPORT_TYPE_INPUT)
return -EINVAL;
if (uref.report_id == HID_REPORT_ID_UNKNOWN) {
field = hiddev_lookup_usage(hid, &uref);
if (field == NULL)
return -EINVAL;
} else { } else {
field->value[uref.usage_index] = uref.value; 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;
} }
field->value[uref.usage_index] = uref.value;
return 0; return 0;
default: default:
...@@ -626,9 +635,9 @@ int hiddev_connect(struct hid_device *hid) ...@@ -626,9 +635,9 @@ int hiddev_connect(struct hid_device *hid)
if (i == hid->maxapplication) if (i == hid->maxapplication)
return -1; 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) { if (retval) {
err ("Not able to get a minor for this device."); err("Not able to get a minor for this device.");
return -1; return -1;
} }
......
/*
* 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);
/*
* 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
/*
* 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");
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment