diff options
Diffstat (limited to 'drivers')
52 files changed, 4241 insertions, 2037 deletions
diff --git a/drivers/char/keyboard.c b/drivers/char/keyboard.c index 99fb070fdbf..e2011669c7b 100644 --- a/drivers/char/keyboard.c +++ b/drivers/char/keyboard.c @@ -1293,7 +1293,7 @@ static void kbd_event(struct input_handle *handle, unsigned int event_type, */ static struct input_handle *kbd_connect(struct input_handler *handler, struct input_dev *dev, - struct input_device_id *id) + const struct input_device_id *id) { struct input_handle *handle; int i; @@ -1342,7 +1342,7 @@ static void kbd_start(struct input_handle *handle) tasklet_enable(&keyboard_tasklet); } -static struct input_device_id kbd_ids[] = { +static const struct input_device_id kbd_ids[] = { { .flags = INPUT_DEVICE_ID_MATCH_EVBIT, .evbit = { BIT(EV_KEY) }, @@ -1370,6 +1370,7 @@ static struct input_handler kbd_handler = { int __init kbd_init(void) { int i; + int error; for (i = 0; i < MAX_NR_CONSOLES; i++) { kbd_table[i].ledflagstate = KBD_DEFLEDS; @@ -1381,7 +1382,9 @@ int __init kbd_init(void) kbd_table[i].kbdmode = VC_XLATE; } - input_register_handler(&kbd_handler); + error = input_register_handler(&kbd_handler); + if (error) + return error; tasklet_enable(&keyboard_tasklet); tasklet_schedule(&keyboard_tasklet); diff --git a/drivers/input/Kconfig b/drivers/input/Kconfig index 58223b5d842..96232313b1b 100644 --- a/drivers/input/Kconfig +++ b/drivers/input/Kconfig @@ -24,6 +24,20 @@ config INPUT if INPUT +config INPUT_FF_MEMLESS + tristate "Support for memoryless force-feedback devices" + default n + ---help--- + Say Y here if you have memoryless force-feedback input device + such as Logitech WingMan Force 3D, ThrustMaster FireStorm Dual + Power 2, or similar. You will also need to enable hardware-specific + driver. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called ff-memless. + comment "Userland interfaces" config INPUT_MOUSEDEV diff --git a/drivers/input/Makefile b/drivers/input/Makefile index 1a6ff4982f3..a005b1df5f1 100644 --- a/drivers/input/Makefile +++ b/drivers/input/Makefile @@ -4,7 +4,11 @@ # Each configuration option enables a list of files. -obj-$(CONFIG_INPUT) += input.o +obj-$(CONFIG_INPUT) += input-core.o +input-core-objs := input.o ff-core.o + +obj-$(CONFIG_INPUT_FF_MEMLESS) += ff-memless.o + obj-$(CONFIG_INPUT_MOUSEDEV) += mousedev.o obj-$(CONFIG_INPUT_JOYDEV) += joydev.o obj-$(CONFIG_INPUT_EVDEV) += evdev.o diff --git a/drivers/input/evbug.c b/drivers/input/evbug.c index 07358fb51b8..5a9653c3128 100644 --- a/drivers/input/evbug.c +++ b/drivers/input/evbug.c @@ -42,10 +42,12 @@ static char evbug_name[] = "evbug"; static void evbug_event(struct input_handle *handle, unsigned int type, unsigned int code, int value) { - printk(KERN_DEBUG "evbug.c: Event. Dev: %s, Type: %d, Code: %d, Value: %d\n", handle->dev->phys, type, code, value); + printk(KERN_DEBUG "evbug.c: Event. Dev: %s, Type: %d, Code: %d, Value: %d\n", + handle->dev->phys, type, code, value); } -static struct input_handle *evbug_connect(struct input_handler *handler, struct input_dev *dev, struct input_device_id *id) +static struct input_handle *evbug_connect(struct input_handler *handler, struct input_dev *dev, + const struct input_device_id *id) { struct input_handle *handle; @@ -72,7 +74,7 @@ static void evbug_disconnect(struct input_handle *handle) kfree(handle); } -static struct input_device_id evbug_ids[] = { +static const struct input_device_id evbug_ids[] = { { .driver_info = 1 }, /* Matches all devices */ { }, /* Terminating zero entry */ }; @@ -89,8 +91,7 @@ static struct input_handler evbug_handler = { static int __init evbug_init(void) { - input_register_handler(&evbug_handler); - return 0; + return input_register_handler(&evbug_handler); } static void __exit evbug_exit(void) diff --git a/drivers/input/evdev.c b/drivers/input/evdev.c index 4bf48188cc9..6439f378f6c 100644 --- a/drivers/input/evdev.c +++ b/drivers/input/evdev.c @@ -391,8 +391,10 @@ static long evdev_ioctl_handler(struct file *file, unsigned int cmd, struct evdev *evdev = list->evdev; struct input_dev *dev = evdev->handle.dev; struct input_absinfo abs; + struct ff_effect effect; int __user *ip = (int __user *)p; int i, t, u, v; + int error; if (!evdev->exist) return -ENODEV; @@ -460,27 +462,22 @@ static long evdev_ioctl_handler(struct file *file, unsigned int cmd, return 0; case EVIOCSFF: - if (dev->upload_effect) { - struct ff_effect effect; - int err; - - if (copy_from_user(&effect, p, sizeof(effect))) - return -EFAULT; - err = dev->upload_effect(dev, &effect); - if (put_user(effect.id, &(((struct ff_effect __user *)p)->id))) - return -EFAULT; - return err; - } else - return -ENOSYS; + if (copy_from_user(&effect, p, sizeof(effect))) + return -EFAULT; - case EVIOCRMFF: - if (!dev->erase_effect) - return -ENOSYS; + error = input_ff_upload(dev, &effect, file); - return dev->erase_effect(dev, (int)(unsigned long) p); + if (put_user(effect.id, &(((struct ff_effect __user *)p)->id))) + return -EFAULT; + + return error; + + case EVIOCRMFF: + return input_ff_erase(dev, (int)(unsigned long) p, file); case EVIOCGEFFECTS: - if (put_user(dev->ff_effects_max, ip)) + i = test_bit(EV_FF, dev->evbit) ? dev->ff->max_effects : 0; + if (put_user(i, ip)) return -EFAULT; return 0; @@ -604,7 +601,7 @@ static long evdev_ioctl_compat(struct file *file, unsigned int cmd, unsigned lon } #endif -static struct file_operations evdev_fops = { +static const struct file_operations evdev_fops = { .owner = THIS_MODULE, .read = evdev_read, .write = evdev_write, @@ -619,7 +616,8 @@ static struct file_operations evdev_fops = { .flush = evdev_flush }; -static struct input_handle *evdev_connect(struct input_handler *handler, struct input_dev *dev, struct input_device_id *id) +static struct input_handle *evdev_connect(struct input_handler *handler, struct input_dev *dev, + const struct input_device_id *id) { struct evdev *evdev; struct class_device *cdev; @@ -669,6 +667,7 @@ static void evdev_disconnect(struct input_handle *handle) evdev->exist = 0; if (evdev->open) { + input_flush_device(handle, NULL); input_close_device(handle); wake_up_interruptible(&evdev->wait); list_for_each_entry(list, &evdev->list, node) @@ -677,7 +676,7 @@ static void evdev_disconnect(struct input_handle *handle) evdev_free(evdev); } -static struct input_device_id evdev_ids[] = { +static const struct input_device_id evdev_ids[] = { { .driver_info = 1 }, /* Matches all devices */ { }, /* Terminating zero entry */ }; @@ -696,8 +695,7 @@ static struct input_handler evdev_handler = { static int __init evdev_init(void) { - input_register_handler(&evdev_handler); - return 0; + return input_register_handler(&evdev_handler); } static void __exit evdev_exit(void) diff --git a/drivers/input/ff-core.c b/drivers/input/ff-core.c new file mode 100644 index 00000000000..35656cadc91 --- /dev/null +++ b/drivers/input/ff-core.c @@ -0,0 +1,367 @@ +/* + * Force feedback support for Linux input subsystem + * + * Copyright (c) 2006 Anssi Hannula <anssi.hannula@gmail.com> + * Copyright (c) 2006 Dmitry Torokhov <dtor@mail.ru> + */ + +/* + * 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 + */ + +/* #define DEBUG */ + +#define debug(format, arg...) pr_debug("ff-core: " format "\n", ## arg) + +#include <linux/input.h> +#include <linux/module.h> +#include <linux/mutex.h> + +/* + * Check that the effect_id is a valid effect and whether the user + * is the owner + */ +static int check_effect_access(struct ff_device *ff, int effect_id, + struct file *file) +{ + if (effect_id < 0 || effect_id >= ff->max_effects || + !ff->effect_owners[effect_id]) + return -EINVAL; + + if (file && ff->effect_owners[effect_id] != file) + return -EACCES; + + return 0; +} + +/* + * Checks whether 2 effects can be combined together + */ +static inline int check_effects_compatible(struct ff_effect *e1, + struct ff_effect *e2) +{ + return e1->type == e2->type && + (e1->type != FF_PERIODIC || + e1->u.periodic.waveform == e2->u.periodic.waveform); +} + +/* + * Convert an effect into compatible one + */ +static int compat_effect(struct ff_device *ff, struct ff_effect *effect) +{ + int magnitude; + + switch (effect->type) { + case FF_RUMBLE: + if (!test_bit(FF_PERIODIC, ff->ffbit)) + return -EINVAL; + + /* + * calculate manginude of sine wave as average of rumble's + * 2/3 of strong magnitude and 1/3 of weak magnitude + */ + magnitude = effect->u.rumble.strong_magnitude / 3 + + effect->u.rumble.weak_magnitude / 6; + + effect->type = FF_PERIODIC; + effect->u.periodic.waveform = FF_SINE; + effect->u.periodic.period = 50; + effect->u.periodic.magnitude = max(magnitude, 0x7fff); + effect->u.periodic.offset = 0; + effect->u.periodic.phase = 0; + effect->u.periodic.envelope.attack_length = 0; + effect->u.periodic.envelope.attack_level = 0; + effect->u.periodic.envelope.fade_length = 0; + effect->u.periodic.envelope.fade_level = 0; + + return 0; + + default: + /* Let driver handle conversion */ + return 0; + } +} + +/** + * input_ff_upload() - upload effect into force-feedback device + * @dev: input device + * @effect: effect to be uploaded + * @file: owner of the effect + */ +int input_ff_upload(struct input_dev *dev, struct ff_effect *effect, + struct file *file) +{ + struct ff_device *ff = dev->ff; + struct ff_effect *old; + int ret = 0; + int id; + + if (!test_bit(EV_FF, dev->evbit)) + return -ENOSYS; + + if (effect->type < FF_EFFECT_MIN || effect->type > FF_EFFECT_MAX || + !test_bit(effect->type, dev->ffbit)) { + debug("invalid or not supported effect type in upload"); + return -EINVAL; + } + + if (effect->type == FF_PERIODIC && + (effect->u.periodic.waveform < FF_WAVEFORM_MIN || + effect->u.periodic.waveform > FF_WAVEFORM_MAX || + !test_bit(effect->u.periodic.waveform, dev->ffbit))) { + debug("invalid or not supported wave form in upload"); + return -EINVAL; + } + + if (!test_bit(effect->type, ff->ffbit)) { + ret = compat_effect(ff, effect); + if (ret) + return ret; + } + + mutex_lock(&ff->mutex); + + if (effect->id == -1) { + for (id = 0; id < ff->max_effects; id++) + if (!ff->effect_owners[id]) + break; + + if (id >= ff->max_effects) { + ret = -ENOSPC; + goto out; + } + + effect->id = id; + old = NULL; + + } else { + id = effect->id; + + ret = check_effect_access(ff, id, file); + if (ret) + goto out; + + old = &ff->effects[id]; + + if (!check_effects_compatible(effect, old)) { + ret = -EINVAL; + goto out; + } + } + + ret = ff->upload(dev, effect, old); + if (ret) + goto out; + + ff->effects[id] = *effect; + ff->effect_owners[id] = file; + + out: + mutex_unlock(&ff->mutex); + return ret; +} +EXPORT_SYMBOL_GPL(input_ff_upload); + +/* + * Erases the effect if the requester is also the effect owner. The mutex + * should already be locked before calling this function. + */ +static int erase_effect(struct input_dev *dev, int effect_id, + struct file *file) +{ + struct ff_device *ff = dev->ff; + int error; + + error = check_effect_access(ff, effect_id, file); + if (error) + return error; + + ff->playback(dev, effect_id, 0); + + if (ff->erase) { + error = ff->erase(dev, effect_id); + if (error) + return error; + } + + ff->effect_owners[effect_id] = NULL; + + return 0; +} + +/** + * input_ff_erase - erase an effect from device + * @dev: input device to erase effect from + * @effect_id: id of the ffect to be erased + * @file: purported owner of the request + * + * This function erases a force-feedback effect from specified device. + * The effect will only be erased if it was uploaded through the same + * file handle that is requesting erase. + */ +int input_ff_erase(struct input_dev *dev, int effect_id, struct file *file) +{ + struct ff_device *ff = dev->ff; + int ret; + + if (!test_bit(EV_FF, dev->evbit)) + return -ENOSYS; + + mutex_lock(&ff->mutex); + ret = erase_effect(dev, effect_id, file); + mutex_unlock(&ff->mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(input_ff_erase); + +/* + * flush_effects - erase all effects owned by a file handle + */ +static int flush_effects(struct input_dev *dev, struct file *file) +{ + struct ff_device *ff = dev->ff; + int i; + + debug("flushing now"); + + mutex_lock(&ff->mutex); + + for (i = 0; i < ff->max_effects; i++) + erase_effect(dev, i, file); + + mutex_unlock(&ff->mutex); + + return 0; +} + +/** + * input_ff_event() - generic handler for force-feedback events + * @dev: input device to send the effect to + * @type: event type (anything but EV_FF is ignored) + * @code: event code + * @value: event value + */ +int input_ff_event(struct input_dev *dev, unsigned int type, + unsigned int code, int value) +{ + struct ff_device *ff = dev->ff; + + if (type != EV_FF) + return 0; + + mutex_lock(&ff->mutex); + + switch (code) { + case FF_GAIN: + if (!test_bit(FF_GAIN, dev->ffbit) || value > 0xffff) + break; + + ff->set_gain(dev, value); + break; + + case FF_AUTOCENTER: + if (!test_bit(FF_AUTOCENTER, dev->ffbit) || value > 0xffff) + break; + + ff->set_autocenter(dev, value); + break; + + default: + ff->playback(dev, code, value); + break; + } + + mutex_unlock(&ff->mutex); + return 0; +} +EXPORT_SYMBOL_GPL(input_ff_event); + +/** + * input_ff_create() - create force-feedback device + * @dev: input device supporting force-feedback + * @max_effects: maximum number of effects supported by the device + * + * This function allocates all necessary memory for a force feedback + * portion of an input device and installs all default handlers. + * @dev->ffbit should be already set up before calling this function. + * Once ff device is created you need to setup its upload, erase, + * playback and other handlers before registering input device + */ +int input_ff_create(struct input_dev *dev, int max_effects) +{ + struct ff_device *ff; + int i; + + if (!max_effects) { + printk(KERN_ERR + "ff-core: cannot allocate device without any effects\n"); + return -EINVAL; + } + + ff = kzalloc(sizeof(struct ff_device) + + max_effects * sizeof(struct file *), GFP_KERNEL); + if (!ff) + return -ENOMEM; + + ff->effects = kcalloc(max_effects, sizeof(struct ff_effect), + GFP_KERNEL); + if (!ff->effects) { + kfree(ff); + return -ENOMEM; + } + + ff->max_effects = max_effects; + mutex_init(&ff->mutex); + + dev->ff = ff; + dev->flush = flush_effects; + dev->event = input_ff_event; + set_bit(EV_FF, dev->evbit); + + /* Copy "true" bits into ff device bitmap */ + for (i = 0; i <= FF_MAX; i++) + if (test_bit(i, dev->ffbit)) + set_bit(i, ff->ffbit); + + /* we can emulate RUMBLE with periodic effects */ + if (test_bit(FF_PERIODIC, ff->ffbit)) + set_bit(FF_RUMBLE, dev->ffbit); + + return 0; +} +EXPORT_SYMBOL_GPL(input_ff_create); + +/** + * input_ff_free() - frees force feedback portion of input device + * @dev: input device supporintg force feedback + * + * This function is only needed in error path as input core will + * automatically free force feedback structures when device is + * destroyed. + */ +void input_ff_destroy(struct input_dev *dev) +{ + clear_bit(EV_FF, dev->evbit); + if (dev->ff) { + if (dev->ff->destroy) + dev->ff->destroy(dev->ff); + kfree(dev->ff->private); + kfree(dev->ff); + dev->ff = NULL; + } +} +EXPORT_SYMBOL_GPL(input_ff_destroy); diff --git a/drivers/input/ff-memless.c b/drivers/input/ff-memless.c new file mode 100644 index 00000000000..cd8b7297e6d --- /dev/null +++ b/drivers/input/ff-memless.c @@ -0,0 +1,515 @@ +/* + * Force feedback support for memoryless devices + * + * Copyright (c) 2006 Anssi Hannula <anssi.hannula@gmail.com> + * Copyright (c) 2006 Dmitry Torokhov <dtor@mail.ru> + */ + +/* + * 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 + */ + +/* #define DEBUG */ + +#define debug(format, arg...) pr_debug("ff-memless: " format "\n", ## arg) + +#include <linux/input.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/spinlock.h> +#include <linux/sched.h> + +#include "fixp-arith.h" + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Anssi Hannula <anssi.hannula@gmail.com>"); +MODULE_DESCRIPTION("Force feedback support for memoryless devices"); + +/* Number of effects handled with memoryless devices */ +#define FF_MEMLESS_EFFECTS 16 + +/* Envelope update interval in ms */ +#define FF_ENVELOPE_INTERVAL 50 + +#define FF_EFFECT_STARTED 0 +#define FF_EFFECT_PLAYING 1 +#define FF_EFFECT_ABORTING 2 + +struct ml_effect_state { + struct ff_effect *effect; + unsigned long flags; /* effect state (STARTED, PLAYING, etc) */ + int count; /* loop count of the effect */ + unsigned long play_at; /* start time */ + unsigned long stop_at; /* stop time */ + unsigned long adj_at; /* last time the effect was sent */ +}; + +struct ml_device { + void *private; + struct ml_effect_state states[FF_MEMLESS_EFFECTS]; + int gain; + struct timer_list timer; + spinlock_t timer_lock; + struct input_dev *dev; + + int (*play_effect)(struct input_dev *dev, void *data, + struct ff_effect *effect); +}; + +static const struct ff_envelope *get_envelope(const struct ff_effect *effect) +{ + static const struct ff_envelope empty_envelope; + + switch (effect->type) { + case FF_PERIODIC: + return &effect->u.periodic.envelope; + case FF_CONSTANT: + return &effect->u.constant.envelope; + default: + return &empty_envelope; + } +} + +/* + * Check for the next time envelope requires an update on memoryless devices + */ +static unsigned long calculate_next_time(struct ml_effect_state *state) +{ + const struct ff_envelope *envelope = get_envelope(state->effect); + unsigned long attack_stop, fade_start, next_fade; + + if (envelope->attack_length) { + attack_stop = state->play_at + + msecs_to_jiffies(envelope->attack_length); + if (time_before(state->adj_at, attack_stop)) + return state->adj_at + + msecs_to_jiffies(FF_ENVELOPE_INTERVAL); + } + + if (state->effect->replay.length) { + if (envelope->fade_length) { + /* check when fading should start */ + fade_start = state->stop_at - + msecs_to_jiffies(envelope->fade_length); + + if (time_before(state->adj_at, fade_start)) + return fade_start; + + /* already fading, advance to next checkpoint */ + next_fade = state->adj_at + + msecs_to_jiffies(FF_ENVELOPE_INTERVAL); + if (time_before(next_fade, state->stop_at)) + return next_fade; + } + + return state->stop_at; + } + + return state->play_at; +} + +static void ml_schedule_timer(struct ml_device *ml) +{ + struct ml_effect_state *state; + unsigned long now = jiffies; + unsigned long earliest = 0; + unsigned long next_at; + int events = 0; + int i; + + debug("calculating next timer"); + + for (i = 0; i < FF_MEMLESS_EFFECTS; i++) { + + state = &ml->states[i]; + + if (!test_bit(FF_EFFECT_STARTED, &state->flags)) + continue; + + if (test_bit(FF_EFFECT_PLAYING, &state->flags)) + next_at = calculate_next_time(state); + else + next_at = state->play_at; + + if (time_before_eq(now, next_at) && + (++events == 1 || time_before(ne |