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
If unsure, say Y.
CONFIG_HID_FF
Say Y here is you want force feedback support for a few hid devices. See
below for a list of supported devices.
See Documentation/input/ff.txt for a description of the force feedback API.
If unsure, say N.
CONFIG_LOGITECH_RUMBLE
Say Y here if you have a Logitech WingMan Cordless rumble pad and if you
want to enable force feedback. Note: if you say N here, this device will
still be supported, but without force feedback.
CONFIG_HID_PID
Say Y yes if you have a PID-compliant joystick and wish to enable force
feedback for it. The Microsoft Sidewinder Force Feedback 2 is one such
device.
CONFIG_USB_HIDDEV
Say Y here if you want to support HID devices (from the USB
specification standpoint) that aren't strictly user interface
......@@ -83,3 +100,17 @@ CONFIG_USB_WACOM
inserted in and removed from the running kernel whenever you want).
The module will be called wacom.o. If you want to compile it as a
module, say M here and read <file:Documentation/modules.txt>.
CONFIG_USB_POWERMATE
Say Y here if you want to use Griffin PowerMate or Contour Jog devices.
These are stainless steel dials which can measure clockwise and
anticlockwise rotation. The dial also acts as a pushbutton. The base
contains an LED which can be instructed to pulse or to switch to a
particular intensity.
You can download userspace tools from http://sowerbutts.com/powermate/
This driver is also available as a module ( = code which can be
inserted in and removed from the running kernel whenever you want).
The module will be called powermate.o. If you want to compile it as a
module, say M here and read <file:Documentation/modules.txt>.
......@@ -3,10 +3,16 @@
#
comment 'USB Human Interface Devices (HID)'
dep_tristate ' USB Human Interface Device (full HID) support' CONFIG_USB_HID $CONFIG_USB
if [ "$CONFIG_INPUT" = "n" ]; then
comment ' Input core support is needed for USB HID input layer or HIDBP support'
fi
dep_mbool ' HID input layer support' CONFIG_USB_HIDINPUT $CONFIG_INPUT $CONFIG_USB_HID
dep_mbool ' Force feedback support' CONFIG_HID_FF $CONFIG_USB_HIDINPUT
dep_mbool ' PID Devices' CONFIG_HID_PID $CONFIG_USB_HID $CONFIG_HID_FF
dep_mbool ' Logitech RumblePad support' CONFIG_LOGITECH_RUMBLE $CONFIG_USB_HID $CONFIG_HID_FF
dep_mbool ' Logitech WingMan Force 3D support' CONFIG_LOGITECH_3D $CONFIG_USB_HID $CONFIG_HID_FF
dep_mbool ' /dev/hiddev raw HID device support' CONFIG_USB_HIDDEV $CONFIG_USB_HID
if [ "$CONFIG_USB_HID" != "y" ]; then
......@@ -16,4 +22,5 @@ fi
dep_tristate ' Aiptek 6000U/8000U tablet support' CONFIG_USB_AIPTEK $CONFIG_USB $CONFIG_INPUT
dep_tristate ' Wacom Intuos/Graphire tablet support' CONFIG_USB_WACOM $CONFIG_USB $CONFIG_INPUT
dep_tristate ' Griffin PowerMate and Contour Jog support' CONFIG_USB_POWERMATE $CONFIG_USB $CONFIG_INPUT
......@@ -12,12 +12,27 @@ endif
ifeq ($(CONFIG_USB_HIDINPUT),y)
hid-objs += hid-input.o
endif
ifeq ($(CONFIG_HID_PID),y)
hid-objs += pid.o
endif
ifeq ($(CONFIG_LOGITECH_RUMBLE),y)
hid-objs += hid-lgff.o
endif
ifeq ($(CONFIG_LOGITECH_3D),y)
hid-objs += hid-lg3dff.o
endif
ifeq ($(CONFIG_HID_FF),y)
hid-objs += hid-ff.o
endif
obj-$(CONFIG_USB_AIPTEK) += aiptek.o
obj-$(CONFIG_USB_HID) += hid.o
obj-$(CONFIG_USB_KBD) += usbkbd.o
obj-$(CONFIG_USB_MOUSE) += usbmouse.o
obj-$(CONFIG_USB_WACOM) += wacom.o
obj-$(CONFIG_USB_POWERMATE) += powermate.o
include $(TOPDIR)/Rules.make
#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) 2000-2001 Vojtech Pavlik
......@@ -108,11 +108,10 @@ static struct hid_field *hid_register_field(struct hid_report *report, unsigned
memset(field, 0, sizeof(struct hid_field) + usages * sizeof(struct hid_usage)
+ values * sizeof(unsigned));
report->field[report->maxfield] = field;
report->field[report->maxfield++] = field;
field->usage = (struct hid_usage *)(field + 1);
field->value = (unsigned *)(field->usage + usages);
field->report = report;
field->index = report->maxfield++;
return field;
}
......@@ -518,6 +517,8 @@ static void hid_free_device(struct hid_device *device)
{
unsigned i,j;
hid_ff_exit(device);
for (i = 0; i < HID_REPORT_TYPES; i++) {
struct hid_report_enum *report_enum = device->report_enum + i;
......@@ -1171,8 +1172,8 @@ int hid_wait_io(struct hid_device *hid)
set_current_state(TASK_UNINTERRUPTIBLE);
add_wait_queue(&hid->wait, &wait);
while (timeout && test_bit(HID_CTRL_RUNNING, &hid->iofl) &&
test_bit(HID_OUT_RUNNING, &hid->iofl))
while (timeout && (test_bit(HID_CTRL_RUNNING, &hid->iofl) ||
test_bit(HID_OUT_RUNNING, &hid->iofl)))
timeout = schedule_timeout(timeout);
set_current_state(TASK_RUNNING);
......@@ -1223,6 +1224,7 @@ void hid_init_reports(struct hid_device *hid)
struct hid_report *report;
struct list_head *list;
int len;
int err, ret;
report_enum = hid->report_enum + HID_INPUT_REPORT;
list = report_enum->report_list.next;
......@@ -1240,7 +1242,16 @@ void hid_init_reports(struct hid_device *hid)
list = list->next;
}
if (hid_wait_io(hid)) {
err = 0;
while ((ret = hid_wait_io(hid))) {
err |= ret;
if (test_bit(HID_CTRL_RUNNING, &hid->iofl))
usb_unlink_urb(hid->urbctrl);
if (test_bit(HID_OUT_RUNNING, &hid->iofl))
usb_unlink_urb(hid->urbout);
}
if (err) {
warn("timeout initializing reports\n");
return;
}
......@@ -1299,7 +1310,7 @@ static struct hid_device *usb_hid_configure(struct usb_device *dev, int ifnum)
struct hid_descriptor *hdesc;
struct hid_device *hid;
unsigned quirks = 0, rsize = 0;
char *buf;
char *buf, *rdesc;
int n;
for (n = 0; hid_blacklist[n].idVendor; n++)
......@@ -1325,27 +1336,31 @@ static struct hid_device *usb_hid_configure(struct usb_device *dev, int ifnum)
return NULL;
}
{
__u8 rdesc[rsize];
if (!(rdesc = kmalloc(rsize, GFP_KERNEL))) {
dbg("couldn't allocate rdesc memory");
return NULL;
}
if ((n = hid_get_class_descriptor(dev, interface->bInterfaceNumber, HID_DT_REPORT, rdesc, rsize)) < 0) {
dbg("reading report descriptor failed");
return NULL;
}
if ((n = hid_get_class_descriptor(dev, interface->bInterfaceNumber, HID_DT_REPORT, rdesc, rsize)) < 0) {
dbg("reading report descriptor failed");
kfree(rdesc);
return NULL;
}
#ifdef DEBUG_DATA
printk(KERN_DEBUG __FILE__ ": report descriptor (size %u, read %d) = ", rsize, n);
for (n = 0; n < rsize; n++)
printk(" %02x", (unsigned) rdesc[n]);
printk("\n");
printk(KERN_DEBUG __FILE__ ": report descriptor (size %u, read %d) = ", rsize, n);
for (n = 0; n < rsize; n++)
printk(" %02x", (unsigned) rdesc[n]);
printk("\n");
#endif
if (!(hid = hid_parse_report(rdesc, rsize))) {
dbg("parsing report descriptor failed");
return NULL;
}
if (!(hid = hid_parse_report(rdesc, rsize))) {
dbg("parsing report descriptor failed");
kfree(rdesc);
return NULL;
}
kfree(rdesc);
hid->quirks = quirks;
for (n = 0; n < interface->bNumEndpoints; n++) {
......@@ -1439,6 +1454,8 @@ static void* hid_probe(struct usb_device *dev, unsigned int ifnum,
hid_init_reports(hid);
hid_dump_device(hid);
hid_ff_init(hid);
if (!hidinput_connect(hid))
hid->claimed |= HID_CLAIMED_INPUT;
if (!hiddev_connect(hid))
......@@ -1477,20 +1494,20 @@ static void hid_disconnect(struct usb_device *dev, void *ptr)
{
struct hid_device *hid = ptr;
dbg("cleanup called");
usb_unlink_urb(hid->urbin);
usb_unlink_urb(hid->urbout);
usb_unlink_urb(hid->urbctrl);
if (hid->claimed & HID_CLAIMED_INPUT)
hidinput_disconnect(hid);
if (hid->claimed & HID_CLAIMED_HIDDEV)
hiddev_disconnect(hid);
usb_free_urb(hid->urbin);
usb_free_urb(hid->urbctrl);
if (hid->urbout)
usb_free_urb(hid->urbout);
if (hid->claimed & HID_CLAIMED_INPUT)
hidinput_disconnect(hid);
if (hid->claimed & HID_CLAIMED_HIDDEV)
hiddev_disconnect(hid);
hid_free_device(hid);
}
......
/*
* $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
*
......@@ -273,9 +273,53 @@ static void hidinput_configure_usage(struct hid_device *device, struct hid_field
usage->type = EV_KEY; bit = input->keybit; max = KEY_MAX;
break;
case HID_UP_PID:
usage->type = EV_FF; bit = input->ffbit; max = FF_MAX;
switch(usage->hid & HID_USAGE) {
case 0x26: set_bit(FF_CONSTANT, input->ffbit); break;
case 0x27: set_bit(FF_RAMP, input->ffbit); break;
case 0x28: set_bit(FF_CUSTOM, input->ffbit); break;
case 0x30: set_bit(FF_SQUARE, input->ffbit);
set_bit(FF_PERIODIC, input->ffbit); break;
case 0x31: set_bit(FF_SINE, input->ffbit);
set_bit(FF_PERIODIC, input->ffbit); break;
case 0x32: set_bit(FF_TRIANGLE, input->ffbit);
set_bit(FF_PERIODIC, input->ffbit); break;
case 0x33: set_bit(FF_SAW_UP, input->ffbit);
set_bit(FF_PERIODIC, input->ffbit); break;
case 0x34: set_bit(FF_SAW_DOWN, input->ffbit);
set_bit(FF_PERIODIC, input->ffbit); break;
case 0x40: set_bit(FF_SPRING, input->ffbit); break;
case 0x41: set_bit(FF_DAMPER, input->ffbit); break;
case 0x42: set_bit(FF_INERTIA , input->ffbit); break;
case 0x43: set_bit(FF_FRICTION, input->ffbit); break;
case 0x7e: usage->code = FF_GAIN; break;
case 0x83: /* Simultaneous Effects Max */
input->ff_effects_max = (field->value[0]);
dbg("Maximum Effects - %d",input->ff_effects_max);
break;
case 0x98: /* Device Control */
usage->code = FF_AUTOCENTER; break;
case 0xa4: /* Safety Switch */
usage->code = BTN_DEAD;
bit = input->keybit;
usage->type = EV_KEY;
max = KEY_MAX;
dbg("Safety Switch Report\n");
break;
case 0x9f: /* Device Paused */
case 0xa0: /* Actuators Enabled */
dbg("Not telling the input API about ");
resolv_usage(usage->hid);
return;
}
break;
default:
unknown:
resolv_usage(usage->hid);
if (field->report_size == 1) {
......@@ -365,6 +409,16 @@ void hidinput_hid_event(struct hid_device *hid, struct hid_field *field, struct
input_event(input, EV_KEY, BTN_TOUCH, value > a + ((b - a) >> 3));
}
if (usage->hid == (HID_UP_PID | 0x83UL)) { /* Simultaneous Effects Max */
input->ff_effects_max = value;
dbg("Maximum Effects - %d",input->ff_effects_max);
return;
}
if (usage->hid == (HID_UP_PID | 0x7fUL)) {
dbg("PID Pool Report\n");
return;
}
if((usage->type == EV_KEY) && (usage->code == 0)) /* Key 0 is "unassigned", not KEY_UKNOWN */
return;
......@@ -380,6 +434,9 @@ static int hidinput_input_event(struct input_dev *dev, unsigned int type, unsign
struct hid_field *field = NULL;
int offset;
if (type == EV_FF)
return hid_ff_event(hid, dev, type, code, value);
if ((offset = hid_find_field(hid, type, code, &field)) == -1) {
warn("event field not found");
return -1;
......
/*
* $$
*
* 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 */
char name[128]; /* Device name */
char phys[64]; /* Device physical location */
char uniq[64]; /* Device unique identifier (serial #) */
void *ff_private; /* Private data for the force-feedback driver */
void (*ff_exit)(struct hid_device*); /* Called by hid_exit_ff(hid) */
int (*ff_event)(struct hid_device *hid, struct input_dev *input,
unsigned int type, unsigned int code, int value);
};
#define HID_GLOBAL_STACK_SIZE 4
......@@ -395,6 +400,7 @@ struct hid_descriptor {
#define hid_dump_input(a,b) do { } while (0)
#define hid_dump_device(c) do { } while (0)
#define hid_dump_field(a,b) do { } while (0)
#define resolv_usage(a) do { } while (0)
#endif
#endif
......@@ -419,3 +425,23 @@ int hid_find_field(struct hid_device *, unsigned int, unsigned int, struct hid_f
int hid_set_field(struct hid_field *, unsigned, __s32);
void hid_submit_report(struct hid_device *, struct hid_report *, unsigned char dir);
void hid_init_reports(struct hid_device *hid);
int hid_find_report_by_usage(struct hid_device *hid, __u32 wanted_usage, struct hid_report **report, int type);
#ifdef CONFIG_HID_FF
int hid_ff_init(struct hid_device *hid);
#else
static inline int hid_ff_init(struct hid_device *hid) { return -1; }
#endif
static inline void hid_ff_exit(struct hid_device *hid)
{
if (hid->ff_exit)
hid->ff_exit(hid);
}
static inline int hid_ff_event(struct hid_device *hid, struct input_dev *input,
unsigned int type, unsigned int code, int value)
{
if (hid->ff_event)
return hid->ff_event(hid, input, type, code, value);
return -ENOSYS;
}
......@@ -389,9 +389,7 @@ static int hiddev_ioctl(struct inode *inode, struct file *file,
dinfo.product = dev->descriptor.idProduct;
dinfo.version = dev->descriptor.bcdDevice;
dinfo.num_applications = hid->maxapplication;
if (copy_to_user((void *) arg, &dinfo, sizeof(dinfo)))
return -EFAULT;
return 0;
return copy_to_user((void *) arg, &dinfo, sizeof(dinfo));
}
case HIDIOCGFLAG:
......@@ -482,9 +480,7 @@ static int hiddev_ioctl(struct inode *inode, struct file *file,
rinfo.num_fields = report->maxfield;
if (copy_to_user((void *) arg, &rinfo, sizeof(rinfo)))
return -EFAULT;
return 0;
return copy_to_user((void *) arg, &rinfo, sizeof(rinfo));
case HIDIOCGFIELDINFO:
{
......@@ -516,9 +512,7 @@ static int hiddev_ioctl(struct inode *inode, struct file *file,
finfo.unit_exponent = field->unit_exponent;
finfo.unit = field->unit;
if (copy_to_user((void *) arg, &finfo, sizeof(finfo)))
return -EFAULT;
return 0;
return copy_to_user((void *) arg, &finfo, sizeof(finfo));
}
case HIDIOCGUCODE:
......@@ -539,17 +533,39 @@ static int hiddev_ioctl(struct inode *inode, struct file *file,
uref.usage_code = field->usage[uref.usage_index].hid;
if (copy_to_user((void *) arg, &uref, sizeof(uref)))
return -EFAULT;
return 0;
return copy_to_user((void *) arg, &uref, sizeof(uref));
case HIDIOCGUSAGE:
if (copy_from_user(&uref, (void *) arg, sizeof(uref)))
return -EFAULT;
if (uref.report_id == HID_REPORT_ID_UNKNOWN) {
field = hiddev_lookup_usage(hid, &uref);
if (field == NULL)
return -EINVAL;
} else {
rinfo.report_type = uref.report_type;
rinfo.report_id = uref.report_id;
if ((report = hiddev_lookup_report(hid, &rinfo)) == NULL)
return -EINVAL;
if (uref.field_index >= report->maxfield)
return -EINVAL;
field = report->field[uref.field_index];
if (uref.usage_index >= field->maxusage)
return -EINVAL;
}
uref.value = field->value[uref.usage_index];
return copy_to_user((void *) arg, &uref, sizeof(uref));
case HIDIOCSUSAGE:
if (copy_from_user(&uref, (void *) arg, sizeof(uref)))
return -EFAULT;
if (cmd == HIDIOCSUSAGE &&
uref.report_type != HID_REPORT_TYPE_OUTPUT)
if (uref.report_type == HID_REPORT_TYPE_INPUT)
return -EINVAL;
if (uref.report_id == HID_REPORT_ID_UNKNOWN) {
......@@ -570,14 +586,7 @@ static int hiddev_ioctl(struct inode *inode, struct file *file,
return -EINVAL;
}
if (cmd == HIDIOCGUSAGE) {
uref.value = field->value[uref.usage_index];
if (copy_to_user((void *) arg, &uref, sizeof(uref)))
return -EFAULT;
return 0;
} else {
field->value[uref.usage_index] = uref.value;
}
field->value[uref.usage_index] = uref.value;
return 0;
......@@ -626,9 +635,9 @@ int hiddev_connect(struct hid_device *hid)
if (i == hid->maxapplication)
return -1;
retval = usb_register_dev (&hiddev_fops, HIDDEV_MINOR_BASE, 1, &minor);
retval = usb_register_dev(&hiddev_fops, HIDDEV_MINOR_BASE, 1, &minor);
if (retval) {
err ("Not able to get a minor for this device.");
err("Not able to get a minor for this device.");
return -1;
}
......
/*
* 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