diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/usb/input/hid-lgff.c | |
download | linux-1da177e4c3f41524e886b7f1b8a0c1fc7321cac2.tar.bz2 |
Linux-2.6.12-rc2v2.6.12-rc2
Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.
Let it rip!
Diffstat (limited to 'drivers/usb/input/hid-lgff.c')
-rw-r--r-- | drivers/usb/input/hid-lgff.c | 528 |
1 files changed, 528 insertions, 0 deletions
diff --git a/drivers/usb/input/hid-lgff.c b/drivers/usb/input/hid-lgff.c new file mode 100644 index 000000000000..0d7404bab92f --- /dev/null +++ b/drivers/usb/input/hid-lgff.c @@ -0,0 +1,528 @@ +/* + * $$ + * + * Force feedback support for hid-compliant for some of the devices from + * Logitech, namely: + * - WingMan Cordless RumblePad + * - WingMan Force 3D + * + * Copyright (c) 2002-2004 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 <johann.deneux@it.uu.se> + */ + +#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" + + +/* Periodicity of the update */ +#define PERIOD (HZ/10) + +#define RUN_AT(t) (jiffies + (t)) + +/* 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 + +// For lgff_device::flags +#define DEVICE_CLOSING 0 /* The driver is being unitialised */ + +/* Check that the current process can access an effect */ +#define CHECK_OWNERSHIP(effect) (current->pid == 0 \ + || effect.owner == current->pid) + +#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_EFFECTS 8 + +struct device_type { + u16 idVendor; + u16 idProduct; + signed short *ff; +}; + +struct lgff_effect { + pid_t owner; + + struct ff_effect effect; + + unsigned long flags[1]; + unsigned int count; /* Number of times left to play */ + unsigned long started_at; /* When the effect started to play */ +}; + +struct lgff_device { + struct hid_device* hid; + + struct hid_report* constant; + struct hid_report* rumble; + struct hid_report* condition; + + 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 */ + + unsigned long flags[1]; /* Contains various information about the + state of the driver for this device */ + + struct timer_list timer; +}; + +/* Callbacks */ +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 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); + +/* Local functions */ +static void hid_lgff_input_init(struct hid_device* hid); +static void hid_lgff_timer(unsigned long timer_data); +static struct hid_report* hid_lgff_duplicate_report(struct hid_report*); +static void hid_lgff_delete_report(struct hid_report*); + +static signed short ff_rumble[] = { + FF_RUMBLE, + -1 +}; + +static signed short ff_joystick[] = { + FF_CONSTANT, + -1 +}; + +static struct device_type devices[] = { + {0x046d, 0xc211, ff_rumble}, + {0x046d, 0xc219, ff_rumble}, + {0x046d, 0xc283, ff_joystick}, + {0x0000, 0x0000, ff_joystick} +}; + +int hid_lgff_init(struct hid_device* hid) +{ + struct lgff_device *private; + struct hid_report* report; + struct hid_field* field; + + /* Find the report to use */ + if (list_empty(&hid->report_enum[HID_OUTPUT_REPORT].report_list)) { + err("No output report found"); + return -1; + } + /* Check that the report looks ok */ + report = (struct hid_report*)hid->report_enum[HID_OUTPUT_REPORT].report_list.next; + if (!report) { + err("NULL output report"); + return -1; + } + field = report->field[0]; + if (!field) { + err("NULL field"); + return -1; + } + + private = kmalloc(sizeof(struct lgff_device), GFP_KERNEL); + if (!private) + return -1; + memset(private, 0, sizeof(struct lgff_device)); + hid->ff_private = private; + + /* Input init */ + hid_lgff_input_init(hid); + + + private->constant = hid_lgff_duplicate_report(report); + if (!private->constant) { + kfree(private); + return -1; + } + private->constant->field[0]->value[0] = 0x51; + private->constant->field[0]->value[1] = 0x08; + private->constant->field[0]->value[2] = 0x7f; + private->constant->field[0]->value[3] = 0x7f; + + private->rumble = hid_lgff_duplicate_report(report); + if (!private->rumble) { + hid_lgff_delete_report(private->constant); + kfree(private); + return -1; + } + private->rumble->field[0]->value[0] = 0x42; + + + private->condition = hid_lgff_duplicate_report(report); + if (!private->condition) { + hid_lgff_delete_report(private->rumble); + hid_lgff_delete_report(private->constant); + kfree(private); + return -1; + } + + private->hid = hid; + + spin_lock_init(&private->lock); + init_timer(&private->timer); + private->timer.data = (unsigned long)private; + private->timer.function = hid_lgff_timer; + + /* Event and exit callbacks */ + hid->ff_exit = hid_lgff_exit; + hid->ff_event = hid_lgff_event; + + /* 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 */ + + printk(KERN_INFO "Force feedback for Logitech force feedback devices by Johann Deneux <johann.deneux@it.uu.se>\n"); + + return 0; +} + +static struct hid_report* hid_lgff_duplicate_report(struct hid_report* report) +{ + struct hid_report* ret; + + ret = kmalloc(sizeof(struct lgff_device), GFP_KERNEL); + if (!ret) + return NULL; + *ret = *report; + + ret->field[0] = kmalloc(sizeof(struct hid_field), GFP_KERNEL); + if (!ret->field[0]) { + kfree(ret); + return NULL; + } + *ret->field[0] = *report->field[0]; + + ret->field[0]->value = kmalloc(sizeof(s32[8]), GFP_KERNEL); + if (!ret->field[0]->value) { + kfree(ret->field[0]); + kfree(ret); + return NULL; + } + memset(ret->field[0]->value, 0, sizeof(s32[8])); + + return ret; +} + +static void hid_lgff_delete_report(struct hid_report* report) +{ + if (report) { + kfree(report->field[0]->value); + kfree(report->field[0]); + kfree(report); + } +} + +static void hid_lgff_input_init(struct hid_device* hid) +{ + struct device_type* dev = devices; + signed short* ff; + u16 idVendor = le16_to_cpu(hid->dev->descriptor.idVendor); + u16 idProduct = le16_to_cpu(hid->dev->descriptor.idProduct); + struct hid_input *hidinput = list_entry(hid->inputs.next, struct hid_input, list); + + while (dev->idVendor && (idVendor != dev->idVendor || idProduct != dev->idProduct)) + dev++; + + ff = dev->ff; + + while (*ff >= 0) { + set_bit(*ff, hidinput->input.ffbit); + ++ff; + } + + hidinput->input.upload_effect = hid_lgff_upload_effect; + hidinput->input.flush = hid_lgff_flush; + + set_bit(EV_FF, hidinput->input.evbit); + hidinput->input.ff_effects_max = LGFF_EFFECTS; +} + +static void hid_lgff_exit(struct hid_device* hid) +{ + struct lgff_device *lgff = hid->ff_private; + + set_bit(DEVICE_CLOSING, lgff->flags); + del_timer_sync(&lgff->timer); + + hid_lgff_delete_report(lgff->condition); + hid_lgff_delete_report(lgff->rumble); + hid_lgff_delete_report(lgff->constant); + + kfree(lgff); +} + +static int hid_lgff_event(struct hid_device *hid, struct input_dev* input, + unsigned int type, unsigned int code, int value) +{ + struct lgff_device *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->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(&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 lgff_device *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 lgff_device *lgff = hid->ff_private; + unsigned long flags; + + if (!LGFF_CHECK_OWNERSHIP(id, lgff)) return -EACCES; + + spin_lock_irqsave(&lgff->lock, flags); + 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 lgff_device *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; + + 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.effect = *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.effect.replay.delay != lgff->effects[id].effect.replay.delay + || new.effect.replay.length != lgff->effects[id].effect.replay.length) { + spin_unlock_irqrestore(&lgff->lock, flags); + return -ENOSYS; + } + + lgff->effects[id] = new; + + } else { + lgff->effects[id] = new; + } + + spin_unlock_irqrestore(&lgff->lock, flags); + return 0; +} + +static void hid_lgff_timer(unsigned long timer_data) +{ + struct lgff_device *lgff = (struct lgff_device*)timer_data; + struct hid_device *hid = lgff->hid; + unsigned long flags; + int x = 0x7f, y = 0x7f; // Coordinates of constant effects + unsigned int left = 0, right = 0; // Rumbling + int i; + + spin_lock_irqsave(&lgff->lock, flags); + + for (i=0; i<LGFF_EFFECTS; ++i) { + struct lgff_effect* effect = lgff->effects +i; + + if (test_bit(EFFECT_PLAYING, effect->flags)) { + + switch (effect->effect.type) { + case 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)); + } break; + case FF_RUMBLE: + right += effect->effect.u.rumble.strong_magnitude; + left += effect->effect.u.rumble.weak_magnitude; + break; + }; + + /* 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 %d", i); + if (--effect->count <= 0) { + dbg("Stopped %d", i); + clear_bit(EFFECT_PLAYING, effect->flags); + } + else { + dbg("Start again %d", i); + 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, lgff->effects[i].flags)) { + /* Check if we should start playing the effect */ + if (time_after(jiffies, + lgff->effects[i].started_at + + lgff->effects[i].effect.replay.delay*HZ/1000)) { + dbg("Now playing %d", i); + clear_bit(EFFECT_STARTED, lgff->effects[i].flags); + set_bit(EFFECT_PLAYING, lgff->effects[i].flags); + } + } + } + +#define CLAMP(x) if (x < 0) x = 0; if (x > 0xff) x = 0xff + + // Clamp values + CLAMP(x); + CLAMP(y); + CLAMP(left); + CLAMP(right); + +#undef CLAMP + + if (x != lgff->constant->field[0]->value[2] + || y != lgff->constant->field[0]->value[3]) { + lgff->constant->field[0]->value[2] = x; + lgff->constant->field[0]->value[3] = y; + dbg("(x,y)=(%04x, %04x)", x, y); + hid_submit_report(hid, lgff->constant, USB_DIR_OUT); + } + + if (left != lgff->rumble->field[0]->value[2] + || right != lgff->rumble->field[0]->value[3]) { + lgff->rumble->field[0]->value[2] = left; + lgff->rumble->field[0]->value[3] = right; + dbg("(left,right)=(%04x, %04x)", left, right); + hid_submit_report(hid, lgff->rumble, USB_DIR_OUT); + } + + if (!test_bit(DEVICE_CLOSING, lgff->flags)) { + lgff->timer.expires = RUN_AT(PERIOD); + add_timer(&lgff->timer); + } + + spin_unlock_irqrestore(&lgff->lock, flags); +} |