diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-17 02:20:36 +0400 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-17 02:20:36 +0400 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/input | |
download | linux-1da177e4c3f41524e886b7f1b8a0c1fc7321cac2.tar.xz |
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/input')
125 files changed, 39668 insertions, 0 deletions
diff --git a/drivers/input/Kconfig b/drivers/input/Kconfig new file mode 100644 index 000000000000..58223b5d842a --- /dev/null +++ b/drivers/input/Kconfig @@ -0,0 +1,157 @@ +# +# Input device configuration +# + +menu "Input device support" + +config INPUT + tristate "Generic input layer (needed for keyboard, mouse, ...)" if EMBEDDED + default y + ---help--- + Say Y here if you have any input device (mouse, keyboard, tablet, + joystick, steering wheel ...) connected to your system and want + it to be available to applications. This includes standard PS/2 + keyboard and mouse. + + Say N here if you have a headless (no monitor, no keyboard) system. + + More information is available: <file:Documentation/input/input.txt> + + If unsure, say Y. + + To compile this driver as a module, choose M here: the + module will be called input. + +if INPUT + +comment "Userland interfaces" + +config INPUT_MOUSEDEV + tristate "Mouse interface" if EMBEDDED + default y + ---help--- + Say Y here if you want your mouse to be accessible as char devices + 13:32+ - /dev/input/mouseX and 13:63 - /dev/input/mice as an + emulated IntelliMouse Explorer PS/2 mouse. That way, all user space + programs (including SVGAlib, GPM and X) will be able to use your + mouse. + + If unsure, say Y. + + To compile this driver as a module, choose M here: the + module will be called mousedev. + +config INPUT_MOUSEDEV_PSAUX + bool "Provide legacy /dev/psaux device" + default y + depends on INPUT_MOUSEDEV + ---help--- + Say Y here if you want your mouse also be accessible as char device + 10:1 - /dev/psaux. The data available through /dev/psaux is exactly + the same as the data from /dev/input/mice. + + If unsure, say Y. + + +config INPUT_MOUSEDEV_SCREEN_X + int "Horizontal screen resolution" + depends on INPUT_MOUSEDEV + default "1024" + help + If you're using a digitizer, or a graphic tablet, and want to use + it as a mouse then the mousedev driver needs to know the X window + screen resolution you are using to correctly scale the data. If + you're not using a digitizer, this value is ignored. + +config INPUT_MOUSEDEV_SCREEN_Y + int "Vertical screen resolution" + depends on INPUT_MOUSEDEV + default "768" + help + If you're using a digitizer, or a graphic tablet, and want to use + it as a mouse then the mousedev driver needs to know the X window + screen resolution you are using to correctly scale the data. If + you're not using a digitizer, this value is ignored. + +config INPUT_JOYDEV + tristate "Joystick interface" + ---help--- + Say Y here if you want your joystick or gamepad to be + accessible as char device 13:0+ - /dev/input/jsX device. + + If unsure, say Y. + + More information is available: <file:Documentation/input/joystick.txt> + + To compile this driver as a module, choose M here: the + module will be called joydev. + +config INPUT_TSDEV + tristate "Touchscreen interface" + ---help--- + Say Y here if you have an application that only can understand the + Compaq touchscreen protocol for absolute pointer data. This is + useful namely for embedded configurations. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called tsdev. + +config INPUT_TSDEV_SCREEN_X + int "Horizontal screen resolution" + depends on INPUT_TSDEV + default "240" + +config INPUT_TSDEV_SCREEN_Y + int "Vertical screen resolution" + depends on INPUT_TSDEV + default "320" + +config INPUT_EVDEV + tristate "Event interface" + help + Say Y here if you want your input device events be accessible + under char device 13:64+ - /dev/input/eventX in a generic way. + + To compile this driver as a module, choose M here: the + module will be called evdev. + +config INPUT_EVBUG + tristate "Event debugging" + ---help--- + Say Y here if you have a problem with the input subsystem and + want all events (keypresses, mouse movements), to be output to + the system log. While this is useful for debugging, it's also + a security threat - your keypresses include your passwords, of + course. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called evbug. + +comment "Input Device Drivers" + +source "drivers/input/keyboard/Kconfig" + +source "drivers/input/mouse/Kconfig" + +source "drivers/input/joystick/Kconfig" + +source "drivers/input/touchscreen/Kconfig" + +source "drivers/input/misc/Kconfig" + +endif + +menu "Hardware I/O ports" + +source "drivers/input/serio/Kconfig" + +source "drivers/input/gameport/Kconfig" + +endmenu + +endmenu + diff --git a/drivers/input/Makefile b/drivers/input/Makefile new file mode 100644 index 000000000000..1a6ff4982f30 --- /dev/null +++ b/drivers/input/Makefile @@ -0,0 +1,19 @@ +# +# Makefile for the input core drivers. +# + +# Each configuration option enables a list of files. + +obj-$(CONFIG_INPUT) += input.o +obj-$(CONFIG_INPUT_MOUSEDEV) += mousedev.o +obj-$(CONFIG_INPUT_JOYDEV) += joydev.o +obj-$(CONFIG_INPUT_EVDEV) += evdev.o +obj-$(CONFIG_INPUT_TSDEV) += tsdev.o +obj-$(CONFIG_INPUT_POWER) += power.o +obj-$(CONFIG_INPUT_EVBUG) += evbug.o + +obj-$(CONFIG_INPUT_KEYBOARD) += keyboard/ +obj-$(CONFIG_INPUT_MOUSE) += mouse/ +obj-$(CONFIG_INPUT_JOYSTICK) += joystick/ +obj-$(CONFIG_INPUT_TOUCHSCREEN) += touchscreen/ +obj-$(CONFIG_INPUT_MISC) += misc/ diff --git a/drivers/input/evbug.c b/drivers/input/evbug.c new file mode 100644 index 000000000000..d7828936fd8f --- /dev/null +++ b/drivers/input/evbug.c @@ -0,0 +1,103 @@ +/* + * $Id: evbug.c,v 1.10 2001/09/25 10:12:07 vojtech Exp $ + * + * Copyright (c) 1999-2001 Vojtech Pavlik + */ + +/* + * Input driver event debug module - dumps all events into syslog + */ + +/* + * 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 either by + * e-mail - mail your message to <vojtech@ucw.cz>, or by paper mail: + * Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic + */ + +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/input.h> +#include <linux/init.h> +#include <linux/device.h> + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION("Input driver event debug module"); +MODULE_LICENSE("GPL"); + +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); +} + +static struct input_handle *evbug_connect(struct input_handler *handler, struct input_dev *dev, struct input_device_id *id) +{ + struct input_handle *handle; + + if (!(handle = kmalloc(sizeof(struct input_handle), GFP_KERNEL))) + return NULL; + memset(handle, 0, sizeof(struct input_handle)); + + handle->dev = dev; + handle->handler = handler; + handle->name = evbug_name; + + input_open_device(handle); + + printk(KERN_DEBUG "evbug.c: Connected device: \"%s\", %s\n", dev->name, dev->phys); + + return handle; +} + +static void evbug_disconnect(struct input_handle *handle) +{ + printk(KERN_DEBUG "evbug.c: Disconnected device: %s\n", handle->dev->phys); + + input_close_device(handle); + + kfree(handle); +} + +static struct input_device_id evbug_ids[] = { + { .driver_info = 1 }, /* Matches all devices */ + { }, /* Terminating zero entry */ +}; + +MODULE_DEVICE_TABLE(input, evbug_ids); + +static struct input_handler evbug_handler = { + .event = evbug_event, + .connect = evbug_connect, + .disconnect = evbug_disconnect, + .name = "evbug", + .id_table = evbug_ids, +}; + +static int __init evbug_init(void) +{ + input_register_handler(&evbug_handler); + return 0; +} + +static void __exit evbug_exit(void) +{ + input_unregister_handler(&evbug_handler); +} + +module_init(evbug_init); +module_exit(evbug_exit); diff --git a/drivers/input/evdev.c b/drivers/input/evdev.c new file mode 100644 index 000000000000..17552a29978b --- /dev/null +++ b/drivers/input/evdev.c @@ -0,0 +1,492 @@ +/* + * Event char devices, giving access to raw input device events. + * + * Copyright (c) 1999-2002 Vojtech Pavlik + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +#define EVDEV_MINOR_BASE 64 +#define EVDEV_MINORS 32 +#define EVDEV_BUFFER_SIZE 64 + +#include <linux/poll.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/major.h> +#include <linux/smp_lock.h> +#include <linux/device.h> +#include <linux/devfs_fs_kernel.h> + +struct evdev { + int exist; + int open; + int minor; + char name[16]; + struct input_handle handle; + wait_queue_head_t wait; + struct evdev_list *grab; + struct list_head list; +}; + +struct evdev_list { + struct input_event buffer[EVDEV_BUFFER_SIZE]; + int head; + int tail; + struct fasync_struct *fasync; + struct evdev *evdev; + struct list_head node; +}; + +static struct evdev *evdev_table[EVDEV_MINORS]; + +static void evdev_event(struct input_handle *handle, unsigned int type, unsigned int code, int value) +{ + struct evdev *evdev = handle->private; + struct evdev_list *list; + + if (evdev->grab) { + list = evdev->grab; + + do_gettimeofday(&list->buffer[list->head].time); + list->buffer[list->head].type = type; + list->buffer[list->head].code = code; + list->buffer[list->head].value = value; + list->head = (list->head + 1) & (EVDEV_BUFFER_SIZE - 1); + + kill_fasync(&list->fasync, SIGIO, POLL_IN); + } else + list_for_each_entry(list, &evdev->list, node) { + + do_gettimeofday(&list->buffer[list->head].time); + list->buffer[list->head].type = type; + list->buffer[list->head].code = code; + list->buffer[list->head].value = value; + list->head = (list->head + 1) & (EVDEV_BUFFER_SIZE - 1); + + kill_fasync(&list->fasync, SIGIO, POLL_IN); + } + + wake_up_interruptible(&evdev->wait); +} + +static int evdev_fasync(int fd, struct file *file, int on) +{ + int retval; + struct evdev_list *list = file->private_data; + retval = fasync_helper(fd, file, on, &list->fasync); + return retval < 0 ? retval : 0; +} + +static int evdev_flush(struct file * file) +{ + struct evdev_list *list = file->private_data; + if (!list->evdev->exist) return -ENODEV; + return input_flush_device(&list->evdev->handle, file); +} + +static void evdev_free(struct evdev *evdev) +{ + evdev_table[evdev->minor] = NULL; + kfree(evdev); +} + +static int evdev_release(struct inode * inode, struct file * file) +{ + struct evdev_list *list = file->private_data; + + if (list->evdev->grab == list) { + input_release_device(&list->evdev->handle); + list->evdev->grab = NULL; + } + + evdev_fasync(-1, file, 0); + list_del(&list->node); + + if (!--list->evdev->open) { + if (list->evdev->exist) + input_close_device(&list->evdev->handle); + else + evdev_free(list->evdev); + } + + kfree(list); + return 0; +} + +static int evdev_open(struct inode * inode, struct file * file) +{ + struct evdev_list *list; + int i = iminor(inode) - EVDEV_MINOR_BASE; + int accept_err; + + if (i >= EVDEV_MINORS || !evdev_table[i] || !evdev_table[i]->exist) + return -ENODEV; + + if ((accept_err = input_accept_process(&(evdev_table[i]->handle), file))) + return accept_err; + + if (!(list = kmalloc(sizeof(struct evdev_list), GFP_KERNEL))) + return -ENOMEM; + memset(list, 0, sizeof(struct evdev_list)); + + list->evdev = evdev_table[i]; + list_add_tail(&list->node, &evdev_table[i]->list); + file->private_data = list; + + if (!list->evdev->open++) + if (list->evdev->exist) + input_open_device(&list->evdev->handle); + + return 0; +} + +static ssize_t evdev_write(struct file * file, const char __user * buffer, size_t count, loff_t *ppos) +{ + struct evdev_list *list = file->private_data; + struct input_event event; + int retval = 0; + + if (!list->evdev->exist) return -ENODEV; + + while (retval < count) { + + if (copy_from_user(&event, buffer + retval, sizeof(struct input_event))) + return -EFAULT; + input_event(list->evdev->handle.dev, event.type, event.code, event.value); + retval += sizeof(struct input_event); + } + + return retval; +} + +static ssize_t evdev_read(struct file * file, char __user * buffer, size_t count, loff_t *ppos) +{ + struct evdev_list *list = file->private_data; + int retval; + + if (count < sizeof(struct input_event)) + return -EINVAL; + + if (list->head == list->tail && list->evdev->exist && (file->f_flags & O_NONBLOCK)) + return -EAGAIN; + + retval = wait_event_interruptible(list->evdev->wait, + list->head != list->tail || (!list->evdev->exist)); + + if (retval) + return retval; + + if (!list->evdev->exist) + return -ENODEV; + + while (list->head != list->tail && retval + sizeof(struct input_event) <= count) { + if (copy_to_user(buffer + retval, list->buffer + list->tail, + sizeof(struct input_event))) return -EFAULT; + list->tail = (list->tail + 1) & (EVDEV_BUFFER_SIZE - 1); + retval += sizeof(struct input_event); + } + + return retval; +} + +/* No kernel lock - fine */ +static unsigned int evdev_poll(struct file *file, poll_table *wait) +{ + struct evdev_list *list = file->private_data; + poll_wait(file, &list->evdev->wait, wait); + return ((list->head == list->tail) ? 0 : (POLLIN | POLLRDNORM)) | + (list->evdev->exist ? 0 : (POLLHUP | POLLERR)); +} + +static int evdev_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) +{ + struct evdev_list *list = file->private_data; + struct evdev *evdev = list->evdev; + struct input_dev *dev = evdev->handle.dev; + struct input_absinfo abs; + void __user *p = (void __user *)arg; + int __user *ip = (int __user *)arg; + int i, t, u, v; + + if (!evdev->exist) return -ENODEV; + + switch (cmd) { + + case EVIOCGVERSION: + return put_user(EV_VERSION, ip); + + case EVIOCGID: + return copy_to_user(p, &dev->id, sizeof(struct input_id)) ? -EFAULT : 0; + + case EVIOCGKEYCODE: + if (get_user(t, ip)) return -EFAULT; + if (t < 0 || t >= dev->keycodemax || !dev->keycodesize) return -EINVAL; + if (put_user(INPUT_KEYCODE(dev, t), ip + 1)) return -EFAULT; + return 0; + + case EVIOCSKEYCODE: + if (get_user(t, ip)) return -EFAULT; + if (t < 0 || t >= dev->keycodemax || !dev->keycodesize) return -EINVAL; + if (get_user(v, ip + 1)) return -EFAULT; + if (v < 0 || v > KEY_MAX) return -EINVAL; + u = SET_INPUT_KEYCODE(dev, t, v); + clear_bit(u, dev->keybit); + set_bit(v, dev->keybit); + for (i = 0; i < dev->keycodemax; i++) + if (INPUT_KEYCODE(dev,i) == u) + set_bit(u, dev->keybit); + 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 *)arg)->id))) + return -EFAULT; + return err; + } + else return -ENOSYS; + + case EVIOCRMFF: + if (dev->erase_effect) { + return dev->erase_effect(dev, (int)arg); + } + else return -ENOSYS; + + case EVIOCGEFFECTS: + if (put_user(dev->ff_effects_max, ip)) + return -EFAULT; + return 0; + + case EVIOCGRAB: + if (arg) { + if (evdev->grab) + return -EBUSY; + if (input_grab_device(&evdev->handle)) + return -EBUSY; + evdev->grab = list; + return 0; + } else { + if (evdev->grab != list) + return -EINVAL; + input_release_device(&evdev->handle); + evdev->grab = NULL; + return 0; + } + + default: + + if (_IOC_TYPE(cmd) != 'E' || _IOC_DIR(cmd) != _IOC_READ) + return -EINVAL; + + if ((_IOC_NR(cmd) & ~EV_MAX) == _IOC_NR(EVIOCGBIT(0,0))) { + + long *bits; + int len; + + switch (_IOC_NR(cmd) & EV_MAX) { + case 0: bits = dev->evbit; len = EV_MAX; break; + case EV_KEY: bits = dev->keybit; len = KEY_MAX; break; + case EV_REL: bits = dev->relbit; len = REL_MAX; break; + case EV_ABS: bits = dev->absbit; len = ABS_MAX; break; + case EV_MSC: bits = dev->mscbit; len = MSC_MAX; break; + case EV_LED: bits = dev->ledbit; len = LED_MAX; break; + case EV_SND: bits = dev->sndbit; len = SND_MAX; break; + case EV_FF: bits = dev->ffbit; len = FF_MAX; break; + default: return -EINVAL; + } + len = NBITS(len) * sizeof(long); + if (len > _IOC_SIZE(cmd)) len = _IOC_SIZE(cmd); + return copy_to_user(p, bits, len) ? -EFAULT : len; + } + + if (_IOC_NR(cmd) == _IOC_NR(EVIOCGKEY(0))) { + int len; + len = NBITS(KEY_MAX) * sizeof(long); + if (len > _IOC_SIZE(cmd)) len = _IOC_SIZE(cmd); + return copy_to_user(p, dev->key, len) ? -EFAULT : len; + } + + if (_IOC_NR(cmd) == _IOC_NR(EVIOCGLED(0))) { + int len; + len = NBITS(LED_MAX) * sizeof(long); + if (len > _IOC_SIZE(cmd)) len = _IOC_SIZE(cmd); + return copy_to_user(p, dev->led, len) ? -EFAULT : len; + } + + if (_IOC_NR(cmd) == _IOC_NR(EVIOCGSND(0))) { + int len; + len = NBITS(SND_MAX) * sizeof(long); + if (len > _IOC_SIZE(cmd)) len = _IOC_SIZE(cmd); + return copy_to_user(p, dev->snd, len) ? -EFAULT : len; + } + + if (_IOC_NR(cmd) == _IOC_NR(EVIOCGNAME(0))) { + int len; + if (!dev->name) return -ENOENT; + len = strlen(dev->name) + 1; + if (len > _IOC_SIZE(cmd)) len = _IOC_SIZE(cmd); + return copy_to_user(p, dev->name, len) ? -EFAULT : len; + } + + if (_IOC_NR(cmd) == _IOC_NR(EVIOCGPHYS(0))) { + int len; + if (!dev->phys) return -ENOENT; + len = strlen(dev->phys) + 1; + if (len > _IOC_SIZE(cmd)) len = _IOC_SIZE(cmd); + return copy_to_user(p, dev->phys, len) ? -EFAULT : len; + } + + if (_IOC_NR(cmd) == _IOC_NR(EVIOCGUNIQ(0))) { + int len; + if (!dev->uniq) return -ENOENT; + len = strlen(dev->uniq) + 1; + if (len > _IOC_SIZE(cmd)) len = _IOC_SIZE(cmd); + return copy_to_user(p, dev->uniq, len) ? -EFAULT : len; + } + + if ((_IOC_NR(cmd) & ~ABS_MAX) == _IOC_NR(EVIOCGABS(0))) { + + int t = _IOC_NR(cmd) & ABS_MAX; + + abs.value = dev->abs[t]; + abs.minimum = dev->absmin[t]; + abs.maximum = dev->absmax[t]; + abs.fuzz = dev->absfuzz[t]; + abs.flat = dev->absflat[t]; + + if (copy_to_user(p, &abs, sizeof(struct input_absinfo))) + return -EFAULT; + + return 0; + } + + if ((_IOC_NR(cmd) & ~ABS_MAX) == _IOC_NR(EVIOCSABS(0))) { + + int t = _IOC_NR(cmd) & ABS_MAX; + + if (copy_from_user(&abs, p, sizeof(struct input_absinfo))) + return -EFAULT; + + dev->abs[t] = abs.value; + dev->absmin[t] = abs.minimum; + dev->absmax[t] = abs.maximum; + dev->absfuzz[t] = abs.fuzz; + dev->absflat[t] = abs.flat; + + return 0; + } + } + return -EINVAL; +} + +static struct file_operations evdev_fops = { + .owner = THIS_MODULE, + .read = evdev_read, + .write = evdev_write, + .poll = evdev_poll, + .open = evdev_open, + .release = evdev_release, + .ioctl = evdev_ioctl, + .fasync = evdev_fasync, + .flush = evdev_flush +}; + +static struct input_handle *evdev_connect(struct input_handler *handler, struct input_dev *dev, struct input_device_id *id) +{ + struct evdev *evdev; + int minor; + + for (minor = 0; minor < EVDEV_MINORS && evdev_table[minor]; minor++); + if (minor == EVDEV_MINORS) { + printk(KERN_ERR "evdev: no more free evdev devices\n"); + return NULL; + } + + if (!(evdev = kmalloc(sizeof(struct evdev), GFP_KERNEL))) + return NULL; + memset(evdev, 0, sizeof(struct evdev)); + + INIT_LIST_HEAD(&evdev->list); + init_waitqueue_head(&evdev->wait); + + evdev->exist = 1; + evdev->minor = minor; + evdev->handle.dev = dev; + evdev->handle.name = evdev->name; + evdev->handle.handler = handler; + evdev->handle.private = evdev; + sprintf(evdev->name, "event%d", minor); + + evdev_table[minor] = evdev; + + devfs_mk_cdev(MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor), + S_IFCHR|S_IRUGO|S_IWUSR, "input/event%d", minor); + class_simple_device_add(input_class, + MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor), + dev->dev, "event%d", minor); + + return &evdev->handle; +} + +static void evdev_disconnect(struct input_handle *handle) +{ + struct evdev *evdev = handle->private; + struct evdev_list *list; + + class_simple_device_remove(MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + evdev->minor)); + devfs_remove("input/event%d", evdev->minor); + evdev->exist = 0; + + if (evdev->open) { + input_close_device(handle); + wake_up_interruptible(&evdev->wait); + list_for_each_entry(list, &evdev->list, node) + kill_fasync(&list->fasync, SIGIO, POLL_HUP); + } else + evdev_free(evdev); +} + +static struct input_device_id evdev_ids[] = { + { .driver_info = 1 }, /* Matches all devices */ + { }, /* Terminating zero entry */ +}; + +MODULE_DEVICE_TABLE(input, evdev_ids); + +static struct input_handler evdev_handler = { + .event = evdev_event, + .connect = evdev_connect, + .disconnect = evdev_disconnect, + .fops = &evdev_fops, + .minor = EVDEV_MINOR_BASE, + .name = "evdev", + .id_table = evdev_ids, +}; + +static int __init evdev_init(void) +{ + input_register_handler(&evdev_handler); + return 0; +} + +static void __exit evdev_exit(void) +{ + input_unregister_handler(&evdev_handler); +} + +module_init(evdev_init); +module_exit(evdev_exit); + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION("Input driver event char devices"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/gameport/Kconfig b/drivers/input/gameport/Kconfig new file mode 100644 index 000000000000..6282f460aba0 --- /dev/null +++ b/drivers/input/gameport/Kconfig @@ -0,0 +1,90 @@ +# +# Gameport configuration +# +config GAMEPORT + tristate "Gameport support" + ---help--- + Gameport support is for the standard 15-pin PC gameport. If you + have a joystick, gamepad, gameport card, a soundcard with a gameport + or anything else that uses the gameport, say Y or M here and also to + at least one of the hardware specific drivers. + + For Ensoniq AudioPCI (ES1370), AudioPCI 97 (ES1371), ESS Solo1, + S3 SonicVibes, Trident 4DWave, SiS7018, and ALi 5451 gameport + support is provided by the sound drivers, so you won't need any + from the below listed modules. You still need to say Y here. + + If unsure, say Y. + + To compile this driver as a module, choose M here: the + module will be called gameport. + +if GAMEPORT + +config GAMEPORT_NS558 + tristate "Classic ISA and PnP gameport support" + help + Say Y here if you have an ISA or PnP gameport. + + If unsure, say Y. + + To compile this driver as a module, choose M here: the + module will be called ns558. + +config GAMEPORT_L4 + tristate "PDPI Lightning 4 gamecard support" + help + Say Y here if you have a PDPI Lightning 4 gamecard. + + To compile this driver as a module, choose M here: the + module will be called lightning. + +config GAMEPORT_EMU10K1 + tristate "SB Live and Audigy gameport support" + depends on PCI + help + Say Y here if you have a SoundBlaster Live! or SoundBlaster + Audigy card and want to use its gameport. + + To compile this driver as a module, choose M here: the + module will be called emu10k1-gp. + +config GAMEPORT_VORTEX + tristate "Aureal Vortex, Vortex 2 gameport support" + depends on PCI + help + Say Y here if you have an Aureal Vortex 1 or 2 card and want + to use its gameport. + + To compile this driver as a module, choose M here: the + module will be called vortex. + +config GAMEPORT_FM801 + tristate "ForteMedia FM801 gameport support" + depends on PCI + +config GAMEPORT_CS461X + tristate "Crystal SoundFusion gameport support" + depends on PCI + +endif + +# Yes, SOUND_GAMEPORT looks a bit odd. Yes, it ends up being turned on +# in every .config. Please don't touch it. It is here to handle an +# unusual dependency between GAMEPORT and sound drivers. +# +# Some sound drivers call gameport functions. If GAMEPORT is +# not selected, empty stubs are provided for the functions and all is +# well. +# If GAMEPORT is built in, everything is fine. +# If GAMEPORT is a module, however, it would need to be loaded for the +# sound driver to be able to link properly. Therefore, the sound +# driver must be a module as well in that case. Since there's no way +# to express that directly in Kconfig, we use SOUND_GAMEPORT to +# express it. SOUND_GAMEPORT boils down to "if GAMEPORT is 'm', +# anything that depends on SOUND_GAMEPORT must be 'm' as well. if +# GAMEPORT is 'y' or 'n', it can be anything". +config SOUND_GAMEPORT + tristate + default m if GAMEPORT=m + default y diff --git a/drivers/input/gameport/Makefile b/drivers/input/gameport/Makefile new file mode 100644 index 000000000000..5367b4267adf --- /dev/null +++ b/drivers/input/gameport/Makefile @@ -0,0 +1,13 @@ +# +# Makefile for the gameport drivers. +# + +# Each configuration option enables a list of files. + +obj-$(CONFIG_GAMEPORT) += gameport.o +obj-$(CONFIG_GAMEPORT_CS461X) += cs461x.o +obj-$(CONFIG_GAMEPORT_EMU10K1) += emu10k1-gp.o +obj-$(CONFIG_GAMEPORT_FM801) += fm801-gp.o +obj-$(CONFIG_GAMEPORT_L4) += lightning.o +obj-$(CONFIG_GAMEPORT_NS558) += ns558.o +obj-$(CONFIG_GAMEPORT_VORTEX) += vortex.o diff --git a/drivers/input/gameport/cs461x.c b/drivers/input/gameport/cs461x.c new file mode 100644 index 000000000000..d4013ff98623 --- /dev/null +++ b/drivers/input/gameport/cs461x.c @@ -0,0 +1,322 @@ +/* + The all defines and part of code (such as cs461x_*) are + contributed from ALSA 0.5.8 sources. + See http://www.alsa-project.org/ for sources + + Tested on Linux 686 2.4.0-test9, ALSA 0.5.8a and CS4610 +*/ + +#include <asm/io.h> + +#include <linux/module.h> +#include <linux/ioport.h> +#include <linux/config.h> +#include <linux/init.h> +#include <linux/gameport.h> +#include <linux/slab.h> +#include <linux/pci.h> + +MODULE_AUTHOR("Victor Krapivin"); +MODULE_LICENSE("GPL"); + +/* + These options are experimental + +#define CS461X_FULL_MAP +*/ + + +#ifndef PCI_VENDOR_ID_CIRRUS +#define PCI_VENDOR_ID_CIRRUS 0x1013 +#endif +#ifndef PCI_DEVICE_ID_CIRRUS_4610 +#define PCI_DEVICE_ID_CIRRUS_4610 0x6001 +#endif +#ifndef PCI_DEVICE_ID_CIRRUS_4612 +#define PCI_DEVICE_ID_CIRRUS_4612 0x6003 +#endif +#ifndef PCI_DEVICE_ID_CIRRUS_4615 +#define PCI_DEVICE_ID_CIRRUS_4615 0x6004 +#endif + +/* Registers */ + +#define BA0_JSPT 0x00000480 +#define BA0_JSCTL 0x00000484 +#define BA0_JSC1 0x00000488 +#define BA0_JSC2 0x0000048C +#define BA0_JSIO 0x000004A0 + +/* Bits for JSPT */ + +#define JSPT_CAX 0x00000001 +#define JSPT_CAY 0x00000002 +#define JSPT_CBX 0x00000004 +#define JSPT_CBY 0x00000008 +#define JSPT_BA1 0x00000010 +#define JSPT_BA2 0x00000020 +#define JSPT_BB1 0x00000040 +#define JSPT_BB2 0x00000080 + +/* Bits for JSCTL */ + +#define JSCTL_SP_MASK 0x00000003 +#define JSCTL_SP_SLOW 0x00000000 +#define JSCTL_SP_MEDIUM_SLOW 0x00000001 +#define JSCTL_SP_MEDIUM_FAST 0x00000002 +#define JSCTL_SP_FAST 0x00000003 +#define JSCTL_ARE 0x00000004 + +/* Data register pairs masks */ + +#define JSC1_Y1V_MASK 0x0000FFFF +#define JSC1_X1V_MASK 0xFFFF0000 +#define JSC1_Y1V_SHIFT 0 +#define JSC1_X1V_SHIFT 16 +#define JSC2_Y2V_MASK 0x0000FFFF +#define JSC2_X2V_MASK 0xFFFF0000 +#define JSC2_Y2V_SHIFT 0 +#define JSC2_X2V_SHIFT 16 + +/* JS GPIO */ + +#define JSIO_DAX 0x00000001 +#define JSIO_DAY 0x00000002 +#define JSIO_DBX 0x00000004 +#define JSIO_DBY 0x00000008 +#define JSIO_AXOE 0x00000010 +#define JSIO_AYOE 0x00000020 +#define JSIO_BXOE 0x00000040 +#define JSIO_BYOE 0x00000080 + +/* + The card initialization code is obfuscated; the module cs461x + need to be loaded after ALSA modules initialized and something + played on the CS 4610 chip (see sources for details of CS4610 + initialization code from ALSA) +*/ + +/* Card specific definitions */ + +#define CS461X_BA0_SIZE 0x2000 +#define CS461X_BA1_DATA0_SIZE 0x3000 +#define CS461X_BA1_DATA1_SIZE 0x3800 +#define CS461X_BA1_PRG_SIZE 0x7000 +#define CS461X_BA1_REG_SIZE 0x0100 + +#define BA1_SP_DMEM0 0x00000000 +#define BA1_SP_DMEM1 0x00010000 +#define BA1_SP_PMEM 0x00020000 +#define BA1_SP_REG 0x00030000 + +#define BA1_DWORD_SIZE (13 * 1024 + 512) +#define BA1_MEMORY_COUNT 3 + +/* + Only one CS461x card is still suppoted; the code requires + redesign to avoid this limitatuion. +*/ + +static unsigned long ba0_addr; +static unsigned int __iomem *ba0; + +#ifdef CS461X_FULL_MAP +static unsigned long ba1_addr; +static union ba1_t { + struct { + unsigned int __iomem *data0; + unsigned int __iomem *data1; + unsigned int __iomem *pmem; + unsigned int __iomem *reg; + } name; + unsigned int __iomem *idx[4]; +} ba1; + +static void cs461x_poke(unsigned long reg, unsigned int val) +{ + writel(val, &ba1.idx[(reg >> 16) & 3][(reg >> 2) & 0x3fff]); +} + +static unsigned int cs461x_peek(unsigned long reg) +{ + return readl(&ba1.idx[(reg >> 16) & 3][(reg >> 2) & 0x3fff]); +} + +#endif + +static void cs461x_pokeBA0(unsigned long reg, unsigned int val) +{ + writel(val, &ba0[reg >> 2]); +} + +static unsigned int cs461x_peekBA0(unsigned long reg) +{ + return readl(&ba0[reg >> 2]); +} + +static int cs461x_free(struct pci_dev *pdev) +{ + struct gameport *port = pci_get_drvdata(pdev); + + if (port) + gameport_unregister_port(port); + + if (ba0) iounmap(ba0); +#ifdef CS461X_FULL_MAP + if (ba1.name.data0) iounmap(ba1.name.data0); + if (ba1.name.data1) iounmap(ba1.name.data1); + if (ba1.name.pmem) iounmap(ba1.name.pmem); + if (ba1.name.reg) iounmap(ba1.name.reg); +#endif + return 0; +} + +static void cs461x_gameport_trigger(struct gameport *gameport) +{ + cs461x_pokeBA0(BA0_JSPT, 0xFF); //outb(gameport->io, 0xFF); +} + +static unsigned char cs461x_gameport_read(struct gameport *gameport) +{ + return cs461x_peekBA0(BA0_JSPT); //inb(gameport->io); +} + +static int cs461x_gameport_cooked_read(struct gameport *gameport, int *axes, int *buttons) +{ + unsigned js1, js2, jst; + + js1 = cs461x_peekBA0(BA0_JSC1); + js2 = cs461x_peekBA0(BA0_JSC2); + jst = cs461x_peekBA0(BA0_JSPT); + + *buttons = (~jst >> 4) & 0x0F; + + axes[0] = ((js1 & JSC1_Y1V_MASK) >> JSC1_Y1V_SHIFT) & 0xFFFF; + axes[1] = ((js1 & JSC1_X1V_MASK) >> JSC1_X1V_SHIFT) & 0xFFFF; + axes[2] = ((js2 & JSC2_Y2V_MASK) >> JSC2_Y2V_SHIFT) & 0xFFFF; + axes[3] = ((js2 & JSC2_X2V_MASK) >> JSC2_X2V_SHIFT) & 0xFFFF; + + for(jst=0;jst<4;++jst) + if(axes[jst]==0xFFFF) axes[jst] = -1; + return 0; +} + +static int cs461x_gameport_open(struct gameport *gameport, int mode) +{ + switch (mode) { + case GAMEPORT_MODE_COOKED: + case GAMEPORT_MODE_RAW: + return 0; + default: + return -1; + } + return 0; +} + +static struct pci_device_id cs461x_pci_tbl[] = { + { PCI_VENDOR_ID_CIRRUS, 0x6001, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, /* Cirrus CS4610 */ + { PCI_VENDOR_ID_CIRRUS, 0x6003, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, /* Cirrus CS4612 */ + { PCI_VENDOR_ID_CIRRUS, 0x6005, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, /* Cirrus CS4615 */ + { 0, } +}; +MODULE_DEVICE_TABLE(pci, cs461x_pci_tbl); + +static int __devinit cs461x_pci_probe(struct pci_dev *pdev, const struct pci_device_id *ent) +{ + int rc; + struct gameport* port; + + rc = pci_enable_device(pdev); + if (rc) { + printk(KERN_ERR "cs461x: Cannot enable PCI gameport (bus %d, devfn %d) error=%d\n", + pdev->bus->number, pdev->devfn, rc); + return rc; + } + + ba0_addr = pci_resource_start(pdev, 0); +#ifdef CS461X_FULL_MAP + ba1_addr = pci_resource_start(pdev, 1); +#endif + if (ba0_addr == 0 || ba0_addr == ~0 +#ifdef CS461X_FULL_MAP + || ba1_addr == 0 || ba1_addr == ~0 +#endif + ) { + printk(KERN_ERR "cs461x: wrong address - ba0 = 0x%lx\n", ba0_addr); +#ifdef CS461X_FULL_MAP + printk(KERN_ERR "cs461x: wrong address - ba1 = 0x%lx\n", ba1_addr); +#endif + cs461x_free(pdev); + return -ENOMEM; + } + + ba0 = ioremap(ba0_addr, CS461X_BA0_SIZE); +#ifdef CS461X_FULL_MAP + ba1.name.data0 = ioremap(ba1_addr + BA1_SP_DMEM0, CS461X_BA1_DATA0_SIZE); + ba1.name.data1 = ioremap(ba1_addr + BA1_SP_DMEM1, CS461X_BA1_DATA1_SIZE); + ba1.name.pmem = ioremap(ba1_addr + BA1_SP_PMEM, CS461X_BA1_PRG_SIZE); + ba1.name.reg = ioremap(ba1_addr + BA1_SP_REG, CS461X_BA1_REG_SIZE); + + if (ba0 == NULL || ba1.name.data0 == NULL || + ba1.name.data1 == NULL || ba1.name.pmem == NULL || + ba1.name.reg == NULL) { + cs461x_free(pdev); + return -ENOMEM; + } +#else + if (ba0 == NULL) { + cs461x_free(pdev); + return -ENOMEM; + } +#endif + + if (!(port = gameport_allocate_port())) { + printk(KERN_ERR "cs461x: Memory allocation failed\n"); + cs461x_free(pdev); + return -ENOMEM; + } + + pci_set_drvdata(pdev, port); + + port->open = cs461x_gameport_open; + port->trigger = cs461x_gameport_trigger; + port->read = cs461x_gameport_read; + port->cooked_read = cs461x_gameport_cooked_read; + + gameport_set_name(port, "CS416x"); + gameport_set_phys(port, "pci%s/gameport0", pci_name(pdev)); + port->dev.parent = &pdev->dev; + + cs461x_pokeBA0(BA0_JSIO, 0xFF); // ? + cs461x_pokeBA0(BA0_JSCTL, JSCTL_SP_MEDIUM_SLOW); + + gameport_register_port(port); + + return 0; +} + +static void __devexit cs461x_pci_remove(struct pci_dev *pdev) +{ + cs461x_free(pdev); +} + +static struct pci_driver cs461x_pci_driver = { + .name = "CS461x_gameport", + .id_table = cs461x_pci_tbl, + .probe = cs461x_pci_probe, + .remove = __devexit_p(cs461x_pci_remove), +}; + +static int __init cs461x_init(void) +{ + return pci_register_driver(&cs461x_pci_driver); +} + +static void __exit cs461x_exit(void) +{ + pci_unregister_driver(&cs461x_pci_driver); +} + +module_init(cs461x_init); +module_exit(cs461x_exit); + diff --git a/drivers/input/gameport/emu10k1-gp.c b/drivers/input/gameport/emu10k1-gp.c new file mode 100644 index 000000000000..a0118038330a --- /dev/null +++ b/drivers/input/gameport/emu10k1-gp.c @@ -0,0 +1,132 @@ +/* + * $Id: emu10k1-gp.c,v 1.8 2002/01/22 20:40:46 vojtech Exp $ + * + * Copyright (c) 2001 Vojtech Pavlik + */ + +/* + * EMU10k1 - SB Live / Audigy - gameport driver for Linux + */ + +/* + * 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 either by + * e-mail - mail your message to <vojtech@ucw.cz>, or by paper mail: + * Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic + */ + +#include <asm/io.h> + +#include <linux/module.h> +#include <linux/ioport.h> +#include <linux/config.h> +#include <linux/init.h> +#include <linux/gameport.h> +#include <linux/slab.h> +#include <linux/pci.h> + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION("EMU10k1 gameport driver"); +MODULE_LICENSE("GPL"); + +struct emu { + struct pci_dev *dev; + struct gameport *gameport; + int io; + int size; +}; + +static struct pci_device_id emu_tbl[] = { + + { 0x1102, 0x7002, PCI_ANY_ID, PCI_ANY_ID }, /* SB Live gameport */ + { 0x1102, 0x7003, PCI_ANY_ID, PCI_ANY_ID }, /* Audigy gameport */ + { 0x1102, 0x7004, PCI_ANY_ID, PCI_ANY_ID }, /* Dell SB Live */ + { 0x1102, 0x7005, PCI_ANY_ID, PCI_ANY_ID }, /* Audigy LS gameport */ + { 0, } +}; + +MODULE_DEVICE_TABLE(pci, emu_tbl); + +static int __devinit emu_probe(struct pci_dev *pdev, const struct pci_device_id *ent) +{ + int ioport, iolen; + struct emu *emu; + struct gameport *port; + + if (pci_enable_device(pdev)) + return -EBUSY; + + ioport = pci_resource_start(pdev, 0); + iolen = pci_resource_len(pdev, 0); + + if (!request_region(ioport, iolen, "emu10k1-gp")) + return -EBUSY; + + emu = kcalloc(1, sizeof(struct emu), GFP_KERNEL); + port = gameport_allocate_port(); + if (!emu || !port) { + printk(KERN_ERR "emu10k1-gp: Memory allocation failed\n"); + release_region(ioport, iolen); + kfree(emu); + gameport_free_port(port); + return -ENOMEM; + } + + emu->io = ioport; + emu->size = iolen; + emu->dev = pdev; + emu->gameport = port; + + gameport_set_name(port, "EMU10K1"); + gameport_set_phys(port, "pci%s/gameport0", pci_name(pdev)); + port->dev.parent = &pdev->dev; + port->io = ioport; + + pci_set_drvdata(pdev, emu); + + gameport_register_port(port); + + return 0; +} + +static void __devexit emu_remove(struct pci_dev *pdev) +{ + struct emu *emu = pci_get_drvdata(pdev); + + gameport_unregister_port(emu->gameport); + release_region(emu->io, emu->size); + kfree(emu); +} + +static struct pci_driver emu_driver = { + .name = "Emu10k1_gameport", + .id_table = emu_tbl, + .probe = emu_probe, + .remove = __devexit_p(emu_remove), +}; + +static int __init emu_init(void) +{ + return pci_register_driver(&emu_driver); +} + +static void __exit emu_exit(void) +{ + pci_unregister_driver(&emu_driver); +} + +module_init(emu_init); +module_exit(emu_exit); diff --git a/drivers/input/gameport/fm801-gp.c b/drivers/input/gameport/fm801-gp.c new file mode 100644 index 000000000000..57615bc63906 --- /dev/null +++ b/drivers/input/gameport/fm801-gp.c @@ -0,0 +1,163 @@ +/* + * FM801 gameport driver for Linux + * + * Copyright (c) by Takashi Iwai <tiwai@suse.de> + * + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include <asm/io.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/ioport.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/gameport.h> + +#define PCI_VENDOR_ID_FORTEMEDIA 0x1319 +#define PCI_DEVICE_ID_FM801_GP 0x0802 + +#define HAVE_COOKED + +struct fm801_gp { + struct gameport *gameport; + struct resource *res_port; +}; + +#ifdef HAVE_COOKED +static int fm801_gp_cooked_read(struct gameport *gameport, int *axes, int *buttons) +{ + unsigned short w; + + w = inw(gameport->io + 2); + *buttons = (~w >> 14) & 0x03; + axes[0] = (w == 0xffff) ? -1 : ((w & 0x1fff) << 5); + w = inw(gameport->io + 4); + axes[1] = (w == 0xffff) ? -1 : ((w & 0x1fff) << 5); + w = inw(gameport->io + 6); + *buttons |= ((~w >> 14) & 0x03) << 2; + axes[2] = (w == 0xffff) ? -1 : ((w & 0x1fff) << 5); + w = inw(gameport->io + 8); + axes[3] = (w == 0xffff) ? -1 : ((w & 0x1fff) << 5); + outw(0xff, gameport->io); /* reset */ + + return 0; +} +#endif + +static int fm801_gp_open(struct gameport *gameport, int mode) +{ + switch (mode) { +#ifdef HAVE_COOKED + case GAMEPORT_MODE_COOKED: + return 0; +#endif + case GAMEPORT_MODE_RAW: + return 0; + default: + return -1; + } + + return 0; +} + +static int __devinit fm801_gp_probe(struct pci_dev *pci, const struct pci_device_id *id) +{ + struct fm801_gp *gp; + struct gameport *port; + + gp = kcalloc(1, sizeof(struct fm801_gp), GFP_KERNEL); + port = gameport_allocate_port(); + if (!gp || !port) { + printk(KERN_ERR "fm801-gp: Memory allocation failed\n"); + kfree(gp); + gameport_free_port(port); + return -ENOMEM; + } + + pci_enable_device(pci); + + port->open = fm801_gp_open; +#ifdef HAVE_COOKED + port->cooked_read = fm801_gp_cooked_read; +#endif + gameport_set_name(port, "FM801"); + gameport_set_phys(port, "pci%s/gameport0", pci_name(pci)); + port->dev.parent = &pci->dev; + port->io = pci_resource_start(pci, 0); + + gp->gameport = port; + gp->res_port = request_region(port->io, 0x10, "FM801 GP"); + if (!gp->res_port) { + kfree(gp); + gameport_free_port(port); + printk(KERN_DEBUG "fm801-gp: unable to grab region 0x%x-0x%x\n", + port->io, port->io + 0x0f); + return -EBUSY; + } + + pci_set_drvdata(pci, gp); + + outb(0x60, port->io + 0x0d); /* enable joystick 1 and 2 */ + gameport_register_port(port); + + return 0; +} + +static void __devexit fm801_gp_remove(struct pci_dev *pci) +{ + struct fm801_gp *gp = pci_get_drvdata(pci); + + if (gp) { + gameport_unregister_port(gp->gameport); + release_resource(gp->res_port); + kfree(gp); + } +} + +static struct pci_device_id fm801_gp_id_table[] = { + { PCI_VENDOR_ID_FORTEMEDIA, PCI_DEVICE_ID_FM801_GP, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, + { 0 } +}; + +static struct pci_driver fm801_gp_driver = { + .name = "FM801_gameport", + .id_table = fm801_gp_id_table, + .probe = fm801_gp_probe, + .remove = __devexit_p(fm801_gp_remove), +}; + +static int __init fm801_gp_init(void) +{ + return pci_register_driver(&fm801_gp_driver); +} + +static void __exit fm801_gp_exit(void) +{ + pci_unregister_driver(&fm801_gp_driver); +} + +module_init(fm801_gp_init); +module_exit(fm801_gp_exit); + +MODULE_DEVICE_TABLE(pci, fm801_gp_id_table); + +MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/gameport/gameport.c b/drivers/input/gameport/gameport.c new file mode 100644 index 000000000000..f20c3f23388b --- /dev/null +++ b/drivers/input/gameport/gameport.c @@ -0,0 +1,797 @@ +/* + * Generic gameport layer + * + * Copyright (c) 1999-2002 Vojtech Pavlik + * Copyright (c) 2005 Dmitry Torokhov + */ + +/* + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +#include <linux/stddef.h> +#include <linux/module.h> +#include <linux/ioport.h> +#include <linux/init.h> +#include <linux/gameport.h> +#include <linux/wait.h> +#include <linux/completion.h> +#include <linux/sched.h> +#include <linux/smp_lock.h> +#include <linux/slab.h> +#include <linux/delay.h> + +/*#include <asm/io.h>*/ + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION("Generic gameport layer"); +MODULE_LICENSE("GPL"); + +EXPORT_SYMBOL(__gameport_register_port); +EXPORT_SYMBOL(gameport_unregister_port); +EXPORT_SYMBOL(__gameport_register_driver); +EXPORT_SYMBOL(gameport_unregister_driver); +EXPORT_SYMBOL(gameport_open); +EXPORT_SYMBOL(gameport_close); +EXPORT_SYMBOL(gameport_rescan); +EXPORT_SYMBOL(gameport_cooked_read); +EXPORT_SYMBOL(gameport_set_name); +EXPORT_SYMBOL(gameport_set_phys); +EXPORT_SYMBOL(gameport_start_polling); +EXPORT_SYMBOL(gameport_stop_polling); + +/* + * gameport_sem protects entire gameport subsystem and is taken + * every time gameport port or driver registrered or unregistered. + */ +static DECLARE_MUTEX(gameport_sem); + +static LIST_HEAD(gameport_list); + +static struct bus_type gameport_bus = { + .name = "gameport", +}; + +static void gameport_add_port(struct gameport *gameport); +static void gameport_destroy_port(struct gameport *gameport); +static void gameport_reconnect_port(struct gameport *gameport); +static void gameport_disconnect_port(struct gameport *gameport); + +#if defined(__i386__) + +#define DELTA(x,y) ((y)-(x)+((y)<(x)?1193182/HZ:0)) +#define GET_TIME(x) do { x = get_time_pit(); } while (0) + +static unsigned int get_time_pit(void) +{ + extern spinlock_t i8253_lock; + unsigned long flags; + unsigned int count; + + spin_lock_irqsave(&i8253_lock, flags); + outb_p(0x00, 0x43); + count = inb_p(0x40); + count |= inb_p(0x40) << 8; + spin_unlock_irqrestore(&i8253_lock, flags); + + return count; +} + +#endif + + + +/* + * gameport_measure_speed() measures the gameport i/o speed. + */ + +static int gameport_measure_speed(struct gameport *gameport) +{ +#if defined(__i386__) + + unsigned int i, t, t1, t2, t3, tx; + unsigned long flags; + + if (gameport_open(gameport, NULL, GAMEPORT_MODE_RAW)) + return 0; + + tx = 1 << 30; + + for(i = 0; i < 50; i++) { + local_irq_save(flags); + GET_TIME(t1); + for (t = 0; t < 50; t++) gameport_read(gameport); + GET_TIME(t2); + GET_TIME(t3); + local_irq_restore(flags); + udelay(i * 10); + if ((t = DELTA(t2,t1) - DELTA(t3,t2)) < tx) tx = t; + } + + gameport_close(gameport); + return 59659 / (tx < 1 ? 1 : tx); + +#elif defined (__x86_64__) + + unsigned int i, t; + unsigned long tx, t1, t2, flags; + + if (gameport_open(gameport, NULL, GAMEPORT_MODE_RAW)) + return 0; + + tx = 1 << 30; + + for(i = 0; i < 50; i++) { + local_irq_save(flags); + rdtscl(t1); + for (t = 0; t < 50; t++) gameport_read(gameport); + rdtscl(t2); + local_irq_restore(flags); + udelay(i * 10); + if (t2 - t1 < tx) tx = t2 - t1; + } + + gameport_close(gameport); + return (cpu_data[_smp_processor_id()].loops_per_jiffy * (unsigned long)HZ / (1000 / 50)) / (tx < 1 ? 1 : tx); + +#else + + unsigned int j, t = 0; + + if (gameport_open(gameport, NULL, GAMEPORT_MODE_RAW)) + return 0; + + j = jiffies; while (j == jiffies); + j = jiffies; while (j == jiffies) { t++; gameport_read(gameport); } + + gameport_close(gameport); + return t * HZ / 1000; + +#endif +} + +void gameport_start_polling(struct gameport *gameport) +{ + spin_lock(&gameport->timer_lock); + + if (!gameport->poll_cnt++) { + BUG_ON(!gameport->poll_handler); + BUG_ON(!gameport->poll_interval); + mod_timer(&gameport->poll_timer, jiffies + msecs_to_jiffies(gameport->poll_interval)); + } + + spin_unlock(&gameport->timer_lock); +} + +void gameport_stop_polling(struct gameport *gameport) +{ + spin_lock(&gameport->timer_lock); + + if (!--gameport->poll_cnt) + del_timer(&gameport->poll_timer); + + spin_unlock(&gameport->timer_lock); +} + +static void gameport_run_poll_handler(unsigned long d) +{ + struct gameport *gameport = (struct gameport *)d; + + gameport->poll_handler(gameport); + if (gameport->poll_cnt) + mod_timer(&gameport->poll_timer, jiffies + msecs_to_jiffies(gameport->poll_interval)); +} + +/* + * Basic gameport -> driver core mappings + */ + +static void gameport_bind_driver(struct gameport *gameport, struct gameport_driver *drv) +{ + down_write(&gameport_bus.subsys.rwsem); + + gameport->dev.driver = &drv->driver; + if (drv->connect(gameport, drv)) { + gameport->dev.driver = NULL; + goto out; + } + device_bind_driver(&gameport->dev); +out: + up_write(&gameport_bus.subsys.rwsem); +} + +static void gameport_release_driver(struct gameport *gameport) +{ + down_write(&gameport_bus.subsys.rwsem); + device_release_driver(&gameport->dev); + up_write(&gameport_bus.subsys.rwsem); +} + +static void gameport_find_driver(struct gameport *gameport) +{ + down_write(&gameport_bus.subsys.rwsem); + device_attach(&gameport->dev); + up_write(&gameport_bus.subsys.rwsem); +} + + +/* + * Gameport event processing. + */ + +enum gameport_event_type { + GAMEPORT_RESCAN, + GAMEPORT_RECONNECT, + GAMEPORT_REGISTER_PORT, + GAMEPORT_REGISTER_DRIVER, +}; + +struct gameport_event { + enum gameport_event_type type; + void *object; + struct module *owner; + struct list_head node; +}; + +static DEFINE_SPINLOCK(gameport_event_lock); /* protects gameport_event_list */ +static LIST_HEAD(gameport_event_list); +static DECLARE_WAIT_QUEUE_HEAD(gameport_wait); +static DECLARE_COMPLETION(gameport_exited); +static int gameport_pid; + +static void gameport_queue_event(void *object, struct module *owner, + enum gameport_event_type event_type) +{ + unsigned long flags; + struct gameport_event *event; + + spin_lock_irqsave(&gameport_event_lock, flags); + + /* + * Scan event list for the other events for the same gameport port, + * starting with the most recent one. If event is the same we + * do not need add new one. If event is of different type we + * need to add this event and should not look further because + * we need to preseve sequence of distinct events. + */ + list_for_each_entry_reverse(event, &gameport_event_list, node) { + if (event->object == object) { + if (event->type == event_type) + goto out; + break; + } + } + + if ((event = kmalloc(sizeof(struct gameport_event), GFP_ATOMIC))) { + if (!try_module_get(owner)) { + printk(KERN_WARNING "gameport: Can't get module reference, dropping event %d\n", event_type); + goto out; + } + + event->type = event_type; + event->object = object; + event->owner = owner; + + list_add_tail(&event->node, &gameport_event_list); + wake_up(&gameport_wait); + } else { + printk(KERN_ERR "gameport: Not enough memory to queue event %d\n", event_type); + } +out: + spin_unlock_irqrestore(&gameport_event_lock, flags); +} + +static void gameport_free_event(struct gameport_event *event) +{ + module_put(event->owner); + kfree(event); +} + +static void gameport_remove_duplicate_events(struct gameport_event *event) +{ + struct list_head *node, *next; + struct gameport_event *e; + unsigned long flags; + + spin_lock_irqsave(&gameport_event_lock, flags); + + list_for_each_safe(node, next, &gameport_event_list) { + e = list_entry(node, struct gameport_event, node); + if (event->object == e->object) { + /* + * If this event is of different type we should not + * look further - we only suppress duplicate events + * that were sent back-to-back. + */ + if (event->type != e->type) + break; + + list_del_init(node); + gameport_free_event(e); + } + } + + spin_unlock_irqrestore(&gameport_event_lock, flags); +} + + +static struct gameport_event *gameport_get_event(void) +{ + struct gameport_event *event; + struct list_head *node; + unsigned long flags; + + spin_lock_irqsave(&gameport_event_lock, flags); + + if (list_empty(&gameport_event_list)) { + spin_unlock_irqrestore(&gameport_event_lock, flags); + return NULL; + } + + node = gameport_event_list.next; + event = list_entry(node, struct gameport_event, node); + list_del_init(node); + + spin_unlock_irqrestore(&gameport_event_lock, flags); + + return event; +} + +static void gameport_handle_events(void) +{ + struct gameport_event *event; + struct gameport_driver *gameport_drv; + + down(&gameport_sem); + + while ((event = gameport_get_event())) { + + switch (event->type) { + case GAMEPORT_REGISTER_PORT: + gameport_add_port(event->object); + break; + + case GAMEPORT_RECONNECT: + gameport_reconnect_port(event->object); + break; + + case GAMEPORT_RESCAN: + gameport_disconnect_port(event->object); + gameport_find_driver(event->object); + break; + + case GAMEPORT_REGISTER_DRIVER: + gameport_drv = event->object; + driver_register(&gameport_drv->driver); + break; + + default: + break; + } + + gameport_remove_duplicate_events(event); + gameport_free_event(event); + } + + up(&gameport_sem); +} + +/* + * Remove all events that have been submitted for a given gameport port. + */ +static void gameport_remove_pending_events(struct gameport *gameport) +{ + struct list_head *node, *next; + struct gameport_event *event; + unsigned long flags; + + spin_lock_irqsave(&gameport_event_lock, flags); + + list_for_each_safe(node, next, &gameport_event_list) { + event = list_entry(node, struct gameport_event, node); + if (event->object == gameport) { + list_del_init(node); + gameport_free_event(event); + } + } + + spin_unlock_irqrestore(&gameport_event_lock, flags); +} + +/* + * Destroy child gameport port (if any) that has not been fully registered yet. + * + * Note that we rely on the fact that port can have only one child and therefore + * only one child registration request can be pending. Additionally, children + * are registered by driver's connect() handler so there can't be a grandchild + * pending registration together with a child. + */ +static struct gameport *gameport_get_pending_child(struct gameport *parent) +{ + struct gameport_event *event; + struct gameport *gameport, *child = NULL; + unsigned long flags; + + spin_lock_irqsave(&gameport_event_lock, flags); + + list_for_each_entry(event, &gameport_event_list, node) { + if (event->type == GAMEPORT_REGISTER_PORT) { + gameport = event->object; + if (gameport->parent == parent) { + child = gameport; + break; + } + } + } + + spin_unlock_irqrestore(&gameport_event_lock, flags); + return child; +} + +static int gameport_thread(void *nothing) +{ + lock_kernel(); + daemonize("kgameportd"); + allow_signal(SIGTERM); + + do { + gameport_handle_events(); + wait_event_interruptible(gameport_wait, !list_empty(&gameport_event_list)); + try_to_freeze(PF_FREEZE); + } while (!signal_pending(current)); + + printk(KERN_DEBUG "gameport: kgameportd exiting\n"); + + unlock_kernel(); + complete_and_exit(&gameport_exited, 0); +} + + +/* + * Gameport port operations + */ + +static ssize_t gameport_show_description(struct device *dev, char *buf) +{ + struct gameport *gameport = to_gameport_port(dev); + return sprintf(buf, "%s\n", gameport->name); +} + +static ssize_t gameport_rebind_driver(struct device *dev, const char *buf, size_t count) +{ + struct gameport *gameport = to_gameport_port(dev); + struct device_driver *drv; + int retval; + + retval = down_interruptible(&gameport_sem); + if (retval) + return retval; + + retval = count; + if (!strncmp(buf, "none", count)) { + gameport_disconnect_port(gameport); + } else if (!strncmp(buf, "reconnect", count)) { + gameport_reconnect_port(gameport); + } else if (!strncmp(buf, "rescan", count)) { + gameport_disconnect_port(gameport); + gameport_find_driver(gameport); + } else if ((drv = driver_find(buf, &gameport_bus)) != NULL) { + gameport_disconnect_port(gameport); + gameport_bind_driver(gameport, to_gameport_driver(drv)); + put_driver(drv); + } else { + retval = -EINVAL; + } + + up(&gameport_sem); + + return retval; +} + +static struct device_attribute gameport_device_attrs[] = { + __ATTR(description, S_IRUGO, gameport_show_description, NULL), + __ATTR(drvctl, S_IWUSR, NULL, gameport_rebind_driver), + __ATTR_NULL +}; + +static void gameport_release_port(struct device *dev) +{ + struct gameport *gameport = to_gameport_port(dev); + + kfree(gameport); + module_put(THIS_MODULE); +} + +void gameport_set_phys(struct gameport *gameport, const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + vsnprintf(gameport->phys, sizeof(gameport->phys), fmt, args); + va_end(args); +} + +/* + * Prepare gameport port for registration. + */ +static void gameport_init_port(struct gameport *gameport) +{ + static atomic_t gameport_no = ATOMIC_INIT(0); + + __module_get(THIS_MODULE); + + init_MUTEX(&gameport->drv_sem); + device_initialize(&gameport->dev); + snprintf(gameport->dev.bus_id, sizeof(gameport->dev.bus_id), + "gameport%lu", (unsigned long)atomic_inc_return(&gameport_no) - 1); + gameport->dev.bus = &gameport_bus; + gameport->dev.release = gameport_release_port; + if (gameport->parent) + gameport->dev.parent = &gameport->parent->dev; + + spin_lock_init(&gameport->timer_lock); + init_timer(&gameport->poll_timer); + gameport->poll_timer.function = gameport_run_poll_handler; + gameport->poll_timer.data = (unsigned long)gameport; +} + +/* + * Complete gameport port registration. + * Driver core will attempt to find appropriate driver for the port. + */ +static void gameport_add_port(struct gameport *gameport) +{ + if (gameport->parent) + gameport->parent->child = gameport; + + gameport->speed = gameport_measure_speed(gameport); + + list_add_tail(&gameport->node, &gameport_list); + + if (gameport->io) + printk(KERN_INFO "gameport: %s is %s, io %#x, speed %dkHz\n", + gameport->name, gameport->phys, gameport->io, gameport->speed); + else + printk(KERN_INFO "gameport: %s is %s, speed %dkHz\n", + gameport->name, gameport->phys, gameport->speed); + + device_add(&gameport->dev); + gameport->registered = 1; +} + +/* + * gameport_destroy_port() completes deregistration process and removes + * port from the system + */ +static void gameport_destroy_port(struct gameport *gameport) +{ + struct gameport *child; + + child = gameport_get_pending_child(gameport); + if (child) { + gameport_remove_pending_events(child); + put_device(&child->dev); + } + + if (gameport->parent) { + gameport->parent->child = NULL; + gameport->parent = NULL; + } + + if (gameport->registered) { + device_del(&gameport->dev); + list_del_init(&gameport->node); + gameport->registered = 0; + } + + gameport_remove_pending_events(gameport); + put_device(&gameport->dev); +} + +/* + * Reconnect gameport port and all its children (re-initialize attached devices) + */ +static void gameport_reconnect_port(struct gameport *gameport) +{ + do { + if (!gameport->drv || !gameport->drv->reconnect || gameport->drv->reconnect(gameport)) { + gameport_disconnect_port(gameport); + gameport_find_driver(gameport); + /* Ok, old children are now gone, we are done */ + break; + } + gameport = gameport->child; + } while (gameport); +} + +/* + * gameport_disconnect_port() unbinds a port from its driver. As a side effect + * all child ports are unbound and destroyed. + */ +static void gameport_disconnect_port(struct gameport *gameport) +{ + struct gameport *s, *parent; + + if (gameport->child) { + /* + * Children ports should be disconnected and destroyed + * first, staring with the leaf one, since we don't want + * to do recursion + */ + for (s = gameport; s->child; s = s->child) + /* empty */; + + do { + parent = s->parent; + + gameport_release_driver(s); + gameport_destroy_port(s); + } while ((s = parent) != gameport); + } + + /* + * Ok, no children left, now disconnect this port + */ + gameport_release_driver(gameport); +} + +void gameport_rescan(struct gameport *gameport) +{ + gameport_queue_event(gameport, NULL, GAMEPORT_RESCAN); +} + +void gameport_reconnect(struct gameport *gameport) +{ + gameport_queue_event(gameport, NULL, GAMEPORT_RECONNECT); +} + +/* + * Submits register request to kgameportd for subsequent execution. + * Note that port registration is always asynchronous. + */ +void __gameport_register_port(struct gameport *gameport, struct module *owner) +{ + gameport_init_port(gameport); + gameport_queue_event(gameport, owner, GAMEPORT_REGISTER_PORT); +} + +/* + * Synchronously unregisters gameport port. + */ +void gameport_unregister_port(struct gameport *gameport) +{ + down(&gameport_sem); + gameport_disconnect_port(gameport); + gameport_destroy_port(gameport); + up(&gameport_sem); +} + + +/* + * Gameport driver operations + */ + +static ssize_t gameport_driver_show_description(struct device_driver *drv, char *buf) +{ + struct gameport_driver *driver = to_gameport_driver(drv); + return sprintf(buf, "%s\n", driver->description ? driver->description : "(none)"); +} + +static struct driver_attribute gameport_driver_attrs[] = { + __ATTR(description, S_IRUGO, gameport_driver_show_description, NULL), + __ATTR_NULL +}; + +static int gameport_driver_probe(struct device *dev) +{ + struct gameport *gameport = to_gameport_port(dev); + struct gameport_driver *drv = to_gameport_driver(dev->driver); + + drv->connect(gameport, drv); + return gameport->drv ? 0 : -ENODEV; +} + +static int gameport_driver_remove(struct device *dev) +{ + struct gameport *gameport = to_gameport_port(dev); + struct gameport_driver *drv = to_gameport_driver(dev->driver); + + drv->disconnect(gameport); + return 0; +} + +void __gameport_register_driver(struct gameport_driver *drv, struct module *owner) +{ + drv->driver.bus = &gameport_bus; + drv->driver.probe = gameport_driver_probe; + drv->driver.remove = gameport_driver_remove; + gameport_queue_event(drv, owner, GAMEPORT_REGISTER_DRIVER); +} + +void gameport_unregister_driver(struct gameport_driver *drv) +{ + struct gameport *gameport; + + down(&gameport_sem); + drv->ignore = 1; /* so gameport_find_driver ignores it */ + +start_over: + list_for_each_entry(gameport, &gameport_list, node) { + if (gameport->drv == drv) { + gameport_disconnect_port(gameport); + gameport_find_driver(gameport); + /* we could've deleted some ports, restart */ + goto start_over; + } + } + + driver_unregister(&drv->driver); + up(&gameport_sem); +} + +static int gameport_bus_match(struct device *dev, struct device_driver *drv) +{ + struct gameport_driver *gameport_drv = to_gameport_driver(drv); + + return !gameport_drv->ignore; +} + +static void gameport_set_drv(struct gameport *gameport, struct gameport_driver *drv) +{ + down(&gameport->drv_sem); + gameport->drv = drv; + up(&gameport->drv_sem); +} + +int gameport_open(struct gameport *gameport, struct gameport_driver *drv, int mode) +{ + + if (gameport->open) { + if (gameport->open(gameport, mode)) { + return -1; + } + } else { + if (mode != GAMEPORT_MODE_RAW) + return -1; + } + + gameport_set_drv(gameport, drv); + return 0; +} + +void gameport_close(struct gameport *gameport) +{ + del_timer_sync(&gameport->poll_timer); + gameport->poll_handler = NULL; + gameport->poll_interval = 0; + gameport_set_drv(gameport, NULL); + if (gameport->close) + gameport->close(gameport); +} + +static int __init gameport_init(void) +{ + if (!(gameport_pid = kernel_thread(gameport_thread, NULL, CLONE_KERNEL))) { + printk(KERN_ERR "gameport: Failed to start kgameportd\n"); + return -1; + } + + gameport_bus.dev_attrs = gameport_device_attrs; + gameport_bus.drv_attrs = gameport_driver_attrs; + gameport_bus.match = gameport_bus_match; + bus_register(&gameport_bus); + + return 0; +} + +static void __exit gameport_exit(void) +{ + bus_unregister(&gameport_bus); + kill_proc(gameport_pid, SIGTERM, 1); + wait_for_completion(&gameport_exited); +} + +module_init(gameport_init); +module_exit(gameport_exit); diff --git a/drivers/input/gameport/lightning.c b/drivers/input/gameport/lightning.c new file mode 100644 index 000000000000..d65d81080257 --- /dev/null +++ b/drivers/input/gameport/lightning.c @@ -0,0 +1,344 @@ +/* + * $Id: lightning.c,v 1.20 2002/01/22 20:41:31 vojtech Exp $ + * + * Copyright (c) 1998-2001 Vojtech Pavlik + */ + +/* + * PDPI Lightning 4 gamecard driver for Linux. + */ + +/* + * 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 either by + * e-mail - mail your message to <vojtech@ucw.cz>, or by paper mail: + * Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic + */ + +#include <asm/io.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/ioport.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/gameport.h> +#include <linux/slab.h> + +#define L4_PORT 0x201 +#define L4_SELECT_ANALOG 0xa4 +#define L4_SELECT_DIGITAL 0xa5 +#define L4_SELECT_SECONDARY 0xa6 +#define L4_CMD_ID 0x80 +#define L4_CMD_GETCAL 0x92 +#define L4_CMD_SETCAL 0x93 +#define L4_ID 0x04 +#define L4_BUSY 0x01 +#define L4_TIMEOUT 80 /* 80 us */ + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION("PDPI Lightning 4 gamecard driver"); +MODULE_LICENSE("GPL"); + +struct l4 { + struct gameport *gameport; + unsigned char port; +}; + +static struct l4 l4_ports[8]; + +/* + * l4_wait_ready() waits for the L4 to become ready. + */ + +static int l4_wait_ready(void) +{ + unsigned int t = L4_TIMEOUT; + + while ((inb(L4_PORT) & L4_BUSY) && t > 0) t--; + return -(t <= 0); +} + +/* + * l4_cooked_read() reads data from the Lightning 4. + */ + +static int l4_cooked_read(struct gameport *gameport, int *axes, int *buttons) +{ + struct l4 *l4 = gameport->port_data; + unsigned char status; + int i, result = -1; + + outb(L4_SELECT_ANALOG, L4_PORT); + outb(L4_SELECT_DIGITAL + (l4->port >> 2), L4_PORT); + + if (inb(L4_PORT) & L4_BUSY) goto fail; + outb(l4->port & 3, L4_PORT); + + if (l4_wait_ready()) goto fail; + status = inb(L4_PORT); + + for (i = 0; i < 4; i++) + if (status & (1 << i)) { + if (l4_wait_ready()) goto fail; + axes[i] = inb(L4_PORT); + if (axes[i] > 252) axes[i] = -1; + } + + if (status & 0x10) { + if (l4_wait_ready()) goto fail; + *buttons = inb(L4_PORT) & 0x0f; + } + + result = 0; + +fail: outb(L4_SELECT_ANALOG, L4_PORT); + return result; +} + +static int l4_open(struct gameport *gameport, int mode) +{ + struct l4 *l4 = gameport->port_data; + + if (l4->port != 0 && mode != GAMEPORT_MODE_COOKED) + return -1; + outb(L4_SELECT_ANALOG, L4_PORT); + return 0; +} + +/* + * l4_getcal() reads the L4 with calibration values. + */ + +static int l4_getcal(int port, int *cal) +{ + int i, result = -1; + + outb(L4_SELECT_ANALOG, L4_PORT); + outb(L4_SELECT_DIGITAL + (port >> 2), L4_PORT); + if (inb(L4_PORT) & L4_BUSY) + goto out; + + outb(L4_CMD_GETCAL, L4_PORT); + if (l4_wait_ready()) + goto out; + + if (inb(L4_PORT) != L4_SELECT_DIGITAL + (port >> 2)) + goto out; + + if (l4_wait_ready()) + goto out; + outb(port & 3, L4_PORT); + + for (i = 0; i < 4; i++) { + if (l4_wait_ready()) + goto out; + cal[i] = inb(L4_PORT); + } + + result = 0; + +out: outb(L4_SELECT_ANALOG, L4_PORT); + return result; +} + +/* + * l4_setcal() programs the L4 with calibration values. + */ + +static int l4_setcal(int port, int *cal) +{ + int i, result = -1; + + outb(L4_SELECT_ANALOG, L4_PORT); + outb(L4_SELECT_DIGITAL + (port >> 2), L4_PORT); + if (inb(L4_PORT) & L4_BUSY) + goto out; + + outb(L4_CMD_SETCAL, L4_PORT); + if (l4_wait_ready()) + goto out; + + if (inb(L4_PORT) != L4_SELECT_DIGITAL + (port >> 2)) + goto out; + + if (l4_wait_ready()) + goto out; + outb(port & 3, L4_PORT); + + for (i = 0; i < 4; i++) { + if (l4_wait_ready()) + goto out; + outb(cal[i], L4_PORT); + } + + result = 0; + +out: outb(L4_SELECT_ANALOG, L4_PORT); + return result; +} + +/* + * l4_calibrate() calibrates the L4 for the attached device, so + * that the device's resistance fits into the L4's 8-bit range. + */ + +static int l4_calibrate(struct gameport *gameport, int *axes, int *max) +{ + int i, t; + int cal[4]; + struct l4 *l4 = gameport->port_data; + + if (l4_getcal(l4->port, cal)) + return -1; + + for (i = 0; i < 4; i++) { + t = (max[i] * cal[i]) / 200; + t = (t < 1) ? 1 : ((t > 255) ? 255 : t); + axes[i] = (axes[i] < 0) ? -1 : (axes[i] * cal[i]) / t; + axes[i] = (axes[i] > 252) ? 252 : axes[i]; + cal[i] = t; + } + + if (l4_setcal(l4->port, cal)) + return -1; + + return 0; +} + +static int __init l4_create_ports(int card_no) +{ + struct l4 *l4; + struct gameport *port; + int i, idx; + + for (i = 0; i < 4; i++) { + + idx = card_no * 4 + i; + l4 = &l4_ports[idx]; + + if (!(l4->gameport = port = gameport_allocate_port())) { + printk(KERN_ERR "lightning: Memory allocation failed\n"); + while (--i >= 0) { + gameport_free_port(l4->gameport); + l4->gameport = NULL; + } + return -ENOMEM; + } + l4->port = idx; + + port->port_data = l4; + port->open = l4_open; + port->cooked_read = l4_cooked_read; + port->calibrate = l4_calibrate; + + gameport_set_name(port, "PDPI Lightning 4"); + gameport_set_phys(port, "isa%04x/gameport%d", L4_PORT, idx); + + if (idx == 0) + port->io = L4_PORT; + } + + return 0; +} + +static int __init l4_add_card(int card_no) +{ + int cal[4] = { 255, 255, 255, 255 }; + int i, rev, result; + struct l4 *l4; + + outb(L4_SELECT_ANALOG, L4_PORT); + outb(L4_SELECT_DIGITAL + card_no, L4_PORT); + + if (inb(L4_PORT) & L4_BUSY) + return -1; + outb(L4_CMD_ID, L4_PORT); + + if (l4_wait_ready()) + return -1; + + if (inb(L4_PORT) != L4_SELECT_DIGITAL + card_no) + return -1; + + if (l4_wait_ready()) + return -1; + if (inb(L4_PORT) != L4_ID) + return -1; + + if (l4_wait_ready()) + return -1; + rev = inb(L4_PORT); + + if (!rev) + return -1; + + result = l4_create_ports(card_no); + if (result) + return result; + + printk(KERN_INFO "gameport: PDPI Lightning 4 %s card v%d.%d at %#x\n", + card_no ? "secondary" : "primary", rev >> 4, rev, L4_PORT); + + for (i = 0; i < 4; i++) { + l4 = &l4_ports[card_no * 4 + i]; + + if (rev > 0x28) /* on 2.9+ the setcal command works correctly */ + l4_setcal(l4->port, cal); + gameport_register_port(l4->gameport); + } + + return 0; +} + +static int __init l4_init(void) +{ + int i, cards = 0; + + if (!request_region(L4_PORT, 1, "lightning")) + return -1; + + for (i = 0; i < 2; i++) + if (l4_add_card(i) == 0) + cards++; + + outb(L4_SELECT_ANALOG, L4_PORT); + + if (!cards) { + release_region(L4_PORT, 1); + return -1; + } + + return 0; +} + +static void __exit l4_exit(void) +{ + int i; + int cal[4] = { 59, 59, 59, 59 }; + + for (i = 0; i < 8; i++) + if (l4_ports[i].gameport) { + l4_setcal(l4_ports[i].port, cal); + gameport_unregister_port(l4_ports[i].gameport); + } + + outb(L4_SELECT_ANALOG, L4_PORT); + release_region(L4_PORT, 1); +} + +module_init(l4_init); +module_exit(l4_exit); diff --git a/drivers/input/gameport/ns558.c b/drivers/input/gameport/ns558.c new file mode 100644 index 000000000000..7c5c6318eeb9 --- /dev/null +++ b/drivers/input/gameport/ns558.c @@ -0,0 +1,291 @@ +/* + * $Id: ns558.c,v 1.43 2002/01/24 19:23:21 vojtech Exp $ + * + * Copyright (c) 1999-2001 Vojtech Pavlik + * Copyright (c) 1999 Brian Gerst + */ + +/* + * NS558 based standard IBM game port driver for Linux + */ + +/* + * 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 either by + * e-mail - mail your message to <vojtech@ucw.cz>, or by paper mail: + * Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic + */ + +#include <asm/io.h> + +#include <linux/module.h> +#include <linux/ioport.h> +#include <linux/config.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/gameport.h> +#include <linux/slab.h> +#include <linux/pnp.h> + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION("Classic gameport (ISA/PnP) driver"); +MODULE_LICENSE("GPL"); + +static int ns558_isa_portlist[] = { 0x201, 0x200, 0x202, 0x203, 0x204, 0x205, 0x207, 0x209, + 0x20b, 0x20c, 0x20e, 0x20f, 0x211, 0x219, 0x101, 0 }; + +struct ns558 { + int type; + int io; + int size; + struct pnp_dev *dev; + struct gameport *gameport; + struct list_head node; +}; + +static LIST_HEAD(ns558_list); + +/* + * ns558_isa_probe() tries to find an isa gameport at the + * specified address, and also checks for mirrors. + * A joystick must be attached for this to work. + */ + +static int ns558_isa_probe(int io) +{ + int i, j, b; + unsigned char c, u, v; + struct ns558 *ns558; + struct gameport *port; + +/* + * No one should be using this address. + */ + + if (!request_region(io, 1, "ns558-isa")) + return -EBUSY; + +/* + * We must not be able to write arbitrary values to the port. + * The lower two axis bits must be 1 after a write. + */ + + c = inb(io); + outb(~c & ~3, io); + if (~(u = v = inb(io)) & 3) { + outb(c, io); + release_region(io, 1); + return -ENODEV; + } +/* + * After a trigger, there must be at least some bits changing. + */ + + for (i = 0; i < 1000; i++) v &= inb(io); + + if (u == v) { + outb(c, io); + release_region(io, 1); + return -ENODEV; + } + msleep(3); +/* + * After some time (4ms) the axes shouldn't change anymore. + */ + + u = inb(io); + for (i = 0; i < 1000; i++) + if ((u ^ inb(io)) & 0xf) { + outb(c, io); + release_region(io, 1); + return -ENODEV; + } +/* + * And now find the number of mirrors of the port. + */ + + for (i = 1; i < 5; i++) { + + release_region(io & (-1 << (i - 1)), (1 << (i - 1))); + + if (!request_region(io & (-1 << i), (1 << i), "ns558-isa")) + break; /* Don't disturb anyone */ + + outb(0xff, io & (-1 << i)); + for (j = b = 0; j < 1000; j++) + if (inb(io & (-1 << i)) != inb((io & (-1 << i)) + (1 << i) - 1)) b++; + msleep(3); + + if (b > 300) { /* We allow 30% difference */ + release_region(io & (-1 << i), (1 << i)); + break; + } + } + + i--; + + if (i != 4) { + if (!request_region(io & (-1 << i), (1 << i), "ns558-isa")) + return -EBUSY; + } + + ns558 = kcalloc(1, sizeof(struct ns558), GFP_KERNEL); + port = gameport_allocate_port(); + if (!ns558 || !port) { + printk(KERN_ERR "ns558: Memory allocation failed.\n"); + release_region(io & (-1 << i), (1 << i)); + kfree(ns558); + gameport_free_port(port); + return -ENOMEM; + } + + memset(ns558, 0, sizeof(struct ns558)); + ns558->io = io; + ns558->size = 1 << i; + ns558->gameport = port; + + port->io = io; + gameport_set_name(port, "NS558 ISA Gameport"); + gameport_set_phys(port, "isa%04x/gameport0", io & (-1 << i)); + + gameport_register_port(port); + + list_add(&ns558->node, &ns558_list); + + return 0; +} + +#ifdef CONFIG_PNP + +static struct pnp_device_id pnp_devids[] = { + { .id = "@P@0001", .driver_data = 0 }, /* ALS 100 */ + { .id = "@P@0020", .driver_data = 0 }, /* ALS 200 */ + { .id = "@P@1001", .driver_data = 0 }, /* ALS 100+ */ + { .id = "@P@2001", .driver_data = 0 }, /* ALS 120 */ + { .id = "ASB16fd", .driver_data = 0 }, /* AdLib NSC16 */ + { .id = "AZT3001", .driver_data = 0 }, /* AZT1008 */ + { .id = "CDC0001", .driver_data = 0 }, /* Opl3-SAx */ + { .id = "CSC0001", .driver_data = 0 }, /* CS4232 */ + { .id = "CSC000f", .driver_data = 0 }, /* CS4236 */ + { .id = "CSC0101", .driver_data = 0 }, /* CS4327 */ + { .id = "CTL7001", .driver_data = 0 }, /* SB16 */ + { .id = "CTL7002", .driver_data = 0 }, /* AWE64 */ + { .id = "CTL7005", .driver_data = 0 }, /* Vibra16 */ + { .id = "ENS2020", .driver_data = 0 }, /* SoundscapeVIVO */ + { .id = "ESS0001", .driver_data = 0 }, /* ES1869 */ + { .id = "ESS0005", .driver_data = 0 }, /* ES1878 */ + { .id = "ESS6880", .driver_data = 0 }, /* ES688 */ + { .id = "IBM0012", .driver_data = 0 }, /* CS4232 */ + { .id = "OPT0001", .driver_data = 0 }, /* OPTi Audio16 */ + { .id = "YMH0006", .driver_data = 0 }, /* Opl3-SA */ + { .id = "YMH0022", .driver_data = 0 }, /* Opl3-SAx */ + { .id = "PNPb02f", .driver_data = 0 }, /* Generic */ + { .id = "", }, +}; + +MODULE_DEVICE_TABLE(pnp, pnp_devids); + +static int ns558_pnp_probe(struct pnp_dev *dev, const struct pnp_device_id *did) +{ + int ioport, iolen; + struct ns558 *ns558; + struct gameport *port; + + if (!pnp_port_valid(dev, 0)) { + printk(KERN_WARNING "ns558: No i/o ports on a gameport? Weird\n"); + return -ENODEV; + } + + ioport = pnp_port_start(dev, 0); + iolen = pnp_port_len(dev, 0); + + if (!request_region(ioport, iolen, "ns558-pnp")) + return -EBUSY; + + ns558 = kcalloc(1, sizeof(struct ns558), GFP_KERNEL); + port = gameport_allocate_port(); + if (!ns558 || !port) { + printk(KERN_ERR "ns558: Memory allocation failed\n"); + kfree(ns558); + gameport_free_port(port); + return -ENOMEM; + } + + ns558->io = ioport; + ns558->size = iolen; + ns558->dev = dev; + ns558->gameport = port; + + gameport_set_name(port, "NS558 PnP Gameport"); + gameport_set_phys(port, "pnp%s/gameport0", dev->dev.bus_id); + port->dev.parent = &dev->dev; + port->io = ioport; + + gameport_register_port(port); + + list_add_tail(&ns558->node, &ns558_list); + return 0; +} + +static struct pnp_driver ns558_pnp_driver = { + .name = "ns558", + .id_table = pnp_devids, + .probe = ns558_pnp_probe, +}; + +#else + +static struct pnp_driver ns558_pnp_driver; + +#endif + +static int pnp_registered = 0; + +static int __init ns558_init(void) +{ + int i = 0; + +/* + * Probe ISA ports first so that PnP gets to choose free port addresses + * not occupied by the ISA ports. + */ + + while (ns558_isa_portlist[i]) + ns558_isa_probe(ns558_isa_portlist[i++]); + + if (pnp_register_driver(&ns558_pnp_driver) >= 0) + pnp_registered = 1; + + + return (list_empty(&ns558_list) && !pnp_registered) ? -ENODEV : 0; +} + +static void __exit ns558_exit(void) +{ + struct ns558 *ns558; + + list_for_each_entry(ns558, &ns558_list, node) { + gameport_unregister_port(ns558->gameport); + release_region(ns558->io & ~(ns558->size - 1), ns558->size); + kfree(ns558); + } + + if (pnp_registered) + pnp_unregister_driver(&ns558_pnp_driver); +} + +module_init(ns558_init); +module_exit(ns558_exit); diff --git a/drivers/input/gameport/vortex.c b/drivers/input/gameport/vortex.c new file mode 100644 index 000000000000..36b0309c8bf6 --- /dev/null +++ b/drivers/input/gameport/vortex.c @@ -0,0 +1,186 @@ +/* + * $Id: vortex.c,v 1.5 2002/07/01 15:39:30 vojtech Exp $ + * + * Copyright (c) 2000-2001 Vojtech Pavlik + * + * Based on the work of: + * Raymond Ingles + */ + +/* + * Trident 4DWave and Aureal Vortex gameport driver for Linux + */ + +/* + * 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 either by + * e-mail - mail your message to <vojtech@ucw.cz>, or by paper mail: + * Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic + */ + +#include <asm/io.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/ioport.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/gameport.h> + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION("Aureal Vortex and Vortex2 gameport driver"); +MODULE_LICENSE("GPL"); + +#define VORTEX_GCR 0x0c /* Gameport control register */ +#define VORTEX_LEG 0x08 /* Legacy port location */ +#define VORTEX_AXD 0x10 /* Axes start */ +#define VORTEX_DATA_WAIT 20 /* 20 ms */ + +struct vortex { + struct gameport *gameport; + struct pci_dev *dev; + unsigned char __iomem *base; + unsigned char __iomem *io; +}; + +static unsigned char vortex_read(struct gameport *gameport) +{ + struct vortex *vortex = gameport->port_data; + return readb(vortex->io + VORTEX_LEG); +} + +static void vortex_trigger(struct gameport *gameport) +{ + struct vortex *vortex = gameport->port_data; + writeb(0xff, vortex->io + VORTEX_LEG); +} + +static int vortex_cooked_read(struct gameport *gameport, int *axes, int *buttons) +{ + struct vortex *vortex = gameport->port_data; + int i; + + *buttons = (~readb(vortex->base + VORTEX_LEG) >> 4) & 0xf; + + for (i = 0; i < 4; i++) { + axes[i] = readw(vortex->io + VORTEX_AXD + i * sizeof(u32)); + if (axes[i] == 0x1fff) axes[i] = -1; + } + + return 0; +} + +static int vortex_open(struct gameport *gameport, int mode) +{ + struct vortex *vortex = gameport->port_data; + + switch (mode) { + case GAMEPORT_MODE_COOKED: + writeb(0x40, vortex->io + VORTEX_GCR); + msleep(VORTEX_DATA_WAIT); + return 0; + case GAMEPORT_MODE_RAW: + writeb(0x00, vortex->io + VORTEX_GCR); + return 0; + default: + return -1; + } + + return 0; +} + +static int __devinit vortex_probe(struct pci_dev *dev, const struct pci_device_id *id) +{ + struct vortex *vortex; + struct gameport *port; + int i; + + vortex = kcalloc(1, sizeof(struct vortex), GFP_KERNEL); + port = gameport_allocate_port(); + if (!vortex || !port) { + printk(KERN_ERR "vortex: Memory allocation failed.\n"); + kfree(vortex); + gameport_free_port(port); + return -ENOMEM; + } + + for (i = 0; i < 6; i++) + if (~pci_resource_flags(dev, i) & IORESOURCE_IO) + break; + + pci_enable_device(dev); + + vortex->dev = dev; + vortex->gameport = port; + vortex->base = ioremap(pci_resource_start(vortex->dev, i), + pci_resource_len(vortex->dev, i)); + vortex->io = vortex->base + id->driver_data; + + pci_set_drvdata(dev, vortex); + + port->port_data = vortex; + port->fuzz = 64; + + gameport_set_name(port, "AU88x0"); + gameport_set_phys(port, "pci%s/gameport0", pci_name(dev)); + port->dev.parent = &dev->dev; + port->read = vortex_read; + port->trigger = vortex_trigger; + port->cooked_read = vortex_cooked_read; + port->open = vortex_open; + + gameport_register_port(port); + + return 0; +} + +static void __devexit vortex_remove(struct pci_dev *dev) +{ + struct vortex *vortex = pci_get_drvdata(dev); + + gameport_unregister_port(vortex->gameport); + iounmap(vortex->base); + kfree(vortex); +} + +static struct pci_device_id vortex_id_table[] = { + { 0x12eb, 0x0001, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0x11000 }, + { 0x12eb, 0x0002, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0x28800 }, + { 0 } +}; + +static struct pci_driver vortex_driver = { + .name = "vortex_gameport", + .id_table = vortex_id_table, + .probe = vortex_probe, + .remove = __devexit_p(vortex_remove), +}; + +static int __init vortex_init(void) +{ + return pci_register_driver(&vortex_driver); +} + +static void __exit vortex_exit(void) +{ + pci_unregister_driver(&vortex_driver); +} + +module_init(vortex_init); +module_exit(vortex_exit); diff --git a/drivers/input/input.c b/drivers/input/input.c new file mode 100644 index 000000000000..3385dd03abfc --- /dev/null +++ b/drivers/input/input.c @@ -0,0 +1,748 @@ +/* + * The input core + * + * Copyright (c) 1999-2002 Vojtech Pavlik + */ + +/* + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +#include <linux/init.h> +#include <linux/sched.h> +#include <linux/smp_lock.h> +#include <linux/input.h> +#include <linux/module.h> +#include <linux/random.h> +#include <linux/major.h> +#include <linux/proc_fs.h> +#include <linux/kobject_uevent.h> +#include <linux/interrupt.h> +#include <linux/poll.h> +#include <linux/device.h> +#include <linux/devfs_fs_kernel.h> + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@suse.cz>"); +MODULE_DESCRIPTION("Input core"); +MODULE_LICENSE("GPL"); + +EXPORT_SYMBOL(input_register_device); +EXPORT_SYMBOL(input_unregister_device); +EXPORT_SYMBOL(input_register_handler); +EXPORT_SYMBOL(input_unregister_handler); +EXPORT_SYMBOL(input_grab_device); +EXPORT_SYMBOL(input_release_device); +EXPORT_SYMBOL(input_open_device); +EXPORT_SYMBOL(input_close_device); +EXPORT_SYMBOL(input_accept_process); +EXPORT_SYMBOL(input_flush_device); +EXPORT_SYMBOL(input_event); +EXPORT_SYMBOL(input_class); + +#define INPUT_DEVICES 256 + +static LIST_HEAD(input_dev_list); +static LIST_HEAD(input_handler_list); + +static struct input_handler *input_table[8]; + +#ifdef CONFIG_PROC_FS +static struct proc_dir_entry *proc_bus_input_dir; +static DECLARE_WAIT_QUEUE_HEAD(input_devices_poll_wait); +static int input_devices_state; +#endif + +void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) +{ + struct input_handle *handle; + + if (type > EV_MAX || !test_bit(type, dev->evbit)) + return; + + add_input_randomness(type, code, value); + + switch (type) { + + case EV_SYN: + switch (code) { + case SYN_CONFIG: + if (dev->event) dev->event(dev, type, code, value); + break; + + case SYN_REPORT: + if (dev->sync) return; + dev->sync = 1; + break; + } + break; + + case EV_KEY: + + if (code > KEY_MAX || !test_bit(code, dev->keybit) || !!test_bit(code, dev->key) == value) + return; + + if (value == 2) + break; + + change_bit(code, dev->key); + + if (test_bit(EV_REP, dev->evbit) && dev->rep[REP_PERIOD] && dev->rep[REP_DELAY] && dev->timer.data && value) { + dev->repeat_key = code; + mod_timer(&dev->timer, jiffies + msecs_to_jiffies(dev->rep[REP_DELAY])); + } + + break; + + case EV_ABS: + + if (code > ABS_MAX || !test_bit(code, dev->absbit)) + return; + + if (dev->absfuzz[code]) { + if ((value > dev->abs[code] - (dev->absfuzz[code] >> 1)) && + (value < dev->abs[code] + (dev->absfuzz[code] >> 1))) + return; + + if ((value > dev->abs[code] - dev->absfuzz[code]) && + (value < dev->abs[code] + dev->absfuzz[code])) + value = (dev->abs[code] * 3 + value) >> 2; + + if ((value > dev->abs[code] - (dev->absfuzz[code] << 1)) && + (value < dev->abs[code] + (dev->absfuzz[code] << 1))) + value = (dev->abs[code] + value) >> 1; + } + + if (dev->abs[code] == value) + return; + + dev->abs[code] = value; + break; + + case EV_REL: + + if (code > REL_MAX || !test_bit(code, dev->relbit) || (value == 0)) + return; + + break; + + case EV_MSC: + + if (code > MSC_MAX || !test_bit(code, dev->mscbit)) + return; + + if (dev->event) dev->event(dev, type, code, value); + + break; + + case EV_LED: + + if (code > LED_MAX || !test_bit(code, dev->ledbit) || !!test_bit(code, dev->led) == value) + return; + + change_bit(code, dev->led); + if (dev->event) dev->event(dev, type, code, value); + + break; + + case EV_SND: + + if (code > SND_MAX || !test_bit(code, dev->sndbit)) + return; + + if (dev->event) dev->event(dev, type, code, value); + + break; + + case EV_REP: + + if (code > REP_MAX || value < 0 || dev->rep[code] == value) return; + + dev->rep[code] = value; + if (dev->event) dev->event(dev, type, code, value); + + break; + + case EV_FF: + if (dev->event) dev->event(dev, type, code, value); + break; + } + + if (type != EV_SYN) + dev->sync = 0; + + if (dev->grab) + dev->grab->handler->event(dev->grab, type, code, value); + else + list_for_each_entry(handle, &dev->h_list, d_node) + if (handle->open) + handle->handler->event(handle, type, code, value); +} + +static void input_repeat_key(unsigned long data) +{ + struct input_dev *dev = (void *) data; + + if (!test_bit(dev->repeat_key, dev->key)) + return; + + input_event(dev, EV_KEY, dev->repeat_key, 2); + input_sync(dev); + + if (dev->rep[REP_PERIOD]) + mod_timer(&dev->timer, jiffies + msecs_to_jiffies(dev->rep[REP_PERIOD])); +} + +int input_accept_process(struct input_handle *handle, struct file *file) +{ + if (handle->dev->accept) + return handle->dev->accept(handle->dev, file); + + return 0; +} + +int input_grab_device(struct input_handle *handle) +{ + if (handle->dev->grab) + return -EBUSY; + + handle->dev->grab = handle; + return 0; +} + +void input_release_device(struct input_handle *handle) +{ + if (handle->dev->grab == handle) + handle->dev->grab = NULL; +} + +int input_open_device(struct input_handle *handle) +{ + handle->open++; + if (handle->dev->open) + return handle->dev->open(handle->dev); + return 0; +} + +int input_flush_device(struct input_handle* handle, struct file* file) +{ + if (handle->dev->flush) + return handle->dev->flush(handle->dev, file); + + return 0; +} + +void input_close_device(struct input_handle *handle) +{ + input_release_device(handle); + if (handle->dev->close) + handle->dev->close(handle->dev); + handle->open--; +} + +static void input_link_handle(struct input_handle *handle) +{ + list_add_tail(&handle->d_node, &handle->dev->h_list); + list_add_tail(&handle->h_node, &handle->handler->h_list); +} + +#define MATCH_BIT(bit, max) \ + for (i = 0; i < NBITS(max); i++) \ + if ((id->bit[i] & dev->bit[i]) != id->bit[i]) \ + break; \ + if (i != NBITS(max)) \ + continue; + +static struct input_device_id *input_match_device(struct input_device_id *id, struct input_dev *dev) +{ + int i; + + for (; id->flags || id->driver_info; id++) { + + if (id->flags & INPUT_DEVICE_ID_MATCH_BUS) + if (id->id.bustype != dev->id.bustype) + continue; + + if (id->flags & INPUT_DEVICE_ID_MATCH_VENDOR) + if (id->id.vendor != dev->id.vendor) + continue; + + if (id->flags & INPUT_DEVICE_ID_MATCH_PRODUCT) + if (id->id.product != dev->id.product) + continue; + + if (id->flags & INPUT_DEVICE_ID_MATCH_VERSION) + if (id->id.version != dev->id.version) + continue; + + MATCH_BIT(evbit, EV_MAX); + MATCH_BIT(keybit, KEY_MAX); + MATCH_BIT(relbit, REL_MAX); + MATCH_BIT(absbit, ABS_MAX); + MATCH_BIT(mscbit, MSC_MAX); + MATCH_BIT(ledbit, LED_MAX); + MATCH_BIT(sndbit, SND_MAX); + MATCH_BIT(ffbit, FF_MAX); + + return id; + } + + return NULL; +} + +/* + * Input hotplugging interface - loading event handlers based on + * device bitfields. + */ + +#ifdef CONFIG_HOTPLUG + +/* + * Input hotplugging invokes what /proc/sys/kernel/hotplug says + * (normally /sbin/hotplug) when input devices get added or removed. + * + * This invokes a user mode policy agent, typically helping to load driver + * or other modules, configure the device, and more. Drivers can provide + * a MODULE_DEVICE_TABLE to help with module loading subtasks. + * + */ + +#define SPRINTF_BIT_A(bit, name, max) \ + do { \ + envp[i++] = scratch; \ + scratch += sprintf(scratch, name); \ + for (j = NBITS(max) - 1; j >= 0; j--) \ + if (dev->bit[j]) break; \ + for (; j >= 0; j--) \ + scratch += sprintf(scratch, "%lx ", dev->bit[j]); \ + scratch++; \ + } while (0) + +#define SPRINTF_BIT_A2(bit, name, max, ev) \ + do { \ + if (test_bit(ev, dev->evbit)) \ + SPRINTF_BIT_A(bit, name, max); \ + } while (0) + +static void input_call_hotplug(char *verb, struct input_dev *dev) +{ + char *argv[3], **envp, *buf, *scratch; + int i = 0, j, value; + + if (!hotplug_path[0]) { + printk(KERN_ERR "input.c: calling hotplug without a hotplug agent defined\n"); + return; + } + if (in_interrupt()) { + printk(KERN_ERR "input.c: calling hotplug from interrupt\n"); + return; + } + if (!current->fs->root) { + printk(KERN_WARNING "input.c: calling hotplug without valid filesystem\n"); + return; + } + if (!(envp = (char **) kmalloc(20 * sizeof(char *), GFP_KERNEL))) { + printk(KERN_ERR "input.c: not enough memory allocating hotplug environment\n"); + return; + } + if (!(buf = kmalloc(1024, GFP_KERNEL))) { + kfree (envp); + printk(KERN_ERR "input.c: not enough memory allocating hotplug environment\n"); + return; + } + + argv[0] = hotplug_path; + argv[1] = "input"; + argv[2] = NULL; + + envp[i++] = "HOME=/"; + envp[i++] = "PATH=/sbin:/bin:/usr/sbin:/usr/bin"; + + scratch = buf; + + envp[i++] = scratch; + scratch += sprintf(scratch, "ACTION=%s", verb) + 1; + + envp[i++] = scratch; + scratch += sprintf(scratch, "PRODUCT=%x/%x/%x/%x", + dev->id.bustype, dev->id.vendor, dev->id.product, dev->id.version) + 1; + + if (dev->name) { + envp[i++] = scratch; + scratch += sprintf(scratch, "NAME=%s", dev->name) + 1; + } + + if (dev->phys) { + envp[i++] = scratch; + scratch += sprintf(scratch, "PHYS=%s", dev->phys) + 1; + } + + SPRINTF_BIT_A(evbit, "EV=", EV_MAX); + SPRINTF_BIT_A2(keybit, "KEY=", KEY_MAX, EV_KEY); + SPRINTF_BIT_A2(relbit, "REL=", REL_MAX, EV_REL); + SPRINTF_BIT_A2(absbit, "ABS=", ABS_MAX, EV_ABS); + SPRINTF_BIT_A2(mscbit, "MSC=", MSC_MAX, EV_MSC); + SPRINTF_BIT_A2(ledbit, "LED=", LED_MAX, EV_LED); + SPRINTF_BIT_A2(sndbit, "SND=", SND_MAX, EV_SND); + SPRINTF_BIT_A2(ffbit, "FF=", FF_MAX, EV_FF); + + envp[i++] = NULL; + +#ifdef INPUT_DEBUG + printk(KERN_DEBUG "input.c: calling %s %s [%s %s %s %s %s]\n", + argv[0], argv[1], envp[0], envp[1], envp[2], envp[3], envp[4]); +#endif + + value = call_usermodehelper(argv [0], argv, envp, 0); + + kfree(buf); + kfree(envp); + +#ifdef INPUT_DEBUG + if (value != 0) + printk(KERN_DEBUG "input.c: hotplug returned %d\n", value); +#endif +} + +#endif + +void input_register_device(struct input_dev *dev) +{ + struct input_handle *handle; + struct input_handler *handler; + struct input_device_id *id; + + set_bit(EV_SYN, dev->evbit); + + /* + * If delay and period are pre-set by the driver, then autorepeating + * is handled by the driver itself and we don't do it in input.c. + */ + + init_timer(&dev->timer); + if (!dev->rep[REP_DELAY] && !dev->rep[REP_PERIOD]) { + dev->timer.data = (long) dev; + dev->timer.function = input_repeat_key; + dev->rep[REP_DELAY] = 250; + dev->rep[REP_PERIOD] = 33; + } + + INIT_LIST_HEAD(&dev->h_list); + list_add_tail(&dev->node, &input_dev_list); + + list_for_each_entry(handler, &input_handler_list, node) + if (!handler->blacklist || !input_match_device(handler->blacklist, dev)) + if ((id = input_match_device(handler->id_table, dev))) + if ((handle = handler->connect(handler, dev, id))) + input_link_handle(handle); + +#ifdef CONFIG_HOTPLUG + input_call_hotplug("add", dev); +#endif + +#ifdef CONFIG_PROC_FS + input_devices_state++; + wake_up(&input_devices_poll_wait); +#endif +} + +void input_unregister_device(struct input_dev *dev) +{ + struct list_head * node, * next; + + if (!dev) return; + + del_timer_sync(&dev->timer); + + list_for_each_safe(node, next, &dev->h_list) { + struct input_handle * handle = to_handle(node); + list_del_init(&handle->d_node); + list_del_init(&handle->h_node); + handle->handler->disconnect(handle); + } + +#ifdef CONFIG_HOTPLUG + input_call_hotplug("remove", dev); +#endif + + list_del_init(&dev->node); + +#ifdef CONFIG_PROC_FS + input_devices_state++; + wake_up(&input_devices_poll_wait); +#endif +} + +void input_register_handler(struct input_handler *handler) +{ + struct input_dev *dev; + struct input_handle *handle; + struct input_device_id *id; + + if (!handler) return; + + INIT_LIST_HEAD(&handler->h_list); + + if (handler->fops != NULL) + input_table[handler->minor >> 5] = handler; + + list_add_tail(&handler->node, &input_handler_list); + + list_for_each_entry(dev, &input_dev_list, node) + if (!handler->blacklist || !input_match_device(handler->blacklist, dev)) + if ((id = input_match_device(handler->id_table, dev))) + if ((handle = handler->connect(handler, dev, id))) + input_link_handle(handle); + +#ifdef CONFIG_PROC_FS + input_devices_state++; + wake_up(&input_devices_poll_wait); +#endif +} + +void input_unregister_handler(struct input_handler *handler) +{ + struct list_head * node, * next; + + list_for_each_safe(node, next, &handler->h_list) { + struct input_handle * handle = to_handle_h(node); + list_del_init(&handle->h_node); + list_del_init(&handle->d_node); + handler->disconnect(handle); + } + + list_del_init(&handler->node); + + if (handler->fops != NULL) + input_table[handler->minor >> 5] = NULL; + +#ifdef CONFIG_PROC_FS + input_devices_state++; + wake_up(&input_devices_poll_wait); +#endif +} + +static int input_open_file(struct inode *inode, struct file *file) +{ + struct input_handler *handler = input_table[iminor(inode) >> 5]; + struct file_operations *old_fops, *new_fops = NULL; + int err; + + /* No load-on-demand here? */ + if (!handler || !(new_fops = fops_get(handler->fops))) + return -ENODEV; + + /* + * That's _really_ odd. Usually NULL ->open means "nothing special", + * not "no device". Oh, well... + */ + if (!new_fops->open) { + fops_put(new_fops); + return -ENODEV; + } + old_fops = file->f_op; + file->f_op = new_fops; + + err = new_fops->open(inode, file); + + if (err) { + fops_put(file->f_op); + file->f_op = fops_get(old_fops); + } + fops_put(old_fops); + return err; +} + +static struct file_operations input_fops = { + .owner = THIS_MODULE, + .open = input_open_file, +}; + +#ifdef CONFIG_PROC_FS + +#define SPRINTF_BIT_B(bit, name, max) \ + do { \ + len += sprintf(buf + len, "B: %s", name); \ + for (i = NBITS(max) - 1; i >= 0; i--) \ + if (dev->bit[i]) break; \ + for (; i >= 0; i--) \ + len += sprintf(buf + len, "%lx ", dev->bit[i]); \ + len += sprintf(buf + len, "\n"); \ + } while (0) + +#define SPRINTF_BIT_B2(bit, name, max, ev) \ + do { \ + if (test_bit(ev, dev->evbit)) \ + SPRINTF_BIT_B(bit, name, max); \ + } while (0) + + +static unsigned int input_devices_poll(struct file *file, poll_table *wait) +{ + int state = input_devices_state; + poll_wait(file, &input_devices_poll_wait, wait); + if (state != input_devices_state) + return POLLIN | POLLRDNORM; + return 0; +} + +static int input_devices_read(char *buf, char **start, off_t pos, int count, int *eof, void *data) +{ + struct input_dev *dev; + struct input_handle *handle; + + off_t at = 0; + int i, len, cnt = 0; + + list_for_each_entry(dev, &input_dev_list, node) { + + len = sprintf(buf, "I: Bus=%04x Vendor=%04x Product=%04x Version=%04x\n", + dev->id.bustype, dev->id.vendor, dev->id.product, dev->id.version); + + len += sprintf(buf + len, "N: Name=\"%s\"\n", dev->name ? dev->name : ""); + len += sprintf(buf + len, "P: Phys=%s\n", dev->phys ? dev->phys : ""); + len += sprintf(buf + len, "H: Handlers="); + + list_for_each_entry(handle, &dev->h_list, d_node) + len += sprintf(buf + len, "%s ", handle->name); + + len += sprintf(buf + len, "\n"); + + SPRINTF_BIT_B(evbit, "EV=", EV_MAX); + SPRINTF_BIT_B2(keybit, "KEY=", KEY_MAX, EV_KEY); + SPRINTF_BIT_B2(relbit, "REL=", REL_MAX, EV_REL); + SPRINTF_BIT_B2(absbit, "ABS=", ABS_MAX, EV_ABS); + SPRINTF_BIT_B2(mscbit, "MSC=", MSC_MAX, EV_MSC); + SPRINTF_BIT_B2(ledbit, "LED=", LED_MAX, EV_LED); + SPRINTF_BIT_B2(sndbit, "SND=", SND_MAX, EV_SND); + SPRINTF_BIT_B2(ffbit, "FF=", FF_MAX, EV_FF); + + len += sprintf(buf + len, "\n"); + + at += len; + + if (at >= pos) { + if (!*start) { + *start = buf + (pos - (at - len)); + cnt = at - pos; + } else cnt += len; + buf += len; + if (cnt >= count) + break; + } + } + + if (&dev->node == &input_dev_list) + *eof = 1; + + return (count > cnt) ? cnt : count; +} + +static int input_handlers_read(char *buf, char **start, off_t pos, int count, int *eof, void *data) +{ + struct input_handler *handler; + + off_t at = 0; + int len = 0, cnt = 0; + int i = 0; + + list_for_each_entry(handler, &input_handler_list, node) { + + if (handler->fops) + len = sprintf(buf, "N: Number=%d Name=%s Minor=%d\n", + i++, handler->name, handler->minor); + else + len = sprintf(buf, "N: Number=%d Name=%s\n", + i++, handler->name); + + at += len; + + if (at >= pos) { + if (!*start) { + *start = buf + (pos - (at - len)); + cnt = at - pos; + } else cnt += len; + buf += len; + if (cnt >= count) + break; + } + } + if (&handler->node == &input_handler_list) + *eof = 1; + + return (count > cnt) ? cnt : count; +} + +static int __init input_proc_init(void) +{ + struct proc_dir_entry *entry; + + proc_bus_input_dir = proc_mkdir("input", proc_bus); + if (proc_bus_input_dir == NULL) + return -ENOMEM; + proc_bus_input_dir->owner = THIS_MODULE; + entry = create_proc_read_entry("devices", 0, proc_bus_input_dir, input_devices_read, NULL); + if (entry == NULL) { + remove_proc_entry("input", proc_bus); + return -ENOMEM; + } + entry->owner = THIS_MODULE; + entry->proc_fops->poll = input_devices_poll; + entry = create_proc_read_entry("handlers", 0, proc_bus_input_dir, input_handlers_read, NULL); + if (entry == NULL) { + remove_proc_entry("devices", proc_bus_input_dir); + remove_proc_entry("input", proc_bus); + return -ENOMEM; + } + entry->owner = THIS_MODULE; + return 0; +} +#else /* !CONFIG_PROC_FS */ +static inline int input_proc_init(void) { return 0; } +#endif + +struct class_simple *input_class; + +static int __init input_init(void) +{ + int retval = -ENOMEM; + + input_class = class_simple_create(THIS_MODULE, "input"); + if (IS_ERR(input_class)) + return PTR_ERR(input_class); + input_proc_init(); + retval = register_chrdev(INPUT_MAJOR, "input", &input_fops); + if (retval) { + printk(KERN_ERR "input: unable to register char major %d", INPUT_MAJOR); + remove_proc_entry("devices", proc_bus_input_dir); + remove_proc_entry("handlers", proc_bus_input_dir); + remove_proc_entry("input", proc_bus); + class_simple_destroy(input_class); + return retval; + } + + retval = devfs_mk_dir("input"); + if (retval) { + remove_proc_entry("devices", proc_bus_input_dir); + remove_proc_entry("handlers", proc_bus_input_dir); + remove_proc_entry("input", proc_bus); + unregister_chrdev(INPUT_MAJOR, "input"); + class_simple_destroy(input_class); + } + return retval; +} + +static void __exit input_exit(void) +{ + remove_proc_entry("devices", proc_bus_input_dir); + remove_proc_entry("handlers", proc_bus_input_dir); + remove_proc_entry("input", proc_bus); + + devfs_remove("input"); + unregister_chrdev(INPUT_MAJOR, "input"); + class_simple_destroy(input_class); +} + +subsys_initcall(input_init); +module_exit(input_exit); diff --git a/drivers/input/joydev.c b/drivers/input/joydev.c new file mode 100644 index 000000000000..7d7527f8b02d --- /dev/null +++ b/drivers/input/joydev.c @@ -0,0 +1,533 @@ +/* + * Joystick device driver for the input driver suite. + * + * Copyright (c) 1999-2002 Vojtech Pavlik + * Copyright (c) 1999 Colin Van Dyke + * + * 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. + */ + +#include <asm/io.h> +#include <asm/system.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/joystick.h> +#include <linux/input.h> +#include <linux/kernel.h> +#include <linux/major.h> +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/miscdevice.h> +#include <linux/module.h> +#include <linux/poll.h> +#include <linux/init.h> +#include <linux/smp_lock.h> +#include <linux/device.h> +#include <linux/devfs_fs_kernel.h> + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION("Joystick device interfaces"); +MODULE_SUPPORTED_DEVICE("input/js"); +MODULE_LICENSE("GPL"); + +#define JOYDEV_MINOR_BASE 0 +#define JOYDEV_MINORS 16 +#define JOYDEV_BUFFER_SIZE 64 + +#define MSECS(t) (1000 * ((t) / HZ) + 1000 * ((t) % HZ) / HZ) + +struct joydev { + int exist; + int open; + int minor; + char name[16]; + struct input_handle handle; + wait_queue_head_t wait; + struct list_head list; + struct js_corr corr[ABS_MAX + 1]; + struct JS_DATA_SAVE_TYPE glue; + int nabs; + int nkey; + __u16 keymap[KEY_MAX - BTN_MISC + 1]; + __u16 keypam[KEY_MAX - BTN_MISC + 1]; + __u8 absmap[ABS_MAX + 1]; + __u8 abspam[ABS_MAX + 1]; + __s16 abs[ABS_MAX + 1]; +}; + +struct joydev_list { + struct js_event buffer[JOYDEV_BUFFER_SIZE]; + int head; + int tail; + int startup; + struct fasync_struct *fasync; + struct joydev *joydev; + struct list_head node; +}; + +static struct joydev *joydev_table[JOYDEV_MINORS]; + +static int joydev_correct(int value, struct js_corr *corr) +{ + switch (corr->type) { + case JS_CORR_NONE: + break; + case JS_CORR_BROKEN: + value = value > corr->coef[0] ? (value < corr->coef[1] ? 0 : + ((corr->coef[3] * (value - corr->coef[1])) >> 14)) : + ((corr->coef[2] * (value - corr->coef[0])) >> 14); + break; + default: + return 0; + } + + if (value < -32767) return -32767; + if (value > 32767) return 32767; + + return value; +} + +static void joydev_event(struct input_handle *handle, unsigned int type, unsigned int code, int value) +{ + struct joydev *joydev = handle->private; + struct joydev_list *list; + struct js_event event; + + switch (type) { + + case EV_KEY: + if (code < BTN_MISC || value == 2) return; + event.type = JS_EVENT_BUTTON; + event.number = joydev->keymap[code - BTN_MISC]; + event.value = value; + break; + + case EV_ABS: + event.type = JS_EVENT_AXIS; + event.number = joydev->absmap[code]; + event.value = joydev_correct(value, joydev->corr + event.number); + if (event.value == joydev->abs[event.number]) return; + joydev->abs[event.number] = event.value; + break; + + default: + return; + } + + event.time = MSECS(jiffies); + + list_for_each_entry(list, &joydev->list, node) { + + memcpy(list->buffer + list->head, &event, sizeof(struct js_event)); + + if (list->startup == joydev->nabs + joydev->nkey) + if (list->tail == (list->head = (list->head + 1) & (JOYDEV_BUFFER_SIZE - 1))) + list->startup = 0; + + kill_fasync(&list->fasync, SIGIO, POLL_IN); + } + + wake_up_interruptible(&joydev->wait); +} + +static int joydev_fasync(int fd, struct file *file, int on) +{ + int retval; + struct joydev_list *list = file->private_data; + retval = fasync_helper(fd, file, on, &list->fasync); + return retval < 0 ? retval : 0; +} + +static void joydev_free(struct joydev *joydev) +{ + joydev_table[joydev->minor] = NULL; + kfree(joydev); +} + +static int joydev_release(struct inode * inode, struct file * file) +{ + struct joydev_list *list = file->private_data; + + joydev_fasync(-1, file, 0); + + list_del(&list->node); + + if (!--list->joydev->open) { + if (list->joydev->exist) + input_close_device(&list->joydev->handle); + else + joydev_free(list->joydev); + } + + kfree(list); + return 0; +} + +static int joydev_open(struct inode *inode, struct file *file) +{ + struct joydev_list *list; + int i = iminor(inode) - JOYDEV_MINOR_BASE; + + if (i >= JOYDEV_MINORS || !joydev_table[i]) + return -ENODEV; + + if (!(list = kmalloc(sizeof(struct joydev_list), GFP_KERNEL))) + return -ENOMEM; + memset(list, 0, sizeof(struct joydev_list)); + + list->joydev = joydev_table[i]; + list_add_tail(&list->node, &joydev_table[i]->list); + file->private_data = list; + + if (!list->joydev->open++) + if (list->joydev->exist) + input_open_device(&list->joydev->handle); + + return 0; +} + +static ssize_t joydev_write(struct file * file, const char __user * buffer, size_t count, loff_t *ppos) +{ + return -EINVAL; +} + +static ssize_t joydev_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) +{ + struct joydev_list *list = file->private_data; + struct joydev *joydev = list->joydev; + struct input_dev *input = joydev->handle.dev; + int retval = 0; + + if (!list->joydev->exist) + return -ENODEV; + + if (count < sizeof(struct js_event)) + return -EINVAL; + + if (count == sizeof(struct JS_DATA_TYPE)) { + + struct JS_DATA_TYPE data; + int i; + + for (data.buttons = i = 0; i < 32 && i < joydev->nkey; i++) + data.buttons |= test_bit(joydev->keypam[i], input->key) ? (1 << i) : 0; + data.x = (joydev->abs[0] / 256 + 128) >> joydev->glue.JS_CORR.x; + data.y = (joydev->abs[1] / 256 + 128) >> joydev->glue.JS_CORR.y; + + if (copy_to_user(buf, &data, sizeof(struct JS_DATA_TYPE))) + return -EFAULT; + + list->startup = 0; + list->tail = list->head; + + return sizeof(struct JS_DATA_TYPE); + } + + if (list->startup == joydev->nabs + joydev->nkey + && list->head == list->tail && (file->f_flags & O_NONBLOCK)) + return -EAGAIN; + + retval = wait_event_interruptible(list->joydev->wait, + !list->joydev->exist || + list->startup < joydev->nabs + joydev->nkey || + list->head != list->tail); + + if (retval) + return retval; + + if (!list->joydev->exist) + return -ENODEV; + + while (list->startup < joydev->nabs + joydev->nkey && retval + sizeof(struct js_event) <= count) { + + struct js_event event; + + event.time = MSECS(jiffies); + + if (list->startup < joydev->nkey) { + event.type = JS_EVENT_BUTTON | JS_EVENT_INIT; + event.number = list->startup; + event.value = !!test_bit(joydev->keypam[event.number], input->key); + } else { + event.type = JS_EVENT_AXIS | JS_EVENT_INIT; + event.number = list->startup - joydev->nkey; + event.value = joydev->abs[event.number]; + } + + if (copy_to_user(buf + retval, &event, sizeof(struct js_event))) + return -EFAULT; + + list->startup++; + retval += sizeof(struct js_event); + } + + while (list->head != list->tail && retval + sizeof(struct js_event) <= count) { + + if (copy_to_user(buf + retval, list->buffer + list->tail, sizeof(struct js_event))) + return -EFAULT; + + list->tail = (list->tail + 1) & (JOYDEV_BUFFER_SIZE - 1); + retval += sizeof(struct js_event); + } + + return retval; +} + +/* No kernel lock - fine */ +static unsigned int joydev_poll(struct file *file, poll_table *wait) +{ + struct joydev_list *list = file->private_data; + poll_wait(file, &list->joydev->wait, wait); + return ((list->head != list->tail || list->startup < list->joydev->nabs + list->joydev->nkey) ? + (POLLIN | POLLRDNORM) : 0) | (list->joydev->exist ? 0 : (POLLHUP | POLLERR)); +} + +static int joydev_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) +{ + struct joydev_list *list = file->private_data; + struct joydev *joydev = list->joydev; + struct input_dev *dev = joydev->handle.dev; + void __user *argp = (void __user *)arg; + int i, j; + + if (!joydev->exist) return -ENODEV; + + switch (cmd) { + + case JS_SET_CAL: + return copy_from_user(&joydev->glue.JS_CORR, argp, + sizeof(struct JS_DATA_TYPE)) ? -EFAULT : 0; + case JS_GET_CAL: + return copy_to_user(argp, &joydev->glue.JS_CORR, + sizeof(struct JS_DATA_TYPE)) ? -EFAULT : 0; + case JS_SET_TIMEOUT: + return get_user(joydev->glue.JS_TIMEOUT, (int __user *) arg); + case JS_GET_TIMEOUT: + return put_user(joydev->glue.JS_TIMEOUT, (int __user *) arg); + case JS_SET_TIMELIMIT: + return get_user(joydev->glue.JS_TIMELIMIT, (long __user *) arg); + case JS_GET_TIMELIMIT: + return put_user(joydev->glue.JS_TIMELIMIT, (long __user *) arg); + case JS_SET_ALL: + return copy_from_user(&joydev->glue, argp, + sizeof(struct JS_DATA_SAVE_TYPE)) ? -EFAULT : 0; + case JS_GET_ALL: + return copy_to_user(argp, &joydev->glue, + sizeof(struct JS_DATA_SAVE_TYPE)) ? -EFAULT : 0; + + case JSIOCGVERSION: + return put_user(JS_VERSION, (__u32 __user *) arg); + case JSIOCGAXES: + return put_user(joydev->nabs, (__u8 __user *) arg); + case JSIOCGBUTTONS: + return put_user(joydev->nkey, (__u8 __user *) arg); + case JSIOCSCORR: + if (copy_from_user(joydev->corr, argp, + sizeof(struct js_corr) * joydev->nabs)) + return -EFAULT; + for (i = 0; i < joydev->nabs; i++) { + j = joydev->abspam[i]; + joydev->abs[i] = joydev_correct(dev->abs[j], joydev->corr + i); + } + return 0; + case JSIOCGCORR: + return copy_to_user(argp, joydev->corr, + sizeof(struct js_corr) * joydev->nabs) ? -EFAULT : 0; + case JSIOCSAXMAP: + if (copy_from_user(joydev->abspam, argp, sizeof(__u8) * (ABS_MAX + 1))) + return -EFAULT; + for (i = 0; i < joydev->nabs; i++) { + if (joydev->abspam[i] > ABS_MAX) return -EINVAL; + joydev->absmap[joydev->abspam[i]] = i; + } + return 0; + case JSIOCGAXMAP: + return copy_to_user(argp, joydev->abspam, + sizeof(__u8) * (ABS_MAX + 1)) ? -EFAULT : 0; + case JSIOCSBTNMAP: + if (copy_from_user(joydev->keypam, argp, sizeof(__u16) * (KEY_MAX - BTN_MISC + 1))) + return -EFAULT; + for (i = 0; i < joydev->nkey; i++) { + if (joydev->keypam[i] > KEY_MAX || joydev->keypam[i] < BTN_MISC) return -EINVAL; + joydev->keymap[joydev->keypam[i] - BTN_MISC] = i; + } + return 0; + case JSIOCGBTNMAP: + return copy_to_user(argp, joydev->keypam, + sizeof(__u16) * (KEY_MAX - BTN_MISC + 1)) ? -EFAULT : 0; + default: + if ((cmd & ~(_IOC_SIZEMASK << _IOC_SIZESHIFT)) == JSIOCGNAME(0)) { + int len; + if (!dev->name) return 0; + len = strlen(dev->name) + 1; + if (len > _IOC_SIZE(cmd)) len = _IOC_SIZE(cmd); + if (copy_to_user(argp, dev->name, len)) return -EFAULT; + return len; + } + } + return -EINVAL; +} + +static struct file_operations joydev_fops = { + .owner = THIS_MODULE, + .read = joydev_read, + .write = joydev_write, + .poll = joydev_poll, + .open = joydev_open, + .release = joydev_release, + .ioctl = joydev_ioctl, + .fasync = joydev_fasync, +}; + +static struct input_handle *joydev_connect(struct input_handler *handler, struct input_dev *dev, struct input_device_id *id) +{ + struct joydev *joydev; + int i, j, t, minor; + + for (minor = 0; minor < JOYDEV_MINORS && joydev_table[minor]; minor++); + if (minor == JOYDEV_MINORS) { + printk(KERN_ERR "joydev: no more free joydev devices\n"); + return NULL; + } + + if (!(joydev = kmalloc(sizeof(struct joydev), GFP_KERNEL))) + return NULL; + memset(joydev, 0, sizeof(struct joydev)); + + INIT_LIST_HEAD(&joydev->list); + init_waitqueue_head(&joydev->wait); + + joydev->minor = minor; + joydev->exist = 1; + joydev->handle.dev = dev; + joydev->handle.name = joydev->name; + joydev->handle.handler = handler; + joydev->handle.private = joydev; + sprintf(joydev->name, "js%d", minor); + + for (i = 0; i < ABS_MAX + 1; i++) + if (test_bit(i, dev->absbit)) { + joydev->absmap[i] = joydev->nabs; + joydev->abspam[joydev->nabs] = i; + joydev->nabs++; + } + + for (i = BTN_JOYSTICK - BTN_MISC; i < KEY_MAX - BTN_MISC + 1; i++) + if (test_bit(i + BTN_MISC, dev->keybit)) { + joydev->keymap[i] = joydev->nkey; + joydev->keypam[joydev->nkey] = i + BTN_MISC; + joydev->nkey++; + } + + for (i = 0; i < BTN_JOYSTICK - BTN_MISC + 1; i++) + if (test_bit(i + BTN_MISC, dev->keybit)) { + joydev->keymap[i] = joydev->nkey; + joydev->keypam[joydev->nkey] = i + BTN_MISC; + joydev->nkey++; + } + + for (i = 0; i < joydev->nabs; i++) { + j = joydev->abspam[i]; + if (dev->absmax[j] == dev->absmin[j]) { + joydev->corr[i].type = JS_CORR_NONE; + joydev->abs[i] = dev->abs[j]; + continue; + } + joydev->corr[i].type = JS_CORR_BROKEN; + joydev->corr[i].prec = dev->absfuzz[j]; + joydev->corr[i].coef[0] = (dev->absmax[j] + dev->absmin[j]) / 2 - dev->absflat[j]; + joydev->corr[i].coef[1] = (dev->absmax[j] + dev->absmin[j]) / 2 + dev->absflat[j]; + if (!(t = ((dev->absmax[j] - dev->absmin[j]) / 2 - 2 * dev->absflat[j]))) + continue; + joydev->corr[i].coef[2] = (1 << 29) / t; + joydev->corr[i].coef[3] = (1 << 29) / t; + + joydev->abs[i] = joydev_correct(dev->abs[j], joydev->corr + i); + } + + joydev_table[minor] = joydev; + + devfs_mk_cdev(MKDEV(INPUT_MAJOR, JOYDEV_MINOR_BASE + minor), + S_IFCHR|S_IRUGO|S_IWUSR, "input/js%d", minor); + class_simple_device_add(input_class, + MKDEV(INPUT_MAJOR, JOYDEV_MINOR_BASE + minor), + dev->dev, "js%d", minor); + + return &joydev->handle; +} + +static void joydev_disconnect(struct input_handle *handle) +{ + struct joydev *joydev = handle->private; + struct joydev_list *list; + + class_simple_device_remove(MKDEV(INPUT_MAJOR, JOYDEV_MINOR_BASE + joydev->minor)); + devfs_remove("input/js%d", joydev->minor); + joydev->exist = 0; + + if (joydev->open) { + input_close_device(handle); + wake_up_interruptible(&joydev->wait); + list_for_each_entry(list, &joydev->list, node) + kill_fasync(&list->fasync, SIGIO, POLL_HUP); + } else + joydev_free(joydev); +} + +static struct input_device_id joydev_blacklist[] = { + { + .flags = INPUT_DEVICE_ID_MATCH_EVBIT | INPUT_DEVICE_ID_MATCH_KEYBIT, + .evbit = { BIT(EV_KEY) }, + .keybit = { [LONG(BTN_TOUCH)] = BIT(BTN_TOUCH) }, + }, /* Avoid itouchpads, touchscreens and tablets */ + { }, /* Terminating entry */ +}; + +static struct input_device_id joydev_ids[] = { + { + .flags = INPUT_DEVICE_ID_MATCH_EVBIT | INPUT_DEVICE_ID_MATCH_ABSBIT, + .evbit = { BIT(EV_ABS) }, + .absbit = { BIT(ABS_X) }, + }, + { + .flags = INPUT_DEVICE_ID_MATCH_EVBIT | INPUT_DEVICE_ID_MATCH_ABSBIT, + .evbit = { BIT(EV_ABS) }, + .absbit = { BIT(ABS_WHEEL) }, + }, + { + .flags = INPUT_DEVICE_ID_MATCH_EVBIT | INPUT_DEVICE_ID_MATCH_ABSBIT, + .evbit = { BIT(EV_ABS) }, + .absbit = { BIT(ABS_THROTTLE) }, + }, + { }, /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(input, joydev_ids); + +static struct input_handler joydev_handler = { + .event = joydev_event, + .connect = joydev_connect, + .disconnect = joydev_disconnect, + .fops = &joydev_fops, + .minor = JOYDEV_MINOR_BASE, + .name = "joydev", + .id_table = joydev_ids, + .blacklist = joydev_blacklist, +}; + +static int __init joydev_init(void) +{ + input_register_handler(&joydev_handler); + return 0; +} + +static void __exit joydev_exit(void) +{ + input_unregister_handler(&joydev_handler); +} + +module_init(joydev_init); +module_exit(joydev_exit); diff --git a/drivers/input/joystick/Kconfig b/drivers/input/joystick/Kconfig new file mode 100644 index 000000000000..67519ef0ef95 --- /dev/null +++ b/drivers/input/joystick/Kconfig @@ -0,0 +1,256 @@ +# +# Joystick driver configuration +# +menuconfig INPUT_JOYSTICK + bool "Joysticks" + help + If you have a joystick, 6dof controller, gamepad, steering wheel, + weapon control system or something like that you can say Y here + and the list of supported devices will be displayed. This option + doesn't affect the kernel. + + Please read the file <file:Documentation/input/joystick.txt> which + contains more information. + +if INPUT_JOYSTICK + +config JOYSTICK_ANALOG + tristate "Classic PC analog joysticks and gamepads" + select GAMEPORT + ---help--- + Say Y here if you have a joystick that connects to the PC + gameport. In addition to the usual PC analog joystick, this driver + supports many extensions, including joysticks with throttle control, + with rudders, additional hats and buttons compatible with CH + Flightstick Pro, ThrustMaster FCS, 6 and 8 button gamepads, or + Saitek Cyborg joysticks. + + Please read the file <file:Documentation/input/joystick.txt> which + contains more information. + + To compile this driver as a module, choose M here: the + module will be called analog. + +config JOYSTICK_A3D + tristate "Assasin 3D and MadCatz Panther devices" + select GAMEPORT + help + Say Y here if you have an FPGaming or MadCatz controller using the + A3D protocol over the PC gameport. + + To compile this driver as a module, choose M here: the + module will be called a3d. + +config JOYSTICK_ADI + tristate "Logitech ADI digital joysticks and gamepads" + select GAMEPORT + help + Say Y here if you have a Logitech controller using the ADI + protocol over the PC gameport. + + To compile this driver as a module, choose M here: the + module will be called adi. + +config JOYSTICK_COBRA + tristate "Creative Labs Blaster Cobra gamepad" + select GAMEPORT + help + Say Y here if you have a Creative Labs Blaster Cobra gamepad. + + To compile this driver as a module, choose M here: the + module will be called cobra. + +config JOYSTICK_GF2K + tristate "Genius Flight2000 Digital joysticks and gamepads" + select GAMEPORT + help + Say Y here if you have a Genius Flight2000 or MaxFighter digitally + communicating joystick or gamepad. + + To compile this driver as a module, choose M here: the + module will be called gf2k. + +config JOYSTICK_GRIP + tristate "Gravis GrIP joysticks and gamepads" + select GAMEPORT + help + Say Y here if you have a Gravis controller using the GrIP protocol + over the PC gameport. + + To compile this driver as a module, choose M here: the + module will be called grip. + +config JOYSTICK_GRIP_MP + tristate "Gravis GrIP MultiPort" + select GAMEPORT + help + Say Y here if you have the original Gravis GrIP MultiPort, a hub + that connects to the gameport and you connect gamepads to it. + + To compile this driver as a module, choose M here: the + module will be called grip_mp. + +config JOYSTICK_GUILLEMOT + tristate "Guillemot joysticks and gamepads" + select GAMEPORT + help + Say Y here if you have a Guillemot joystick using a digital + protocol over the PC gameport. + + To compile this driver as a module, choose M here: the + module will be called guillemot. + +config JOYSTICK_INTERACT + tristate "InterAct digital joysticks and gamepads" + select GAMEPORT + help + Say Y here if you have an InterAct gameport or joystick + communicating digitally over the gameport. + + To compile this driver as a module, choose M here: the + module will be called interact. + +config JOYSTICK_SIDEWINDER + tristate "Microsoft SideWinder digital joysticks and gamepads" + select GAMEPORT + help + Say Y here if you have a Microsoft controller using the Digital + Overdrive protocol over PC gameport. + + To compile this driver as a module, choose M here: the + module will be called sidewinder. + +config JOYSTICK_TMDC + tristate "ThrustMaster DirectConnect joysticks and gamepads" + select GAMEPORT + help + Say Y here if you have a ThrustMaster controller using the + DirectConnect (BSP) protocol over the PC gameport. + + To compile this driver as a module, choose M here: the + module will be called tmdc. + +source "drivers/input/joystick/iforce/Kconfig" + +config JOYSTICK_WARRIOR + tristate "Logitech WingMan Warrior joystick" + select SERIO + help + Say Y here if you have a Logitech WingMan Warrior joystick connected + to your computer's serial port. + + To compile this driver as a module, choose M here: the + module will be called warrior. + +config JOYSTICK_MAGELLAN + tristate "LogiCad3d Magellan/SpaceMouse 6dof controllers" + select SERIO + help + Say Y here if you have a Magellan or Space Mouse 6DOF controller + connected to your computer's serial port. + + To compile this driver as a module, choose M here: the + module will be called magellan. + +config JOYSTICK_SPACEORB + tristate "SpaceTec SpaceOrb/Avenger 6dof controllers" + select SERIO + help + Say Y here if you have a SpaceOrb 360 or SpaceBall Avenger 6DOF + controller connected to your computer's serial port. + + To compile this driver as a module, choose M here: the + module will be called spaceorb. + +config JOYSTICK_SPACEBALL + tristate "SpaceTec SpaceBall 6dof controllers" + select SERIO + help + Say Y here if you have a SpaceTec SpaceBall 2003/3003/4000 FLX + controller connected to your computer's serial port. For the + SpaceBall 4000 USB model, use the USB HID driver. + + To compile this driver as a module, choose M here: the + module will be called spaceball. + +config JOYSTICK_STINGER + tristate "Gravis Stinger gamepad" + select SERIO + help + Say Y here if you have a Gravis Stinger connected to one of your + serial ports. + + To compile this driver as a module, choose M here: the + module will be called stinger. + +config JOYSTICK_TWIDJOY + tristate "Twiddler as a joystick" + select SERIO + help + Say Y here if you have a Handykey Twiddler connected to your + computer's serial port and want to use it as a joystick. + + To compile this driver as a module, choose M here: the + module will be called twidjoy. + +config JOYSTICK_DB9 + tristate "Multisystem, Sega Genesis, Saturn joysticks and gamepads" + depends on PARPORT + ---help--- + Say Y here if you have a Sega Master System gamepad, Sega Genesis + gamepad, Sega Saturn gamepad, or a Multisystem -- Atari, Amiga, + Commodore, Amstrad CPC joystick connected to your parallel port. + For more information on how to use the driver please read + <file:Documentation/input/joystick-parport.txt>. + + To compile this driver as a module, choose M here: the + module will be called db9. + +config JOYSTICK_GAMECON + tristate "Multisystem, NES, SNES, N64, PSX joysticks and gamepads" + depends on PARPORT + ---help--- + Say Y here if you have a Nintendo Entertainment System gamepad, + Super Nintendo Entertainment System gamepad, Nintendo 64 gamepad, + Sony PlayStation gamepad or a Multisystem -- Atari, Amiga, + Commodore, Amstrad CPC joystick connected to your parallel port. + For more information on how to use the driver please read + <file:Documentation/input/joystick-parport.txt>. + + To compile this driver as a module, choose M here: the + module will be called gamecon. + +config JOYSTICK_TURBOGRAFX + tristate "Multisystem joysticks via TurboGraFX device" + depends on PARPORT + help + Say Y here if you have the TurboGraFX interface by Steffen Schwenke, + and want to use it with Multisystem -- Atari, Amiga, Commodore, + Amstrad CPC joystick. For more information on how to use the driver + please read <file:Documentation/input/joystick-parport.txt>. + + To compile this driver as a module, choose M here: the + module will be called turbografx. + +config JOYSTICK_AMIGA + tristate "Amiga joysticks" + depends on AMIGA + help + Say Y here if you have an Amiga with a digital joystick connected + to it. + + To compile this driver as a module, choose M here: the + module will be called amijoy. + +config JOYSTICK_JOYDUMP + tristate "Gameport data dumper" + select GAMEPORT + help + Say Y here if you want to dump data from your joystick into the system + log for debugging purposes. Say N if you are making a production + configuration or aren't sure. + + To compile this driver as a module, choose M here: the + module will be called joydump. + +endif diff --git a/drivers/input/joystick/Makefile b/drivers/input/joystick/Makefile new file mode 100644 index 000000000000..5231f6ff75b8 --- /dev/null +++ b/drivers/input/joystick/Makefile @@ -0,0 +1,30 @@ +# +# Makefile for the input core drivers. +# + +# Each configuration option enables a list of files. + +obj-$(CONFIG_JOYSTICK_A3D) += a3d.o +obj-$(CONFIG_JOYSTICK_ADI) += adi.o +obj-$(CONFIG_JOYSTICK_AMIGA) += amijoy.o +obj-$(CONFIG_JOYSTICK_ANALOG) += analog.o +obj-$(CONFIG_JOYSTICK_COBRA) += cobra.o +obj-$(CONFIG_JOYSTICK_DB9) += db9.o +obj-$(CONFIG_JOYSTICK_GAMECON) += gamecon.o +obj-$(CONFIG_JOYSTICK_GF2K) += gf2k.o +obj-$(CONFIG_JOYSTICK_GRIP) += grip.o +obj-$(CONFIG_JOYSTICK_GRIP_MP) += grip_mp.o +obj-$(CONFIG_JOYSTICK_GUILLEMOT) += guillemot.o +obj-$(CONFIG_JOYSTICK_INTERACT) += interact.o +obj-$(CONFIG_JOYSTICK_JOYDUMP) += joydump.o +obj-$(CONFIG_JOYSTICK_MAGELLAN) += magellan.o +obj-$(CONFIG_JOYSTICK_SIDEWINDER) += sidewinder.o +obj-$(CONFIG_JOYSTICK_SPACEBALL) += spaceball.o +obj-$(CONFIG_JOYSTICK_SPACEORB) += spaceorb.o +obj-$(CONFIG_JOYSTICK_STINGER) += stinger.o +obj-$(CONFIG_JOYSTICK_TMDC) += tmdc.o +obj-$(CONFIG_JOYSTICK_TURBOGRAFX) += turbografx.o +obj-$(CONFIG_JOYSTICK_TWIDJOY) += twidjoy.o +obj-$(CONFIG_JOYSTICK_WARRIOR) += warrior.o + +obj-$(CONFIG_JOYSTICK_IFORCE) += iforce/ diff --git a/drivers/input/joystick/a3d.c b/drivers/input/joystick/a3d.c new file mode 100644 index 000000000000..ad39fe4bf35f --- /dev/null +++ b/drivers/input/joystick/a3d.c @@ -0,0 +1,417 @@ +/* + * $Id: a3d.c,v 1.21 2002/01/22 20:11:50 vojtech Exp $ + * + * Copyright (c) 1998-2001 Vojtech Pavlik + */ + +/* + * FP-Gaming Assasin 3D joystick driver for Linux + */ + +/* + * 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 either by + * e-mail - mail your message to <vojtech@ucw.cz>, or by paper mail: + * Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/gameport.h> +#include <linux/input.h> + +#define DRIVER_DESC "FP-Gaming Assasin 3D joystick driver" + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +#define A3D_MAX_START 600 /* 600 us */ +#define A3D_MAX_STROBE 80 /* 80 us */ +#define A3D_MAX_LENGTH 40 /* 40*3 bits */ + +#define A3D_MODE_A3D 1 /* Assassin 3D */ +#define A3D_MODE_PAN 2 /* Panther */ +#define A3D_MODE_OEM 3 /* Panther OEM version */ +#define A3D_MODE_PXL 4 /* Panther XL */ + +static char *a3d_names[] = { NULL, "FP-Gaming Assassin 3D", "MadCatz Panther", "OEM Panther", + "MadCatz Panther XL", "MadCatz Panther XL w/ rudder" }; + +struct a3d { + struct gameport *gameport; + struct gameport *adc; + struct input_dev dev; + int axes[4]; + int buttons; + int mode; + int length; + int reads; + int bads; + char phys[32]; +}; + +/* + * a3d_read_packet() reads an Assassin 3D packet. + */ + +static int a3d_read_packet(struct gameport *gameport, int length, char *data) +{ + unsigned long flags; + unsigned char u, v; + unsigned int t, s; + int i; + + i = 0; + t = gameport_time(gameport, A3D_MAX_START); + s = gameport_time(gameport, A3D_MAX_STROBE); + + local_irq_save(flags); + gameport_trigger(gameport); + v = gameport_read(gameport); + + while (t > 0 && i < length) { + t--; + u = v; v = gameport_read(gameport); + if (~v & u & 0x10) { + data[i++] = v >> 5; + t = s; + } + } + + local_irq_restore(flags); + + return i; +} + +/* + * a3d_csum() computes checksum of triplet packet + */ + +static int a3d_csum(char *data, int count) +{ + int i, csum = 0; + + for (i = 0; i < count - 2; i++) + csum += data[i]; + return (csum & 0x3f) != ((data[count - 2] << 3) | data[count - 1]); +} + +static void a3d_read(struct a3d *a3d, unsigned char *data) +{ + struct input_dev *dev = &a3d->dev; + + switch (a3d->mode) { + + case A3D_MODE_A3D: + case A3D_MODE_OEM: + case A3D_MODE_PAN: + + input_report_rel(dev, REL_X, ((data[5] << 6) | (data[6] << 3) | data[ 7]) - ((data[5] & 4) << 7)); + input_report_rel(dev, REL_Y, ((data[8] << 6) | (data[9] << 3) | data[10]) - ((data[8] & 4) << 7)); + + input_report_key(dev, BTN_RIGHT, data[2] & 1); + input_report_key(dev, BTN_LEFT, data[3] & 2); + input_report_key(dev, BTN_MIDDLE, data[3] & 4); + + input_sync(dev); + + a3d->axes[0] = ((signed char)((data[11] << 6) | (data[12] << 3) | (data[13]))) + 128; + a3d->axes[1] = ((signed char)((data[14] << 6) | (data[15] << 3) | (data[16]))) + 128; + a3d->axes[2] = ((signed char)((data[17] << 6) | (data[18] << 3) | (data[19]))) + 128; + a3d->axes[3] = ((signed char)((data[20] << 6) | (data[21] << 3) | (data[22]))) + 128; + + a3d->buttons = ((data[3] << 3) | data[4]) & 0xf; + + break; + + case A3D_MODE_PXL: + + input_report_rel(dev, REL_X, ((data[ 9] << 6) | (data[10] << 3) | data[11]) - ((data[ 9] & 4) << 7)); + input_report_rel(dev, REL_Y, ((data[12] << 6) | (data[13] << 3) | data[14]) - ((data[12] & 4) << 7)); + + input_report_key(dev, BTN_RIGHT, data[2] & 1); + input_report_key(dev, BTN_LEFT, data[3] & 2); + input_report_key(dev, BTN_MIDDLE, data[3] & 4); + input_report_key(dev, BTN_SIDE, data[7] & 2); + input_report_key(dev, BTN_EXTRA, data[7] & 4); + + input_report_abs(dev, ABS_X, ((signed char)((data[15] << 6) | (data[16] << 3) | (data[17]))) + 128); + input_report_abs(dev, ABS_Y, ((signed char)((data[18] << 6) | (data[19] << 3) | (data[20]))) + 128); + input_report_abs(dev, ABS_RUDDER, ((signed char)((data[21] << 6) | (data[22] << 3) | (data[23]))) + 128); + input_report_abs(dev, ABS_THROTTLE, ((signed char)((data[24] << 6) | (data[25] << 3) | (data[26]))) + 128); + + input_report_abs(dev, ABS_HAT0X, ( data[5] & 1) - ((data[5] >> 2) & 1)); + input_report_abs(dev, ABS_HAT0Y, ((data[5] >> 1) & 1) - ((data[6] >> 2) & 1)); + input_report_abs(dev, ABS_HAT1X, ((data[4] >> 1) & 1) - ( data[3] & 1)); + input_report_abs(dev, ABS_HAT1Y, ((data[4] >> 2) & 1) - ( data[4] & 1)); + + input_report_key(dev, BTN_TRIGGER, data[8] & 1); + input_report_key(dev, BTN_THUMB, data[8] & 2); + input_report_key(dev, BTN_TOP, data[8] & 4); + input_report_key(dev, BTN_PINKIE, data[7] & 1); + + input_sync(dev); + + break; + } +} + + +/* + * a3d_poll() reads and analyzes A3D joystick data. + */ + +static void a3d_poll(struct gameport *gameport) +{ + struct a3d *a3d = gameport_get_drvdata(gameport); + unsigned char data[A3D_MAX_LENGTH]; + + a3d->reads++; + if (a3d_read_packet(a3d->gameport, a3d->length, data) != a3d->length || + data[0] != a3d->mode || a3d_csum(data, a3d->length)) + a3d->bads++; + else + a3d_read(a3d, data); +} + +/* + * a3d_adc_cooked_read() copies the acis and button data to the + * callers arrays. It could do the read itself, but the caller could + * call this more than 50 times a second, which would use too much CPU. + */ + +static int a3d_adc_cooked_read(struct gameport *gameport, int *axes, int *buttons) +{ + struct a3d *a3d = gameport->port_data; + int i; + + for (i = 0; i < 4; i++) + axes[i] = (a3d->axes[i] < 254) ? a3d->axes[i] : -1; + *buttons = a3d->buttons; + return 0; +} + +/* + * a3d_adc_open() is the gameport open routine. It refuses to serve + * any but cooked data. + */ + +static int a3d_adc_open(struct gameport *gameport, int mode) +{ + struct a3d *a3d = gameport->port_data; + + if (mode != GAMEPORT_MODE_COOKED) + return -1; + + gameport_start_polling(a3d->gameport); + return 0; +} + +/* + * a3d_adc_close() is a callback from the input close routine. + */ + +static void a3d_adc_close(struct gameport *gameport) +{ + struct a3d *a3d = gameport->port_data; + + gameport_stop_polling(a3d->gameport); +} + +/* + * a3d_open() is a callback from the input open routine. + */ + +static int a3d_open(struct input_dev *dev) +{ + struct a3d *a3d = dev->private; + + gameport_start_polling(a3d->gameport); + return 0; +} + +/* + * a3d_close() is a callback from the input close routine. + */ + +static void a3d_close(struct input_dev *dev) +{ + struct a3d *a3d = dev->private; + + gameport_stop_polling(a3d->gameport); +} + +/* + * a3d_connect() probes for A3D joysticks. + */ + +static int a3d_connect(struct gameport *gameport, struct gameport_driver *drv) +{ + struct a3d *a3d; + struct gameport *adc; + unsigned char data[A3D_MAX_LENGTH]; + int i; + int err; + + if (!(a3d = kcalloc(1, sizeof(struct a3d), GFP_KERNEL))) + return -ENOMEM; + + a3d->gameport = gameport; + + gameport_set_drvdata(gameport, a3d); + + err = gameport_open(gameport, drv, GAMEPORT_MODE_RAW); + if (err) + goto fail1; + + i = a3d_read_packet(gameport, A3D_MAX_LENGTH, data); + + if (!i || a3d_csum(data, i)) { + err = -ENODEV; + goto fail2; + } + + a3d->mode = data[0]; + + if (!a3d->mode || a3d->mode > 5) { + printk(KERN_WARNING "a3d.c: Unknown A3D device detected " + "(%s, id=%d), contact <vojtech@ucw.cz>\n", gameport->phys, a3d->mode); + err = -ENODEV; + goto fail2; + } + + gameport_set_poll_handler(gameport, a3d_poll); + gameport_set_poll_interval(gameport, 20); + + sprintf(a3d->phys, "%s/input0", gameport->phys); + + if (a3d->mode == A3D_MODE_PXL) { + + int axes[] = { ABS_X, ABS_Y, ABS_THROTTLE, ABS_RUDDER }; + + a3d->length = 33; + + init_input_dev(&a3d->dev); + + a3d->dev.evbit[0] |= BIT(EV_ABS) | BIT(EV_KEY) | BIT(EV_REL); + a3d->dev.relbit[0] |= BIT(REL_X) | BIT(REL_Y); + a3d->dev.absbit[0] |= BIT(ABS_X) | BIT(ABS_Y) | BIT(ABS_THROTTLE) | BIT(ABS_RUDDER) + | BIT(ABS_HAT0X) | BIT(ABS_HAT0Y) | BIT(ABS_HAT1X) | BIT(ABS_HAT1Y); + + a3d->dev.keybit[LONG(BTN_MOUSE)] |= BIT(BTN_RIGHT) | BIT(BTN_LEFT) | BIT(BTN_MIDDLE) + | BIT(BTN_SIDE) | BIT(BTN_EXTRA); + + a3d->dev.keybit[LONG(BTN_JOYSTICK)] |= BIT(BTN_TRIGGER) | BIT(BTN_THUMB) | BIT(BTN_TOP) | BIT(BTN_PINKIE); + + a3d_read(a3d, data); + + for (i = 0; i < 4; i++) { + if (i < 2) + input_set_abs_params(&a3d->dev, axes[i], 48, a3d->dev.abs[axes[i]] * 2 - 48, 0, 8); + else + input_set_abs_params(&a3d->dev, axes[i], 2, 253, 0, 0); + input_set_abs_params(&a3d->dev, ABS_HAT0X + i, -1, 1, 0, 0); + } + + } else { + a3d->length = 29; + + init_input_dev(&a3d->dev); + + a3d->dev.evbit[0] |= BIT(EV_KEY) | BIT(EV_REL); + a3d->dev.relbit[0] |= BIT(REL_X) | BIT(REL_Y); + a3d->dev.keybit[LONG(BTN_MOUSE)] |= BIT(BTN_RIGHT) | BIT(BTN_LEFT) | BIT(BTN_MIDDLE); + + a3d_read(a3d, data); + + if (!(a3d->adc = adc = gameport_allocate_port())) + printk(KERN_ERR "a3d: Not enough memory for ADC port\n"); + else { + adc->port_data = a3d; + adc->open = a3d_adc_open; + adc->close = a3d_adc_close; + adc->cooked_read = a3d_adc_cooked_read; + adc->fuzz = 1; + + gameport_set_name(adc, a3d_names[a3d->mode]); + gameport_set_phys(adc, "%s/gameport0", gameport->phys); + adc->dev.parent = &gameport->dev; + + gameport_register_port(adc); + } + } + + a3d->dev.private = a3d; + a3d->dev.open = a3d_open; + a3d->dev.close = a3d_close; + + a3d->dev.name = a3d_names[a3d->mode]; + a3d->dev.phys = a3d->phys; + a3d->dev.id.bustype = BUS_GAMEPORT; + a3d->dev.id.vendor = GAMEPORT_ID_VENDOR_MADCATZ; + a3d->dev.id.product = a3d->mode; + a3d->dev.id.version = 0x0100; + + input_register_device(&a3d->dev); + printk(KERN_INFO "input: %s on %s\n", a3d_names[a3d->mode], a3d->phys); + + return 0; + +fail2: gameport_close(gameport); +fail1: gameport_set_drvdata(gameport, NULL); + kfree(a3d); + return err; +} + +static void a3d_disconnect(struct gameport *gameport) +{ + struct a3d *a3d = gameport_get_drvdata(gameport); + + input_unregister_device(&a3d->dev); + if (a3d->adc) { + gameport_unregister_port(a3d->adc); + a3d->adc = NULL; + } + gameport_close(gameport); + gameport_set_drvdata(gameport, NULL); + kfree(a3d); +} + +static struct gameport_driver a3d_drv = { + .driver = { + .name = "adc", + }, + .description = DRIVER_DESC, + .connect = a3d_connect, + .disconnect = a3d_disconnect, +}; + +static int __init a3d_init(void) +{ + gameport_register_driver(&a3d_drv); + return 0; +} + +static void __exit a3d_exit(void) +{ + gameport_unregister_driver(&a3d_drv); +} + +module_init(a3d_init); +module_exit(a3d_exit); diff --git a/drivers/input/joystick/adi.c b/drivers/input/joystick/adi.c new file mode 100644 index 000000000000..83f6dafc1716 --- /dev/null +++ b/drivers/input/joystick/adi.c @@ -0,0 +1,560 @@ +/* + * Copyright (c) 1998-2005 Vojtech Pavlik + */ + +/* + * Logitech ADI joystick family driver for Linux + */ + +/* + * 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 either by + * e-mail - mail your message to <vojtech@ucw.cz>, or by paper mail: + * Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic + */ + +#include <linux/delay.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/string.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/gameport.h> +#include <linux/init.h> + +#define DRIVER_DESC "Logitech ADI joystick family driver" + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +/* + * Times, array sizes, flags, ids. + */ + +#define ADI_MAX_START 200 /* Trigger to packet timeout [200us] */ +#define ADI_MAX_STROBE 40 /* Single bit timeout [40us] */ +#define ADI_INIT_DELAY 10 /* Delay after init packet [10ms] */ +#define ADI_DATA_DELAY 4 /* Delay after data packet [4ms] */ + +#define ADI_MAX_LENGTH 256 +#define ADI_MIN_LENGTH 8 +#define ADI_MIN_LEN_LENGTH 10 +#define ADI_MIN_ID_LENGTH 66 +#define ADI_MAX_NAME_LENGTH 48 +#define ADI_MAX_CNAME_LENGTH 16 +#define ADI_MAX_PHYS_LENGTH 64 + +#define ADI_FLAG_HAT 0x04 +#define ADI_FLAG_10BIT 0x08 + +#define ADI_ID_TPD 0x01 +#define ADI_ID_WGP 0x06 +#define ADI_ID_WGPE 0x08 +#define ADI_ID_MAX 0x0a + +/* + * Names, buttons, axes ... + */ + +static char *adi_names[] = { "WingMan Extreme Digital", "ThunderPad Digital", "SideCar", "CyberMan 2", + "WingMan Interceptor", "WingMan Formula", "WingMan GamePad", + "WingMan Extreme Digital 3D", "WingMan GamePad Extreme", + "WingMan GamePad USB", "Unknown Device %#x" }; + +static char adi_wmgpe_abs[] = { ABS_X, ABS_Y, ABS_HAT0X, ABS_HAT0Y }; +static char adi_wmi_abs[] = { ABS_X, ABS_Y, ABS_THROTTLE, ABS_HAT0X, ABS_HAT0Y, ABS_HAT1X, ABS_HAT1Y, ABS_HAT2X, ABS_HAT2Y }; +static char adi_wmed3d_abs[] = { ABS_X, ABS_Y, ABS_THROTTLE, ABS_RZ, ABS_HAT0X, ABS_HAT0Y }; +static char adi_cm2_abs[] = { ABS_X, ABS_Y, ABS_Z, ABS_RX, ABS_RY, ABS_RZ }; +static char adi_wmf_abs[] = { ABS_WHEEL, ABS_GAS, ABS_BRAKE, ABS_HAT0X, ABS_HAT0Y, ABS_HAT1X, ABS_HAT1Y, ABS_HAT2X, ABS_HAT2Y }; + +static short adi_wmgpe_key[] = { BTN_A, BTN_B, BTN_C, BTN_X, BTN_Y, BTN_Z, BTN_TL, BTN_TR, BTN_START, BTN_MODE, BTN_SELECT }; +static short adi_wmi_key[] = { BTN_TRIGGER, BTN_TOP, BTN_THUMB, BTN_TOP2, BTN_BASE, BTN_BASE2, BTN_BASE3, BTN_BASE4, BTN_EXTRA }; +static short adi_wmed3d_key[] = { BTN_TRIGGER, BTN_THUMB, BTN_THUMB2, BTN_TOP, BTN_TOP2, BTN_BASE, BTN_BASE2 }; +static short adi_cm2_key[] = { BTN_1, BTN_2, BTN_3, BTN_4, BTN_5, BTN_6, BTN_7, BTN_8 }; + +static char* adi_abs[] = { adi_wmi_abs, adi_wmgpe_abs, adi_wmf_abs, adi_cm2_abs, adi_wmi_abs, adi_wmf_abs, + adi_wmgpe_abs, adi_wmed3d_abs, adi_wmgpe_abs, adi_wmgpe_abs, adi_wmi_abs }; + +static short* adi_key[] = { adi_wmi_key, adi_wmgpe_key, adi_cm2_key, adi_cm2_key, adi_wmi_key, adi_cm2_key, + adi_wmgpe_key, adi_wmed3d_key, adi_wmgpe_key, adi_wmgpe_key, adi_wmi_key }; + +/* + * Hat to axis conversion arrays. + */ + +static struct { + int x; + int y; +} adi_hat_to_axis[] = {{ 0, 0}, { 0,-1}, { 1,-1}, { 1, 0}, { 1, 1}, { 0, 1}, {-1, 1}, {-1, 0}, {-1,-1}}; + +/* + * Per-port information. + */ + +struct adi { + struct input_dev dev; + int length; + int ret; + int idx; + unsigned char id; + char buttons; + char axes10; + char axes8; + signed char pad; + char hats; + char *abs; + short *key; + char name[ADI_MAX_NAME_LENGTH]; + char cname[ADI_MAX_CNAME_LENGTH]; + char phys[ADI_MAX_PHYS_LENGTH]; + unsigned char data[ADI_MAX_LENGTH]; +}; + +struct adi_port { + struct gameport *gameport; + struct adi adi[2]; + int bad; + int reads; +}; + +/* + * adi_read_packet() reads a Logitech ADI packet. + */ + +static void adi_read_packet(struct adi_port *port) +{ + struct adi *adi = port->adi; + struct gameport *gameport = port->gameport; + unsigned char u, v, w, x, z; + int t[2], s[2], i; + unsigned long flags; + + for (i = 0; i < 2; i++) { + adi[i].ret = -1; + t[i] = gameport_time(gameport, ADI_MAX_START); + s[i] = 0; + } + + local_irq_save(flags); + + gameport_trigger(gameport); + v = z = gameport_read(gameport); + + do { + u = v; + w = u ^ (v = x = gameport_read(gameport)); + for (i = 0; i < 2; i++, w >>= 2, x >>= 2) { + t[i]--; + if ((w & 0x30) && s[i]) { + if ((w & 0x30) < 0x30 && adi[i].ret < ADI_MAX_LENGTH && t[i] > 0) { + adi[i].data[++adi[i].ret] = w; + t[i] = gameport_time(gameport, ADI_MAX_STROBE); + } else t[i] = 0; + } else if (!(x & 0x30)) s[i] = 1; + } + } while (t[0] > 0 || t[1] > 0); + + local_irq_restore(flags); + + return; +} + +/* + * adi_move_bits() detects a possible 2-stream mode, and moves + * the bits accordingly. + */ + +static void adi_move_bits(struct adi_port *port, int length) +{ + int i; + struct adi *adi = port->adi; + + adi[0].idx = adi[1].idx = 0; + + if (adi[0].ret <= 0 || adi[1].ret <= 0) return; + if (adi[0].data[0] & 0x20 || ~adi[1].data[0] & 0x20) return; + + for (i = 1; i <= adi[1].ret; i++) + adi[0].data[((length - 1) >> 1) + i + 1] = adi[1].data[i]; + + adi[0].ret += adi[1].ret; + adi[1].ret = -1; +} + +/* + * adi_get_bits() gathers bits from the data packet. + */ + +static inline int adi_get_bits(struct adi *adi, int count) +{ + int bits = 0; + int i; + if ((adi->idx += count) > adi->ret) return 0; + for (i = 0; i < count; i++) + bits |= ((adi->data[adi->idx - i] >> 5) & 1) << i; + return bits; +} + +/* + * adi_decode() decodes Logitech joystick data into input events. + */ + +static int adi_decode(struct adi *adi) +{ + struct input_dev *dev = &adi->dev; + char *abs = adi->abs; + short *key = adi->key; + int i, t; + + if (adi->ret < adi->length || adi->id != (adi_get_bits(adi, 4) | (adi_get_bits(adi, 4) << 4))) + return -1; + + for (i = 0; i < adi->axes10; i++) + input_report_abs(dev, *abs++, adi_get_bits(adi, 10)); + + for (i = 0; i < adi->axes8; i++) + input_report_abs(dev, *abs++, adi_get_bits(adi, 8)); + + for (i = 0; i < adi->buttons && i < 63; i++) { + if (i == adi->pad) { + t = adi_get_bits(adi, 4); + input_report_abs(dev, *abs++, ((t >> 2) & 1) - ( t & 1)); + input_report_abs(dev, *abs++, ((t >> 1) & 1) - ((t >> 3) & 1)); + } + input_report_key(dev, *key++, adi_get_bits(adi, 1)); + } + + for (i = 0; i < adi->hats; i++) { + if ((t = adi_get_bits(adi, 4)) > 8) t = 0; + input_report_abs(dev, *abs++, adi_hat_to_axis[t].x); + input_report_abs(dev, *abs++, adi_hat_to_axis[t].y); + } + + for (i = 63; i < adi->buttons; i++) + input_report_key(dev, *key++, adi_get_bits(adi, 1)); + + input_sync(dev); + + return 0; +} + +/* + * adi_read() reads the data packet and decodes it. + */ + +static int adi_read(struct adi_port *port) +{ + int i; + int result = 0; + + adi_read_packet(port); + adi_move_bits(port, port->adi[0].length); + + for (i = 0; i < 2; i++) + if (port->adi[i].length) + result |= adi_decode(port->adi + i); + + return result; +} + +/* + * adi_poll() repeatedly polls the Logitech joysticks. + */ + +static void adi_poll(struct gameport *gameport) +{ + struct adi_port *port = gameport_get_drvdata(gameport); + + port->bad -= adi_read(port); + port->reads++; +} + +/* + * adi_open() is a callback from the input open routine. + */ + +static int adi_open(struct input_dev *dev) +{ + struct adi_port *port = dev->private; + + gameport_start_polling(port->gameport); + return 0; +} + +/* + * adi_close() is a callback from the input close routine. + */ + +static void adi_close(struct input_dev *dev) +{ + struct adi_port *port = dev->private; + + gameport_stop_polling(port->gameport); +} + +/* + * adi_init_digital() sends a trigger & delay sequence + * to reset and initialize a Logitech joystick into digital mode. + */ + +static void adi_init_digital(struct gameport *gameport) +{ + int seq[] = { 4, -2, -3, 10, -6, -11, -7, -9, 11, 0 }; + int i; + + for (i = 0; seq[i]; i++) { + gameport_trigger(gameport); + if (seq[i] > 0) msleep(seq[i]); + if (seq[i] < 0) { + mdelay(-seq[i]); + udelay(-seq[i]*14); /* It looks like mdelay() is off by approx 1.4% */ + } + } +} + +static void adi_id_decode(struct adi *adi, struct adi_port *port) +{ + int i, t; + + if (adi->ret < ADI_MIN_ID_LENGTH) /* Minimum ID packet length */ + return; + + if (adi->ret < (t = adi_get_bits(adi, 10))) { + printk(KERN_WARNING "adi: Short ID packet: reported: %d != read: %d\n", t, adi->ret); + return; + } + + adi->id = adi_get_bits(adi, 4) | (adi_get_bits(adi, 4) << 4); + + if ((t = adi_get_bits(adi, 4)) & ADI_FLAG_HAT) adi->hats++; + + adi->length = adi_get_bits(adi, 10); + + if (adi->length >= ADI_MAX_LENGTH || adi->length < ADI_MIN_LENGTH) { + printk(KERN_WARNING "adi: Bad data packet length (%d).\n", adi->length); + adi->length = 0; + return; + } + + adi->axes8 = adi_get_bits(adi, 4); + adi->buttons = adi_get_bits(adi, 6); + + if (adi_get_bits(adi, 6) != 8 && adi->hats) { + printk(KERN_WARNING "adi: Other than 8-dir POVs not supported yet.\n"); + adi->length = 0; + return; + } + + adi->buttons += adi_get_bits(adi, 6); + adi->hats += adi_get_bits(adi, 4); + + i = adi_get_bits(adi, 4); + + if (t & ADI_FLAG_10BIT) { + adi->axes10 = adi->axes8 - i; + adi->axes8 = i; + } + + t = adi_get_bits(adi, 4); + + for (i = 0; i < t; i++) + adi->cname[i] = adi_get_bits(adi, 8); + adi->cname[i] = 0; + + t = 8 + adi->buttons + adi->axes10 * 10 + adi->axes8 * 8 + adi->hats * 4; + if (adi->length != t && adi->length != t + (t & 1)) { + printk(KERN_WARNING "adi: Expected length %d != data length %d\n", t, adi->length); + adi->length = 0; + return; + } + + switch (adi->id) { + case ADI_ID_TPD: + adi->pad = 4; + adi->buttons -= 4; + break; + case ADI_ID_WGP: + adi->pad = 0; + adi->buttons -= 4; + break; + default: + adi->pad = -1; + break; + } +} + +static void adi_init_input(struct adi *adi, struct adi_port *port, int half) +{ + int i, t; + char buf[ADI_MAX_NAME_LENGTH]; + + if (!adi->length) return; + + init_input_dev(&adi->dev); + + t = adi->id < ADI_ID_MAX ? adi->id : ADI_ID_MAX; + + snprintf(buf, ADI_MAX_PHYS_LENGTH, adi_names[t], adi->id); + snprintf(adi->name, ADI_MAX_NAME_LENGTH, "Logitech %s", buf); + snprintf(adi->phys, ADI_MAX_PHYS_LENGTH, "%s/input%d", port->gameport->phys, half); + + adi->abs = adi_abs[t]; + adi->key = adi_key[t]; + + adi->dev.open = adi_open; + adi->dev.close = adi_close; + + adi->dev.name = adi->name; + adi->dev.phys = adi->phys; + adi->dev.id.bustype = BUS_GAMEPORT; + adi->dev.id.vendor = GAMEPORT_ID_VENDOR_LOGITECH; + adi->dev.id.product = adi->id; + adi->dev.id.version = 0x0100; + + adi->dev.private = port; + adi->dev.evbit[0] = BIT(EV_KEY) | BIT(EV_ABS); + + for (i = 0; i < adi->axes10 + adi->axes8 + (adi->hats + (adi->pad != -1)) * 2; i++) + set_bit(adi->abs[i], adi->dev.absbit); + + for (i = 0; i < adi->buttons; i++) + set_bit(adi->key[i], adi->dev.keybit); +} + +static void adi_init_center(struct adi *adi) +{ + int i, t, x; + + if (!adi->length) + return; + + for (i = 0; i < adi->axes10 + adi->axes8 + (adi->hats + (adi->pad != -1)) * 2; i++) { + + t = adi->abs[i]; + x = adi->dev.abs[t]; + + if (t == ABS_THROTTLE || t == ABS_RUDDER || adi->id == ADI_ID_WGPE) + x = i < adi->axes10 ? 512 : 128; + + if (i < adi->axes10) + input_set_abs_params(&adi->dev, t, 64, x * 2 - 64, 2, 16); + else if (i < adi->axes10 + adi->axes8) + input_set_abs_params(&adi->dev, t, 48, x * 2 - 48, 1, 16); + else + input_set_abs_params(&adi->dev, t, -1, 1, 0, 0); + } +} + +/* + * adi_connect() probes for Logitech ADI joysticks. + */ + +static int adi_connect(struct gameport *gameport, struct gameport_driver *drv) +{ + struct adi_port *port; + int i; + int err; + + if (!(port = kcalloc(1, sizeof(struct adi_port), GFP_KERNEL))) + return -ENOMEM; + + port->gameport = gameport; + + gameport_set_drvdata(gameport, port); + + err = gameport_open(gameport, drv, GAMEPORT_MODE_RAW); + if (err) { + kfree(port); + return err; + } + + adi_init_digital(gameport); + adi_read_packet(port); + + if (port->adi[0].ret >= ADI_MIN_LEN_LENGTH) + adi_move_bits(port, adi_get_bits(port->adi, 10)); + + for (i = 0; i < 2; i++) { + adi_id_decode(port->adi + i, port); + adi_init_input(port->adi + i, port, i); + } + + if (!port->adi[0].length && !port->adi[1].length) { + gameport_close(gameport); + kfree(port); + return -ENODEV; + } + + gameport_set_poll_handler(gameport, adi_poll); + gameport_set_poll_interval(gameport, 20); + + msleep(ADI_INIT_DELAY); + if (adi_read(port)) { + msleep(ADI_DATA_DELAY); + adi_read(port); + } + + for (i = 0; i < 2; i++) + if (port->adi[i].length > 0) { + adi_init_center(port->adi + i); + input_register_device(&port->adi[i].dev); + printk(KERN_INFO "input: %s [%s] on %s\n", + port->adi[i].name, port->adi[i].cname, gameport->phys); + } + + return 0; +} + +static void adi_disconnect(struct gameport *gameport) +{ + int i; + struct adi_port *port = gameport_get_drvdata(gameport); + + for (i = 0; i < 2; i++) + if (port->adi[i].length > 0) + input_unregister_device(&port->adi[i].dev); + gameport_close(gameport); + gameport_set_drvdata(gameport, NULL); + kfree(port); +} + +/* + * The gameport device structure. + */ + +static struct gameport_driver adi_drv = { + .driver = { + .name = "adi", + }, + .description = DRIVER_DESC, + .connect = adi_connect, + .disconnect = adi_disconnect, +}; + +static int __init adi_init(void) +{ + gameport_register_driver(&adi_drv); + return 0; +} + +static void __exit adi_exit(void) +{ + gameport_unregister_driver(&adi_drv); +} + +module_init(adi_init); +module_exit(adi_exit); diff --git a/drivers/input/joystick/amijoy.c b/drivers/input/joystick/amijoy.c new file mode 100644 index 000000000000..cf36ca9b92f3 --- /dev/null +++ b/drivers/input/joystick/amijoy.c @@ -0,0 +1,161 @@ +/* + * $Id: amijoy.c,v 1.13 2002/01/22 20:26:32 vojtech Exp $ + * + * Copyright (c) 1998-2001 Vojtech Pavlik + */ + +/* + * Driver for Amiga joysticks for Linux/m68k + */ + +/* + * 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 either by + * e-mail - mail your message to <vojtech@ucw.cz>, or by paper mail: + * Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic + */ + +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/interrupt.h> + +#include <asm/system.h> +#include <asm/amigahw.h> +#include <asm/amigaints.h> + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION("Driver for Amiga joysticks"); +MODULE_LICENSE("GPL"); + +static int amijoy[2] = { 0, 1 }; +module_param_array_named(map, amijoy, uint, NULL, 0); +MODULE_PARM_DESC(map, "Map of attached joysticks in form of <a>,<b> (default is 0,1)"); + +__obsolete_setup("amijoy="); + +static int amijoy_used[2] = { 0, 0 }; +static struct input_dev amijoy_dev[2]; +static char *amijoy_phys[2] = { "amijoy/input0", "amijoy/input1" }; + +static char *amijoy_name = "Amiga joystick"; + +static irqreturn_t amijoy_interrupt(int irq, void *dummy, struct pt_regs *fp) +{ + int i, data = 0, button = 0; + + for (i = 0; i < 2; i++) + if (amijoy[i]) { + + switch (i) { + case 0: data = ~custom.joy0dat; button = (~ciaa.pra >> 6) & 1; break; + case 1: data = ~custom.joy1dat; button = (~ciaa.pra >> 7) & 1; break; + } + + input_regs(amijoy_dev + i, fp); + + input_report_key(amijoy_dev + i, BTN_TRIGGER, button); + + input_report_abs(amijoy_dev + i, ABS_X, ((data >> 1) & 1) - ((data >> 9) & 1)); + data = ~(data ^ (data << 1)); + input_report_abs(amijoy_dev + i, ABS_Y, ((data >> 1) & 1) - ((data >> 9) & 1)); + + input_sync(amijoy_dev + i); + } + return IRQ_HANDLED; +} + +static int amijoy_open(struct input_dev *dev) +{ + int *used = dev->private; + + if ((*used)++) + return 0; + + if (request_irq(IRQ_AMIGA_VERTB, amijoy_interrupt, 0, "amijoy", amijoy_interrupt)) { + (*used)--; + printk(KERN_ERR "amijoy.c: Can't allocate irq %d\n", IRQ_AMIGA_VERTB); + return -EBUSY; + } + + return 0; +} + +static void amijoy_close(struct input_dev *dev) +{ + int *used = dev->private; + + if (!--(*used)) + free_irq(IRQ_AMIGA_VERTB, amijoy_interrupt); +} + +static int __init amijoy_init(void) +{ + int i, j; + + for (i = 0; i < 2; i++) + if (amijoy[i]) { + if (!request_mem_region(CUSTOM_PHYSADDR+10+i*2, 2, + "amijoy [Denise]")) { + if (i == 1 && amijoy[0]) { + input_unregister_device(amijoy_dev); + release_mem_region(CUSTOM_PHYSADDR+10, 2); + } + return -EBUSY; + } + + amijoy_dev[i].open = amijoy_open; + amijoy_dev[i].close = amijoy_close; + amijoy_dev[i].evbit[0] = BIT(EV_KEY) | BIT(EV_ABS); + amijoy_dev[i].absbit[0] = BIT(ABS_X) | BIT(ABS_Y); + amijoy_dev[i].keybit[LONG(BTN_LEFT)] = BIT(BTN_LEFT) | BIT(BTN_MIDDLE) | BIT(BTN_RIGHT); + for (j = 0; j < 2; j++) { + amijoy_dev[i].absmin[ABS_X + j] = -1; + amijoy_dev[i].absmax[ABS_X + j] = 1; + } + + amijoy_dev[i].name = amijoy_name; + amijoy_dev[i].phys = amijoy_phys[i]; + amijoy_dev[i].id.bustype = BUS_AMIGA; + amijoy_dev[i].id.vendor = 0x0001; + amijoy_dev[i].id.product = 0x0003; + amijoy_dev[i].id.version = 0x0100; + + amijoy_dev[i].private = amijoy_used + i; + + input_register_device(amijoy_dev + i); + printk(KERN_INFO "input: %s at joy%ddat\n", amijoy_name, i); + } + return 0; +} + +static void __exit amijoy_exit(void) +{ + int i; + + for (i = 0; i < 2; i++) + if (amijoy[i]) { + input_unregister_device(amijoy_dev + i); + release_mem_region(CUSTOM_PHYSADDR+10+i*2, 2); + } +} + +module_init(amijoy_init); +module_exit(amijoy_exit); diff --git a/drivers/input/joystick/analog.c b/drivers/input/joystick/analog.c new file mode 100644 index 000000000000..504b7d550567 --- /dev/null +++ b/drivers/input/joystick/analog.c @@ -0,0 +1,772 @@ +/* + * $Id: analog.c,v 1.68 2002/01/22 20:18:32 vojtech Exp $ + * + * Copyright (c) 1996-2001 Vojtech Pavlik + */ + +/* + * Analog joystick and gamepad driver for Linux + */ + +/* + * 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 either by + * e-mail - mail your message to <vojtech@ucw.cz>, or by paper mail: + * Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic + */ + +#include <linux/config.h> +#include <linux/delay.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/slab.h> +#include <linux/bitops.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/gameport.h> +#include <asm/timex.h> + +#define DRIVER_DESC "Analog joystick and gamepad driver" + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +/* + * Option parsing. + */ + +#define ANALOG_PORTS 16 + +static char *js[ANALOG_PORTS]; +static int js_nargs; +static int analog_options[ANALOG_PORTS]; +module_param_array_named(map, js, charp, &js_nargs, 0); +MODULE_PARM_DESC(map, "Describes analog joysticks type/capabilities"); + +__obsolete_setup("js="); + +/* + * Times, feature definitions. + */ + +#define ANALOG_RUDDER 0x00004 +#define ANALOG_THROTTLE 0x00008 +#define ANALOG_AXES_STD 0x0000f +#define ANALOG_BTNS_STD 0x000f0 + +#define ANALOG_BTNS_CHF 0x00100 +#define ANALOG_HAT1_CHF 0x00200 +#define ANALOG_HAT2_CHF 0x00400 +#define ANALOG_HAT_FCS 0x00800 +#define ANALOG_HATS_ALL 0x00e00 +#define ANALOG_BTN_TL 0x01000 +#define ANALOG_BTN_TR 0x02000 +#define ANALOG_BTN_TL2 0x04000 +#define ANALOG_BTN_TR2 0x08000 +#define ANALOG_BTNS_TLR 0x03000 +#define ANALOG_BTNS_TLR2 0x0c000 +#define ANALOG_BTNS_GAMEPAD 0x0f000 + +#define ANALOG_HBTN_CHF 0x10000 +#define ANALOG_ANY_CHF 0x10700 +#define ANALOG_SAITEK 0x20000 +#define ANALOG_EXTENSIONS 0x7ff00 +#define ANALOG_GAMEPAD 0x80000 + +#define ANALOG_MAX_TIME 3 /* 3 ms */ +#define ANALOG_LOOP_TIME 2000 /* 2 * loop */ +#define ANALOG_SAITEK_DELAY 200 /* 200 us */ +#define ANALOG_SAITEK_TIME 2000 /* 2000 us */ +#define ANALOG_AXIS_TIME 2 /* 2 * refresh */ +#define ANALOG_INIT_RETRIES 8 /* 8 times */ +#define ANALOG_FUZZ_BITS 2 /* 2 bit more */ +#define ANALOG_FUZZ_MAGIC 36 /* 36 u*ms/loop */ + +#define ANALOG_MAX_NAME_LENGTH 128 +#define ANALOG_MAX_PHYS_LENGTH 32 + +static short analog_axes[] = { ABS_X, ABS_Y, ABS_RUDDER, ABS_THROTTLE }; +static short analog_hats[] = { ABS_HAT0X, ABS_HAT0Y, ABS_HAT1X, ABS_HAT1Y, ABS_HAT2X, ABS_HAT2Y }; +static short analog_pads[] = { BTN_Y, BTN_Z, BTN_TL, BTN_TR }; +static short analog_exts[] = { ANALOG_HAT1_CHF, ANALOG_HAT2_CHF, ANALOG_HAT_FCS }; +static short analog_pad_btn[] = { BTN_A, BTN_B, BTN_C, BTN_X, BTN_TL2, BTN_TR2, BTN_SELECT, BTN_START, BTN_MODE, BTN_BASE }; +static short analog_joy_btn[] = { BTN_TRIGGER, BTN_THUMB, BTN_TOP, BTN_TOP2, BTN_BASE, BTN_BASE2, + BTN_BASE3, BTN_BASE4, BTN_BASE5, BTN_BASE6 }; + +static unsigned char analog_chf[] = { 0xf, 0x0, 0x1, 0x9, 0x2, 0x4, 0xc, 0x8, 0x3, 0x5, 0xb, 0x7, 0xd, 0xe, 0xa, 0x6 }; + +struct analog { + struct input_dev dev; + int mask; + short *buttons; + char name[ANALOG_MAX_NAME_LENGTH]; + char phys[ANALOG_MAX_PHYS_LENGTH]; +}; + +struct analog_port { + struct gameport *gameport; + struct analog analog[2]; + unsigned char mask; + char saitek; + char cooked; + int bads; + int reads; + int speed; + int loop; + int fuzz; + int axes[4]; + int buttons; + int initial[4]; + int axtime; +}; + +/* + * Time macros. + */ + +#ifdef __i386__ +#define GET_TIME(x) do { if (cpu_has_tsc) rdtscl(x); else x = get_time_pit(); } while (0) +#define DELTA(x,y) (cpu_has_tsc ? ((y) - (x)) : ((x) - (y) + ((x) < (y) ? CLOCK_TICK_RATE / HZ : 0))) +#define TIME_NAME (cpu_has_tsc?"TSC":"PIT") +static unsigned int get_time_pit(void) +{ + extern spinlock_t i8253_lock; + unsigned long flags; + unsigned int count; + + spin_lock_irqsave(&i8253_lock, flags); + outb_p(0x00, 0x43); + count = inb_p(0x40); + count |= inb_p(0x40) << 8; + spin_unlock_irqrestore(&i8253_lock, flags); + + return count; +} +#elif defined(__x86_64__) +#define GET_TIME(x) rdtscl(x) +#define DELTA(x,y) ((y)-(x)) +#define TIME_NAME "TSC" +#elif defined(__alpha__) +#define GET_TIME(x) do { x = get_cycles(); } while (0) +#define DELTA(x,y) ((y)-(x)) +#define TIME_NAME "PCC" +#else +#define FAKE_TIME +static unsigned long analog_faketime = 0; +#define GET_TIME(x) do { x = analog_faketime++; } while(0) +#define DELTA(x,y) ((y)-(x)) +#define TIME_NAME "Unreliable" +#warning Precise timer not defined for this architecture. +#endif + +/* + * analog_decode() decodes analog joystick data and reports input events. + */ + +static void analog_decode(struct analog *analog, int *axes, int *initial, int buttons) +{ + struct input_dev *dev = &analog->dev; + int i, j; + + if (analog->mask & ANALOG_HAT_FCS) + for (i = 0; i < 4; i++) + if (axes[3] < ((initial[3] * ((i << 1) + 1)) >> 3)) { + buttons |= 1 << (i + 14); + break; + } + + for (i = j = 0; i < 6; i++) + if (analog->mask & (0x10 << i)) + input_report_key(dev, analog->buttons[j++], (buttons >> i) & 1); + + if (analog->mask & ANALOG_HBTN_CHF) + for (i = 0; i < 4; i++) + input_report_key(dev, analog->buttons[j++], (buttons >> (i + 10)) & 1); + + if (analog->mask & ANALOG_BTN_TL) + input_report_key(dev, analog_pads[0], axes[2] < (initial[2] >> 1)); + if (analog->mask & ANALOG_BTN_TR) + input_report_key(dev, analog_pads[1], axes[3] < (initial[3] >> 1)); + if (analog->mask & ANALOG_BTN_TL2) + input_report_key(dev, analog_pads[2], axes[2] > (initial[2] + (initial[2] >> 1))); + if (analog->mask & ANALOG_BTN_TR2) + input_report_key(dev, analog_pads[3], axes[3] > (initial[3] + (initial[3] >> 1))); + + for (i = j = 0; i < 4; i++) + if (analog->mask & (1 << i)) + input_report_abs(dev, analog_axes[j++], axes[i]); + + for (i = j = 0; i < 3; i++) + if (analog->mask & analog_exts[i]) { + input_report_abs(dev, analog_hats[j++], + ((buttons >> ((i << 2) + 7)) & 1) - ((buttons >> ((i << 2) + 9)) & 1)); + input_report_abs(dev, analog_hats[j++], + ((buttons >> ((i << 2) + 8)) & 1) - ((buttons >> ((i << 2) + 6)) & 1)); + } + + input_sync(dev); +} + +/* + * analog_cooked_read() reads analog joystick data. + */ + +static int analog_cooked_read(struct analog_port *port) +{ + struct gameport *gameport = port->gameport; + unsigned int time[4], start, loop, now, loopout, timeout; + unsigned char data[4], this, last; + unsigned long flags; + int i, j; + + loopout = (ANALOG_LOOP_TIME * port->loop) / 1000; + timeout = ANALOG_MAX_TIME * port->speed; + + local_irq_save(flags); + gameport_trigger(gameport); + GET_TIME(now); + local_irq_restore(flags); + + start = now; + this = port->mask; + i = 0; + + do { + loop = now; + last = this; + + local_irq_disable(); + this = gameport_read(gameport) & port->mask; + GET_TIME(now); + local_irq_restore(flags); + + if ((last ^ this) && (DELTA(loop, now) < loopout)) { + data[i] = last ^ this; + time[i] = now; + i++; + } + + } while (this && (i < 4) && (DELTA(start, now) < timeout)); + + this <<= 4; + + for (--i; i >= 0; i--) { + this |= data[i]; + for (j = 0; j < 4; j++) + if (data[i] & (1 << j)) + port->axes[j] = (DELTA(start, time[i]) << ANALOG_FUZZ_BITS) / port->loop; + } + + return -(this != port->mask); +} + +static int analog_button_read(struct analog_port *port, char saitek, char chf) +{ + unsigned char u; + int t = 1, i = 0; + int strobe = gameport_time(port->gameport, ANALOG_SAITEK_TIME); + + u = gameport_read(port->gameport); + + if (!chf) { + port->buttons = (~u >> 4) & 0xf; + return 0; + } + + port->buttons = 0; + + while ((~u & 0xf0) && (i < 16) && t) { + port->buttons |= 1 << analog_chf[(~u >> 4) & 0xf]; + if (!saitek) return 0; + udelay(ANALOG_SAITEK_DELAY); + t = strobe; + gameport_trigger(port->gameport); + while (((u = gameport_read(port->gameport)) & port->mask) && t) t--; + i++; + } + + return -(!t || (i == 16)); +} + +/* + * analog_poll() repeatedly polls the Analog joysticks. + */ + +static void analog_poll(struct gameport *gameport) +{ + struct analog_port *port = gameport_get_drvdata(gameport); + int i; + + char saitek = !!(port->analog[0].mask & ANALOG_SAITEK); + char chf = !!(port->analog[0].mask & ANALOG_ANY_CHF); + + if (port->cooked) { + port->bads -= gameport_cooked_read(port->gameport, port->axes, &port->buttons); + if (chf) + port->buttons = port->buttons ? (1 << analog_chf[port->buttons]) : 0; + port->reads++; + } else { + if (!port->axtime--) { + port->bads -= analog_cooked_read(port); + port->bads -= analog_button_read(port, saitek, chf); + port->reads++; + port->axtime = ANALOG_AXIS_TIME - 1; + } else { + if (!saitek) + analog_button_read(port, saitek, chf); + } + } + + for (i = 0; i < 2; i++) + if (port->analog[i].mask) + analog_decode(port->analog + i, port->axes, port->initial, port->buttons); +} + +/* + * analog_open() is a callback from the input open routine. + */ + +static int analog_open(struct input_dev *dev) +{ + struct analog_port *port = dev->private; + + gameport_start_polling(port->gameport); + return 0; +} + +/* + * analog_close() is a callback from the input close routine. + */ + +static void analog_close(struct input_dev *dev) +{ + struct analog_port *port = dev->private; + + gameport_stop_polling(port->gameport); +} + +/* + * analog_calibrate_timer() calibrates the timer and computes loop + * and timeout values for a joystick port. + */ + +static void analog_calibrate_timer(struct analog_port *port) +{ + struct gameport *gameport = port->gameport; + unsigned int i, t, tx, t1, t2, t3; + unsigned long flags; + + local_irq_save(flags); + GET_TIME(t1); +#ifdef FAKE_TIME + analog_faketime += 830; +#endif + mdelay(1); + GET_TIME(t2); + GET_TIME(t3); + local_irq_restore(flags); + + port->speed = DELTA(t1, t2) - DELTA(t2, t3); + + tx = ~0; + + for (i = 0; i < 50; i++) { + local_irq_save(flags); + GET_TIME(t1); + for (t = 0; t < 50; t++) { gameport_read(gameport); GET_TIME(t2); } + GET_TIME(t3); + local_irq_restore(flags); + udelay(i); + t = DELTA(t1, t2) - DELTA(t2, t3); + if (t < tx) tx = t; + } + + port->loop = tx / 50; +} + +/* + * analog_name() constructs a name for an analog joystick. + */ + +static void analog_name(struct analog *analog) +{ + sprintf(analog->name, "Analog %d-axis %d-button", + hweight8(analog->mask & ANALOG_AXES_STD), + hweight8(analog->mask & ANALOG_BTNS_STD) + !!(analog->mask & ANALOG_BTNS_CHF) * 2 + + hweight16(analog->mask & ANALOG_BTNS_GAMEPAD) + !!(analog->mask & ANALOG_HBTN_CHF) * 4); + + if (analog->mask & ANALOG_HATS_ALL) + sprintf(analog->name, "%s %d-hat", + analog->name, hweight16(analog->mask & ANALOG_HATS_ALL)); + + if (analog->mask & ANALOG_HAT_FCS) + strcat(analog->name, " FCS"); + if (analog->mask & ANALOG_ANY_CHF) + strcat(analog->name, (analog->mask & ANALOG_SAITEK) ? " Saitek" : " CHF"); + + strcat(analog->name, (analog->mask & ANALOG_GAMEPAD) ? " gamepad": " joystick"); +} + +/* + * analog_init_device() + */ + +static void analog_init_device(struct analog_port *port, struct analog *analog, int index) +{ + int i, j, t, v, w, x, y, z; + + analog_name(analog); + sprintf(analog->phys, "%s/input%d", port->gameport->phys, index); + analog->buttons = (analog->mask & ANALOG_GAMEPAD) ? analog_pad_btn : analog_joy_btn; + + init_input_dev(&analog->dev); + + analog->dev.name = analog->name; + analog->dev.phys = analog->phys; + analog->dev.id.bustype = BUS_GAMEPORT; + analog->dev.id.vendor = GAMEPORT_ID_VENDOR_ANALOG; + analog->dev.id.product = analog->mask >> 4; + analog->dev.id.version = 0x0100; + + analog->dev.open = analog_open; + analog->dev.close = analog_close; + analog->dev.private = port; + analog->dev.evbit[0] = BIT(EV_KEY) | BIT(EV_ABS); + + for (i = j = 0; i < 4; i++) + if (analog->mask & (1 << i)) { + + t = analog_axes[j]; + x = port->axes[i]; + y = (port->axes[0] + port->axes[1]) >> 1; + z = y - port->axes[i]; + z = z > 0 ? z : -z; + v = (x >> 3); + w = (x >> 3); + + set_bit(t, analog->dev.absbit); + + if ((i == 2 || i == 3) && (j == 2 || j == 3) && (z > (y >> 3))) + x = y; + + if (analog->mask & ANALOG_SAITEK) { + if (i == 2) x = port->axes[i]; + v = x - (x >> 2); + w = (x >> 4); + } + + analog->dev.absmax[t] = (x << 1) - v; + analog->dev.absmin[t] = v; + analog->dev.absfuzz[t] = port->fuzz; + analog->dev.absflat[t] = w; + + j++; + } + + for (i = j = 0; i < 3; i++) + if (analog->mask & analog_exts[i]) + for (x = 0; x < 2; x++) { + t = analog_hats[j++]; + set_bit(t, analog->dev.absbit); + analog->dev.absmax[t] = 1; + analog->dev.absmin[t] = -1; + } + + for (i = j = 0; i < 4; i++) + if (analog->mask & (0x10 << i)) + set_bit(analog->buttons[j++], analog->dev.keybit); + + if (analog->mask & ANALOG_BTNS_CHF) + for (i = 0; i < 2; i++) + set_bit(analog->buttons[j++], analog->dev.keybit); + + if (analog->mask & ANALOG_HBTN_CHF) + for (i = 0; i < 4; i++) + set_bit(analog->buttons[j++], analog->dev.keybit); + + for (i = 0; i < 4; i++) + if (analog->mask & (ANALOG_BTN_TL << i)) + set_bit(analog_pads[i], analog->dev.keybit); + + analog_decode(analog, port->axes, port->initial, port->buttons); + + input_register_device(&analog->dev); + + printk(KERN_INFO "input: %s at %s", analog->name, port->gameport->phys); + + if (port->cooked) + printk(" [ADC port]\n"); + else + printk(" [%s timer, %d %sHz clock, %d ns res]\n", TIME_NAME, + port->speed > 10000 ? (port->speed + 800) / 1000 : port->speed, + port->speed > 10000 ? "M" : "k", + port->speed > 10000 ? (port->loop * 1000) / (port->speed / 1000) + : (port->loop * 1000000) / port->speed); +} + +/* + * analog_init_devices() sets up device-specific values and registers the input devices. + */ + +static int analog_init_masks(struct analog_port *port) +{ + int i; + struct analog *analog = port->analog; + int max[4]; + + if (!port->mask) + return -1; + + if ((port->mask & 3) != 3 && port->mask != 0xc) { + printk(KERN_WARNING "analog.c: Unknown joystick device found " + "(data=%#x, %s), probably not analog joystick.\n", + port->mask, port->gameport->phys); + return -1; + } + + + i = analog_options[0]; /* FIXME !!! - need to specify options for different ports */ + + analog[0].mask = i & 0xfffff; + + analog[0].mask &= ~(ANALOG_AXES_STD | ANALOG_HAT_FCS | ANALOG_BTNS_GAMEPAD) + | port->mask | ((port->mask << 8) & ANALOG_HAT_FCS) + | ((port->mask << 10) & ANALOG_BTNS_TLR) | ((port->mask << 12) & ANALOG_BTNS_TLR2); + + analog[0].mask &= ~(ANALOG_HAT2_CHF) + | ((analog[0].mask & ANALOG_HBTN_CHF) ? 0 : ANALOG_HAT2_CHF); + + analog[0].mask &= ~(ANALOG_THROTTLE | ANALOG_BTN_TR | ANALOG_BTN_TR2) + | ((~analog[0].mask & ANALOG_HAT_FCS) >> 8) + | ((~analog[0].mask & ANALOG_HAT_FCS) << 2) + | ((~analog[0].mask & ANALOG_HAT_FCS) << 4); + + analog[0].mask &= ~(ANALOG_THROTTLE | ANALOG_RUDDER) + | (((~analog[0].mask & ANALOG_BTNS_TLR ) >> 10) + & ((~analog[0].mask & ANALOG_BTNS_TLR2) >> 12)); + + analog[1].mask = ((i >> 20) & 0xff) | ((i >> 12) & 0xf0000); + + analog[1].mask &= (analog[0].mask & ANALOG_EXTENSIONS) ? ANALOG_GAMEPAD + : (((ANALOG_BTNS_STD | port->mask) & ~analog[0].mask) | ANALOG_GAMEPAD); + + if (port->cooked) { + + for (i = 0; i < 4; i++) max[i] = port->axes[i] << 1; + + if ((analog[0].mask & 0x7) == 0x7) max[2] = (max[0] + max[1]) >> 1; + if ((analog[0].mask & 0xb) == 0xb) max[3] = (max[0] + max[1]) >> 1; + if ((analog[0].mask & ANALOG_BTN_TL) && !(analog[0].mask & ANALOG_BTN_TL2)) max[2] >>= 1; + if ((analog[0].mask & ANALOG_BTN_TR) && !(analog[0].mask & ANALOG_BTN_TR2)) max[3] >>= 1; + if ((analog[0].mask & ANALOG_HAT_FCS)) max[3] >>= 1; + + gameport_calibrate(port->gameport, port->axes, max); + } + + for (i = 0; i < 4; i++) + port->initial[i] = port->axes[i]; + + return -!(analog[0].mask || analog[1].mask); +} + +static int analog_init_port(struct gameport *gameport, struct gameport_driver *drv, struct analog_port *port) +{ + int i, t, u, v; + + port->gameport = gameport; + + gameport_set_drvdata(gameport, port); + + if (!gameport_open(gameport, drv, GAMEPORT_MODE_RAW)) { + + analog_calibrate_timer(port); + + gameport_trigger(gameport); + t = gameport_read(gameport); + msleep(ANALOG_MAX_TIME); + port->mask = (gameport_read(gameport) ^ t) & t & 0xf; + port->fuzz = (port->speed * ANALOG_FUZZ_MAGIC) / port->loop / 1000 + ANALOG_FUZZ_BITS; + + for (i = 0; i < ANALOG_INIT_RETRIES; i++) { + if (!analog_cooked_read(port)) + break; + msleep(ANALOG_MAX_TIME); + } + + u = v = 0; + + msleep(ANALOG_MAX_TIME); + t = gameport_time(gameport, ANALOG_MAX_TIME * 1000); + gameport_trigger(gameport); + while ((gameport_read(port->gameport) & port->mask) && (u < t)) + u++; + udelay(ANALOG_SAITEK_DELAY); + t = gameport_time(gameport, ANALOG_SAITEK_TIME); + gameport_trigger(gameport); + while ((gameport_read(port->gameport) & port->mask) && (v < t)) + v++; + + if (v < (u >> 1)) { /* FIXME - more than one port */ + analog_options[0] |= /* FIXME - more than one port */ + ANALOG_SAITEK | ANALOG_BTNS_CHF | ANALOG_HBTN_CHF | ANALOG_HAT1_CHF; + return 0; + } + + gameport_close(gameport); + } + + if (!gameport_open(gameport, drv, GAMEPORT_MODE_COOKED)) { + + for (i = 0; i < ANALOG_INIT_RETRIES; i++) + if (!gameport_cooked_read(gameport, port->axes, &port->buttons)) + break; + for (i = 0; i < 4; i++) + if (port->axes[i] != -1) + port->mask |= 1 << i; + + port->fuzz = gameport->fuzz; + port->cooked = 1; + return 0; + } + + return gameport_open(gameport, drv, GAMEPORT_MODE_RAW); +} + +static int analog_connect(struct gameport *gameport, struct gameport_driver *drv) +{ + struct analog_port *port; + int i; + int err; + + if (!(port = kcalloc(1, sizeof(struct analog_port), GFP_KERNEL))) + return - ENOMEM; + + err = analog_init_port(gameport, drv, port); + if (err) { + kfree(port); + return err; + } + + err = analog_init_masks(port); + if (err) { + gameport_close(gameport); + gameport_set_drvdata(gameport, NULL); + kfree(port); + return err; + } + + gameport_set_poll_handler(gameport, analog_poll); + gameport_set_poll_interval(gameport, 10); + + for (i = 0; i < 2; i++) + if (port->analog[i].mask) + analog_init_device(port, port->analog + i, i); + + return 0; +} + +static void analog_disconnect(struct gameport *gameport) +{ + int i; + struct analog_port *port = gameport_get_drvdata(gameport); + + for (i = 0; i < 2; i++) + if (port->analog[i].mask) + input_unregister_device(&port->analog[i].dev); + gameport_close(gameport); + gameport_set_drvdata(gameport, NULL); + printk(KERN_INFO "analog.c: %d out of %d reads (%d%%) on %s failed\n", + port->bads, port->reads, port->reads ? (port->bads * 100 / port->reads) : 0, + port->gameport->phys); + kfree(port); +} + +struct analog_types { + char *name; + int value; +}; + +static struct analog_types analog_types[] = { + { "none", 0x00000000 }, + { "auto", 0x000000ff }, + { "2btn", 0x0000003f }, + { "y-joy", 0x0cc00033 }, + { "y-pad", 0x8cc80033 }, + { "fcs", 0x000008f7 }, + { "chf", 0x000002ff }, + { "fullchf", 0x000007ff }, + { "gamepad", 0x000830f3 }, + { "gamepad8", 0x0008f0f3 }, + { NULL, 0 } +}; + +static void analog_parse_options(void) +{ + int i, j; + char *end; + + for (i = 0; i < js_nargs; i++) { + + for (j = 0; analog_types[j].name; j++) + if (!strcmp(analog_types[j].name, js[i])) { + analog_options[i] = analog_types[j].value; + break; + } + if (analog_types[j].name) continue; + + analog_options[i] = simple_strtoul(js[i], &end, 0); + if (end != js[i]) continue; + + analog_options[i] = 0xff; + if (!strlen(js[i])) continue; + + printk(KERN_WARNING "analog.c: Bad config for port %d - \"%s\"\n", i, js[i]); + } + + for (; i < ANALOG_PORTS; i++) + analog_options[i] = 0xff; +} + +/* + * The gameport device structure. + */ + +static struct gameport_driver analog_drv = { + .driver = { + .name = "analog", + }, + .description = DRIVER_DESC, + .connect = analog_connect, + .disconnect = analog_disconnect, +}; + +static int __init analog_init(void) +{ + analog_parse_options(); + gameport_register_driver(&analog_drv); + + return 0; +} + +static void __exit analog_exit(void) +{ + gameport_unregister_driver(&analog_drv); +} + +module_init(analog_init); +module_exit(analog_exit); diff --git a/drivers/input/joystick/cobra.c b/drivers/input/joystick/cobra.c new file mode 100644 index 000000000000..a6002205328f --- /dev/null +++ b/drivers/input/joystick/cobra.c @@ -0,0 +1,264 @@ +/* + * $Id: cobra.c,v 1.19 2002/01/22 20:26:52 vojtech Exp $ + * + * Copyright (c) 1999-2001 Vojtech Pavlik + */ + +/* + * Creative Labs Blaster GamePad Cobra driver for Linux + */ + +/* + * 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 either by + * e-mail - mail your message to <vojtech@ucw.cz>, or by paper mail: + * Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/gameport.h> +#include <linux/input.h> + +#define DRIVER_DESC "Creative Labs Blaster GamePad Cobra driver" + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +#define COBRA_MAX_STROBE 45 /* 45 us max wait for first strobe */ +#define COBRA_LENGTH 36 + +static char* cobra_name = "Creative Labs Blaster GamePad Cobra"; + +static int cobra_btn[] = { BTN_START, BTN_SELECT, BTN_TL, BTN_TR, BTN_X, BTN_Y, BTN_Z, BTN_A, BTN_B, BTN_C, BTN_TL2, BTN_TR2, 0 }; + +struct cobra { + struct gameport *gameport; + struct input_dev dev[2]; + int reads; + int bads; + unsigned char exists; + char phys[2][32]; +}; + +static unsigned char cobra_read_packet(struct gameport *gameport, unsigned int *data) +{ + unsigned long flags; + unsigned char u, v, w; + __u64 buf[2]; + int r[2], t[2]; + int i, j, ret; + + int strobe = gameport_time(gameport, COBRA_MAX_STROBE); + + for (i = 0; i < 2; i++) { + r[i] = buf[i] = 0; + t[i] = COBRA_MAX_STROBE; + } + + local_irq_save(flags); + + u = gameport_read(gameport); + + do { + t[0]--; t[1]--; + v = gameport_read(gameport); + for (i = 0, w = u ^ v; i < 2 && w; i++, w >>= 2) + if (w & 0x30) { + if ((w & 0x30) < 0x30 && r[i] < COBRA_LENGTH && t[i] > 0) { + buf[i] |= (__u64)((w >> 5) & 1) << r[i]++; + t[i] = strobe; + u = v; + } else t[i] = 0; + } + } while (t[0] > 0 || t[1] > 0); + + local_irq_restore(flags); + + ret = 0; + + for (i = 0; i < 2; i++) { + + if (r[i] != COBRA_LENGTH) continue; + + for (j = 0; j < COBRA_LENGTH && (buf[i] & 0x04104107f) ^ 0x041041040; j++) + buf[i] = (buf[i] >> 1) | ((__u64)(buf[i] & 1) << (COBRA_LENGTH - 1)); + + if (j < COBRA_LENGTH) ret |= (1 << i); + + data[i] = ((buf[i] >> 7) & 0x000001f) | ((buf[i] >> 8) & 0x00003e0) + | ((buf[i] >> 9) & 0x0007c00) | ((buf[i] >> 10) & 0x00f8000) + | ((buf[i] >> 11) & 0x1f00000); + + } + + return ret; +} + +static void cobra_poll(struct gameport *gameport) +{ + struct cobra *cobra = gameport_get_drvdata(gameport); + struct input_dev *dev; + unsigned int data[2]; + int i, j, r; + + cobra->reads++; + + if ((r = cobra_read_packet(gameport, data)) != cobra->exists) { + cobra->bads++; + return; + } + + for (i = 0; i < 2; i++) + if (cobra->exists & r & (1 << i)) { + + dev = cobra->dev + i; + + input_report_abs(dev, ABS_X, ((data[i] >> 4) & 1) - ((data[i] >> 3) & 1)); + input_report_abs(dev, ABS_Y, ((data[i] >> 2) & 1) - ((data[i] >> 1) & 1)); + + for (j = 0; cobra_btn[j]; j++) + input_report_key(dev, cobra_btn[j], data[i] & (0x20 << j)); + + input_sync(dev); + + } +} + +static int cobra_open(struct input_dev *dev) +{ + struct cobra *cobra = dev->private; + + gameport_start_polling(cobra->gameport); + return 0; +} + +static void cobra_close(struct input_dev *dev) +{ + struct cobra *cobra = dev->private; + + gameport_stop_polling(cobra->gameport); +} + +static int cobra_connect(struct gameport *gameport, struct gameport_driver *drv) +{ + struct cobra *cobra; + unsigned int data[2]; + int i, j; + int err; + + if (!(cobra = kcalloc(1, sizeof(struct cobra), GFP_KERNEL))) + return -ENOMEM; + + cobra->gameport = gameport; + + gameport_set_drvdata(gameport, cobra); + + err = gameport_open(gameport, drv, GAMEPORT_MODE_RAW); + if (err) + goto fail1; + + cobra->exists = cobra_read_packet(gameport, data); + + for (i = 0; i < 2; i++) + if ((cobra->exists >> i) & data[i] & 1) { + printk(KERN_WARNING "cobra.c: Device %d on %s has the Ext bit set. ID is: %d" + " Contact vojtech@ucw.cz\n", i, gameport->phys, (data[i] >> 2) & 7); + cobra->exists &= ~(1 << i); + } + + if (!cobra->exists) { + err = -ENODEV; + goto fail2; + } + + gameport_set_poll_handler(gameport, cobra_poll); + gameport_set_poll_interval(gameport, 20); + + for (i = 0; i < 2; i++) + if ((cobra->exists >> i) & 1) { + + sprintf(cobra->phys[i], "%s/input%d", gameport->phys, i); + + cobra->dev[i].private = cobra; + cobra->dev[i].open = cobra_open; + cobra->dev[i].close = cobra_close; + + cobra->dev[i].name = cobra_name; + cobra->dev[i].phys = cobra->phys[i]; + cobra->dev[i].id.bustype = BUS_GAMEPORT; + cobra->dev[i].id.vendor = GAMEPORT_ID_VENDOR_CREATIVE; + cobra->dev[i].id.product = 0x0008; + cobra->dev[i].id.version = 0x0100; + + cobra->dev[i].evbit[0] = BIT(EV_KEY) | BIT(EV_ABS); + + input_set_abs_params(&cobra->dev[i], ABS_X, -1, 1, 0, 0); + input_set_abs_params(&cobra->dev[i], ABS_Y, -1, 1, 0, 0); + + for (j = 0; cobra_btn[j]; j++) + set_bit(cobra_btn[j], cobra->dev[i].keybit); + + input_register_device(&cobra->dev[i]); + printk(KERN_INFO "input: %s on %s\n", cobra_name, gameport->phys); + } + + return 0; + +fail2: gameport_close(gameport); +fail1: gameport_set_drvdata(gameport, NULL); + kfree(cobra); + return err; +} + +static void cobra_disconnect(struct gameport *gameport) +{ + struct cobra *cobra = gameport_get_drvdata(gameport); + int i; + + for (i = 0; i < 2; i++) + if ((cobra->exists >> i) & 1) + input_unregister_device(cobra->dev + i); + gameport_close(gameport); + gameport_set_drvdata(gameport, NULL); + kfree(cobra); +} + +static struct gameport_driver cobra_drv = { + .driver = { + .name = "cobra", + }, + .description = DRIVER_DESC, + .connect = cobra_connect, + .disconnect = cobra_disconnect, +}; + +static int __init cobra_init(void) +{ + gameport_register_driver(&cobra_drv); + return 0; +} + +static void __exit cobra_exit(void) +{ + gameport_unregister_driver(&cobra_drv); +} + +module_init(cobra_init); +module_exit(cobra_exit); diff --git a/drivers/input/joystick/db9.c b/drivers/input/joystick/db9.c new file mode 100644 index 000000000000..cfdd3acf06a1 --- /dev/null +++ b/drivers/input/joystick/db9.c @@ -0,0 +1,647 @@ +/* + * $Id: db9.c,v 1.13 2002/04/07 20:13:37 vojtech Exp $ + * + * Copyright (c) 1999-2001 Vojtech Pavlik + * + * Based on the work of: + * Andree Borrmann Mats Sjövall + */ + +/* + * Atari, Amstrad, Commodore, Amiga, Sega, etc. joystick driver for Linux + */ + +/* + * 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 either by + * e-mail - mail your message to <vojtech@ucw.cz>, or by paper mail: + * Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/parport.h> +#include <linux/input.h> + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION("Atari, Amstrad, Commodore, Amiga, Sega, etc. joystick driver"); +MODULE_LICENSE("GPL"); + +static int db9[] __initdata = { -1, 0 }; +static int db9_nargs __initdata = 0; +module_param_array_named(dev, db9, int, &db9_nargs, 0); +MODULE_PARM_DESC(dev, "Describes first attached device (<parport#>,<type>)"); + +static int db9_2[] __initdata = { -1, 0 }; +static int db9_nargs_2 __initdata = 0; +module_param_array_named(dev2, db9_2, int, &db9_nargs_2, 0); +MODULE_PARM_DESC(dev2, "Describes second attached device (<parport#>,<type>)"); + +static int db9_3[] __initdata = { -1, 0 }; +static int db9_nargs_3 __initdata = 0; +module_param_array_named(dev3, db9_3, int, &db9_nargs_3, 0); +MODULE_PARM_DESC(dev3, "Describes third attached device (<parport#>,<type>)"); + +__obsolete_setup("db9="); +__obsolete_setup("db9_2="); +__obsolete_setup("db9_3="); + +#define DB9_MULTI_STICK 0x01 +#define DB9_MULTI2_STICK 0x02 +#define DB9_GENESIS_PAD 0x03 +#define DB9_GENESIS5_PAD 0x05 +#define DB9_GENESIS6_PAD 0x06 +#define DB9_SATURN_PAD 0x07 +#define DB9_MULTI_0802 0x08 +#define DB9_MULTI_0802_2 0x09 +#define DB9_CD32_PAD 0x0A +#define DB9_SATURN_DPP 0x0B +#define DB9_SATURN_DPP_2 0x0C +#define DB9_MAX_PAD 0x0D + +#define DB9_UP 0x01 +#define DB9_DOWN 0x02 +#define DB9_LEFT 0x04 +#define DB9_RIGHT 0x08 +#define DB9_FIRE1 0x10 +#define DB9_FIRE2 0x20 +#define DB9_FIRE3 0x40 +#define DB9_FIRE4 0x80 + +#define DB9_NORMAL 0x0a +#define DB9_NOSELECT 0x08 + +#define DB9_MAX_DEVICES 2 + +#define DB9_GENESIS6_DELAY 14 +#define DB9_REFRESH_TIME HZ/100 + +struct db9 { + struct input_dev dev[DB9_MAX_DEVICES]; + struct timer_list timer; + struct pardevice *pd; + int mode; + int used; + char phys[2][32]; +}; + +static struct db9 *db9_base[3]; + +static short db9_multi_btn[] = { BTN_TRIGGER, BTN_THUMB }; +static short db9_genesis_btn[] = { BTN_START, BTN_A, BTN_B, BTN_C, BTN_X, BTN_Y, BTN_Z, BTN_MODE }; +static short db9_cd32_btn[] = { BTN_A, BTN_B, BTN_C, BTN_X, BTN_Y, BTN_Z, BTN_TL, BTN_TR, BTN_START }; + +static char db9_buttons[DB9_MAX_PAD] = { 0, 1, 2, 4, 0, 6, 8, 9, 1, 1, 7, 9, 9 }; +static short *db9_btn[DB9_MAX_PAD] = { NULL, db9_multi_btn, db9_multi_btn, db9_genesis_btn, NULL, db9_genesis_btn, + db9_genesis_btn, db9_cd32_btn, db9_multi_btn, db9_multi_btn, db9_cd32_btn, + db9_cd32_btn, db9_cd32_btn }; +static char *db9_name[DB9_MAX_PAD] = { NULL, "Multisystem joystick", "Multisystem joystick (2 fire)", "Genesis pad", + NULL, "Genesis 5 pad", "Genesis 6 pad", "Saturn pad", "Multisystem (0.8.0.2) joystick", + "Multisystem (0.8.0.2-dual) joystick", "Amiga CD-32 pad", "Saturn dpp", "Saturn dpp dual" }; + +static const int db9_max_pads[DB9_MAX_PAD] = { 0, 1, 1, 1, 0, 1, 1, 6, 1, 2, 1, 6, 12 }; +static const int db9_num_axis[DB9_MAX_PAD] = { 0, 2, 2, 2, 0, 2, 2, 7, 2, 2, 2 ,7, 7 }; +static const short db9_abs[] = { ABS_X, ABS_Y, ABS_RX, ABS_RY, ABS_RZ, ABS_Z, ABS_HAT0X, ABS_HAT0Y, ABS_HAT1X, ABS_HAT1Y }; +static const int db9_bidirectional[DB9_MAX_PAD] = { 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0 }; +static const int db9_reverse[DB9_MAX_PAD] = { 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0 }; + +/* + * Saturn controllers + */ +#define DB9_SATURN_DELAY 300 +static const int db9_saturn_byte[] = { 1, 1, 1, 2, 2, 2, 2, 2, 1 }; +static const unsigned char db9_saturn_mask[] = { 0x04, 0x01, 0x02, 0x40, 0x20, 0x10, 0x08, 0x80, 0x08 }; + +/* + * db9_saturn_write_sub() writes 2 bit data. + */ +static void db9_saturn_write_sub(struct parport *port, int type, unsigned char data, int powered, int pwr_sub) +{ + unsigned char c; + + switch (type) { + case 1: /* DPP1 */ + c = 0x80 | 0x30 | (powered ? 0x08 : 0) | (pwr_sub ? 0x04 : 0) | data; + parport_write_data(port, c); + break; + case 2: /* DPP2 */ + c = 0x40 | data << 4 | (powered ? 0x08 : 0) | (pwr_sub ? 0x04 : 0) | 0x03; + parport_write_data(port, c); + break; + case 0: /* DB9 */ + c = ((((data & 2) ? 2 : 0) | ((data & 1) ? 4 : 0)) ^ 0x02) | !powered; + parport_write_control(port, c); + break; + } +} + +/* + * gc_saturn_read_sub() reads 4 bit data. + */ +static unsigned char db9_saturn_read_sub(struct parport *port, int type) +{ + unsigned char data; + + if (type) { + /* DPP */ + data = parport_read_status(port) ^ 0x80; + return (data & 0x80 ? 1 : 0) | (data & 0x40 ? 2 : 0) + | (data & 0x20 ? 4 : 0) | (data & 0x10 ? 8 : 0); + } else { + /* DB9 */ + data = parport_read_data(port) & 0x0f; + return (data & 0x8 ? 1 : 0) | (data & 0x4 ? 2 : 0) + | (data & 0x2 ? 4 : 0) | (data & 0x1 ? 8 : 0); + } +} + +/* + * db9_saturn_read_analog() sends clock and reads 8 bit data. + */ +static unsigned char db9_saturn_read_analog(struct parport *port, int type, int powered) +{ + unsigned char data; + + db9_saturn_write_sub(port, type, 0, powered, 0); + udelay(DB9_SATURN_DELAY); + data = db9_saturn_read_sub(port, type) << 4; + db9_saturn_write_sub(port, type, 2, powered, 0); + udelay(DB9_SATURN_DELAY); + data |= db9_saturn_read_sub(port, type); + return data; +} + +/* + * db9_saturn_read_packet() reads whole saturn packet at connector + * and returns device identifier code. + */ +static unsigned char db9_saturn_read_packet(struct parport *port, unsigned char *data, int type, int powered) +{ + int i, j; + unsigned char tmp; + + db9_saturn_write_sub(port, type, 3, powered, 0); + data[0] = db9_saturn_read_sub(port, type); + switch (data[0] & 0x0f) { + case 0xf: + /* 1111 no pad */ + return data[0] = 0xff; + case 0x4: case 0x4 | 0x8: + /* ?100 : digital controller */ + db9_saturn_write_sub(port, type, 0, powered, 1); + data[2] = db9_saturn_read_sub(port, type) << 4; + db9_saturn_write_sub(port, type, 2, powered, 1); + data[1] = db9_saturn_read_sub(port, type) << 4; + db9_saturn_write_sub(port, type, 1, powered, 1); + data[1] |= db9_saturn_read_sub(port, type); + db9_saturn_write_sub(port, type, 3, powered, 1); + /* data[2] |= db9_saturn_read_sub(port, type); */ + data[2] |= data[0]; + return data[0] = 0x02; + case 0x1: + /* 0001 : analog controller or multitap */ + db9_saturn_write_sub(port, type, 2, powered, 0); + udelay(DB9_SATURN_DELAY); + data[0] = db9_saturn_read_analog(port, type, powered); + if (data[0] != 0x41) { + /* read analog controller */ + for (i = 0; i < (data[0] & 0x0f); i++) + data[i + 1] = db9_saturn_read_analog(port, type, powered); + db9_saturn_write_sub(port, type, 3, powered, 0); + return data[0]; + } else { + /* read multitap */ + if (db9_saturn_read_analog(port, type, powered) != 0x60) + return data[0] = 0xff; + for (i = 0; i < 60; i += 10) { + data[i] = db9_saturn_read_analog(port, type, powered); + if (data[i] != 0xff) + /* read each pad */ + for (j = 0; j < (data[i] & 0x0f); j++) + data[i + j + 1] = db9_saturn_read_analog(port, type, powered); + } + db9_saturn_write_sub(port, type, 3, powered, 0); + return 0x41; + } + case 0x0: + /* 0000 : mouse */ + db9_saturn_write_sub(port, type, 2, powered, 0); + udelay(DB9_SATURN_DELAY); + tmp = db9_saturn_read_analog(port, type, powered); + if (tmp == 0xff) { + for (i = 0; i < 3; i++) + data[i + 1] = db9_saturn_read_analog(port, type, powered); + db9_saturn_write_sub(port, type, 3, powered, 0); + return data[0] = 0xe3; + } + default: + return data[0]; + } +} + +/* + * db9_saturn_report() analyzes packet and reports. + */ +static int db9_saturn_report(unsigned char id, unsigned char data[60], struct input_dev *dev, int n, int max_pads) +{ + int tmp, i, j; + + tmp = (id == 0x41) ? 60 : 10; + for (j = 0; (j < tmp) && (n < max_pads); j += 10, n++) { + switch (data[j]) { + case 0x16: /* multi controller (analog 4 axis) */ + input_report_abs(dev + n, db9_abs[5], data[j + 6]); + case 0x15: /* mission stick (analog 3 axis) */ + input_report_abs(dev + n, db9_abs[3], data[j + 4]); + input_report_abs(dev + n, db9_abs[4], data[j + 5]); + case 0x13: /* racing controller (analog 1 axis) */ + input_report_abs(dev + n, db9_abs[2], data[j + 3]); + case 0x34: /* saturn keyboard (udlr ZXC ASD QE Esc) */ + case 0x02: /* digital pad (digital 2 axis + buttons) */ + input_report_abs(dev + n, db9_abs[0], !(data[j + 1] & 128) - !(data[j + 1] & 64)); + input_report_abs(dev + n, db9_abs[1], !(data[j + 1] & 32) - !(data[j + 1] & 16)); + for (i = 0; i < 9; i++) + input_report_key(dev + n, db9_cd32_btn[i], ~data[j + db9_saturn_byte[i]] & db9_saturn_mask[i]); + break; + case 0x19: /* mission stick x2 (analog 6 axis + buttons) */ + input_report_abs(dev + n, db9_abs[0], !(data[j + 1] & 128) - !(data[j + 1] & 64)); + input_report_abs(dev + n, db9_abs[1], !(data[j + 1] & 32) - !(data[j + 1] & 16)); + for (i = 0; i < 9; i++) + input_report_key(dev + n, db9_cd32_btn[i], ~data[j + db9_saturn_byte[i]] & db9_saturn_mask[i]); + input_report_abs(dev + n, db9_abs[2], data[j + 3]); + input_report_abs(dev + n, db9_abs[3], data[j + 4]); + input_report_abs(dev + n, db9_abs[4], data[j + 5]); + /* + input_report_abs(dev + n, db9_abs[8], (data[j + 6] & 128 ? 0 : 1) - (data[j + 6] & 64 ? 0 : 1)); + input_report_abs(dev + n, db9_abs[9], (data[j + 6] & 32 ? 0 : 1) - (data[j + 6] & 16 ? 0 : 1)); + */ + input_report_abs(dev + n, db9_abs[6], data[j + 7]); + input_report_abs(dev + n, db9_abs[7], data[j + 8]); + input_report_abs(dev + n, db9_abs[5], data[j + 9]); + break; + case 0xd3: /* sankyo ff (analog 1 axis + stop btn) */ + input_report_key(dev + n, BTN_A, data[j + 3] & 0x80); + input_report_abs(dev + n, db9_abs[2], data[j + 3] & 0x7f); + break; + case 0xe3: /* shuttle mouse (analog 2 axis + buttons. signed value) */ + input_report_key(dev + n, BTN_START, data[j + 1] & 0x08); + input_report_key(dev + n, BTN_A, data[j + 1] & 0x04); + input_report_key(dev + n, BTN_C, data[j + 1] & 0x02); + input_report_key(dev + n, BTN_B, data[j + 1] & 0x01); + input_report_abs(dev + n, db9_abs[2], data[j + 2] ^ 0x80); + input_report_abs(dev + n, db9_abs[3], (0xff-(data[j + 3] ^ 0x80))+1); /* */ + break; + case 0xff: + default: /* no pad */ + input_report_abs(dev + n, db9_abs[0], 0); + input_report_abs(dev + n, db9_abs[1], 0); + for (i = 0; i < 9; i++) + input_report_key(dev + n, db9_cd32_btn[i], 0); + break; + } + } + return n; +} + +static int db9_saturn(int mode, struct parport *port, struct input_dev *dev) +{ + unsigned char id, data[60]; + int type, n, max_pads; + int tmp, i; + + switch (mode) { + case DB9_SATURN_PAD: + type = 0; + n = 1; + break; + case DB9_SATURN_DPP: + type = 1; + n = 1; + break; + case DB9_SATURN_DPP_2: + type = 1; + n = 2; + break; + default: + return -1; + } + max_pads = min(db9_max_pads[mode], DB9_MAX_DEVICES); + for (tmp = 0, i = 0; i < n; i++) { + id = db9_saturn_read_packet(port, data, type + i, 1); + tmp = db9_saturn_report(id, data, dev, tmp, max_pads); + } + return 0; +} + +static void db9_timer(unsigned long private) +{ + struct db9 *db9 = (void *) private; + struct parport *port = db9->pd->port; + struct input_dev *dev = db9->dev; + int data, i; + + switch(db9->mode) { + case DB9_MULTI_0802_2: + + data = parport_read_data(port) >> 3; + + input_report_abs(dev + 1, ABS_X, (data & DB9_RIGHT ? 0 : 1) - (data & DB9_LEFT ? 0 : 1)); + input_report_abs(dev + 1, ABS_Y, (data & DB9_DOWN ? 0 : 1) - (data & DB9_UP ? 0 : 1)); + input_report_key(dev + 1, BTN_TRIGGER, ~data & DB9_FIRE1); + + case DB9_MULTI_0802: + + data = parport_read_status(port) >> 3; + + input_report_abs(dev, ABS_X, (data & DB9_RIGHT ? 0 : 1) - (data & DB9_LEFT ? 0 : 1)); + input_report_abs(dev, ABS_Y, (data & DB9_DOWN ? 0 : 1) - (data & DB9_UP ? 0 : 1)); + input_report_key(dev, BTN_TRIGGER, data & DB9_FIRE1); + break; + + case DB9_MULTI_STICK: + + data = parport_read_data(port); + + input_report_abs(dev, ABS_X, (data & DB9_RIGHT ? 0 : 1) - (data & DB9_LEFT ? 0 : 1)); + input_report_abs(dev, ABS_Y, (data & DB9_DOWN ? 0 : 1) - (data & DB9_UP ? 0 : 1)); + input_report_key(dev, BTN_TRIGGER, ~data & DB9_FIRE1); + break; + + case DB9_MULTI2_STICK: + + data = parport_read_data(port); + + input_report_abs(dev, ABS_X, (data & DB9_RIGHT ? 0 : 1) - (data & DB9_LEFT ? 0 : 1)); + input_report_abs(dev, ABS_Y, (data & DB9_DOWN ? 0 : 1) - (data & DB9_UP ? 0 : 1)); + input_report_key(dev, BTN_TRIGGER, ~data & DB9_FIRE1); + input_report_key(dev, BTN_THUMB, ~data & DB9_FIRE2); + break; + + case DB9_GENESIS_PAD: + + parport_write_control(port, DB9_NOSELECT); + data = parport_read_data(port); + + input_report_abs(dev, ABS_X, (data & DB9_RIGHT ? 0 : 1) - (data & DB9_LEFT ? 0 : 1)); + input_report_abs(dev, ABS_Y, (data & DB9_DOWN ? 0 : 1) - (data & DB9_UP ? 0 : 1)); + input_report_key(dev, BTN_B, ~data & DB9_FIRE1); + input_report_key(dev, BTN_C, ~data & DB9_FIRE2); + + parport_write_control(port, DB9_NORMAL); + data=parport_read_data(port); + + input_report_key(dev, BTN_A, ~data & DB9_FIRE1); + input_report_key(dev, BTN_START, ~data & DB9_FIRE2); + break; + + case DB9_GENESIS5_PAD: + + parport_write_control(port, DB9_NOSELECT); + data=parport_read_data(port); + + input_report_abs(dev, ABS_X, (data & DB9_RIGHT ? 0 : 1) - (data & DB9_LEFT ? 0 : 1)); + input_report_abs(dev, ABS_Y, (data & DB9_DOWN ? 0 : 1) - (data & DB9_UP ? 0 : 1)); + input_report_key(dev, BTN_B, ~data & DB9_FIRE1); + input_report_key(dev, BTN_C, ~data & DB9_FIRE2); + + parport_write_control(port, DB9_NORMAL); + data=parport_read_data(port); + + input_report_key(dev, BTN_A, ~data & DB9_FIRE1); + input_report_key(dev, BTN_X, ~data & DB9_FIRE2); + input_report_key(dev, BTN_Y, ~data & DB9_LEFT); + input_report_key(dev, BTN_START, ~data & DB9_RIGHT); + break; + + case DB9_GENESIS6_PAD: + + parport_write_control(port, DB9_NOSELECT); /* 1 */ + udelay(DB9_GENESIS6_DELAY); + data=parport_read_data(port); + + input_report_abs(dev, ABS_X, (data & DB9_RIGHT ? 0 : 1) - (data & DB9_LEFT ? 0 : 1)); + input_report_abs(dev, ABS_Y, (data & DB9_DOWN ? 0 : 1) - (data & DB9_UP ? 0 : 1)); + input_report_key(dev, BTN_B, ~data & DB9_FIRE1); + input_report_key(dev, BTN_C, ~data & DB9_FIRE2); + + parport_write_control(port, DB9_NORMAL); + udelay(DB9_GENESIS6_DELAY); + data=parport_read_data(port); + + input_report_key(dev, BTN_A, ~data & DB9_FIRE1); + input_report_key(dev, BTN_START, ~data & DB9_FIRE2); + + parport_write_control(port, DB9_NOSELECT); /* 2 */ + udelay(DB9_GENESIS6_DELAY); + parport_write_control(port, DB9_NORMAL); + udelay(DB9_GENESIS6_DELAY); + parport_write_control(port, DB9_NOSELECT); /* 3 */ + udelay(DB9_GENESIS6_DELAY); + data=parport_read_data(port); + + input_report_key(dev, BTN_X, ~data & DB9_LEFT); + input_report_key(dev, BTN_Y, ~data & DB9_DOWN); + input_report_key(dev, BTN_Z, ~data & DB9_UP); + input_report_key(dev, BTN_MODE, ~data & DB9_RIGHT); + + parport_write_control(port, DB9_NORMAL); + udelay(DB9_GENESIS6_DELAY); + parport_write_control(port, DB9_NOSELECT); /* 4 */ + udelay(DB9_GENESIS6_DELAY); + parport_write_control(port, DB9_NORMAL); + break; + + case DB9_SATURN_PAD: + case DB9_SATURN_DPP: + case DB9_SATURN_DPP_2: + + db9_saturn(db9->mode, port, dev); + break; + + case DB9_CD32_PAD: + + data=parport_read_data(port); + + input_report_abs(dev, ABS_X, (data & DB9_RIGHT ? 0 : 1) - (data & DB9_LEFT ? 0 : 1)); + input_report_abs(dev, ABS_Y, (data & DB9_DOWN ? 0 : 1) - (data & DB9_UP ? 0 : 1)); + + parport_write_control(port, 0x0a); + + for (i = 0; i < 7; i++) { + data = parport_read_data(port); + parport_write_control(port, 0x02); + parport_write_control(port, 0x0a); + input_report_key(dev, db9_cd32_btn[i], ~data & DB9_FIRE2); + } + + parport_write_control(port, 0x00); + break; + } + + input_sync(dev); + + mod_timer(&db9->timer, jiffies + DB9_REFRESH_TIME); +} + +static int db9_open(struct input_dev *dev) +{ + struct db9 *db9 = dev->private; + struct parport *port = db9->pd->port; + + if (!db9->used++) { + parport_claim(db9->pd); + parport_write_data(port, 0xff); + if (db9_reverse[db9->mode]) { + parport_data_reverse(port); + parport_write_control(port, DB9_NORMAL); + } + mod_timer(&db9->timer, jiffies + DB9_REFRESH_TIME); + } + + return 0; +} + +static void db9_close(struct input_dev *dev) +{ + struct db9 *db9 = dev->private; + struct parport *port = db9->pd->port; + + if (!--db9->used) { + del_timer(&db9->timer); + parport_write_control(port, 0x00); + parport_data_forward(port); + parport_release(db9->pd); + } +} + +static struct db9 __init *db9_probe(int *config, int nargs) +{ + struct db9 *db9; + struct parport *pp; + int i, j; + + if (config[0] < 0) + return NULL; + + if (nargs < 2) { + printk(KERN_ERR "db9.c: Device type must be specified.\n"); + return NULL; + } + + if (config[1] < 1 || config[1] >= DB9_MAX_PAD || !db9_buttons[config[1]]) { + printk(KERN_ERR "db9.c: bad config\n"); + return NULL; + } + + pp = parport_find_number(config[0]); + if (!pp) { + printk(KERN_ERR "db9.c: no such parport\n"); + return NULL; + } + + if (db9_bidirectional[config[1]]) { + if (!(pp->modes & PARPORT_MODE_TRISTATE)) { + printk(KERN_ERR "db9.c: specified parport is not bidirectional\n"); + parport_put_port(pp); + return NULL; + } + } + + if (!(db9 = kmalloc(sizeof(struct db9), GFP_KERNEL))) { + parport_put_port(pp); + return NULL; + } + memset(db9, 0, sizeof(struct db9)); + + db9->mode = config[1]; + init_timer(&db9->timer); + db9->timer.data = (long) db9; + db9->timer.function = db9_timer; + + db9->pd = parport_register_device(pp, "db9", NULL, NULL, NULL, PARPORT_DEV_EXCL, NULL); + parport_put_port(pp); + + if (!db9->pd) { + printk(KERN_ERR "db9.c: parport busy already - lp.o loaded?\n"); + kfree(db9); + return NULL; + } + + for (i = 0; i < (min(db9_max_pads[db9->mode], DB9_MAX_DEVICES)); i++) { + + sprintf(db9->phys[i], "%s/input%d", db9->pd->port->name, i); + + db9->dev[i].private = db9; + db9->dev[i].open = db9_open; + db9->dev[i].close = db9_close; + + db9->dev[i].name = db9_name[db9->mode]; + db9->dev[i].phys = db9->phys[i]; + db9->dev[i].id.bustype = BUS_PARPORT; + db9->dev[i].id.vendor = 0x0002; + db9->dev[i].id.product = config[1]; + db9->dev[i].id.version = 0x0100; + + db9->dev[i].evbit[0] = BIT(EV_KEY) | BIT(EV_ABS); + for (j = 0; j < db9_buttons[db9->mode]; j++) + set_bit(db9_btn[db9->mode][j], db9->dev[i].keybit); + for (j = 0; j < db9_num_axis[db9->mode]; j++) { + set_bit(db9_abs[j], db9->dev[i].absbit); + if (j < 2) { + db9->dev[i].absmin[db9_abs[j]] = -1; + db9->dev[i].absmax[db9_abs[j]] = 1; + } else { + db9->dev[i].absmin[db9_abs[j]] = 1; + db9->dev[i].absmax[db9_abs[j]] = 255; + db9->dev[i].absflat[db9_abs[j]] = 0; + } + } + input_register_device(db9->dev + i); + printk(KERN_INFO "input: %s on %s\n", db9->dev[i].name, db9->pd->port->name); + } + + return db9; +} + +static int __init db9_init(void) +{ + db9_base[0] = db9_probe(db9, db9_nargs); + db9_base[1] = db9_probe(db9_2, db9_nargs_2); + db9_base[2] = db9_probe(db9_3, db9_nargs_3); + + if (db9_base[0] || db9_base[1] || db9_base[2]) + return 0; + + return -ENODEV; +} + +static void __exit db9_exit(void) +{ + int i, j; + + for (i = 0; i < 3; i++) + if (db9_base[i]) { + for (j = 0; j < min(db9_max_pads[db9_base[i]->mode], DB9_MAX_DEVICES); j++) + input_unregister_device(db9_base[i]->dev + j); + parport_unregister_device(db9_base[i]->pd); + } +} + +module_init(db9_init); +module_exit(db9_exit); diff --git a/drivers/input/joystick/gamecon.c b/drivers/input/joystick/gamecon.c new file mode 100644 index 000000000000..8732f52bdd08 --- /dev/null +++ b/drivers/input/joystick/gamecon.c @@ -0,0 +1,697 @@ +/* + * NES, SNES, N64, MultiSystem, PSX gamepad driver for Linux + * + * Copyright (c) 1999-2004 Vojtech Pavlik <vojtech@suse.cz> + * Copyright (c) 2004 Peter Nelson <rufus-kernel@hackish.org> + * + * Based on the work of: + * Andree Borrmann John Dahlstrom + * David Kuder Nathan Hand + */ + +/* + * 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 either by + * e-mail - mail your message to <vojtech@ucw.cz>, or by paper mail: + * Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic + */ + +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/parport.h> +#include <linux/input.h> + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION("NES, SNES, N64, MultiSystem, PSX gamepad driver"); +MODULE_LICENSE("GPL"); + +static int gc[] __initdata = { -1, 0, 0, 0, 0, 0 }; +static int gc_nargs __initdata = 0; +module_param_array_named(map, gc, int, &gc_nargs, 0); +MODULE_PARM_DESC(map, "Describers first set of devices (<parport#>,<pad1>,<pad2>,..<pad5>)"); + +static int gc_2[] __initdata = { -1, 0, 0, 0, 0, 0 }; +static int gc_nargs_2 __initdata = 0; +module_param_array_named(map2, gc_2, int, &gc_nargs_2, 0); +MODULE_PARM_DESC(map2, "Describers second set of devices"); + +static int gc_3[] __initdata = { -1, 0, 0, 0, 0, 0 }; +static int gc_nargs_3 __initdata = 0; +module_param_array_named(map3, gc_3, int, &gc_nargs_3, 0); +MODULE_PARM_DESC(map3, "Describers third set of devices"); + +__obsolete_setup("gc="); +__obsolete_setup("gc_2="); +__obsolete_setup("gc_3="); + +/* see also gs_psx_delay parameter in PSX support section */ + +#define GC_SNES 1 +#define GC_NES 2 +#define GC_NES4 3 +#define GC_MULTI 4 +#define GC_MULTI2 5 +#define GC_N64 6 +#define GC_PSX 7 +#define GC_DDR 8 + +#define GC_MAX 8 + +#define GC_REFRESH_TIME HZ/100 + +struct gc { + struct pardevice *pd; + struct input_dev dev[5]; + struct timer_list timer; + unsigned char pads[GC_MAX + 1]; + int used; + char phys[5][32]; +}; + +static struct gc *gc_base[3]; + +static int gc_status_bit[] = { 0x40, 0x80, 0x20, 0x10, 0x08 }; + +static char *gc_names[] = { NULL, "SNES pad", "NES pad", "NES FourPort", "Multisystem joystick", + "Multisystem 2-button joystick", "N64 controller", "PSX controller", + "PSX DDR controller" }; +/* + * N64 support. + */ + +static unsigned char gc_n64_bytes[] = { 0, 1, 13, 15, 14, 12, 10, 11, 2, 3 }; +static short gc_n64_btn[] = { BTN_A, BTN_B, BTN_C, BTN_X, BTN_Y, BTN_Z, BTN_TL, BTN_TR, BTN_TRIGGER, BTN_START }; + +#define GC_N64_LENGTH 32 /* N64 bit length, not including stop bit */ +#define GC_N64_REQUEST_LENGTH 37 /* transmit request sequence is 9 bits long */ +#define GC_N64_DELAY 133 /* delay between transmit request, and response ready (us) */ +#define GC_N64_REQUEST 0x1dd1111111ULL /* the request data command (encoded for 000000011) */ +#define GC_N64_DWS 3 /* delay between write segments (required for sound playback because of ISA DMA) */ + /* GC_N64_DWS > 24 is known to fail */ +#define GC_N64_POWER_W 0xe2 /* power during write (transmit request) */ +#define GC_N64_POWER_R 0xfd /* power during read */ +#define GC_N64_OUT 0x1d /* output bits to the 4 pads */ + /* Reading the main axes of any N64 pad is known to fail if the corresponding bit */ + /* in GC_N64_OUT is pulled low on the output port (by any routine) for more */ + /* than 123 us */ +#define GC_N64_CLOCK 0x02 /* clock bits for read */ + +/* + * gc_n64_read_packet() reads an N64 packet. + * Each pad uses one bit per byte. So all pads connected to this port are read in parallel. + */ + +static void gc_n64_read_packet(struct gc *gc, unsigned char *data) +{ + int i; + unsigned long flags; + +/* + * Request the pad to transmit data + */ + + local_irq_save(flags); + for (i = 0; i < GC_N64_REQUEST_LENGTH; i++) { + parport_write_data(gc->pd->port, GC_N64_POWER_W | ((GC_N64_REQUEST >> i) & 1 ? GC_N64_OUT : 0)); + udelay(GC_N64_DWS); + } + local_irq_restore(flags); + +/* + * Wait for the pad response to be loaded into the 33-bit register of the adapter + */ + + udelay(GC_N64_DELAY); + +/* + * Grab data (ignoring the last bit, which is a stop bit) + */ + + for (i = 0; i < GC_N64_LENGTH; i++) { + parport_write_data(gc->pd->port, GC_N64_POWER_R); + data[i] = parport_read_status(gc->pd->port); + parport_write_data(gc->pd->port, GC_N64_POWER_R | GC_N64_CLOCK); + } + +/* + * We must wait 200 ms here for the controller to reinitialize before the next read request. + * No worries as long as gc_read is polled less frequently than this. + */ + +} + +/* + * NES/SNES support. + */ + +#define GC_NES_DELAY 6 /* Delay between bits - 6us */ +#define GC_NES_LENGTH 8 /* The NES pads use 8 bits of data */ +#define GC_SNES_LENGTH 12 /* The SNES true length is 16, but the last 4 bits are unused */ + +#define GC_NES_POWER 0xfc +#define GC_NES_CLOCK 0x01 +#define GC_NES_LATCH 0x02 + +static unsigned char gc_nes_bytes[] = { 0, 1, 2, 3 }; +static unsigned char gc_snes_bytes[] = { 8, 0, 2, 3, 9, 1, 10, 11 }; +static short gc_snes_btn[] = { BTN_A, BTN_B, BTN_SELECT, BTN_START, BTN_X, BTN_Y, BTN_TL, BTN_TR }; + +/* + * gc_nes_read_packet() reads a NES/SNES packet. + * Each pad uses one bit per byte. So all pads connected to + * this port are read in parallel. + */ + +static void gc_nes_read_packet(struct gc *gc, int length, unsigned char *data) +{ + int i; + + parport_write_data(gc->pd->port, GC_NES_POWER | GC_NES_CLOCK | GC_NES_LATCH); + udelay(GC_NES_DELAY * 2); + parport_write_data(gc->pd->port, GC_NES_POWER | GC_NES_CLOCK); + + for (i = 0; i < length; i++) { + udelay(GC_NES_DELAY); + parport_write_data(gc->pd->port, GC_NES_POWER); + data[i] = parport_read_status(gc->pd->port) ^ 0x7f; + udelay(GC_NES_DELAY); + parport_write_data(gc->pd->port, GC_NES_POWER | GC_NES_CLOCK); + } +} + +/* + * Multisystem joystick support + */ + +#define GC_MULTI_LENGTH 5 /* Multi system joystick packet length is 5 */ +#define GC_MULTI2_LENGTH 6 /* One more bit for one more button */ + +/* + * gc_multi_read_packet() reads a Multisystem joystick packet. + */ + +static void gc_multi_read_packet(struct gc *gc, int length, unsigned char *data) +{ + int i; + + for (i = 0; i < length; i++) { + parport_write_data(gc->pd->port, ~(1 << i)); + data[i] = parport_read_status(gc->pd->port) ^ 0x7f; + } +} + +/* + * PSX support + * + * See documentation at: + * http://www.dim.com/~mackys/psxmemcard/ps-eng2.txt + * http://www.gamesx.com/controldata/psxcont/psxcont.htm + * ftp://milano.usal.es/pablo/ + * + */ + +#define GC_PSX_DELAY 25 /* 25 usec */ +#define GC_PSX_LENGTH 8 /* talk to the controller in bits */ +#define GC_PSX_BYTES 6 /* the maximum number of bytes to read off the controller */ + +#define GC_PSX_MOUSE 1 /* Mouse */ +#define GC_PSX_NEGCON 2 /* NegCon */ +#define GC_PSX_NORMAL 4 /* Digital / Analog or Rumble in Digital mode */ +#define GC_PSX_ANALOG 5 /* Analog in Analog mode / Rumble in Green mode */ +#define GC_PSX_RUMBLE 7 /* Rumble in Red mode */ + +#define GC_PSX_CLOCK 0x04 /* Pin 4 */ +#define GC_PSX_COMMAND 0x01 /* Pin 2 */ +#define GC_PSX_POWER 0xf8 /* Pins 5-9 */ +#define GC_PSX_SELECT 0x02 /* Pin 3 */ + +#define GC_PSX_ID(x) ((x) >> 4) /* High nibble is device type */ +#define GC_PSX_LEN(x) (((x) & 0xf) << 1) /* Low nibble is length in bytes/2 */ + +static int gc_psx_delay = GC_PSX_DELAY; +module_param_named(psx_delay, gc_psx_delay, uint, 0); +MODULE_PARM_DESC(psx_delay, "Delay when accessing Sony PSX controller (usecs)"); + +__obsolete_setup("gc_psx_delay="); + +static short gc_psx_abs[] = { ABS_X, ABS_Y, ABS_RX, ABS_RY, ABS_HAT0X, ABS_HAT0Y }; +static short gc_psx_btn[] = { BTN_TL, BTN_TR, BTN_TL2, BTN_TR2, BTN_A, BTN_B, BTN_X, BTN_Y, + BTN_START, BTN_SELECT, BTN_THUMBL, BTN_THUMBR }; +static short gc_psx_ddr_btn[] = { BTN_0, BTN_1, BTN_2, BTN_3 }; + +/* + * gc_psx_command() writes 8bit command and reads 8bit data from + * the psx pad. + */ + +static void gc_psx_command(struct gc *gc, int b, unsigned char data[5]) +{ + int i, j, cmd, read; + for (i = 0; i < 5; i++) + data[i] = 0; + + for (i = 0; i < GC_PSX_LENGTH; i++, b >>= 1) { + cmd = (b & 1) ? GC_PSX_COMMAND : 0; + parport_write_data(gc->pd->port, cmd | GC_PSX_POWER); + udelay(gc_psx_delay); + read = parport_read_status(gc->pd->port) ^ 0x80; + for (j = 0; j < 5; j++) + data[j] |= (read & gc_status_bit[j] & (gc->pads[GC_PSX] | gc->pads[GC_DDR])) ? (1 << i) : 0; + parport_write_data(gc->pd->port, cmd | GC_PSX_CLOCK | GC_PSX_POWER); + udelay(gc_psx_delay); + } +} + +/* + * gc_psx_read_packet() reads a whole psx packet and returns + * device identifier code. + */ + +static void gc_psx_read_packet(struct gc *gc, unsigned char data[5][GC_PSX_BYTES], unsigned char id[5]) +{ + int i, j, max_len = 0; + unsigned long flags; + unsigned char data2[5]; + + parport_write_data(gc->pd->port, GC_PSX_CLOCK | GC_PSX_SELECT | GC_PSX_POWER); /* Select pad */ + udelay(gc_psx_delay); + parport_write_data(gc->pd->port, GC_PSX_CLOCK | GC_PSX_POWER); /* Deselect, begin command */ + udelay(gc_psx_delay); + + local_irq_save(flags); + + gc_psx_command(gc, 0x01, data2); /* Access pad */ + gc_psx_command(gc, 0x42, id); /* Get device ids */ + gc_psx_command(gc, 0, data2); /* Dump status */ + + for (i =0; i < 5; i++) /* Find the longest pad */ + if((gc_status_bit[i] & (gc->pads[GC_PSX] | gc->pads[GC_DDR])) + && (GC_PSX_LEN(id[i]) > max_len) + && (GC_PSX_LEN(id[i]) <= GC_PSX_BYTES)) + max_len = GC_PSX_LEN(id[i]); + + for (i = 0; i < max_len; i++) { /* Read in all the data */ + gc_psx_command(gc, 0, data2); + for (j = 0; j < 5; j++) + data[j][i] = data2[j]; + } + + local_irq_restore(flags); + + parport_write_data(gc->pd->port, GC_PSX_CLOCK | GC_PSX_SELECT | GC_PSX_POWER); + + for(i = 0; i < 5; i++) /* Set id's to the real value */ + id[i] = GC_PSX_ID(id[i]); +} + +/* + * gc_timer() reads and analyzes console pads data. + */ + +#define GC_MAX_LENGTH GC_N64_LENGTH + +static void gc_timer(unsigned long private) +{ + struct gc *gc = (void *) private; + struct input_dev *dev = gc->dev; + unsigned char data[GC_MAX_LENGTH]; + unsigned char data_psx[5][GC_PSX_BYTES]; + int i, j, s; + +/* + * N64 pads - must be read first, any read confuses them for 200 us + */ + + if (gc->pads[GC_N64]) { + + gc_n64_read_packet(gc, data); + + for (i = 0; i < 5; i++) { + + s = gc_status_bit[i]; + + if (s & gc->pads[GC_N64] & ~(data[8] | data[9])) { + + signed char axes[2]; + axes[0] = axes[1] = 0; + + for (j = 0; j < 8; j++) { + if (data[23 - j] & s) axes[0] |= 1 << j; + if (data[31 - j] & s) axes[1] |= 1 << j; + } + + input_report_abs(dev + i, ABS_X, axes[0]); + input_report_abs(dev + i, ABS_Y, -axes[1]); + + input_report_abs(dev + i, ABS_HAT0X, !(s & data[6]) - !(s & data[7])); + input_report_abs(dev + i, ABS_HAT0Y, !(s & data[4]) - !(s & data[5])); + + for (j = 0; j < 10; j++) + input_report_key(dev + i, gc_n64_btn[j], s & data[gc_n64_bytes[j]]); + + input_sync(dev + i); + } + } + } + +/* + * NES and SNES pads + */ + + if (gc->pads[GC_NES] || gc->pads[GC_SNES]) { + + gc_nes_read_packet(gc, gc->pads[GC_SNES] ? GC_SNES_LENGTH : GC_NES_LENGTH, data); + + for (i = 0; i < 5; i++) { + + s = gc_status_bit[i]; + + if (s & (gc->pads[GC_NES] | gc->pads[GC_SNES])) { + input_report_abs(dev + i, ABS_X, !(s & data[6]) - !(s & data[7])); + input_report_abs(dev + i, ABS_Y, !(s & data[4]) - !(s & data[5])); + } + + if (s & gc->pads[GC_NES]) + for (j = 0; j < 4; j++) + input_report_key(dev + i, gc_snes_btn[j], s & data[gc_nes_bytes[j]]); + + if (s & gc->pads[GC_SNES]) + for (j = 0; j < 8; j++) + input_report_key(dev + i, gc_snes_btn[j], s & data[gc_snes_bytes[j]]); + + input_sync(dev + i); + } + } + +/* + * Multi and Multi2 joysticks + */ + + if (gc->pads[GC_MULTI] || gc->pads[GC_MULTI2]) { + + gc_multi_read_packet(gc, gc->pads[GC_MULTI2] ? GC_MULTI2_LENGTH : GC_MULTI_LENGTH, data); + + for (i = 0; i < 5; i++) { + + s = gc_status_bit[i]; + + if (s & (gc->pads[GC_MULTI] | gc->pads[GC_MULTI2])) { + input_report_abs(dev + i, ABS_X, !(s & data[2]) - !(s & data[3])); + input_report_abs(dev + i, ABS_Y, !(s & data[0]) - !(s & data[1])); + input_report_key(dev + i, BTN_TRIGGER, s & data[4]); + } + + if (s & gc->pads[GC_MULTI2]) + input_report_key(dev + i, BTN_THUMB, s & data[5]); + + input_sync(dev + i); + } + } + +/* + * PSX controllers + */ + + if (gc->pads[GC_PSX] || gc->pads[GC_DDR]) { + + gc_psx_read_packet(gc, data_psx, data); + + for (i = 0; i < 5; i++) { + switch (data[i]) { + + case GC_PSX_RUMBLE: + + input_report_key(dev + i, BTN_THUMBL, ~data_psx[i][0] & 0x04); + input_report_key(dev + i, BTN_THUMBR, ~data_psx[i][0] & 0x02); + + case GC_PSX_NEGCON: + case GC_PSX_ANALOG: + + if(gc->pads[GC_DDR] & gc_status_bit[i]) { + for(j = 0; j < 4; j++) + input_report_key(dev + i, gc_psx_ddr_btn[j], ~data_psx[i][0] & (0x10 << j)); + } else { + for (j = 0; j < 4; j++) + input_report_abs(dev + i, gc_psx_abs[j+2], data_psx[i][j + 2]); + + input_report_abs(dev + i, ABS_X, 128 + !(data_psx[i][0] & 0x20) * 127 - !(data_psx[i][0] & 0x80) * 128); + input_report_abs(dev + i, ABS_Y, 128 + !(data_psx[i][0] & 0x40) * 127 - !(data_psx[i][0] & 0x10) * 128); + } + + for (j = 0; j < 8; j++) + input_report_key(dev + i, gc_psx_btn[j], ~data_psx[i][1] & (1 << j)); + + input_report_key(dev + i, BTN_START, ~data_psx[i][0] & 0x08); + input_report_key(dev + i, BTN_SELECT, ~data_psx[i][0] & 0x01); + + input_sync(dev + i); + + break; + + case GC_PSX_NORMAL: + if(gc->pads[GC_DDR] & gc_status_bit[i]) { + for(j = 0; j < 4; j++) + input_report_key(dev + i, gc_psx_ddr_btn[j], ~data_psx[i][0] & (0x10 << j)); + } else { + input_report_abs(dev + i, ABS_X, 128 + !(data_psx[i][0] & 0x20) * 127 - !(data_psx[i][0] & 0x80) * 128); + input_report_abs(dev + i, ABS_Y, 128 + !(data_psx[i][0] & 0x40) * 127 - !(data_psx[i][0] & 0x10) * 128); + + /* for some reason if the extra axes are left unset they drift */ + /* for (j = 0; j < 4; j++) + input_report_abs(dev + i, gc_psx_abs[j+2], 128); + * This needs to be debugged properly, + * maybe fuzz processing needs to be done in input_sync() + * --vojtech + */ + } + + for (j = 0; j < 8; j++) + input_report_key(dev + i, gc_psx_btn[j], ~data_psx[i][1] & (1 << j)); + + input_report_key(dev + i, BTN_START, ~data_psx[i][0] & 0x08); + input_report_key(dev + i, BTN_SELECT, ~data_psx[i][0] & 0x01); + + input_sync(dev + i); + + break; + + case 0: /* not a pad, ignore */ + break; + } + } + } + + mod_timer(&gc->timer, jiffies + GC_REFRESH_TIME); +} + +static int gc_open(struct input_dev *dev) +{ + struct gc *gc = dev->private; + if (!gc->used++) { + parport_claim(gc->pd); + parport_write_control(gc->pd->port, 0x04); + mod_timer(&gc->timer, jiffies + GC_REFRESH_TIME); + } + return 0; +} + +static void gc_close(struct input_dev *dev) +{ + struct gc *gc = dev->private; + if (!--gc->used) { + del_timer(&gc->timer); + parport_write_control(gc->pd->port, 0x00); + parport_release(gc->pd); + } +} + +static struct gc __init *gc_probe(int *config, int nargs) +{ + struct gc *gc; + struct parport *pp; + int i, j; + + if (config[0] < 0) + return NULL; + + if (nargs < 2) { + printk(KERN_ERR "gamecon.c: at least one device must be specified\n"); + return NULL; + } + + pp = parport_find_number(config[0]); + + if (!pp) { + printk(KERN_ERR "gamecon.c: no such parport\n"); + return NULL; + } + + if (!(gc = kmalloc(sizeof(struct gc), GFP_KERNEL))) { + parport_put_port(pp); + return NULL; + } + memset(gc, 0, sizeof(struct gc)); + + gc->pd = parport_register_device(pp, "gamecon", NULL, NULL, NULL, PARPORT_DEV_EXCL, NULL); + + parport_put_port(pp); + + if (!gc->pd) { + printk(KERN_ERR "gamecon.c: parport busy already - lp.o loaded?\n"); + kfree(gc); + return NULL; + } + + parport_claim(gc->pd); + + init_timer(&gc->timer); + gc->timer.data = (long) gc; + gc->timer.function = gc_timer; + + for (i = 0; i < nargs - 1; i++) { + + if (!config[i + 1]) + continue; + + if (config[i + 1] < 1 || config[i + 1] > GC_MAX) { + printk(KERN_WARNING "gamecon.c: Pad type %d unknown\n", config[i + 1]); + continue; + } + + gc->dev[i].private = gc; + gc->dev[i].open = gc_open; + gc->dev[i].close = gc_close; + + gc->dev[i].evbit[0] = BIT(EV_KEY) | BIT(EV_ABS); + + for (j = 0; j < 2; j++) { + set_bit(ABS_X + j, gc->dev[i].absbit); + gc->dev[i].absmin[ABS_X + j] = -1; + gc->dev[i].absmax[ABS_X + j] = 1; + } + + gc->pads[0] |= gc_status_bit[i]; + gc->pads[config[i + 1]] |= gc_status_bit[i]; + + switch(config[i + 1]) { + + case GC_N64: + for (j = 0; j < 10; j++) + set_bit(gc_n64_btn[j], gc->dev[i].keybit); + + for (j = 0; j < 2; j++) { + set_bit(ABS_X + j, gc->dev[i].absbit); + gc->dev[i].absmin[ABS_X + j] = -127; + gc->dev[i].absmax[ABS_X + j] = 126; + gc->dev[i].absflat[ABS_X + j] = 2; + set_bit(ABS_HAT0X + j, gc->dev[i].absbit); + gc->dev[i].absmin[ABS_HAT0X + j] = -1; + gc->dev[i].absmax[ABS_HAT0X + j] = 1; + } + + break; + + case GC_SNES: + for (j = 4; j < 8; j++) + set_bit(gc_snes_btn[j], gc->dev[i].keybit); + case GC_NES: + for (j = 0; j < 4; j++) + set_bit(gc_snes_btn[j], gc->dev[i].keybit); + break; + + case GC_MULTI2: + set_bit(BTN_THUMB, gc->dev[i].keybit); + case GC_MULTI: + set_bit(BTN_TRIGGER, gc->dev[i].keybit); + break; + + case GC_PSX: + case GC_DDR: + if(config[i + 1] == GC_DDR) { + for (j = 0; j < 4; j++) + set_bit(gc_psx_ddr_btn[j], gc->dev[i].keybit); + } else { + for (j = 0; j < 6; j++) { + set_bit(gc_psx_abs[j], gc->dev[i].absbit); + gc->dev[i].absmin[gc_psx_abs[j]] = 4; + gc->dev[i].absmax[gc_psx_abs[j]] = 252; + gc->dev[i].absflat[gc_psx_abs[j]] = 2; + } + } + + for (j = 0; j < 12; j++) + set_bit(gc_psx_btn[j], gc->dev[i].keybit); + + break; + } + + sprintf(gc->phys[i], "%s/input%d", gc->pd->port->name, i); + + gc->dev[i].name = gc_names[config[i + 1]]; + gc->dev[i].phys = gc->phys[i]; + gc->dev[i].id.bustype = BUS_PARPORT; + gc->dev[i].id.vendor = 0x0001; + gc->dev[i].id.product = config[i + 1]; + gc->dev[i].id.version = 0x0100; + } + + parport_release(gc->pd); + + if (!gc->pads[0]) { + parport_unregister_device(gc->pd); + kfree(gc); + return NULL; + } + + for (i = 0; i < 5; i++) + if (gc->pads[0] & gc_status_bit[i]) { + input_register_device(gc->dev + i); + printk(KERN_INFO "input: %s on %s\n", gc->dev[i].name, gc->pd->port->name); + } + + return gc; +} + +static int __init gc_init(void) +{ + gc_base[0] = gc_probe(gc, gc_nargs); + gc_base[1] = gc_probe(gc_2, gc_nargs_2); + gc_base[2] = gc_probe(gc_3, gc_nargs_3); + + if (gc_base[0] || gc_base[1] || gc_base[2]) + return 0; + + return -ENODEV; +} + +static void __exit gc_exit(void) +{ + int i, j; + + for (i = 0; i < 3; i++) + if (gc_base[i]) { + for (j = 0; j < 5; j++) + if (gc_base[i]->pads[0] & gc_status_bit[j]) + input_unregister_device(gc_base[i]->dev + j); + parport_unregister_device(gc_base[i]->pd); + } +} + +module_init(gc_init); +module_exit(gc_exit); diff --git a/drivers/input/joystick/gf2k.c b/drivers/input/joystick/gf2k.c new file mode 100644 index 000000000000..ad13f09a4e71 --- /dev/null +++ b/drivers/input/joystick/gf2k.c @@ -0,0 +1,380 @@ +/* + * $Id: gf2k.c,v 1.19 2002/01/22 20:27:43 vojtech Exp $ + * + * Copyright (c) 1998-2001 Vojtech Pavlik + */ + +/* + * Genius Flight 2000 joystick driver for Linux + */ + +/* + * 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 either by + * e-mail - mail your message to <vojtech@ucw.cz>, or by paper mail: + * Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic + */ + +#include <linux/delay.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/gameport.h> + +#define DRIVER_DESC "Genius Flight 2000 joystick driver" + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +#define GF2K_START 400 /* The time we wait for the first bit [400 us] */ +#define GF2K_STROBE 40 /* The time we wait for the first bit [40 us] */ +#define GF2K_TIMEOUT 4 /* Wait for everything to settle [4 ms] */ +#define GF2K_LENGTH 80 /* Max number of triplets in a packet */ + +/* + * Genius joystick ids ... + */ + +#define GF2K_ID_G09 1 +#define GF2K_ID_F30D 2 +#define GF2K_ID_F30 3 +#define GF2K_ID_F31D 4 +#define GF2K_ID_F305 5 +#define GF2K_ID_F23P 6 +#define GF2K_ID_F31 7 +#define GF2K_ID_MAX 7 + +static char gf2k_length[] = { 40, 40, 40, 40, 40, 40, 40, 40 }; +static char gf2k_hat_to_axis[][2] = {{ 0, 0}, { 0,-1}, { 1,-1}, { 1, 0}, { 1, 1}, { 0, 1}, {-1, 1}, {-1, 0}, {-1,-1}}; + +static char *gf2k_names[] = {"", "Genius G-09D", "Genius F-30D", "Genius F-30", "Genius MaxFighter F-31D", + "Genius F-30-5", "Genius Flight2000 F-23", "Genius F-31"}; +static unsigned char gf2k_hats[] = { 0, 2, 0, 0, 2, 0, 2, 0 }; +static unsigned char gf2k_axes[] = { 0, 2, 0, 0, 4, 0, 4, 0 }; +static unsigned char gf2k_joys[] = { 0, 0, 0, 0,10, 0, 8, 0 }; +static unsigned char gf2k_pads[] = { 0, 6, 0, 0, 0, 0, 0, 0 }; +static unsigned char gf2k_lens[] = { 0,18, 0, 0,18, 0,18, 0 }; + +static unsigned char gf2k_abs[] = { ABS_X, ABS_Y, ABS_THROTTLE, ABS_RUDDER, ABS_GAS, ABS_BRAKE }; +static short gf2k_btn_joy[] = { BTN_TRIGGER, BTN_THUMB, BTN_TOP, BTN_TOP2, BTN_BASE, BTN_BASE2, BTN_BASE3, BTN_BASE4 }; +static short gf2k_btn_pad[] = { BTN_A, BTN_B, BTN_C, BTN_X, BTN_Y, BTN_Z, BTN_TL, BTN_TR, BTN_TL2, BTN_TR2, BTN_START, BTN_SELECT }; + + +static short gf2k_seq_reset[] = { 240, 340, 0 }; +static short gf2k_seq_digital[] = { 590, 320, 860, 0 }; + +struct gf2k { + struct gameport *gameport; + struct input_dev dev; + int reads; + int bads; + unsigned char id; + unsigned char length; + char phys[32]; +}; + +/* + * gf2k_read_packet() reads a Genius Flight2000 packet. + */ + +static int gf2k_read_packet(struct gameport *gameport, int length, char *data) +{ + unsigned char u, v; + int i; + unsigned int t, p; + unsigned long flags; + + t = gameport_time(gameport, GF2K_START); + p = gameport_time(gameport, GF2K_STROBE); + + i = 0; + + local_irq_save(flags); + + gameport_trigger(gameport); + v = gameport_read(gameport); + + while (t > 0 && i < length) { + t--; u = v; + v = gameport_read(gameport); + if (v & ~u & 0x10) { + data[i++] = v >> 5; + t = p; + } + } + + local_irq_restore(flags); + + return i; +} + +/* + * gf2k_trigger_seq() initializes a Genius Flight2000 joystick + * into digital mode. + */ + +static void gf2k_trigger_seq(struct gameport *gameport, short *seq) +{ + + unsigned long flags; + int i, t; + + local_irq_save(flags); + + i = 0; + do { + gameport_trigger(gameport); + t = gameport_time(gameport, GF2K_TIMEOUT * 1000); + while ((gameport_read(gameport) & 1) && t) t--; + udelay(seq[i]); + } while (seq[++i]); + + gameport_trigger(gameport); + + local_irq_restore(flags); +} + +/* + * js_sw_get_bits() composes bits from the triplet buffer into a __u64. + * Parameter 'pos' is bit number inside packet where to start at, 'num' is number + * of bits to be read, 'shift' is offset in the resulting __u64 to start at, bits + * is number of bits per triplet. + */ + +#define GB(p,n,s) gf2k_get_bits(data, p, n, s) + +static int gf2k_get_bits(unsigned char *buf, int pos, int num, int shift) +{ + __u64 data = 0; + int i; + + for (i = 0; i < num / 3 + 2; i++) + data |= buf[pos / 3 + i] << (i * 3); + data >>= pos % 3; + data &= (1 << num) - 1; + data <<= shift; + + return data; +} + +static void gf2k_read(struct gf2k *gf2k, unsigned char *data) +{ + struct input_dev *dev = &gf2k->dev; + int i, t; + + for (i = 0; i < 4 && i < gf2k_axes[gf2k->id]; i++) + input_report_abs(dev, gf2k_abs[i], GB(i<<3,8,0) | GB(i+46,1,8) | GB(i+50,1,9)); + + for (i = 0; i < 2 && i < gf2k_axes[gf2k->id] - 4; i++) + input_report_abs(dev, gf2k_abs[i], GB(i*9+60,8,0) | GB(i+54,1,9)); + + t = GB(40,4,0); + + for (i = 0; i < gf2k_hats[gf2k->id]; i++) + input_report_abs(dev, ABS_HAT0X + i, gf2k_hat_to_axis[t][i]); + + t = GB(44,2,0) | GB(32,8,2) | GB(78,2,10); + + for (i = 0; i < gf2k_joys[gf2k->id]; i++) + input_report_key(dev, gf2k_btn_joy[i], (t >> i) & 1); + + for (i = 0; i < gf2k_pads[gf2k->id]; i++) + input_report_key(dev, gf2k_btn_pad[i], (t >> i) & 1); + + input_sync(dev); +} + +/* + * gf2k_poll() reads and analyzes Genius joystick data. + */ + +static void gf2k_poll(struct gameport *gameport) +{ + struct gf2k *gf2k = gameport_get_drvdata(gameport); + unsigned char data[GF2K_LENGTH]; + + gf2k->reads++; + + if (gf2k_read_packet(gf2k->gameport, gf2k_length[gf2k->id], data) < gf2k_length[gf2k->id]) + gf2k->bads++; + else + gf2k_read(gf2k, data); +} + +static int gf2k_open(struct input_dev *dev) +{ + struct gf2k *gf2k = dev->private; + + gameport_start_polling(gf2k->gameport); + return 0; +} + +static void gf2k_close(struct input_dev *dev) +{ + struct gf2k *gf2k = dev->private; + + gameport_stop_polling(gf2k->gameport); +} + +/* + * gf2k_connect() probes for Genius id joysticks. + */ + +static int gf2k_connect(struct gameport *gameport, struct gameport_driver *drv) +{ + struct gf2k *gf2k; + unsigned char data[GF2K_LENGTH]; + int i, err; + + if (!(gf2k = kcalloc(1, sizeof(struct gf2k), GFP_KERNEL))) + return -ENOMEM; + + gf2k->gameport = gameport; + + gameport_set_drvdata(gameport, gf2k); + + err = gameport_open(gameport, drv, GAMEPORT_MODE_RAW); + if (err) + goto fail1; + + gf2k_trigger_seq(gameport, gf2k_seq_reset); + + msleep(GF2K_TIMEOUT); + + gf2k_trigger_seq(gameport, gf2k_seq_digital); + + msleep(GF2K_TIMEOUT); + + if (gf2k_read_packet(gameport, GF2K_LENGTH, data) < 12) { + err = -ENODEV; + goto fail2; + } + + if (!(gf2k->id = GB(7,2,0) | GB(3,3,2) | GB(0,3,5))) { + err = -ENODEV; + goto fail2; + } + +#ifdef RESET_WORKS + if ((gf2k->id != (GB(19,2,0) | GB(15,3,2) | GB(12,3,5))) || + (gf2k->id != (GB(31,2,0) | GB(27,3,2) | GB(24,3,5)))) { + err = -ENODEV; + goto fail2; + } +#else + gf2k->id = 6; +#endif + + if (gf2k->id > GF2K_ID_MAX || !gf2k_axes[gf2k->id]) { + printk(KERN_WARNING "gf2k.c: Not yet supported joystick on %s. [id: %d type:%s]\n", + gameport->phys, gf2k->id, gf2k->id > GF2K_ID_MAX ? "Unknown" : gf2k_names[gf2k->id]); + err = -ENODEV; + goto fail2; + } + + gameport_set_poll_handler(gameport, gf2k_poll); + gameport_set_poll_interval(gameport, 20); + + sprintf(gf2k->phys, "%s/input0", gameport->phys); + + gf2k->length = gf2k_lens[gf2k->id]; + + init_input_dev(&gf2k->dev); + + gf2k->dev.private = gf2k; + gf2k->dev.open = gf2k_open; + gf2k->dev.close = gf2k_close; + gf2k->dev.evbit[0] = BIT(EV_KEY) | BIT(EV_ABS); + + gf2k->dev.name = gf2k_names[gf2k->id]; + gf2k->dev.phys = gf2k->phys; + gf2k->dev.id.bustype = BUS_GAMEPORT; + gf2k->dev.id.vendor = GAMEPORT_ID_VENDOR_GENIUS; + gf2k->dev.id.product = gf2k->id; + gf2k->dev.id.version = 0x0100; + + for (i = 0; i < gf2k_axes[gf2k->id]; i++) + set_bit(gf2k_abs[i], gf2k->dev.absbit); + + for (i = 0; i < gf2k_hats[gf2k->id]; i++) { + set_bit(ABS_HAT0X + i, gf2k->dev.absbit); + gf2k->dev.absmin[ABS_HAT0X + i] = -1; + gf2k->dev.absmax[ABS_HAT0X + i] = 1; + } + + for (i = 0; i < gf2k_joys[gf2k->id]; i++) + set_bit(gf2k_btn_joy[i], gf2k->dev.keybit); + + for (i = 0; i < gf2k_pads[gf2k->id]; i++) + set_bit(gf2k_btn_pad[i], gf2k->dev.keybit); + + gf2k_read_packet(gameport, gf2k->length, data); + gf2k_read(gf2k, data); + + for (i = 0; i < gf2k_axes[gf2k->id]; i++) { + gf2k->dev.absmax[gf2k_abs[i]] = (i < 2) ? gf2k->dev.abs[gf2k_abs[i]] * 2 - 32 : + gf2k->dev.abs[gf2k_abs[0]] + gf2k->dev.abs[gf2k_abs[1]] - 32; + gf2k->dev.absmin[gf2k_abs[i]] = 32; + gf2k->dev.absfuzz[gf2k_abs[i]] = 8; + gf2k->dev.absflat[gf2k_abs[i]] = (i < 2) ? 24 : 0; + } + + input_register_device(&gf2k->dev); + printk(KERN_INFO "input: %s on %s\n", gf2k_names[gf2k->id], gameport->phys); + + return 0; + +fail2: gameport_close(gameport); +fail1: gameport_set_drvdata(gameport, NULL); + kfree(gf2k); + return err; +} + +static void gf2k_disconnect(struct gameport *gameport) +{ + struct gf2k *gf2k = gameport_get_drvdata(gameport); + + input_unregister_device(&gf2k->dev); + gameport_close(gameport); + gameport_set_drvdata(gameport, NULL); + kfree(gf2k); +} + +static struct gameport_driver gf2k_drv = { + .driver = { + .name = "gf2k", + }, + .description = DRIVER_DESC, + .connect = gf2k_connect, + .disconnect = gf2k_disconnect, +}; + +static int __init gf2k_init(void) +{ + gameport_register_driver(&gf2k_drv); + return 0; +} + +static void __exit gf2k_exit(void) +{ + gameport_unregister_driver(&gf2k_drv); +} + +module_init(gf2k_init); +module_exit(gf2k_exit); diff --git a/drivers/input/joystick/grip.c b/drivers/input/joystick/grip.c new file mode 100644 index 000000000000..d1500d2562d6 --- /dev/null +++ b/drivers/input/joystick/grip.c @@ -0,0 +1,422 @@ +/* + * $Id: grip.c,v 1.21 2002/01/22 20:27:57 vojtech Exp $ + * + * Copyright (c) 1998-2001 Vojtech Pavlik + */ + +/* + * Gravis/Kensington GrIP protocol joystick and gamepad driver for Linux + */ + +/* + * 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 either by + * e-mail - mail your message to <vojtech@ucw.cz>, or by paper mail: + * Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/gameport.h> +#include <linux/input.h> + +#define DRIVER_DESC "Gravis GrIP protocol joystick driver" + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +#define GRIP_MODE_GPP 1 +#define GRIP_MODE_BD 2 +#define GRIP_MODE_XT 3 +#define GRIP_MODE_DC 4 + +#define GRIP_LENGTH_GPP 24 +#define GRIP_STROBE_GPP 200 /* 200 us */ +#define GRIP_LENGTH_XT 4 +#define GRIP_STROBE_XT 64 /* 64 us */ +#define GRIP_MAX_CHUNKS_XT 10 +#define GRIP_MAX_BITS_XT 30 + +struct grip { + struct gameport *gameport; + struct input_dev dev[2]; + unsigned char mode[2]; + int reads; + int bads; + char phys[2][32]; +}; + +static int grip_btn_gpp[] = { BTN_START, BTN_SELECT, BTN_TR2, BTN_Y, 0, BTN_TL2, BTN_A, BTN_B, BTN_X, 0, BTN_TL, BTN_TR, -1 }; +static int grip_btn_bd[] = { BTN_THUMB, BTN_THUMB2, BTN_TRIGGER, BTN_TOP, BTN_BASE, -1 }; +static int grip_btn_xt[] = { BTN_TRIGGER, BTN_THUMB, BTN_A, BTN_B, BTN_C, BTN_X, BTN_Y, BTN_Z, BTN_SELECT, BTN_START, BTN_MODE, -1 }; +static int grip_btn_dc[] = { BTN_TRIGGER, BTN_THUMB, BTN_TOP, BTN_TOP2, BTN_BASE, BTN_BASE2, BTN_BASE3, BTN_BASE4, BTN_BASE5, -1 }; + +static int grip_abs_gpp[] = { ABS_X, ABS_Y, -1 }; +static int grip_abs_bd[] = { ABS_X, ABS_Y, ABS_THROTTLE, ABS_HAT0X, ABS_HAT0Y, -1 }; +static int grip_abs_xt[] = { ABS_X, ABS_Y, ABS_BRAKE, ABS_GAS, ABS_THROTTLE, ABS_HAT0X, ABS_HAT0Y, ABS_HAT1X, ABS_HAT1Y, -1 }; +static int grip_abs_dc[] = { ABS_X, ABS_Y, ABS_RX, ABS_RY, ABS_THROTTLE, ABS_HAT0X, ABS_HAT0Y, -1 }; + +static char *grip_name[] = { NULL, "Gravis GamePad Pro", "Gravis Blackhawk Digital", + "Gravis Xterminator Digital", "Gravis Xterminator DualControl" }; +static int *grip_abs[] = { NULL, grip_abs_gpp, grip_abs_bd, grip_abs_xt, grip_abs_dc }; +static int *grip_btn[] = { NULL, grip_btn_gpp, grip_btn_bd, grip_btn_xt, grip_btn_dc }; +static char grip_anx[] = { 0, 0, 3, 5, 5 }; +static char grip_cen[] = { 0, 0, 2, 2, 4 }; + +/* + * grip_gpp_read_packet() reads a Gravis GamePad Pro packet. + */ + +static int grip_gpp_read_packet(struct gameport *gameport, int shift, unsigned int *data) +{ + unsigned long flags; + unsigned char u, v; + unsigned int t; + int i; + + int strobe = gameport_time(gameport, GRIP_STROBE_GPP); + + data[0] = 0; + t = strobe; + i = 0; + + local_irq_save(flags); + + v = gameport_read(gameport) >> shift; + + do { + t--; + u = v; v = (gameport_read(gameport) >> shift) & 3; + if (~v & u & 1) { + data[0] |= (v >> 1) << i++; + t = strobe; + } + } while (i < GRIP_LENGTH_GPP && t > 0); + + local_irq_restore(flags); + + if (i < GRIP_LENGTH_GPP) return -1; + + for (i = 0; i < GRIP_LENGTH_GPP && (data[0] & 0xfe4210) ^ 0x7c0000; i++) + data[0] = data[0] >> 1 | (data[0] & 1) << (GRIP_LENGTH_GPP - 1); + + return -(i == GRIP_LENGTH_GPP); +} + +/* + * grip_xt_read_packet() reads a Gravis Xterminator packet. + */ + +static int grip_xt_read_packet(struct gameport *gameport, int shift, unsigned int *data) +{ + unsigned int i, j, buf, crc; + unsigned char u, v, w; + unsigned long flags; + unsigned int t; + char status; + + int strobe = gameport_time(gameport, GRIP_STROBE_XT); + + data[0] = data[1] = data[2] = data[3] = 0; + status = buf = i = j = 0; + t = strobe; + + local_irq_save(flags); + + v = w = (gameport_read(gameport) >> shift) & 3; + + do { + t--; + u = (gameport_read(gameport) >> shift) & 3; + + if (u ^ v) { + + if ((u ^ v) & 1) { + buf = (buf << 1) | (u >> 1); + t = strobe; + i++; + } else + + if ((((u ^ v) & (v ^ w)) >> 1) & ~(u | v | w) & 1) { + if (i == 20) { + crc = buf ^ (buf >> 7) ^ (buf >> 14); + if (!((crc ^ (0x25cb9e70 >> ((crc >> 2) & 0x1c))) & 0xf)) { + data[buf >> 18] = buf >> 4; + status |= 1 << (buf >> 18); + } + j++; + } + t = strobe; + buf = 0; + i = 0; + } + w = v; + v = u; + } + + } while (status != 0xf && i < GRIP_MAX_BITS_XT && j < GRIP_MAX_CHUNKS_XT && t > 0); + + local_irq_restore(flags); + + return -(status != 0xf); +} + +/* + * grip_timer() repeatedly polls the joysticks and generates events. + */ + +static void grip_poll(struct gameport *gameport) +{ + struct grip *grip = gameport_get_drvdata(gameport); + unsigned int data[GRIP_LENGTH_XT]; + struct input_dev *dev; + int i, j; + + for (i = 0; i < 2; i++) { + + dev = grip->dev + i; + grip->reads++; + + switch (grip->mode[i]) { + + case GRIP_MODE_GPP: + + if (grip_gpp_read_packet(grip->gameport, (i << 1) + 4, data)) { + grip->bads++; + break; + } + + input_report_abs(dev, ABS_X, ((*data >> 15) & 1) - ((*data >> 16) & 1)); + input_report_abs(dev, ABS_Y, ((*data >> 13) & 1) - ((*data >> 12) & 1)); + + for (j = 0; j < 12; j++) + if (grip_btn_gpp[j]) + input_report_key(dev, grip_btn_gpp[j], (*data >> j) & 1); + + break; + + case GRIP_MODE_BD: + + if (grip_xt_read_packet(grip->gameport, (i << 1) + 4, data)) { + grip->bads++; + break; + } + + input_report_abs(dev, ABS_X, (data[0] >> 2) & 0x3f); + input_report_abs(dev, ABS_Y, 63 - ((data[0] >> 8) & 0x3f)); + input_report_abs(dev, ABS_THROTTLE, (data[2] >> 8) & 0x3f); + + input_report_abs(dev, ABS_HAT0X, ((data[2] >> 1) & 1) - ( data[2] & 1)); + input_report_abs(dev, ABS_HAT0Y, ((data[2] >> 2) & 1) - ((data[2] >> 3) & 1)); + + for (j = 0; j < 5; j++) + input_report_key(dev, grip_btn_bd[j], (data[3] >> (j + 4)) & 1); + + break; + + case GRIP_MODE_XT: + + if (grip_xt_read_packet(grip->gameport, (i << 1) + 4, data)) { + grip->bads++; + break; + } + + input_report_abs(dev, ABS_X, (data[0] >> 2) & 0x3f); + input_report_abs(dev, ABS_Y, 63 - ((data[0] >> 8) & 0x3f)); + input_report_abs(dev, ABS_BRAKE, (data[1] >> 2) & 0x3f); + input_report_abs(dev, ABS_GAS, (data[1] >> 8) & 0x3f); + input_report_abs(dev, ABS_THROTTLE, (data[2] >> 8) & 0x3f); + + input_report_abs(dev, ABS_HAT0X, ((data[2] >> 1) & 1) - ( data[2] & 1)); + input_report_abs(dev, ABS_HAT0Y, ((data[2] >> 2) & 1) - ((data[2] >> 3) & 1)); + input_report_abs(dev, ABS_HAT1X, ((data[2] >> 5) & 1) - ((data[2] >> 4) & 1)); + input_report_abs(dev, ABS_HAT1Y, ((data[2] >> 6) & 1) - ((data[2] >> 7) & 1)); + + for (j = 0; j < 11; j++) + input_report_key(dev, grip_btn_xt[j], (data[3] >> (j + 3)) & 1); + break; + + case GRIP_MODE_DC: + + if (grip_xt_read_packet(grip->gameport, (i << 1) + 4, data)) { + grip->bads++; + break; + } + + input_report_abs(dev, ABS_X, (data[0] >> 2) & 0x3f); + input_report_abs(dev, ABS_Y, (data[0] >> 8) & 0x3f); + input_report_abs(dev, ABS_RX, (data[1] >> 2) & 0x3f); + input_report_abs(dev, ABS_RY, (data[1] >> 8) & 0x3f); + input_report_abs(dev, ABS_THROTTLE, (data[2] >> 8) & 0x3f); + + input_report_abs(dev, ABS_HAT0X, ((data[2] >> 1) & 1) - ( data[2] & 1)); + input_report_abs(dev, ABS_HAT0Y, ((data[2] >> 2) & 1) - ((data[2] >> 3) & 1)); + + for (j = 0; j < 9; j++) + input_report_key(dev, grip_btn_dc[j], (data[3] >> (j + 3)) & 1); + break; + + + } + + input_sync(dev); + } +} + +static int grip_open(struct input_dev *dev) +{ + struct grip *grip = dev->private; + + gameport_start_polling(grip->gameport); + return 0; +} + +static void grip_close(struct input_dev *dev) +{ + struct grip *grip = dev->private; + + gameport_stop_polling(grip->gameport); +} + +static int grip_connect(struct gameport *gameport, struct gameport_driver *drv) +{ + struct grip *grip; + unsigned int data[GRIP_LENGTH_XT]; + int i, j, t; + int err; + + if (!(grip = kcalloc(1, sizeof(struct grip), GFP_KERNEL))) + return -ENOMEM; + + grip->gameport = gameport; + + gameport_set_drvdata(gameport, grip); + + err = gameport_open(gameport, drv, GAMEPORT_MODE_RAW); + if (err) + goto fail1; + + for (i = 0; i < 2; i++) { + if (!grip_gpp_read_packet(gameport, (i << 1) + 4, data)) { + grip->mode[i] = GRIP_MODE_GPP; + continue; + } + if (!grip_xt_read_packet(gameport, (i << 1) + 4, data)) { + if (!(data[3] & 7)) { + grip->mode[i] = GRIP_MODE_BD; + continue; + } + if (!(data[2] & 0xf0)) { + grip->mode[i] = GRIP_MODE_XT; + continue; + } + grip->mode[i] = GRIP_MODE_DC; + continue; + } + } + + if (!grip->mode[0] && !grip->mode[1]) { + err = -ENODEV; + goto fail2; + } + + gameport_set_poll_handler(gameport, grip_poll); + gameport_set_poll_interval(gameport, 20); + + for (i = 0; i < 2; i++) + if (grip->mode[i]) { + + sprintf(grip->phys[i], "%s/input%d", gameport->phys, i); + + grip->dev[i].private = grip; + + grip->dev[i].open = grip_open; + grip->dev[i].close = grip_close; + + grip->dev[i].name = grip_name[grip->mode[i]]; + grip->dev[i].phys = grip->phys[i]; + grip->dev[i].id.bustype = BUS_GAMEPORT; + grip->dev[i].id.vendor = GAMEPORT_ID_VENDOR_GRAVIS; + grip->dev[i].id.product = grip->mode[i]; + grip->dev[i].id.version = 0x0100; + + grip->dev[i].evbit[0] = BIT(EV_KEY) | BIT(EV_ABS); + + for (j = 0; (t = grip_abs[grip->mode[i]][j]) >= 0; j++) { + + if (j < grip_cen[grip->mode[i]]) + input_set_abs_params(&grip->dev[i], t, 14, 52, 1, 2); + else if (j < grip_anx[grip->mode[i]]) + input_set_abs_params(&grip->dev[i], t, 3, 57, 1, 0); + else + input_set_abs_params(&grip->dev[i], t, -1, 1, 0, 0); + } + + for (j = 0; (t = grip_btn[grip->mode[i]][j]) >= 0; j++) + if (t > 0) + set_bit(t, grip->dev[i].keybit); + + printk(KERN_INFO "input: %s on %s\n", + grip_name[grip->mode[i]], gameport->phys); + input_register_device(grip->dev + i); + } + + return 0; + +fail2: gameport_close(gameport); +fail1: gameport_set_drvdata(gameport, NULL); + kfree(grip); + return err; +} + +static void grip_disconnect(struct gameport *gameport) +{ + struct grip *grip = gameport_get_drvdata(gameport); + int i; + + for (i = 0; i < 2; i++) + if (grip->mode[i]) + input_unregister_device(grip->dev + i); + gameport_close(gameport); + gameport_set_drvdata(gameport, NULL); + kfree(grip); +} + +static struct gameport_driver grip_drv = { + .driver = { + .name = "grip", + }, + .description = DRIVER_DESC, + .connect = grip_connect, + .disconnect = grip_disconnect, +}; + +static int __init grip_init(void) +{ + gameport_register_driver(&grip_drv); + return 0; +} + +static void __exit grip_exit(void) +{ + gameport_unregister_driver(&grip_drv); +} + +module_init(grip_init); +module_exit(grip_exit); diff --git a/drivers/input/joystick/grip_mp.c b/drivers/input/joystick/grip_mp.c new file mode 100644 index 000000000000..42e5005d621f --- /dev/null +++ b/drivers/input/joystick/grip_mp.c @@ -0,0 +1,677 @@ +/* + * $Id: grip_mp.c,v 1.9 2002/07/20 19:28:45 bonnland Exp $ + * + * Driver for the Gravis Grip Multiport, a gamepad "hub" that + * connects up to four 9-pin digital gamepads/joysticks. + * Driver tested on SMP and UP kernel versions 2.4.18-4 and 2.4.18-5. + * + * Thanks to Chris Gassib for helpful advice. + * + * Copyright (c) 2002 Brian Bonnlander, Bill Soudan + * Copyright (c) 1998-2000 Vojtech Pavlik + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/gameport.h> +#include <linux/input.h> +#include <linux/delay.h> +#include <linux/proc_fs.h> + +#define DRIVER_DESC "Gravis Grip Multiport driver" + +MODULE_AUTHOR("Brian Bonnlander"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +#ifdef GRIP_DEBUG +#define dbg(format, arg...) printk(KERN_ERR __FILE__ ": " format "\n" , ## arg) +#else +#define dbg(format, arg...) do {} while (0) +#endif + +/* + * Grip multiport state + */ + +struct grip_mp { + struct gameport *gameport; + struct input_dev dev[4]; + int mode[4]; + int registered[4]; + int reads; + int bads; + + /* individual gamepad states */ + int buttons[4]; + int xaxes[4]; + int yaxes[4]; + int dirty[4]; /* has the state been updated? */ +}; + +/* + * Multiport packet interpretation + */ + +#define PACKET_FULL 0x80000000 /* packet is full */ +#define PACKET_IO_FAST 0x40000000 /* 3 bits per gameport read */ +#define PACKET_IO_SLOW 0x20000000 /* 1 bit per gameport read */ +#define PACKET_MP_MORE 0x04000000 /* multiport wants to send more */ +#define PACKET_MP_DONE 0x02000000 /* multiport done sending */ + +/* + * Packet status code interpretation + */ + +#define IO_GOT_PACKET 0x0100 /* Got a packet */ +#define IO_MODE_FAST 0x0200 /* Used 3 data bits per gameport read */ +#define IO_SLOT_CHANGE 0x0800 /* Multiport physical slot status changed */ +#define IO_DONE 0x1000 /* Multiport is done sending packets */ +#define IO_RETRY 0x4000 /* Try again later to get packet */ +#define IO_RESET 0x8000 /* Force multiport to resend all packets */ + +/* + * Gamepad configuration data. Other 9-pin digital joystick devices + * may work with the multiport, so this may not be an exhaustive list! + * Commodore 64 joystick remains untested. + */ + +#define GRIP_INIT_DELAY 2000 /* 2 ms */ + +#define GRIP_MODE_NONE 0 +#define GRIP_MODE_RESET 1 +#define GRIP_MODE_GP 2 +#define GRIP_MODE_C64 3 + +static int grip_btn_gp[] = { BTN_TR, BTN_TL, BTN_A, BTN_B, BTN_C, BTN_X, BTN_Y, BTN_Z, -1 }; +static int grip_btn_c64[] = { BTN_JOYSTICK, -1 }; + +static int grip_abs_gp[] = { ABS_X, ABS_Y, -1 }; +static int grip_abs_c64[] = { ABS_X, ABS_Y, -1 }; + +static int *grip_abs[] = { NULL, NULL, grip_abs_gp, grip_abs_c64 }; +static int *grip_btn[] = { NULL, NULL, grip_btn_gp, grip_btn_c64 }; + +static char *grip_name[] = { NULL, NULL, "Gravis Grip Pad", "Commodore 64 Joystick" }; + +static const int init_seq[] = { + 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, + 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1 }; + +/* Maps multiport directional values to X,Y axis values (each axis encoded in 3 bits) */ + +static int axis_map[] = { 5, 9, 1, 5, 6, 10, 2, 6, 4, 8, 0, 4, 5, 9, 1, 5 }; + +static void register_slot(int i, struct grip_mp *grip); + +/* + * Returns whether an odd or even number of bits are on in pkt. + */ + +static int bit_parity(u32 pkt) +{ + int x = pkt ^ (pkt >> 16); + x ^= x >> 8; + x ^= x >> 4; + x ^= x >> 2; + x ^= x >> 1; + return x & 1; +} + +/* + * Poll gameport; return true if all bits set in 'onbits' are on and + * all bits set in 'offbits' are off. + */ + +static inline int poll_until(u8 onbits, u8 offbits, int u_sec, struct gameport* gp, u8 *data) +{ + int i, nloops; + + nloops = gameport_time(gp, u_sec); + for (i = 0; i < nloops; i++) { + *data = gameport_read(gp); + if ((*data & onbits) == onbits && + (~(*data) & offbits) == offbits) + return 1; + } + dbg("gameport timed out after %d microseconds.\n", u_sec); + return 0; +} + +/* + * Gets a 28-bit packet from the multiport. + * + * After getting a packet successfully, commands encoded by sendcode may + * be sent to the multiport. + * + * The multiport clock value is reflected in gameport bit B4. + * + * Returns a packet status code indicating whether packet is valid, the transfer + * mode, and any error conditions. + * + * sendflags: current I/O status + * sendcode: data to send to the multiport if sendflags is nonzero + */ + +static int mp_io(struct gameport* gameport, int sendflags, int sendcode, u32 *packet) +{ + u8 raw_data; /* raw data from gameport */ + u8 data_mask; /* packet data bits from raw_data */ + u32 pkt; /* packet temporary storage */ + int bits_per_read; /* num packet bits per gameport read */ + int portvals = 0; /* used for port value sanity check */ + int i; + + /* Gameport bits B0, B4, B5 should first be off, then B4 should come on. */ + + *packet = 0; + raw_data = gameport_read(gameport); + if (raw_data & 1) + return IO_RETRY; + + for (i = 0; i < 64; i++) { + raw_data = gameport_read(gameport); + portvals |= 1 << ((raw_data >> 4) & 3); /* Demux B4, B5 */ + } + + if (portvals == 1) { /* B4, B5 off */ + raw_data = gameport_read(gameport); + portvals = raw_data & 0xf0; + + if (raw_data & 0x31) + return IO_RESET; + gameport_trigger(gameport); + + if (!poll_until(0x10, 0, 308, gameport, &raw_data)) + return IO_RESET; + } else + return IO_RETRY; + + /* Determine packet transfer mode and prepare for packet construction. */ + + if (raw_data & 0x20) { /* 3 data bits/read */ + portvals |= raw_data >> 4; /* Compare B4-B7 before & after trigger */ + + if (portvals != 0xb) + return 0; + data_mask = 7; + bits_per_read = 3; + pkt = (PACKET_FULL | PACKET_IO_FAST) >> 28; + } else { /* 1 data bit/read */ + data_mask = 1; + bits_per_read = 1; + pkt = (PACKET_FULL | PACKET_IO_SLOW) >> 28; + } + + /* Construct a packet. Final data bits must be zero. */ + + while (1) { + if (!poll_until(0, 0x10, 77, gameport, &raw_data)) + return IO_RESET; + raw_data = (raw_data >> 5) & data_mask; + + if (pkt & PACKET_FULL) + break; + pkt = (pkt << bits_per_read) | raw_data; + + if (!poll_until(0x10, 0, 77, gameport, &raw_data)) + return IO_RESET; + } + + if (raw_data) + return IO_RESET; + + /* If 3 bits/read used, drop from 30 bits to 28. */ + + if (bits_per_read == 3) { + pkt = (pkt & 0xffff0000) | ((pkt << 1) & 0xffff); + pkt = (pkt >> 2) | 0xf0000000; + } + + if (bit_parity(pkt) == 1) + return IO_RESET; + + /* Acknowledge packet receipt */ + + if (!poll_until(0x30, 0, 77, gameport, &raw_data)) + return IO_RESET; + + raw_data = gameport_read(gameport); + + if (raw_data & 1) + return IO_RESET; + + gameport_trigger(gameport); + + if (!poll_until(0, 0x20, 77, gameport, &raw_data)) + return IO_RESET; + + /* Return if we just wanted the packet or multiport wants to send more */ + + *packet = pkt; + if ((sendflags == 0) || ((sendflags & IO_RETRY) && !(pkt & PACKET_MP_DONE))) + return IO_GOT_PACKET; + + if (pkt & PACKET_MP_MORE) + return IO_GOT_PACKET | IO_RETRY; + + /* Multiport is done sending packets and is ready to receive data */ + + if (!poll_until(0x20, 0, 77, gameport, &raw_data)) + return IO_GOT_PACKET | IO_RESET; + + raw_data = gameport_read(gameport); + if (raw_data & 1) + return IO_GOT_PACKET | IO_RESET; + + /* Trigger gameport based on bits in sendcode */ + + gameport_trigger(gameport); + do { + if (!poll_until(0x20, 0x10, 116, gameport, &raw_data)) + return IO_GOT_PACKET | IO_RESET; + + if (!poll_until(0x30, 0, 193, gameport, &raw_data)) + return IO_GOT_PACKET | IO_RESET; + + if (raw_data & 1) + return IO_GOT_PACKET | IO_RESET; + + if (sendcode & 1) + gameport_trigger(gameport); + + sendcode >>= 1; + } while (sendcode); + + return IO_GOT_PACKET | IO_MODE_FAST; +} + +/* + * Disables and restores interrupts for mp_io(), which does the actual I/O. + */ + +static int multiport_io(struct gameport* gameport, int sendflags, int sendcode, u32 *packet) +{ + int status; + unsigned long flags; + + local_irq_save(flags); + status = mp_io(gameport, sendflags, sendcode, packet); + local_irq_restore(flags); + + return status; +} + +/* + * Puts multiport into digital mode. Multiport LED turns green. + * + * Returns true if a valid digital packet was received, false otherwise. + */ + +static int dig_mode_start(struct gameport *gameport, u32 *packet) +{ + int i, seq_len = sizeof(init_seq)/sizeof(int); + int flags, tries = 0, bads = 0; + + for (i = 0; i < seq_len; i++) { /* Send magic sequence */ + if (init_seq[i]) + gameport_trigger(gameport); + udelay(GRIP_INIT_DELAY); + } + + for (i = 0; i < 16; i++) /* Wait for multiport to settle */ + udelay(GRIP_INIT_DELAY); + + while (tries < 64 && bads < 8) { /* Reset multiport and try getting a packet */ + + flags = multiport_io(gameport, IO_RESET, 0x27, packet); + + if (flags & IO_MODE_FAST) + return 1; + + if (flags & IO_RETRY) + tries++; + else + bads++; + } + return 0; +} + +/* + * Packet structure: B0-B15 => gamepad state + * B16-B20 => gamepad device type + * B21-B24 => multiport slot index (1-4) + * + * Known device types: 0x1f (grip pad), 0x0 (no device). Others may exist. + * + * Returns the packet status. + */ + +static int get_and_decode_packet(struct grip_mp *grip, int flags) +{ + u32 packet; + int joytype = 0; + int slot = 0; + + /* Get a packet and check for validity */ + + flags &= IO_RESET | IO_RETRY; + flags = multiport_io(grip->gameport, flags, 0, &packet); + grip->reads++; + + if (packet & PACKET_MP_DONE) + flags |= IO_DONE; + + if (flags && !(flags & IO_GOT_PACKET)) { + grip->bads++; + return flags; + } + + /* Ignore non-gamepad packets, e.g. multiport hardware version */ + + slot = ((packet >> 21) & 0xf) - 1; + if ((slot < 0) || (slot > 3)) + return flags; + + /* + * Handle "reset" packets, which occur at startup, and when gamepads + * are removed or plugged in. May contain configuration of a new gamepad. + */ + + joytype = (packet >> 16) & 0x1f; + if (!joytype) { + + if (grip->registered[slot]) { + printk(KERN_INFO "grip_mp: removing %s, slot %d\n", + grip_name[grip->mode[slot]], slot); + input_unregister_device(grip->dev + slot); + grip->registered[slot] = 0; + } + dbg("Reset: grip multiport slot %d\n", slot); + grip->mode[slot] = GRIP_MODE_RESET; + flags |= IO_SLOT_CHANGE; + return flags; + } + + /* Interpret a grip pad packet */ + + if (joytype == 0x1f) { + + int dir = (packet >> 8) & 0xf; /* eight way directional value */ + grip->buttons[slot] = (~packet) & 0xff; + grip->yaxes[slot] = ((axis_map[dir] >> 2) & 3) - 1; + grip->xaxes[slot] = (axis_map[dir] & 3) - 1; + grip->dirty[slot] = 1; + + if (grip->mode[slot] == GRIP_MODE_RESET) + flags |= IO_SLOT_CHANGE; + + grip->mode[slot] = GRIP_MODE_GP; + + if (!grip->registered[slot]) { + dbg("New Grip pad in multiport slot %d.\n", slot); + register_slot(slot, grip); + } + return flags; + } + + /* Handle non-grip device codes. For now, just print diagnostics. */ + + { + static int strange_code = 0; + if (strange_code != joytype) { + printk(KERN_INFO "Possible non-grip pad/joystick detected.\n"); + printk(KERN_INFO "Got joy type 0x%x and packet 0x%x.\n", joytype, packet); + strange_code = joytype; + } + } + return flags; +} + +/* + * Returns true if all multiport slot states appear valid. + */ + +static int slots_valid(struct grip_mp *grip) +{ + int flags, slot, invalid = 0, active = 0; + + flags = get_and_decode_packet(grip, 0); + if (!(flags & IO_GOT_PACKET)) + return 0; + + for (slot = 0; slot < 4; slot++) { + if (grip->mode[slot] == GRIP_MODE_RESET) + invalid = 1; + if (grip->mode[slot] != GRIP_MODE_NONE) + active = 1; + } + + /* Return true if no active slot but multiport sent all its data */ + if (!active) + return (flags & IO_DONE) ? 1 : 0; + + /* Return false if invalid device code received */ + return invalid ? 0 : 1; +} + +/* + * Returns whether the multiport was placed into digital mode and + * able to communicate its state successfully. + */ + +static int multiport_init(struct grip_mp *grip) +{ + int dig_mode, initialized = 0, tries = 0; + u32 packet; + + dig_mode = dig_mode_start(grip->gameport, &packet); + while (!dig_mode && tries < 4) { + dig_mode = dig_mode_start(grip->gameport, &packet); + tries++; + } + + if (dig_mode) + dbg("multiport_init(): digital mode activated.\n"); + else { + dbg("multiport_init(): unable to activate digital mode.\n"); + return 0; + } + + /* Get packets, store multiport state, and check state's validity */ + for (tries = 0; tries < 4096; tries++) { + if ( slots_valid(grip) ) { + initialized = 1; + break; + } + } + dbg("multiport_init(): initialized == %d\n", initialized); + return initialized; +} + +/* + * Reports joystick state to the linux input layer. + */ + +static void report_slot(struct grip_mp *grip, int slot) +{ + struct input_dev *dev = &(grip->dev[slot]); + int i, buttons = grip->buttons[slot]; + + /* Store button states with linux input driver */ + + for (i = 0; i < 8; i++) + input_report_key(dev, grip_btn_gp[i], (buttons >> i) & 1); + + /* Store axis states with linux driver */ + + input_report_abs(dev, ABS_X, grip->xaxes[slot]); + input_report_abs(dev, ABS_Y, grip->yaxes[slot]); + + /* Tell the receiver of the events to process them */ + + input_sync(dev); + + grip->dirty[slot] = 0; +} + +/* + * Get the multiport state. + */ + +static void grip_poll(struct gameport *gameport) +{ + struct grip_mp *grip = gameport_get_drvdata(gameport); + int i, npkts, flags; + + for (npkts = 0; npkts < 4; npkts++) { + flags = IO_RETRY; + for (i = 0; i < 32; i++) { + flags = get_and_decode_packet(grip, flags); + if ((flags & IO_GOT_PACKET) || !(flags & IO_RETRY)) + break; + } + if (flags & IO_DONE) + break; + } + + for (i = 0; i < 4; i++) + if (grip->dirty[i]) + report_slot(grip, i); +} + +/* + * Called when a joystick device file is opened + */ + +static int grip_open(struct input_dev *dev) +{ + struct grip_mp *grip = dev->private; + + gameport_start_polling(grip->gameport); + return 0; +} + +/* + * Called when a joystick device file is closed + */ + +static void grip_close(struct input_dev *dev) +{ + struct grip_mp *grip = dev->private; + + gameport_start_polling(grip->gameport); +} + +/* + * Tell the linux input layer about a newly plugged-in gamepad. + */ + +static void register_slot(int slot, struct grip_mp *grip) +{ + int j, t; + + grip->dev[slot].private = grip; + grip->dev[slot].open = grip_open; + grip->dev[slot].close = grip_close; + grip->dev[slot].name = grip_name[grip->mode[slot]]; + grip->dev[slot].id.bustype = BUS_GAMEPORT; + grip->dev[slot].id.vendor = GAMEPORT_ID_VENDOR_GRAVIS; + grip->dev[slot].id.product = 0x0100 + grip->mode[slot]; + grip->dev[slot].id.version = 0x0100; + grip->dev[slot].evbit[0] = BIT(EV_KEY) | BIT(EV_ABS); + + for (j = 0; (t = grip_abs[grip->mode[slot]][j]) >= 0; j++) + input_set_abs_params(&grip->dev[slot], t, -1, 1, 0, 0); + + for (j = 0; (t = grip_btn[grip->mode[slot]][j]) >= 0; j++) + if (t > 0) + set_bit(t, grip->dev[slot].keybit); + + input_register_device(grip->dev + slot); + grip->registered[slot] = 1; + + if (grip->dirty[slot]) /* report initial state, if any */ + report_slot(grip, slot); + + printk(KERN_INFO "grip_mp: added %s, slot %d\n", + grip_name[grip->mode[slot]], slot); +} + +static int grip_connect(struct gameport *gameport, struct gameport_driver *drv) +{ + struct grip_mp *grip; + int err; + + if (!(grip = kcalloc(1, sizeof(struct grip_mp), GFP_KERNEL))) + return -ENOMEM; + + grip->gameport = gameport; + + gameport_set_drvdata(gameport, grip); + + err = gameport_open(gameport, drv, GAMEPORT_MODE_RAW); + if (err) + goto fail1; + + gameport_set_poll_handler(gameport, grip_poll); + gameport_set_poll_interval(gameport, 20); + + if (!multiport_init(grip)) { + err = -ENODEV; + goto fail2; + } + + if (!grip->mode[0] && !grip->mode[1] && !grip->mode[2] && !grip->mode[3]) { + /* nothing plugged in */ + err = -ENODEV; + goto fail2; + } + + return 0; + +fail2: gameport_close(gameport); +fail1: gameport_set_drvdata(gameport, NULL); + kfree(grip); + return err; +} + +static void grip_disconnect(struct gameport *gameport) +{ + struct grip_mp *grip = gameport_get_drvdata(gameport); + int i; + + for (i = 0; i < 4; i++) + if (grip->registered[i]) + input_unregister_device(grip->dev + i); + gameport_close(gameport); + gameport_set_drvdata(gameport, NULL); + kfree(grip); +} + +static struct gameport_driver grip_drv = { + .driver = { + .name = "grip_mp", + }, + .description = DRIVER_DESC, + .connect = grip_connect, + .disconnect = grip_disconnect, +}; + +static int __init grip_init(void) +{ + gameport_register_driver(&grip_drv); + return 0; +} + +static void __exit grip_exit(void) +{ + gameport_unregister_driver(&grip_drv); +} + +module_init(grip_init); +module_exit(grip_exit); diff --git a/drivers/input/joystick/guillemot.c b/drivers/input/joystick/guillemot.c new file mode 100644 index 000000000000..f93da7bc082d --- /dev/null +++ b/drivers/input/joystick/guillemot.c @@ -0,0 +1,289 @@ +/* + * $Id: guillemot.c,v 1.10 2002/01/22 20:28:12 vojtech Exp $ + * + * Copyright (c) 2001 Vojtech Pavlik + */ + +/* + * Guillemot Digital Interface Protocol driver for Linux + */ + +/* + * 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 either by + * e-mail - mail your message to <vojtech@ucw.cz>, or by paper mail: + * Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic + */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/gameport.h> +#include <linux/input.h> + +#define DRIVER_DESC "Guillemot Digital joystick driver" + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +#define GUILLEMOT_MAX_START 600 /* 600 us */ +#define GUILLEMOT_MAX_STROBE 60 /* 60 us */ +#define GUILLEMOT_MAX_LENGTH 17 /* 17 bytes */ + +static short guillemot_abs_pad[] = + { ABS_X, ABS_Y, ABS_THROTTLE, ABS_RUDDER, -1 }; + +static short guillemot_btn_pad[] = + { BTN_A, BTN_B, BTN_C, BTN_X, BTN_Y, BTN_Z, BTN_TL, BTN_TR, BTN_MODE, BTN_SELECT, -1 }; + +static struct { + int x; + int y; +} guillemot_hat_to_axis[16] = {{ 0,-1}, { 1,-1}, { 1, 0}, { 1, 1}, { 0, 1}, {-1, 1}, {-1, 0}, {-1,-1}}; + +struct guillemot_type { + unsigned char id; + short *abs; + short *btn; + int hat; + char *name; +}; + +struct guillemot { + struct gameport *gameport; + struct input_dev dev; + int bads; + int reads; + struct guillemot_type *type; + unsigned char length; + char phys[32]; +}; + +static struct guillemot_type guillemot_type[] = { + { 0x00, guillemot_abs_pad, guillemot_btn_pad, 1, "Guillemot Pad" }, + { 0 }}; + +/* + * guillemot_read_packet() reads Guillemot joystick data. + */ + +static int guillemot_read_packet(struct gameport *gameport, u8 *data) +{ + unsigned long flags; + unsigned char u, v; + unsigned int t, s; + int i; + + for (i = 0; i < GUILLEMOT_MAX_LENGTH; i++) + data[i] = 0; + + i = 0; + t = gameport_time(gameport, GUILLEMOT_MAX_START); + s = gameport_time(gameport, GUILLEMOT_MAX_STROBE); + + local_irq_save(flags); + gameport_trigger(gameport); + v = gameport_read(gameport); + + while (t > 0 && i < GUILLEMOT_MAX_LENGTH * 8) { + t--; + u = v; v = gameport_read(gameport); + if (v & ~u & 0x10) { + data[i >> 3] |= ((v >> 5) & 1) << (i & 7); + i++; + t = s; + } + } + + local_irq_restore(flags); + + return i; +} + +/* + * guillemot_poll() reads and analyzes Guillemot joystick data. + */ + +static void guillemot_poll(struct gameport *gameport) +{ + struct guillemot *guillemot = gameport_get_drvdata(gameport); + struct input_dev *dev = &guillemot->dev; + u8 data[GUILLEMOT_MAX_LENGTH]; + int i; + + guillemot->reads++; + + if (guillemot_read_packet(guillemot->gameport, data) != GUILLEMOT_MAX_LENGTH * 8 || + data[0] != 0x55 || data[16] != 0xaa) { + guillemot->bads++; + } else { + + for (i = 0; i < 6 && guillemot->type->abs[i] >= 0; i++) + input_report_abs(dev, guillemot->type->abs[i], data[i + 5]); + + if (guillemot->type->hat) { + input_report_abs(dev, ABS_HAT0X, guillemot_hat_to_axis[data[4] >> 4].x); + input_report_abs(dev, ABS_HAT0Y, guillemot_hat_to_axis[data[4] >> 4].y); + } + + for (i = 0; i < 16 && guillemot->type->btn[i] >= 0; i++) + input_report_key(dev, guillemot->type->btn[i], (data[2 + (i >> 3)] >> (i & 7)) & 1); + } + + input_sync(dev); +} + +/* + * guillemot_open() is a callback from the input open routine. + */ + +static int guillemot_open(struct input_dev *dev) +{ + struct guillemot *guillemot = dev->private; + + gameport_start_polling(guillemot->gameport); + return 0; +} + +/* + * guillemot_close() is a callback from the input close routine. + */ + +static void guillemot_close(struct input_dev *dev) +{ + struct guillemot *guillemot = dev->private; + + gameport_stop_polling(guillemot->gameport); +} + +/* + * guillemot_connect() probes for Guillemot joysticks. + */ + +static int guillemot_connect(struct gameport *gameport, struct gameport_driver *drv) +{ + struct guillemot *guillemot; + u8 data[GUILLEMOT_MAX_LENGTH]; + int i, t; + int err; + + if (!(guillemot = kcalloc(1, sizeof(struct guillemot), GFP_KERNEL))) + return -ENOMEM; + + guillemot->gameport = gameport; + + gameport_set_drvdata(gameport, guillemot); + + err = gameport_open(gameport, drv, GAMEPORT_MODE_RAW); + if (err) + goto fail1; + + i = guillemot_read_packet(gameport, data); + + if (i != GUILLEMOT_MAX_LENGTH * 8 || data[0] != 0x55 || data[16] != 0xaa) { + err = -ENODEV; + goto fail2; + } + + for (i = 0; guillemot_type[i].name; i++) + if (guillemot_type[i].id == data[11]) + break; + + if (!guillemot_type[i].name) { + printk(KERN_WARNING "guillemot.c: Unknown joystick on %s. [ %02x%02x:%04x, ver %d.%02d ]\n", + gameport->phys, data[12], data[13], data[11], data[14], data[15]); + err = -ENODEV; + goto fail2; + } + + gameport_set_poll_handler(gameport, guillemot_poll); + gameport_set_poll_interval(gameport, 20); + + sprintf(guillemot->phys, "%s/input0", gameport->phys); + + guillemot->type = guillemot_type + i; + + guillemot->dev.private = guillemot; + guillemot->dev.open = guillemot_open; + guillemot->dev.close = guillemot_close; + + guillemot->dev.name = guillemot_type[i].name; + guillemot->dev.phys = guillemot->phys; + guillemot->dev.id.bustype = BUS_GAMEPORT; + guillemot->dev.id.vendor = GAMEPORT_ID_VENDOR_GUILLEMOT; + guillemot->dev.id.product = guillemot_type[i].id; + guillemot->dev.id.version = (int)data[14] << 8 | data[15]; + + guillemot->dev.evbit[0] = BIT(EV_KEY) | BIT(EV_ABS); + + for (i = 0; (t = guillemot->type->abs[i]) >= 0; i++) + input_set_abs_params(&guillemot->dev, t, 0, 255, 0, 0); + + if (guillemot->type->hat) { + input_set_abs_params(&guillemot->dev, ABS_HAT0X, -1, 1, 0, 0); + input_set_abs_params(&guillemot->dev, ABS_HAT0Y, -1, 1, 0, 0); + } + + for (i = 0; (t = guillemot->type->btn[i]) >= 0; i++) + set_bit(t, guillemot->dev.keybit); + + input_register_device(&guillemot->dev); + printk(KERN_INFO "input: %s ver %d.%02d on %s\n", + guillemot->type->name, data[14], data[15], gameport->phys); + + return 0; + +fail2: gameport_close(gameport); +fail1: gameport_set_drvdata(gameport, NULL); + kfree(guillemot); + return err; +} + +static void guillemot_disconnect(struct gameport *gameport) +{ + struct guillemot *guillemot = gameport_get_drvdata(gameport); + + printk(KERN_INFO "guillemot.c: Failed %d reads out of %d on %s\n", guillemot->reads, guillemot->bads, guillemot->phys); + input_unregister_device(&guillemot->dev); + gameport_close(gameport); + kfree(guillemot); +} + +static struct gameport_driver guillemot_drv = { + .driver = { + .name = "guillemot", + }, + .description = DRIVER_DESC, + .connect = guillemot_connect, + .disconnect = guillemot_disconnect, +}; + +static int __init guillemot_init(void) +{ + gameport_register_driver(&guillemot_drv); + return 0; +} + +static void __exit guillemot_exit(void) +{ + gameport_unregister_driver(&guillemot_drv); +} + +module_init(guillemot_init); +module_exit(guillemot_exit); diff --git a/drivers/input/joystick/iforce/Kconfig b/drivers/input/joystick/iforce/Kconfig new file mode 100644 index 000000000000..8fde22a021b3 --- /dev/null +++ b/drivers/input/joystick/iforce/Kconfig @@ -0,0 +1,32 @@ +# +# I-Force driver configuration +# +config JOYSTICK_IFORCE + tristate "I-Force devices" + depends on INPUT && INPUT_JOYSTICK + help + Say Y here if you have an I-Force joystick or steering wheel + + You also must choose at least one of the two options below. + + To compile this driver as a module, choose M here: the + module will be called iforce. + +config JOYSTICK_IFORCE_USB + bool "I-Force USB joysticks and wheels" + depends on JOYSTICK_IFORCE && (JOYSTICK_IFORCE=m || USB=y) && USB + help + Say Y here if you have an I-Force joystick or steering wheel + connected to your USB port. + +config JOYSTICK_IFORCE_232 + bool "I-Force Serial joysticks and wheels" + depends on JOYSTICK_IFORCE && (JOYSTICK_IFORCE=m || SERIO=y) && SERIO + help + Say Y here if you have an I-Force joystick or steering wheel + connected to your serial (COM) port. + + You will need an additional utility called inputattach, see + <file:Documentation/input/joystick.txt> + and <file:Documentation/input/ff.txt>. + diff --git a/drivers/input/joystick/iforce/Makefile b/drivers/input/joystick/iforce/Makefile new file mode 100644 index 000000000000..17ae42bf9ffd --- /dev/null +++ b/drivers/input/joystick/iforce/Makefile @@ -0,0 +1,20 @@ +# +# Makefile for the I-Force driver +# +# By Johann Deneux <deneux@ifrance.com> +# + +# Goal definition +iforce-objs := iforce-ff.o iforce-main.o iforce-packets.o + +obj-$(CONFIG_JOYSTICK_IFORCE) += iforce.o + +ifeq ($(CONFIG_JOYSTICK_IFORCE_232),y) + iforce-objs += iforce-serio.o +endif + +ifeq ($(CONFIG_JOYSTICK_IFORCE_USB),y) + iforce-objs += iforce-usb.o +endif + +EXTRA_CFLAGS = -Werror-implicit-function-declaration diff --git a/drivers/input/joystick/iforce/iforce-ff.c b/drivers/input/joystick/iforce/iforce-ff.c new file mode 100644 index 000000000000..4678b6dab43b --- /dev/null +++ b/drivers/input/joystick/iforce/iforce-ff.c @@ -0,0 +1,543 @@ +/* + * $Id: iforce-ff.c,v 1.9 2002/02/02 19:28:35 jdeneux Exp $ + * + * Copyright (c) 2000-2002 Vojtech Pavlik <vojtech@ucw.cz> + * Copyright (c) 2001-2002 Johann Deneux <deneux@ifrance.com> + * + * USB/RS232 I-Force joysticks and wheels. + */ + +/* + * 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 either by + * e-mail - mail your message to <vojtech@ucw.cz>, or by paper mail: + * Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic + */ + +#include "iforce.h" + +/* + * Set the magnitude of a constant force effect + * Return error code + * + * Note: caller must ensure exclusive access to device + */ + +static int make_magnitude_modifier(struct iforce* iforce, + struct resource* mod_chunk, int no_alloc, __s16 level) +{ + unsigned char data[3]; + + if (!no_alloc) { + down(&iforce->mem_mutex); + if (allocate_resource(&(iforce->device_memory), mod_chunk, 2, + iforce->device_memory.start, iforce->device_memory.end, 2L, + NULL, NULL)) { + up(&iforce->mem_mutex); + return -ENOMEM; + } + up(&iforce->mem_mutex); + } + + data[0] = LO(mod_chunk->start); + data[1] = HI(mod_chunk->start); + data[2] = HIFIX80(level); + + iforce_send_packet(iforce, FF_CMD_MAGNITUDE, data); + + iforce_dump_packet("magnitude: ", FF_CMD_MAGNITUDE, data); + return 0; +} + +/* + * Upload the component of an effect dealing with the period, phase and magnitude + */ + +static int make_period_modifier(struct iforce* iforce, + struct resource* mod_chunk, int no_alloc, + __s16 magnitude, __s16 offset, u16 period, u16 phase) +{ + unsigned char data[7]; + + period = TIME_SCALE(period); + + if (!no_alloc) { + down(&iforce->mem_mutex); + if (allocate_resource(&(iforce->device_memory), mod_chunk, 0x0c, + iforce->device_memory.start, iforce->device_memory.end, 2L, + NULL, NULL)) { + up(&iforce->mem_mutex); + return -ENOMEM; + } + up(&iforce->mem_mutex); + } + + data[0] = LO(mod_chunk->start); + data[1] = HI(mod_chunk->start); + + data[2] = HIFIX80(magnitude); + data[3] = HIFIX80(offset); + data[4] = HI(phase); + + data[5] = LO(period); + data[6] = HI(period); + + iforce_send_packet(iforce, FF_CMD_PERIOD, data); + + return 0; +} + +/* + * Uploads the part of an effect setting the envelope of the force + */ + +static int make_envelope_modifier(struct iforce* iforce, + struct resource* mod_chunk, int no_alloc, + u16 attack_duration, __s16 initial_level, + u16 fade_duration, __s16 final_level) +{ + unsigned char data[8]; + + attack_duration = TIME_SCALE(attack_duration); + fade_duration = TIME_SCALE(fade_duration); + + if (!no_alloc) { + down(&iforce->mem_mutex); + if (allocate_resource(&(iforce->device_memory), mod_chunk, 0x0e, + iforce->device_memory.start, iforce->device_memory.end, 2L, + NULL, NULL)) { + up(&iforce->mem_mutex); + return -ENOMEM; + } + up(&iforce->mem_mutex); + } + + data[0] = LO(mod_chunk->start); + data[1] = HI(mod_chunk->start); + + data[2] = LO(attack_duration); + data[3] = HI(attack_duration); + data[4] = HI(initial_level); + + data[5] = LO(fade_duration); + data[6] = HI(fade_duration); + data[7] = HI(final_level); + + iforce_send_packet(iforce, FF_CMD_ENVELOPE, data); + + return 0; +} + +/* + * Component of spring, friction, inertia... effects + */ + +static int make_condition_modifier(struct iforce* iforce, + struct resource* mod_chunk, int no_alloc, + __u16 rsat, __u16 lsat, __s16 rk, __s16 lk, u16 db, __s16 center) +{ + unsigned char data[10]; + + if (!no_alloc) { + down(&iforce->mem_mutex); + if (allocate_resource(&(iforce->device_memory), mod_chunk, 8, + iforce->device_memory.start, iforce->device_memory.end, 2L, + NULL, NULL)) { + up(&iforce->mem_mutex); + return -ENOMEM; + } + up(&iforce->mem_mutex); + } + + data[0] = LO(mod_chunk->start); + data[1] = HI(mod_chunk->start); + + data[2] = (100*rk)>>15; /* Dangerous: the sign is extended by gcc on plateforms providing an arith shift */ + data[3] = (100*lk)>>15; /* This code is incorrect on cpus lacking arith shift */ + + center = (500*center)>>15; + data[4] = LO(center); + data[5] = HI(center); + + db = (1000*db)>>16; + data[6] = LO(db); + data[7] = HI(db); + + data[8] = (100*rsat)>>16; + data[9] = (100*lsat)>>16; + + iforce_send_packet(iforce, FF_CMD_CONDITION, data); + iforce_dump_packet("condition", FF_CMD_CONDITION, data); + + return 0; +} + +static unsigned char find_button(struct iforce *iforce, signed short button) +{ + int i; + for (i = 1; iforce->type->btn[i] >= 0; i++) + if (iforce->type->btn[i] == button) + return i + 1; + return 0; +} + +/* + * Analyse the changes in an effect, and tell if we need to send an condition + * parameter packet + */ +static int need_condition_modifier(struct iforce* iforce, struct ff_effect* new) +{ + int id = new->id; + struct ff_effect* old = &iforce->core_effects[id].effect; + int ret=0; + int i; + + if (new->type != FF_SPRING && new->type != FF_FRICTION) { + printk(KERN_WARNING "iforce.c: bad effect type in need_condition_modifier\n"); + return FALSE; + } + + for(i=0; i<2; i++) { + ret |= old->u.condition[i].right_saturation != new->u.condition[i].right_saturation + || old->u.condition[i].left_saturation != new->u.condition[i].left_saturation + || old->u.condition[i].right_coeff != new->u.condition[i].right_coeff + || old->u.condition[i].left_coeff != new->u.condition[i].left_coeff + || old->u.condition[i].deadband != new->u.condition[i].deadband + || old->u.condition[i].center != new->u.condition[i].center; + } + return ret; +} + +/* + * Analyse the changes in an effect, and tell if we need to send a magnitude + * parameter packet + */ +static int need_magnitude_modifier(struct iforce* iforce, struct ff_effect* effect) +{ + int id = effect->id; + struct ff_effect* old = &iforce->core_effects[id].effect; + + if (effect->type != FF_CONSTANT) { + printk(KERN_WARNING "iforce.c: bad effect type in need_envelope_modifier\n"); + return FALSE; + } + + return (old->u.constant.level != effect->u.constant.level); +} + +/* + * Analyse the changes in an effect, and tell if we need to send an envelope + * parameter packet + */ +static int need_envelope_modifier(struct iforce* iforce, struct ff_effect* effect) +{ + int id = effect->id; + struct ff_effect* old = &iforce->core_effects[id].effect; + + switch (effect->type) { + case FF_CONSTANT: + if (old->u.constant.envelope.attack_length != effect->u.constant.envelope.attack_length + || old->u.constant.envelope.attack_level != effect->u.constant.envelope.attack_level + || old->u.constant.envelope.fade_length != effect->u.constant.envelope.fade_length + || old->u.constant.envelope.fade_level != effect->u.constant.envelope.fade_level) + return TRUE; + break; + + case FF_PERIODIC: + if (old->u.periodic.envelope.attack_length != effect->u.periodic.envelope.attack_length + || old->u.periodic.envelope.attack_level != effect->u.periodic.envelope.attack_level + || old->u.periodic.envelope.fade_length != effect->u.periodic.envelope.fade_length + || old->u.periodic.envelope.fade_level != effect->u.periodic.envelope.fade_level) + return TRUE; + break; + + default: + printk(KERN_WARNING "iforce.c: bad effect type in need_envelope_modifier\n"); + } + + return FALSE; +} + +/* + * Analyse the changes in an effect, and tell if we need to send a periodic + * parameter effect + */ +static int need_period_modifier(struct iforce* iforce, struct ff_effect* new) +{ + int id = new->id; + struct ff_effect* old = &iforce->core_effects[id].effect; + + if (new->type != FF_PERIODIC) { + printk(KERN_WARNING "iforce.c: bad effect type in need_periodic_modifier\n"); + return FALSE; + } + + return (old->u.periodic.period != new->u.periodic.period + || old->u.periodic.magnitude != new->u.periodic.magnitude + || old->u.periodic.offset != new->u.periodic.offset + || old->u.periodic.phase != new->u.periodic.phase); +} + +/* + * Analyse the changes in an effect, and tell if we need to send an effect + * packet + */ +static int need_core(struct iforce* iforce, struct ff_effect* new) +{ + int id = new->id; + struct ff_effect* old = &iforce->core_effects[id].effect; + + if (old->direction != new->direction + || old->trigger.button != new->trigger.button + || old->trigger.interval != new->trigger.interval + || old->replay.length != new->replay.length + || old->replay.delay != new->replay.delay) + return TRUE; + + return FALSE; +} +/* + * Send the part common to all effects to the device + */ +static int make_core(struct iforce* iforce, u16 id, u16 mod_id1, u16 mod_id2, + u8 effect_type, u8 axes, u16 duration, u16 delay, u16 button, + u16 interval, u16 direction) +{ + unsigned char data[14]; + + duration = TIME_SCALE(duration); + delay = TIME_SCALE(delay); + interval = TIME_SCALE(interval); + + data[0] = LO(id); + data[1] = effect_type; + data[2] = LO(axes) | find_button(iforce, button); + + data[3] = LO(duration); + data[4] = HI(duration); + + data[5] = HI(direction); + + data[6] = LO(interval); + data[7] = HI(interval); + + data[8] = LO(mod_id1); + data[9] = HI(mod_id1); + data[10] = LO(mod_id2); + data[11] = HI(mod_id2); + + data[12] = LO(delay); + data[13] = HI(delay); + + /* Stop effect */ +/* iforce_control_playback(iforce, id, 0);*/ + + iforce_send_packet(iforce, FF_CMD_EFFECT, data); + + /* If needed, restart effect */ + if (test_bit(FF_CORE_SHOULD_PLAY, iforce->core_effects[id].flags)) { + /* BUG: perhaps we should replay n times, instead of 1. But we do not know n */ + iforce_control_playback(iforce, id, 1); + } + + return 0; +} + +/* + * Upload a periodic effect to the device + * See also iforce_upload_constant. + */ +int iforce_upload_periodic(struct iforce* iforce, struct ff_effect* effect, int is_update) +{ + u8 wave_code; + int core_id = effect->id; + struct iforce_core_effect* core_effect = iforce->core_effects + core_id; + struct resource* mod1_chunk = &(iforce->core_effects[core_id].mod1_chunk); + struct resource* mod2_chunk = &(iforce->core_effects[core_id].mod2_chunk); + int param1_err = 1; + int param2_err = 1; + int core_err = 0; + + if (!is_update || need_period_modifier(iforce, effect)) { + param1_err = make_period_modifier(iforce, mod1_chunk, + is_update, + effect->u.periodic.magnitude, effect->u.periodic.offset, + effect->u.periodic.period, effect->u.periodic.phase); + if (param1_err) return param1_err; + set_bit(FF_MOD1_IS_USED, core_effect->flags); + } + + if (!is_update || need_envelope_modifier(iforce, effect)) { + param2_err = make_envelope_modifier(iforce, mod2_chunk, + is_update, + effect->u.periodic.envelope.attack_length, + effect->u.periodic.envelope.attack_level, + effect->u.periodic.envelope.fade_length, + effect->u.periodic.envelope.fade_level); + if (param2_err) return param2_err; + set_bit(FF_MOD2_IS_USED, core_effect->flags); + } + + switch (effect->u.periodic.waveform) { + case FF_SQUARE: wave_code = 0x20; break; + case FF_TRIANGLE: wave_code = 0x21; break; + case FF_SINE: wave_code = 0x22; break; + case FF_SAW_UP: wave_code = 0x23; break; + case FF_SAW_DOWN: wave_code = 0x24; break; + default: wave_code = 0x20; break; + } + + if (!is_update || need_core(iforce, effect)) { + core_err = make_core(iforce, effect->id, + mod1_chunk->start, + mod2_chunk->start, + wave_code, + 0x20, + effect->replay.length, + effect->replay.delay, + effect->trigger.button, + effect->trigger.interval, + effect->direction); + } + + /* If one of the parameter creation failed, we already returned an + * error code. + * If the core creation failed, we return its error code. + * Else: if one parameter at least was created, we return 0 + * else we return 1; + */ + return core_err < 0 ? core_err : (param1_err && param2_err); +} + +/* + * Upload a constant force effect + * Return value: + * <0 Error code + * 0 Ok, effect created or updated + * 1 effect did not change since last upload, and no packet was therefore sent + */ +int iforce_upload_constant(struct iforce* iforce, struct ff_effect* effect, int is_update) +{ + int core_id = effect->id; + struct iforce_core_effect* core_effect = iforce->core_effects + core_id; + struct resource* mod1_chunk = &(iforce->core_effects[core_id].mod1_chunk); + struct resource* mod2_chunk = &(iforce->core_effects[core_id].mod2_chunk); + int param1_err = 1; + int param2_err = 1; + int core_err = 0; + + if (!is_update || need_magnitude_modifier(iforce, effect)) { + param1_err = make_magnitude_modifier(iforce, mod1_chunk, + is_update, + effect->u.constant.level); + if (param1_err) return param1_err; + set_bit(FF_MOD1_IS_USED, core_effect->flags); + } + + if (!is_update || need_envelope_modifier(iforce, effect)) { + param2_err = make_envelope_modifier(iforce, mod2_chunk, + is_update, + effect->u.constant.envelope.attack_length, + effect->u.constant.envelope.attack_level, + effect->u.constant.envelope.fade_length, + effect->u.constant.envelope.fade_level); + if (param2_err) return param2_err; + set_bit(FF_MOD2_IS_USED, core_effect->flags); + } + + if (!is_update || need_core(iforce, effect)) { + core_err = make_core(iforce, effect->id, + mod1_chunk->start, + mod2_chunk->start, + 0x00, + 0x20, + effect->replay.length, + effect->replay.delay, + effect->trigger.button, + effect->trigger.interval, + effect->direction); + } + + /* If one of the parameter creation failed, we already returned an + * error code. + * If the core creation failed, we return its error code. + * Else: if one parameter at least was created, we return 0 + * else we return 1; + */ + return core_err < 0 ? core_err : (param1_err && param2_err); +} + +/* + * Upload an condition effect. Those are for example friction, inertia, springs... + */ +int iforce_upload_condition(struct iforce* iforce, struct ff_effect* effect, int is_update) +{ + int core_id = effect->id; + struct iforce_core_effect* core_effect = iforce->core_effects + core_id; + struct resource* mod1_chunk = &(core_effect->mod1_chunk); + struct resource* mod2_chunk = &(core_effect->mod2_chunk); + u8 type; + int param_err = 1; + int core_err = 0; + + switch (effect->type) { + case FF_SPRING: type = 0x40; break; + case FF_DAMPER: type = 0x41; break; + default: return -1; + } + + if (!is_update || need_condition_modifier(iforce, effect)) { + param_err = make_condition_modifier(iforce, mod1_chunk, + is_update, + effect->u.condition[0].right_saturation, + effect->u.condition[0].left_saturation, + effect->u.condition[0].right_coeff, + effect->u.condition[0].left_coeff, + effect->u.condition[0].deadband, + effect->u.condition[0].center); + if (param_err) return param_err; + set_bit(FF_MOD1_IS_USED, core_effect->flags); + + param_err = make_condition_modifier(iforce, mod2_chunk, + is_update, + effect->u.condition[1].right_saturation, + effect->u.condition[1].left_saturation, + effect->u.condition[1].right_coeff, + effect->u.condition[1].left_coeff, + effect->u.condition[1].deadband, + effect->u.condition[1].center); + if (param_err) return param_err; + set_bit(FF_MOD2_IS_USED, core_effect->flags); + + } + + if (!is_update || need_core(iforce, effect)) { + core_err = make_core(iforce, effect->id, + mod1_chunk->start, mod2_chunk->start, + type, 0xc0, + effect->replay.length, effect->replay.delay, + effect->trigger.button, effect->trigger.interval, + effect->direction); + } + + /* If the parameter creation failed, we already returned an + * error code. + * If the core creation failed, we return its error code. + * Else: if a parameter was created, we return 0 + * else we return 1; + */ + return core_err < 0 ? core_err : param_err; +} diff --git a/drivers/input/joystick/iforce/iforce-main.c b/drivers/input/joystick/iforce/iforce-main.c new file mode 100644 index 000000000000..028f3513629a --- /dev/null +++ b/drivers/input/joystick/iforce/iforce-main.c @@ -0,0 +1,557 @@ +/* + * $Id: iforce-main.c,v 1.19 2002/07/07 10:22:50 jdeneux Exp $ + * + * Copyright (c) 2000-2002 Vojtech Pavlik <vojtech@ucw.cz> + * Copyright (c) 2001-2002 Johann Deneux <deneux@ifrance.com> + * + * USB/RS232 I-Force joysticks and wheels. + */ + +/* + * 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 either by + * e-mail - mail your message to <vojtech@ucw.cz>, or by paper mail: + * Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic + */ + +#include "iforce.h" + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>, Johann Deneux <deneux@ifrance.com>"); +MODULE_DESCRIPTION("USB/RS232 I-Force joysticks and wheels driver"); +MODULE_LICENSE("GPL"); + +static signed short btn_joystick[] = +{ BTN_TRIGGER, BTN_TOP, BTN_THUMB, BTN_TOP2, BTN_BASE, + BTN_BASE2, BTN_BASE3, BTN_BASE4, BTN_BASE5, BTN_A, BTN_B, BTN_C, -1 }; + +static signed short btn_avb_pegasus[] = +{ BTN_TRIGGER, BTN_TOP, BTN_THUMB, BTN_TOP2, BTN_BASE, + BTN_BASE2, BTN_BASE3, BTN_BASE4, -1 }; + +static signed short btn_wheel[] = +{ BTN_TRIGGER, BTN_TOP, BTN_THUMB, BTN_TOP2, BTN_BASE, + BTN_BASE2, BTN_BASE3, BTN_BASE4, BTN_BASE5, BTN_A, BTN_B, BTN_C, -1 }; + +static signed short btn_avb_tw[] = +{ BTN_TRIGGER, BTN_THUMB, BTN_TOP, BTN_TOP2, BTN_BASE, + BTN_BASE2, BTN_BASE3, BTN_BASE4, -1 }; + +static signed short btn_avb_wheel[] = +{ BTN_GEAR_DOWN, BTN_GEAR_UP, BTN_BASE, BTN_BASE2, BTN_BASE3, + BTN_BASE4, BTN_BASE5, BTN_BASE6, -1 }; + +static signed short abs_joystick[] = +{ ABS_X, ABS_Y, ABS_THROTTLE, ABS_HAT0X, ABS_HAT0Y, -1 }; + +static signed short abs_avb_pegasus[] = +{ ABS_X, ABS_Y, ABS_THROTTLE, ABS_RUDDER, ABS_HAT0X, ABS_HAT0Y, + ABS_HAT1X, ABS_HAT1Y, -1 }; + +static signed short abs_wheel[] = +{ ABS_WHEEL, ABS_GAS, ABS_BRAKE, ABS_HAT0X, ABS_HAT0Y, -1 }; + +static signed short ff_iforce[] = +{ FF_PERIODIC, FF_CONSTANT, FF_SPRING, FF_DAMPER, + FF_SQUARE, FF_TRIANGLE, FF_SINE, FF_SAW_UP, FF_SAW_DOWN, FF_GAIN, + FF_AUTOCENTER, -1 }; + +static struct iforce_device iforce_device[] = { + { 0x044f, 0xa01c, "Thrustmaster Motor Sport GT", btn_wheel, abs_wheel, ff_iforce }, + { 0x046d, 0xc281, "Logitech WingMan Force", btn_joystick, abs_joystick, ff_iforce }, + { 0x046d, 0xc291, "Logitech WingMan Formula Force", btn_wheel, abs_wheel, ff_iforce }, + { 0x05ef, 0x020a, "AVB Top Shot Pegasus", btn_avb_pegasus, abs_avb_pegasus, ff_iforce }, + { 0x05ef, 0x8884, "AVB Mag Turbo Force", btn_avb_wheel, abs_wheel, ff_iforce }, + { 0x05ef, 0x8888, "AVB Top Shot Force Feedback Racing Wheel", btn_avb_tw, abs_wheel, ff_iforce }, //? + { 0x061c, 0xc0a4, "ACT LABS Force RS", btn_wheel, abs_wheel, ff_iforce }, //? + { 0x06f8, 0x0001, "Guillemot Race Leader Force Feedback", btn_wheel, abs_wheel, ff_iforce }, //? + { 0x06f8, 0x0004, "Guillemot Force Feedback Racing Wheel", btn_wheel, abs_wheel, ff_iforce }, //? + { 0x0000, 0x0000, "Unknown I-Force Device [%04x:%04x]", btn_joystick, abs_joystick, ff_iforce } +}; + + + +static int iforce_input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) +{ + struct iforce* iforce = (struct iforce*)(dev->private); + unsigned char data[3]; + + if (type != EV_FF) + return -1; + + switch (code) { + + case FF_GAIN: + + data[0] = value >> 9; + iforce_send_packet(iforce, FF_CMD_GAIN, data); + + return 0; + + case FF_AUTOCENTER: + + data[0] = 0x03; + data[1] = value >> 9; + iforce_send_packet(iforce, FF_CMD_AUTOCENTER, data); + + data[0] = 0x04; + data[1] = 0x01; + iforce_send_packet(iforce, FF_CMD_AUTOCENTER, data); + + return 0; + + default: /* Play or stop an effect */ + + if (!CHECK_OWNERSHIP(code, iforce)) { + return -1; + } + if (value > 0) { + set_bit(FF_CORE_SHOULD_PLAY, iforce->core_effects[code].flags); + } + else { + clear_bit(FF_CORE_SHOULD_PLAY, iforce->core_effects[code].flags); + } + + iforce_control_playback(iforce, code, value); + return 0; + } + + return -1; +} + +/* + * Function called when an ioctl is performed on the event dev entry. + * It uploads an effect to the device + */ +static int iforce_upload_effect(struct input_dev *dev, struct ff_effect *effect) +{ + struct iforce* iforce = (struct iforce*)(dev->private); + int id; + int ret; + int is_update; + +/* Check this effect type is supported by this device */ + if (!test_bit(effect->type, iforce->dev.ffbit)) + return -EINVAL; + +/* + * If we want to create a new effect, get a free id + */ + if (effect->id == -1) { + + for (id=0; id < FF_EFFECTS_MAX; ++id) + if (!test_and_set_bit(FF_CORE_IS_USED, iforce->core_effects[id].flags)) break; + + if ( id == FF_EFFECTS_MAX || id >= iforce->dev.ff_effects_max) + return -ENOMEM; + + effect->id = id; + iforce->core_effects[id].owner = current->pid; + iforce->core_effects[id].flags[0] = (1<<FF_CORE_IS_USED); /* Only IS_USED bit must be set */ + + is_update = FALSE; + } + else { + /* We want to update an effect */ + if (!CHECK_OWNERSHIP(effect->id, iforce)) return -EACCES; + + /* Parameter type cannot be updated */ + if (effect->type != iforce->core_effects[effect->id].effect.type) + return -EINVAL; + + /* Check the effect is not already being updated */ + if (test_bit(FF_CORE_UPDATE, iforce->core_effects[effect->id].flags)) { + return -EAGAIN; + } + + is_update = TRUE; + } + +/* + * Upload the effect + */ + switch (effect->type) { + + case FF_PERIODIC: + ret = iforce_upload_periodic(iforce, effect, is_update); + break; + + case FF_CONSTANT: + ret = iforce_upload_constant(iforce, effect, is_update); + break; + + case FF_SPRING: + case FF_DAMPER: + ret = iforce_upload_condition(iforce, effect, is_update); + break; + + default: + return -EINVAL; + } + if (ret == 0) { + /* A packet was sent, forbid new updates until we are notified + * that the packet was updated + */ + set_bit(FF_CORE_UPDATE, iforce->core_effects[effect->id].flags); + } + iforce->core_effects[effect->id].effect = *effect; + return ret; +} + +/* + * Erases an effect: it frees the effect id and mark as unused the memory + * allocated for the parameters + */ +static int iforce_erase_effect(struct input_dev *dev, int effect_id) +{ + struct iforce* iforce = (struct iforce*)(dev->private); + int err = 0; + struct iforce_core_effect* core_effect; + + /* Check who is trying to erase this effect */ + if (iforce->core_effects[effect_id].owner != current->pid) { + printk(KERN_WARNING "iforce-main.c: %d tried to erase an effect belonging to %d\n", current->pid, iforce->core_effects[effect_id].owner); + return -EACCES; + } + + if (effect_id < 0 || effect_id >= FF_EFFECTS_MAX) + return -EINVAL; + + core_effect = iforce->core_effects + effect_id; + + if (test_bit(FF_MOD1_IS_USED, core_effect->flags)) + err = release_resource(&(iforce->core_effects[effect_id].mod1_chunk)); + + if (!err && test_bit(FF_MOD2_IS_USED, core_effect->flags)) + err = release_resource(&(iforce->core_effects[effect_id].mod2_chunk)); + + /*TODO: remember to change that if more FF_MOD* bits are added */ + core_effect->flags[0] = 0; + + return err; +} + +static int iforce_open(struct input_dev *dev) +{ + struct iforce *iforce = dev->private; + + switch (iforce->bus) { +#ifdef CONFIG_JOYSTICK_IFORCE_USB + case IFORCE_USB: + iforce->irq->dev = iforce->usbdev; + if (usb_submit_urb(iforce->irq, GFP_KERNEL)) + return -EIO; + break; +#endif + } + + /* Enable force feedback */ + iforce_send_packet(iforce, FF_CMD_ENABLE, "\004"); + + return 0; +} + +static int iforce_flush(struct input_dev *dev, struct file *file) +{ + struct iforce *iforce = dev->private; + int i; + + /* Erase all effects this process owns */ + for (i=0; i<dev->ff_effects_max; ++i) { + + if (test_bit(FF_CORE_IS_USED, iforce->core_effects[i].flags) && + current->pid == iforce->core_effects[i].owner) { + + /* Stop effect */ + input_report_ff(dev, i, 0); + + /* Free ressources assigned to effect */ + if (iforce_erase_effect(dev, i)) { + printk(KERN_WARNING "iforce_flush: erase effect %d failed\n", i); + } + } + + } + return 0; +} + +static void iforce_release(struct input_dev *dev) +{ + struct iforce *iforce = dev->private; + int i; + + /* Check: no effect should be present in memory */ + for (i=0; i<dev->ff_effects_max; ++i) { + if (test_bit(FF_CORE_IS_USED, iforce->core_effects[i].flags)) + break; + } + if (i<dev->ff_effects_max) { + printk(KERN_WARNING "iforce_release: Device still owns effects\n"); + } + + /* Disable force feedback playback */ + iforce_send_packet(iforce, FF_CMD_ENABLE, "\001"); + + switch (iforce->bus) { +#ifdef CONFIG_JOYSTICK_IFORCE_USB + case IFORCE_USB: + usb_unlink_urb(iforce->irq); + + /* The device was unplugged before the file + * was released */ + if (iforce->usbdev == NULL) { + iforce_delete_device(iforce); + kfree(iforce); + } + break; +#endif + } +} + +void iforce_delete_device(struct iforce *iforce) +{ + switch (iforce->bus) { +#ifdef CONFIG_JOYSTICK_IFORCE_USB + case IFORCE_USB: + iforce_usb_delete(iforce); + break; +#endif +#ifdef CONFIG_JOYSTICK_IFORCE_232 + case IFORCE_232: + //TODO: Wait for the last packets to be sent + break; +#endif + } +} + +int iforce_init_device(struct iforce *iforce) +{ + unsigned char c[] = "CEOV"; + int i; + + init_waitqueue_head(&iforce->wait); + spin_lock_init(&iforce->xmit_lock); + init_MUTEX(&iforce->mem_mutex); + iforce->xmit.buf = iforce->xmit_data; + + iforce->dev.ff_effects_max = 10; + +/* + * Input device fields. + */ + + switch (iforce->bus) { +#ifdef CONFIG_JOYSTICK_IFORCE_USB + case IFORCE_USB: + iforce->dev.id.bustype = BUS_USB; + iforce->dev.dev = &iforce->usbdev->dev; + break; +#endif +#ifdef CONFIG_JOYSTICK_IFORCE_232 + case IFORCE_232: + iforce->dev.id.bustype = BUS_RS232; + iforce->dev.dev = &iforce->serio->dev; + break; +#endif + } + + iforce->dev.private = iforce; + iforce->dev.name = "Unknown I-Force device"; + iforce->dev.open = iforce_open; + iforce->dev.close = iforce_release; + iforce->dev.flush = iforce_flush; + iforce->dev.event = iforce_input_event; + iforce->dev.upload_effect = iforce_upload_effect; + iforce->dev.erase_effect = iforce_erase_effect; + +/* + * On-device memory allocation. + */ + + iforce->device_memory.name = "I-Force device effect memory"; + iforce->device_memory.start = 0; + iforce->device_memory.end = 200; + iforce->device_memory.flags = IORESOURCE_MEM; + iforce->device_memory.parent = NULL; + iforce->device_memory.child = NULL; + iforce->device_memory.sibling = NULL; + +/* + * Wait until device ready - until it sends its first response. + */ + + for (i = 0; i < 20; i++) + if (!iforce_get_id_packet(iforce, "O")) + break; + + if (i == 20) { /* 5 seconds */ + printk(KERN_ERR "iforce-main.c: Timeout waiting for response from device.\n"); + return -1; + } + +/* + * Get device info. + */ + + if (!iforce_get_id_packet(iforce, "M")) + iforce->dev.id.vendor = (iforce->edata[2] << 8) | iforce->edata[1]; + else + printk(KERN_WARNING "iforce-main.c: Device does not respond to id packet M\n"); + + if (!iforce_get_id_packet(iforce, "P")) + iforce->dev.id.product = (iforce->edata[2] << 8) | iforce->edata[1]; + else + printk(KERN_WARNING "iforce-main.c: Device does not respond to id packet P\n"); + + if (!iforce_get_id_packet(iforce, "B")) + iforce->device_memory.end = (iforce->edata[2] << 8) | iforce->edata[1]; + else + printk(KERN_WARNING "iforce-main.c: Device does not respond to id packet B\n"); + + if (!iforce_get_id_packet(iforce, "N")) + iforce->dev.ff_effects_max = iforce->edata[1]; + else + printk(KERN_WARNING "iforce-main.c: Device does not respond to id packet N\n"); + + /* Check if the device can store more effects than the driver can really handle */ + if (iforce->dev.ff_effects_max > FF_EFFECTS_MAX) { + printk(KERN_WARNING "input??: Device can handle %d effects, but N_EFFECTS_MAX is set to %d in iforce.h\n", + iforce->dev.ff_effects_max, FF_EFFECTS_MAX); + iforce->dev.ff_effects_max = FF_EFFECTS_MAX; + } + +/* + * Display additional info. + */ + + for (i = 0; c[i]; i++) + if (!iforce_get_id_packet(iforce, c + i)) + iforce_dump_packet("info", iforce->ecmd, iforce->edata); + +/* + * Disable spring, enable force feedback. + * FIXME: We should use iforce_set_autocenter() et al here. + */ + + iforce_send_packet(iforce, FF_CMD_AUTOCENTER, "\004\000"); + +/* + * Find appropriate device entry + */ + + for (i = 0; iforce_device[i].idvendor; i++) + if (iforce_device[i].idvendor == iforce->dev.id.vendor && + iforce_device[i].idproduct == iforce->dev.id.product) + break; + + iforce->type = iforce_device + i; + iforce->dev.name = iforce->type->name; + +/* + * Set input device bitfields and ranges. + */ + + iforce->dev.evbit[0] = BIT(EV_KEY) | BIT(EV_ABS) | BIT(EV_FF) | BIT(EV_FF_STATUS); + + for (i = 0; iforce->type->btn[i] >= 0; i++) { + signed short t = iforce->type->btn[i]; + set_bit(t, iforce->dev.keybit); + } + set_bit(BTN_DEAD, iforce->dev.keybit); + + for (i = 0; iforce->type->abs[i] >= 0; i++) { + + signed short t = iforce->type->abs[i]; + set_bit(t, iforce->dev.absbit); + + switch (t) { + + case ABS_X: + case ABS_Y: + case ABS_WHEEL: + + iforce->dev.absmax[t] = 1920; + iforce->dev.absmin[t] = -1920; + iforce->dev.absflat[t] = 128; + iforce->dev.absfuzz[t] = 16; + + set_bit(t, iforce->dev.ffbit); + break; + + case ABS_THROTTLE: + case ABS_GAS: + case ABS_BRAKE: + + iforce->dev.absmax[t] = 255; + iforce->dev.absmin[t] = 0; + break; + + case ABS_RUDDER: + + iforce->dev.absmax[t] = 127; + iforce->dev.absmin[t] = -128; + break; + + case ABS_HAT0X: + case ABS_HAT0Y: + case ABS_HAT1X: + case ABS_HAT1Y: + iforce->dev.absmax[t] = 1; + iforce->dev.absmin[t] = -1; + break; + } + } + + for (i = 0; iforce->type->ff[i] >= 0; i++) + set_bit(iforce->type->ff[i], iforce->dev.ffbit); + +/* + * Register input device. + */ + + input_register_device(&iforce->dev); + + printk(KERN_DEBUG "iforce->dev.open = %p\n", iforce->dev.open); + + printk(KERN_INFO "input: %s [%d effects, %ld bytes memory]\n", + iforce->dev.name, iforce->dev.ff_effects_max, + iforce->device_memory.end); + + return 0; +} + +static int __init iforce_init(void) +{ +#ifdef CONFIG_JOYSTICK_IFORCE_USB + usb_register(&iforce_usb_driver); +#endif +#ifdef CONFIG_JOYSTICK_IFORCE_232 + serio_register_driver(&iforce_serio_drv); +#endif + return 0; +} + +static void __exit iforce_exit(void) +{ +#ifdef CONFIG_JOYSTICK_IFORCE_USB + usb_deregister(&iforce_usb_driver); +#endif +#ifdef CONFIG_JOYSTICK_IFORCE_232 + serio_unregister_driver(&iforce_serio_drv); +#endif +} + +module_init(iforce_init); +module_exit(iforce_exit); diff --git a/drivers/input/joystick/iforce/iforce-packets.c b/drivers/input/joystick/iforce/iforce-packets.c new file mode 100644 index 000000000000..58728ebaaf80 --- /dev/null +++ b/drivers/input/joystick/iforce/iforce-packets.c @@ -0,0 +1,319 @@ +/* + * $Id: iforce-packets.c,v 1.16 2002/07/07 10:22:50 jdeneux Exp $ + * + * Copyright (c) 2000-2002 Vojtech Pavlik <vojtech@ucw.cz> + * Copyright (c) 2001-2002 Johann Deneux <deneux@ifrance.com> + * + * USB/RS232 I-Force joysticks and wheels. + */ + +/* + * 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 either by + * e-mail - mail your message to <vojtech@ucw.cz>, or by paper mail: + * Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic + */ + +#include "iforce.h" + +static struct { + __s32 x; + __s32 y; +} iforce_hat_to_axis[16] = {{ 0,-1}, { 1,-1}, { 1, 0}, { 1, 1}, { 0, 1}, {-1, 1}, {-1, 0}, {-1,-1}}; + + +void iforce_dump_packet(char *msg, u16 cmd, unsigned char *data) +{ + int i; + + printk(KERN_DEBUG "iforce.c: %s ( cmd = %04x, data = ", msg, cmd); + for (i = 0; i < LO(cmd); i++) + printk("%02x ", data[i]); + printk(")\n"); +} + +/* + * Send a packet of bytes to the device + */ +int iforce_send_packet(struct iforce *iforce, u16 cmd, unsigned char* data) +{ + /* Copy data to buffer */ + int n = LO(cmd); + int c; + int empty; + int head, tail; + unsigned long flags; + +/* + * Update head and tail of xmit buffer + */ + spin_lock_irqsave(&iforce->xmit_lock, flags); + + head = iforce->xmit.head; + tail = iforce->xmit.tail; + + if (CIRC_SPACE(head, tail, XMIT_SIZE) < n+2) { + printk(KERN_WARNING "iforce.c: not enough space in xmit buffer to send new packet\n"); + spin_unlock_irqrestore(&iforce->xmit_lock, flags); + return -1; + } + + empty = head == tail; + XMIT_INC(iforce->xmit.head, n+2); + +/* + * Store packet in xmit buffer + */ + iforce->xmit.buf[head] = HI(cmd); + XMIT_INC(head, 1); + iforce->xmit.buf[head] = LO(cmd); + XMIT_INC(head, 1); + + c = CIRC_SPACE_TO_END(head, tail, XMIT_SIZE); + if (n < c) c=n; + + memcpy(&iforce->xmit.buf[head], + data, + c); + if (n != c) { + memcpy(&iforce->xmit.buf[0], + data + c, + n - c); + } + XMIT_INC(head, n); + + spin_unlock_irqrestore(&iforce->xmit_lock, flags); +/* + * If necessary, start the transmission + */ + switch (iforce->bus) { + +#ifdef CONFIG_JOYSTICK_IFORCE_232 + case IFORCE_232: + if (empty) + iforce_serial_xmit(iforce); + break; +#endif +#ifdef CONFIG_JOYSTICK_IFORCE_USB + case IFORCE_USB: + + if (iforce->usbdev && empty && + !test_and_set_bit(IFORCE_XMIT_RUNNING, iforce->xmit_flags)) { + + iforce_usb_xmit(iforce); + } + break; +#endif + } + return 0; +} + +/* Start or stop an effect */ +int iforce_control_playback(struct iforce* iforce, u16 id, unsigned int value) +{ + unsigned char data[3]; + +printk(KERN_DEBUG "iforce-packets.c: control_playback %d %d\n", id, value); + + data[0] = LO(id); + data[1] = (value > 0) ? ((value > 1) ? 0x41 : 0x01) : 0; + data[2] = LO(value); + return iforce_send_packet(iforce, FF_CMD_PLAY, data); +} + +/* Mark an effect that was being updated as ready. That means it can be updated + * again */ +static int mark_core_as_ready(struct iforce *iforce, unsigned short addr) +{ + int i; + for (i=0; i<iforce->dev.ff_effects_max; ++i) { + if (test_bit(FF_CORE_IS_USED, iforce->core_effects[i].flags) && + (iforce->core_effects[i].mod1_chunk.start == addr || + iforce->core_effects[i].mod2_chunk.start == addr)) { + clear_bit(FF_CORE_UPDATE, iforce->core_effects[i].flags); + return 0; + } + } + printk(KERN_WARNING "iforce-packets.c: unused effect %04x updated !!!\n", addr); + return -1; +} + +void iforce_process_packet(struct iforce *iforce, u16 cmd, unsigned char *data, struct pt_regs *regs) +{ + struct input_dev *dev = &iforce->dev; + int i; + static int being_used = 0; + + if (being_used) + printk(KERN_WARNING "iforce-packets.c: re-entrant call to iforce_process %d\n", being_used); + being_used++; + +#ifdef CONFIG_JOYSTICK_IFORCE_232 + if (HI(iforce->expect_packet) == HI(cmd)) { + iforce->expect_packet = 0; + iforce->ecmd = cmd; + memcpy(iforce->edata, data, IFORCE_MAX_LENGTH); + wake_up(&iforce->wait); + } +#endif + + if (!iforce->type) { + being_used--; + return; + } + + switch (HI(cmd)) { + + case 0x01: /* joystick position data */ + case 0x03: /* wheel position data */ + + input_regs(dev, regs); + + if (HI(cmd) == 1) { + input_report_abs(dev, ABS_X, (__s16) (((__s16)data[1] << 8) | data[0])); + input_report_abs(dev, ABS_Y, (__s16) (((__s16)data[3] << 8) | data[2])); + input_report_abs(dev, ABS_THROTTLE, 255 - data[4]); + if (LO(cmd) >= 8 && test_bit(ABS_RUDDER ,dev->absbit)) + input_report_abs(dev, ABS_RUDDER, (__s8)data[7]); + } else { + input_report_abs(dev, ABS_WHEEL, (__s16) (((__s16)data[1] << 8) | data[0])); + input_report_abs(dev, ABS_GAS, 255 - data[2]); + input_report_abs(dev, ABS_BRAKE, 255 - data[3]); + } + + input_report_abs(dev, ABS_HAT0X, iforce_hat_to_axis[data[6] >> 4].x); + input_report_abs(dev, ABS_HAT0Y, iforce_hat_to_axis[data[6] >> 4].y); + + for (i = 0; iforce->type->btn[i] >= 0; i++) + input_report_key(dev, iforce->type->btn[i], data[(i >> 3) + 5] & (1 << (i & 7))); + + /* If there are untouched bits left, interpret them as the second hat */ + if (i <= 8) { + int btns = data[6]; + if (test_bit(ABS_HAT1X, dev->absbit)) { + if (btns & 8) input_report_abs(dev, ABS_HAT1X, -1); + else if (btns & 2) input_report_abs(dev, ABS_HAT1X, 1); + else input_report_abs(dev, ABS_HAT1X, 0); + } + if (test_bit(ABS_HAT1Y, dev->absbit)) { + if (btns & 1) input_report_abs(dev, ABS_HAT1Y, -1); + else if (btns & 4) input_report_abs(dev, ABS_HAT1Y, 1); + else input_report_abs(dev, ABS_HAT1Y, 0); + } + } + + input_sync(dev); + + break; + + case 0x02: /* status report */ + input_regs(dev, regs); + input_report_key(dev, BTN_DEAD, data[0] & 0x02); + input_sync(dev); + + /* Check if an effect was just started or stopped */ + i = data[1] & 0x7f; + if (data[1] & 0x80) { + if (!test_and_set_bit(FF_CORE_IS_PLAYED, iforce->core_effects[i].flags)) { + /* Report play event */ + input_report_ff_status(dev, i, FF_STATUS_PLAYING); + } + } + else if (test_and_clear_bit(FF_CORE_IS_PLAYED, iforce->core_effects[i].flags)) { + /* Report stop event */ + input_report_ff_status(dev, i, FF_STATUS_STOPPED); + } + if (LO(cmd) > 3) { + int j; + for (j=3; j<LO(cmd); j+=2) { + mark_core_as_ready(iforce, data[j] | (data[j+1]<<8)); + } + } + break; + } + being_used--; +} + +int iforce_get_id_packet(struct iforce *iforce, char *packet) +{ + DECLARE_WAITQUEUE(wait, current); + int timeout = HZ; /* 1 second */ + + switch (iforce->bus) { + + case IFORCE_USB: + +#ifdef CONFIG_JOYSTICK_IFORCE_USB + iforce->cr.bRequest = packet[0]; + iforce->ctrl->dev = iforce->usbdev; + + set_current_state(TASK_INTERRUPTIBLE); + add_wait_queue(&iforce->wait, &wait); + + if (usb_submit_urb(iforce->ctrl, GFP_ATOMIC)) { + set_current_state(TASK_RUNNING); + remove_wait_queue(&iforce->wait, &wait); + return -1; + } + + while (timeout && iforce->ctrl->status == -EINPROGRESS) + timeout = schedule_timeout(timeout); + + set_current_state(TASK_RUNNING); + remove_wait_queue(&iforce->wait, &wait); + + if (!timeout) { + usb_unlink_urb(iforce->ctrl); + return -1; + } +#else + printk(KERN_ERR "iforce_get_id_packet: iforce->bus = USB!\n"); +#endif + break; + + case IFORCE_232: + +#ifdef CONFIG_JOYSTICK_IFORCE_232 + iforce->expect_packet = FF_CMD_QUERY; + iforce_send_packet(iforce, FF_CMD_QUERY, packet); + + set_current_state(TASK_INTERRUPTIBLE); + add_wait_queue(&iforce->wait, &wait); + + while (timeout && iforce->expect_packet) + timeout = schedule_timeout(timeout); + + set_current_state(TASK_RUNNING); + remove_wait_queue(&iforce->wait, &wait); + + if (!timeout) { + iforce->expect_packet = 0; + return -1; + } +#else + printk(KERN_ERR "iforce_get_id_packet: iforce->bus = SERIO!\n"); +#endif + break; + + default: + printk(KERN_ERR "iforce_get_id_packet: iforce->bus = %d\n", + iforce->bus); + break; + } + + return -(iforce->edata[0] != packet[0]); +} + diff --git a/drivers/input/joystick/iforce/iforce-serio.c b/drivers/input/joystick/iforce/iforce-serio.c new file mode 100644 index 000000000000..11f51905cba7 --- /dev/null +++ b/drivers/input/joystick/iforce/iforce-serio.c @@ -0,0 +1,193 @@ +/* + * $Id: iforce-serio.c,v 1.4 2002/01/28 22:45:00 jdeneux Exp $ + * + * Copyright (c) 2000-2001 Vojtech Pavlik <vojtech@ucw.cz> + * Copyright (c) 2001 Johann Deneux <deneux@ifrance.com> + * + * USB/RS232 I-Force joysticks and wheels. + */ + +/* + * 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 either by + * e-mail - mail your message to <vojtech@ucw.cz>, or by paper mail: + * Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic + */ + +#include "iforce.h" + +void iforce_serial_xmit(struct iforce *iforce) +{ + unsigned char cs; + int i; + unsigned long flags; + + if (test_and_set_bit(IFORCE_XMIT_RUNNING, iforce->xmit_flags)) { + set_bit(IFORCE_XMIT_AGAIN, iforce->xmit_flags); + return; + } + + spin_lock_irqsave(&iforce->xmit_lock, flags); + +again: + if (iforce->xmit.head == iforce->xmit.tail) { + clear_bit(IFORCE_XMIT_RUNNING, iforce->xmit_flags); + spin_unlock_irqrestore(&iforce->xmit_lock, flags); + return; + } + + cs = 0x2b; + + serio_write(iforce->serio, 0x2b); + + serio_write(iforce->serio, iforce->xmit.buf[iforce->xmit.tail]); + cs ^= iforce->xmit.buf[iforce->xmit.tail]; + XMIT_INC(iforce->xmit.tail, 1); + + for (i=iforce->xmit.buf[iforce->xmit.tail]; i >= 0; --i) { + serio_write(iforce->serio, iforce->xmit.buf[iforce->xmit.tail]); + cs ^= iforce->xmit.buf[iforce->xmit.tail]; + XMIT_INC(iforce->xmit.tail, 1); + } + + serio_write(iforce->serio, cs); + + if (test_and_clear_bit(IFORCE_XMIT_AGAIN, iforce->xmit_flags)) + goto again; + + clear_bit(IFORCE_XMIT_RUNNING, iforce->xmit_flags); + + spin_unlock_irqrestore(&iforce->xmit_lock, flags); +} + +static void iforce_serio_write_wakeup(struct serio *serio) +{ + struct iforce *iforce = serio_get_drvdata(serio); + + iforce_serial_xmit(iforce); +} + +static irqreturn_t iforce_serio_irq(struct serio *serio, + unsigned char data, unsigned int flags, struct pt_regs *regs) +{ + struct iforce *iforce = serio_get_drvdata(serio); + + if (!iforce->pkt) { + if (data == 0x2b) + iforce->pkt = 1; + goto out; + } + + if (!iforce->id) { + if (data > 3 && data != 0xff) + iforce->pkt = 0; + else + iforce->id = data; + goto out; + } + + if (!iforce->len) { + if (data > IFORCE_MAX_LENGTH) { + iforce->pkt = 0; + iforce->id = 0; + } else { + iforce->len = data; + } + goto out; + } + + if (iforce->idx < iforce->len) { + iforce->csum += iforce->data[iforce->idx++] = data; + goto out; + } + + if (iforce->idx == iforce->len) { + iforce_process_packet(iforce, (iforce->id << 8) | iforce->idx, iforce->data, regs); + iforce->pkt = 0; + iforce->id = 0; + iforce->len = 0; + iforce->idx = 0; + iforce->csum = 0; + } +out: + return IRQ_HANDLED; +} + +static int iforce_serio_connect(struct serio *serio, struct serio_driver *drv) +{ + struct iforce *iforce; + int err; + + if (!(iforce = kmalloc(sizeof(struct iforce), GFP_KERNEL))) + return -ENOMEM; + + memset(iforce, 0, sizeof(struct iforce)); + + iforce->bus = IFORCE_232; + iforce->serio = serio; + + serio_set_drvdata(serio, iforce); + + err = serio_open(serio, drv); + if (err) { + serio_set_drvdata(serio, NULL); + kfree(iforce); + return err; + } + + if (iforce_init_device(iforce)) { + serio_close(serio); + serio_set_drvdata(serio, NULL); + kfree(iforce); + return -ENODEV; + } + + return 0; +} + +static void iforce_serio_disconnect(struct serio *serio) +{ + struct iforce *iforce = serio_get_drvdata(serio); + + input_unregister_device(&iforce->dev); + serio_close(serio); + serio_set_drvdata(serio, NULL); + kfree(iforce); +} + +static struct serio_device_id iforce_serio_ids[] = { + { + .type = SERIO_RS232, + .proto = SERIO_IFORCE, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { 0 } +}; + +MODULE_DEVICE_TABLE(serio, iforce_serio_ids); + +struct serio_driver iforce_serio_drv = { + .driver = { + .name = "iforce", + }, + .description = "RS232 I-Force joysticks and wheels driver", + .id_table = iforce_serio_ids, + .write_wakeup = iforce_serio_write_wakeup, + .interrupt = iforce_serio_irq, + .connect = iforce_serio_connect, + .disconnect = iforce_serio_disconnect, +}; diff --git a/drivers/input/joystick/iforce/iforce-usb.c b/drivers/input/joystick/iforce/iforce-usb.c new file mode 100644 index 000000000000..617c0b0e5a39 --- /dev/null +++ b/drivers/input/joystick/iforce/iforce-usb.c @@ -0,0 +1,243 @@ + /* + * $Id: iforce-usb.c,v 1.16 2002/06/09 11:08:04 jdeneux Exp $ + * + * Copyright (c) 2000-2002 Vojtech Pavlik <vojtech@ucw.cz> + * Copyright (c) 2001-2002 Johann Deneux <deneux@ifrance.com> + * + * USB/RS232 I-Force joysticks and wheels. + */ + +/* + * 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 either by + * e-mail - mail your message to <vojtech@ucw.cz>, or by paper mail: + * Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic + */ + +#include "iforce.h" + +void iforce_usb_xmit(struct iforce *iforce) +{ + int n, c; + unsigned long flags; + + spin_lock_irqsave(&iforce->xmit_lock, flags); + + if (iforce->xmit.head == iforce->xmit.tail) { + clear_bit(IFORCE_XMIT_RUNNING, iforce->xmit_flags); + spin_unlock_irqrestore(&iforce->xmit_lock, flags); + return; + } + + ((char *)iforce->out->transfer_buffer)[0] = iforce->xmit.buf[iforce->xmit.tail]; + XMIT_INC(iforce->xmit.tail, 1); + n = iforce->xmit.buf[iforce->xmit.tail]; + XMIT_INC(iforce->xmit.tail, 1); + + iforce->out->transfer_buffer_length = n + 1; + iforce->out->dev = iforce->usbdev; + + /* Copy rest of data then */ + c = CIRC_CNT_TO_END(iforce->xmit.head, iforce->xmit.tail, XMIT_SIZE); + if (n < c) c=n; + + memcpy(iforce->out->transfer_buffer + 1, + &iforce->xmit.buf[iforce->xmit.tail], + c); + if (n != c) { + memcpy(iforce->out->transfer_buffer + 1 + c, + &iforce->xmit.buf[0], + n-c); + } + XMIT_INC(iforce->xmit.tail, n); + + if ( (n=usb_submit_urb(iforce->out, GFP_ATOMIC)) ) { + printk(KERN_WARNING "iforce-usb.c: iforce_usb_xmit: usb_submit_urb failed %d\n", n); + } + + /* The IFORCE_XMIT_RUNNING bit is not cleared here. That's intended. + * As long as the urb completion handler is not called, the transmiting + * is considered to be running */ + spin_unlock_irqrestore(&iforce->xmit_lock, flags); +} + +static void iforce_usb_irq(struct urb *urb, struct pt_regs *regs) +{ + struct iforce *iforce = urb->context; + int status; + + switch (urb->status) { + case 0: + /* success */ + break; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + /* this urb is terminated, clean up */ + dbg("%s - urb shutting down with status: %d", + __FUNCTION__, urb->status); + return; + default: + dbg("%s - urb has status of: %d", __FUNCTION__, urb->status); + goto exit; + } + + iforce_process_packet(iforce, + (iforce->data[0] << 8) | (urb->actual_length - 1), iforce->data + 1, regs); + +exit: + status = usb_submit_urb (urb, GFP_ATOMIC); + if (status) + err ("%s - usb_submit_urb failed with result %d", + __FUNCTION__, status); +} + +static void iforce_usb_out(struct urb *urb, struct pt_regs *regs) +{ + struct iforce *iforce = urb->context; + + if (urb->status) { + printk(KERN_DEBUG "iforce_usb_out: urb->status %d, exiting", urb->status); + return; + } + + iforce_usb_xmit(iforce); + + wake_up(&iforce->wait); +} + +static void iforce_usb_ctrl(struct urb *urb, struct pt_regs *regs) +{ + struct iforce *iforce = urb->context; + if (urb->status) return; + iforce->ecmd = 0xff00 | urb->actual_length; + wake_up(&iforce->wait); +} + +static int iforce_usb_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_device *dev = interface_to_usbdev(intf); + struct usb_host_interface *interface; + struct usb_endpoint_descriptor *epirq, *epout; + struct iforce *iforce; + + interface = intf->cur_altsetting; + + epirq = &interface->endpoint[0].desc; + epout = &interface->endpoint[1].desc; + + if (!(iforce = kmalloc(sizeof(struct iforce) + 32, GFP_KERNEL))) + goto fail; + + memset(iforce, 0, sizeof(struct iforce)); + + if (!(iforce->irq = usb_alloc_urb(0, GFP_KERNEL))) { + goto fail; + } + + if (!(iforce->out = usb_alloc_urb(0, GFP_KERNEL))) { + goto fail; + } + + if (!(iforce->ctrl = usb_alloc_urb(0, GFP_KERNEL))) { + goto fail; + } + + iforce->bus = IFORCE_USB; + iforce->usbdev = dev; + + iforce->cr.bRequestType = USB_TYPE_VENDOR | USB_DIR_IN | USB_RECIP_INTERFACE; + iforce->cr.wIndex = 0; + iforce->cr.wLength = 16; + + usb_fill_int_urb(iforce->irq, dev, usb_rcvintpipe(dev, epirq->bEndpointAddress), + iforce->data, 16, iforce_usb_irq, iforce, epirq->bInterval); + + usb_fill_bulk_urb(iforce->out, dev, usb_sndbulkpipe(dev, epout->bEndpointAddress), + iforce + 1, 32, iforce_usb_out, iforce); + + usb_fill_control_urb(iforce->ctrl, dev, usb_rcvctrlpipe(dev, 0), + (void*) &iforce->cr, iforce->edata, 16, iforce_usb_ctrl, iforce); + + if (iforce_init_device(iforce)) goto fail; + + usb_set_intfdata(intf, iforce); + return 0; + +fail: + if (iforce) { + if (iforce->irq) usb_free_urb(iforce->irq); + if (iforce->out) usb_free_urb(iforce->out); + if (iforce->ctrl) usb_free_urb(iforce->ctrl); + kfree(iforce); + } + + return -ENODEV; +} + +/* Called by iforce_delete() */ +void iforce_usb_delete(struct iforce* iforce) +{ + usb_unlink_urb(iforce->irq); +/* Is it ok to unlink those ? */ + usb_unlink_urb(iforce->out); + usb_unlink_urb(iforce->ctrl); + + usb_free_urb(iforce->irq); + usb_free_urb(iforce->out); + usb_free_urb(iforce->ctrl); +} + +static void iforce_usb_disconnect(struct usb_interface *intf) +{ + struct iforce *iforce = usb_get_intfdata(intf); + int open = 0; /* FIXME! iforce->dev.handle->open; */ + + usb_set_intfdata(intf, NULL); + if (iforce) { + iforce->usbdev = NULL; + input_unregister_device(&iforce->dev); + + if (!open) { + iforce_delete_device(iforce); + kfree(iforce); + } + } +} + +static struct usb_device_id iforce_usb_ids [] = { + { USB_DEVICE(0x044f, 0xa01c) }, /* Thrustmaster Motor Sport GT */ + { USB_DEVICE(0x046d, 0xc281) }, /* Logitech WingMan Force */ + { USB_DEVICE(0x046d, 0xc291) }, /* Logitech WingMan Formula Force */ + { USB_DEVICE(0x05ef, 0x020a) }, /* AVB Top Shot Pegasus */ + { USB_DEVICE(0x05ef, 0x8884) }, /* AVB Mag Turbo Force */ + { USB_DEVICE(0x05ef, 0x8888) }, /* AVB Top Shot FFB Racing Wheel */ + { USB_DEVICE(0x061c, 0xc0a4) }, /* ACT LABS Force RS */ + { USB_DEVICE(0x06f8, 0x0001) }, /* Guillemot Race Leader Force Feedback */ + { USB_DEVICE(0x06f8, 0x0004) }, /* Guillemot Force Feedback Racing Wheel */ + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE (usb, iforce_usb_ids); + +struct usb_driver iforce_usb_driver = { + .owner = THIS_MODULE, + .name = "iforce", + .probe = iforce_usb_probe, + .disconnect = iforce_usb_disconnect, + .id_table = iforce_usb_ids, +}; diff --git a/drivers/input/joystick/iforce/iforce.h b/drivers/input/joystick/iforce/iforce.h new file mode 100644 index 000000000000..bce247bc300b --- /dev/null +++ b/drivers/input/joystick/iforce/iforce.h @@ -0,0 +1,191 @@ +/* + * $Id: iforce.h,v 1.13 2002/07/07 10:22:50 jdeneux Exp $ + * + * Copyright (c) 2000-2002 Vojtech Pavlik <vojtech@ucw.cz> + * Copyright (c) 2001-2002 Johann Deneux <deneux@ifrance.com> + * + * USB/RS232 I-Force joysticks and wheels. + */ + +/* + * 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 either by + * e-mail - mail your message to <vojtech@ucw.cz>, or by paper mail: + * Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic + */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/spinlock.h> +#include <linux/usb.h> +#include <linux/serio.h> +#include <linux/config.h> +#include <linux/circ_buf.h> +#include <asm/semaphore.h> + +/* This module provides arbitrary resource management routines. + * I use it to manage the device's memory. + * Despite the name of this module, I am *not* going to access the ioports. + */ +#include <linux/ioport.h> + +#define IFORCE_MAX_LENGTH 16 + +/* iforce::bus */ +#define IFORCE_232 1 +#define IFORCE_USB 2 + +#define FALSE 0 +#define TRUE 1 + +#define FF_EFFECTS_MAX 32 + +/* Each force feedback effect is made of one core effect, which can be + * associated to at most to effect modifiers + */ +#define FF_MOD1_IS_USED 0 +#define FF_MOD2_IS_USED 1 +#define FF_CORE_IS_USED 2 +#define FF_CORE_IS_PLAYED 3 /* Effect is currently being played */ +#define FF_CORE_SHOULD_PLAY 4 /* User wants the effect to be played */ +#define FF_CORE_UPDATE 5 /* Effect is being updated */ +#define FF_MODCORE_MAX 5 + +#define CHECK_OWNERSHIP(i, iforce) \ + ((i) < FF_EFFECTS_MAX && i >= 0 && \ + test_bit(FF_CORE_IS_USED, (iforce)->core_effects[(i)].flags) && \ + (current->pid == 0 || \ + (iforce)->core_effects[(i)].owner == current->pid)) + +struct iforce_core_effect { + /* Information about where modifiers are stored in the device's memory */ + struct resource mod1_chunk; + struct resource mod2_chunk; + unsigned long flags[NBITS(FF_MODCORE_MAX)]; + pid_t owner; + /* Used to keep track of parameters of an effect. They are needed + * to know what parts of an effect changed in an update operation. + * We try to send only parameter packets if possible, as sending + * effect parameter requires the effect to be stoped and restarted + */ + struct ff_effect effect; +}; + +#define FF_CMD_EFFECT 0x010e +#define FF_CMD_ENVELOPE 0x0208 +#define FF_CMD_MAGNITUDE 0x0303 +#define FF_CMD_PERIOD 0x0407 +#define FF_CMD_CONDITION 0x050a + +#define FF_CMD_AUTOCENTER 0x4002 +#define FF_CMD_PLAY 0x4103 +#define FF_CMD_ENABLE 0x4201 +#define FF_CMD_GAIN 0x4301 + +#define FF_CMD_QUERY 0xff01 + +/* Buffer for async write */ +#define XMIT_SIZE 256 +#define XMIT_INC(var, n) (var)+=n; (var)&= XMIT_SIZE -1 +/* iforce::xmit_flags */ +#define IFORCE_XMIT_RUNNING 0 +#define IFORCE_XMIT_AGAIN 1 + +struct iforce_device { + u16 idvendor; + u16 idproduct; + char *name; + signed short *btn; + signed short *abs; + signed short *ff; +}; + +struct iforce { + struct input_dev dev; /* Input device interface */ + struct iforce_device *type; + int bus; + + unsigned char data[IFORCE_MAX_LENGTH]; + unsigned char edata[IFORCE_MAX_LENGTH]; + u16 ecmd; + u16 expect_packet; + +#ifdef CONFIG_JOYSTICK_IFORCE_232 + struct serio *serio; /* RS232 transfer */ + int idx, pkt, len, id; + unsigned char csum; +#endif +#ifdef CONFIG_JOYSTICK_IFORCE_USB + struct usb_device *usbdev; /* USB transfer */ + struct urb *irq, *out, *ctrl; + struct usb_ctrlrequest cr; +#endif + spinlock_t xmit_lock; + /* Buffer used for asynchronous sending of bytes to the device */ + struct circ_buf xmit; + unsigned char xmit_data[XMIT_SIZE]; + long xmit_flags[1]; + + /* Force Feedback */ + wait_queue_head_t wait; + struct resource device_memory; + struct iforce_core_effect core_effects[FF_EFFECTS_MAX]; + struct semaphore mem_mutex; +}; + +/* Get hi and low bytes of a 16-bits int */ +#define HI(a) ((unsigned char)((a) >> 8)) +#define LO(a) ((unsigned char)((a) & 0xff)) + +/* For many parameters, it seems that 0x80 is a special value that should + * be avoided. Instead, we replace this value by 0x7f + */ +#define HIFIX80(a) ((unsigned char)(((a)<0? (a)+255 : (a))>>8)) + +/* Encode a time value */ +#define TIME_SCALE(a) (a) + + +/* Public functions */ +/* iforce-serio.c */ +void iforce_serial_xmit(struct iforce *iforce); + +/* iforce-usb.c */ +void iforce_usb_xmit(struct iforce *iforce); +void iforce_usb_delete(struct iforce *iforce); + +/* iforce-main.c */ +int iforce_init_device(struct iforce *iforce); +void iforce_delete_device(struct iforce *iforce); + +/* iforce-packets.c */ +int iforce_control_playback(struct iforce*, u16 id, unsigned int); +void iforce_process_packet(struct iforce *iforce, u16 cmd, unsigned char *data, struct pt_regs *regs); +int iforce_send_packet(struct iforce *iforce, u16 cmd, unsigned char* data); +void iforce_dump_packet(char *msg, u16 cmd, unsigned char *data) ; +int iforce_get_id_packet(struct iforce *iforce, char *packet); + +/* iforce-ff.c */ +int iforce_upload_periodic(struct iforce*, struct ff_effect*, int is_update); +int iforce_upload_constant(struct iforce*, struct ff_effect*, int is_update); +int iforce_upload_condition(struct iforce*, struct ff_effect*, int is_update); + +/* Public variables */ +extern struct serio_driver iforce_serio_drv; +extern struct usb_driver iforce_usb_driver; diff --git a/drivers/input/joystick/interact.c b/drivers/input/joystick/interact.c new file mode 100644 index 000000000000..9d3f8c38cb09 --- /dev/null +++ b/drivers/input/joystick/interact.c @@ -0,0 +1,322 @@ +/* + * $Id: interact.c,v 1.16 2002/01/22 20:28:25 vojtech Exp $ + * + * Copyright (c) 2001 Vojtech Pavlik + * + * Based on the work of: + * Toby Deshane + */ + +/* + * InterAct digital gamepad/joystick driver for Linux + */ + +/* + * 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 either by + * e-mail - mail your message to <vojtech@ucw.cz>, or by paper mail: + * Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic + */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/gameport.h> +#include <linux/input.h> + +#define DRIVER_DESC "InterAct digital joystick driver" + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +#define INTERACT_MAX_START 600 /* 400 us */ +#define INTERACT_MAX_STROBE 60 /* 40 us */ +#define INTERACT_MAX_LENGTH 32 /* 32 bits */ + +#define INTERACT_TYPE_HHFX 0 /* HammerHead/FX */ +#define INTERACT_TYPE_PP8D 1 /* ProPad 8 */ + +struct interact { + struct gameport *gameport; + struct input_dev dev; + int bads; + int reads; + unsigned char type; + unsigned char length; + char phys[32]; +}; + +static short interact_abs_hhfx[] = + { ABS_RX, ABS_RY, ABS_X, ABS_Y, ABS_HAT0X, ABS_HAT0Y, -1 }; +static short interact_abs_pp8d[] = + { ABS_X, ABS_Y, -1 }; + +static short interact_btn_hhfx[] = + { BTN_TR, BTN_X, BTN_Y, BTN_Z, BTN_A, BTN_B, BTN_C, BTN_TL, BTN_TL2, BTN_TR2, BTN_MODE, BTN_SELECT, -1 }; +static short interact_btn_pp8d[] = + { BTN_C, BTN_TL, BTN_TR, BTN_A, BTN_B, BTN_Y, BTN_Z, BTN_X, -1 }; + +struct interact_type { + int id; + short *abs; + short *btn; + char *name; + unsigned char length; + unsigned char b8; +}; + +static struct interact_type interact_type[] = { + { 0x6202, interact_abs_hhfx, interact_btn_hhfx, "InterAct HammerHead/FX", 32, 4 }, + { 0x53f8, interact_abs_pp8d, interact_btn_pp8d, "InterAct ProPad 8 Digital", 16, 0 }, + { 0 }}; + +/* + * interact_read_packet() reads and InterAct joystick data. + */ + +static int interact_read_packet(struct gameport *gameport, int length, u32 *data) +{ + unsigned long flags; + unsigned char u, v; + unsigned int t, s; + int i; + + i = 0; + data[0] = data[1] = data[2] = 0; + t = gameport_time(gameport, INTERACT_MAX_START); + s = gameport_time(gameport, INTERACT_MAX_STROBE); + + local_irq_save(flags); + gameport_trigger(gameport); + v = gameport_read(gameport); + + while (t > 0 && i < length) { + t--; + u = v; v = gameport_read(gameport); + if (v & ~u & 0x40) { + data[0] = (data[0] << 1) | ((v >> 4) & 1); + data[1] = (data[1] << 1) | ((v >> 5) & 1); + data[2] = (data[2] << 1) | ((v >> 7) & 1); + i++; + t = s; + } + } + + local_irq_restore(flags); + + return i; +} + +/* + * interact_poll() reads and analyzes InterAct joystick data. + */ + +static void interact_poll(struct gameport *gameport) +{ + struct interact *interact = gameport_get_drvdata(gameport); + struct input_dev *dev = &interact->dev; + u32 data[3]; + int i; + + interact->reads++; + + if (interact_read_packet(interact->gameport, interact->length, data) < interact->length) { + interact->bads++; + } else { + + for (i = 0; i < 3; i++) + data[i] <<= INTERACT_MAX_LENGTH - interact->length; + + switch (interact->type) { + + case INTERACT_TYPE_HHFX: + + for (i = 0; i < 4; i++) + input_report_abs(dev, interact_abs_hhfx[i], (data[i & 1] >> ((i >> 1) << 3)) & 0xff); + + for (i = 0; i < 2; i++) + input_report_abs(dev, ABS_HAT0Y - i, + ((data[1] >> ((i << 1) + 17)) & 1) - ((data[1] >> ((i << 1) + 16)) & 1)); + + for (i = 0; i < 8; i++) + input_report_key(dev, interact_btn_hhfx[i], (data[0] >> (i + 16)) & 1); + + for (i = 0; i < 4; i++) + input_report_key(dev, interact_btn_hhfx[i + 8], (data[1] >> (i + 20)) & 1); + + break; + + case INTERACT_TYPE_PP8D: + + for (i = 0; i < 2; i++) + input_report_abs(dev, interact_abs_pp8d[i], + ((data[0] >> ((i << 1) + 20)) & 1) - ((data[0] >> ((i << 1) + 21)) & 1)); + + for (i = 0; i < 8; i++) + input_report_key(dev, interact_btn_pp8d[i], (data[1] >> (i + 16)) & 1); + + break; + } + } + + input_sync(dev); +} + +/* + * interact_open() is a callback from the input open routine. + */ + +static int interact_open(struct input_dev *dev) +{ + struct interact *interact = dev->private; + + gameport_start_polling(interact->gameport); + return 0; +} + +/* + * interact_close() is a callback from the input close routine. + */ + +static void interact_close(struct input_dev *dev) +{ + struct interact *interact = dev->private; + + gameport_stop_polling(interact->gameport); +} + +/* + * interact_connect() probes for InterAct joysticks. + */ + +static int interact_connect(struct gameport *gameport, struct gameport_driver *drv) +{ + struct interact *interact; + __u32 data[3]; + int i, t; + int err; + + if (!(interact = kcalloc(1, sizeof(struct interact), GFP_KERNEL))) + return -ENOMEM; + + interact->gameport = gameport; + + gameport_set_drvdata(gameport, interact); + + err = gameport_open(gameport, drv, GAMEPORT_MODE_RAW); + if (err) + goto fail1; + + i = interact_read_packet(gameport, INTERACT_MAX_LENGTH * 2, data); + + if (i != 32 || (data[0] >> 24) != 0x0c || (data[1] >> 24) != 0x02) { + err = -ENODEV; + goto fail2; + } + + for (i = 0; interact_type[i].length; i++) + if (interact_type[i].id == (data[2] >> 16)) + break; + + if (!interact_type[i].length) { + printk(KERN_WARNING "interact.c: Unknown joystick on %s. [len %d d0 %08x d1 %08x i2 %08x]\n", + gameport->phys, i, data[0], data[1], data[2]); + err = -ENODEV; + goto fail2; + } + + gameport_set_poll_handler(gameport, interact_poll); + gameport_set_poll_interval(gameport, 20); + + sprintf(interact->phys, "%s/input0", gameport->phys); + + interact->type = i; + interact->length = interact_type[i].length; + + interact->dev.private = interact; + interact->dev.open = interact_open; + interact->dev.close = interact_close; + + interact->dev.name = interact_type[i].name; + interact->dev.phys = interact->phys; + interact->dev.id.bustype = BUS_GAMEPORT; + interact->dev.id.vendor = GAMEPORT_ID_VENDOR_INTERACT; + interact->dev.id.product = interact_type[i].id; + interact->dev.id.version = 0x0100; + + interact->dev.evbit[0] = BIT(EV_KEY) | BIT(EV_ABS); + + for (i = 0; (t = interact_type[interact->type].abs[i]) >= 0; i++) { + set_bit(t, interact->dev.absbit); + if (i < interact_type[interact->type].b8) { + interact->dev.absmin[t] = 0; + interact->dev.absmax[t] = 255; + } else { + interact->dev.absmin[t] = -1; + interact->dev.absmax[t] = 1; + } + } + + for (i = 0; (t = interact_type[interact->type].btn[i]) >= 0; i++) + set_bit(t, interact->dev.keybit); + + input_register_device(&interact->dev); + printk(KERN_INFO "input: %s on %s\n", + interact_type[interact->type].name, gameport->phys); + + return 0; + +fail2: gameport_close(gameport); +fail1: gameport_set_drvdata(gameport, NULL); + kfree(interact); + return err; +} + +static void interact_disconnect(struct gameport *gameport) +{ + struct interact *interact = gameport_get_drvdata(gameport); + + input_unregister_device(&interact->dev); + gameport_close(gameport); + gameport_set_drvdata(gameport, NULL); + kfree(interact); +} + +static struct gameport_driver interact_drv = { + .driver = { + .name = "interact", + }, + .description = DRIVER_DESC, + .connect = interact_connect, + .disconnect = interact_disconnect, +}; + +static int __init interact_init(void) +{ + gameport_register_driver(&interact_drv); + return 0; +} + +static void __exit interact_exit(void) +{ + gameport_unregister_driver(&interact_drv); +} + +module_init(interact_init); +module_exit(interact_exit); diff --git a/drivers/input/joystick/joydump.c b/drivers/input/joystick/joydump.c new file mode 100644 index 000000000000..4234ccaf9146 --- /dev/null +++ b/drivers/input/joystick/joydump.c @@ -0,0 +1,175 @@ +/* + * $Id: joydump.c,v 1.1 2002/01/23 06:56:16 jsimmons Exp $ + * + * Copyright (c) 1996-2001 Vojtech Pavlik + */ + +/* + * This is just a very simple driver that can dump the data + * out of the joystick port into the syslog ... + */ + +/* + * 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 either by + * e-mail - mail your message to <vojtech@ucw.cz>, or by paper mail: + * Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic + */ + +#include <linux/module.h> +#include <linux/gameport.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/init.h> + +#define DRIVER_DESC "Gameport data dumper module" + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +#define BUF_SIZE 256 + +struct joydump { + unsigned int time; + unsigned char data; +}; + +static int joydump_connect(struct gameport *gameport, struct gameport_driver *drv) +{ + struct joydump *buf; /* all entries */ + struct joydump *dump, *prev; /* one entry each */ + int axes[4], buttons; + int i, j, t, timeout; + unsigned long flags; + unsigned char u; + + printk(KERN_INFO "joydump: ,------------------ START ----------------.\n"); + printk(KERN_INFO "joydump: | Dumping: %30s |\n", gameport->phys); + printk(KERN_INFO "joydump: | Speed: %28d kHz |\n", gameport->speed); + + if (gameport_open(gameport, drv, GAMEPORT_MODE_RAW)) { + + printk(KERN_INFO "joydump: | Raw mode not available - trying cooked. |\n"); + + if (gameport_open(gameport, drv, GAMEPORT_MODE_COOKED)) { + + printk(KERN_INFO "joydump: | Cooked not available either. Failing. |\n"); + printk(KERN_INFO "joydump: `------------------- END -----------------'\n"); + return -ENODEV; + } + + gameport_cooked_read(gameport, axes, &buttons); + + for (i = 0; i < 4; i++) + printk(KERN_INFO "joydump: | Axis %d: %4d. |\n", i, axes[i]); + printk(KERN_INFO "joydump: | Buttons %02x. |\n", buttons); + printk(KERN_INFO "joydump: `------------------- END -----------------'\n"); + } + + timeout = gameport_time(gameport, 10000); /* 10 ms */ + + buf = kmalloc(BUF_SIZE * sizeof(struct joydump), GFP_KERNEL); + if (!buf) { + printk(KERN_INFO "joydump: no memory for testing\n"); + goto jd_end; + } + dump = buf; + t = 0; + i = 1; + + local_irq_save(flags); + + u = gameport_read(gameport); + + dump->data = u; + dump->time = t; + dump++; + + gameport_trigger(gameport); + + while (i < BUF_SIZE && t < timeout) { + + dump->data = gameport_read(gameport); + + if (dump->data ^ u) { + u = dump->data; + dump->time = t; + i++; + dump++; + } + t++; + } + + local_irq_restore(flags); + +/* + * Dump data. + */ + + t = i; + dump = buf; + prev = dump; + + printk(KERN_INFO "joydump: >------------------ DATA -----------------<\n"); + printk(KERN_INFO "joydump: | index: %3d delta: %3d us data: ", 0, 0); + for (j = 7; j >= 0; j--) + printk("%d", (dump->data >> j) & 1); + printk(" |\n"); + dump++; + + for (i = 1; i < t; i++, dump++, prev++) { + printk(KERN_INFO "joydump: | index: %3d delta: %3d us data: ", + i, dump->time - prev->time); + for (j = 7; j >= 0; j--) + printk("%d", (dump->data >> j) & 1); + printk(" |\n"); + } + kfree(buf); + +jd_end: + printk(KERN_INFO "joydump: `------------------- END -----------------'\n"); + + return 0; +} + +static void joydump_disconnect(struct gameport *gameport) +{ + gameport_close(gameport); +} + +static struct gameport_driver joydump_drv = { + .driver = { + .name = "joydump", + }, + .description = DRIVER_DESC, + .connect = joydump_connect, + .disconnect = joydump_disconnect, +}; + +static int __init joydump_init(void) +{ + gameport_register_driver(&joydump_drv); + return 0; +} + +static void __exit joydump_exit(void) +{ + gameport_unregister_driver(&joydump_drv); +} + +module_init(joydump_init); +module_exit(joydump_exit); diff --git a/drivers/input/joystick/magellan.c b/drivers/input/joystick/magellan.c new file mode 100644 index 000000000000..1ba503627242 --- /dev/null +++ b/drivers/input/joystick/magellan.c @@ -0,0 +1,247 @@ +/* + * $Id: magellan.c,v 1.16 2002/01/22 20:28:39 vojtech Exp $ + * + * Copyright (c) 1999-2001 Vojtech Pavlik + */ + +/* + * Magellan and Space Mouse 6dof controller driver for Linux + */ + +/* + * 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 either by + * e-mail - mail your message to <vojtech@ucw.cz>, or by paper mail: + * Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/serio.h> +#include <linux/init.h> + +#define DRIVER_DESC "Magellan and SpaceMouse 6dof controller driver" + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +/* + * Definitions & global arrays. + */ + +#define MAGELLAN_MAX_LENGTH 32 + +static int magellan_buttons[] = { BTN_0, BTN_1, BTN_2, BTN_3, BTN_4, BTN_5, BTN_6, BTN_7, BTN_8 }; +static int magellan_axes[] = { ABS_X, ABS_Y, ABS_Z, ABS_RX, ABS_RY, ABS_RZ }; +static char *magellan_name = "LogiCad3D Magellan / SpaceMouse"; + +/* + * Per-Magellan data. + */ + +struct magellan { + struct input_dev dev; + int idx; + unsigned char data[MAGELLAN_MAX_LENGTH]; + char phys[32]; +}; + +/* + * magellan_crunch_nibbles() verifies that the bytes sent from the Magellan + * have correct upper nibbles for the lower ones, if not, the packet will + * be thrown away. It also strips these upper halves to simplify further + * processing. + */ + +static int magellan_crunch_nibbles(unsigned char *data, int count) +{ + static unsigned char nibbles[16] = "0AB3D56GH9:K<MN?"; + + do { + if (data[count] == nibbles[data[count] & 0xf]) + data[count] = data[count] & 0xf; + else + return -1; + } while (--count); + + return 0; +} + +static void magellan_process_packet(struct magellan* magellan, struct pt_regs *regs) +{ + struct input_dev *dev = &magellan->dev; + unsigned char *data = magellan->data; + int i, t; + + if (!magellan->idx) return; + + input_regs(dev, regs); + + switch (magellan->data[0]) { + + case 'd': /* Axis data */ + if (magellan->idx != 25) return; + if (magellan_crunch_nibbles(data, 24)) return; + for (i = 0; i < 6; i++) + input_report_abs(dev, magellan_axes[i], + (data[(i << 2) + 1] << 12 | data[(i << 2) + 2] << 8 | + data[(i << 2) + 3] << 4 | data[(i << 2) + 4]) - 32768); + break; + + case 'k': /* Button data */ + if (magellan->idx != 4) return; + if (magellan_crunch_nibbles(data, 3)) return; + t = (data[1] << 1) | (data[2] << 5) | data[3]; + for (i = 0; i < 9; i++) input_report_key(dev, magellan_buttons[i], (t >> i) & 1); + break; + } + + input_sync(dev); +} + +static irqreturn_t magellan_interrupt(struct serio *serio, + unsigned char data, unsigned int flags, struct pt_regs *regs) +{ + struct magellan* magellan = serio_get_drvdata(serio); + + if (data == '\r') { + magellan_process_packet(magellan, regs); + magellan->idx = 0; + } else { + if (magellan->idx < MAGELLAN_MAX_LENGTH) + magellan->data[magellan->idx++] = data; + } + return IRQ_HANDLED; +} + +/* + * magellan_disconnect() is the opposite of magellan_connect() + */ + +static void magellan_disconnect(struct serio *serio) +{ + struct magellan* magellan = serio_get_drvdata(serio); + + input_unregister_device(&magellan->dev); + serio_close(serio); + serio_set_drvdata(serio, NULL); + kfree(magellan); +} + +/* + * magellan_connect() is the routine that is called when someone adds a + * new serio device that supports Magellan protocol and registers it as + * an input device. + */ + +static int magellan_connect(struct serio *serio, struct serio_driver *drv) +{ + struct magellan *magellan; + int i, t; + int err; + + if (!(magellan = kmalloc(sizeof(struct magellan), GFP_KERNEL))) + return -ENOMEM; + + memset(magellan, 0, sizeof(struct magellan)); + + magellan->dev.evbit[0] = BIT(EV_KEY) | BIT(EV_ABS); + + for (i = 0; i < 9; i++) + set_bit(magellan_buttons[i], magellan->dev.keybit); + + for (i = 0; i < 6; i++) { + t = magellan_axes[i]; + set_bit(t, magellan->dev.absbit); + magellan->dev.absmin[t] = -360; + magellan->dev.absmax[t] = 360; + } + + sprintf(magellan->phys, "%s/input0", serio->phys); + + init_input_dev(&magellan->dev); + magellan->dev.private = magellan; + magellan->dev.name = magellan_name; + magellan->dev.phys = magellan->phys; + magellan->dev.id.bustype = BUS_RS232; + magellan->dev.id.vendor = SERIO_MAGELLAN; + magellan->dev.id.product = 0x0001; + magellan->dev.id.version = 0x0100; + magellan->dev.dev = &serio->dev; + + serio_set_drvdata(serio, magellan); + + err = serio_open(serio, drv); + if (err) { + serio_set_drvdata(serio, NULL); + kfree(magellan); + return err; + } + + input_register_device(&magellan->dev); + + printk(KERN_INFO "input: %s on %s\n", magellan_name, serio->phys); + + return 0; +} + +/* + * The serio driver structure. + */ + +static struct serio_device_id magellan_serio_ids[] = { + { + .type = SERIO_RS232, + .proto = SERIO_MAGELLAN, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { 0 } +}; + +MODULE_DEVICE_TABLE(serio, magellan_serio_ids); + +static struct serio_driver magellan_drv = { + .driver = { + .name = "magellan", + }, + .description = DRIVER_DESC, + .id_table = magellan_serio_ids, + .interrupt = magellan_interrupt, + .connect = magellan_connect, + .disconnect = magellan_disconnect, +}; + +/* + * The functions for inserting/removing us as a module. + */ + +static int __init magellan_init(void) +{ + serio_register_driver(&magellan_drv); + return 0; +} + +static void __exit magellan_exit(void) +{ + serio_unregister_driver(&magellan_drv); +} + +module_init(magellan_init); +module_exit(magellan_exit); diff --git a/drivers/input/joystick/sidewinder.c b/drivers/input/joystick/sidewinder.c new file mode 100644 index 000000000000..47144a7ed9e7 --- /dev/null +++ b/drivers/input/joystick/sidewinder.c @@ -0,0 +1,807 @@ +/* + * Copyright (c) 1998-2005 Vojtech Pavlik + */ + +/* + * Microsoft SideWinder joystick family driver for Linux + */ + +/* + * 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 either by + * e-mail - mail your message to <vojtech@ucw.cz>, or by paper mail: + * Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic + */ + +#include <linux/delay.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/gameport.h> + +#define DRIVER_DESC "Microsoft SideWinder joystick family driver" + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +/* + * These are really magic values. Changing them can make a problem go away, + * as well as break everything. + */ + +#undef SW_DEBUG +#undef SW_DEBUG_DATA + +#define SW_START 600 /* The time we wait for the first bit [600 us] */ +#define SW_STROBE 60 /* Max time per bit [60 us] */ +#define SW_TIMEOUT 6 /* Wait for everything to settle [6 ms] */ +#define SW_KICK 45 /* Wait after A0 fall till kick [45 us] */ +#define SW_END 8 /* Number of bits before end of packet to kick */ +#define SW_FAIL 16 /* Number of packet read errors to fail and reinitialize */ +#define SW_BAD 2 /* Number of packet read errors to switch off 3d Pro optimization */ +#define SW_OK 64 /* Number of packet read successes to switch optimization back on */ +#define SW_LENGTH 512 /* Max number of bits in a packet */ + +#ifdef SW_DEBUG +#define dbg(format, arg...) printk(KERN_DEBUG __FILE__ ": " format "\n" , ## arg) +#else +#define dbg(format, arg...) do {} while (0) +#endif + +/* + * SideWinder joystick types ... + */ + +#define SW_ID_3DP 0 +#define SW_ID_GP 1 +#define SW_ID_PP 2 +#define SW_ID_FFP 3 +#define SW_ID_FSP 4 +#define SW_ID_FFW 5 + +/* + * Names, buttons, axes ... + */ + +static char *sw_name[] = { "3D Pro", "GamePad", "Precision Pro", "Force Feedback Pro", "FreeStyle Pro", + "Force Feedback Wheel" }; + +static char sw_abs[][7] = { + { ABS_X, ABS_Y, ABS_RZ, ABS_THROTTLE, ABS_HAT0X, ABS_HAT0Y }, + { ABS_X, ABS_Y }, + { ABS_X, ABS_Y, ABS_RZ, ABS_THROTTLE, ABS_HAT0X, ABS_HAT0Y }, + { ABS_X, ABS_Y, ABS_RZ, ABS_THROTTLE, ABS_HAT0X, ABS_HAT0Y }, + { ABS_X, ABS_Y, ABS_THROTTLE, ABS_HAT0X, ABS_HAT0Y }, + { ABS_RX, ABS_RUDDER, ABS_THROTTLE }}; + +static char sw_bit[][7] = { + { 10, 10, 9, 10, 1, 1 }, + { 1, 1 }, + { 10, 10, 6, 7, 1, 1 }, + { 10, 10, 6, 7, 1, 1 }, + { 10, 10, 6, 1, 1 }, + { 10, 7, 7, 1, 1 }}; + +static short sw_btn[][12] = { + { BTN_TRIGGER, BTN_TOP, BTN_THUMB, BTN_THUMB2, BTN_BASE, BTN_BASE2, BTN_BASE3, BTN_BASE4, BTN_MODE }, + { BTN_A, BTN_B, BTN_C, BTN_X, BTN_Y, BTN_Z, BTN_TL, BTN_TR, BTN_START, BTN_MODE }, + { BTN_TRIGGER, BTN_THUMB, BTN_TOP, BTN_TOP2, BTN_BASE, BTN_BASE2, BTN_BASE3, BTN_BASE4, BTN_SELECT }, + { BTN_TRIGGER, BTN_THUMB, BTN_TOP, BTN_TOP2, BTN_BASE, BTN_BASE2, BTN_BASE3, BTN_BASE4, BTN_SELECT }, + { BTN_A, BTN_B, BTN_C, BTN_X, BTN_Y, BTN_Z, BTN_TL, BTN_TR, BTN_START, BTN_MODE, BTN_SELECT }, + { BTN_TRIGGER, BTN_TOP, BTN_THUMB, BTN_THUMB2, BTN_BASE, BTN_BASE2, BTN_BASE3, BTN_BASE4 }}; + +static struct { + int x; + int y; +} sw_hat_to_axis[] = {{ 0, 0}, { 0,-1}, { 1,-1}, { 1, 0}, { 1, 1}, { 0, 1}, {-1, 1}, {-1, 0}, {-1,-1}}; + +struct sw { + struct gameport *gameport; + struct input_dev dev[4]; + char name[64]; + char phys[4][32]; + int length; + int type; + int bits; + int number; + int fail; + int ok; + int reads; + int bads; +}; + +/* + * sw_read_packet() is a function which reads either a data packet, or an + * identification packet from a SideWinder joystick. The protocol is very, + * very, very braindamaged. Microsoft patented it in US patent #5628686. + */ + +static int sw_read_packet(struct gameport *gameport, unsigned char *buf, int length, int id) +{ + unsigned long flags; + int timeout, bitout, sched, i, kick, start, strobe; + unsigned char pending, u, v; + + i = -id; /* Don't care about data, only want ID */ + timeout = id ? gameport_time(gameport, SW_TIMEOUT * 1000) : 0; /* Set up global timeout for ID packet */ + kick = id ? gameport_time(gameport, SW_KICK) : 0; /* Set up kick timeout for ID packet */ + start = gameport_time(gameport, SW_START); + strobe = gameport_time(gameport, SW_STROBE); + bitout = start; + pending = 0; + sched = 0; + + local_irq_save(flags); /* Quiet, please */ + + gameport_trigger(gameport); /* Trigger */ + v = gameport_read(gameport); + + do { + bitout--; + u = v; + v = gameport_read(gameport); + } while (!(~v & u & 0x10) && (bitout > 0)); /* Wait for first falling edge on clock */ + + if (bitout > 0) + bitout = strobe; /* Extend time if not timed out */ + + while ((timeout > 0 || bitout > 0) && (i < length)) { + + timeout--; + bitout--; /* Decrement timers */ + sched--; + + u = v; + v = gameport_read(gameport); + + if ((~u & v & 0x10) && (bitout > 0)) { /* Rising edge on clock - data bit */ + if (i >= 0) /* Want this data */ + buf[i] = v >> 5; /* Store it */ + i++; /* Advance index */ + bitout = strobe; /* Extend timeout for next bit */ + } + + if (kick && (~v & u & 0x01)) { /* Falling edge on axis 0 */ + sched = kick; /* Schedule second trigger */ + kick = 0; /* Don't schedule next time on falling edge */ + pending = 1; /* Mark schedule */ + } + + if (pending && sched < 0 && (i > -SW_END)) { /* Second trigger time */ + gameport_trigger(gameport); /* Trigger */ + bitout = start; /* Long bit timeout */ + pending = 0; /* Unmark schedule */ + timeout = 0; /* Switch from global to bit timeouts */ + } + } + + local_irq_restore(flags); /* Done - relax */ + +#ifdef SW_DEBUG_DATA + { + int j; + printk(KERN_DEBUG "sidewinder.c: Read %d triplets. [", i); + for (j = 0; j < i; j++) printk("%d", buf[j]); + printk("]\n"); + } +#endif + + return i; +} + +/* + * sw_get_bits() and GB() compose bits from the triplet buffer into a __u64. + * Parameter 'pos' is bit number inside packet where to start at, 'num' is number + * of bits to be read, 'shift' is offset in the resulting __u64 to start at, bits + * is number of bits per triplet. + */ + +#define GB(pos,num) sw_get_bits(buf, pos, num, sw->bits) + +static __u64 sw_get_bits(unsigned char *buf, int pos, int num, char bits) +{ + __u64 data = 0; + int tri = pos % bits; /* Start position */ + int i = pos / bits; + int bit = 0; + + while (num--) { + data |= (__u64)((buf[i] >> tri++) & 1) << bit++; /* Transfer bit */ + if (tri == bits) { + i++; /* Next triplet */ + tri = 0; + } + } + + return data; +} + +/* + * sw_init_digital() initializes a SideWinder 3D Pro joystick + * into digital mode. + */ + +static void sw_init_digital(struct gameport *gameport) +{ + int seq[] = { 140, 140+725, 140+300, 0 }; + unsigned long flags; + int i, t; + + local_irq_save(flags); + + i = 0; + do { + gameport_trigger(gameport); /* Trigger */ + t = gameport_time(gameport, SW_TIMEOUT * 1000); + while ((gameport_read(gameport) & 1) && t) t--; /* Wait for axis to fall back to 0 */ + udelay(seq[i]); /* Delay magic time */ + } while (seq[++i]); + + gameport_trigger(gameport); /* Last trigger */ + + local_irq_restore(flags); +} + +/* + * sw_parity() computes parity of __u64 + */ + +static int sw_parity(__u64 t) +{ + int x = t ^ (t >> 32); + + x ^= x >> 16; + x ^= x >> 8; + x ^= x >> 4; + x ^= x >> 2; + x ^= x >> 1; + return x & 1; +} + +/* + * sw_ccheck() checks synchronization bits and computes checksum of nibbles. + */ + +static int sw_check(__u64 t) +{ + unsigned char sum = 0; + + if ((t & 0x8080808080808080ULL) ^ 0x80) /* Sync */ + return -1; + + while (t) { /* Sum */ + sum += t & 0xf; + t >>= 4; + } + + return sum & 0xf; +} + +/* + * sw_parse() analyzes SideWinder joystick data, and writes the results into + * the axes and buttons arrays. + */ + +static int sw_parse(unsigned char *buf, struct sw *sw) +{ + int hat, i, j; + struct input_dev *dev = sw->dev; + + switch (sw->type) { + + case SW_ID_3DP: + + if (sw_check(GB(0,64)) || (hat = (GB(6,1) << 3) | GB(60,3)) > 8) + return -1; + + input_report_abs(dev, ABS_X, (GB( 3,3) << 7) | GB(16,7)); + input_report_abs(dev, ABS_Y, (GB( 0,3) << 7) | GB(24,7)); + input_report_abs(dev, ABS_RZ, (GB(35,2) << 7) | GB(40,7)); + input_report_abs(dev, ABS_THROTTLE, (GB(32,3) << 7) | GB(48,7)); + + input_report_abs(dev, ABS_HAT0X, sw_hat_to_axis[hat].x); + input_report_abs(dev, ABS_HAT0Y, sw_hat_to_axis[hat].y); + + for (j = 0; j < 7; j++) + input_report_key(dev, sw_btn[SW_ID_3DP][j], !GB(j+8,1)); + + input_report_key(dev, BTN_BASE4, !GB(38,1)); + input_report_key(dev, BTN_BASE5, !GB(37,1)); + + input_sync(dev); + + return 0; + + case SW_ID_GP: + + for (i = 0; i < sw->number; i ++) { + + if (sw_parity(GB(i*15,15))) + return -1; + + input_report_abs(dev + i, ABS_X, GB(i*15+3,1) - GB(i*15+2,1)); + input_report_abs(dev + i, ABS_Y, GB(i*15+0,1) - GB(i*15+1,1)); + + for (j = 0; j < 10; j++) + input_report_key(dev + i, sw_btn[SW_ID_GP][j], !GB(i*15+j+4,1)); + + input_sync(dev + i); + } + + return 0; + + case SW_ID_PP: + case SW_ID_FFP: + + if (!sw_parity(GB(0,48)) || (hat = GB(42,4)) > 8) + return -1; + + input_report_abs(dev, ABS_X, GB( 9,10)); + input_report_abs(dev, ABS_Y, GB(19,10)); + input_report_abs(dev, ABS_RZ, GB(36, 6)); + input_report_abs(dev, ABS_THROTTLE, GB(29, 7)); + + input_report_abs(dev, ABS_HAT0X, sw_hat_to_axis[hat].x); + input_report_abs(dev, ABS_HAT0Y, sw_hat_to_axis[hat].y); + + for (j = 0; j < 9; j++) + input_report_key(dev, sw_btn[SW_ID_PP][j], !GB(j,1)); + + input_sync(dev); + + return 0; + + case SW_ID_FSP: + + if (!sw_parity(GB(0,43)) || (hat = GB(28,4)) > 8) + return -1; + + input_report_abs(dev, ABS_X, GB( 0,10)); + input_report_abs(dev, ABS_Y, GB(16,10)); + input_report_abs(dev, ABS_THROTTLE, GB(32, 6)); + + input_report_abs(dev, ABS_HAT0X, sw_hat_to_axis[hat].x); + input_report_abs(dev, ABS_HAT0Y, sw_hat_to_axis[hat].y); + + for (j = 0; j < 6; j++) + input_report_key(dev, sw_btn[SW_ID_FSP][j], !GB(j+10,1)); + + input_report_key(dev, BTN_TR, !GB(26,1)); + input_report_key(dev, BTN_START, !GB(27,1)); + input_report_key(dev, BTN_MODE, !GB(38,1)); + input_report_key(dev, BTN_SELECT, !GB(39,1)); + + input_sync(dev); + + return 0; + + case SW_ID_FFW: + + if (!sw_parity(GB(0,33))) + return -1; + + input_report_abs(dev, ABS_RX, GB( 0,10)); + input_report_abs(dev, ABS_RUDDER, GB(10, 6)); + input_report_abs(dev, ABS_THROTTLE, GB(16, 6)); + + for (j = 0; j < 8; j++) + input_report_key(dev, sw_btn[SW_ID_FFW][j], !GB(j+22,1)); + + input_sync(dev); + + return 0; + } + + return -1; +} + +/* + * sw_read() reads SideWinder joystick data, and reinitializes + * the joystick in case of persistent problems. This is the function that is + * called from the generic code to poll the joystick. + */ + +static int sw_read(struct sw *sw) +{ + unsigned char buf[SW_LENGTH]; + int i; + + i = sw_read_packet(sw->gameport, buf, sw->length, 0); + + if (sw->type == SW_ID_3DP && sw->length == 66 && i != 66) { /* Broken packet, try to fix */ + + if (i == 64 && !sw_check(sw_get_bits(buf,0,64,1))) { /* Last init failed, 1 bit mode */ + printk(KERN_WARNING "sidewinder.c: Joystick in wrong mode on %s" + " - going to reinitialize.\n", sw->gameport->phys); + sw->fail = SW_FAIL; /* Reinitialize */ + i = 128; /* Bogus value */ + } + + if (i < 66 && GB(0,64) == GB(i*3-66,64)) /* 1 == 3 */ + i = 66; /* Everything is fine */ + + if (i < 66 && GB(0,64) == GB(66,64)) /* 1 == 2 */ + i = 66; /* Everything is fine */ + + if (i < 66 && GB(i*3-132,64) == GB(i*3-66,64)) { /* 2 == 3 */ + memmove(buf, buf + i - 22, 22); /* Move data */ + i = 66; /* Carry on */ + } + } + + if (i == sw->length && !sw_parse(buf, sw)) { /* Parse data */ + + sw->fail = 0; + sw->ok++; + + if (sw->type == SW_ID_3DP && sw->length == 66 /* Many packets OK */ + && sw->ok > SW_OK) { + + printk(KERN_INFO "sidewinder.c: No more trouble on %s" + " - enabling optimization again.\n", sw->gameport->phys); + sw->length = 22; + } + + return 0; + } + + sw->ok = 0; + sw->fail++; + + if (sw->type == SW_ID_3DP && sw->length == 22 && sw->fail > SW_BAD) { /* Consecutive bad packets */ + + printk(KERN_INFO "sidewinder.c: Many bit errors on %s" + " - disabling optimization.\n", sw->gameport->phys); + sw->length = 66; + } + + if (sw->fail < SW_FAIL) + return -1; /* Not enough, don't reinitialize yet */ + + printk(KERN_WARNING "sidewinder.c: Too many bit errors on %s" + " - reinitializing joystick.\n", sw->gameport->phys); + + if (!i && sw->type == SW_ID_3DP) { /* 3D Pro can be in analog mode */ + mdelay(3 * SW_TIMEOUT); + sw_init_digital(sw->gameport); + } + + mdelay(SW_TIMEOUT); + i = sw_read_packet(sw->gameport, buf, SW_LENGTH, 0); /* Read normal data packet */ + mdelay(SW_TIMEOUT); + sw_read_packet(sw->gameport, buf, SW_LENGTH, i); /* Read ID packet, this initializes the stick */ + + sw->fail = SW_FAIL; + + return -1; +} + +static void sw_poll(struct gameport *gameport) +{ + struct sw *sw = gameport_get_drvdata(gameport); + + sw->reads++; + if (sw_read(sw)) + sw->bads++; +} + +static int sw_open(struct input_dev *dev) +{ + struct sw *sw = dev->private; + + gameport_start_polling(sw->gameport); + return 0; +} + +static void sw_close(struct input_dev *dev) +{ + struct sw *sw = dev->private; + + gameport_stop_polling(sw->gameport); +} + +/* + * sw_print_packet() prints the contents of a SideWinder packet. + */ + +static void sw_print_packet(char *name, int length, unsigned char *buf, char bits) +{ + int i; + + printk(KERN_INFO "sidewinder.c: %s packet, %d bits. [", name, length); + for (i = (((length + 3) >> 2) - 1); i >= 0; i--) + printk("%x", (int)sw_get_bits(buf, i << 2, 4, bits)); + printk("]\n"); +} + +/* + * sw_3dp_id() translates the 3DP id into a human legible string. + * Unfortunately I don't know how to do this for the other SW types. + */ + +static void sw_3dp_id(unsigned char *buf, char *comment) +{ + int i; + char pnp[8], rev[9]; + + for (i = 0; i < 7; i++) /* ASCII PnP ID */ + pnp[i] = sw_get_bits(buf, 24+8*i, 8, 1); + + for (i = 0; i < 8; i++) /* ASCII firmware revision */ + rev[i] = sw_get_bits(buf, 88+8*i, 8, 1); + + pnp[7] = rev[8] = 0; + + sprintf(comment, " [PnP %d.%02d id %s rev %s]", + (int) ((sw_get_bits(buf, 8, 6, 1) << 6) | /* Two 6-bit values */ + sw_get_bits(buf, 16, 6, 1)) / 100, + (int) ((sw_get_bits(buf, 8, 6, 1) << 6) | + sw_get_bits(buf, 16, 6, 1)) % 100, + pnp, rev); +} + +/* + * sw_guess_mode() checks the upper two button bits for toggling - + * indication of that the joystick is in 3-bit mode. This is documented + * behavior for 3DP ID packet, and for example the FSP does this in + * normal packets instead. Fun ... + */ + +static int sw_guess_mode(unsigned char *buf, int len) +{ + int i; + unsigned char xor = 0; + + for (i = 1; i < len; i++) + xor |= (buf[i - 1] ^ buf[i]) & 6; + + return !!xor * 2 + 1; +} + +/* + * sw_connect() probes for SideWinder type joysticks. + */ + +static int sw_connect(struct gameport *gameport, struct gameport_driver *drv) +{ + struct sw *sw; + int i, j, k, l; + int err; + unsigned char *buf = NULL; /* [SW_LENGTH] */ + unsigned char *idbuf = NULL; /* [SW_LENGTH] */ + unsigned char m = 1; + char comment[40]; + + comment[0] = 0; + + sw = kcalloc(1, sizeof(struct sw), GFP_KERNEL); + buf = kmalloc(SW_LENGTH, GFP_KERNEL); + idbuf = kmalloc(SW_LENGTH, GFP_KERNEL); + if (!sw || !buf || !idbuf) { + err = -ENOMEM; + goto fail1; + } + + sw->gameport = gameport; + + gameport_set_drvdata(gameport, sw); + + err = gameport_open(gameport, drv, GAMEPORT_MODE_RAW); + if (err) + goto fail1; + + dbg("Init 0: Opened %s, io %#x, speed %d", + gameport->phys, gameport->io, gameport->speed); + + i = sw_read_packet(gameport, buf, SW_LENGTH, 0); /* Read normal packet */ + msleep(SW_TIMEOUT); + dbg("Init 1: Mode %d. Length %d.", m , i); + + if (!i) { /* No data. 3d Pro analog mode? */ + sw_init_digital(gameport); /* Switch to digital */ + msleep(SW_TIMEOUT); + i = sw_read_packet(gameport, buf, SW_LENGTH, 0); /* Retry reading packet */ + msleep(SW_TIMEOUT); + dbg("Init 1b: Length %d.", i); + if (!i) { /* No data -> FAIL */ + err = -ENODEV; + goto fail2; + } + } + + j = sw_read_packet(gameport, idbuf, SW_LENGTH, i); /* Read ID. This initializes the stick */ + m |= sw_guess_mode(idbuf, j); /* ID packet should carry mode info [3DP] */ + dbg("Init 2: Mode %d. ID Length %d.", m, j); + + if (j <= 0) { /* Read ID failed. Happens in 1-bit mode on PP */ + msleep(SW_TIMEOUT); + i = sw_read_packet(gameport, buf, SW_LENGTH, 0); /* Retry reading packet */ + m |= sw_guess_mode(buf, i); + dbg("Init 2b: Mode %d. Length %d.", m, i); + if (!i) { + err = -ENODEV; + goto fail2; + } + msleep(SW_TIMEOUT); + j = sw_read_packet(gameport, idbuf, SW_LENGTH, i); /* Retry reading ID */ + dbg("Init 2c: ID Length %d.", j); + } + + sw->type = -1; + k = SW_FAIL; /* Try SW_FAIL times */ + l = 0; + + do { + k--; + msleep(SW_TIMEOUT); + i = sw_read_packet(gameport, buf, SW_LENGTH, 0); /* Read data packet */ + dbg("Init 3: Mode %d. Length %d. Last %d. Tries %d.", m, i, l, k); + + if (i > l) { /* Longer? As we can only lose bits, it makes */ + /* no sense to try detection for a packet shorter */ + l = i; /* than the previous one */ + + sw->number = 1; + sw->gameport = gameport; + sw->length = i; + sw->bits = m; + + dbg("Init 3a: Case %d.\n", i * m); + + switch (i * m) { + case 60: + sw->number++; + case 45: /* Ambiguous packet length */ + if (j <= 40) { /* ID length less or eq 40 -> FSP */ + case 43: + sw->type = SW_ID_FSP; + break; + } + sw->number++; + case 30: + sw->number++; + case 15: + sw->type = SW_ID_GP; + break; + case 33: + case 31: + sw->type = SW_ID_FFW; + break; + case 48: /* Ambiguous */ + if (j == 14) { /* ID length 14*3 -> FFP */ + sw->type = SW_ID_FFP; + sprintf(comment, " [AC %s]", sw_get_bits(idbuf,38,1,3) ? "off" : "on"); + } else + sw->type = SW_ID_PP; + break; + case 66: + sw->bits = 3; + case 198: + sw->length = 22; + case 64: + sw->type = SW_ID_3DP; + if (j == 160) sw_3dp_id(idbuf, comment); + break; + } + } + + } while (k && sw->type == -1); + + if (sw->type == -1) { + printk(KERN_WARNING "sidewinder.c: unknown joystick device detected " + "on %s, contact <vojtech@ucw.cz>\n", gameport->phys); + sw_print_packet("ID", j * 3, idbuf, 3); + sw_print_packet("Data", i * m, buf, m); + err = -ENODEV; + goto fail2; + } + +#ifdef SW_DEBUG + sw_print_packet("ID", j * 3, idbuf, 3); + sw_print_packet("Data", i * m, buf, m); +#endif + + gameport_set_poll_handler(gameport, sw_poll); + gameport_set_poll_interval(gameport, 20); + + k = i; + l = j; + + for (i = 0; i < sw->number; i++) { + int bits, code; + + sprintf(sw->name, "Microsoft SideWinder %s", sw_name[sw->type]); + sprintf(sw->phys[i], "%s/input%d", gameport->phys, i); + + sw->dev[i].private = sw; + + sw->dev[i].open = sw_open; + sw->dev[i].close = sw_close; + + sw->dev[i].name = sw->name; + sw->dev[i].phys = sw->phys[i]; + sw->dev[i].id.bustype = BUS_GAMEPORT; + sw->dev[i].id.vendor = GAMEPORT_ID_VENDOR_MICROSOFT; + sw->dev[i].id.product = sw->type; + sw->dev[i].id.version = 0x0100; + + sw->dev[i].evbit[0] = BIT(EV_KEY) | BIT(EV_ABS); + + for (j = 0; (bits = sw_bit[sw->type][j]); j++) { + code = sw_abs[sw->type][j]; + set_bit(code, sw->dev[i].absbit); + sw->dev[i].absmax[code] = (1 << bits) - 1; + sw->dev[i].absmin[code] = (bits == 1) ? -1 : 0; + sw->dev[i].absfuzz[code] = ((bits >> 1) >= 2) ? (1 << ((bits >> 1) - 2)) : 0; + if (code != ABS_THROTTLE) + sw->dev[i].absflat[code] = (bits >= 5) ? (1 << (bits - 5)) : 0; + } + + for (j = 0; (code = sw_btn[sw->type][j]); j++) + set_bit(code, sw->dev[i].keybit); + + input_register_device(sw->dev + i); + printk(KERN_INFO "input: %s%s on %s [%d-bit id %d data %d]\n", + sw->name, comment, gameport->phys, m, l, k); + } + + return 0; + +fail2: gameport_close(gameport); +fail1: gameport_set_drvdata(gameport, NULL); + kfree(sw); + kfree(buf); + kfree(idbuf); + return err; +} + +static void sw_disconnect(struct gameport *gameport) +{ + struct sw *sw = gameport_get_drvdata(gameport); + int i; + + for (i = 0; i < sw->number; i++) + input_unregister_device(sw->dev + i); + gameport_close(gameport); + gameport_set_drvdata(gameport, NULL); + kfree(sw); +} + +static struct gameport_driver sw_drv = { + .driver = { + .name = "sidewinder", + }, + .description = DRIVER_DESC, + .connect = sw_connect, + .disconnect = sw_disconnect, +}; + +static int __init sw_init(void) +{ + gameport_register_driver(&sw_drv); + return 0; +} + +static void __exit sw_exit(void) +{ + gameport_unregister_driver(&sw_drv); +} + +module_init(sw_init); +module_exit(sw_exit); diff --git a/drivers/input/joystick/spaceball.c b/drivers/input/joystick/spaceball.c new file mode 100644 index 000000000000..ec0a2a64d49c --- /dev/null +++ b/drivers/input/joystick/spaceball.c @@ -0,0 +1,319 @@ +/* + * $Id: spaceball.c,v 1.17 2002/01/22 20:29:03 vojtech Exp $ + * + * Copyright (c) 1999-2001 Vojtech Pavlik + * + * Based on the work of: + * David Thompson + * Joseph Krahn + */ + +/* + * SpaceTec SpaceBall 2003/3003/4000 FLX driver for Linux + */ + +/* + * 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 either by + * e-mail - mail your message to <vojtech@ucw.cz>, or by paper mail: + * Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic + */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/serio.h> + +#define DRIVER_DESC "SpaceTec SpaceBall 2003/3003/4000 FLX driver" + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +/* + * Constants. + */ + +#define SPACEBALL_MAX_LENGTH 128 +#define SPACEBALL_MAX_ID 8 + +#define SPACEBALL_1003 1 +#define SPACEBALL_2003B 3 +#define SPACEBALL_2003C 4 +#define SPACEBALL_3003C 7 +#define SPACEBALL_4000FLX 8 +#define SPACEBALL_4000FLX_L 9 + +static int spaceball_axes[] = { ABS_X, ABS_Z, ABS_Y, ABS_RX, ABS_RZ, ABS_RY }; +static char *spaceball_names[] = { + "?", "SpaceTec SpaceBall 1003", "SpaceTec SpaceBall 2003", "SpaceTec SpaceBall 2003B", + "SpaceTec SpaceBall 2003C", "SpaceTec SpaceBall 3003", "SpaceTec SpaceBall SpaceController", + "SpaceTec SpaceBall 3003C", "SpaceTec SpaceBall 4000FLX", "SpaceTec SpaceBall 4000FLX Lefty" }; + +/* + * Per-Ball data. + */ + +struct spaceball { + struct input_dev dev; + struct serio *serio; + int idx; + int escape; + unsigned char data[SPACEBALL_MAX_LENGTH]; + char phys[32]; +}; + +/* + * spaceball_process_packet() decodes packets the driver receives from the + * SpaceBall. + */ + +static void spaceball_process_packet(struct spaceball* spaceball, struct pt_regs *regs) +{ + struct input_dev *dev = &spaceball->dev; + unsigned char *data = spaceball->data; + int i; + + if (spaceball->idx < 2) return; + + input_regs(dev, regs); + + switch (spaceball->data[0]) { + + case 'D': /* Ball data */ + if (spaceball->idx != 15) return; + for (i = 0; i < 6; i++) + input_report_abs(dev, spaceball_axes[i], + (__s16)((data[2 * i + 3] << 8) | data[2 * i + 2])); + break; + + case 'K': /* Button data */ + if (spaceball->idx != 3) return; + input_report_key(dev, BTN_1, (data[2] & 0x01) || (data[2] & 0x20)); + input_report_key(dev, BTN_2, data[2] & 0x02); + input_report_key(dev, BTN_3, data[2] & 0x04); + input_report_key(dev, BTN_4, data[2] & 0x08); + input_report_key(dev, BTN_5, data[1] & 0x01); + input_report_key(dev, BTN_6, data[1] & 0x02); + input_report_key(dev, BTN_7, data[1] & 0x04); + input_report_key(dev, BTN_8, data[1] & 0x10); + break; + + case '.': /* Advanced button data */ + if (spaceball->idx != 3) return; + input_report_key(dev, BTN_1, data[2] & 0x01); + input_report_key(dev, BTN_2, data[2] & 0x02); + input_report_key(dev, BTN_3, data[2] & 0x04); + input_report_key(dev, BTN_4, data[2] & 0x08); + input_report_key(dev, BTN_5, data[2] & 0x10); + input_report_key(dev, BTN_6, data[2] & 0x20); + input_report_key(dev, BTN_7, data[2] & 0x80); + input_report_key(dev, BTN_8, data[1] & 0x01); + input_report_key(dev, BTN_9, data[1] & 0x02); + input_report_key(dev, BTN_A, data[1] & 0x04); + input_report_key(dev, BTN_B, data[1] & 0x08); + input_report_key(dev, BTN_C, data[1] & 0x10); + input_report_key(dev, BTN_MODE, data[1] & 0x20); + break; + + case 'E': /* Device error */ + spaceball->data[spaceball->idx - 1] = 0; + printk(KERN_ERR "spaceball: Device error. [%s]\n", spaceball->data + 1); + break; + + case '?': /* Bad command packet */ + spaceball->data[spaceball->idx - 1] = 0; + printk(KERN_ERR "spaceball: Bad command. [%s]\n", spaceball->data + 1); + break; + } + + input_sync(dev); +} + +/* + * Spaceball 4000 FLX packets all start with a one letter packet-type decriptor, + * and end in 0x0d. It uses '^' as an escape for CR, XOFF and XON characters which + * can occur in the axis values. + */ + +static irqreturn_t spaceball_interrupt(struct serio *serio, + unsigned char data, unsigned int flags, struct pt_regs *regs) +{ + struct spaceball *spaceball = serio_get_drvdata(serio); + + switch (data) { + case 0xd: + spaceball_process_packet(spaceball, regs); + spaceball->idx = 0; + spaceball->escape = 0; + break; + case '^': + if (!spaceball->escape) { + spaceball->escape = 1; + break; + } + spaceball->escape = 0; + case 'M': + case 'Q': + case 'S': + if (spaceball->escape) { + spaceball->escape = 0; + data &= 0x1f; + } + default: + if (spaceball->escape) + spaceball->escape = 0; + if (spaceball->idx < SPACEBALL_MAX_LENGTH) + spaceball->data[spaceball->idx++] = data; + break; + } + return IRQ_HANDLED; +} + +/* + * spaceball_disconnect() is the opposite of spaceball_connect() + */ + +static void spaceball_disconnect(struct serio *serio) +{ + struct spaceball* spaceball = serio_get_drvdata(serio); + + input_unregister_device(&spaceball->dev); + serio_close(serio); + serio_set_drvdata(serio, NULL); + kfree(spaceball); +} + +/* + * spaceball_connect() is the routine that is called when someone adds a + * new serio device that supports Spaceball protocol and registers it as + * an input device. + */ + +static int spaceball_connect(struct serio *serio, struct serio_driver *drv) +{ + struct spaceball *spaceball; + int i, t, id; + int err; + + if ((id = serio->id.id) > SPACEBALL_MAX_ID) + return -ENODEV; + + if (!(spaceball = kmalloc(sizeof(struct spaceball), GFP_KERNEL))) + return - ENOMEM; + + memset(spaceball, 0, sizeof(struct spaceball)); + + spaceball->dev.evbit[0] = BIT(EV_KEY) | BIT(EV_ABS); + + switch (id) { + case SPACEBALL_4000FLX: + case SPACEBALL_4000FLX_L: + spaceball->dev.keybit[LONG(BTN_0)] |= BIT(BTN_9); + spaceball->dev.keybit[LONG(BTN_A)] |= BIT(BTN_A) | BIT(BTN_B) | BIT(BTN_C) | BIT(BTN_MODE); + default: + spaceball->dev.keybit[LONG(BTN_0)] |= BIT(BTN_2) | BIT(BTN_3) | BIT(BTN_4) + | BIT(BTN_5) | BIT(BTN_6) | BIT(BTN_7) | BIT(BTN_8); + case SPACEBALL_3003C: + spaceball->dev.keybit[LONG(BTN_0)] |= BIT(BTN_1) | BIT(BTN_8); + } + + for (i = 0; i < 6; i++) { + t = spaceball_axes[i]; + set_bit(t, spaceball->dev.absbit); + spaceball->dev.absmin[t] = i < 3 ? -8000 : -1600; + spaceball->dev.absmax[t] = i < 3 ? 8000 : 1600; + spaceball->dev.absflat[t] = i < 3 ? 40 : 8; + spaceball->dev.absfuzz[t] = i < 3 ? 8 : 2; + } + + spaceball->serio = serio; + spaceball->dev.private = spaceball; + + sprintf(spaceball->phys, "%s/input0", serio->phys); + + init_input_dev(&spaceball->dev); + spaceball->dev.name = spaceball_names[id]; + spaceball->dev.phys = spaceball->phys; + spaceball->dev.id.bustype = BUS_RS232; + spaceball->dev.id.vendor = SERIO_SPACEBALL; + spaceball->dev.id.product = id; + spaceball->dev.id.version = 0x0100; + spaceball->dev.dev = &serio->dev; + + serio_set_drvdata(serio, spaceball); + + err = serio_open(serio, drv); + if (err) { + serio_set_drvdata(serio, NULL); + kfree(spaceball); + return err; + } + + input_register_device(&spaceball->dev); + + printk(KERN_INFO "input: %s on serio%s\n", + spaceball_names[id], serio->phys); + + return 0; +} + +/* + * The serio driver structure. + */ + +static struct serio_device_id spaceball_serio_ids[] = { + { + .type = SERIO_RS232, + .proto = SERIO_SPACEBALL, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { 0 } +}; + +MODULE_DEVICE_TABLE(serio, spaceball_serio_ids); + +static struct serio_driver spaceball_drv = { + .driver = { + .name = "spaceball", + }, + .description = DRIVER_DESC, + .id_table = spaceball_serio_ids, + .interrupt = spaceball_interrupt, + .connect = spaceball_connect, + .disconnect = spaceball_disconnect, +}; + +/* + * The functions for inserting/removing us as a module. + */ + +static int __init spaceball_init(void) +{ + serio_register_driver(&spaceball_drv); + return 0; +} + +static void __exit spaceball_exit(void) +{ + serio_unregister_driver(&spaceball_drv); +} + +module_init(spaceball_init); +module_exit(spaceball_exit); diff --git a/drivers/input/joystick/spaceorb.c b/drivers/input/joystick/spaceorb.c new file mode 100644 index 000000000000..c76cf8ff29c0 --- /dev/null +++ b/drivers/input/joystick/spaceorb.c @@ -0,0 +1,263 @@ +/* + * $Id: spaceorb.c,v 1.15 2002/01/22 20:29:19 vojtech Exp $ + * + * Copyright (c) 1999-2001 Vojtech Pavlik + * + * Based on the work of: + * David Thompson + */ + +/* + * SpaceTec SpaceOrb 360 and Avenger 6dof controller driver for Linux + */ + +/* + * 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 either by + * e-mail - mail your message to <vojtech@ucw.cz>, or by paper mail: + * Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic + */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/serio.h> + +#define DRIVER_DESC "SpaceTec SpaceOrb 360 and Avenger 6dof controller driver" + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +/* + * Constants. + */ + +#define SPACEORB_MAX_LENGTH 64 + +static int spaceorb_buttons[] = { BTN_TL, BTN_TR, BTN_Y, BTN_X, BTN_B, BTN_A }; +static int spaceorb_axes[] = { ABS_X, ABS_Y, ABS_Z, ABS_RX, ABS_RY, ABS_RZ }; +static char *spaceorb_name = "SpaceTec SpaceOrb 360 / Avenger"; + +/* + * Per-Orb data. + */ + +struct spaceorb { + struct input_dev dev; + struct serio *serio; + int idx; + unsigned char data[SPACEORB_MAX_LENGTH]; + char phys[32]; +}; + +static unsigned char spaceorb_xor[] = "SpaceWare"; + +static unsigned char *spaceorb_errors[] = { "EEPROM storing 0 failed", "Receive queue overflow", "Transmit queue timeout", + "Bad packet", "Power brown-out", "EEPROM checksum error", "Hardware fault" }; + +/* + * spaceorb_process_packet() decodes packets the driver receives from the + * SpaceOrb. + */ + +static void spaceorb_process_packet(struct spaceorb *spaceorb, struct pt_regs *regs) +{ + struct input_dev *dev = &spaceorb->dev; + unsigned char *data = spaceorb->data; + unsigned char c = 0; + int axes[6]; + int i; + + if (spaceorb->idx < 2) return; + for (i = 0; i < spaceorb->idx; i++) c ^= data[i]; + if (c) return; + + input_regs(dev, regs); + + switch (data[0]) { + + case 'R': /* Reset packet */ + spaceorb->data[spaceorb->idx - 1] = 0; + for (i = 1; i < spaceorb->idx && spaceorb->data[i] == ' '; i++); + printk(KERN_INFO "input: %s [%s] on %s\n", + spaceorb_name, spaceorb->data + i, spaceorb->serio->phys); + break; + + case 'D': /* Ball + button data */ + if (spaceorb->idx != 12) return; + for (i = 0; i < 9; i++) spaceorb->data[i+2] ^= spaceorb_xor[i]; + axes[0] = ( data[2] << 3) | (data[ 3] >> 4); + axes[1] = ((data[3] & 0x0f) << 6) | (data[ 4] >> 1); + axes[2] = ((data[4] & 0x01) << 9) | (data[ 5] << 2) | (data[4] >> 5); + axes[3] = ((data[6] & 0x1f) << 5) | (data[ 7] >> 2); + axes[4] = ((data[7] & 0x03) << 8) | (data[ 8] << 1) | (data[7] >> 6); + axes[5] = ((data[9] & 0x3f) << 4) | (data[10] >> 3); + for (i = 0; i < 6; i++) + input_report_abs(dev, spaceorb_axes[i], axes[i] - ((axes[i] & 0x200) ? 1024 : 0)); + for (i = 0; i < 6; i++) + input_report_key(dev, spaceorb_buttons[i], (data[1] >> i) & 1); + break; + + case 'K': /* Button data */ + if (spaceorb->idx != 5) return; + for (i = 0; i < 7; i++) + input_report_key(dev, spaceorb_buttons[i], (data[2] >> i) & 1); + + break; + + case 'E': /* Error packet */ + if (spaceorb->idx != 4) return; + printk(KERN_ERR "joy-spaceorb: Device error. [ "); + for (i = 0; i < 7; i++) if (data[1] & (1 << i)) printk("%s ", spaceorb_errors[i]); + printk("]\n"); + break; + } + + input_sync(dev); +} + +static irqreturn_t spaceorb_interrupt(struct serio *serio, + unsigned char data, unsigned int flags, struct pt_regs *regs) +{ + struct spaceorb* spaceorb = serio_get_drvdata(serio); + + if (~data & 0x80) { + if (spaceorb->idx) spaceorb_process_packet(spaceorb, regs); + spaceorb->idx = 0; + } + if (spaceorb->idx < SPACEORB_MAX_LENGTH) + spaceorb->data[spaceorb->idx++] = data & 0x7f; + return IRQ_HANDLED; +} + +/* + * spaceorb_disconnect() is the opposite of spaceorb_connect() + */ + +static void spaceorb_disconnect(struct serio *serio) +{ + struct spaceorb* spaceorb = serio_get_drvdata(serio); + + input_unregister_device(&spaceorb->dev); + serio_close(serio); + serio_set_drvdata(serio, NULL); + kfree(spaceorb); +} + +/* + * spaceorb_connect() is the routine that is called when someone adds a + * new serio device that supports SpaceOrb/Avenger protocol and registers + * it as an input device. + */ + +static int spaceorb_connect(struct serio *serio, struct serio_driver *drv) +{ + struct spaceorb *spaceorb; + int i, t; + int err; + + if (!(spaceorb = kmalloc(sizeof(struct spaceorb), GFP_KERNEL))) + return -ENOMEM; + + memset(spaceorb, 0, sizeof(struct spaceorb)); + + spaceorb->dev.evbit[0] = BIT(EV_KEY) | BIT(EV_ABS); + + for (i = 0; i < 6; i++) + set_bit(spaceorb_buttons[i], spaceorb->dev.keybit); + + for (i = 0; i < 6; i++) { + t = spaceorb_axes[i]; + set_bit(t, spaceorb->dev.absbit); + spaceorb->dev.absmin[t] = -508; + spaceorb->dev.absmax[t] = 508; + } + + spaceorb->serio = serio; + spaceorb->dev.private = spaceorb; + + sprintf(spaceorb->phys, "%s/input0", serio->phys); + + init_input_dev(&spaceorb->dev); + spaceorb->dev.name = spaceorb_name; + spaceorb->dev.phys = spaceorb->phys; + spaceorb->dev.id.bustype = BUS_RS232; + spaceorb->dev.id.vendor = SERIO_SPACEORB; + spaceorb->dev.id.product = 0x0001; + spaceorb->dev.id.version = 0x0100; + spaceorb->dev.dev = &serio->dev; + + serio_set_drvdata(serio, spaceorb); + + err = serio_open(serio, drv); + if (err) { + serio_set_drvdata(serio, NULL); + kfree(spaceorb); + return err; + } + + input_register_device(&spaceorb->dev); + + return 0; +} + +/* + * The serio driver structure. + */ + +static struct serio_device_id spaceorb_serio_ids[] = { + { + .type = SERIO_RS232, + .proto = SERIO_SPACEORB, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { 0 } +}; + +MODULE_DEVICE_TABLE(serio, spaceorb_serio_ids); + +static struct serio_driver spaceorb_drv = { + .driver = { + .name = "spaceorb", + }, + .description = DRIVER_DESC, + .id_table = spaceorb_serio_ids, + .interrupt = spaceorb_interrupt, + .connect = spaceorb_connect, + .disconnect = spaceorb_disconnect, +}; + +/* + * The functions for inserting/removing us as a module. + */ + +static int __init spaceorb_init(void) +{ + serio_register_driver(&spaceorb_drv); + return 0; +} + +static void __exit spaceorb_exit(void) +{ + serio_unregister_driver(&spaceorb_drv); +} + +module_init(spaceorb_init); +module_exit(spaceorb_exit); diff --git a/drivers/input/joystick/stinger.c b/drivers/input/joystick/stinger.c new file mode 100644 index 000000000000..6f6e6753d590 --- /dev/null +++ b/drivers/input/joystick/stinger.c @@ -0,0 +1,236 @@ +/* + * $Id: stinger.c,v 1.10 2002/01/22 20:29:31 vojtech Exp $ + * + * Copyright (c) 2000-2001 Vojtech Pavlik + * Copyright (c) 2000 Mark Fletcher + */ + +/* + * Gravis Stinger gamepad driver for Linux + */ + +/* + * This program is free warftware; 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 either by + * e-mail - mail your message to <vojtech@ucw.cz>, or by paper mail: + * Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/serio.h> +#include <linux/init.h> + +#define DRIVER_DESC "Gravis Stinger gamepad driver" + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +/* + * Constants. + */ + +#define STINGER_MAX_LENGTH 8 + +static char *stinger_name = "Gravis Stinger"; + +/* + * Per-Stinger data. + */ + +struct stinger { + struct input_dev dev; + int idx; + unsigned char data[STINGER_MAX_LENGTH]; + char phys[32]; +}; + +/* + * stinger_process_packet() decodes packets the driver receives from the + * Stinger. It updates the data accordingly. + */ + +static void stinger_process_packet(struct stinger *stinger, struct pt_regs *regs) +{ + struct input_dev *dev = &stinger->dev; + unsigned char *data = stinger->data; + + if (!stinger->idx) return; + + input_regs(dev, regs); + + input_report_key(dev, BTN_A, ((data[0] & 0x20) >> 5)); + input_report_key(dev, BTN_B, ((data[0] & 0x10) >> 4)); + input_report_key(dev, BTN_C, ((data[0] & 0x08) >> 3)); + input_report_key(dev, BTN_X, ((data[0] & 0x04) >> 2)); + input_report_key(dev, BTN_Y, ((data[3] & 0x20) >> 5)); + input_report_key(dev, BTN_Z, ((data[3] & 0x10) >> 4)); + input_report_key(dev, BTN_TL, ((data[3] & 0x08) >> 3)); + input_report_key(dev, BTN_TR, ((data[3] & 0x04) >> 2)); + input_report_key(dev, BTN_SELECT, ((data[3] & 0x02) >> 1)); + input_report_key(dev, BTN_START, (data[3] & 0x01)); + + input_report_abs(dev, ABS_X, (data[1] & 0x3F) - ((data[0] & 0x01) << 6)); + input_report_abs(dev, ABS_Y, ((data[0] & 0x02) << 5) - (data[2] & 0x3F)); + + input_sync(dev); + + return; +} + +/* + * stinger_interrupt() is called by the low level driver when characters + * are ready for us. We then buffer them for further processing, or call the + * packet processing routine. + */ + +static irqreturn_t stinger_interrupt(struct serio *serio, + unsigned char data, unsigned int flags, struct pt_regs *regs) +{ + struct stinger *stinger = serio_get_drvdata(serio); + + /* All Stinger packets are 4 bytes */ + + if (stinger->idx < STINGER_MAX_LENGTH) + stinger->data[stinger->idx++] = data; + + if (stinger->idx == 4) { + stinger_process_packet(stinger, regs); + stinger->idx = 0; + } + + return IRQ_HANDLED; +} + +/* + * stinger_disconnect() is the opposite of stinger_connect() + */ + +static void stinger_disconnect(struct serio *serio) +{ + struct stinger *stinger = serio_get_drvdata(serio); + + input_unregister_device(&stinger->dev); + serio_close(serio); + serio_set_drvdata(serio, NULL); + kfree(stinger); +} + +/* + * stinger_connect() is the routine that is called when someone adds a + * new serio device that supports Stinger protocol and registers it as + * an input device. + */ + +static int stinger_connect(struct serio *serio, struct serio_driver *drv) +{ + struct stinger *stinger; + int i; + int err; + + if (!(stinger = kmalloc(sizeof(struct stinger), GFP_KERNEL))) + return -ENOMEM; + + memset(stinger, 0, sizeof(struct stinger)); + + stinger->dev.evbit[0] = BIT(EV_KEY) | BIT(EV_ABS); + stinger->dev.keybit[LONG(BTN_A)] = BIT(BTN_A) | BIT(BTN_B) | BIT(BTN_C) | BIT(BTN_X) | \ + BIT(BTN_Y) | BIT(BTN_Z) | BIT(BTN_TL) | BIT(BTN_TR) | \ + BIT(BTN_START) | BIT(BTN_SELECT); + stinger->dev.absbit[0] = BIT(ABS_X) | BIT(ABS_Y); + + sprintf(stinger->phys, "%s/serio0", serio->phys); + + init_input_dev(&stinger->dev); + stinger->dev.name = stinger_name; + stinger->dev.phys = stinger->phys; + stinger->dev.id.bustype = BUS_RS232; + stinger->dev.id.vendor = SERIO_STINGER; + stinger->dev.id.product = 0x0001; + stinger->dev.id.version = 0x0100; + stinger->dev.dev = &serio->dev; + + for (i = 0; i < 2; i++) { + stinger->dev.absmax[ABS_X+i] = 64; + stinger->dev.absmin[ABS_X+i] = -64; + stinger->dev.absflat[ABS_X+i] = 4; + } + + stinger->dev.private = stinger; + + serio_set_drvdata(serio, stinger); + + err = serio_open(serio, drv); + if (err) { + serio_set_drvdata(serio, NULL); + kfree(stinger); + return err; + } + + input_register_device(&stinger->dev); + + printk(KERN_INFO "input: %s on %s\n", stinger_name, serio->phys); + + return 0; +} + +/* + * The serio driver structure. + */ + +static struct serio_device_id stinger_serio_ids[] = { + { + .type = SERIO_RS232, + .proto = SERIO_STINGER, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { 0 } +}; + +MODULE_DEVICE_TABLE(serio, stinger_serio_ids); + +static struct serio_driver stinger_drv = { + .driver = { + .name = "stinger", + }, + .description = DRIVER_DESC, + .id_table = stinger_serio_ids, + .interrupt = stinger_interrupt, + .connect = stinger_connect, + .disconnect = stinger_disconnect, +}; + +/* + * The functions for inserting/removing us as a module. + */ + +static int __init stinger_init(void) +{ + serio_register_driver(&stinger_drv); + return 0; +} + +static void __exit stinger_exit(void) +{ + serio_unregister_driver(&stinger_drv); +} + +module_init(stinger_init); +module_exit(stinger_exit); diff --git a/drivers/input/joystick/tmdc.c b/drivers/input/joystick/tmdc.c new file mode 100644 index 000000000000..aaee52ceb920 --- /dev/null +++ b/drivers/input/joystick/tmdc.c @@ -0,0 +1,384 @@ +/* + * $Id: tmdc.c,v 1.31 2002/01/22 20:29:52 vojtech Exp $ + * + * Copyright (c) 1998-2001 Vojtech Pavlik + * + * Based on the work of: + * Trystan Larey-Williams + */ + +/* + * ThrustMaster DirectConnect (BSP) joystick family driver for Linux + */ + +/* + * 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 either by + * e-mail - mail your message to <vojtech@ucw.cz>, or by paper mail: + * Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic + */ + +#include <linux/delay.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/gameport.h> +#include <linux/input.h> + +#define DRIVER_DESC "ThrustMaster DirectConnect joystick driver" + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +#define TMDC_MAX_START 600 /* 600 us */ +#define TMDC_MAX_STROBE 60 /* 60 us */ +#define TMDC_MAX_LENGTH 13 + +#define TMDC_MODE_M3DI 1 +#define TMDC_MODE_3DRP 3 +#define TMDC_MODE_AT 4 +#define TMDC_MODE_FM 8 +#define TMDC_MODE_FGP 163 + +#define TMDC_BYTE_ID 10 +#define TMDC_BYTE_REV 11 +#define TMDC_BYTE_DEF 12 + +#define TMDC_ABS 7 +#define TMDC_ABS_HAT 4 +#define TMDC_BTN 16 + +static unsigned char tmdc_byte_a[16] = { 0, 1, 3, 4, 6, 7 }; +static unsigned char tmdc_byte_d[16] = { 2, 5, 8, 9 }; + +static signed char tmdc_abs[TMDC_ABS] = + { ABS_X, ABS_Y, ABS_RUDDER, ABS_THROTTLE, ABS_RX, ABS_RY, ABS_RZ }; +static signed char tmdc_abs_hat[TMDC_ABS_HAT] = + { ABS_HAT0X, ABS_HAT0Y, ABS_HAT1X, ABS_HAT1Y }; +static signed char tmdc_abs_at[TMDC_ABS] = + { ABS_X, ABS_Y, ABS_RUDDER, -1, ABS_THROTTLE }; +static signed char tmdc_abs_fm[TMDC_ABS] = + { ABS_RX, ABS_RY, ABS_X, ABS_Y }; + +static short tmdc_btn_pad[TMDC_BTN] = + { BTN_A, BTN_B, BTN_C, BTN_X, BTN_Y, BTN_Z, BTN_START, BTN_SELECT, BTN_TL, BTN_TR }; +static short tmdc_btn_joy[TMDC_BTN] = + { BTN_TRIGGER, BTN_THUMB, BTN_TOP, BTN_TOP2, BTN_BASE, BTN_BASE2, BTN_THUMB2, BTN_PINKIE, + BTN_BASE3, BTN_BASE4, BTN_A, BTN_B, BTN_C, BTN_X, BTN_Y, BTN_Z }; +static short tmdc_btn_fm[TMDC_BTN] = + { BTN_TRIGGER, BTN_C, BTN_B, BTN_A, BTN_THUMB, BTN_X, BTN_Y, BTN_Z, BTN_TOP, BTN_TOP2 }; +static short tmdc_btn_at[TMDC_BTN] = + { BTN_TRIGGER, BTN_THUMB2, BTN_PINKIE, BTN_THUMB, BTN_BASE6, BTN_BASE5, BTN_BASE4, + BTN_BASE3, BTN_BASE2, BTN_BASE }; + +static struct { + int x; + int y; +} tmdc_hat_to_axis[] = {{ 0, 0}, { 1, 0}, { 0,-1}, {-1, 0}, { 0, 1}}; + +struct tmdc { + struct gameport *gameport; + struct input_dev dev[2]; + char name[2][64]; + char phys[2][32]; + int mode[2]; + signed char *abs[2]; + short *btn[2]; + unsigned char absc[2]; + unsigned char btnc[2][4]; + unsigned char btno[2][4]; + int reads; + int bads; + unsigned char exists; +}; + +/* + * tmdc_read_packet() reads a ThrustMaster packet. + */ + +static int tmdc_read_packet(struct gameport *gameport, unsigned char data[2][TMDC_MAX_LENGTH]) +{ + unsigned char u, v, w, x; + unsigned long flags; + int i[2], j[2], t[2], p, k; + + p = gameport_time(gameport, TMDC_MAX_STROBE); + + for (k = 0; k < 2; k++) { + t[k] = gameport_time(gameport, TMDC_MAX_START); + i[k] = j[k] = 0; + } + + local_irq_save(flags); + gameport_trigger(gameport); + + w = gameport_read(gameport) >> 4; + + do { + x = w; + w = gameport_read(gameport) >> 4; + + for (k = 0, v = w, u = x; k < 2; k++, v >>= 2, u >>= 2) { + if (~v & u & 2) { + if (t[k] <= 0 || i[k] >= TMDC_MAX_LENGTH) continue; + t[k] = p; + if (j[k] == 0) { /* Start bit */ + if (~v & 1) t[k] = 0; + data[k][i[k]] = 0; j[k]++; continue; + } + if (j[k] == 9) { /* Stop bit */ + if (v & 1) t[k] = 0; + j[k] = 0; i[k]++; continue; + } + data[k][i[k]] |= (~v & 1) << (j[k]++ - 1); /* Data bit */ + } + t[k]--; + } + } while (t[0] > 0 || t[1] > 0); + + local_irq_restore(flags); + + return (i[0] == TMDC_MAX_LENGTH) | ((i[1] == TMDC_MAX_LENGTH) << 1); +} + +/* + * tmdc_poll() reads and analyzes ThrustMaster joystick data. + */ + +static void tmdc_poll(struct gameport *gameport) +{ + unsigned char data[2][TMDC_MAX_LENGTH]; + struct tmdc *tmdc = gameport_get_drvdata(gameport); + struct input_dev *dev; + unsigned char r, bad = 0; + int i, j, k, l; + + tmdc->reads++; + + if ((r = tmdc_read_packet(tmdc->gameport, data)) != tmdc->exists) + bad = 1; + else + + for (j = 0; j < 2; j++) + if (r & (1 << j) & tmdc->exists) { + + if (data[j][TMDC_BYTE_ID] != tmdc->mode[j]) { + bad = 1; + continue; + } + + dev = tmdc->dev + j; + + for (i = 0; i < tmdc->absc[j]; i++) { + if (tmdc->abs[j][i] < 0) continue; + input_report_abs(dev, tmdc->abs[j][i], data[j][tmdc_byte_a[i]]); + } + + switch (tmdc->mode[j]) { + + case TMDC_MODE_M3DI: + + i = tmdc_byte_d[0]; + input_report_abs(dev, ABS_HAT0X, ((data[j][i] >> 3) & 1) - ((data[j][i] >> 1) & 1)); + input_report_abs(dev, ABS_HAT0Y, ((data[j][i] >> 2) & 1) - ( data[j][i] & 1)); + break; + + case TMDC_MODE_AT: + + i = tmdc_byte_a[3]; + input_report_abs(dev, ABS_HAT0X, tmdc_hat_to_axis[(data[j][i] - 141) / 25].x); + input_report_abs(dev, ABS_HAT0Y, tmdc_hat_to_axis[(data[j][i] - 141) / 25].y); + break; + + } + + for (k = l = 0; k < 4; k++) { + for (i = 0; i < tmdc->btnc[j][k]; i++) + input_report_key(dev, tmdc->btn[j][i + l], + ((data[j][tmdc_byte_d[k]] >> (i + tmdc->btno[j][k])) & 1)); + l += tmdc->btnc[j][k]; + } + + input_sync(dev); + } + + tmdc->bads += bad; +} + +static int tmdc_open(struct input_dev *dev) +{ + struct tmdc *tmdc = dev->private; + + gameport_start_polling(tmdc->gameport); + return 0; +} + +static void tmdc_close(struct input_dev *dev) +{ + struct tmdc *tmdc = dev->private; + + gameport_stop_polling(tmdc->gameport); +} + +/* + * tmdc_probe() probes for ThrustMaster type joysticks. + */ + +static int tmdc_connect(struct gameport *gameport, struct gameport_driver *drv) +{ + static struct models { + unsigned char id; + char *name; + char abs; + char hats; + char btnc[4]; + char btno[4]; + signed char *axes; + short *buttons; + } models[] = { { 1, "ThrustMaster Millenium 3D Inceptor", 6, 2, { 4, 2 }, { 4, 6 }, tmdc_abs, tmdc_btn_joy }, + { 3, "ThrustMaster Rage 3D Gamepad", 2, 0, { 8, 2 }, { 0, 0 }, tmdc_abs, tmdc_btn_pad }, + { 4, "ThrustMaster Attack Throttle", 5, 2, { 4, 6 }, { 4, 2 }, tmdc_abs_at, tmdc_btn_at }, + { 8, "ThrustMaster FragMaster", 4, 0, { 8, 2 }, { 0, 0 }, tmdc_abs_fm, tmdc_btn_fm }, + { 163, "Thrustmaster Fusion GamePad", 2, 0, { 8, 2 }, { 0, 0 }, tmdc_abs, tmdc_btn_pad }, + { 0, "Unknown %d-axis, %d-button TM device %d", 0, 0, { 0, 0 }, { 0, 0 }, tmdc_abs, tmdc_btn_joy }}; + + unsigned char data[2][TMDC_MAX_LENGTH]; + struct tmdc *tmdc; + int i, j, k, l, m; + int err; + + if (!(tmdc = kcalloc(1, sizeof(struct tmdc), GFP_KERNEL))) + return -ENOMEM; + + tmdc->gameport = gameport; + + gameport_set_drvdata(gameport, tmdc); + + err = gameport_open(gameport, drv, GAMEPORT_MODE_RAW); + if (err) + goto fail1; + + if (!(tmdc->exists = tmdc_read_packet(gameport, data))) { + err = -ENODEV; + goto fail2; + } + + gameport_set_poll_handler(gameport, tmdc_poll); + gameport_set_poll_interval(gameport, 20); + + for (j = 0; j < 2; j++) + if (tmdc->exists & (1 << j)) { + + tmdc->mode[j] = data[j][TMDC_BYTE_ID]; + + for (m = 0; models[m].id && models[m].id != tmdc->mode[j]; m++); + + tmdc->abs[j] = models[m].axes; + tmdc->btn[j] = models[m].buttons; + + if (!models[m].id) { + models[m].abs = data[j][TMDC_BYTE_DEF] >> 4; + for (k = 0; k < 4; k++) + models[m].btnc[k] = k < (data[j][TMDC_BYTE_DEF] & 0xf) ? 8 : 0; + } + + tmdc->absc[j] = models[m].abs; + for (k = 0; k < 4; k++) { + tmdc->btnc[j][k] = models[m].btnc[k]; + tmdc->btno[j][k] = models[m].btno[k]; + } + + sprintf(tmdc->name[j], models[m].name, models[m].abs, + (data[j][TMDC_BYTE_DEF] & 0xf) << 3, tmdc->mode[j]); + + sprintf(tmdc->phys[j], "%s/input%d", gameport->phys, j); + + tmdc->dev[j].private = tmdc; + tmdc->dev[j].open = tmdc_open; + tmdc->dev[j].close = tmdc_close; + + tmdc->dev[j].name = tmdc->name[j]; + tmdc->dev[j].phys = tmdc->phys[j]; + tmdc->dev[j].id.bustype = BUS_GAMEPORT; + tmdc->dev[j].id.vendor = GAMEPORT_ID_VENDOR_THRUSTMASTER; + tmdc->dev[j].id.product = models[m].id; + tmdc->dev[j].id.version = 0x0100; + + tmdc->dev[j].evbit[0] = BIT(EV_KEY) | BIT(EV_ABS); + + for (i = 0; i < models[m].abs && i < TMDC_ABS; i++) + if (tmdc->abs[j][i] >= 0) + input_set_abs_params(&tmdc->dev[j], tmdc->abs[j][i], 8, 248, 2, 4); + + for (i = 0; i < models[m].hats && i < TMDC_ABS_HAT; i++) + input_set_abs_params(&tmdc->dev[j], tmdc_abs_hat[i], -1, 1, 0, 0); + + + for (k = l = 0; k < 4; k++) { + for (i = 0; i < models[m].btnc[k] && i < TMDC_BTN; i++) + set_bit(tmdc->btn[j][i + l], tmdc->dev[j].keybit); + l += models[m].btnc[k]; + } + + input_register_device(tmdc->dev + j); + printk(KERN_INFO "input: %s on %s\n", tmdc->name[j], gameport->phys); + } + + return 0; + +fail2: gameport_close(gameport); +fail1: gameport_set_drvdata(gameport, NULL); + kfree(tmdc); + return err; +} + +static void tmdc_disconnect(struct gameport *gameport) +{ + struct tmdc *tmdc = gameport_get_drvdata(gameport); + int i; + + for (i = 0; i < 2; i++) + if (tmdc->exists & (1 << i)) + input_unregister_device(tmdc->dev + i); + gameport_close(gameport); + gameport_set_drvdata(gameport, NULL); + kfree(tmdc); +} + +static struct gameport_driver tmdc_drv = { + .driver = { + .name = "tmdc", + }, + .description = DRIVER_DESC, + .connect = tmdc_connect, + .disconnect = tmdc_disconnect, +}; + +static int __init tmdc_init(void) +{ + gameport_register_driver(&tmdc_drv); + return 0; +} + +static void __exit tmdc_exit(void) +{ + gameport_unregister_driver(&tmdc_drv); +} + +module_init(tmdc_init); +module_exit(tmdc_exit); diff --git a/drivers/input/joystick/turbografx.c b/drivers/input/joystick/turbografx.c new file mode 100644 index 000000000000..dd88b9cb49fa --- /dev/null +++ b/drivers/input/joystick/turbografx.c @@ -0,0 +1,258 @@ +/* + * $Id: turbografx.c,v 1.14 2002/01/22 20:30:39 vojtech Exp $ + * + * Copyright (c) 1998-2001 Vojtech Pavlik + * + * Based on the work of: + * Steffen Schwenke + */ + +/* + * TurboGraFX parallel port interface driver for Linux. + */ + +/* + * 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 either by + * e-mail - mail your message to <vojtech@ucw.cz>, or by paper mail: + * Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic + */ + +#include <linux/kernel.h> +#include <linux/parport.h> +#include <linux/input.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION("TurboGraFX parallel port interface driver"); +MODULE_LICENSE("GPL"); + +static int tgfx[] __initdata = { -1, 0, 0, 0, 0, 0, 0, 0 }; +static int tgfx_nargs __initdata = 0; +module_param_array_named(map, tgfx, int, &tgfx_nargs, 0); +MODULE_PARM_DESC(map, "Describes first set of devices (<parport#>,<js1>,<js2>,..<js7>"); + +static int tgfx_2[] __initdata = { -1, 0, 0, 0, 0, 0, 0, 0 }; +static int tgfx_nargs_2 __initdata = 0; +module_param_array_named(map2, tgfx_2, int, &tgfx_nargs_2, 0); +MODULE_PARM_DESC(map2, "Describes second set of devices"); + +static int tgfx_3[] __initdata = { -1, 0, 0, 0, 0, 0, 0, 0 }; +static int tgfx_nargs_3 __initdata = 0; +module_param_array_named(map3, tgfx_3, int, &tgfx_nargs_3, 0); +MODULE_PARM_DESC(map3, "Describes third set of devices"); + +__obsolete_setup("tgfx="); +__obsolete_setup("tgfx_2="); +__obsolete_setup("tgfx_3="); + +#define TGFX_REFRESH_TIME HZ/100 /* 10 ms */ + +#define TGFX_TRIGGER 0x08 +#define TGFX_UP 0x10 +#define TGFX_DOWN 0x20 +#define TGFX_LEFT 0x40 +#define TGFX_RIGHT 0x80 + +#define TGFX_THUMB 0x02 +#define TGFX_THUMB2 0x04 +#define TGFX_TOP 0x01 +#define TGFX_TOP2 0x08 + +static int tgfx_buttons[] = { BTN_TRIGGER, BTN_THUMB, BTN_THUMB2, BTN_TOP, BTN_TOP2 }; +static char *tgfx_name = "TurboGraFX Multisystem joystick"; + +static struct tgfx { + struct pardevice *pd; + struct timer_list timer; + struct input_dev dev[7]; + char phys[7][32]; + int sticks; + int used; +} *tgfx_base[3]; + +/* + * tgfx_timer() reads and analyzes TurboGraFX joystick data. + */ + +static void tgfx_timer(unsigned long private) +{ + struct tgfx *tgfx = (void *) private; + struct input_dev *dev; + int data1, data2, i; + + for (i = 0; i < 7; i++) + if (tgfx->sticks & (1 << i)) { + + dev = tgfx->dev + i; + + parport_write_data(tgfx->pd->port, ~(1 << i)); + data1 = parport_read_status(tgfx->pd->port) ^ 0x7f; + data2 = parport_read_control(tgfx->pd->port) ^ 0x04; /* CAVEAT parport */ + + input_report_abs(dev, ABS_X, !!(data1 & TGFX_RIGHT) - !!(data1 & TGFX_LEFT)); + input_report_abs(dev, ABS_Y, !!(data1 & TGFX_DOWN ) - !!(data1 & TGFX_UP )); + + input_report_key(dev, BTN_TRIGGER, (data1 & TGFX_TRIGGER)); + input_report_key(dev, BTN_THUMB, (data2 & TGFX_THUMB )); + input_report_key(dev, BTN_THUMB2, (data2 & TGFX_THUMB2 )); + input_report_key(dev, BTN_TOP, (data2 & TGFX_TOP )); + input_report_key(dev, BTN_TOP2, (data2 & TGFX_TOP2 )); + + input_sync(dev); + } + + mod_timer(&tgfx->timer, jiffies + TGFX_REFRESH_TIME); +} + +static int tgfx_open(struct input_dev *dev) +{ + struct tgfx *tgfx = dev->private; + if (!tgfx->used++) { + parport_claim(tgfx->pd); + parport_write_control(tgfx->pd->port, 0x04); + mod_timer(&tgfx->timer, jiffies + TGFX_REFRESH_TIME); + } + return 0; +} + +static void tgfx_close(struct input_dev *dev) +{ + struct tgfx *tgfx = dev->private; + if (!--tgfx->used) { + del_timer(&tgfx->timer); + parport_write_control(tgfx->pd->port, 0x00); + parport_release(tgfx->pd); + } +} + +/* + * tgfx_probe() probes for tg gamepads. + */ + +static struct tgfx __init *tgfx_probe(int *config, int nargs) +{ + struct tgfx *tgfx; + struct parport *pp; + int i, j; + + if (config[0] < 0) + return NULL; + + if (nargs < 2) { + printk(KERN_ERR "turbografx.c: at least one joystick must be specified\n"); + return NULL; + } + + pp = parport_find_number(config[0]); + + if (!pp) { + printk(KERN_ERR "turbografx.c: no such parport\n"); + return NULL; + } + + if (!(tgfx = kmalloc(sizeof(struct tgfx), GFP_KERNEL))) { + parport_put_port(pp); + return NULL; + } + memset(tgfx, 0, sizeof(struct tgfx)); + + tgfx->pd = parport_register_device(pp, "turbografx", NULL, NULL, NULL, PARPORT_DEV_EXCL, NULL); + + parport_put_port(pp); + + if (!tgfx->pd) { + printk(KERN_ERR "turbografx.c: parport busy already - lp.o loaded?\n"); + kfree(tgfx); + return NULL; + } + + init_timer(&tgfx->timer); + tgfx->timer.data = (long) tgfx; + tgfx->timer.function = tgfx_timer; + + tgfx->sticks = 0; + + for (i = 0; i < nargs - 1; i++) + if (config[i+1] > 0 && config[i+1] < 6) { + + tgfx->sticks |= (1 << i); + + tgfx->dev[i].private = tgfx; + tgfx->dev[i].open = tgfx_open; + tgfx->dev[i].close = tgfx_close; + + sprintf(tgfx->phys[i], "%s/input0", tgfx->pd->port->name); + + tgfx->dev[i].name = tgfx_name; + tgfx->dev[i].phys = tgfx->phys[i]; + tgfx->dev[i].id.bustype = BUS_PARPORT; + tgfx->dev[i].id.vendor = 0x0003; + tgfx->dev[i].id.product = config[i+1]; + tgfx->dev[i].id.version = 0x0100; + + tgfx->dev[i].evbit[0] = BIT(EV_KEY) | BIT(EV_ABS); + tgfx->dev[i].absbit[0] = BIT(ABS_X) | BIT(ABS_Y); + + for (j = 0; j < config[i+1]; j++) + set_bit(tgfx_buttons[j], tgfx->dev[i].keybit); + + tgfx->dev[i].absmin[ABS_X] = -1; tgfx->dev[i].absmax[ABS_X] = 1; + tgfx->dev[i].absmin[ABS_Y] = -1; tgfx->dev[i].absmax[ABS_Y] = 1; + + input_register_device(tgfx->dev + i); + printk(KERN_INFO "input: %d-button Multisystem joystick on %s\n", + config[i+1], tgfx->pd->port->name); + } + + if (!tgfx->sticks) { + parport_unregister_device(tgfx->pd); + kfree(tgfx); + return NULL; + } + + return tgfx; +} + +static int __init tgfx_init(void) +{ + tgfx_base[0] = tgfx_probe(tgfx, tgfx_nargs); + tgfx_base[1] = tgfx_probe(tgfx_2, tgfx_nargs_2); + tgfx_base[2] = tgfx_probe(tgfx_3, tgfx_nargs_3); + + if (tgfx_base[0] || tgfx_base[1] || tgfx_base[2]) + return 0; + + return -ENODEV; +} + +static void __exit tgfx_exit(void) +{ + int i, j; + + for (i = 0; i < 3; i++) + if (tgfx_base[i]) { + for (j = 0; j < 7; j++) + if (tgfx_base[i]->sticks & (1 << j)) + input_unregister_device(tgfx_base[i]->dev + j); + parport_unregister_device(tgfx_base[i]->pd); + } +} + +module_init(tgfx_init); +module_exit(tgfx_exit); diff --git a/drivers/input/joystick/twidjoy.c b/drivers/input/joystick/twidjoy.c new file mode 100644 index 000000000000..0379bc166525 --- /dev/null +++ b/drivers/input/joystick/twidjoy.c @@ -0,0 +1,296 @@ +/* + * $Id: twidjoy.c,v 1.5 2002/01/22 20:31:53 vojtech Exp $ + * + * derived from CVS-ID "stinger.c,v 1.5 2001/05/29 12:57:18 vojtech Exp" + * + * Copyright (c) 2001 Arndt Schoenewald + * Copyright (c) 2000-2001 Vojtech Pavlik + * Copyright (c) 2000 Mark Fletcher + * + * Sponsored by Quelltext AG (http://www.quelltext-ag.de), Dortmund, Germany + */ + +/* + * Driver to use Handykey's Twiddler (the first edition, i.e. the one with + * the RS232 interface) as a joystick under Linux + * + * The Twiddler is a one-handed chording keyboard featuring twelve buttons on + * the front, six buttons on the top, and a built-in tilt sensor. The buttons + * on the front, which are grouped as four rows of three buttons, are pressed + * by the four fingers (this implies only one button per row can be held down + * at the same time) and the buttons on the top are for the thumb. The tilt + * sensor delivers X and Y axis data depending on how the Twiddler is held. + * Additional information can be found at http://www.handykey.com. + * + * This driver does not use the Twiddler for its intended purpose, i.e. as + * a chording keyboard, but as a joystick: pressing and releasing a button + * immediately sends a corresponding button event, and tilting it generates + * corresponding ABS_X and ABS_Y events. This turns the Twiddler into a game + * controller with amazing 18 buttons :-) + * + * Note: The Twiddler2 (the successor of the Twiddler that connects directly + * to the PS/2 keyboard and mouse ports) is NOT supported by this driver! + * + * For questions or feedback regarding this driver module please contact: + * Arndt Schoenewald <arndt@quelltext.com> + */ + +/* + * 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 + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/serio.h> +#include <linux/init.h> + +#define DRIVER_DESC "Handykey Twiddler keyboard as a joystick driver" + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +/* + * Constants. + */ + +#define TWIDJOY_MAX_LENGTH 5 + +static char *twidjoy_name = "Handykey Twiddler"; + +static struct twidjoy_button_spec { + int bitshift; + int bitmask; + int buttons[3]; +} +twidjoy_buttons[] = { + { 0, 3, { BTN_A, BTN_B, BTN_C } }, + { 2, 3, { BTN_X, BTN_Y, BTN_Z } }, + { 4, 3, { BTN_TL, BTN_TR, BTN_TR2 } }, + { 6, 3, { BTN_SELECT, BTN_START, BTN_MODE } }, + { 8, 1, { BTN_BASE5 } }, + { 9, 1, { BTN_BASE } }, + { 10, 1, { BTN_BASE3 } }, + { 11, 1, { BTN_BASE4 } }, + { 12, 1, { BTN_BASE2 } }, + { 13, 1, { BTN_BASE6 } }, + { 0, 0, { 0 } } +}; + +/* + * Per-Twiddler data. + */ + +struct twidjoy { + struct input_dev dev; + int idx; + unsigned char data[TWIDJOY_MAX_LENGTH]; + char phys[32]; +}; + +/* + * twidjoy_process_packet() decodes packets the driver receives from the + * Twiddler. It updates the data accordingly. + */ + +static void twidjoy_process_packet(struct twidjoy *twidjoy, struct pt_regs *regs) +{ + if (twidjoy->idx == TWIDJOY_MAX_LENGTH) { + struct input_dev *dev = &twidjoy->dev; + unsigned char *data = twidjoy->data; + struct twidjoy_button_spec *bp; + int button_bits, abs_x, abs_y; + + button_bits = ((data[1] & 0x7f) << 7) | (data[0] & 0x7f); + + input_regs(dev, regs); + + for (bp = twidjoy_buttons; bp->bitmask; bp++) { + int value = (button_bits & (bp->bitmask << bp->bitshift)) >> bp->bitshift; + int i; + + for (i = 0; i < bp->bitmask; i++) + input_report_key(dev, bp->buttons[i], i+1 == value); + } + + abs_x = ((data[4] & 0x07) << 5) | ((data[3] & 0x7C) >> 2); + if (data[4] & 0x08) abs_x -= 256; + + abs_y = ((data[3] & 0x01) << 7) | ((data[2] & 0x7F) >> 0); + if (data[3] & 0x02) abs_y -= 256; + + input_report_abs(dev, ABS_X, -abs_x); + input_report_abs(dev, ABS_Y, +abs_y); + + input_sync(dev); + } + + return; +} + +/* + * twidjoy_interrupt() is called by the low level driver when characters + * are ready for us. We then buffer them for further processing, or call the + * packet processing routine. + */ + +static irqreturn_t twidjoy_interrupt(struct serio *serio, unsigned char data, unsigned int flags, struct pt_regs *regs) +{ + struct twidjoy *twidjoy = serio_get_drvdata(serio); + + /* All Twiddler packets are 5 bytes. The fact that the first byte + * has a MSB of 0 and all other bytes have a MSB of 1 can be used + * to check and regain sync. */ + + if ((data & 0x80) == 0) + twidjoy->idx = 0; /* this byte starts a new packet */ + else if (twidjoy->idx == 0) + return IRQ_HANDLED; /* wrong MSB -- ignore this byte */ + + if (twidjoy->idx < TWIDJOY_MAX_LENGTH) + twidjoy->data[twidjoy->idx++] = data; + + if (twidjoy->idx == TWIDJOY_MAX_LENGTH) { + twidjoy_process_packet(twidjoy, regs); + twidjoy->idx = 0; + } + + return IRQ_HANDLED; +} + +/* + * twidjoy_disconnect() is the opposite of twidjoy_connect() + */ + +static void twidjoy_disconnect(struct serio *serio) +{ + struct twidjoy *twidjoy = serio_get_drvdata(serio); + + input_unregister_device(&twidjoy->dev); + serio_close(serio); + serio_set_drvdata(serio, NULL); + kfree(twidjoy); +} + +/* + * twidjoy_connect() is the routine that is called when someone adds a + * new serio device. It looks for the Twiddler, and if found, registers + * it as an input device. + */ + +static int twidjoy_connect(struct serio *serio, struct serio_driver *drv) +{ + struct twidjoy_button_spec *bp; + struct twidjoy *twidjoy; + int i; + int err; + + if (!(twidjoy = kmalloc(sizeof(struct twidjoy), GFP_KERNEL))) + return -ENOMEM; + + memset(twidjoy, 0, sizeof(struct twidjoy)); + + sprintf(twidjoy->phys, "%s/input0", serio->phys); + + init_input_dev(&twidjoy->dev); + twidjoy->dev.name = twidjoy_name; + twidjoy->dev.phys = twidjoy->phys; + twidjoy->dev.id.bustype = BUS_RS232; + twidjoy->dev.id.vendor = SERIO_TWIDJOY; + twidjoy->dev.id.product = 0x0001; + twidjoy->dev.id.version = 0x0100; + twidjoy->dev.dev = &serio->dev; + + twidjoy->dev.evbit[0] = BIT(EV_KEY) | BIT(EV_ABS); + + for (bp = twidjoy_buttons; bp->bitmask; bp++) { + for (i = 0; i < bp->bitmask; i++) + set_bit(bp->buttons[i], twidjoy->dev.keybit); + } + + twidjoy->dev.absbit[0] = BIT(ABS_X) | BIT(ABS_Y); + + for (i = 0; i < 2; i++) { + twidjoy->dev.absmax[ABS_X+i] = 50; + twidjoy->dev.absmin[ABS_X+i] = -50; + + /* TODO: arndt 20010708: Are these values appropriate? */ + twidjoy->dev.absfuzz[ABS_X+i] = 4; + twidjoy->dev.absflat[ABS_X+i] = 4; + } + + twidjoy->dev.private = twidjoy; + + serio_set_drvdata(serio, twidjoy); + + err = serio_open(serio, drv); + if (err) { + serio_set_drvdata(serio, NULL); + kfree(twidjoy); + return err; + } + + input_register_device(&twidjoy->dev); + + printk(KERN_INFO "input: %s on %s\n", twidjoy_name, serio->phys); + + return 0; +} + +/* + * The serio driver structure. + */ + +static struct serio_device_id twidjoy_serio_ids[] = { + { + .type = SERIO_RS232, + .proto = SERIO_TWIDJOY, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { 0 } +}; + +MODULE_DEVICE_TABLE(serio, twidjoy_serio_ids); + +static struct serio_driver twidjoy_drv = { + .driver = { + .name = "twidjoy", + }, + .description = DRIVER_DESC, + .id_table = twidjoy_serio_ids, + .interrupt = twidjoy_interrupt, + .connect = twidjoy_connect, + .disconnect = twidjoy_disconnect, +}; + +/* + * The functions for inserting/removing us as a module. + */ + +int __init twidjoy_init(void) +{ + serio_register_driver(&twidjoy_drv); + return 0; +} + +void __exit twidjoy_exit(void) +{ + serio_unregister_driver(&twidjoy_drv); +} + +module_init(twidjoy_init); +module_exit(twidjoy_exit); diff --git a/drivers/input/joystick/warrior.c b/drivers/input/joystick/warrior.c new file mode 100644 index 000000000000..6976a219504c --- /dev/null +++ b/drivers/input/joystick/warrior.c @@ -0,0 +1,248 @@ +/* + * $Id: warrior.c,v 1.14 2002/01/22 20:32:10 vojtech Exp $ + * + * Copyright (c) 1999-2001 Vojtech Pavlik + */ + +/* + * Logitech WingMan Warrior joystick driver for Linux + */ + +/* + * This program is free warftware; 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 either by + * e-mail - mail your message to <vojtech@ucw.cz>, or by paper mail: + * Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/serio.h> +#include <linux/init.h> + +#define DRIVER_DESC "Logitech WingMan Warrior joystick driver" + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +/* + * Constants. + */ + +#define WARRIOR_MAX_LENGTH 16 +static char warrior_lengths[] = { 0, 4, 12, 3, 4, 4, 0, 0 }; +static char *warrior_name = "Logitech WingMan Warrior"; + +/* + * Per-Warrior data. + */ + +struct warrior { + struct input_dev dev; + int idx, len; + unsigned char data[WARRIOR_MAX_LENGTH]; + char phys[32]; +}; + +/* + * warrior_process_packet() decodes packets the driver receives from the + * Warrior. It updates the data accordingly. + */ + +static void warrior_process_packet(struct warrior *warrior, struct pt_regs *regs) +{ + struct input_dev *dev = &warrior->dev; + unsigned char *data = warrior->data; + + if (!warrior->idx) return; + + input_regs(dev, regs); + + switch ((data[0] >> 4) & 7) { + case 1: /* Button data */ + input_report_key(dev, BTN_TRIGGER, data[3] & 1); + input_report_key(dev, BTN_THUMB, (data[3] >> 1) & 1); + input_report_key(dev, BTN_TOP, (data[3] >> 2) & 1); + input_report_key(dev, BTN_TOP2, (data[3] >> 3) & 1); + break; + case 3: /* XY-axis info->data */ + input_report_abs(dev, ABS_X, ((data[0] & 8) << 5) - (data[2] | ((data[0] & 4) << 5))); + input_report_abs(dev, ABS_Y, (data[1] | ((data[0] & 1) << 7)) - ((data[0] & 2) << 7)); + break; + case 5: /* Throttle, spinner, hat info->data */ + input_report_abs(dev, ABS_THROTTLE, (data[1] | ((data[0] & 1) << 7)) - ((data[0] & 2) << 7)); + input_report_abs(dev, ABS_HAT0X, (data[3] & 2 ? 1 : 0) - (data[3] & 1 ? 1 : 0)); + input_report_abs(dev, ABS_HAT0Y, (data[3] & 8 ? 1 : 0) - (data[3] & 4 ? 1 : 0)); + input_report_rel(dev, REL_DIAL, (data[2] | ((data[0] & 4) << 5)) - ((data[0] & 8) << 5)); + break; + } + input_sync(dev); +} + +/* + * warrior_interrupt() is called by the low level driver when characters + * are ready for us. We then buffer them for further processing, or call the + * packet processing routine. + */ + +static irqreturn_t warrior_interrupt(struct serio *serio, + unsigned char data, unsigned int flags, struct pt_regs *regs) +{ + struct warrior *warrior = serio_get_drvdata(serio); + + if (data & 0x80) { + if (warrior->idx) warrior_process_packet(warrior, regs); + warrior->idx = 0; + warrior->len = warrior_lengths[(data >> 4) & 7]; + } + + if (warrior->idx < warrior->len) + warrior->data[warrior->idx++] = data; + + if (warrior->idx == warrior->len) { + if (warrior->idx) warrior_process_packet(warrior, regs); + warrior->idx = 0; + warrior->len = 0; + } + return IRQ_HANDLED; +} + +/* + * warrior_disconnect() is the opposite of warrior_connect() + */ + +static void warrior_disconnect(struct serio *serio) +{ + struct warrior *warrior = serio_get_drvdata(serio); + + input_unregister_device(&warrior->dev); + serio_close(serio); + serio_set_drvdata(serio, NULL); + kfree(warrior); +} + +/* + * warrior_connect() is the routine that is called when someone adds a + * new serio device. It looks for the Warrior, and if found, registers + * it as an input device. + */ + +static int warrior_connect(struct serio *serio, struct serio_driver *drv) +{ + struct warrior *warrior; + int i; + int err; + + if (!(warrior = kmalloc(sizeof(struct warrior), GFP_KERNEL))) + return -ENOMEM; + + memset(warrior, 0, sizeof(struct warrior)); + + warrior->dev.evbit[0] = BIT(EV_KEY) | BIT(EV_REL) | BIT(EV_ABS); + warrior->dev.keybit[LONG(BTN_TRIGGER)] = BIT(BTN_TRIGGER) | BIT(BTN_THUMB) | BIT(BTN_TOP) | BIT(BTN_TOP2); + warrior->dev.relbit[0] = BIT(REL_DIAL); + warrior->dev.absbit[0] = BIT(ABS_X) | BIT(ABS_Y) | BIT(ABS_THROTTLE) | BIT(ABS_HAT0X) | BIT(ABS_HAT0Y); + + sprintf(warrior->phys, "%s/input0", serio->phys); + + init_input_dev(&warrior->dev); + warrior->dev.name = warrior_name; + warrior->dev.phys = warrior->phys; + warrior->dev.id.bustype = BUS_RS232; + warrior->dev.id.vendor = SERIO_WARRIOR; + warrior->dev.id.product = 0x0001; + warrior->dev.id.version = 0x0100; + warrior->dev.dev = &serio->dev; + + for (i = 0; i < 2; i++) { + warrior->dev.absmax[ABS_X+i] = -64; + warrior->dev.absmin[ABS_X+i] = 64; + warrior->dev.absflat[ABS_X+i] = 8; + } + + warrior->dev.absmax[ABS_THROTTLE] = -112; + warrior->dev.absmin[ABS_THROTTLE] = 112; + + for (i = 0; i < 2; i++) { + warrior->dev.absmax[ABS_HAT0X+i] = -1; + warrior->dev.absmin[ABS_HAT0X+i] = 1; + } + + warrior->dev.private = warrior; + + serio_set_drvdata(serio, warrior); + + err = serio_open(serio, drv); + if (err) { + serio_set_drvdata(serio, NULL); + kfree(warrior); + return err; + } + + input_register_device(&warrior->dev); + + printk(KERN_INFO "input: Logitech WingMan Warrior on %s\n", serio->phys); + + return 0; +} + +/* + * The serio driver structure. + */ + +static struct serio_device_id warrior_serio_ids[] = { + { + .type = SERIO_RS232, + .proto = SERIO_WARRIOR, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { 0 } +}; + +MODULE_DEVICE_TABLE(serio, warrior_serio_ids); + +static struct serio_driver warrior_drv = { + .driver = { + .name = "warrior", + }, + .description = DRIVER_DESC, + .id_table = warrior_serio_ids, + .interrupt = warrior_interrupt, + .connect = warrior_connect, + .disconnect = warrior_disconnect, +}; + +/* + * The functions for inserting/removing us as a module. + */ + +static int __init warrior_init(void) +{ + serio_register_driver(&warrior_drv); + return 0; +} + +static void __exit warrior_exit(void) +{ + serio_unregister_driver(&warrior_drv); +} + +module_init(warrior_init); +module_exit(warrior_exit); diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig new file mode 100644 index 000000000000..e55dee390775 --- /dev/null +++ b/drivers/input/keyboard/Kconfig @@ -0,0 +1,185 @@ +# +# Input core configuration +# +menuconfig INPUT_KEYBOARD + bool "Keyboards" if EMBEDDED || !X86 + default y + help + Say Y here, and a list of supported keyboards will be displayed. + This option doesn't affect the kernel. + + If unsure, say Y. + +if INPUT_KEYBOARD + +config KEYBOARD_ATKBD + tristate "AT keyboard" if !PC + default y + select SERIO + select SERIO_LIBPS2 + select SERIO_I8042 if PC + select SERIO_GSCPS2 if GSC + help + Say Y here if you want to use a standard AT or PS/2 keyboard. Usually + you'll need this, unless you have a different type keyboard (USB, ADB + or other). This also works for AT and PS/2 keyboards connected over a + PS/2 to serial converter. + + If unsure, say Y. + + To compile this driver as a module, choose M here: the + module will be called atkbd. + +config KEYBOARD_ATKBD_HP_KEYCODES + bool "Use HP keyboard scancodes" + depends on PARISC && KEYBOARD_ATKBD + default y + help + Say Y here if you have a PA-RISC machine and want to use an AT or + PS/2 keyboard, and your keyboard uses keycodes that are specific to + PA-RISC keyboards. + + Say N if you use a standard keyboard. + +config KEYBOARD_ATKBD_RDI_KEYCODES + bool "Use PrecisionBook keyboard scancodes" + depends on KEYBOARD_ATKBD_HP_KEYCODES + default n + help + If you have an RDI PrecisionBook, say Y here if you want to use its + built-in keyboard (as opposed to an external keyboard). + + The PrecisionBook has five keys that conflict with those used by most + AT and PS/2 keyboards. These are as follows: + + PrecisionBook Standard AT or PS/2 + + F1 F12 + Left Ctrl Left Alt + Caps Lock Left Ctrl + Right Ctrl Caps Lock + Left 102nd key (the key to the right of Left Shift) + + If you say N here, and use the PrecisionBook keyboard, then each key + in the left-hand column will be interpreted as the corresponding key + in the right-hand column. + + If you say Y here, and use an external keyboard, then each key in the + right-hand column will be interpreted as the key shown in the + left-hand column. + +config KEYBOARD_SUNKBD + tristate "Sun Type 4 and Type 5 keyboard" + select SERIO + help + Say Y here if you want to use a Sun Type 4 or Type 5 keyboard, + connected either to the Sun keyboard connector or to an serial + (RS-232) port via a simple adapter. + + To compile this driver as a module, choose M here: the + module will be called sunkbd. + +config KEYBOARD_LKKBD + tristate "DECstation/VAXstation LK201/LK401 keyboard" + select SERIO + help + Say Y here if you want to use a LK201 or LK401 style serial + keyboard. This keyboard is also useable on PCs if you attach + it with the inputattach program. The connector pinout is + described within lkkbd.c. + + To compile this driver as a module, choose M here: the + module will be called lkkbd. + +config KEYBOARD_LOCOMO + tristate "LoCoMo Keyboard Support" + depends on SHARP_LOCOMO + help + Say Y here if you are running Linux on a Sharp Zaurus Collie or Poodle based PDA + + To compile this driver as a module, choose M here: the + module will be called locomokbd. + +config KEYBOARD_XTKBD + tristate "XT keyboard" + select SERIO + help + Say Y here if you want to use the old IBM PC/XT keyboard (or + compatible) on your system. This is only possible with a + parallel port keyboard adapter, you cannot connect it to the + keyboard port on a PC that runs Linux. + + To compile this driver as a module, choose M here: the + module will be called xtkbd. + +config KEYBOARD_NEWTON + tristate "Newton keyboard" + select SERIO + help + Say Y here if you have a Newton keyboard on a serial port. + + To compile this driver as a module, choose M here: the + module will be called newtonkbd. + +config KEYBOARD_CORGI + tristate "Corgi keyboard" + depends on PXA_SHARPSL + default y + help + Say Y here to enable the keyboard on the Sharp Zaurus SL-C7xx + series of PDAs. + + To compile this driver as a module, choose M here: the + module will be called corgikbd. + +config KEYBOARD_MAPLE + tristate "Maple bus keyboard" + depends on SH_DREAMCAST && MAPLE + help + Say Y here if you have a DreamCast console running Linux and have + a keyboard attached to its Maple bus. + + To compile this driver as a module, choose M here: the + module will be called maple_keyb. + +config KEYBOARD_AMIGA + tristate "Amiga keyboard" + depends on AMIGA + help + Say Y here if you are running Linux on any AMIGA and have a keyboard + attached. + + To compile this driver as a module, choose M here: the + module will be called amikbd. + +config KEYBOARD_HIL_OLD + tristate "HP HIL keyboard support (simple driver)" + depends on GSC + default y + help + The "Human Interface Loop" is a older, 8-channel USB-like + controller used in several Hewlett Packard models. This driver + was adapted from the one written for m68k/hp300, and implements + support for a keyboard attached to the HIL port, but not for + any other types of HIL input devices like mice or tablets. + However, it has been thoroughly tested and is stable. + + If you want full HIL support including support for multiple + keyboards, mices and tablets, you have to enable the + "HP System Device Controller i8042 Support" in the input/serio + submenu. + +config KEYBOARD_HIL + tristate "HP HIL keyboard support" + depends on GSC + default y + select HP_SDC + select HIL_MLC + select SERIO + help + The "Human Interface Loop" is a older, 8-channel USB-like + controller used in several Hewlett Packard models. + This driver implements support for HIL-keyboards attached + to your machine, so normally you should say Y here. + +endif diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile new file mode 100644 index 000000000000..b02eeceea3c3 --- /dev/null +++ b/drivers/input/keyboard/Makefile @@ -0,0 +1,19 @@ +# +# Makefile for the input core drivers. +# + +# Each configuration option enables a list of files. + +obj-$(CONFIG_KEYBOARD_ATKBD) += atkbd.o +obj-$(CONFIG_KEYBOARD_MAPLE) += maple_keyb.o +obj-$(CONFIG_KEYBOARD_SUNKBD) += sunkbd.o +obj-$(CONFIG_KEYBOARD_LKKBD) += lkkbd.o +obj-$(CONFIG_KEYBOARD_XTKBD) += xtkbd.o +obj-$(CONFIG_KEYBOARD_AMIGA) += amikbd.o +obj-$(CONFIG_KEYBOARD_LOCOMO) += locomokbd.o +obj-$(CONFIG_KEYBOARD_NEWTON) += newtonkbd.o +obj-$(CONFIG_KEYBOARD_98KBD) += 98kbd.o +obj-$(CONFIG_KEYBOARD_CORGI) += corgikbd.o +obj-$(CONFIG_KEYBOARD_HIL) += hil_kbd.o +obj-$(CONFIG_KEYBOARD_HIL_OLD) += hilkbd.o + diff --git a/drivers/input/keyboard/amikbd.c b/drivers/input/keyboard/amikbd.c new file mode 100644 index 000000000000..4e8e8ea214ab --- /dev/null +++ b/drivers/input/keyboard/amikbd.c @@ -0,0 +1,241 @@ +/* + * $Id: amikbd.c,v 1.13 2002/02/01 16:02:24 vojtech Exp $ + * + * Copyright (c) 2000-2001 Vojtech Pavlik + * + * Based on the work of: + * Hamish Macdonald + */ + +/* + * Amiga keyboard driver for Linux/m68k + */ + +/* + * 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 either by + * e-mail - mail your message to <vojtech@ucw.cz>, or by paper mail: + * Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/delay.h> +#include <linux/interrupt.h> + +#include <asm/amigaints.h> +#include <asm/amigahw.h> +#include <asm/irq.h> + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION("Amiga keyboard driver"); +MODULE_LICENSE("GPL"); + +static unsigned char amikbd_keycode[0x78] = { + [0] = KEY_GRAVE, + [1] = KEY_1, + [2] = KEY_2, + [3] = KEY_3, + [4] = KEY_4, + [5] = KEY_5, + [6] = KEY_6, + [7] = KEY_7, + [8] = KEY_8, + [9] = KEY_9, + [10] = KEY_0, + [11] = KEY_MINUS, + [12] = KEY_EQUAL, + [13] = KEY_BACKSLASH, + [15] = KEY_KP0, + [16] = KEY_Q, + [17] = KEY_W, + [18] = KEY_E, + [19] = KEY_R, + [20] = KEY_T, + [21] = KEY_Y, + [22] = KEY_U, + [23] = KEY_I, + [24] = KEY_O, + [25] = KEY_P, + [26] = KEY_LEFTBRACE, + [27] = KEY_RIGHTBRACE, + [29] = KEY_KP1, + [30] = KEY_KP2, + [31] = KEY_KP3, + [32] = KEY_A, + [33] = KEY_S, + [34] = KEY_D, + [35] = KEY_F, + [36] = KEY_G, + [37] = KEY_H, + [38] = KEY_J, + [39] = KEY_K, + [40] = KEY_L, + [41] = KEY_SEMICOLON, + [42] = KEY_APOSTROPHE, + [43] = KEY_BACKSLASH, + [45] = KEY_KP4, + [46] = KEY_KP5, + [47] = KEY_KP6, + [48] = KEY_102ND, + [49] = KEY_Z, + [50] = KEY_X, + [51] = KEY_C, + [52] = KEY_V, + [53] = KEY_B, + [54] = KEY_N, + [55] = KEY_M, + [56] = KEY_COMMA, + [57] = KEY_DOT, + [58] = KEY_SLASH, + [60] = KEY_KPDOT, + [61] = KEY_KP7, + [62] = KEY_KP8, + [63] = KEY_KP9, + [64] = KEY_SPACE, + [65] = KEY_BACKSPACE, + [66] = KEY_TAB, + [67] = KEY_KPENTER, + [68] = KEY_ENTER, + [69] = KEY_ESC, + [70] = KEY_DELETE, + [74] = KEY_KPMINUS, + [76] = KEY_UP, + [77] = KEY_DOWN, + [78] = KEY_RIGHT, + [79] = KEY_LEFT, + [80] = KEY_F1, + [81] = KEY_F2, + [82] = KEY_F3, + [83] = KEY_F4, + [84] = KEY_F5, + [85] = KEY_F6, + [86] = KEY_F7, + [87] = KEY_F8, + [88] = KEY_F9, + [89] = KEY_F10, + [90] = KEY_KPLEFTPAREN, + [91] = KEY_KPRIGHTPAREN, + [92] = KEY_KPSLASH, + [93] = KEY_KPASTERISK, + [94] = KEY_KPPLUS, + [95] = KEY_HELP, + [96] = KEY_LEFTSHIFT, + [97] = KEY_RIGHTSHIFT, + [98] = KEY_CAPSLOCK, + [99] = KEY_LEFTCTRL, + [100] = KEY_LEFTALT, + [101] = KEY_RIGHTALT, + [102] = KEY_LEFTMETA, + [103] = KEY_RIGHTMETA +}; + +static const char *amikbd_messages[8] = { + [0] = KERN_ALERT "amikbd: Ctrl-Amiga-Amiga reset warning!!\n", + [1] = KERN_WARNING "amikbd: keyboard lost sync\n", + [2] = KERN_WARNING "amikbd: keyboard buffer overflow\n", + [3] = KERN_WARNING "amikbd: keyboard controller failure\n", + [4] = KERN_ERR "amikbd: keyboard selftest failure\n", + [5] = KERN_INFO "amikbd: initiate power-up key stream\n", + [6] = KERN_INFO "amikbd: terminate power-up key stream\n", + [7] = KERN_WARNING "amikbd: keyboard interrupt\n" +}; + +static struct input_dev amikbd_dev; + +static char *amikbd_name = "Amiga keyboard"; +static char *amikbd_phys = "amikbd/input0"; + +static irqreturn_t amikbd_interrupt(int irq, void *dummy, struct pt_regs *fp) +{ + unsigned char scancode, down; + + scancode = ~ciaa.sdr; /* get and invert scancode (keyboard is active low) */ + ciaa.cra |= 0x40; /* switch SP pin to output for handshake */ + udelay(85); /* wait until 85 us have expired */ + ciaa.cra &= ~0x40; /* switch CIA serial port to input mode */ + + down = !(scancode & 1); /* lowest bit is release bit */ + scancode >>= 1; + + if (scancode < 0x78) { /* scancodes < 0x78 are keys */ + + scancode = amikbd_keycode[scancode]; + + input_regs(&amikbd_dev, fp); + + if (scancode == KEY_CAPSLOCK) { /* CapsLock is a toggle switch key on Amiga */ + input_report_key(&amikbd_dev, scancode, 1); + input_report_key(&amikbd_dev, scancode, 0); + input_sync(&amikbd_dev); + } else { + input_report_key(&amikbd_dev, scancode, down); + input_sync(&amikbd_dev); + } + } else /* scancodes >= 0x78 are error codes */ + printk(amikbd_messages[scancode - 0x78]); + + return IRQ_HANDLED; +} + +static int __init amikbd_init(void) +{ + int i; + + if (!AMIGAHW_PRESENT(AMI_KEYBOARD)) + return -EIO; + + if (!request_mem_region(CIAA_PHYSADDR-1+0xb00, 0x100, "amikeyb")) + return -EBUSY; + + init_input_dev(&amikbd_dev); + + amikbd_dev.evbit[0] = BIT(EV_KEY) | BIT(EV_REP); + amikbd_dev.keycode = amikbd_keycode; + amikbd_dev.keycodesize = sizeof(unsigned char); + amikbd_dev.keycodemax = ARRAY_SIZE(amikbd_keycode); + + for (i = 0; i < 0x78; i++) + if (amikbd_keycode[i]) + set_bit(amikbd_keycode[i], amikbd_dev.keybit); + + ciaa.cra &= ~0x41; /* serial data in, turn off TA */ + request_irq(IRQ_AMIGA_CIAA_SP, amikbd_interrupt, 0, "amikbd", amikbd_interrupt); + + amikbd_dev.name = amikbd_name; + amikbd_dev.phys = amikbd_phys; + amikbd_dev.id.bustype = BUS_AMIGA; + amikbd_dev.id.vendor = 0x0001; + amikbd_dev.id.product = 0x0001; + amikbd_dev.id.version = 0x0100; + + input_register_device(&amikbd_dev); + + printk(KERN_INFO "input: %s\n", amikbd_name); + + return 0; +} + +static void __exit amikbd_exit(void) +{ + input_unregister_device(&amikbd_dev); + free_irq(IRQ_AMIGA_CIAA_SP, amikbd_interrupt); + release_mem_region(CIAA_PHYSADDR-1+0xb00, 0x100); +} + +module_init(amikbd_init); +module_exit(amikbd_exit); diff --git a/drivers/input/keyboard/atkbd.c b/drivers/input/keyboard/atkbd.c new file mode 100644 index 000000000000..f7304f0ce542 --- /dev/null +++ b/drivers/input/keyboard/atkbd.c @@ -0,0 +1,1148 @@ +/* + * AT and PS/2 keyboard driver + * + * Copyright (c) 1999-2002 Vojtech Pavlik + */ + +/* + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +/* + * This driver can handle standard AT keyboards and PS/2 keyboards in + * Translated and Raw Set 2 and Set 3, as well as AT keyboards on dumb + * input-only controllers and AT keyboards connected over a one way RS232 + * converter. + */ + +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/serio.h> +#include <linux/workqueue.h> +#include <linux/libps2.h> + +#define DRIVER_DESC "AT and PS/2 keyboard driver" + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@suse.cz>"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +static int atkbd_set = 2; +module_param_named(set, atkbd_set, int, 0); +MODULE_PARM_DESC(set, "Select keyboard code set (2 = default, 3 = PS/2 native)"); + +#if defined(__i386__) || defined(__x86_64__) || defined(__hppa__) +static int atkbd_reset; +#else +static int atkbd_reset = 1; +#endif +module_param_named(reset, atkbd_reset, bool, 0); +MODULE_PARM_DESC(reset, "Reset keyboard during initialization"); + +static int atkbd_softrepeat; +module_param_named(softrepeat, atkbd_softrepeat, bool, 0); +MODULE_PARM_DESC(softrepeat, "Use software keyboard repeat"); + +static int atkbd_softraw = 1; +module_param_named(softraw, atkbd_softraw, bool, 0); +MODULE_PARM_DESC(softraw, "Use software generated rawmode"); + +static int atkbd_scroll = 1; +module_param_named(scroll, atkbd_scroll, bool, 0); +MODULE_PARM_DESC(scroll, "Enable scroll-wheel on MS Office and similar keyboards"); + +static int atkbd_extra; +module_param_named(extra, atkbd_extra, bool, 0); +MODULE_PARM_DESC(extra, "Enable extra LEDs and keys on IBM RapidAcces, EzKey and similar keyboards"); + +__obsolete_setup("atkbd_set="); +__obsolete_setup("atkbd_reset"); +__obsolete_setup("atkbd_softrepeat="); + +/* + * Scancode to keycode tables. These are just the default setting, and + * are loadable via an userland utility. + */ + +static unsigned char atkbd_set2_keycode[512] = { + +#ifdef CONFIG_KEYBOARD_ATKBD_HP_KEYCODES + +/* XXX: need a more general approach */ + +#include "hpps2atkbd.h" /* include the keyboard scancodes */ + +#else + 0, 67, 65, 63, 61, 59, 60, 88, 0, 68, 66, 64, 62, 15, 41,117, + 0, 56, 42, 93, 29, 16, 2, 0, 0, 0, 44, 31, 30, 17, 3, 0, + 0, 46, 45, 32, 18, 5, 4, 95, 0, 57, 47, 33, 20, 19, 6,183, + 0, 49, 48, 35, 34, 21, 7,184, 0, 0, 50, 36, 22, 8, 9,185, + 0, 51, 37, 23, 24, 11, 10, 0, 0, 52, 53, 38, 39, 25, 12, 0, + 0, 89, 40, 0, 26, 13, 0, 0, 58, 54, 28, 27, 0, 43, 0, 85, + 0, 86, 91, 90, 92, 0, 14, 94, 0, 79,124, 75, 71,121, 0, 0, + 82, 83, 80, 76, 77, 72, 1, 69, 87, 78, 81, 74, 55, 73, 70, 99, + + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 217,100,255, 0, 97,165, 0, 0,156, 0, 0, 0, 0, 0, 0,125, + 173,114, 0,113, 0, 0, 0,126,128, 0, 0,140, 0, 0, 0,127, + 159, 0,115, 0,164, 0, 0,116,158, 0,150,166, 0, 0, 0,142, + 157, 0, 0, 0, 0, 0, 0, 0,155, 0, 98, 0, 0,163, 0, 0, + 226, 0, 0, 0, 0, 0, 0, 0, 0,255, 96, 0, 0, 0,143, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0,107, 0,105,102, 0, 0,112, + 110,111,108,112,106,103, 0,119, 0,118,109, 0, 99,104,119, 0, + + 0, 0, 0, 65, 99, +#endif +}; + +static unsigned char atkbd_set3_keycode[512] = { + + 0, 0, 0, 0, 0, 0, 0, 59, 1,138,128,129,130, 15, 41, 60, + 131, 29, 42, 86, 58, 16, 2, 61,133, 56, 44, 31, 30, 17, 3, 62, + 134, 46, 45, 32, 18, 5, 4, 63,135, 57, 47, 33, 20, 19, 6, 64, + 136, 49, 48, 35, 34, 21, 7, 65,137,100, 50, 36, 22, 8, 9, 66, + 125, 51, 37, 23, 24, 11, 10, 67,126, 52, 53, 38, 39, 25, 12, 68, + 113,114, 40, 43, 26, 13, 87, 99, 97, 54, 28, 27, 43, 43, 88, 70, + 108,105,119,103,111,107, 14,110, 0, 79,106, 75, 71,109,102,104, + 82, 83, 80, 76, 77, 72, 69, 98, 0, 96, 81, 0, 78, 73, 55,183, + + 184,185,186,187, 74, 94, 92, 93, 0, 0, 0,125,126,127,112, 0, + 0,139,150,163,165,115,152,150,166,140,160,154,113,114,167,168, + 148,149,147,140 +}; + +static unsigned char atkbd_unxlate_table[128] = { + 0,118, 22, 30, 38, 37, 46, 54, 61, 62, 70, 69, 78, 85,102, 13, + 21, 29, 36, 45, 44, 53, 60, 67, 68, 77, 84, 91, 90, 20, 28, 27, + 35, 43, 52, 51, 59, 66, 75, 76, 82, 14, 18, 93, 26, 34, 33, 42, + 50, 49, 58, 65, 73, 74, 89,124, 17, 41, 88, 5, 6, 4, 12, 3, + 11, 2, 10, 1, 9,119,126,108,117,125,123,107,115,116,121,105, + 114,122,112,113,127, 96, 97,120, 7, 15, 23, 31, 39, 47, 55, 63, + 71, 79, 86, 94, 8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 87,111, + 19, 25, 57, 81, 83, 92, 95, 98, 99,100,101,103,104,106,109,110 +}; + +#define ATKBD_CMD_SETLEDS 0x10ed +#define ATKBD_CMD_GSCANSET 0x11f0 +#define ATKBD_CMD_SSCANSET 0x10f0 +#define ATKBD_CMD_GETID 0x02f2 +#define ATKBD_CMD_SETREP 0x10f3 +#define ATKBD_CMD_ENABLE 0x00f4 +#define ATKBD_CMD_RESET_DIS 0x00f5 +#define ATKBD_CMD_SETALL_MBR 0x00fa +#define ATKBD_CMD_RESET_BAT 0x02ff +#define ATKBD_CMD_RESEND 0x00fe +#define ATKBD_CMD_EX_ENABLE 0x10ea +#define ATKBD_CMD_EX_SETLEDS 0x20eb +#define ATKBD_CMD_OK_GETID 0x02e8 + +#define ATKBD_RET_ACK 0xfa +#define ATKBD_RET_NAK 0xfe +#define ATKBD_RET_BAT 0xaa +#define ATKBD_RET_EMUL0 0xe0 +#define ATKBD_RET_EMUL1 0xe1 +#define ATKBD_RET_RELEASE 0xf0 +#define ATKBD_RET_HANGUEL 0xf1 +#define ATKBD_RET_HANJA 0xf2 +#define ATKBD_RET_ERR 0xff + +#define ATKBD_KEY_UNKNOWN 0 +#define ATKBD_KEY_NULL 255 + +#define ATKBD_SCR_1 254 +#define ATKBD_SCR_2 253 +#define ATKBD_SCR_4 252 +#define ATKBD_SCR_8 251 +#define ATKBD_SCR_CLICK 250 +#define ATKBD_SCR_LEFT 249 +#define ATKBD_SCR_RIGHT 248 + +#define ATKBD_SPECIAL 248 + +static struct { + unsigned char keycode; + unsigned char set2; +} atkbd_scroll_keys[] = { + { ATKBD_SCR_1, 0xc5 }, + { ATKBD_SCR_2, 0xa9 }, + { ATKBD_SCR_4, 0xb6 }, + { ATKBD_SCR_8, 0xa7 }, + { ATKBD_SCR_CLICK, 0xe0 }, + { ATKBD_SCR_LEFT, 0xcb }, + { ATKBD_SCR_RIGHT, 0xd2 }, +}; + +/* + * The atkbd control structure + */ + +struct atkbd { + + struct ps2dev ps2dev; + + /* Written only during init */ + char name[64]; + char phys[32]; + struct input_dev dev; + + unsigned short id; + unsigned char keycode[512]; + unsigned char set; + unsigned char translated; + unsigned char extra; + unsigned char write; + unsigned char softrepeat; + unsigned char softraw; + unsigned char scroll; + unsigned char enabled; + + /* Accessed only from interrupt */ + unsigned char emul; + unsigned char resend; + unsigned char release; + unsigned char bat_xl; + unsigned int last; + unsigned long time; +}; + +static ssize_t atkbd_attr_show_helper(struct device *dev, char *buf, + ssize_t (*handler)(struct atkbd *, char *)); +static ssize_t atkbd_attr_set_helper(struct device *dev, const char *buf, size_t count, + ssize_t (*handler)(struct atkbd *, const char *, size_t)); +#define ATKBD_DEFINE_ATTR(_name) \ +static ssize_t atkbd_show_##_name(struct atkbd *, char *); \ +static ssize_t atkbd_set_##_name(struct atkbd *, const char *, size_t); \ +static ssize_t atkbd_do_show_##_name(struct device *d, char *b) \ +{ \ + return atkbd_attr_show_helper(d, b, atkbd_show_##_name); \ +} \ +static ssize_t atkbd_do_set_##_name(struct device *d, const char *b, size_t s) \ +{ \ + return atkbd_attr_set_helper(d, b, s, atkbd_set_##_name); \ +} \ +static struct device_attribute atkbd_attr_##_name = \ + __ATTR(_name, S_IWUSR | S_IRUGO, atkbd_do_show_##_name, atkbd_do_set_##_name); + +ATKBD_DEFINE_ATTR(extra); +ATKBD_DEFINE_ATTR(scroll); +ATKBD_DEFINE_ATTR(set); +ATKBD_DEFINE_ATTR(softrepeat); +ATKBD_DEFINE_ATTR(softraw); + + +static void atkbd_report_key(struct input_dev *dev, struct pt_regs *regs, int code, int value) +{ + input_regs(dev, regs); + if (value == 3) { + input_report_key(dev, code, 1); + input_sync(dev); + input_report_key(dev, code, 0); + } else + input_event(dev, EV_KEY, code, value); + input_sync(dev); +} + +/* + * atkbd_interrupt(). Here takes place processing of data received from + * the keyboard into events. + */ + +static irqreturn_t atkbd_interrupt(struct serio *serio, unsigned char data, + unsigned int flags, struct pt_regs *regs) +{ + struct atkbd *atkbd = serio_get_drvdata(serio); + unsigned int code = data; + int scroll = 0, hscroll = 0, click = -1; + int value; + +#ifdef ATKBD_DEBUG + printk(KERN_DEBUG "atkbd.c: Received %02x flags %02x\n", data, flags); +#endif + +#if !defined(__i386__) && !defined (__x86_64__) + if ((flags & (SERIO_FRAME | SERIO_PARITY)) && (~flags & SERIO_TIMEOUT) && !atkbd->resend && atkbd->write) { + printk(KERN_WARNING "atkbd.c: frame/parity error: %02x\n", flags); + serio_write(serio, ATKBD_CMD_RESEND); + atkbd->resend = 1; + goto out; + } + + if (!flags && data == ATKBD_RET_ACK) + atkbd->resend = 0; +#endif + + if (unlikely(atkbd->ps2dev.flags & PS2_FLAG_ACK)) + if (ps2_handle_ack(&atkbd->ps2dev, data)) + goto out; + + if (unlikely(atkbd->ps2dev.flags & PS2_FLAG_CMD)) + if (ps2_handle_response(&atkbd->ps2dev, data)) + goto out; + + if (!atkbd->enabled) + goto out; + + input_event(&atkbd->dev, EV_MSC, MSC_RAW, code); + + if (atkbd->translated) { + + if (atkbd->emul || + !(code == ATKBD_RET_EMUL0 || code == ATKBD_RET_EMUL1 || + code == ATKBD_RET_HANGUEL || code == ATKBD_RET_HANJA || + code == ATKBD_RET_ERR || + (code == ATKBD_RET_BAT && !atkbd->bat_xl))) { + atkbd->release = code >> 7; + code &= 0x7f; + } + + if (!atkbd->emul && + (code & 0x7f) == (ATKBD_RET_BAT & 0x7f)) + atkbd->bat_xl = !atkbd->release; + } + + switch (code) { + case ATKBD_RET_BAT: + atkbd->enabled = 0; + serio_rescan(atkbd->ps2dev.serio); + goto out; + case ATKBD_RET_EMUL0: + atkbd->emul = 1; + goto out; + case ATKBD_RET_EMUL1: + atkbd->emul = 2; + goto out; + case ATKBD_RET_RELEASE: + atkbd->release = 1; + goto out; + case ATKBD_RET_HANGUEL: + atkbd_report_key(&atkbd->dev, regs, KEY_HANGUEL, 3); + goto out; + case ATKBD_RET_HANJA: + atkbd_report_key(&atkbd->dev, regs, KEY_HANJA, 3); + goto out; + case ATKBD_RET_ERR: + printk(KERN_DEBUG "atkbd.c: Keyboard on %s reports too many keys pressed.\n", serio->phys); + goto out; + } + + if (atkbd->set != 3) + code = (code & 0x7f) | ((code & 0x80) << 1); + if (atkbd->emul) { + if (--atkbd->emul) + goto out; + code |= (atkbd->set != 3) ? 0x80 : 0x100; + } + + if (atkbd->keycode[code] != ATKBD_KEY_NULL) + input_event(&atkbd->dev, EV_MSC, MSC_SCAN, code); + + switch (atkbd->keycode[code]) { + case ATKBD_KEY_NULL: + break; + case ATKBD_KEY_UNKNOWN: + if (data == ATKBD_RET_ACK || data == ATKBD_RET_NAK) { + printk(KERN_WARNING "atkbd.c: Spurious %s on %s. Some program, " + "like XFree86, might be trying access hardware directly.\n", + data == ATKBD_RET_ACK ? "ACK" : "NAK", serio->phys); + } else { + printk(KERN_WARNING "atkbd.c: Unknown key %s " + "(%s set %d, code %#x on %s).\n", + atkbd->release ? "released" : "pressed", + atkbd->translated ? "translated" : "raw", + atkbd->set, code, serio->phys); + printk(KERN_WARNING "atkbd.c: Use 'setkeycodes %s%02x <keycode>' " + "to make it known.\n", + code & 0x80 ? "e0" : "", code & 0x7f); + } + input_sync(&atkbd->dev); + break; + case ATKBD_SCR_1: + scroll = 1 - atkbd->release * 2; + break; + case ATKBD_SCR_2: + scroll = 2 - atkbd->release * 4; + break; + case ATKBD_SCR_4: + scroll = 4 - atkbd->release * 8; + break; + case ATKBD_SCR_8: + scroll = 8 - atkbd->release * 16; + break; + case ATKBD_SCR_CLICK: + click = !atkbd->release; + break; + case ATKBD_SCR_LEFT: + hscroll = -1; + break; + case ATKBD_SCR_RIGHT: + hscroll = 1; + break; + default: + value = atkbd->release ? 0 : + (1 + (!atkbd->softrepeat && test_bit(atkbd->keycode[code], atkbd->dev.key))); + + switch (value) { /* Workaround Toshiba laptop multiple keypress */ + case 0: + atkbd->last = 0; + break; + case 1: + atkbd->last = code; + atkbd->time = jiffies + msecs_to_jiffies(atkbd->dev.rep[REP_DELAY]) / 2; + break; + case 2: + if (!time_after(jiffies, atkbd->time) && atkbd->last == code) + value = 1; + break; + } + + atkbd_report_key(&atkbd->dev, regs, atkbd->keycode[code], value); + } + + if (atkbd->scroll) { + input_regs(&atkbd->dev, regs); + if (click != -1) + input_report_key(&atkbd->dev, BTN_MIDDLE, click); + input_report_rel(&atkbd->dev, REL_WHEEL, scroll); + input_report_rel(&atkbd->dev, REL_HWHEEL, hscroll); + input_sync(&atkbd->dev); + } + + atkbd->release = 0; +out: + return IRQ_HANDLED; +} + +/* + * Event callback from the input module. Events that change the state of + * the hardware are processed here. + */ + +static int atkbd_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) +{ + struct atkbd *atkbd = dev->private; + const short period[32] = + { 33, 37, 42, 46, 50, 54, 58, 63, 67, 75, 83, 92, 100, 109, 116, 125, + 133, 149, 167, 182, 200, 217, 232, 250, 270, 303, 333, 370, 400, 435, 470, 500 }; + const short delay[4] = + { 250, 500, 750, 1000 }; + unsigned char param[2]; + int i, j; + + if (!atkbd->write) + return -1; + + switch (type) { + + case EV_LED: + + param[0] = (test_bit(LED_SCROLLL, dev->led) ? 1 : 0) + | (test_bit(LED_NUML, dev->led) ? 2 : 0) + | (test_bit(LED_CAPSL, dev->led) ? 4 : 0); + ps2_schedule_command(&atkbd->ps2dev, param, ATKBD_CMD_SETLEDS); + + if (atkbd->extra) { + param[0] = 0; + param[1] = (test_bit(LED_COMPOSE, dev->led) ? 0x01 : 0) + | (test_bit(LED_SLEEP, dev->led) ? 0x02 : 0) + | (test_bit(LED_SUSPEND, dev->led) ? 0x04 : 0) + | (test_bit(LED_MISC, dev->led) ? 0x10 : 0) + | (test_bit(LED_MUTE, dev->led) ? 0x20 : 0); + ps2_schedule_command(&atkbd->ps2dev, param, ATKBD_CMD_EX_SETLEDS); + } + + return 0; + + + case EV_REP: + + if (atkbd->softrepeat) return 0; + + i = j = 0; + while (i < 32 && period[i] < dev->rep[REP_PERIOD]) i++; + while (j < 4 && delay[j] < dev->rep[REP_DELAY]) j++; + dev->rep[REP_PERIOD] = period[i]; + dev->rep[REP_DELAY] = delay[j]; + param[0] = i | (j << 5); + ps2_schedule_command(&atkbd->ps2dev, param, ATKBD_CMD_SETREP); + + return 0; + } + + return -1; +} + +/* + * atkbd_enable() signals that interrupt handler is allowed to + * generate input events. + */ + +static inline void atkbd_enable(struct atkbd *atkbd) +{ + serio_pause_rx(atkbd->ps2dev.serio); + atkbd->enabled = 1; + serio_continue_rx(atkbd->ps2dev.serio); +} + +/* + * atkbd_disable() tells input handler that all incoming data except + * for ACKs and command response should be dropped. + */ + +static inline void atkbd_disable(struct atkbd *atkbd) +{ + serio_pause_rx(atkbd->ps2dev.serio); + atkbd->enabled = 0; + serio_continue_rx(atkbd->ps2dev.serio); +} + +/* + * atkbd_probe() probes for an AT keyboard on a serio port. + */ + +static int atkbd_probe(struct atkbd *atkbd) +{ + struct ps2dev *ps2dev = &atkbd->ps2dev; + unsigned char param[2]; + +/* + * Some systems, where the bit-twiddling when testing the io-lines of the + * controller may confuse the keyboard need a full reset of the keyboard. On + * these systems the BIOS also usually doesn't do it for us. + */ + + if (atkbd_reset) + if (ps2_command(ps2dev, NULL, ATKBD_CMD_RESET_BAT)) + printk(KERN_WARNING "atkbd.c: keyboard reset failed on %s\n", ps2dev->serio->phys); + +/* + * Then we check the keyboard ID. We should get 0xab83 under normal conditions. + * Some keyboards report different values, but the first byte is always 0xab or + * 0xac. Some old AT keyboards don't report anything. If a mouse is connected, this + * should make sure we don't try to set the LEDs on it. + */ + + param[0] = param[1] = 0xa5; /* initialize with invalid values */ + if (ps2_command(ps2dev, param, ATKBD_CMD_GETID)) { + +/* + * If the get ID command failed, we check if we can at least set the LEDs on + * the keyboard. This should work on every keyboard out there. It also turns + * the LEDs off, which we want anyway. + */ + param[0] = 0; + if (ps2_command(ps2dev, param, ATKBD_CMD_SETLEDS)) + return -1; + atkbd->id = 0xabba; + return 0; + } + + if (param[0] != 0xab && param[0] != 0xac && /* Regular and NCD Sun keyboards */ + param[0] != 0x2b && param[0] != 0x5d && /* Trust keyboard, raw and translated */ + param[0] != 0x60 && param[0] != 0x47) /* NMB SGI keyboard, raw and translated */ + return -1; + + atkbd->id = (param[0] << 8) | param[1]; + + if (atkbd->id == 0xaca1 && atkbd->translated) { + printk(KERN_ERR "atkbd.c: NCD terminal keyboards are only supported on non-translating\n"); + printk(KERN_ERR "atkbd.c: controllers. Use i8042.direct=1 to disable translation.\n"); + return -1; + } + + return 0; +} + +/* + * atkbd_select_set checks if a keyboard has a working Set 3 support, and + * sets it into that. Unfortunately there are keyboards that can be switched + * to Set 3, but don't work well in that (BTC Multimedia ...) + */ + +static int atkbd_select_set(struct atkbd *atkbd, int target_set, int allow_extra) +{ + struct ps2dev *ps2dev = &atkbd->ps2dev; + unsigned char param[2]; + + atkbd->extra = 0; +/* + * For known special keyboards we can go ahead and set the correct set. + * We check for NCD PS/2 Sun, NorthGate OmniKey 101 and + * IBM RapidAccess / IBM EzButton / Chicony KBP-8993 keyboards. + */ + + if (atkbd->translated) + return 2; + + if (atkbd->id == 0xaca1) { + param[0] = 3; + ps2_command(ps2dev, param, ATKBD_CMD_SSCANSET); + return 3; + } + + if (allow_extra) { + param[0] = 0x71; + if (!ps2_command(ps2dev, param, ATKBD_CMD_EX_ENABLE)) { + atkbd->extra = 1; + return 2; + } + } + + if (target_set != 3) + return 2; + + if (!ps2_command(ps2dev, param, ATKBD_CMD_OK_GETID)) { + atkbd->id = param[0] << 8 | param[1]; + return 2; + } + + param[0] = 3; + if (ps2_command(ps2dev, param, ATKBD_CMD_SSCANSET)) + return 2; + + param[0] = 0; + if (ps2_command(ps2dev, param, ATKBD_CMD_GSCANSET)) + return 2; + + if (param[0] != 3) { + param[0] = 2; + if (ps2_command(ps2dev, param, ATKBD_CMD_SSCANSET)) + return 2; + } + + ps2_command(ps2dev, param, ATKBD_CMD_SETALL_MBR); + + return 3; +} + +static int atkbd_activate(struct atkbd *atkbd) +{ + struct ps2dev *ps2dev = &atkbd->ps2dev; + unsigned char param[1]; + +/* + * Set the LEDs to a defined state. + */ + + param[0] = 0; + if (ps2_command(ps2dev, param, ATKBD_CMD_SETLEDS)) + return -1; + +/* + * Set autorepeat to fastest possible. + */ + + param[0] = 0; + if (ps2_command(ps2dev, param, ATKBD_CMD_SETREP)) + return -1; + +/* + * Enable the keyboard to receive keystrokes. + */ + + if (ps2_command(ps2dev, NULL, ATKBD_CMD_ENABLE)) { + printk(KERN_ERR "atkbd.c: Failed to enable keyboard on %s\n", + ps2dev->serio->phys); + return -1; + } + + return 0; +} + +/* + * atkbd_cleanup() restores the keyboard state so that BIOS is happy after a + * reboot. + */ + +static void atkbd_cleanup(struct serio *serio) +{ + struct atkbd *atkbd = serio_get_drvdata(serio); + ps2_command(&atkbd->ps2dev, NULL, ATKBD_CMD_RESET_BAT); +} + + +/* + * atkbd_disconnect() closes and frees. + */ + +static void atkbd_disconnect(struct serio *serio) +{ + struct atkbd *atkbd = serio_get_drvdata(serio); + + atkbd_disable(atkbd); + + /* make sure we don't have a command in flight */ + synchronize_kernel(); + flush_scheduled_work(); + + device_remove_file(&serio->dev, &atkbd_attr_extra); + device_remove_file(&serio->dev, &atkbd_attr_scroll); + device_remove_file(&serio->dev, &atkbd_attr_set); + device_remove_file(&serio->dev, &atkbd_attr_softrepeat); + device_remove_file(&serio->dev, &atkbd_attr_softraw); + + input_unregister_device(&atkbd->dev); + serio_close(serio); + serio_set_drvdata(serio, NULL); + kfree(atkbd); +} + + +/* + * atkbd_set_device_attrs() initializes keyboard's keycode table + * according to the selected scancode set + */ + +static void atkbd_set_keycode_table(struct atkbd *atkbd) +{ + int i, j; + + memset(atkbd->keycode, 0, sizeof(atkbd->keycode)); + + if (atkbd->translated) { + for (i = 0; i < 128; i++) { + atkbd->keycode[i] = atkbd_set2_keycode[atkbd_unxlate_table[i]]; + atkbd->keycode[i | 0x80] = atkbd_set2_keycode[atkbd_unxlate_table[i] | 0x80]; + if (atkbd->scroll) + for (j = 0; j < ARRAY_SIZE(atkbd_scroll_keys); j++) + if ((atkbd_unxlate_table[i] | 0x80) == atkbd_scroll_keys[j].set2) + atkbd->keycode[i | 0x80] = atkbd_scroll_keys[j].keycode; + } + } else if (atkbd->set == 3) { + memcpy(atkbd->keycode, atkbd_set3_keycode, sizeof(atkbd->keycode)); + } else { + memcpy(atkbd->keycode, atkbd_set2_keycode, sizeof(atkbd->keycode)); + + if (atkbd->scroll) + for (i = 0; i < ARRAY_SIZE(atkbd_scroll_keys); i++) + atkbd->keycode[atkbd_scroll_keys[i].set2] = atkbd_scroll_keys[i].keycode; + } +} + +/* + * atkbd_set_device_attrs() sets up keyboard's input device structure + */ + +static void atkbd_set_device_attrs(struct atkbd *atkbd) +{ + int i; + + memset(&atkbd->dev, 0, sizeof(struct input_dev)); + + init_input_dev(&atkbd->dev); + + atkbd->dev.name = atkbd->name; + atkbd->dev.phys = atkbd->phys; + atkbd->dev.id.bustype = BUS_I8042; + atkbd->dev.id.vendor = 0x0001; + atkbd->dev.id.product = atkbd->translated ? 1 : atkbd->set; + atkbd->dev.id.version = atkbd->id; + atkbd->dev.event = atkbd_event; + atkbd->dev.private = atkbd; + atkbd->dev.dev = &atkbd->ps2dev.serio->dev; + + atkbd->dev.evbit[0] = BIT(EV_KEY) | BIT(EV_REP) | BIT(EV_MSC); + + if (atkbd->write) { + atkbd->dev.evbit[0] |= BIT(EV_LED); + atkbd->dev.ledbit[0] = BIT(LED_NUML) | BIT(LED_CAPSL) | BIT(LED_SCROLLL); + } + + if (atkbd->extra) + atkbd->dev.ledbit[0] |= BIT(LED_COMPOSE) | BIT(LED_SUSPEND) | + BIT(LED_SLEEP) | BIT(LED_MUTE) | BIT(LED_MISC); + + if (!atkbd->softrepeat) { + atkbd->dev.rep[REP_DELAY] = 250; + atkbd->dev.rep[REP_PERIOD] = 33; + } + + atkbd->dev.mscbit[0] = atkbd->softraw ? BIT(MSC_SCAN) : BIT(MSC_RAW) | BIT(MSC_SCAN); + + if (atkbd->scroll) { + atkbd->dev.evbit[0] |= BIT(EV_REL); + atkbd->dev.relbit[0] = BIT(REL_WHEEL) | BIT(REL_HWHEEL); + set_bit(BTN_MIDDLE, atkbd->dev.keybit); + } + + atkbd->dev.keycode = atkbd->keycode; + atkbd->dev.keycodesize = sizeof(unsigned char); + atkbd->dev.keycodemax = ARRAY_SIZE(atkbd_set2_keycode); + + for (i = 0; i < 512; i++) + if (atkbd->keycode[i] && atkbd->keycode[i] < ATKBD_SPECIAL) + set_bit(atkbd->keycode[i], atkbd->dev.keybit); +} + +/* + * atkbd_connect() is called when the serio module finds an interface + * that isn't handled yet by an appropriate device driver. We check if + * there is an AT keyboard out there and if yes, we register ourselves + * to the input module. + */ + +static int atkbd_connect(struct serio *serio, struct serio_driver *drv) +{ + struct atkbd *atkbd; + int err; + + if (!(atkbd = kmalloc(sizeof(struct atkbd), GFP_KERNEL))) + return - ENOMEM; + + memset(atkbd, 0, sizeof(struct atkbd)); + + ps2_init(&atkbd->ps2dev, serio); + + switch (serio->id.type) { + + case SERIO_8042_XL: + atkbd->translated = 1; + case SERIO_8042: + if (serio->write) + atkbd->write = 1; + break; + } + + atkbd->softraw = atkbd_softraw; + atkbd->softrepeat = atkbd_softrepeat; + atkbd->scroll = atkbd_scroll; + + if (!atkbd->write) + atkbd->softrepeat = 1; + + if (atkbd->softrepeat) + atkbd->softraw = 1; + + serio_set_drvdata(serio, atkbd); + + err = serio_open(serio, drv); + if (err) { + serio_set_drvdata(serio, NULL); + kfree(atkbd); + return err; + } + + if (atkbd->write) { + + if (atkbd_probe(atkbd)) { + serio_close(serio); + serio_set_drvdata(serio, NULL); + kfree(atkbd); + return -ENODEV; + } + + atkbd->set = atkbd_select_set(atkbd, atkbd_set, atkbd_extra); + atkbd_activate(atkbd); + + } else { + atkbd->set = 2; + atkbd->id = 0xab00; + } + + if (atkbd->extra) + sprintf(atkbd->name, "AT Set 2 Extra keyboard"); + else + sprintf(atkbd->name, "AT %s Set %d keyboard", + atkbd->translated ? "Translated" : "Raw", atkbd->set); + + sprintf(atkbd->phys, "%s/input0", serio->phys); + + atkbd_set_keycode_table(atkbd); + atkbd_set_device_attrs(atkbd); + + input_register_device(&atkbd->dev); + + device_create_file(&serio->dev, &atkbd_attr_extra); + device_create_file(&serio->dev, &atkbd_attr_scroll); + device_create_file(&serio->dev, &atkbd_attr_set); + device_create_file(&serio->dev, &atkbd_attr_softrepeat); + device_create_file(&serio->dev, &atkbd_attr_softraw); + + atkbd_enable(atkbd); + + printk(KERN_INFO "input: %s on %s\n", atkbd->name, serio->phys); + + return 0; +} + +/* + * atkbd_reconnect() tries to restore keyboard into a sane state and is + * most likely called on resume. + */ + +static int atkbd_reconnect(struct serio *serio) +{ + struct atkbd *atkbd = serio_get_drvdata(serio); + struct serio_driver *drv = serio->drv; + unsigned char param[1]; + + if (!atkbd || !drv) { + printk(KERN_DEBUG "atkbd: reconnect request, but serio is disconnected, ignoring...\n"); + return -1; + } + + atkbd_disable(atkbd); + + if (atkbd->write) { + param[0] = (test_bit(LED_SCROLLL, atkbd->dev.led) ? 1 : 0) + | (test_bit(LED_NUML, atkbd->dev.led) ? 2 : 0) + | (test_bit(LED_CAPSL, atkbd->dev.led) ? 4 : 0); + + if (atkbd_probe(atkbd)) + return -1; + if (atkbd->set != atkbd_select_set(atkbd, atkbd->set, atkbd->extra)) + return -1; + + atkbd_activate(atkbd); + + if (ps2_command(&atkbd->ps2dev, param, ATKBD_CMD_SETLEDS)) + return -1; + } + + atkbd_enable(atkbd); + + return 0; +} + +static struct serio_device_id atkbd_serio_ids[] = { + { + .type = SERIO_8042, + .proto = SERIO_ANY, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { + .type = SERIO_8042_XL, + .proto = SERIO_ANY, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { + .type = SERIO_RS232, + .proto = SERIO_PS2SER, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { 0 } +}; + +MODULE_DEVICE_TABLE(serio, atkbd_serio_ids); + +static struct serio_driver atkbd_drv = { + .driver = { + .name = "atkbd", + }, + .description = DRIVER_DESC, + .id_table = atkbd_serio_ids, + .interrupt = atkbd_interrupt, + .connect = atkbd_connect, + .reconnect = atkbd_reconnect, + .disconnect = atkbd_disconnect, + .cleanup = atkbd_cleanup, +}; + +static ssize_t atkbd_attr_show_helper(struct device *dev, char *buf, + ssize_t (*handler)(struct atkbd *, char *)) +{ + struct serio *serio = to_serio_port(dev); + int retval; + + retval = serio_pin_driver(serio); + if (retval) + return retval; + + if (serio->drv != &atkbd_drv) { + retval = -ENODEV; + goto out; + } + + retval = handler((struct atkbd *)serio_get_drvdata(serio), buf); + +out: + serio_unpin_driver(serio); + return retval; +} + +static ssize_t atkbd_attr_set_helper(struct device *dev, const char *buf, size_t count, + ssize_t (*handler)(struct atkbd *, const char *, size_t)) +{ + struct serio *serio = to_serio_port(dev); + struct atkbd *atkbd; + int retval; + + retval = serio_pin_driver(serio); + if (retval) + return retval; + + if (serio->drv != &atkbd_drv) { + retval = -ENODEV; + goto out; + } + + atkbd = serio_get_drvdata(serio); + atkbd_disable(atkbd); + retval = handler(atkbd, buf, count); + atkbd_enable(atkbd); + +out: + serio_unpin_driver(serio); + return retval; +} + +static ssize_t atkbd_show_extra(struct atkbd *atkbd, char *buf) +{ + return sprintf(buf, "%d\n", atkbd->extra ? 1 : 0); +} + +static ssize_t atkbd_set_extra(struct atkbd *atkbd, const char *buf, size_t count) +{ + unsigned long value; + char *rest; + + if (!atkbd->write) + return -EIO; + + value = simple_strtoul(buf, &rest, 10); + if (*rest || value > 1) + return -EINVAL; + + if (atkbd->extra != value) { + /* unregister device as it's properties will change */ + input_unregister_device(&atkbd->dev); + atkbd->set = atkbd_select_set(atkbd, atkbd->set, value); + atkbd_activate(atkbd); + atkbd_set_device_attrs(atkbd); + input_register_device(&atkbd->dev); + } + return count; +} + +static ssize_t atkbd_show_scroll(struct atkbd *atkbd, char *buf) +{ + return sprintf(buf, "%d\n", atkbd->scroll ? 1 : 0); +} + +static ssize_t atkbd_set_scroll(struct atkbd *atkbd, const char *buf, size_t count) +{ + unsigned long value; + char *rest; + + value = simple_strtoul(buf, &rest, 10); + if (*rest || value > 1) + return -EINVAL; + + if (atkbd->scroll != value) { + /* unregister device as it's properties will change */ + input_unregister_device(&atkbd->dev); + atkbd->scroll = value; + atkbd_set_keycode_table(atkbd); + atkbd_set_device_attrs(atkbd); + input_register_device(&atkbd->dev); + } + return count; +} + +static ssize_t atkbd_show_set(struct atkbd *atkbd, char *buf) +{ + return sprintf(buf, "%d\n", atkbd->set); +} + +static ssize_t atkbd_set_set(struct atkbd *atkbd, const char *buf, size_t count) +{ + unsigned long value; + char *rest; + + if (!atkbd->write) + return -EIO; + + value = simple_strtoul(buf, &rest, 10); + if (*rest || (value != 2 && value != 3)) + return -EINVAL; + + if (atkbd->set != value) { + /* unregister device as it's properties will change */ + input_unregister_device(&atkbd->dev); + atkbd->set = atkbd_select_set(atkbd, value, atkbd->extra); + atkbd_activate(atkbd); + atkbd_set_keycode_table(atkbd); + atkbd_set_device_attrs(atkbd); + input_register_device(&atkbd->dev); + } + return count; +} + +static ssize_t atkbd_show_softrepeat(struct atkbd *atkbd, char *buf) +{ + return sprintf(buf, "%d\n", atkbd->softrepeat ? 1 : 0); +} + +static ssize_t atkbd_set_softrepeat(struct atkbd *atkbd, const char *buf, size_t count) +{ + unsigned long value; + char *rest; + + if (!atkbd->write) + return -EIO; + + value = simple_strtoul(buf, &rest, 10); + if (*rest || value > 1) + return -EINVAL; + + if (atkbd->softrepeat != value) { + /* unregister device as it's properties will change */ + input_unregister_device(&atkbd->dev); + atkbd->softrepeat = value; + if (atkbd->softrepeat) + atkbd->softraw = 1; + atkbd_set_device_attrs(atkbd); + input_register_device(&atkbd->dev); + } + + return count; +} + + +static ssize_t atkbd_show_softraw(struct atkbd *atkbd, char *buf) +{ + return sprintf(buf, "%d\n", atkbd->softraw ? 1 : 0); +} + +static ssize_t atkbd_set_softraw(struct atkbd *atkbd, const char *buf, size_t count) +{ + unsigned long value; + char *rest; + + value = simple_strtoul(buf, &rest, 10); + if (*rest || value > 1) + return -EINVAL; + + if (atkbd->softraw != value) { + /* unregister device as it's properties will change */ + input_unregister_device(&atkbd->dev); + atkbd->softraw = value; + atkbd_set_device_attrs(atkbd); + input_register_device(&atkbd->dev); + } + return count; +} + + +static int __init atkbd_init(void) +{ + serio_register_driver(&atkbd_drv); + return 0; +} + +static void __exit atkbd_exit(void) +{ + serio_unregister_driver(&atkbd_drv); +} + +module_init(atkbd_init); +module_exit(atkbd_exit); diff --git a/drivers/input/keyboard/corgikbd.c b/drivers/input/keyboard/corgikbd.c new file mode 100644 index 000000000000..0f1220a0ceb5 --- /dev/null +++ b/drivers/input/keyboard/corgikbd.c @@ -0,0 +1,361 @@ +/* + * Keyboard driver for Sharp Corgi models (SL-C7xx) + * + * Copyright (c) 2004-2005 Richard Purdie + * + * Based on xtkbd.c/locomkbd.c + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <asm/irq.h> + +#include <asm/arch/corgi.h> +#include <asm/arch/hardware.h> +#include <asm/arch/pxa-regs.h> +#include <asm/hardware/scoop.h> + +#define KB_ROWS 8 +#define KB_COLS 12 +#define KB_ROWMASK(r) (1 << (r)) +#define SCANCODE(r,c) ( ((r)<<4) + (c) + 1 ) +/* zero code, 124 scancodes + 3 hinge combinations */ +#define NR_SCANCODES ( SCANCODE(KB_ROWS-1,KB_COLS-1) +1 +1 +3 ) +#define SCAN_INTERVAL (HZ/10) +#define CORGIKBD_PRESSED 1 + +#define HINGE_SCAN_INTERVAL (HZ/4) + +#define CORGI_KEY_CALENDER KEY_F1 +#define CORGI_KEY_ADDRESS KEY_F2 +#define CORGI_KEY_FN KEY_F3 +#define CORGI_KEY_OFF KEY_SUSPEND +#define CORGI_KEY_EXOK KEY_F5 +#define CORGI_KEY_EXCANCEL KEY_F6 +#define CORGI_KEY_EXJOGDOWN KEY_F7 +#define CORGI_KEY_EXJOGUP KEY_F8 +#define CORGI_KEY_JAP1 KEY_LEFTCTRL +#define CORGI_KEY_JAP2 KEY_LEFTALT +#define CORGI_KEY_OK KEY_F11 +#define CORGI_KEY_MENU KEY_F12 +#define CORGI_HINGE_0 KEY_KP0 +#define CORGI_HINGE_1 KEY_KP1 +#define CORGI_HINGE_2 KEY_KP2 + +static unsigned char corgikbd_keycode[NR_SCANCODES] = { + 0, /* 0 */ + 0, KEY_1, KEY_3, KEY_5, KEY_6, KEY_7, KEY_9, KEY_0, KEY_BACKSPACE, 0, 0, 0, 0, 0, 0, 0, /* 1-16 */ + 0, KEY_2, KEY_4, KEY_R, KEY_Y, KEY_8, KEY_I, KEY_O, KEY_P, 0, 0, 0, 0, 0, 0, 0, /* 17-32 */ + KEY_TAB, KEY_Q, KEY_E, KEY_T, KEY_G, KEY_U, KEY_J, KEY_K, 0, 0, 0, 0, 0, 0, 0, 0, /* 33-48 */ + CORGI_KEY_CALENDER, KEY_W, KEY_S, KEY_F, KEY_V, KEY_H, KEY_M, KEY_L, 0, KEY_RIGHTSHIFT, 0, 0, 0, 0, 0, 0, /* 49-64 */ + CORGI_KEY_ADDRESS, KEY_A, KEY_D, KEY_C, KEY_B, KEY_N, KEY_DOT, 0, KEY_ENTER, 0, KEY_LEFTSHIFT, 0, 0, 0, 0, 0, /* 65-80 */ + KEY_MAIL, KEY_Z, KEY_X, KEY_MINUS, KEY_SPACE, KEY_COMMA, 0, KEY_UP, 0, 0, 0, CORGI_KEY_FN, 0, 0, 0, 0, /* 81-96 */ + KEY_SYSRQ, CORGI_KEY_JAP1, CORGI_KEY_JAP2, KEY_CANCEL, CORGI_KEY_OK, CORGI_KEY_MENU, KEY_LEFT, KEY_DOWN, KEY_RIGHT, 0, 0, 0, 0, 0, 0, 0, /* 97-112 */ + CORGI_KEY_OFF, CORGI_KEY_EXOK, CORGI_KEY_EXCANCEL, CORGI_KEY_EXJOGDOWN, CORGI_KEY_EXJOGUP, 0, 0, 0, 0, 0, 0, 0, /* 113-124 */ + CORGI_HINGE_0, CORGI_HINGE_1, CORGI_HINGE_2 /* 125-127 */ +}; + + +struct corgikbd { + unsigned char keycode[ARRAY_SIZE(corgikbd_keycode)]; + struct input_dev input; + char phys[32]; + + unsigned char state[ARRAY_SIZE(corgikbd_keycode)]; + spinlock_t lock; + + struct timer_list timer; + struct timer_list htimer; +}; + +static void handle_scancode(unsigned int pressed,unsigned int scancode, struct corgikbd *corgikbd_data) +{ + if (pressed && !(corgikbd_data->state[scancode] & CORGIKBD_PRESSED)) { + corgikbd_data->state[scancode] |= CORGIKBD_PRESSED; + input_report_key(&corgikbd_data->input, corgikbd_data->keycode[scancode], 1); + if (corgikbd_data->keycode[scancode] == CORGI_KEY_OFF) + input_event(&corgikbd_data->input, EV_PWR, CORGI_KEY_OFF, 1); + } else if (!pressed && corgikbd_data->state[scancode] & CORGIKBD_PRESSED) { + corgikbd_data->state[scancode] &= ~CORGIKBD_PRESSED; + input_report_key(&corgikbd_data->input, corgikbd_data->keycode[scancode], 0); + } +} + +#define KB_DISCHARGE_DELAY 10 +#define KB_ACTIVATE_DELAY 10 + +/* Helper functions for reading the keyboard matrix + * Note: We should really be using pxa_gpio_mode to alter GPDR but it + * requires a function call per GPIO bit which is excessive + * when we need to access 12 bits at once multiple times. + * These functions must be called within local_irq_save()/local_irq_restore() + * or similar. + */ +static inline void corgikbd_discharge_all(void) +{ + // STROBE All HiZ + GPCR2 = CORGI_GPIO_ALL_STROBE_BIT; + GPDR2 &= ~CORGI_GPIO_ALL_STROBE_BIT; +} + +static inline void corgikbd_activate_all(void) +{ + // STROBE ALL -> High + GPSR2 = CORGI_GPIO_ALL_STROBE_BIT; + GPDR2 |= CORGI_GPIO_ALL_STROBE_BIT; + + udelay(KB_DISCHARGE_DELAY); + + // Clear any interrupts we may have triggered when altering the GPIO lines + GEDR1 = CORGI_GPIO_HIGH_SENSE_BIT; + GEDR2 = CORGI_GPIO_LOW_SENSE_BIT; +} + +static inline void corgikbd_activate_col(int col) +{ + // STROBE col -> High, not col -> HiZ + GPSR2 = CORGI_GPIO_STROBE_BIT(col); + GPDR2 = (GPDR2 & ~CORGI_GPIO_ALL_STROBE_BIT) | CORGI_GPIO_STROBE_BIT(col); +} + +static inline void corgikbd_reset_col(int col) +{ + // STROBE col -> Low + GPCR2 = CORGI_GPIO_STROBE_BIT(col); + // STROBE col -> out, not col -> HiZ + GPDR2 = (GPDR2 & ~CORGI_GPIO_ALL_STROBE_BIT) | CORGI_GPIO_STROBE_BIT(col); +} + +#define GET_ROWS_STATUS(c) (((GPLR1 & CORGI_GPIO_HIGH_SENSE_BIT) >> CORGI_GPIO_HIGH_SENSE_RSHIFT) | ((GPLR2 & CORGI_GPIO_LOW_SENSE_BIT) << CORGI_GPIO_LOW_SENSE_LSHIFT)) + +/* + * The corgi keyboard only generates interrupts when a key is pressed. + * When a key is pressed, we enable a timer which then scans the + * keyboard to detect when the key is released. + */ + +/* Scan the hardware keyboard and push any changes up through the input layer */ +static void corgikbd_scankeyboard(struct corgikbd *corgikbd_data, struct pt_regs *regs) +{ + unsigned int row, col, rowd, scancode; + unsigned long flags; + unsigned int num_pressed; + + spin_lock_irqsave(&corgikbd_data->lock, flags); + + if (regs) + input_regs(&corgikbd_data->input, regs); + + num_pressed = 0; + for (col = 0; col < KB_COLS; col++) { + /* + * Discharge the output driver capacitatance + * in the keyboard matrix. (Yes it is significant..) + */ + + corgikbd_discharge_all(); + udelay(KB_DISCHARGE_DELAY); + + corgikbd_activate_col(col); + udelay(KB_ACTIVATE_DELAY); + + rowd = GET_ROWS_STATUS(col); + for (row = 0; row < KB_ROWS; row++) { + scancode = SCANCODE(row, col); + handle_scancode((rowd & KB_ROWMASK(row)), scancode, corgikbd_data); + if (rowd & KB_ROWMASK(row)) + num_pressed++; + } + corgikbd_reset_col(col); + } + + corgikbd_activate_all(); + + input_sync(&corgikbd_data->input); + + /* if any keys are pressed, enable the timer */ + if (num_pressed) + mod_timer(&corgikbd_data->timer, jiffies + SCAN_INTERVAL); + + spin_unlock_irqrestore(&corgikbd_data->lock, flags); +} + +/* + * corgi keyboard interrupt handler. + */ +static irqreturn_t corgikbd_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + struct corgikbd *corgikbd_data = dev_id; + + if (!timer_pending(&corgikbd_data->timer)) { + /** wait chattering delay **/ + udelay(20); + corgikbd_scankeyboard(corgikbd_data, regs); + } + + return IRQ_HANDLED; +} + +/* + * corgi timer checking for released keys + */ +static void corgikbd_timer_callback(unsigned long data) +{ + struct corgikbd *corgikbd_data = (struct corgikbd *) data; + corgikbd_scankeyboard(corgikbd_data, NULL); +} + +/* + * The hinge switches generate no interrupt so they need to be + * monitored by a timer. + * + * When we detect changes, we debounce it and then pass the three + * positions the system can take as keypresses to the input system. + */ + +#define HINGE_STABLE_COUNT 2 +static int sharpsl_hinge_state; +static int hinge_count; + +static void corgikbd_hinge_timer(unsigned long data) +{ + struct corgikbd *corgikbd_data = (struct corgikbd *) data; + unsigned long gprr; + unsigned long flags; + + gprr = read_scoop_reg(SCOOP_GPRR) & (CORGI_SCP_SWA | CORGI_SCP_SWB); + if (gprr != sharpsl_hinge_state) { + hinge_count = 0; + sharpsl_hinge_state = gprr; + } else if (hinge_count < HINGE_STABLE_COUNT) { + hinge_count++; + if (hinge_count >= HINGE_STABLE_COUNT) { + spin_lock_irqsave(&corgikbd_data->lock, flags); + + handle_scancode((sharpsl_hinge_state == 0x00), 125, corgikbd_data); /* Keyboard with Landscape Screen */ + handle_scancode((sharpsl_hinge_state == 0x08), 126, corgikbd_data); /* No Keyboard with Portrait Screen */ + handle_scancode((sharpsl_hinge_state == 0x0c), 127, corgikbd_data); /* Keyboard and Screen Closed */ + input_sync(&corgikbd_data->input); + + spin_unlock_irqrestore(&corgikbd_data->lock, flags); + } + } + mod_timer(&corgikbd_data->htimer, jiffies + HINGE_SCAN_INTERVAL); +} + +static int __init corgikbd_probe(struct device *dev) +{ + int i; + struct corgikbd *corgikbd; + + corgikbd = kcalloc(1, sizeof(struct corgikbd), GFP_KERNEL); + if (!corgikbd) + return -ENOMEM; + + dev_set_drvdata(dev,corgikbd); + strcpy(corgikbd->phys, "corgikbd/input0"); + + spin_lock_init(corgikbd->lock); + + /* Init Keyboard rescan timer */ + init_timer(&corgikbd->timer); + corgikbd->timer.function = corgikbd_timer_callback; + corgikbd->timer.data = (unsigned long) corgikbd; + + /* Init Hinge Timer */ + init_timer(&corgikbd->htimer); + corgikbd->htimer.function = corgikbd_hinge_timer; + corgikbd->htimer.data = (unsigned long) corgikbd; + + init_input_dev(&corgikbd->input); + corgikbd->input.private = corgikbd; + corgikbd->input.name = "Corgi Keyboard"; + corgikbd->input.dev = dev; + corgikbd->input.phys = corgikbd->phys; + corgikbd->input.id.bustype = BUS_HOST; + corgikbd->input.id.vendor = 0x0001; + corgikbd->input.id.product = 0x0001; + corgikbd->input.id.version = 0x0100; + corgikbd->input.evbit[0] = BIT(EV_KEY) | BIT(EV_REP) | BIT(EV_PWR); + corgikbd->input.keycode = corgikbd->keycode; + corgikbd->input.keycodesize = sizeof(unsigned char); + corgikbd->input.keycodemax = ARRAY_SIZE(corgikbd_keycode); + + memcpy(corgikbd->keycode, corgikbd_keycode, sizeof(corgikbd->keycode)); + for (i = 0; i < ARRAY_SIZE(corgikbd_keycode); i++) + set_bit(corgikbd->keycode[i], corgikbd->input.keybit); + clear_bit(0, corgikbd->input.keybit); + + input_register_device(&corgikbd->input); + mod_timer(&corgikbd->htimer, jiffies + HINGE_SCAN_INTERVAL); + + /* Setup sense interrupts - RisingEdge Detect, sense lines as inputs */ + for (i = 0; i < CORGI_KEY_SENSE_NUM; i++) { + pxa_gpio_mode(CORGI_GPIO_KEY_SENSE(i) | GPIO_IN); + if (request_irq(CORGI_IRQ_GPIO_KEY_SENSE(i), corgikbd_interrupt, + SA_INTERRUPT, "corgikbd", corgikbd)) + printk(KERN_WARNING "corgikbd: Can't get IRQ: %d!\n", i); + else + set_irq_type(CORGI_IRQ_GPIO_KEY_SENSE(i),IRQT_RISING); + } + + /* Set Strobe lines as outputs - set high */ + for (i = 0; i < CORGI_KEY_STROBE_NUM; i++) + pxa_gpio_mode(CORGI_GPIO_KEY_STROBE(i) | GPIO_OUT | GPIO_DFLT_HIGH); + + printk(KERN_INFO "input: Corgi Keyboard Registered\n"); + + return 0; +} + +static int corgikbd_remove(struct device *dev) +{ + int i; + struct corgikbd *corgikbd = dev_get_drvdata(dev); + + for (i = 0; i < CORGI_KEY_SENSE_NUM; i++) + free_irq(CORGI_IRQ_GPIO_KEY_SENSE(i), corgikbd); + + del_timer_sync(&corgikbd->htimer); + del_timer_sync(&corgikbd->timer); + + input_unregister_device(&corgikbd->input); + + kfree(corgikbd); + + return 0; +} + +static struct device_driver corgikbd_driver = { + .name = "corgi-keyboard", + .bus = &platform_bus_type, + .probe = corgikbd_probe, + .remove = corgikbd_remove, +}; + +static int __devinit corgikbd_init(void) +{ + return driver_register(&corgikbd_driver); +} + +static void __exit corgikbd_exit(void) +{ + driver_unregister(&corgikbd_driver); +} + +module_init(corgikbd_init); +module_exit(corgikbd_exit); + +MODULE_AUTHOR("Richard Purdie <rpurdie@rpsys.net>"); +MODULE_DESCRIPTION("Corgi Keyboard Driver"); +MODULE_LICENSE("GPLv2"); diff --git a/drivers/input/keyboard/hil_kbd.c b/drivers/input/keyboard/hil_kbd.c new file mode 100644 index 000000000000..ef78bffed5e7 --- /dev/null +++ b/drivers/input/keyboard/hil_kbd.c @@ -0,0 +1,375 @@ +/* + * Generic linux-input device driver for keyboard devices + * + * Copyright (c) 2001 Brian S. Julin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * Alternatively, this software may be distributed under the terms of the + * GNU General Public License ("GPL"). + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * + * References: + * HP-HIL Technical Reference Manual. Hewlett Packard Product No. 45918A + * + */ + +#include <linux/hil.h> +#include <linux/input.h> +#include <linux/serio.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/pci_ids.h> + +#define PREFIX "HIL KEYB: " +#define HIL_GENERIC_NAME "HIL keyboard" + +MODULE_AUTHOR("Brian S. Julin <bri@calyx.com>"); +MODULE_DESCRIPTION(HIL_GENERIC_NAME " driver"); +MODULE_LICENSE("Dual BSD/GPL"); + +#define HIL_KBD_MAX_LENGTH 16 + +#define HIL_KBD_SET1_UPBIT 0x01 +#define HIL_KBD_SET1_SHIFT 1 +static unsigned int hil_kbd_set1[HIL_KEYCODES_SET1_TBLSIZE] = + { HIL_KEYCODES_SET1 }; + +#define HIL_KBD_SET2_UPBIT 0x01 +#define HIL_KBD_SET2_SHIFT 1 +/* Set2 is user defined */ + +#define HIL_KBD_SET3_UPBIT 0x80 +#define HIL_KBD_SET3_SHIFT 0 +static unsigned int hil_kbd_set3[HIL_KEYCODES_SET3_TBLSIZE] = + { HIL_KEYCODES_SET3 }; + +static char hil_language[][16] = { HIL_LOCALE_MAP }; + +struct hil_kbd { + struct input_dev dev; + struct serio *serio; + + /* Input buffer and index for packets from HIL bus. */ + hil_packet data[HIL_KBD_MAX_LENGTH]; + int idx4; /* four counts per packet */ + + /* Raw device info records from HIL bus, see hil.h for fields. */ + char idd[HIL_KBD_MAX_LENGTH]; /* DID byte and IDD record */ + char rsc[HIL_KBD_MAX_LENGTH]; /* RSC record */ + char exd[HIL_KBD_MAX_LENGTH]; /* EXD record */ + char rnm[HIL_KBD_MAX_LENGTH + 1]; /* RNM record + NULL term. */ + + /* Something to sleep around with. */ + struct semaphore sem; +}; + +/* Process a complete packet after transfer from the HIL */ +static void hil_kbd_process_record(struct hil_kbd *kbd) +{ + struct input_dev *dev = &kbd->dev; + hil_packet *data = kbd->data; + hil_packet p; + int idx, i, cnt; + + idx = kbd->idx4/4; + p = data[idx - 1]; + + if ((p & ~HIL_CMDCT_POL) == + (HIL_ERR_INT | HIL_PKT_CMD | HIL_CMD_POL)) goto report; + if ((p & ~HIL_CMDCT_RPL) == + (HIL_ERR_INT | HIL_PKT_CMD | HIL_CMD_RPL)) goto report; + + /* Not a poll response. See if we are loading config records. */ + switch (p & HIL_PKT_DATA_MASK) { + case HIL_CMD_IDD: + for (i = 0; i < idx; i++) + kbd->idd[i] = kbd->data[i] & HIL_PKT_DATA_MASK; + for (; i < HIL_KBD_MAX_LENGTH; i++) + kbd->idd[i] = 0; + break; + case HIL_CMD_RSC: + for (i = 0; i < idx; i++) + kbd->rsc[i] = kbd->data[i] & HIL_PKT_DATA_MASK; + for (; i < HIL_KBD_MAX_LENGTH; i++) + kbd->rsc[i] = 0; + break; + case HIL_CMD_EXD: + for (i = 0; i < idx; i++) + kbd->exd[i] = kbd->data[i] & HIL_PKT_DATA_MASK; + for (; i < HIL_KBD_MAX_LENGTH; i++) + kbd->exd[i] = 0; + break; + case HIL_CMD_RNM: + for (i = 0; i < idx; i++) + kbd->rnm[i] = kbd->data[i] & HIL_PKT_DATA_MASK; + for (; i < HIL_KBD_MAX_LENGTH + 1; i++) + kbd->rnm[i] = '\0'; + break; + default: + /* These occur when device isn't present */ + if (p == (HIL_ERR_INT | HIL_PKT_CMD)) break; + /* Anything else we'd like to know about. */ + printk(KERN_WARNING PREFIX "Device sent unknown record %x\n", p); + break; + } + goto out; + + report: + cnt = 1; + switch (kbd->data[0] & HIL_POL_CHARTYPE_MASK) { + case HIL_POL_CHARTYPE_NONE: + break; + case HIL_POL_CHARTYPE_ASCII: + while (cnt < idx - 1) + input_report_key(dev, kbd->data[cnt++] & 0x7f, 1); + break; + case HIL_POL_CHARTYPE_RSVD1: + case HIL_POL_CHARTYPE_RSVD2: + case HIL_POL_CHARTYPE_BINARY: + while (cnt < idx - 1) + input_report_key(dev, kbd->data[cnt++], 1); + break; + case HIL_POL_CHARTYPE_SET1: + while (cnt < idx - 1) { + unsigned int key; + int up; + key = kbd->data[cnt++]; + up = key & HIL_KBD_SET1_UPBIT; + key &= (~HIL_KBD_SET1_UPBIT & 0xff); + key = hil_kbd_set1[key >> HIL_KBD_SET1_SHIFT]; + if (key != KEY_RESERVED) + input_report_key(dev, key, !up); + } + break; + case HIL_POL_CHARTYPE_SET2: + while (cnt < idx - 1) { + unsigned int key; + int up; + key = kbd->data[cnt++]; + up = key & HIL_KBD_SET2_UPBIT; + key &= (~HIL_KBD_SET1_UPBIT & 0xff); + key = key >> HIL_KBD_SET2_SHIFT; + if (key != KEY_RESERVED) + input_report_key(dev, key, !up); + } + break; + case HIL_POL_CHARTYPE_SET3: + while (cnt < idx - 1) { + unsigned int key; + int up; + key = kbd->data[cnt++]; + up = key & HIL_KBD_SET3_UPBIT; + key &= (~HIL_KBD_SET1_UPBIT & 0xff); + key = hil_kbd_set3[key >> HIL_KBD_SET3_SHIFT]; + if (key != KEY_RESERVED) + input_report_key(dev, key, !up); + } + break; + } + out: + kbd->idx4 = 0; + up(&kbd->sem); +} + +static void hil_kbd_process_err(struct hil_kbd *kbd) { + printk(KERN_WARNING PREFIX "errored HIL packet\n"); + kbd->idx4 = 0; + up(&kbd->sem); +} + +static irqreturn_t hil_kbd_interrupt(struct serio *serio, + unsigned char data, unsigned int flags, struct pt_regs *regs) +{ + struct hil_kbd *kbd; + hil_packet packet; + int idx; + + kbd = (struct hil_kbd *)serio->private; + if (kbd == NULL) { + BUG(); + return IRQ_HANDLED; + } + + if (kbd->idx4 >= (HIL_KBD_MAX_LENGTH * sizeof(hil_packet))) { + hil_kbd_process_err(kbd); + return IRQ_HANDLED; + } + idx = kbd->idx4/4; + if (!(kbd->idx4 % 4)) kbd->data[idx] = 0; + packet = kbd->data[idx]; + packet |= ((hil_packet)data) << ((3 - (kbd->idx4 % 4)) * 8); + kbd->data[idx] = packet; + + /* Records of N 4-byte hil_packets must terminate with a command. */ + if ((++(kbd->idx4)) % 4) return IRQ_HANDLED; + if ((packet & 0xffff0000) != HIL_ERR_INT) { + hil_kbd_process_err(kbd); + return IRQ_HANDLED; + } + if (packet & HIL_PKT_CMD) hil_kbd_process_record(kbd); + return IRQ_HANDLED; +} + +static void hil_kbd_disconnect(struct serio *serio) +{ + struct hil_kbd *kbd; + + kbd = (struct hil_kbd *)serio->private; + if (kbd == NULL) { + BUG(); + return; + } + + input_unregister_device(&kbd->dev); + serio_close(serio); + kfree(kbd); +} + +static void hil_kbd_connect(struct serio *serio, struct serio_driver *drv) +{ + struct hil_kbd *kbd; + uint8_t did, *idd; + int i; + + if (serio->type != (SERIO_HIL_MLC | SERIO_HIL)) return; + + if (!(kbd = kmalloc(sizeof(struct hil_kbd), GFP_KERNEL))) return; + memset(kbd, 0, sizeof(struct hil_kbd)); + + if (serio_open(serio, drv)) goto bail0; + + serio->private = kbd; + kbd->serio = serio; + kbd->dev.private = kbd; + + init_MUTEX_LOCKED(&(kbd->sem)); + + /* Get device info. MLC driver supplies devid/status/etc. */ + serio->write(serio, 0); + serio->write(serio, 0); + serio->write(serio, HIL_PKT_CMD >> 8); + serio->write(serio, HIL_CMD_IDD); + down(&(kbd->sem)); + + serio->write(serio, 0); + serio->write(serio, 0); + serio->write(serio, HIL_PKT_CMD >> 8); + serio->write(serio, HIL_CMD_RSC); + down(&(kbd->sem)); + + serio->write(serio, 0); + serio->write(serio, 0); + serio->write(serio, HIL_PKT_CMD >> 8); + serio->write(serio, HIL_CMD_RNM); + down(&(kbd->sem)); + + serio->write(serio, 0); + serio->write(serio, 0); + serio->write(serio, HIL_PKT_CMD >> 8); + serio->write(serio, HIL_CMD_EXD); + down(&(kbd->sem)); + + up(&(kbd->sem)); + + did = kbd->idd[0]; + idd = kbd->idd + 1; + switch (did & HIL_IDD_DID_TYPE_MASK) { + case HIL_IDD_DID_TYPE_KB_INTEGRAL: + case HIL_IDD_DID_TYPE_KB_ITF: + case HIL_IDD_DID_TYPE_KB_RSVD: + case HIL_IDD_DID_TYPE_CHAR: + printk(KERN_INFO PREFIX "HIL keyboard found (did = 0x%02x, lang = %s)\n", + did, hil_language[did & HIL_IDD_DID_TYPE_KB_LANG_MASK]); + break; + default: + goto bail1; + } + + if(HIL_IDD_NUM_BUTTONS(idd) || HIL_IDD_NUM_AXES_PER_SET(*idd)) { + printk(KERN_INFO PREFIX "keyboards only, no combo devices supported.\n"); + goto bail1; + } + + + kbd->dev.evbit[0] = BIT(EV_KEY) | BIT(EV_REP); + kbd->dev.ledbit[0] = BIT(LED_NUML) | BIT(LED_CAPSL) | BIT(LED_SCROLLL); + kbd->dev.keycodemax = HIL_KEYCODES_SET1_TBLSIZE; + kbd->dev.keycodesize = sizeof(hil_kbd_set1[0]); + kbd->dev.keycode = hil_kbd_set1; + kbd->dev.name = strlen(kbd->rnm) ? kbd->rnm : HIL_GENERIC_NAME; + kbd->dev.phys = "hpkbd/input0"; /* XXX */ + + kbd->dev.id.bustype = BUS_HIL; + kbd->dev.id.vendor = PCI_VENDOR_ID_HP; + kbd->dev.id.product = 0x0001; /* TODO: get from kbd->rsc */ + kbd->dev.id.version = 0x0100; /* TODO: get from kbd->rsc */ + kbd->dev.dev = &serio->dev; + + for (i = 0; i < 128; i++) { + set_bit(hil_kbd_set1[i], kbd->dev.keybit); + set_bit(hil_kbd_set3[i], kbd->dev.keybit); + } + clear_bit(0, kbd->dev.keybit); + + input_register_device(&kbd->dev); + printk(KERN_INFO "input: %s, ID: %d\n", + kbd->dev.name, did); + + serio->write(serio, 0); + serio->write(serio, 0); + serio->write(serio, HIL_PKT_CMD >> 8); + serio->write(serio, HIL_CMD_EK1); /* Enable Keyswitch Autorepeat 1 */ + down(&(kbd->sem)); + up(&(kbd->sem)); + + return; + bail1: + serio_close(serio); + bail0: + kfree(kbd); +} + + +struct serio_driver hil_kbd_serio_drv = { + .driver = { + .name = "hil_kbd", + }, + .description = "HP HIL keyboard driver", + .connect = hil_kbd_connect, + .disconnect = hil_kbd_disconnect, + .interrupt = hil_kbd_interrupt +}; + +static int __init hil_kbd_init(void) +{ + serio_register_driver(&hil_kbd_serio_drv); + return 0; +} + +static void __exit hil_kbd_exit(void) +{ + serio_unregister_driver(&hil_kbd_serio_drv); +} + +module_init(hil_kbd_init); +module_exit(hil_kbd_exit); diff --git a/drivers/input/keyboard/hilkbd.c b/drivers/input/keyboard/hilkbd.c new file mode 100644 index 000000000000..eecb77db0847 --- /dev/null +++ b/drivers/input/keyboard/hilkbd.c @@ -0,0 +1,343 @@ +/* + * linux/drivers/hil/hilkbd.c + * + * Copyright (C) 1998 Philip Blundell <philb@gnu.org> + * Copyright (C) 1999 Matthew Wilcox <willy@bofh.ai> + * Copyright (C) 1999-2003 Helge Deller <deller@gmx.de> + * + * Very basic HP Human Interface Loop (HIL) driver. + * This driver handles the keyboard on HP300 (m68k) and on some + * HP700 (parisc) series machines. + * + * + * This file is subject to the terms and conditions of the GNU General Public + * License version 2. See the file COPYING in the main directory of this + * archive for more details. + */ + +#include <linux/pci_ids.h> +#include <linux/ioport.h> +#include <linux/module.h> +#include <linux/config.h> +#include <linux/errno.h> +#include <linux/input.h> +#include <linux/init.h> +#include <linux/irq.h> +#include <linux/hil.h> +#include <linux/spinlock.h> + + +MODULE_AUTHOR("Philip Blundell, Matthew Wilcox, Helge Deller"); +MODULE_DESCRIPTION("HIL keyboard driver (basic functionality)"); +MODULE_LICENSE("GPL v2"); + + +#if defined(CONFIG_PARISC) + + #include <asm/io.h> + #include <asm/hardware.h> + #include <asm/parisc-device.h> + static unsigned long hil_base; /* HPA for the HIL device */ + static unsigned int hil_irq; + #define HILBASE hil_base /* HPPA (parisc) port address */ + #define HIL_DATA 0x800 + #define HIL_CMD 0x801 + #define HIL_IRQ hil_irq + #define hil_readb(p) gsc_readb(p) + #define hil_writeb(v,p) gsc_writeb((v),(p)) + +#elif defined(CONFIG_HP300) + + #define HILBASE 0xf0428000 /* HP300 (m86k) port address */ + #define HIL_DATA 0x1 + #define HIL_CMD 0x3 + #define HIL_IRQ 2 + #define hil_readb(p) readb(p) + #define hil_writeb(v,p) writeb((v),(p)) + +#else +#error "HIL is not supported on this platform" +#endif + + + +/* HIL helper functions */ + +#define hil_busy() (hil_readb(HILBASE + HIL_CMD) & HIL_BUSY) +#define hil_data_available() (hil_readb(HILBASE + HIL_CMD) & HIL_DATA_RDY) +#define hil_status() (hil_readb(HILBASE + HIL_CMD)) +#define hil_command(x) do { hil_writeb((x), HILBASE + HIL_CMD); } while (0) +#define hil_read_data() (hil_readb(HILBASE + HIL_DATA)) +#define hil_write_data(x) do { hil_writeb((x), HILBASE + HIL_DATA); } while (0) + +/* HIL constants */ + +#define HIL_BUSY 0x02 +#define HIL_DATA_RDY 0x01 + +#define HIL_SETARD 0xA0 /* set auto-repeat delay */ +#define HIL_SETARR 0xA2 /* set auto-repeat rate */ +#define HIL_SETTONE 0xA3 /* set tone generator */ +#define HIL_CNMT 0xB2 /* clear nmi */ +#define HIL_INTON 0x5C /* Turn on interrupts. */ +#define HIL_INTOFF 0x5D /* Turn off interrupts. */ + +#define HIL_READKBDSADR 0xF9 +#define HIL_WRITEKBDSADR 0xE9 + +static unsigned int hphilkeyb_keycode[HIL_KEYCODES_SET1_TBLSIZE] = + { HIL_KEYCODES_SET1 }; + +/* HIL structure */ +static struct { + struct input_dev dev; + + unsigned int curdev; + + unsigned char s; + unsigned char c; + int valid; + + unsigned char data[16]; + unsigned int ptr; + spinlock_t lock; + + void *dev_id; /* native bus device */ +} hil_dev; + + +static void poll_finished(void) +{ + int down; + int key; + unsigned char scode; + + switch (hil_dev.data[0]) { + case 0x40: + down = (hil_dev.data[1] & 1) == 0; + scode = hil_dev.data[1] >> 1; + key = hphilkeyb_keycode[scode]; + input_report_key(&hil_dev.dev, key, down); + break; + } + hil_dev.curdev = 0; +} + +static inline void handle_status(unsigned char s, unsigned char c) +{ + if (c & 0x8) { + /* End of block */ + if (c & 0x10) + poll_finished(); + } else { + if (c & 0x10) { + if (hil_dev.curdev) + poll_finished(); /* just in case */ + hil_dev.curdev = c & 7; + hil_dev.ptr = 0; + } + } +} + +static inline void handle_data(unsigned char s, unsigned char c) +{ + if (hil_dev.curdev) { + hil_dev.data[hil_dev.ptr++] = c; + hil_dev.ptr &= 15; + } +} + + +/* + * Handle HIL interrupts. + */ +static irqreturn_t hil_interrupt(int irq, void *handle, struct pt_regs *regs) +{ + unsigned char s, c; + + s = hil_status(); + c = hil_read_data(); + + switch (s >> 4) { + case 0x5: + handle_status(s, c); + break; + case 0x6: + handle_data(s, c); + break; + case 0x4: + hil_dev.s = s; + hil_dev.c = c; + mb(); + hil_dev.valid = 1; + break; + } + return IRQ_HANDLED; +} + +/* + * Send a command to the HIL + */ + +static void hil_do(unsigned char cmd, unsigned char *data, unsigned int len) +{ + unsigned long flags; + + spin_lock_irqsave(&hil_dev.lock, flags); + while (hil_busy()) + /* wait */; + hil_command(cmd); + while (len--) { + while (hil_busy()) + /* wait */; + hil_write_data(*(data++)); + } + spin_unlock_irqrestore(&hil_dev.lock, flags); +} + + +/* + * Initialise HIL. + */ + +static int __init +hil_keyb_init(void) +{ + unsigned char c; + unsigned int i, kbid; + wait_queue_head_t hil_wait; + + if (hil_dev.dev.id.bustype) { + return -ENODEV; /* already initialized */ + } + +#if defined(CONFIG_HP300) + if (!hwreg_present((void *)(HILBASE + HIL_DATA))) + return -ENODEV; + + request_region(HILBASE+HIL_DATA, 2, "hil"); +#endif + + request_irq(HIL_IRQ, hil_interrupt, 0, "hil", hil_dev.dev_id); + + /* Turn on interrupts */ + hil_do(HIL_INTON, NULL, 0); + + /* Look for keyboards */ + hil_dev.valid = 0; /* clear any pending data */ + hil_do(HIL_READKBDSADR, NULL, 0); + + init_waitqueue_head(&hil_wait); + wait_event_interruptible_timeout(hil_wait, hil_dev.valid, 3*HZ); + if (!hil_dev.valid) { + printk(KERN_WARNING "HIL: timed out, assuming no keyboard present.\n"); + } + + c = hil_dev.c; + hil_dev.valid = 0; + if (c == 0) { + kbid = -1; + printk(KERN_WARNING "HIL: no keyboard present.\n"); + } else { + kbid = ffz(~c); + /* printk(KERN_INFO "HIL: keyboard found at id %d\n", kbid); */ + } + + /* set it to raw mode */ + c = 0; + hil_do(HIL_WRITEKBDSADR, &c, 1); + + init_input_dev(&hil_dev.dev); + + for (i = 0; i < HIL_KEYCODES_SET1_TBLSIZE; i++) + if (hphilkeyb_keycode[i] != KEY_RESERVED) + set_bit(hphilkeyb_keycode[i], hil_dev.dev.keybit); + + hil_dev.dev.evbit[0] = BIT(EV_KEY) | BIT(EV_REP); + hil_dev.dev.ledbit[0] = BIT(LED_NUML) | BIT(LED_CAPSL) | BIT(LED_SCROLLL); + hil_dev.dev.keycodemax = HIL_KEYCODES_SET1_TBLSIZE; + hil_dev.dev.keycodesize = sizeof(hphilkeyb_keycode[0]); + hil_dev.dev.keycode = hphilkeyb_keycode; + hil_dev.dev.name = "HIL keyboard"; + hil_dev.dev.phys = "hpkbd/input0"; + + hil_dev.dev.id.bustype = BUS_HIL; + hil_dev.dev.id.vendor = PCI_VENDOR_ID_HP; + hil_dev.dev.id.product = 0x0001; + hil_dev.dev.id.version = 0x0010; + + input_register_device(&hil_dev.dev); + printk(KERN_INFO "input: %s, ID %d at 0x%08lx (irq %d) found and attached\n", + hil_dev.dev.name, kbid, HILBASE, HIL_IRQ); + + return 0; +} + +#if defined(CONFIG_PARISC) +static int __init +hil_init_chip(struct parisc_device *dev) +{ + if (!dev->irq) { + printk(KERN_WARNING "HIL: IRQ not found for HIL bus at 0x%08lx\n", dev->hpa); + return -ENODEV; + } + + hil_base = dev->hpa; + hil_irq = dev->irq; + hil_dev.dev_id = dev; + + printk(KERN_INFO "Found HIL bus at 0x%08lx, IRQ %d\n", hil_base, hil_irq); + + return hil_keyb_init(); +} + +static struct parisc_device_id hil_tbl[] = { + { HPHW_FIO, HVERSION_REV_ANY_ID, HVERSION_ANY_ID, 0x00073 }, + { 0, } +}; + +MODULE_DEVICE_TABLE(parisc, hil_tbl); + +static struct parisc_driver hil_driver = { + .name = "HIL", + .id_table = hil_tbl, + .probe = hil_init_chip, +}; +#endif /* CONFIG_PARISC */ + + + + + +static int __init hil_init(void) +{ +#if defined(CONFIG_PARISC) + return register_parisc_driver(&hil_driver); +#else + return hil_keyb_init(); +#endif +} + + +static void __exit hil_exit(void) +{ + if (HIL_IRQ) { + disable_irq(HIL_IRQ); + free_irq(HIL_IRQ, hil_dev.dev_id); + } + + /* Turn off interrupts */ + hil_do(HIL_INTOFF, NULL, 0); + + input_unregister_device(&hil_dev.dev); + +#if defined(CONFIG_PARISC) + unregister_parisc_driver(&hil_driver); +#else + release_region(HILBASE+HIL_DATA, 2); +#endif +} + +module_init(hil_init); +module_exit(hil_exit); + diff --git a/drivers/input/keyboard/hpps2atkbd.h b/drivers/input/keyboard/hpps2atkbd.h new file mode 100644 index 000000000000..dc33f6945222 --- /dev/null +++ b/drivers/input/keyboard/hpps2atkbd.h @@ -0,0 +1,110 @@ +/* + * drivers/input/keyboard/hpps2atkbd.h + * + * Copyright (c) 2004 Helge Deller <deller@gmx.de> + * Copyright (c) 2002 Laurent Canet <canetl@esiee.fr> + * Copyright (c) 2002 Thibaut Varene <varenet@parisc-linux.org> + * Copyright (c) 2000 Xavier Debacker <debackex@esiee.fr> + * + * HP PS/2 AT-compatible Keyboard, found in PA/RISC Workstations & Laptops + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ + + +/* Is the keyboard an RDI PrecisionBook? */ +#ifndef CONFIG_KEYBOARD_ATKBD_RDI_KEYCODES +# define CONFLICT(x,y) x +#else +# define CONFLICT(x,y) y +#endif + +/* sadly RDI (Tadpole) decided to ship a different keyboard layout + than HP for their PS/2 laptop keyboard which leads to conflicting + keycodes between a normal HP PS/2 keyboard and a RDI Precisionbook. + HP: RDI: */ +#define C_07 CONFLICT( KEY_F12, KEY_F1 ) +#define C_11 CONFLICT( KEY_LEFTALT, KEY_LEFTCTRL ) +#define C_14 CONFLICT( KEY_LEFTCTRL, KEY_CAPSLOCK ) +#define C_58 CONFLICT( KEY_CAPSLOCK, KEY_RIGHTCTRL ) +#define C_61 CONFLICT( KEY_102ND, KEY_LEFT ) + +/* Raw SET 2 scancode table */ + +/* 00 */ KEY_RESERVED, KEY_F9, KEY_RESERVED, KEY_F5, KEY_F3, KEY_F1, KEY_F2, C_07, +/* 08 */ KEY_ESC, KEY_F10, KEY_F8, KEY_F6, KEY_F4, KEY_TAB, KEY_GRAVE, KEY_F2, +/* 10 */ KEY_RESERVED, C_11, KEY_LEFTSHIFT, KEY_RESERVED, C_14, KEY_Q, KEY_1, KEY_F3, +/* 18 */ KEY_RESERVED, KEY_LEFTALT, KEY_Z, KEY_S, KEY_A, KEY_W, KEY_2, KEY_F4, +/* 20 */ KEY_RESERVED, KEY_C, KEY_X, KEY_D, KEY_E, KEY_4, KEY_3, KEY_F5, +/* 28 */ KEY_RESERVED, KEY_SPACE, KEY_V, KEY_F, KEY_T, KEY_R, KEY_5, KEY_F6, +/* 30 */ KEY_RESERVED, KEY_N, KEY_B, KEY_H, KEY_G, KEY_Y, KEY_6, KEY_F7, +/* 38 */ KEY_RESERVED, KEY_RIGHTALT, KEY_M, KEY_J, KEY_U, KEY_7, KEY_8, KEY_F8, +/* 40 */ KEY_RESERVED, KEY_COMMA, KEY_K, KEY_I, KEY_O, KEY_0, KEY_9, KEY_F9, +/* 48 */ KEY_RESERVED, KEY_DOT, KEY_SLASH, KEY_L, KEY_SEMICOLON, KEY_P, KEY_MINUS, KEY_F10, +/* 50 */ KEY_RESERVED, KEY_RESERVED, KEY_APOSTROPHE,KEY_RESERVED, KEY_LEFTBRACE, KEY_EQUAL, KEY_F11, KEY_SYSRQ, +/* 58 */ C_58, KEY_RIGHTSHIFT,KEY_ENTER, KEY_RIGHTBRACE,KEY_BACKSLASH, KEY_BACKSLASH,KEY_F12, KEY_SCROLLLOCK, +/* 60 */ KEY_DOWN, C_61, KEY_PAUSE, KEY_UP, KEY_DELETE, KEY_END, KEY_BACKSPACE, KEY_INSERT, +/* 68 */ KEY_RESERVED, KEY_KP1, KEY_RIGHT, KEY_KP4, KEY_KP7, KEY_PAGEDOWN, KEY_HOME, KEY_PAGEUP, +/* 70 */ KEY_KP0, KEY_KPDOT, KEY_KP2, KEY_KP5, KEY_KP6, KEY_KP8, KEY_ESC, KEY_NUMLOCK, +/* 78 */ KEY_F11, KEY_KPPLUS, KEY_KP3, KEY_KPMINUS, KEY_KPASTERISK,KEY_KP9, KEY_SCROLLLOCK,KEY_102ND, +/* 80 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* 88 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* 90 */ KEY_RESERVED, KEY_RIGHTALT, 255, KEY_RESERVED, KEY_RIGHTCTRL, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* 98 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_CAPSLOCK, KEY_RESERVED, KEY_LEFTMETA, +/* a0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RIGHTMETA, +/* a8 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_COMPOSE, +/* b0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* b8 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* c0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* c8 */ KEY_RESERVED, KEY_RESERVED, KEY_KPSLASH, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* d0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* d8 */ KEY_RESERVED, KEY_RESERVED, KEY_KPENTER, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* e0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* e8 */ KEY_RESERVED, KEY_END, KEY_RESERVED, KEY_LEFT, KEY_HOME, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* f0 */ KEY_INSERT, KEY_DELETE, KEY_DOWN, KEY_RESERVED, KEY_RIGHT, KEY_UP, KEY_RESERVED, KEY_PAUSE, +/* f8 */ KEY_RESERVED, KEY_RESERVED, KEY_PAGEDOWN, KEY_RESERVED, KEY_SYSRQ, KEY_PAGEUP, KEY_RESERVED, KEY_RESERVED, + +/* These are offset for escaped keycodes: */ + +/* 00 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_F7, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* 08 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_LEFTMETA, KEY_RIGHTMETA, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* 10 */ KEY_RESERVED, KEY_RIGHTALT, KEY_RESERVED, KEY_RESERVED, KEY_RIGHTCTRL, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* 18 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* 20 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* 28 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* 30 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* 38 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* 40 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* 48 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* 50 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* 58 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* 60 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* 68 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* 70 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* 78 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* 80 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* 88 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* 90 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* 98 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* a0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* a8 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* b0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* b8 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* c0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* c8 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* d0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* d8 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* e0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* e8 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* f0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* f8 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED + +#undef CONFLICT +#undef C_07 +#undef C_11 +#undef C_14 +#undef C_58 +#undef C_61 + diff --git a/drivers/input/keyboard/lkkbd.c b/drivers/input/keyboard/lkkbd.c new file mode 100644 index 000000000000..2694ff2b5beb --- /dev/null +++ b/drivers/input/keyboard/lkkbd.c @@ -0,0 +1,752 @@ +/* + * Copyright (C) 2004 by Jan-Benedict Glaw <jbglaw@lug-owl.de> + */ + +/* + * LK keyboard driver for Linux, based on sunkbd.c (C) by Vojtech Pavlik + */ + +/* + * DEC LK201 and LK401 keyboard driver for Linux (primary for DECstations + * and VAXstations, but can also be used on any standard RS232 with an + * adaptor). + * + * DISCLAIMER: This works for _me_. If you break anything by using the + * information given below, I will _not_ be liable! + * + * RJ10 pinout: To DE9: Or DB25: + * 1 - RxD <----> Pin 3 (TxD) <-> Pin 2 (TxD) + * 2 - GND <----> Pin 5 (GND) <-> Pin 7 (GND) + * 4 - TxD <----> Pin 2 (RxD) <-> Pin 3 (RxD) + * 3 - +12V (from HDD drive connector), DON'T connect to DE9 or DB25!!! + * + * Pin numbers for DE9 and DB25 are noted on the plug (quite small:). For + * RJ10, it's like this: + * + * __=__ Hold the plug in front of you, cable downwards, + * /___/| nose is hidden behind the plug. Now, pin 1 is at + * |1234|| the left side, pin 4 at the right and 2 and 3 are + * |IIII|| in between, of course:) + * | || + * |____|/ + * || So the adaptor consists of three connected cables + * || for data transmission (RxD and TxD) and signal ground. + * Additionally, you have to get +12V from somewhere. + * Most easily, you'll get that from a floppy or HDD power connector. + * It's the yellow cable there (black is ground and red is +5V). + * + * The keyboard and all the commands it understands are documented in + * "VCB02 Video Subsystem - Technical Manual", EK-104AA-TM-001. This + * document is LK201 specific, but LK401 is mostly compatible. It comes + * up in LK201 mode and doesn't report any of the additional keys it + * has. These need to be switched on with the LK_CMD_ENABLE_LK401 + * command. You'll find this document (scanned .pdf file) on MANX, + * a search engine specific to DEC documentation. Try + * http://www.vt100.net/manx/details?pn=EK-104AA-TM-001;id=21;cp=1 + */ + +/* + * 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 either by + * email or by paper mail: + * Jan-Benedict Glaw, Lilienstraße 16, 33790 Hörste (near Halle/Westf.), + * Germany. + */ + +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/interrupt.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/serio.h> +#include <linux/workqueue.h> + +#define DRIVER_DESC "LK keyboard driver" + +MODULE_AUTHOR ("Jan-Benedict Glaw <jbglaw@lug-owl.de>"); +MODULE_DESCRIPTION (DRIVER_DESC); +MODULE_LICENSE ("GPL"); + +/* + * Known parameters: + * bell_volume + * keyclick_volume + * ctrlclick_volume + * + * Please notice that there's not yet an API to set these at runtime. + */ +static int bell_volume = 100; /* % */ +module_param (bell_volume, int, 0); +MODULE_PARM_DESC (bell_volume, "Bell volume (in %). default is 100%"); + +static int keyclick_volume = 100; /* % */ +module_param (keyclick_volume, int, 0); +MODULE_PARM_DESC (keyclick_volume, "Keyclick volume (in %), default is 100%"); + +static int ctrlclick_volume = 100; /* % */ +module_param (ctrlclick_volume, int, 0); +MODULE_PARM_DESC (ctrlclick_volume, "Ctrlclick volume (in %), default is 100%"); + +static int lk201_compose_is_alt = 0; +module_param (lk201_compose_is_alt, int, 0); +MODULE_PARM_DESC (lk201_compose_is_alt, "If set non-zero, LK201' Compose key " + "will act as an Alt key"); + + + +#undef LKKBD_DEBUG +#ifdef LKKBD_DEBUG +#define DBG(x...) printk (x) +#else +#define DBG(x...) do {} while (0) +#endif + +/* LED control */ +#define LK_LED_WAIT 0x81 +#define LK_LED_COMPOSE 0x82 +#define LK_LED_SHIFTLOCK 0x84 +#define LK_LED_SCROLLLOCK 0x88 +#define LK_CMD_LED_ON 0x13 +#define LK_CMD_LED_OFF 0x11 + +/* Mode control */ +#define LK_MODE_DOWN 0x80 +#define LK_MODE_AUTODOWN 0x82 +#define LK_MODE_UPDOWN 0x86 +#define LK_CMD_SET_MODE(mode,div) ((mode) | ((div) << 3)) + +/* Misc commands */ +#define LK_CMD_ENABLE_KEYCLICK 0x1b +#define LK_CMD_DISABLE_KEYCLICK 0x99 +#define LK_CMD_DISABLE_BELL 0xa1 +#define LK_CMD_SOUND_BELL 0xa7 +#define LK_CMD_ENABLE_BELL 0x23 +#define LK_CMD_DISABLE_CTRCLICK 0xb9 +#define LK_CMD_ENABLE_CTRCLICK 0xbb +#define LK_CMD_SET_DEFAULTS 0xd3 +#define LK_CMD_POWERCYCLE_RESET 0xfd +#define LK_CMD_ENABLE_LK401 0xe9 +#define LK_CMD_REQUEST_ID 0xab + +/* Misc responses from keyboard */ +#define LK_STUCK_KEY 0x3d +#define LK_SELFTEST_FAILED 0x3e +#define LK_ALL_KEYS_UP 0xb3 +#define LK_METRONOME 0xb4 +#define LK_OUTPUT_ERROR 0xb5 +#define LK_INPUT_ERROR 0xb6 +#define LK_KBD_LOCKED 0xb7 +#define LK_KBD_TEST_MODE_ACK 0xb8 +#define LK_PREFIX_KEY_DOWN 0xb9 +#define LK_MODE_CHANGE_ACK 0xba +#define LK_RESPONSE_RESERVED 0xbb + +#define LK_NUM_KEYCODES 256 +#define LK_NUM_IGNORE_BYTES 6 +typedef u_int16_t lk_keycode_t; + + + +static lk_keycode_t lkkbd_keycode[LK_NUM_KEYCODES] = { + [0x56] = KEY_F1, + [0x57] = KEY_F2, + [0x58] = KEY_F3, + [0x59] = KEY_F4, + [0x5a] = KEY_F5, + [0x64] = KEY_F6, + [0x65] = KEY_F7, + [0x66] = KEY_F8, + [0x67] = KEY_F9, + [0x68] = KEY_F10, + [0x71] = KEY_F11, + [0x72] = KEY_F12, + [0x73] = KEY_F13, + [0x74] = KEY_F14, + [0x7c] = KEY_F15, + [0x7d] = KEY_F16, + [0x80] = KEY_F17, + [0x81] = KEY_F18, + [0x82] = KEY_F19, + [0x83] = KEY_F20, + [0x8a] = KEY_FIND, + [0x8b] = KEY_INSERT, + [0x8c] = KEY_DELETE, + [0x8d] = KEY_SELECT, + [0x8e] = KEY_PAGEUP, + [0x8f] = KEY_PAGEDOWN, + [0x92] = KEY_KP0, + [0x94] = KEY_KPDOT, + [0x95] = KEY_KPENTER, + [0x96] = KEY_KP1, + [0x97] = KEY_KP2, + [0x98] = KEY_KP3, + [0x99] = KEY_KP4, + [0x9a] = KEY_KP5, + [0x9b] = KEY_KP6, + [0x9c] = KEY_KPCOMMA, + [0x9d] = KEY_KP7, + [0x9e] = KEY_KP8, + [0x9f] = KEY_KP9, + [0xa0] = KEY_KPMINUS, + [0xa1] = KEY_PROG1, + [0xa2] = KEY_PROG2, + [0xa3] = KEY_PROG3, + [0xa4] = KEY_PROG4, + [0xa7] = KEY_LEFT, + [0xa8] = KEY_RIGHT, + [0xa9] = KEY_DOWN, + [0xaa] = KEY_UP, + [0xab] = KEY_RIGHTSHIFT, + [0xac] = KEY_LEFTALT, + [0xad] = KEY_COMPOSE, /* Right Compose, that is. */ + [0xae] = KEY_LEFTSHIFT, /* Same as KEY_RIGHTSHIFT on LK201 */ + [0xaf] = KEY_LEFTCTRL, + [0xb0] = KEY_CAPSLOCK, + [0xb1] = KEY_COMPOSE, /* Left Compose, that is. */ + [0xb2] = KEY_RIGHTALT, + [0xbc] = KEY_BACKSPACE, + [0xbd] = KEY_ENTER, + [0xbe] = KEY_TAB, + [0xbf] = KEY_ESC, + [0xc0] = KEY_1, + [0xc1] = KEY_Q, + [0xc2] = KEY_A, + [0xc3] = KEY_Z, + [0xc5] = KEY_2, + [0xc6] = KEY_W, + [0xc7] = KEY_S, + [0xc8] = KEY_X, + [0xc9] = KEY_102ND, + [0xcb] = KEY_3, + [0xcc] = KEY_E, + [0xcd] = KEY_D, + [0xce] = KEY_C, + [0xd0] = KEY_4, + [0xd1] = KEY_R, + [0xd2] = KEY_F, + [0xd3] = KEY_V, + [0xd4] = KEY_SPACE, + [0xd6] = KEY_5, + [0xd7] = KEY_T, + [0xd8] = KEY_G, + [0xd9] = KEY_B, + [0xdb] = KEY_6, + [0xdc] = KEY_Y, + [0xdd] = KEY_H, + [0xde] = KEY_N, + [0xe0] = KEY_7, + [0xe1] = KEY_U, + [0xe2] = KEY_J, + [0xe3] = KEY_M, + [0xe5] = KEY_8, + [0xe6] = KEY_I, + [0xe7] = KEY_K, + [0xe8] = KEY_COMMA, + [0xea] = KEY_9, + [0xeb] = KEY_O, + [0xec] = KEY_L, + [0xed] = KEY_DOT, + [0xef] = KEY_0, + [0xf0] = KEY_P, + [0xf2] = KEY_SEMICOLON, + [0xf3] = KEY_SLASH, + [0xf5] = KEY_EQUAL, + [0xf6] = KEY_RIGHTBRACE, + [0xf7] = KEY_BACKSLASH, + [0xf9] = KEY_MINUS, + [0xfa] = KEY_LEFTBRACE, + [0xfb] = KEY_APOSTROPHE, +}; + +#define CHECK_LED(LED, BITS) do { \ + if (test_bit (LED, lk->dev.led)) \ + leds_on |= BITS; \ + else \ + leds_off |= BITS; \ + } while (0) + +/* + * Per-keyboard data + */ +struct lkkbd { + lk_keycode_t keycode[LK_NUM_KEYCODES]; + int ignore_bytes; + unsigned char id[LK_NUM_IGNORE_BYTES]; + struct input_dev dev; + struct serio *serio; + struct work_struct tq; + char name[64]; + char phys[32]; + char type; + int bell_volume; + int keyclick_volume; + int ctrlclick_volume; +}; + +/* + * Calculate volume parameter byte for a given volume. + */ +static unsigned char +volume_to_hw (int volume_percent) +{ + unsigned char ret = 0; + + if (volume_percent < 0) + volume_percent = 0; + if (volume_percent > 100) + volume_percent = 100; + + if (volume_percent >= 0) + ret = 7; + if (volume_percent >= 13) /* 12.5 */ + ret = 6; + if (volume_percent >= 25) + ret = 5; + if (volume_percent >= 38) /* 37.5 */ + ret = 4; + if (volume_percent >= 50) + ret = 3; + if (volume_percent >= 63) /* 62.5 */ + ret = 2; /* This is the default volume */ + if (volume_percent >= 75) + ret = 1; + if (volume_percent >= 88) /* 87.5 */ + ret = 0; + + ret |= 0x80; + + return ret; +} + +static void +lkkbd_detection_done (struct lkkbd *lk) +{ + int i; + + /* + * Reset setting for Compose key. Let Compose be KEY_COMPOSE. + */ + lk->keycode[0xb1] = KEY_COMPOSE; + + /* + * Print keyboard name and modify Compose=Alt on user's request. + */ + switch (lk->id[4]) { + case 1: + sprintf (lk->name, "DEC LK201 keyboard"); + + if (lk201_compose_is_alt) + lk->keycode[0xb1] = KEY_LEFTALT; + break; + + case 2: + sprintf (lk->name, "DEC LK401 keyboard"); + break; + + default: + sprintf (lk->name, "Unknown DEC keyboard"); + printk (KERN_ERR "lkkbd: keyboard on %s is unknown, " + "please report to Jan-Benedict Glaw " + "<jbglaw@lug-owl.de>\n", lk->phys); + printk (KERN_ERR "lkkbd: keyboard ID'ed as:"); + for (i = 0; i < LK_NUM_IGNORE_BYTES; i++) + printk (" 0x%02x", lk->id[i]); + printk ("\n"); + break; + } + printk (KERN_INFO "lkkbd: keyboard on %s identified as: %s\n", + lk->phys, lk->name); + + /* + * Report errors during keyboard boot-up. + */ + switch (lk->id[2]) { + case 0x00: + /* All okay */ + break; + + case LK_STUCK_KEY: + printk (KERN_ERR "lkkbd: Stuck key on keyboard at " + "%s\n", lk->phys); + break; + + case LK_SELFTEST_FAILED: + printk (KERN_ERR "lkkbd: Selftest failed on keyboard " + "at %s, keyboard may not work " + "properly\n", lk->phys); + break; + + default: + printk (KERN_ERR "lkkbd: Unknown error %02x on " + "keyboard at %s\n", lk->id[2], + lk->phys); + break; + } + + /* + * Try to hint user if there's a stuck key. + */ + if (lk->id[2] == LK_STUCK_KEY && lk->id[3] != 0) + printk (KERN_ERR "Scancode of stuck key is 0x%02x, keycode " + "is 0x%04x\n", lk->id[3], + lk->keycode[lk->id[3]]); + + return; +} + +/* + * lkkbd_interrupt() is called by the low level driver when a character + * is received. + */ +static irqreturn_t +lkkbd_interrupt (struct serio *serio, unsigned char data, unsigned int flags, + struct pt_regs *regs) +{ + struct lkkbd *lk = serio_get_drvdata (serio); + int i; + + DBG (KERN_INFO "Got byte 0x%02x\n", data); + + if (lk->ignore_bytes > 0) { + DBG (KERN_INFO "Ignoring a byte on %s\n", + lk->name); + lk->id[LK_NUM_IGNORE_BYTES - lk->ignore_bytes--] = data; + + if (lk->ignore_bytes == 0) + lkkbd_detection_done (lk); + + return IRQ_HANDLED; + } + + switch (data) { + case LK_ALL_KEYS_UP: + input_regs (&lk->dev, regs); + for (i = 0; i < ARRAY_SIZE (lkkbd_keycode); i++) + if (lk->keycode[i] != KEY_RESERVED) + input_report_key (&lk->dev, lk->keycode[i], 0); + input_sync (&lk->dev); + break; + case LK_METRONOME: + DBG (KERN_INFO "Got LK_METRONOME and don't " + "know how to handle...\n"); + break; + case LK_OUTPUT_ERROR: + DBG (KERN_INFO "Got LK_OUTPUT_ERROR and don't " + "know how to handle...\n"); + break; + case LK_INPUT_ERROR: + DBG (KERN_INFO "Got LK_INPUT_ERROR and don't " + "know how to handle...\n"); + break; + case LK_KBD_LOCKED: + DBG (KERN_INFO "Got LK_KBD_LOCKED and don't " + "know how to handle...\n"); + break; + case LK_KBD_TEST_MODE_ACK: + DBG (KERN_INFO "Got LK_KBD_TEST_MODE_ACK and don't " + "know how to handle...\n"); + break; + case LK_PREFIX_KEY_DOWN: + DBG (KERN_INFO "Got LK_PREFIX_KEY_DOWN and don't " + "know how to handle...\n"); + break; + case LK_MODE_CHANGE_ACK: + DBG (KERN_INFO "Got LK_MODE_CHANGE_ACK and ignored " + "it properly...\n"); + break; + case LK_RESPONSE_RESERVED: + DBG (KERN_INFO "Got LK_RESPONSE_RESERVED and don't " + "know how to handle...\n"); + break; + case 0x01: + DBG (KERN_INFO "Got 0x01, scheduling re-initialization\n"); + lk->ignore_bytes = LK_NUM_IGNORE_BYTES; + lk->id[LK_NUM_IGNORE_BYTES - lk->ignore_bytes--] = data; + schedule_work (&lk->tq); + break; + + default: + if (lk->keycode[data] != KEY_RESERVED) { + input_regs (&lk->dev, regs); + if (!test_bit (lk->keycode[data], lk->dev.key)) + input_report_key (&lk->dev, lk->keycode[data], 1); + else + input_report_key (&lk->dev, lk->keycode[data], 0); + input_sync (&lk->dev); + } else + printk (KERN_WARNING "%s: Unknown key with " + "scancode 0x%02x on %s.\n", + __FILE__, data, lk->name); + } + + return IRQ_HANDLED; +} + +/* + * lkkbd_event() handles events from the input module. + */ +static int +lkkbd_event (struct input_dev *dev, unsigned int type, unsigned int code, + int value) +{ + struct lkkbd *lk = dev->private; + unsigned char leds_on = 0; + unsigned char leds_off = 0; + + switch (type) { + case EV_LED: + CHECK_LED (LED_CAPSL, LK_LED_SHIFTLOCK); + CHECK_LED (LED_COMPOSE, LK_LED_COMPOSE); + CHECK_LED (LED_SCROLLL, LK_LED_SCROLLLOCK); + CHECK_LED (LED_SLEEP, LK_LED_WAIT); + if (leds_on != 0) { + lk->serio->write (lk->serio, LK_CMD_LED_ON); + lk->serio->write (lk->serio, leds_on); + } + if (leds_off != 0) { + lk->serio->write (lk->serio, LK_CMD_LED_OFF); + lk->serio->write (lk->serio, leds_off); + } + return 0; + + case EV_SND: + switch (code) { + case SND_CLICK: + if (value == 0) { + DBG ("%s: Deactivating key clicks\n", __FUNCTION__); + lk->serio->write (lk->serio, LK_CMD_DISABLE_KEYCLICK); + lk->serio->write (lk->serio, LK_CMD_DISABLE_CTRCLICK); + } else { + DBG ("%s: Activating key clicks\n", __FUNCTION__); + lk->serio->write (lk->serio, LK_CMD_ENABLE_KEYCLICK); + lk->serio->write (lk->serio, volume_to_hw (lk->keyclick_volume)); + lk->serio->write (lk->serio, LK_CMD_ENABLE_CTRCLICK); + lk->serio->write (lk->serio, volume_to_hw (lk->ctrlclick_volume)); + } + return 0; + + case SND_BELL: + if (value != 0) + lk->serio->write (lk->serio, LK_CMD_SOUND_BELL); + + return 0; + } + break; + + default: + printk (KERN_ERR "%s (): Got unknown type %d, code %d, value %d\n", + __FUNCTION__, type, code, value); + } + + return -1; +} + +/* + * lkkbd_reinit() sets leds and beeps to a state the computer remembers they + * were in. + */ +static void +lkkbd_reinit (void *data) +{ + struct lkkbd *lk = data; + int division; + unsigned char leds_on = 0; + unsigned char leds_off = 0; + + /* Ask for ID */ + lk->serio->write (lk->serio, LK_CMD_REQUEST_ID); + + /* Reset parameters */ + lk->serio->write (lk->serio, LK_CMD_SET_DEFAULTS); + + /* Set LEDs */ + CHECK_LED (LED_CAPSL, LK_LED_SHIFTLOCK); + CHECK_LED (LED_COMPOSE, LK_LED_COMPOSE); + CHECK_LED (LED_SCROLLL, LK_LED_SCROLLLOCK); + CHECK_LED (LED_SLEEP, LK_LED_WAIT); + if (leds_on != 0) { + lk->serio->write (lk->serio, LK_CMD_LED_ON); + lk->serio->write (lk->serio, leds_on); + } + if (leds_off != 0) { + lk->serio->write (lk->serio, LK_CMD_LED_OFF); + lk->serio->write (lk->serio, leds_off); + } + + /* + * Try to activate extended LK401 mode. This command will + * only work with a LK401 keyboard and grants access to + * LAlt, RAlt, RCompose and RShift. + */ + lk->serio->write (lk->serio, LK_CMD_ENABLE_LK401); + + /* Set all keys to UPDOWN mode */ + for (division = 1; division <= 14; division++) + lk->serio->write (lk->serio, LK_CMD_SET_MODE (LK_MODE_UPDOWN, + division)); + + /* Enable bell and set volume */ + lk->serio->write (lk->serio, LK_CMD_ENABLE_BELL); + lk->serio->write (lk->serio, volume_to_hw (lk->bell_volume)); + + /* Enable/disable keyclick (and possibly set volume) */ + if (test_bit (SND_CLICK, lk->dev.snd)) { + lk->serio->write (lk->serio, LK_CMD_ENABLE_KEYCLICK); + lk->serio->write (lk->serio, volume_to_hw (lk->keyclick_volume)); + lk->serio->write (lk->serio, LK_CMD_ENABLE_CTRCLICK); + lk->serio->write (lk->serio, volume_to_hw (lk->ctrlclick_volume)); + } else { + lk->serio->write (lk->serio, LK_CMD_DISABLE_KEYCLICK); + lk->serio->write (lk->serio, LK_CMD_DISABLE_CTRCLICK); + } + + /* Sound the bell if needed */ + if (test_bit (SND_BELL, lk->dev.snd)) + lk->serio->write (lk->serio, LK_CMD_SOUND_BELL); +} + +/* + * lkkbd_connect() probes for a LK keyboard and fills the necessary structures. + */ +static int +lkkbd_connect (struct serio *serio, struct serio_driver *drv) +{ + struct lkkbd *lk; + int i; + int err; + + if (!(lk = kmalloc (sizeof (struct lkkbd), GFP_KERNEL))) + return -ENOMEM; + + memset (lk, 0, sizeof (struct lkkbd)); + + init_input_dev (&lk->dev); + set_bit (EV_KEY, lk->dev.evbit); + set_bit (EV_LED, lk->dev.evbit); + set_bit (EV_SND, lk->dev.evbit); + set_bit (EV_REP, lk->dev.evbit); + set_bit (LED_CAPSL, lk->dev.ledbit); + set_bit (LED_SLEEP, lk->dev.ledbit); + set_bit (LED_COMPOSE, lk->dev.ledbit); + set_bit (LED_SCROLLL, lk->dev.ledbit); + set_bit (SND_BELL, lk->dev.sndbit); + set_bit (SND_CLICK, lk->dev.sndbit); + + lk->serio = serio; + + INIT_WORK (&lk->tq, lkkbd_reinit, lk); + + lk->bell_volume = bell_volume; + lk->keyclick_volume = keyclick_volume; + lk->ctrlclick_volume = ctrlclick_volume; + + lk->dev.keycode = lk->keycode; + lk->dev.keycodesize = sizeof (lk_keycode_t); + lk->dev.keycodemax = LK_NUM_KEYCODES; + + lk->dev.event = lkkbd_event; + lk->dev.private = lk; + + serio_set_drvdata (serio, lk); + + err = serio_open (serio, drv); + if (err) { + serio_set_drvdata (serio, NULL); + kfree (lk); + return err; + } + + sprintf (lk->name, "DEC LK keyboard"); + sprintf (lk->phys, "%s/input0", serio->phys); + + memcpy (lk->keycode, lkkbd_keycode, sizeof (lk_keycode_t) * LK_NUM_KEYCODES); + for (i = 0; i < LK_NUM_KEYCODES; i++) + set_bit (lk->keycode[i], lk->dev.keybit); + + lk->dev.name = lk->name; + lk->dev.phys = lk->phys; + lk->dev.id.bustype = BUS_RS232; + lk->dev.id.vendor = SERIO_LKKBD; + lk->dev.id.product = 0; + lk->dev.id.version = 0x0100; + lk->dev.dev = &serio->dev; + + input_register_device (&lk->dev); + + printk (KERN_INFO "input: %s on %s, initiating reset\n", lk->name, serio->phys); + lk->serio->write (lk->serio, LK_CMD_POWERCYCLE_RESET); + + return 0; +} + +/* + * lkkbd_disconnect() unregisters and closes behind us. + */ +static void +lkkbd_disconnect (struct serio *serio) +{ + struct lkkbd *lk = serio_get_drvdata (serio); + + input_unregister_device (&lk->dev); + serio_close (serio); + serio_set_drvdata (serio, NULL); + kfree (lk); +} + +static struct serio_device_id lkkbd_serio_ids[] = { + { + .type = SERIO_RS232, + .proto = SERIO_LKKBD, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { 0 } +}; + +MODULE_DEVICE_TABLE(serio, lkkbd_serio_ids); + +static struct serio_driver lkkbd_drv = { + .driver = { + .name = "lkkbd", + }, + .description = DRIVER_DESC, + .id_table = lkkbd_serio_ids, + .connect = lkkbd_connect, + .disconnect = lkkbd_disconnect, + .interrupt = lkkbd_interrupt, +}; + +/* + * The functions for insering/removing us as a module. + */ +static int __init +lkkbd_init (void) +{ + serio_register_driver(&lkkbd_drv); + return 0; +} + +static void __exit +lkkbd_exit (void) +{ + serio_unregister_driver(&lkkbd_drv); +} + +module_init (lkkbd_init); +module_exit (lkkbd_exit); + diff --git a/drivers/input/keyboard/locomokbd.c b/drivers/input/keyboard/locomokbd.c new file mode 100644 index 000000000000..d3e9dd6a13cd --- /dev/null +++ b/drivers/input/keyboard/locomokbd.c @@ -0,0 +1,309 @@ +/* + * Copyright (c) 2005 John Lenz + * + * Based on from xtkbd.c + */ + +/* + * LoCoMo keyboard driver for Linux/ARM + */ + +/* + * 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 + * + */ + +#include <linux/config.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/ioport.h> + +#include <asm/hardware/locomo.h> +#include <asm/irq.h> + +MODULE_AUTHOR("John Lenz <lenz@cs.wisc.edu>"); +MODULE_DESCRIPTION("LoCoMo keyboard driver"); +MODULE_LICENSE("GPL"); + +#define LOCOMOKBD_NUMKEYS 128 + +#define KEY_ACTIVITY KEY_F16 +#define KEY_CONTACT KEY_F18 +#define KEY_CENTER KEY_F15 + +static unsigned char locomokbd_keycode[LOCOMOKBD_NUMKEYS] = { + 0, KEY_ESC, KEY_ACTIVITY, 0, 0, 0, 0, 0, 0, 0, /* 0 - 9 */ + 0, 0, 0, 0, 0, 0, 0, KEY_MENU, KEY_HOME, KEY_CONTACT, /* 10 - 19 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 20 - 29 */ + 0, 0, 0, KEY_CENTER, 0, KEY_MAIL, 0, 0, 0, 0, /* 30 - 39 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, KEY_RIGHT, /* 40 - 49 */ + KEY_UP, KEY_LEFT, 0, 0, KEY_P, 0, KEY_O, KEY_I, KEY_Y, KEY_T, /* 50 - 59 */ + KEY_E, KEY_W, 0, 0, 0, 0, KEY_DOWN, KEY_ENTER, 0, 0, /* 60 - 69 */ + KEY_BACKSPACE, 0, KEY_L, KEY_U, KEY_H, KEY_R, KEY_D, KEY_Q, 0, 0, /* 70 - 79 */ + 0, 0, 0, 0, 0, 0, KEY_ENTER, KEY_RIGHTSHIFT, KEY_K, KEY_J, /* 80 - 89 */ + KEY_G, KEY_F, KEY_X, KEY_S, 0, 0, 0, 0, 0, 0, /* 90 - 99 */ + 0, 0, KEY_DOT, 0, KEY_COMMA, KEY_N, KEY_B, KEY_C, KEY_Z, KEY_A, /* 100 - 109 */ + KEY_LEFTSHIFT, KEY_TAB, KEY_LEFTCTRL, 0, 0, 0, 0, 0, 0, 0, /* 110 - 119 */ + KEY_M, KEY_SPACE, KEY_V, KEY_APOSTROPHE, KEY_SLASH, 0, 0, 0 /* 120 - 128 */ +}; + +#define KB_ROWS 16 +#define KB_COLS 8 +#define KB_ROWMASK(r) (1 << (r)) +#define SCANCODE(c,r) ( ((c)<<4) + (r) + 1 ) +#define NR_SCANCODES 128 + +#define KB_DELAY 8 +#define SCAN_INTERVAL (HZ/10) +#define LOCOMOKBD_PRESSED 1 + +struct locomokbd { + unsigned char keycode[LOCOMOKBD_NUMKEYS]; + struct input_dev input; + char phys[32]; + + struct locomo_dev *ldev; + unsigned long base; + spinlock_t lock; + + struct timer_list timer; +}; + +/* helper functions for reading the keyboard matrix */ +static inline void locomokbd_charge_all(unsigned long membase) +{ + locomo_writel(0x00FF, membase + LOCOMO_KSC); +} + +static inline void locomokbd_activate_all(unsigned long membase) +{ + unsigned long r; + + locomo_writel(0, membase + LOCOMO_KSC); + r = locomo_readl(membase + LOCOMO_KIC); + r &= 0xFEFF; + locomo_writel(r, membase + LOCOMO_KIC); +} + +static inline void locomokbd_activate_col(unsigned long membase, int col) +{ + unsigned short nset; + unsigned short nbset; + + nset = 0xFF & ~(1 << col); + nbset = (nset << 8) + nset; + locomo_writel(nbset, membase + LOCOMO_KSC); +} + +static inline void locomokbd_reset_col(unsigned long membase, int col) +{ + unsigned short nbset; + + nbset = ((0xFF & ~(1 << col)) << 8) + 0xFF; + locomo_writel(nbset, membase + LOCOMO_KSC); +} + +/* + * The LoCoMo keyboard only generates interrupts when a key is pressed. + * So when a key is pressed, we enable a timer. This timer scans the + * keyboard, and this is how we detect when the key is released. + */ + +/* Scan the hardware keyboard and push any changes up through the input layer */ +static void locomokbd_scankeyboard(struct locomokbd *locomokbd, struct pt_regs *regs) +{ + unsigned int row, col, rowd, scancode; + unsigned long flags; + unsigned int num_pressed; + unsigned long membase = locomokbd->base; + + spin_lock_irqsave(&locomokbd->lock, flags); + + if (regs) + input_regs(&locomokbd->input, regs); + + locomokbd_charge_all(membase); + + num_pressed = 0; + for (col = 0; col < KB_COLS; col++) { + + locomokbd_activate_col(membase, col); + udelay(KB_DELAY); + + rowd = ~locomo_readl(membase + LOCOMO_KIB); + for (row = 0; row < KB_ROWS; row++ ) { + scancode = SCANCODE(col, row); + if (rowd & KB_ROWMASK(row)) { + num_pressed += 1; + input_report_key(&locomokbd->input, locomokbd->keycode[scancode], 1); + } else { + input_report_key(&locomokbd->input, locomokbd->keycode[scancode], 0); + } + } + locomokbd_reset_col(membase, col); + } + locomokbd_activate_all(membase); + + input_sync(&locomokbd->input); + + /* if any keys are pressed, enable the timer */ + if (num_pressed) + mod_timer(&locomokbd->timer, jiffies + SCAN_INTERVAL); + + spin_unlock_irqrestore(&locomokbd->lock, flags); +} + +/* + * LoCoMo keyboard interrupt handler. + */ +static irqreturn_t locomokbd_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + struct locomokbd *locomokbd = dev_id; + /** wait chattering delay **/ + udelay(100); + + locomokbd_scankeyboard(locomokbd, regs); + + return IRQ_HANDLED; +} + +/* + * LoCoMo timer checking for released keys + */ +static void locomokbd_timer_callback(unsigned long data) +{ + struct locomokbd *locomokbd = (struct locomokbd *) data; + locomokbd_scankeyboard(locomokbd, NULL); +} + +static int locomokbd_probe(struct locomo_dev *dev) +{ + struct locomokbd *locomokbd; + int i, ret; + + locomokbd = kmalloc(sizeof(struct locomokbd), GFP_KERNEL); + if (!locomokbd) + return -ENOMEM; + + memset(locomokbd, 0, sizeof(struct locomokbd)); + + /* try and claim memory region */ + if (!request_mem_region((unsigned long) dev->mapbase, + dev->length, + LOCOMO_DRIVER_NAME(dev))) { + ret = -EBUSY; + printk(KERN_ERR "locomokbd: Can't acquire access to io memory for keyboard\n"); + goto free; + } + + locomokbd->ldev = dev; + locomo_set_drvdata(dev, locomokbd); + + locomokbd->base = (unsigned long) dev->mapbase; + + spin_lock_init(&locomokbd->lock); + + init_timer(&locomokbd->timer); + locomokbd->timer.function = locomokbd_timer_callback; + locomokbd->timer.data = (unsigned long) locomokbd; + + locomokbd->input.evbit[0] = BIT(EV_KEY) | BIT(EV_REP); + + init_input_dev(&locomokbd->input); + locomokbd->input.keycode = locomokbd->keycode; + locomokbd->input.keycodesize = sizeof(unsigned char); + locomokbd->input.keycodemax = ARRAY_SIZE(locomokbd_keycode); + locomokbd->input.private = locomokbd; + + memcpy(locomokbd->keycode, locomokbd_keycode, sizeof(locomokbd->keycode)); + for (i = 0; i < LOCOMOKBD_NUMKEYS; i++) + set_bit(locomokbd->keycode[i], locomokbd->input.keybit); + clear_bit(0, locomokbd->input.keybit); + + strcpy(locomokbd->phys, "locomokbd/input0"); + + locomokbd->input.name = "LoCoMo keyboard"; + locomokbd->input.phys = locomokbd->phys; + locomokbd->input.id.bustype = BUS_XTKBD; + locomokbd->input.id.vendor = 0x0001; + locomokbd->input.id.product = 0x0001; + locomokbd->input.id.version = 0x0100; + + /* attempt to get the interrupt */ + ret = request_irq(dev->irq[0], locomokbd_interrupt, 0, "locomokbd", locomokbd); + if (ret) { + printk(KERN_ERR "locomokbd: Can't get irq for keyboard\n"); + goto out; + } + + input_register_device(&locomokbd->input); + + printk(KERN_INFO "input: LoCoMo keyboard on locomokbd\n"); + + return 0; + +out: + release_mem_region((unsigned long) dev->mapbase, dev->length); + locomo_set_drvdata(dev, NULL); +free: + kfree(locomokbd); + + return ret; +} + +static int locomokbd_remove(struct locomo_dev *dev) +{ + struct locomokbd *locomokbd = locomo_get_drvdata(dev); + + free_irq(dev->irq[0], locomokbd); + + del_timer_sync(&locomokbd->timer); + + input_unregister_device(&locomokbd->input); + locomo_set_drvdata(dev, NULL); + + release_mem_region((unsigned long) dev->mapbase, dev->length); + + kfree(locomokbd); + + return 0; +} + +static struct locomo_driver keyboard_driver = { + .drv = { + .name = "locomokbd" + }, + .devid = LOCOMO_DEVID_KEYBOARD, + .probe = locomokbd_probe, + .remove = locomokbd_remove, +}; + +static int __init locomokbd_init(void) +{ + return locomo_driver_register(&keyboard_driver); +} + +static void __exit locomokbd_exit(void) +{ + locomo_driver_unregister(&keyboard_driver); +} + +module_init(locomokbd_init); +module_exit(locomokbd_exit); diff --git a/drivers/input/keyboard/maple_keyb.c b/drivers/input/keyboard/maple_keyb.c new file mode 100644 index 000000000000..859ed771ee0a --- /dev/null +++ b/drivers/input/keyboard/maple_keyb.c @@ -0,0 +1,190 @@ +/* + * $Id: maple_keyb.c,v 1.4 2004/03/22 01:18:15 lethal Exp $ + * SEGA Dreamcast keyboard driver + * Based on drivers/usb/usbkbd.c + */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/timer.h> +#include <linux/maple.h> + +MODULE_AUTHOR("YAEGASHI Takeshi <t@keshi.org>"); +MODULE_DESCRIPTION("SEGA Dreamcast keyboard driver"); +MODULE_LICENSE("GPL"); + +static unsigned char dc_kbd_keycode[256] = { + 0, 0, 0, 0, 30, 48, 46, 32, 18, 33, 34, 35, 23, 36, 37, 38, + 50, 49, 24, 25, 16, 19, 31, 20, 22, 47, 17, 45, 21, 44, 2, 3, + 4, 5, 6, 7, 8, 9, 10, 11, 28, 1, 14, 15, 57, 12, 13, 26, + 27, 43, 43, 39, 40, 41, 51, 52, 53, 58, 59, 60, 61, 62, 63, 64, + 65, 66, 67, 68, 87, 88, 99, 70,119,110,102,104,111,107,109,106, + 105,108,103, 69, 98, 55, 74, 78, 96, 79, 80, 81, 75, 76, 77, 71, + 72, 73, 82, 83, 86,127,116,117,183,184,185,186,187,188,189,190, + 191,192,193,194,134,138,130,132,128,129,131,137,133,135,136,113, + 115,114, 0, 0, 0,121, 0, 89, 93,124, 92, 94, 95, 0, 0, 0, + 122,123, 90, 91, 85, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 29, 42, 56,125, 97, 54,100,126,164,166,165,163,161,115,114,113, + 150,158,159,128,136,177,178,176,142,152,173,140 +}; + + +struct dc_kbd { + struct input_dev dev; + unsigned char new[8]; + unsigned char old[8]; + int open; +}; + + +static void dc_scan_kbd(struct dc_kbd *kbd) +{ + int i; + struct input_dev *dev = &kbd->dev; + + for(i=0; i<8; i++) + input_report_key(dev, + dc_kbd_keycode[i+224], + (kbd->new[0]>>i)&1); + + for(i=2; i<8; i++) { + + if(kbd->old[i]>3&&memscan(kbd->new+2, kbd->old[i], 6)==NULL) { + if(dc_kbd_keycode[kbd->old[i]]) + input_report_key(dev, + dc_kbd_keycode[kbd->old[i]], + 0); + else + printk("Unknown key (scancode %#x) released.", + kbd->old[i]); + } + + if(kbd->new[i]>3&&memscan(kbd->old+2, kbd->new[i], 6)!=NULL) { + if(dc_kbd_keycode[kbd->new[i]]) + input_report_key(dev, + dc_kbd_keycode[kbd->new[i]], + 1); + else + printk("Unknown key (scancode %#x) pressed.", + kbd->new[i]); + } + } + + input_sync(dev); + + memcpy(kbd->old, kbd->new, 8); +} + + +static void dc_kbd_callback(struct mapleq *mq) +{ + struct maple_device *mapledev = mq->dev; + struct dc_kbd *kbd = mapledev->private_data; + unsigned long *buf = mq->recvbuf; + + if (buf[1] == mapledev->function) { + memcpy(kbd->new, buf+2, 8); + dc_scan_kbd(kbd); + } +} + + +static int dc_kbd_open(struct input_dev *dev) +{ + struct dc_kbd *kbd = dev->private; + kbd->open++; + return 0; +} + + +static void dc_kbd_close(struct input_dev *dev) +{ + struct dc_kbd *kbd = dev->private; + kbd->open--; +} + + +static int dc_kbd_connect(struct maple_device *dev) +{ + int i; + unsigned long data = be32_to_cpu(dev->devinfo.function_data[0]); + struct dc_kbd *kbd; + + if (!(kbd = kmalloc(sizeof(struct dc_kbd), GFP_KERNEL))) + return -1; + memset(kbd, 0, sizeof(struct dc_kbd)); + + dev->private_data = kbd; + + kbd->dev.evbit[0] = BIT(EV_KEY) | BIT(EV_REP); + + init_input_dev(&kbd->dev); + + for (i=0; i<255; i++) + set_bit(dc_kbd_keycode[i], kbd->dev.keybit); + + clear_bit(0, kbd->dev.keybit); + + kbd->dev.private = kbd; + kbd->dev.open = dc_kbd_open; + kbd->dev.close = dc_kbd_close; + kbd->dev.event = NULL; + + kbd->dev.name = dev->product_name; + kbd->dev.id.bustype = BUS_MAPLE; + + input_register_device(&kbd->dev); + + maple_getcond_callback(dev, dc_kbd_callback, 1, MAPLE_FUNC_KEYBOARD); + + printk(KERN_INFO "input: keyboard(0x%lx): %s\n", data, kbd->dev.name); + + return 0; +} + + +static void dc_kbd_disconnect(struct maple_device *dev) +{ + struct dc_kbd *kbd = dev->private_data; + + input_unregister_device(&kbd->dev); + kfree(kbd); +} + + +static struct maple_driver dc_kbd_driver = { + .function = MAPLE_FUNC_KEYBOARD, + .name = "Dreamcast keyboard", + .connect = dc_kbd_connect, + .disconnect = dc_kbd_disconnect, +}; + + +static int __init dc_kbd_init(void) +{ + maple_register_driver(&dc_kbd_driver); + return 0; +} + + +static void __exit dc_kbd_exit(void) +{ + maple_unregister_driver(&dc_kbd_driver); +} + + +module_init(dc_kbd_init); +module_exit(dc_kbd_exit); + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/input/keyboard/newtonkbd.c b/drivers/input/keyboard/newtonkbd.c new file mode 100644 index 000000000000..2e8ce1613eec --- /dev/null +++ b/drivers/input/keyboard/newtonkbd.c @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2000 Justin Cormack + */ + +/* + * Newton keyboard driver for Linux + */ + +/* + * 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 either by + * e-mail - mail your message to <j.cormack@doc.ic.ac.uk>, or by paper mail: + * Justin Cormack, 68 Dartmouth Park Road, London NW5 1SN, UK. + */ + +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/input.h> +#include <linux/init.h> +#include <linux/serio.h> + +#define DRIVER_DESC "Newton keyboard driver" + +MODULE_AUTHOR("Justin Cormack <j.cormack@doc.ic.ac.uk>"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +#define NKBD_KEY 0x7f +#define NKBD_PRESS 0x80 + +static unsigned char nkbd_keycode[128] = { + KEY_A, KEY_S, KEY_D, KEY_F, KEY_H, KEY_G, KEY_Z, KEY_X, + KEY_C, KEY_V, 0, KEY_B, KEY_Q, KEY_W, KEY_E, KEY_R, + KEY_Y, KEY_T, KEY_1, KEY_2, KEY_3, KEY_4, KEY_6, KEY_5, + KEY_EQUAL, KEY_9, KEY_7, KEY_MINUS, KEY_8, KEY_0, KEY_RIGHTBRACE, KEY_O, + KEY_U, KEY_LEFTBRACE, KEY_I, KEY_P, KEY_ENTER, KEY_L, KEY_J, KEY_APOSTROPHE, + KEY_K, KEY_SEMICOLON, KEY_BACKSLASH, KEY_COMMA, KEY_SLASH, KEY_N, KEY_M, KEY_DOT, + KEY_TAB, KEY_SPACE, KEY_GRAVE, KEY_DELETE, 0, 0, 0, KEY_LEFTMETA, + KEY_LEFTSHIFT, KEY_CAPSLOCK, KEY_LEFTALT, KEY_LEFTCTRL, KEY_RIGHTSHIFT, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + KEY_LEFT, KEY_RIGHT, KEY_DOWN, KEY_UP, 0 +}; + +static char *nkbd_name = "Newton Keyboard"; + +struct nkbd { + unsigned char keycode[128]; + struct input_dev dev; + struct serio *serio; + char phys[32]; +}; + +static irqreturn_t nkbd_interrupt(struct serio *serio, + unsigned char data, unsigned int flags, struct pt_regs *regs) +{ + struct nkbd *nkbd = serio_get_drvdata(serio); + + /* invalid scan codes are probably the init sequence, so we ignore them */ + if (nkbd->keycode[data & NKBD_KEY]) { + input_regs(&nkbd->dev, regs); + input_report_key(&nkbd->dev, nkbd->keycode[data & NKBD_KEY], data & NKBD_PRESS); + input_sync(&nkbd->dev); + } + + else if (data == 0xe7) /* end of init sequence */ + printk(KERN_INFO "input: %s on %s\n", nkbd_name, serio->phys); + return IRQ_HANDLED; + +} + +static int nkbd_connect(struct serio *serio, struct serio_driver *drv) +{ + struct nkbd *nkbd; + int i; + int err; + + if (!(nkbd = kmalloc(sizeof(struct nkbd), GFP_KERNEL))) + return -ENOMEM; + + memset(nkbd, 0, sizeof(struct nkbd)); + + nkbd->dev.evbit[0] = BIT(EV_KEY) | BIT(EV_REP); + + nkbd->serio = serio; + + init_input_dev(&nkbd->dev); + nkbd->dev.keycode = nkbd->keycode; + nkbd->dev.keycodesize = sizeof(unsigned char); + nkbd->dev.keycodemax = ARRAY_SIZE(nkbd_keycode); + nkbd->dev.private = nkbd; + + serio_set_drvdata(serio, nkbd); + + err = serio_open(serio, drv); + if (err) { + serio_set_drvdata(serio, NULL); + kfree(nkbd); + return err; + } + + memcpy(nkbd->keycode, nkbd_keycode, sizeof(nkbd->keycode)); + for (i = 0; i < 128; i++) + set_bit(nkbd->keycode[i], nkbd->dev.keybit); + clear_bit(0, nkbd->dev.keybit); + + sprintf(nkbd->phys, "%s/input0", serio->phys); + + nkbd->dev.name = nkbd_name; + nkbd->dev.phys = nkbd->phys; + nkbd->dev.id.bustype = BUS_RS232; + nkbd->dev.id.vendor = SERIO_NEWTON; + nkbd->dev.id.product = 0x0001; + nkbd->dev.id.version = 0x0100; + nkbd->dev.dev = &serio->dev; + + input_register_device(&nkbd->dev); + + printk(KERN_INFO "input: %s on %s\n", nkbd_name, serio->phys); + + return 0; +} + +static void nkbd_disconnect(struct serio *serio) +{ + struct nkbd *nkbd = serio_get_drvdata(serio); + + input_unregister_device(&nkbd->dev); + serio_close(serio); + serio_set_drvdata(serio, NULL); + kfree(nkbd); +} + +static struct serio_device_id nkbd_serio_ids[] = { + { + .type = SERIO_RS232, + .proto = SERIO_NEWTON, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { 0 } +}; + +MODULE_DEVICE_TABLE(serio, nkbd_serio_ids); + +static struct serio_driver nkbd_drv = { + .driver = { + .name = "newtonkbd", + }, + .description = DRIVER_DESC, + .id_table = nkbd_serio_ids, + .interrupt = nkbd_interrupt, + .connect = nkbd_connect, + .disconnect = nkbd_disconnect, +}; + +static int __init nkbd_init(void) +{ + serio_register_driver(&nkbd_drv); + return 0; +} + +static void __exit nkbd_exit(void) +{ + serio_unregister_driver(&nkbd_drv); +} + +module_init(nkbd_init); +module_exit(nkbd_exit); diff --git a/drivers/input/keyboard/sunkbd.c b/drivers/input/keyboard/sunkbd.c new file mode 100644 index 000000000000..596964ceb96d --- /dev/null +++ b/drivers/input/keyboard/sunkbd.c @@ -0,0 +1,353 @@ +/* + * $Id: sunkbd.c,v 1.14 2001/09/25 10:12:07 vojtech Exp $ + * + * Copyright (c) 1999-2001 Vojtech Pavlik + */ + +/* + * Sun keyboard driver for Linux + */ + +/* + * 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 either by + * e-mail - mail your message to <vojtech@ucw.cz>, or by paper mail: + * Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic + */ + +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/serio.h> +#include <linux/workqueue.h> + +#define DRIVER_DESC "Sun keyboard driver" + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +static unsigned char sunkbd_keycode[128] = { + 0,128,114,129,115, 59, 60, 68, 61, 87, 62, 88, 63,100, 64, 0, + 65, 66, 67, 56,103,119, 99, 70,105,130,131,108,106, 1, 2, 3, + 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 41, 14,110,113, 98, 55, + 116,132, 83,133,102, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, + 26, 27,111,127, 71, 72, 73, 74,134,135,107, 0, 29, 30, 31, 32, + 33, 34, 35, 36, 37, 38, 39, 40, 43, 28, 96, 75, 76, 77, 82,136, + 104,137, 69, 42, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54,101, + 79, 80, 81, 0, 0, 0,138, 58,125, 57,126,109, 86, 78 +}; + +#define SUNKBD_CMD_RESET 0x1 +#define SUNKBD_CMD_BELLON 0x2 +#define SUNKBD_CMD_BELLOFF 0x3 +#define SUNKBD_CMD_CLICK 0xa +#define SUNKBD_CMD_NOCLICK 0xb +#define SUNKBD_CMD_SETLED 0xe +#define SUNKBD_CMD_LAYOUT 0xf + +#define SUNKBD_RET_RESET 0xff +#define SUNKBD_RET_ALLUP 0x7f +#define SUNKBD_RET_LAYOUT 0xfe + +#define SUNKBD_LAYOUT_5_MASK 0x20 +#define SUNKBD_RELEASE 0x80 +#define SUNKBD_KEY 0x7f + +/* + * Per-keyboard data. + */ + +struct sunkbd { + unsigned char keycode[128]; + struct input_dev dev; + struct serio *serio; + struct work_struct tq; + wait_queue_head_t wait; + char name[64]; + char phys[32]; + char type; + volatile s8 reset; + volatile s8 layout; +}; + +/* + * sunkbd_interrupt() is called by the low level driver when a character + * is received. + */ + +static irqreturn_t sunkbd_interrupt(struct serio *serio, + unsigned char data, unsigned int flags, struct pt_regs *regs) +{ + struct sunkbd* sunkbd = serio_get_drvdata(serio); + + if (sunkbd->reset <= -1) { /* If cp[i] is 0xff, sunkbd->reset will stay -1. */ + sunkbd->reset = data; /* The keyboard sends 0xff 0xff 0xID on powerup */ + wake_up_interruptible(&sunkbd->wait); + goto out; + } + + if (sunkbd->layout == -1) { + sunkbd->layout = data; + wake_up_interruptible(&sunkbd->wait); + goto out; + } + + switch (data) { + + case SUNKBD_RET_RESET: + schedule_work(&sunkbd->tq); + sunkbd->reset = -1; + break; + + case SUNKBD_RET_LAYOUT: + sunkbd->layout = -1; + break; + + case SUNKBD_RET_ALLUP: /* All keys released */ + break; + + default: + if (sunkbd->keycode[data & SUNKBD_KEY]) { + input_regs(&sunkbd->dev, regs); + input_report_key(&sunkbd->dev, sunkbd->keycode[data & SUNKBD_KEY], !(data & SUNKBD_RELEASE)); + input_sync(&sunkbd->dev); + } else { + printk(KERN_WARNING "sunkbd.c: Unknown key (scancode %#x) %s.\n", + data & SUNKBD_KEY, data & SUNKBD_RELEASE ? "released" : "pressed"); + } + } +out: + return IRQ_HANDLED; +} + +/* + * sunkbd_event() handles events from the input module. + */ + +static int sunkbd_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) +{ + struct sunkbd *sunkbd = dev->private; + + switch (type) { + + case EV_LED: + + sunkbd->serio->write(sunkbd->serio, SUNKBD_CMD_SETLED); + sunkbd->serio->write(sunkbd->serio, + (!!test_bit(LED_CAPSL, dev->led) << 3) | (!!test_bit(LED_SCROLLL, dev->led) << 2) | + (!!test_bit(LED_COMPOSE, dev->led) << 1) | !!test_bit(LED_NUML, dev->led)); + return 0; + + case EV_SND: + + switch (code) { + + case SND_CLICK: + sunkbd->serio->write(sunkbd->serio, SUNKBD_CMD_NOCLICK - value); + return 0; + + case SND_BELL: + sunkbd->serio->write(sunkbd->serio, SUNKBD_CMD_BELLOFF - value); + return 0; + } + + break; + } + + return -1; +} + +/* + * sunkbd_initialize() checks for a Sun keyboard attached, and determines + * its type. + */ + +static int sunkbd_initialize(struct sunkbd *sunkbd) +{ + sunkbd->reset = -2; + sunkbd->serio->write(sunkbd->serio, SUNKBD_CMD_RESET); + wait_event_interruptible_timeout(sunkbd->wait, sunkbd->reset >= 0, HZ); + if (sunkbd->reset <0) + return -1; + + sunkbd->type = sunkbd->reset; + + if (sunkbd->type == 4) { /* Type 4 keyboard */ + sunkbd->layout = -2; + sunkbd->serio->write(sunkbd->serio, SUNKBD_CMD_LAYOUT); + wait_event_interruptible_timeout(sunkbd->wait, sunkbd->layout >= 0, HZ/4); + if (sunkbd->layout < 0) return -1; + if (sunkbd->layout & SUNKBD_LAYOUT_5_MASK) sunkbd->type = 5; + } + + return 0; +} + +/* + * sunkbd_reinit() sets leds and beeps to a state the computer remembers they + * were in. + */ + +static void sunkbd_reinit(void *data) +{ + struct sunkbd *sunkbd = data; + + wait_event_interruptible_timeout(sunkbd->wait, sunkbd->reset >= 0, HZ); + + sunkbd->serio->write(sunkbd->serio, SUNKBD_CMD_SETLED); + sunkbd->serio->write(sunkbd->serio, + (!!test_bit(LED_CAPSL, sunkbd->dev.led) << 3) | (!!test_bit(LED_SCROLLL, sunkbd->dev.led) << 2) | + (!!test_bit(LED_COMPOSE, sunkbd->dev.led) << 1) | !!test_bit(LED_NUML, sunkbd->dev.led)); + sunkbd->serio->write(sunkbd->serio, SUNKBD_CMD_NOCLICK - !!test_bit(SND_CLICK, sunkbd->dev.snd)); + sunkbd->serio->write(sunkbd->serio, SUNKBD_CMD_BELLOFF - !!test_bit(SND_BELL, sunkbd->dev.snd)); +} + +/* + * sunkbd_connect() probes for a Sun keyboard and fills the necessary structures. + */ + +static int sunkbd_connect(struct serio *serio, struct serio_driver *drv) +{ + struct sunkbd *sunkbd; + int i; + int err; + + if (!(sunkbd = kmalloc(sizeof(struct sunkbd), GFP_KERNEL))) + return -ENOMEM; + + memset(sunkbd, 0, sizeof(struct sunkbd)); + + init_input_dev(&sunkbd->dev); + init_waitqueue_head(&sunkbd->wait); + + sunkbd->dev.evbit[0] = BIT(EV_KEY) | BIT(EV_LED) | BIT(EV_SND) | BIT(EV_REP); + sunkbd->dev.ledbit[0] = BIT(LED_CAPSL) | BIT(LED_COMPOSE) | BIT(LED_SCROLLL) | BIT(LED_NUML); + sunkbd->dev.sndbit[0] = BIT(SND_CLICK) | BIT(SND_BELL); + + sunkbd->serio = serio; + + INIT_WORK(&sunkbd->tq, sunkbd_reinit, sunkbd); + + sunkbd->dev.keycode = sunkbd->keycode; + sunkbd->dev.keycodesize = sizeof(unsigned char); + sunkbd->dev.keycodemax = ARRAY_SIZE(sunkbd_keycode); + + sunkbd->dev.event = sunkbd_event; + sunkbd->dev.private = sunkbd; + + serio_set_drvdata(serio, sunkbd); + + err = serio_open(serio, drv); + if (err) { + serio_set_drvdata(serio, NULL); + kfree(sunkbd); + return err; + } + + if (sunkbd_initialize(sunkbd) < 0) { + serio_close(serio); + serio_set_drvdata(serio, NULL); + kfree(sunkbd); + return -ENODEV; + } + + sprintf(sunkbd->name, "Sun Type %d keyboard", sunkbd->type); + + memcpy(sunkbd->keycode, sunkbd_keycode, sizeof(sunkbd->keycode)); + for (i = 0; i < 128; i++) + set_bit(sunkbd->keycode[i], sunkbd->dev.keybit); + clear_bit(0, sunkbd->dev.keybit); + + sprintf(sunkbd->phys, "%s/input0", serio->phys); + + sunkbd->dev.name = sunkbd->name; + sunkbd->dev.phys = sunkbd->phys; + sunkbd->dev.id.bustype = BUS_RS232; + sunkbd->dev.id.vendor = SERIO_SUNKBD; + sunkbd->dev.id.product = sunkbd->type; + sunkbd->dev.id.version = 0x0100; + sunkbd->dev.dev = &serio->dev; + + input_register_device(&sunkbd->dev); + + printk(KERN_INFO "input: %s on %s\n", sunkbd->name, serio->phys); + + return 0; +} + +/* + * sunkbd_disconnect() unregisters and closes behind us. + */ + +static void sunkbd_disconnect(struct serio *serio) +{ + struct sunkbd *sunkbd = serio_get_drvdata(serio); + input_unregister_device(&sunkbd->dev); + serio_close(serio); + serio_set_drvdata(serio, NULL); + kfree(sunkbd); +} + +static struct serio_device_id sunkbd_serio_ids[] = { + { + .type = SERIO_RS232, + .proto = SERIO_SUNKBD, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { + .type = SERIO_RS232, + .proto = SERIO_UNKNOWN, /* sunkbd does probe */ + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { 0 } +}; + +MODULE_DEVICE_TABLE(serio, sunkbd_serio_ids); + +static struct serio_driver sunkbd_drv = { + .driver = { + .name = "sunkbd", + }, + .description = DRIVER_DESC, + .id_table = sunkbd_serio_ids, + .interrupt = sunkbd_interrupt, + .connect = sunkbd_connect, + .disconnect = sunkbd_disconnect, +}; + +/* + * The functions for insering/removing us as a module. + */ + +static int __init sunkbd_init(void) +{ + serio_register_driver(&sunkbd_drv); + return 0; +} + +static void __exit sunkbd_exit(void) +{ + serio_unregister_driver(&sunkbd_drv); +} + +module_init(sunkbd_init); +module_exit(sunkbd_exit); diff --git a/drivers/input/keyboard/xtkbd.c b/drivers/input/keyboard/xtkbd.c new file mode 100644 index 000000000000..19eaec7789d1 --- /dev/null +++ b/drivers/input/keyboard/xtkbd.c @@ -0,0 +1,188 @@ +/* + * $Id: xtkbd.c,v 1.11 2001/09/25 10:12:07 vojtech Exp $ + * + * Copyright (c) 1999-2001 Vojtech Pavlik + */ + +/* + * XT keyboard driver for Linux + */ + +/* + * 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 either by + * e-mail - mail your message to <vojtech@ucw.cz>, or by paper mail: + * Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic + */ + +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/input.h> +#include <linux/init.h> +#include <linux/serio.h> + +#define DRIVER_DESC "XT keyboard driver" + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +#define XTKBD_EMUL0 0xe0 +#define XTKBD_EMUL1 0xe1 +#define XTKBD_KEY 0x7f +#define XTKBD_RELEASE 0x80 + +static unsigned char xtkbd_keycode[256] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, + 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, + 80, 81, 82, 83, 0, 0, 0, 87, 88, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 87, 88, 0, 0, 0, 0,110,111,103,108,105, + 106 +}; + +static char *xtkbd_name = "XT Keyboard"; + +struct xtkbd { + unsigned char keycode[256]; + struct input_dev dev; + struct serio *serio; + char phys[32]; +}; + +static irqreturn_t xtkbd_interrupt(struct serio *serio, + unsigned char data, unsigned int flags, struct pt_regs *regs) +{ + struct xtkbd *xtkbd = serio_get_drvdata(serio); + + switch (data) { + case XTKBD_EMUL0: + case XTKBD_EMUL1: + break; + default: + + if (xtkbd->keycode[data & XTKBD_KEY]) { + input_regs(&xtkbd->dev, regs); + input_report_key(&xtkbd->dev, xtkbd->keycode[data & XTKBD_KEY], !(data & XTKBD_RELEASE)); + input_sync(&xtkbd->dev); + } else { + printk(KERN_WARNING "xtkbd.c: Unknown key (scancode %#x) %s.\n", + data & XTKBD_KEY, data & XTKBD_RELEASE ? "released" : "pressed"); + } + } + return IRQ_HANDLED; +} + +static int xtkbd_connect(struct serio *serio, struct serio_driver *drv) +{ + struct xtkbd *xtkbd; + int i; + int err; + + if (!(xtkbd = kmalloc(sizeof(struct xtkbd), GFP_KERNEL))) + return -ENOMEM; + + memset(xtkbd, 0, sizeof(struct xtkbd)); + + xtkbd->dev.evbit[0] = BIT(EV_KEY) | BIT(EV_REP); + + xtkbd->serio = serio; + + init_input_dev(&xtkbd->dev); + xtkbd->dev.keycode = xtkbd->keycode; + xtkbd->dev.keycodesize = sizeof(unsigned char); + xtkbd->dev.keycodemax = ARRAY_SIZE(xtkbd_keycode); + xtkbd->dev.private = xtkbd; + + serio_set_drvdata(serio, xtkbd); + + err = serio_open(serio, drv); + if (err) { + serio_set_drvdata(serio, NULL); + kfree(xtkbd); + return err; + } + + memcpy(xtkbd->keycode, xtkbd_keycode, sizeof(xtkbd->keycode)); + for (i = 0; i < 255; i++) + set_bit(xtkbd->keycode[i], xtkbd->dev.keybit); + clear_bit(0, xtkbd->dev.keybit); + + sprintf(xtkbd->phys, "%s/input0", serio->phys); + + xtkbd->dev.name = xtkbd_name; + xtkbd->dev.phys = xtkbd->phys; + xtkbd->dev.id.bustype = BUS_XTKBD; + xtkbd->dev.id.vendor = 0x0001; + xtkbd->dev.id.product = 0x0001; + xtkbd->dev.id.version = 0x0100; + xtkbd->dev.dev = &serio->dev; + + input_register_device(&xtkbd->dev); + + printk(KERN_INFO "input: %s on %s\n", xtkbd_name, serio->phys); + + return 0; +} + +static void xtkbd_disconnect(struct serio *serio) +{ + struct xtkbd *xtkbd = serio_get_drvdata(serio); + + input_unregister_device(&xtkbd->dev); + serio_close(serio); + serio_set_drvdata(serio, NULL); + kfree(xtkbd); +} + +static struct serio_device_id xtkbd_serio_ids[] = { + { + .type = SERIO_XT, + .proto = SERIO_ANY, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { 0 } +}; + +MODULE_DEVICE_TABLE(serio, xtkbd_serio_ids); + +static struct serio_driver xtkbd_drv = { + .driver = { + .name = "xtkbd", + }, + .description = DRIVER_DESC, + .id_table = xtkbd_serio_ids, + .interrupt = xtkbd_interrupt, + .connect = xtkbd_connect, + .disconnect = xtkbd_disconnect, +}; + +static int __init xtkbd_init(void) +{ + serio_register_driver(&xtkbd_drv); + return 0; +} + +static void __exit xtkbd_exit(void) +{ + serio_unregister_driver(&xtkbd_drv); +} + +module_init(xtkbd_init); +module_exit(xtkbd_exit); diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig new file mode 100644 index 000000000000..bb934e6d9636 --- /dev/null +++ b/drivers/input/misc/Kconfig @@ -0,0 +1,60 @@ +# +# Input misc drivers configuration +# +menuconfig INPUT_MISC + bool "Miscellaneous devices" + help + Say Y here, and a list of miscellaneous input drivers will be displayed. + Everything that didn't fit into the other categories is here. This option + doesn't affect the kernel. + + If unsure, say Y. + +if INPUT_MISC + +config INPUT_PCSPKR + tristate "PC Speaker support" + depends on ALPHA || X86 || X86_64 || MIPS || PPC_PREP || PPC_CHRP || PPC_PSERIES + help + Say Y here if you want the standard PC Speaker to be used for + bells and whistles. + + If unsure, say Y. + + To compile this driver as a module, choose M here: the + module will be called pcspkr. + +config INPUT_SPARCSPKR + tristate "SPARC Speaker support" + depends on PCI && (SPARC32 || SPARC64) + help + Say Y here if you want the standard Speaker on Sparc PCI systems + to be used for bells and whistles. + + If unsure, say Y. + + To compile this driver as a module, choose M here: the + module will be called sparcspkr. + +config INPUT_M68K_BEEP + tristate "M68k Beeper support" + depends on M68K + +config INPUT_UINPUT + tristate "User level driver support" + help + Say Y here if you want to support user level drivers for input + subsystem accessible under char device 10:223 - /dev/input/uinput. + + To compile this driver as a module, choose M here: the + module will be called uinput. + +config HP_SDC_RTC + tristate "HP SDC Real Time Clock" + depends on GSC + select HP_SDC + help + Say Y here if you want to support the built-in real time clock + of the HP SDC controller. + +endif diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile new file mode 100644 index 000000000000..f8d01c69f349 --- /dev/null +++ b/drivers/input/misc/Makefile @@ -0,0 +1,12 @@ +# +# Makefile for the input misc drivers. +# + +# Each configuration option enables a list of files. + +obj-$(CONFIG_INPUT_SPARCSPKR) += sparcspkr.o +obj-$(CONFIG_INPUT_PCSPKR) += pcspkr.o +obj-$(CONFIG_INPUT_M68K_BEEP) += m68kspkr.o +obj-$(CONFIG_INPUT_98SPKR) += 98spkr.o +obj-$(CONFIG_INPUT_UINPUT) += uinput.o +obj-$(CONFIG_HP_SDC_RTC) += hp_sdc_rtc.o diff --git a/drivers/input/misc/hp_sdc_rtc.c b/drivers/input/misc/hp_sdc_rtc.c new file mode 100644 index 000000000000..1cd7657f7e42 --- /dev/null +++ b/drivers/input/misc/hp_sdc_rtc.c @@ -0,0 +1,724 @@ +/* + * HP i8042 SDC + MSM-58321 BBRTC driver. + * + * Copyright (c) 2001 Brian S. Julin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * Alternatively, this software may be distributed under the terms of the + * GNU General Public License ("GPL"). + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * + * References: + * System Device Controller Microprocessor Firmware Theory of Operation + * for Part Number 1820-4784 Revision B. Dwg No. A-1820-4784-2 + * efirtc.c by Stephane Eranian/Hewlett Packard + * + */ + +#include <linux/hp_sdc.h> +#include <linux/errno.h> +#include <linux/types.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/time.h> +#include <linux/miscdevice.h> +#include <linux/proc_fs.h> +#include <linux/poll.h> +#include <linux/rtc.h> + +MODULE_AUTHOR("Brian S. Julin <bri@calyx.com>"); +MODULE_DESCRIPTION("HP i8042 SDC + MSM-58321 RTC Driver"); +MODULE_LICENSE("Dual BSD/GPL"); + +#define RTC_VERSION "1.10d" + +static unsigned long epoch = 2000; + +static struct semaphore i8042tregs; + +static hp_sdc_irqhook hp_sdc_rtc_isr; + +static struct fasync_struct *hp_sdc_rtc_async_queue; + +static DECLARE_WAIT_QUEUE_HEAD(hp_sdc_rtc_wait); + +static loff_t hp_sdc_rtc_llseek(struct file *file, loff_t offset, int origin); + +static ssize_t hp_sdc_rtc_read(struct file *file, char *buf, + size_t count, loff_t *ppos); + +static int hp_sdc_rtc_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg); + +static unsigned int hp_sdc_rtc_poll(struct file *file, poll_table *wait); + +static int hp_sdc_rtc_open(struct inode *inode, struct file *file); +static int hp_sdc_rtc_release(struct inode *inode, struct file *file); +static int hp_sdc_rtc_fasync (int fd, struct file *filp, int on); + +static int hp_sdc_rtc_read_proc(char *page, char **start, off_t off, + int count, int *eof, void *data); + +static void hp_sdc_rtc_isr (int irq, void *dev_id, + uint8_t status, uint8_t data) +{ + return; +} + +static int hp_sdc_rtc_do_read_bbrtc (struct rtc_time *rtctm) +{ + struct semaphore tsem; + hp_sdc_transaction t; + uint8_t tseq[91]; + int i; + + i = 0; + while (i < 91) { + tseq[i++] = HP_SDC_ACT_DATAREG | + HP_SDC_ACT_POSTCMD | HP_SDC_ACT_DATAIN; + tseq[i++] = 0x01; /* write i8042[0x70] */ + tseq[i] = i / 7; /* BBRTC reg address */ + i++; + tseq[i++] = HP_SDC_CMD_DO_RTCR; /* Trigger command */ + tseq[i++] = 2; /* expect 1 stat/dat pair back. */ + i++; i++; /* buffer for stat/dat pair */ + } + tseq[84] |= HP_SDC_ACT_SEMAPHORE; + t.endidx = 91; + t.seq = tseq; + t.act.semaphore = &tsem; + init_MUTEX_LOCKED(&tsem); + + if (hp_sdc_enqueue_transaction(&t)) return -1; + + down_interruptible(&tsem); /* Put ourselves to sleep for results. */ + + /* Check for nonpresence of BBRTC */ + if (!((tseq[83] | tseq[90] | tseq[69] | tseq[76] | + tseq[55] | tseq[62] | tseq[34] | tseq[41] | + tseq[20] | tseq[27] | tseq[6] | tseq[13]) & 0x0f)) + return -1; + + memset(rtctm, 0, sizeof(struct rtc_time)); + rtctm->tm_year = (tseq[83] & 0x0f) + (tseq[90] & 0x0f) * 10; + rtctm->tm_mon = (tseq[69] & 0x0f) + (tseq[76] & 0x0f) * 10; + rtctm->tm_mday = (tseq[55] & 0x0f) + (tseq[62] & 0x0f) * 10; + rtctm->tm_wday = (tseq[48] & 0x0f); + rtctm->tm_hour = (tseq[34] & 0x0f) + (tseq[41] & 0x0f) * 10; + rtctm->tm_min = (tseq[20] & 0x0f) + (tseq[27] & 0x0f) * 10; + rtctm->tm_sec = (tseq[6] & 0x0f) + (tseq[13] & 0x0f) * 10; + + return 0; +} + +static int hp_sdc_rtc_read_bbrtc (struct rtc_time *rtctm) +{ + struct rtc_time tm, tm_last; + int i = 0; + + /* MSM-58321 has no read latch, so must read twice and compare. */ + + if (hp_sdc_rtc_do_read_bbrtc(&tm_last)) return -1; + if (hp_sdc_rtc_do_read_bbrtc(&tm)) return -1; + + while (memcmp(&tm, &tm_last, sizeof(struct rtc_time))) { + if (i++ > 4) return -1; + memcpy(&tm_last, &tm, sizeof(struct rtc_time)); + if (hp_sdc_rtc_do_read_bbrtc(&tm)) return -1; + } + + memcpy(rtctm, &tm, sizeof(struct rtc_time)); + + return 0; +} + + +static int64_t hp_sdc_rtc_read_i8042timer (uint8_t loadcmd, int numreg) +{ + hp_sdc_transaction t; + uint8_t tseq[26] = { + HP_SDC_ACT_PRECMD | HP_SDC_ACT_POSTCMD | HP_SDC_ACT_DATAIN, + 0, + HP_SDC_CMD_READ_T1, 2, 0, 0, + HP_SDC_ACT_POSTCMD | HP_SDC_ACT_DATAIN, + HP_SDC_CMD_READ_T2, 2, 0, 0, + HP_SDC_ACT_POSTCMD | HP_SDC_ACT_DATAIN, + HP_SDC_CMD_READ_T3, 2, 0, 0, + HP_SDC_ACT_POSTCMD | HP_SDC_ACT_DATAIN, + HP_SDC_CMD_READ_T4, 2, 0, 0, + HP_SDC_ACT_POSTCMD | HP_SDC_ACT_DATAIN, + HP_SDC_CMD_READ_T5, 2, 0, 0 + }; + + t.endidx = numreg * 5; + + tseq[1] = loadcmd; + tseq[t.endidx - 4] |= HP_SDC_ACT_SEMAPHORE; /* numreg assumed > 1 */ + + t.seq = tseq; + t.act.semaphore = &i8042tregs; + + down_interruptible(&i8042tregs); /* Sleep if output regs in use. */ + + if (hp_sdc_enqueue_transaction(&t)) return -1; + + down_interruptible(&i8042tregs); /* Sleep until results come back. */ + up(&i8042tregs); + + return (tseq[5] | + ((uint64_t)(tseq[10]) << 8) | ((uint64_t)(tseq[15]) << 16) | + ((uint64_t)(tseq[20]) << 24) | ((uint64_t)(tseq[25]) << 32)); +} + + +/* Read the i8042 real-time clock */ +static inline int hp_sdc_rtc_read_rt(struct timeval *res) { + int64_t raw; + uint32_t tenms; + unsigned int days; + + raw = hp_sdc_rtc_read_i8042timer(HP_SDC_CMD_LOAD_RT, 5); + if (raw < 0) return -1; + + tenms = (uint32_t)raw & 0xffffff; + days = (unsigned int)(raw >> 24) & 0xffff; + + res->tv_usec = (suseconds_t)(tenms % 100) * 10000; + res->tv_sec = (time_t)(tenms / 100) + days * 86400; + + return 0; +} + + +/* Read the i8042 fast handshake timer */ +static inline int hp_sdc_rtc_read_fhs(struct timeval *res) { + uint64_t raw; + unsigned int tenms; + + raw = hp_sdc_rtc_read_i8042timer(HP_SDC_CMD_LOAD_FHS, 2); + if (raw < 0) return -1; + + tenms = (unsigned int)raw & 0xffff; + + res->tv_usec = (suseconds_t)(tenms % 100) * 10000; + res->tv_sec = (time_t)(tenms / 100); + + return 0; +} + + +/* Read the i8042 match timer (a.k.a. alarm) */ +static inline int hp_sdc_rtc_read_mt(struct timeval *res) { + int64_t raw; + uint32_t tenms; + + raw = hp_sdc_rtc_read_i8042timer(HP_SDC_CMD_LOAD_MT, 3); + if (raw < 0) return -1; + + tenms = (uint32_t)raw & 0xffffff; + + res->tv_usec = (suseconds_t)(tenms % 100) * 10000; + res->tv_sec = (time_t)(tenms / 100); + + return 0; +} + + +/* Read the i8042 delay timer */ +static inline int hp_sdc_rtc_read_dt(struct timeval *res) { + int64_t raw; + uint32_t tenms; + + raw = hp_sdc_rtc_read_i8042timer(HP_SDC_CMD_LOAD_DT, 3); + if (raw < 0) return -1; + + tenms = (uint32_t)raw & 0xffffff; + + res->tv_usec = (suseconds_t)(tenms % 100) * 10000; + res->tv_sec = (time_t)(tenms / 100); + + return 0; +} + + +/* Read the i8042 cycle timer (a.k.a. periodic) */ +static inline int hp_sdc_rtc_read_ct(struct timeval *res) { + int64_t raw; + uint32_t tenms; + + raw = hp_sdc_rtc_read_i8042timer(HP_SDC_CMD_LOAD_CT, 3); + if (raw < 0) return -1; + + tenms = (uint32_t)raw & 0xffffff; + + res->tv_usec = (suseconds_t)(tenms % 100) * 10000; + res->tv_sec = (time_t)(tenms / 100); + + return 0; +} + + +/* Set the i8042 real-time clock */ +static int hp_sdc_rtc_set_rt (struct timeval *setto) +{ + uint32_t tenms; + unsigned int days; + hp_sdc_transaction t; + uint8_t tseq[11] = { + HP_SDC_ACT_PRECMD | HP_SDC_ACT_DATAOUT, + HP_SDC_CMD_SET_RTMS, 3, 0, 0, 0, + HP_SDC_ACT_PRECMD | HP_SDC_ACT_DATAOUT, + HP_SDC_CMD_SET_RTD, 2, 0, 0 + }; + + t.endidx = 10; + + if (0xffff < setto->tv_sec / 86400) return -1; + days = setto->tv_sec / 86400; + if (0xffff < setto->tv_usec / 1000000 / 86400) return -1; + days += ((setto->tv_sec % 86400) + setto->tv_usec / 1000000) / 86400; + if (days > 0xffff) return -1; + + if (0xffffff < setto->tv_sec) return -1; + tenms = setto->tv_sec * 100; + if (0xffffff < setto->tv_usec / 10000) return -1; + tenms += setto->tv_usec / 10000; + if (tenms > 0xffffff) return -1; + + tseq[3] = (uint8_t)(tenms & 0xff); + tseq[4] = (uint8_t)((tenms >> 8) & 0xff); + tseq[5] = (uint8_t)((tenms >> 16) & 0xff); + + tseq[9] = (uint8_t)(days & 0xff); + tseq[10] = (uint8_t)((days >> 8) & 0xff); + + t.seq = tseq; + + if (hp_sdc_enqueue_transaction(&t)) return -1; + return 0; +} + +/* Set the i8042 fast handshake timer */ +static int hp_sdc_rtc_set_fhs (struct timeval *setto) +{ + uint32_t tenms; + hp_sdc_transaction t; + uint8_t tseq[5] = { + HP_SDC_ACT_PRECMD | HP_SDC_ACT_DATAOUT, + HP_SDC_CMD_SET_FHS, 2, 0, 0 + }; + + t.endidx = 4; + + if (0xffff < setto->tv_sec) return -1; + tenms = setto->tv_sec * 100; + if (0xffff < setto->tv_usec / 10000) return -1; + tenms += setto->tv_usec / 10000; + if (tenms > 0xffff) return -1; + + tseq[3] = (uint8_t)(tenms & 0xff); + tseq[4] = (uint8_t)((tenms >> 8) & 0xff); + + t.seq = tseq; + + if (hp_sdc_enqueue_transaction(&t)) return -1; + return 0; +} + + +/* Set the i8042 match timer (a.k.a. alarm) */ +#define hp_sdc_rtc_set_mt (setto) \ + hp_sdc_rtc_set_i8042timer(setto, HP_SDC_CMD_SET_MT) + +/* Set the i8042 delay timer */ +#define hp_sdc_rtc_set_dt (setto) \ + hp_sdc_rtc_set_i8042timer(setto, HP_SDC_CMD_SET_DT) + +/* Set the i8042 cycle timer (a.k.a. periodic) */ +#define hp_sdc_rtc_set_ct (setto) \ + hp_sdc_rtc_set_i8042timer(setto, HP_SDC_CMD_SET_CT) + +/* Set one of the i8042 3-byte wide timers */ +static int hp_sdc_rtc_set_i8042timer (struct timeval *setto, uint8_t setcmd) +{ + uint32_t tenms; + hp_sdc_transaction t; + uint8_t tseq[6] = { + HP_SDC_ACT_PRECMD | HP_SDC_ACT_DATAOUT, + 0, 3, 0, 0, 0 + }; + + t.endidx = 6; + + if (0xffffff < setto->tv_sec) return -1; + tenms = setto->tv_sec * 100; + if (0xffffff < setto->tv_usec / 10000) return -1; + tenms += setto->tv_usec / 10000; + if (tenms > 0xffffff) return -1; + + tseq[1] = setcmd; + tseq[3] = (uint8_t)(tenms & 0xff); + tseq[4] = (uint8_t)((tenms >> 8) & 0xff); + tseq[5] = (uint8_t)((tenms >> 16) & 0xff); + + t.seq = tseq; + + if (hp_sdc_enqueue_transaction(&t)) { + return -1; + } + return 0; +} + +static loff_t hp_sdc_rtc_llseek(struct file *file, loff_t offset, int origin) +{ + return -ESPIPE; +} + +static ssize_t hp_sdc_rtc_read(struct file *file, char *buf, + size_t count, loff_t *ppos) { + ssize_t retval; + + if (count < sizeof(unsigned long)) + return -EINVAL; + + retval = put_user(68, (unsigned long *)buf); + return retval; +} + +static unsigned int hp_sdc_rtc_poll(struct file *file, poll_table *wait) +{ + unsigned long l; + + l = 0; + if (l != 0) + return POLLIN | POLLRDNORM; + return 0; +} + +static int hp_sdc_rtc_open(struct inode *inode, struct file *file) +{ + return 0; +} + +static int hp_sdc_rtc_release(struct inode *inode, struct file *file) +{ + /* Turn off interrupts? */ + + if (file->f_flags & FASYNC) { + hp_sdc_rtc_fasync (-1, file, 0); + } + + return 0; +} + +static int hp_sdc_rtc_fasync (int fd, struct file *filp, int on) +{ + return fasync_helper (fd, filp, on, &hp_sdc_rtc_async_queue); +} + +static int hp_sdc_rtc_proc_output (char *buf) +{ +#define YN(bit) ("no") +#define NY(bit) ("yes") + char *p; + struct rtc_time tm; + struct timeval tv; + + memset(&tm, 0, sizeof(struct rtc_time)); + + p = buf; + + if (hp_sdc_rtc_read_bbrtc(&tm)) { + p += sprintf(p, "BBRTC\t\t: READ FAILED!\n"); + } else { + p += sprintf(p, + "rtc_time\t: %02d:%02d:%02d\n" + "rtc_date\t: %04d-%02d-%02d\n" + "rtc_epoch\t: %04lu\n", + tm.tm_hour, tm.tm_min, tm.tm_sec, + tm.tm_year + 1900, tm.tm_mon + 1, + tm.tm_mday, epoch); + } + + if (hp_sdc_rtc_read_rt(&tv)) { + p += sprintf(p, "i8042 rtc\t: READ FAILED!\n"); + } else { + p += sprintf(p, "i8042 rtc\t: %ld.%02d seconds\n", + tv.tv_sec, tv.tv_usec/1000); + } + + if (hp_sdc_rtc_read_fhs(&tv)) { + p += sprintf(p, "handshake\t: READ FAILED!\n"); + } else { + p += sprintf(p, "handshake\t: %ld.%02d seconds\n", + tv.tv_sec, tv.tv_usec/1000); + } + + if (hp_sdc_rtc_read_mt(&tv)) { + p += sprintf(p, "alarm\t\t: READ FAILED!\n"); + } else { + p += sprintf(p, "alarm\t\t: %ld.%02d seconds\n", + tv.tv_sec, tv.tv_usec/1000); + } + + if (hp_sdc_rtc_read_dt(&tv)) { + p += sprintf(p, "delay\t\t: READ FAILED!\n"); + } else { + p += sprintf(p, "delay\t\t: %ld.%02d seconds\n", + tv.tv_sec, tv.tv_usec/1000); + } + + if (hp_sdc_rtc_read_ct(&tv)) { + p += sprintf(p, "periodic\t: READ FAILED!\n"); + } else { + p += sprintf(p, "periodic\t: %ld.%02d seconds\n", + tv.tv_sec, tv.tv_usec/1000); + } + + p += sprintf(p, + "DST_enable\t: %s\n" + "BCD\t\t: %s\n" + "24hr\t\t: %s\n" + "square_wave\t: %s\n" + "alarm_IRQ\t: %s\n" + "update_IRQ\t: %s\n" + "periodic_IRQ\t: %s\n" + "periodic_freq\t: %ld\n" + "batt_status\t: %s\n", + YN(RTC_DST_EN), + NY(RTC_DM_BINARY), + YN(RTC_24H), + YN(RTC_SQWE), + YN(RTC_AIE), + YN(RTC_UIE), + YN(RTC_PIE), + 1UL, + 1 ? "okay" : "dead"); + + return p - buf; +#undef YN +#undef NY +} + +static int hp_sdc_rtc_read_proc(char *page, char **start, off_t off, + int count, int *eof, void *data) +{ + int len = hp_sdc_rtc_proc_output (page); + if (len <= off+count) *eof = 1; + *start = page + off; + len -= off; + if (len>count) len = count; + if (len<0) len = 0; + return len; +} + +static int hp_sdc_rtc_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ +#if 1 + return -EINVAL; +#else + + struct rtc_time wtime; + struct timeval ttime; + int use_wtime = 0; + + /* This needs major work. */ + + switch (cmd) { + + case RTC_AIE_OFF: /* Mask alarm int. enab. bit */ + case RTC_AIE_ON: /* Allow alarm interrupts. */ + case RTC_PIE_OFF: /* Mask periodic int. enab. bit */ + case RTC_PIE_ON: /* Allow periodic ints */ + case RTC_UIE_ON: /* Allow ints for RTC updates. */ + case RTC_UIE_OFF: /* Allow ints for RTC updates. */ + { + /* We cannot mask individual user timers and we + cannot tell them apart when they occur, so it + would be disingenuous to succeed these IOCTLs */ + return -EINVAL; + } + case RTC_ALM_READ: /* Read the present alarm time */ + { + if (hp_sdc_rtc_read_mt(&ttime)) return -EFAULT; + if (hp_sdc_rtc_read_bbrtc(&wtime)) return -EFAULT; + + wtime.tm_hour = ttime.tv_sec / 3600; ttime.tv_sec %= 3600; + wtime.tm_min = ttime.tv_sec / 60; ttime.tv_sec %= 60; + wtime.tm_sec = ttime.tv_sec; + + break; + } + case RTC_IRQP_READ: /* Read the periodic IRQ rate. */ + { + return put_user(hp_sdc_rtc_freq, (unsigned long *)arg); + } + case RTC_IRQP_SET: /* Set periodic IRQ rate. */ + { + /* + * The max we can do is 100Hz. + */ + + if ((arg < 1) || (arg > 100)) return -EINVAL; + ttime.tv_sec = 0; + ttime.tv_usec = 1000000 / arg; + if (hp_sdc_rtc_set_ct(&ttime)) return -EFAULT; + hp_sdc_rtc_freq = arg; + return 0; + } + case RTC_ALM_SET: /* Store a time into the alarm */ + { + /* + * This expects a struct hp_sdc_rtc_time. Writing 0xff means + * "don't care" or "match all" for PC timers. The HP SDC + * does not support that perk, but it could be emulated fairly + * easily. Only the tm_hour, tm_min and tm_sec are used. + * We could do it with 10ms accuracy with the HP SDC, if the + * rtc interface left us a way to do that. + */ + struct hp_sdc_rtc_time alm_tm; + + if (copy_from_user(&alm_tm, (struct hp_sdc_rtc_time*)arg, + sizeof(struct hp_sdc_rtc_time))) + return -EFAULT; + + if (alm_tm.tm_hour > 23) return -EINVAL; + if (alm_tm.tm_min > 59) return -EINVAL; + if (alm_tm.tm_sec > 59) return -EINVAL; + + ttime.sec = alm_tm.tm_hour * 3600 + + alm_tm.tm_min * 60 + alm_tm.tm_sec; + ttime.usec = 0; + if (hp_sdc_rtc_set_mt(&ttime)) return -EFAULT; + return 0; + } + case RTC_RD_TIME: /* Read the time/date from RTC */ + { + if (hp_sdc_rtc_read_bbrtc(&wtime)) return -EFAULT; + break; + } + case RTC_SET_TIME: /* Set the RTC */ + { + struct rtc_time hp_sdc_rtc_tm; + unsigned char mon, day, hrs, min, sec, leap_yr; + unsigned int yrs; + + if (!capable(CAP_SYS_TIME)) + return -EACCES; + if (copy_from_user(&hp_sdc_rtc_tm, (struct rtc_time *)arg, + sizeof(struct rtc_time))) + return -EFAULT; + + yrs = hp_sdc_rtc_tm.tm_year + 1900; + mon = hp_sdc_rtc_tm.tm_mon + 1; /* tm_mon starts at zero */ + day = hp_sdc_rtc_tm.tm_mday; + hrs = hp_sdc_rtc_tm.tm_hour; + min = hp_sdc_rtc_tm.tm_min; + sec = hp_sdc_rtc_tm.tm_sec; + + if (yrs < 1970) + return -EINVAL; + + leap_yr = ((!(yrs % 4) && (yrs % 100)) || !(yrs % 400)); + + if ((mon > 12) || (day == 0)) + return -EINVAL; + if (day > (days_in_mo[mon] + ((mon == 2) && leap_yr))) + return -EINVAL; + if ((hrs >= 24) || (min >= 60) || (sec >= 60)) + return -EINVAL; + + if ((yrs -= eH) > 255) /* They are unsigned */ + return -EINVAL; + + + return 0; + } + case RTC_EPOCH_READ: /* Read the epoch. */ + { + return put_user (epoch, (unsigned long *)arg); + } + case RTC_EPOCH_SET: /* Set the epoch. */ + { + /* + * There were no RTC clocks before 1900. + */ + if (arg < 1900) + return -EINVAL; + if (!capable(CAP_SYS_TIME)) + return -EACCES; + + epoch = arg; + return 0; + } + default: + return -EINVAL; + } + return copy_to_user((void *)arg, &wtime, sizeof wtime) ? -EFAULT : 0; +#endif +} + +static struct file_operations hp_sdc_rtc_fops = { + .owner = THIS_MODULE, + .llseek = hp_sdc_rtc_llseek, + .read = hp_sdc_rtc_read, + .poll = hp_sdc_rtc_poll, + .ioctl = hp_sdc_rtc_ioctl, + .open = hp_sdc_rtc_open, + .release = hp_sdc_rtc_release, + .fasync = hp_sdc_rtc_fasync, +}; + +static struct miscdevice hp_sdc_rtc_dev = { + .minor = RTC_MINOR, + .name = "rtc_HIL", + .fops = &hp_sdc_rtc_fops +}; + +static int __init hp_sdc_rtc_init(void) +{ + int ret; + + init_MUTEX(&i8042tregs); + + if ((ret = hp_sdc_request_timer_irq(&hp_sdc_rtc_isr))) + return ret; + misc_register(&hp_sdc_rtc_dev); + create_proc_read_entry ("driver/rtc", 0, 0, + hp_sdc_rtc_read_proc, NULL); + + printk(KERN_INFO "HP i8042 SDC + MSM-58321 RTC support loaded " + "(RTC v " RTC_VERSION ")\n"); + + return 0; +} + +static void __exit hp_sdc_rtc_exit(void) +{ + remove_proc_entry ("driver/rtc", NULL); + misc_deregister(&hp_sdc_rtc_dev); + hp_sdc_release_timer_irq(hp_sdc_rtc_isr); + printk(KERN_INFO "HP i8042 SDC + MSM-58321 RTC support unloaded\n"); +} + +module_init(hp_sdc_rtc_init); +module_exit(hp_sdc_rtc_exit); diff --git a/drivers/input/misc/m68kspkr.c b/drivers/input/misc/m68kspkr.c new file mode 100644 index 000000000000..64abdd98d482 --- /dev/null +++ b/drivers/input/misc/m68kspkr.c @@ -0,0 +1,83 @@ +/* + * m68k beeper driver for Linux + * + * Copyright (c) 2002 Richard Zidlicky + * Copyright (c) 2002 Vojtech Pavlik + * Copyright (c) 1992 Orest Zborowski + * + */ + +/* + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/input.h> +#include <asm/machdep.h> +#include <asm/io.h> + +MODULE_AUTHOR("Richard Zidlicky <rz@linux-m68k.org>"); +MODULE_DESCRIPTION("m68k beeper driver"); +MODULE_LICENSE("GPL"); + +static char m68kspkr_name[] = "m68k beeper"; +static char m68kspkr_phys[] = "m68k/generic"; +static struct input_dev m68kspkr_dev; + +static int m68kspkr_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) +{ + unsigned int count = 0; + + if (type != EV_SND) + return -1; + + switch (code) { + case SND_BELL: if (value) value = 1000; + case SND_TONE: break; + default: return -1; + } + + if (value > 20 && value < 32767) + count = 1193182 / value; + + mach_beep(count, -1); + + return 0; +} + +static int __init m68kspkr_init(void) +{ + if (!mach_beep){ + printk("%s: no lowlevel beep support\n", m68kspkr_name); + return -1; + } + + m68kspkr_dev.evbit[0] = BIT(EV_SND); + m68kspkr_dev.sndbit[0] = BIT(SND_BELL) | BIT(SND_TONE); + m68kspkr_dev.event = m68kspkr_event; + + m68kspkr_dev.name = m68kspkr_name; + m68kspkr_dev.phys = m68kspkr_phys; + m68kspkr_dev.id.bustype = BUS_HOST; + m68kspkr_dev.id.vendor = 0x001f; + m68kspkr_dev.id.product = 0x0001; + m68kspkr_dev.id.version = 0x0100; + + input_register_device(&m68kspkr_dev); + + printk(KERN_INFO "input: %s\n", m68kspkr_name); + + return 0; +} + +static void __exit m68kspkr_exit(void) +{ + input_unregister_device(&m68kspkr_dev); +} + +module_init(m68kspkr_init); +module_exit(m68kspkr_exit); diff --git a/drivers/input/misc/pcspkr.c b/drivers/input/misc/pcspkr.c new file mode 100644 index 000000000000..3013194f462b --- /dev/null +++ b/drivers/input/misc/pcspkr.c @@ -0,0 +1,97 @@ +/* + * PC Speaker beeper driver for Linux + * + * Copyright (c) 2002 Vojtech Pavlik + * Copyright (c) 1992 Orest Zborowski + * + */ + +/* + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/input.h> +#include <asm/8253pit.h> +#include <asm/io.h> + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION("PC Speaker beeper driver"); +MODULE_LICENSE("GPL"); + +static char pcspkr_name[] = "PC Speaker"; +static char pcspkr_phys[] = "isa0061/input0"; +static struct input_dev pcspkr_dev; + +static DEFINE_SPINLOCK(i8253_beep_lock); + +static int pcspkr_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) +{ + unsigned int count = 0; + unsigned long flags; + + if (type != EV_SND) + return -1; + + switch (code) { + case SND_BELL: if (value) value = 1000; + case SND_TONE: break; + default: return -1; + } + + if (value > 20 && value < 32767) + count = PIT_TICK_RATE / value; + + spin_lock_irqsave(&i8253_beep_lock, flags); + + if (count) { + /* enable counter 2 */ + outb_p(inb_p(0x61) | 3, 0x61); + /* set command for counter 2, 2 byte write */ + outb_p(0xB6, 0x43); + /* select desired HZ */ + outb_p(count & 0xff, 0x42); + outb((count >> 8) & 0xff, 0x42); + } else { + /* disable counter 2 */ + outb(inb_p(0x61) & 0xFC, 0x61); + } + + spin_unlock_irqrestore(&i8253_beep_lock, flags); + + return 0; +} + +static int __init pcspkr_init(void) +{ + pcspkr_dev.evbit[0] = BIT(EV_SND); + pcspkr_dev.sndbit[0] = BIT(SND_BELL) | BIT(SND_TONE); + pcspkr_dev.event = pcspkr_event; + + pcspkr_dev.name = pcspkr_name; + pcspkr_dev.phys = pcspkr_phys; + pcspkr_dev.id.bustype = BUS_ISA; + pcspkr_dev.id.vendor = 0x001f; + pcspkr_dev.id.product = 0x0001; + pcspkr_dev.id.version = 0x0100; + + input_register_device(&pcspkr_dev); + + printk(KERN_INFO "input: %s\n", pcspkr_name); + + return 0; +} + +static void __exit pcspkr_exit(void) +{ + input_unregister_device(&pcspkr_dev); + /* turn off the speaker */ + pcspkr_event(NULL, EV_SND, SND_BELL, 0); +} + +module_init(pcspkr_init); +module_exit(pcspkr_exit); diff --git a/drivers/input/misc/sparcspkr.c b/drivers/input/misc/sparcspkr.c new file mode 100644 index 000000000000..cdc3fb3d5f46 --- /dev/null +++ b/drivers/input/misc/sparcspkr.c @@ -0,0 +1,189 @@ +/* + * Driver for PC-speaker like devices found on various Sparc systems. + * + * Copyright (c) 2002 Vojtech Pavlik + * Copyright (c) 2002 David S. Miller (davem@redhat.com) + */ +#include <linux/config.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/input.h> + +#include <asm/io.h> +#include <asm/ebus.h> +#ifdef CONFIG_SPARC64 +#include <asm/isa.h> +#endif + +MODULE_AUTHOR("David S. Miller <davem@redhat.com>"); +MODULE_DESCRIPTION("PC Speaker beeper driver"); +MODULE_LICENSE("GPL"); + +static unsigned long beep_iobase; + +static char *sparcspkr_isa_name = "Sparc ISA Speaker"; +static char *sparcspkr_ebus_name = "Sparc EBUS Speaker"; +static char *sparcspkr_phys = "sparc/input0"; +static struct input_dev sparcspkr_dev; + +DEFINE_SPINLOCK(beep_lock); + +static void __init init_sparcspkr_struct(void) +{ + sparcspkr_dev.evbit[0] = BIT(EV_SND); + sparcspkr_dev.sndbit[0] = BIT(SND_BELL) | BIT(SND_TONE); + + sparcspkr_dev.phys = sparcspkr_phys; + sparcspkr_dev.id.bustype = BUS_ISA; + sparcspkr_dev.id.vendor = 0x001f; + sparcspkr_dev.id.product = 0x0001; + sparcspkr_dev.id.version = 0x0100; +} + +static int ebus_spkr_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) +{ + unsigned int count = 0; + unsigned long flags; + + if (type != EV_SND) + return -1; + + switch (code) { + case SND_BELL: if (value) value = 1000; + case SND_TONE: break; + default: return -1; + } + + if (value > 20 && value < 32767) + count = 1193182 / value; + + spin_lock_irqsave(&beep_lock, flags); + + /* EBUS speaker only has on/off state, the frequency does not + * appear to be programmable. + */ + if (count) { + if (beep_iobase & 0x2UL) + outb(1, beep_iobase); + else + outl(1, beep_iobase); + } else { + if (beep_iobase & 0x2UL) + outb(0, beep_iobase); + else + outl(0, beep_iobase); + } + + spin_unlock_irqrestore(&beep_lock, flags); + + return 0; +} + +static int __init init_ebus_beep(struct linux_ebus_device *edev) +{ + beep_iobase = edev->resource[0].start; + + init_sparcspkr_struct(); + + sparcspkr_dev.name = sparcspkr_ebus_name; + sparcspkr_dev.event = ebus_spkr_event; + + input_register_device(&sparcspkr_dev); + + printk(KERN_INFO "input: %s\n", sparcspkr_ebus_name); + return 0; +} + +#ifdef CONFIG_SPARC64 +static int isa_spkr_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) +{ + unsigned int count = 0; + unsigned long flags; + + if (type != EV_SND) + return -1; + + switch (code) { + case SND_BELL: if (value) value = 1000; + case SND_TONE: break; + default: return -1; + } + + if (value > 20 && value < 32767) + count = 1193182 / value; + + spin_lock_irqsave(&beep_lock, flags); + + if (count) { + /* enable counter 2 */ + outb(inb(beep_iobase + 0x61) | 3, beep_iobase + 0x61); + /* set command for counter 2, 2 byte write */ + outb(0xB6, beep_iobase + 0x43); + /* select desired HZ */ + outb(count & 0xff, beep_iobase + 0x42); + outb((count >> 8) & 0xff, beep_iobase + 0x42); + } else { + /* disable counter 2 */ + outb(inb_p(beep_iobase + 0x61) & 0xFC, beep_iobase + 0x61); + } + + spin_unlock_irqrestore(&beep_lock, flags); + + return 0; +} + +static int __init init_isa_beep(struct sparc_isa_device *isa_dev) +{ + beep_iobase = isa_dev->resource.start; + + init_sparcspkr_struct(); + + sparcspkr_dev.name = sparcspkr_isa_name; + sparcspkr_dev.event = isa_spkr_event; + sparcspkr_dev.id.bustype = BUS_ISA; + + input_register_device(&sparcspkr_dev); + + printk(KERN_INFO "input: %s\n", sparcspkr_isa_name); + return 0; +} +#endif + +static int __init sparcspkr_init(void) +{ + struct linux_ebus *ebus; + struct linux_ebus_device *edev = NULL; +#ifdef CONFIG_SPARC64 + struct sparc_isa_bridge *isa_br; + struct sparc_isa_device *isa_dev; +#endif + + for_each_ebus(ebus) { + for_each_ebusdev(edev, ebus) { + if (!strcmp(edev->prom_name, "beep")) + return init_ebus_beep(edev); + } + } +#ifdef CONFIG_SPARC64 + for_each_isa(isa_br) { + for_each_isadev(isa_dev, isa_br) { + /* A hack, the beep device's base lives in + * the DMA isa node. + */ + if (!strcmp(isa_dev->prom_name, "dma")) + return init_isa_beep(isa_dev); + } + } +#endif + + return -ENODEV; +} + +static void __exit sparcspkr_exit(void) +{ + input_unregister_device(&sparcspkr_dev); +} + +module_init(sparcspkr_init); +module_exit(sparcspkr_exit); diff --git a/drivers/input/misc/uinput.c b/drivers/input/misc/uinput.c new file mode 100644 index 000000000000..158c8e845ff9 --- /dev/null +++ b/drivers/input/misc/uinput.c @@ -0,0 +1,620 @@ +/* + * User level driver support for input subsystem + * + * Heavily based on evdev.c by Vojtech Pavlik + * + * 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 + * + * Author: Aristeu Sergio Rozanski Filho <aris@cathedrallabs.org> + * + * Changes/Revisions: + * 0.2 16/10/2004 (Micah Dowty <micah@navi.cx>) + * - added force feedback support + * - added UI_SET_PHYS + * 0.1 20/06/2002 + * - first public version + */ +#include <linux/poll.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/smp_lock.h> +#include <linux/fs.h> +#include <linux/miscdevice.h> +#include <linux/uinput.h> + +static int uinput_dev_open(struct input_dev *dev) +{ + return 0; +} + +static void uinput_dev_close(struct input_dev *dev) +{ + +} + +static int uinput_dev_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) +{ + struct uinput_device *udev; + + udev = dev->private; + + udev->buff[udev->head].type = type; + udev->buff[udev->head].code = code; + udev->buff[udev->head].value = value; + do_gettimeofday(&udev->buff[udev->head].time); + udev->head = (udev->head + 1) % UINPUT_BUFFER_SIZE; + + wake_up_interruptible(&udev->waitq); + + return 0; +} + +static int uinput_request_alloc_id(struct input_dev *dev, struct uinput_request *request) +{ + /* Atomically allocate an ID for the given request. Returns 0 on success. */ + struct uinput_device *udev = dev->private; + int id; + + down(&udev->requests_sem); + for (id=0; id<UINPUT_NUM_REQUESTS; id++) + if (!udev->requests[id]) { + udev->requests[id] = request; + request->id = id; + up(&udev->requests_sem); + return 0; + } + up(&udev->requests_sem); + return -1; +} + +static struct uinput_request* uinput_request_find(struct uinput_device *udev, int id) +{ + /* Find an input request, by ID. Returns NULL if the ID isn't valid. */ + if (id >= UINPUT_NUM_REQUESTS || id < 0) + return NULL; + if (udev->requests[id]->completed) + return NULL; + return udev->requests[id]; +} + +static void uinput_request_init(struct input_dev *dev, struct uinput_request *request, int code) +{ + struct uinput_device *udev = dev->private; + + memset(request, 0, sizeof(struct uinput_request)); + request->code = code; + init_waitqueue_head(&request->waitq); + + /* Allocate an ID. If none are available right away, wait. */ + request->retval = wait_event_interruptible(udev->requests_waitq, + !uinput_request_alloc_id(dev, request)); +} + +static void uinput_request_submit(struct input_dev *dev, struct uinput_request *request) +{ + struct uinput_device *udev = dev->private; + int retval; + + /* Tell our userspace app about this new request by queueing an input event */ + uinput_dev_event(dev, EV_UINPUT, request->code, request->id); + + /* Wait for the request to complete */ + retval = wait_event_interruptible(request->waitq, request->completed); + if (retval) + request->retval = retval; + + /* Release this request's ID, let others know it's available */ + udev->requests[request->id] = NULL; + wake_up_interruptible(&udev->requests_waitq); +} + +static int uinput_dev_upload_effect(struct input_dev *dev, struct ff_effect *effect) +{ + struct uinput_request request; + + if (!test_bit(EV_FF, dev->evbit)) + return -ENOSYS; + + uinput_request_init(dev, &request, UI_FF_UPLOAD); + if (request.retval) + return request.retval; + request.u.effect = effect; + uinput_request_submit(dev, &request); + return request.retval; +} + +static int uinput_dev_erase_effect(struct input_dev *dev, int effect_id) +{ + struct uinput_request request; + + if (!test_bit(EV_FF, dev->evbit)) + return -ENOSYS; + + uinput_request_init(dev, &request, UI_FF_ERASE); + if (request.retval) + return request.retval; + request.u.effect_id = effect_id; + uinput_request_submit(dev, &request); + return request.retval; +} + +static int uinput_create_device(struct uinput_device *udev) +{ + if (!udev->dev->name) { + printk(KERN_DEBUG "%s: write device info first\n", UINPUT_NAME); + return -EINVAL; + } + + udev->dev->open = uinput_dev_open; + udev->dev->close = uinput_dev_close; + udev->dev->event = uinput_dev_event; + udev->dev->upload_effect = uinput_dev_upload_effect; + udev->dev->erase_effect = uinput_dev_erase_effect; + udev->dev->private = udev; + + init_waitqueue_head(&(udev->waitq)); + + input_register_device(udev->dev); + + set_bit(UIST_CREATED, &(udev->state)); + + return 0; +} + +static int uinput_destroy_device(struct uinput_device *udev) +{ + if (!test_bit(UIST_CREATED, &(udev->state))) { + printk(KERN_WARNING "%s: create the device first\n", UINPUT_NAME); + return -EINVAL; + } + + input_unregister_device(udev->dev); + + clear_bit(UIST_CREATED, &(udev->state)); + + return 0; +} + +static int uinput_open(struct inode *inode, struct file *file) +{ + struct uinput_device *newdev; + struct input_dev *newinput; + + newdev = kmalloc(sizeof(struct uinput_device), GFP_KERNEL); + if (!newdev) + goto error; + memset(newdev, 0, sizeof(struct uinput_device)); + init_MUTEX(&newdev->requests_sem); + init_waitqueue_head(&newdev->requests_waitq); + + newinput = kmalloc(sizeof(struct input_dev), GFP_KERNEL); + if (!newinput) + goto cleanup; + memset(newinput, 0, sizeof(struct input_dev)); + + newdev->dev = newinput; + + file->private_data = newdev; + + return 0; +cleanup: + kfree(newdev); +error: + return -ENOMEM; +} + +static int uinput_validate_absbits(struct input_dev *dev) +{ + unsigned int cnt; + int retval = 0; + + for (cnt = 0; cnt < ABS_MAX + 1; cnt++) { + if (!test_bit(cnt, dev->absbit)) + continue; + + if ((dev->absmax[cnt] <= dev->absmin[cnt])) { + printk(KERN_DEBUG + "%s: invalid abs[%02x] min:%d max:%d\n", + UINPUT_NAME, cnt, + dev->absmin[cnt], dev->absmax[cnt]); + retval = -EINVAL; + break; + } + + if (dev->absflat[cnt] > (dev->absmax[cnt] - dev->absmin[cnt])) { + printk(KERN_DEBUG + "%s: absflat[%02x] out of range: %d " + "(min:%d/max:%d)\n", + UINPUT_NAME, cnt, dev->absflat[cnt], + dev->absmin[cnt], dev->absmax[cnt]); + retval = -EINVAL; + break; + } + } + return retval; +} + +static int uinput_alloc_device(struct file *file, const char __user *buffer, size_t count) +{ + struct uinput_user_dev *user_dev; + struct input_dev *dev; + struct uinput_device *udev; + int size, + retval; + + retval = count; + + udev = file->private_data; + dev = udev->dev; + + user_dev = kmalloc(sizeof(*user_dev), GFP_KERNEL); + if (!user_dev) { + retval = -ENOMEM; + goto exit; + } + + if (copy_from_user(user_dev, buffer, sizeof(struct uinput_user_dev))) { + retval = -EFAULT; + goto exit; + } + + if (NULL != dev->name) + kfree(dev->name); + + size = strnlen(user_dev->name, UINPUT_MAX_NAME_SIZE) + 1; + dev->name = kmalloc(size, GFP_KERNEL); + if (!dev->name) { + retval = -ENOMEM; + goto exit; + } + + strlcpy(dev->name, user_dev->name, size); + dev->id.bustype = user_dev->id.bustype; + dev->id.vendor = user_dev->id.vendor; + dev->id.product = user_dev->id.product; + dev->id.version = user_dev->id.version; + dev->ff_effects_max = user_dev->ff_effects_max; + + size = sizeof(int) * (ABS_MAX + 1); + memcpy(dev->absmax, user_dev->absmax, size); + memcpy(dev->absmin, user_dev->absmin, size); + memcpy(dev->absfuzz, user_dev->absfuzz, size); + memcpy(dev->absflat, user_dev->absflat, size); + + /* check if absmin/absmax/absfuzz/absflat are filled as + * told in Documentation/input/input-programming.txt */ + if (test_bit(EV_ABS, dev->evbit)) { + retval = uinput_validate_absbits(dev); + if (retval < 0) + kfree(dev->name); + } + +exit: + kfree(user_dev); + return retval; +} + +static ssize_t uinput_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos) +{ + struct uinput_device *udev = file->private_data; + + if (test_bit(UIST_CREATED, &(udev->state))) { + struct input_event ev; + + if (copy_from_user(&ev, buffer, sizeof(struct input_event))) + return -EFAULT; + input_event(udev->dev, ev.type, ev.code, ev.value); + } + else + count = uinput_alloc_device(file, buffer, count); + + return count; +} + +static ssize_t uinput_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos) +{ + struct uinput_device *udev = file->private_data; + int retval = 0; + + if (!test_bit(UIST_CREATED, &(udev->state))) + return -ENODEV; + + if ((udev->head == udev->tail) && (file->f_flags & O_NONBLOCK)) + return -EAGAIN; + + retval = wait_event_interruptible(udev->waitq, + (udev->head != udev->tail) || + !test_bit(UIST_CREATED, &(udev->state))); + + if (retval) + return retval; + + if (!test_bit(UIST_CREATED, &(udev->state))) + return -ENODEV; + + while ((udev->head != udev->tail) && + (retval + sizeof(struct input_event) <= count)) { + if (copy_to_user(buffer + retval, &(udev->buff[udev->tail]), + sizeof(struct input_event))) return -EFAULT; + udev->tail = (udev->tail + 1) % UINPUT_BUFFER_SIZE; + retval += sizeof(struct input_event); + } + + return retval; +} + +static unsigned int uinput_poll(struct file *file, poll_table *wait) +{ + struct uinput_device *udev = file->private_data; + + poll_wait(file, &udev->waitq, wait); + + if (udev->head != udev->tail) + return POLLIN | POLLRDNORM; + + return 0; +} + +static int uinput_burn_device(struct uinput_device *udev) +{ + if (test_bit(UIST_CREATED, &(udev->state))) + uinput_destroy_device(udev); + + if (NULL != udev->dev->name) + kfree(udev->dev->name); + if (NULL != udev->dev->phys) + kfree(udev->dev->phys); + + kfree(udev->dev); + kfree(udev); + + return 0; +} + +static int uinput_close(struct inode *inode, struct file *file) +{ + return uinput_burn_device(file->private_data); +} + +static int uinput_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) +{ + int retval = 0; + struct uinput_device *udev; + void __user *p = (void __user *)arg; + struct uinput_ff_upload ff_up; + struct uinput_ff_erase ff_erase; + struct uinput_request *req; + int length; + + udev = file->private_data; + + /* device attributes can not be changed after the device is created */ + switch (cmd) { + case UI_SET_EVBIT: + case UI_SET_KEYBIT: + case UI_SET_RELBIT: + case UI_SET_ABSBIT: + case UI_SET_MSCBIT: + case UI_SET_LEDBIT: + case UI_SET_SNDBIT: + case UI_SET_FFBIT: + case UI_SET_PHYS: + if (test_bit(UIST_CREATED, &(udev->state))) + return -EINVAL; + } + + switch (cmd) { + case UI_DEV_CREATE: + retval = uinput_create_device(udev); + break; + + case UI_DEV_DESTROY: + retval = uinput_destroy_device(udev); + break; + + case UI_SET_EVBIT: + if (arg > EV_MAX) { + retval = -EINVAL; + break; + } + set_bit(arg, udev->dev->evbit); + break; + + case UI_SET_KEYBIT: + if (arg > KEY_MAX) { + retval = -EINVAL; + break; + } + set_bit(arg, udev->dev->keybit); + break; + + case UI_SET_RELBIT: + if (arg > REL_MAX) { + retval = -EINVAL; + break; + } + set_bit(arg, udev->dev->relbit); + break; + + case UI_SET_ABSBIT: + if (arg > ABS_MAX) { + retval = -EINVAL; + break; + } + set_bit(arg, udev->dev->absbit); + break; + + case UI_SET_MSCBIT: + if (arg > MSC_MAX) { + retval = -EINVAL; + break; + } + set_bit(arg, udev->dev->mscbit); + break; + + case UI_SET_LEDBIT: + if (arg > LED_MAX) { + retval = -EINVAL; + break; + } + set_bit(arg, udev->dev->ledbit); + break; + + case UI_SET_SNDBIT: + if (arg > SND_MAX) { + retval = -EINVAL; + break; + } + set_bit(arg, udev->dev->sndbit); + break; + + case UI_SET_FFBIT: + if (arg > FF_MAX) { + retval = -EINVAL; + break; + } + set_bit(arg, udev->dev->ffbit); + break; + + case UI_SET_PHYS: + length = strnlen_user(p, 1024); + if (length <= 0) { + retval = -EFAULT; + break; + } + if (NULL != udev->dev->phys) + kfree(udev->dev->phys); + udev->dev->phys = kmalloc(length, GFP_KERNEL); + if (!udev->dev->phys) { + retval = -ENOMEM; + break; + } + if (copy_from_user(udev->dev->phys, p, length)) { + retval = -EFAULT; + kfree(udev->dev->phys); + udev->dev->phys = NULL; + break; + } + udev->dev->phys[length-1] = '\0'; + break; + + case UI_BEGIN_FF_UPLOAD: + if (copy_from_user(&ff_up, p, sizeof(ff_up))) { + retval = -EFAULT; + break; + } + req = uinput_request_find(udev, ff_up.request_id); + if (!(req && req->code==UI_FF_UPLOAD && req->u.effect)) { + retval = -EINVAL; + break; + } + ff_up.retval = 0; + memcpy(&ff_up.effect, req->u.effect, sizeof(struct ff_effect)); + if (copy_to_user(p, &ff_up, sizeof(ff_up))) { + retval = -EFAULT; + break; + } + break; + + case UI_BEGIN_FF_ERASE: + if (copy_from_user(&ff_erase, p, sizeof(ff_erase))) { + retval = -EFAULT; + break; + } + req = uinput_request_find(udev, ff_erase.request_id); + if (!(req && req->code==UI_FF_ERASE)) { + retval = -EINVAL; + break; + } + ff_erase.retval = 0; + ff_erase.effect_id = req->u.effect_id; + if (copy_to_user(p, &ff_erase, sizeof(ff_erase))) { + retval = -EFAULT; + break; + } + break; + + case UI_END_FF_UPLOAD: + if (copy_from_user(&ff_up, p, sizeof(ff_up))) { + retval = -EFAULT; + break; + } + req = uinput_request_find(udev, ff_up.request_id); + if (!(req && req->code==UI_FF_UPLOAD && req->u.effect)) { + retval = -EINVAL; + break; + } + req->retval = ff_up.retval; + memcpy(req->u.effect, &ff_up.effect, sizeof(struct ff_effect)); + req->completed = 1; + wake_up_interruptible(&req->waitq); + break; + + case UI_END_FF_ERASE: + if (copy_from_user(&ff_erase, p, sizeof(ff_erase))) { + retval = -EFAULT; + break; + } + req = uinput_request_find(udev, ff_erase.request_id); + if (!(req && req->code==UI_FF_ERASE)) { + retval = -EINVAL; + break; + } + req->retval = ff_erase.retval; + req->completed = 1; + wake_up_interruptible(&req->waitq); + break; + + default: + retval = -EINVAL; + } + return retval; +} + +static struct file_operations uinput_fops = { + .owner = THIS_MODULE, + .open = uinput_open, + .release = uinput_close, + .read = uinput_read, + .write = uinput_write, + .poll = uinput_poll, + .ioctl = uinput_ioctl, +}; + +static struct miscdevice uinput_misc = { + .fops = &uinput_fops, + .minor = UINPUT_MINOR, + .name = UINPUT_NAME, +}; + +static int __init uinput_init(void) +{ + return misc_register(&uinput_misc); +} + +static void __exit uinput_exit(void) +{ + misc_deregister(&uinput_misc); +} + +MODULE_AUTHOR("Aristeu Sergio Rozanski Filho"); +MODULE_DESCRIPTION("User level driver support for input subsystem"); +MODULE_LICENSE("GPL"); + +module_init(uinput_init); +module_exit(uinput_exit); + diff --git a/drivers/input/mouse/Kconfig b/drivers/input/mouse/Kconfig new file mode 100644 index 000000000000..537154dd7a87 --- /dev/null +++ b/drivers/input/mouse/Kconfig @@ -0,0 +1,138 @@ +# +# Mouse driver configuration +# +menuconfig INPUT_MOUSE + bool "Mouse" + default y + help + Say Y here, and a list of supported mice will be displayed. + This option doesn't affect the kernel. + + If unsure, say Y. + +if INPUT_MOUSE + +config MOUSE_PS2 + tristate "PS/2 mouse" + default y + select SERIO + select SERIO_LIBPS2 + select SERIO_I8042 if PC + select SERIO_GSCPS2 if GSC + ---help--- + Say Y here if you have a PS/2 mouse connected to your system. This + includes the standard 2 or 3-button PS/2 mouse, as well as PS/2 + mice with wheels and extra buttons, Microsoft, Logitech or Genius + compatible. + + Synaptics TouchPad users might be interested in a specialized + XFree86 driver at: + <http://w1.894.telia.com/~u89404340/touchpad/index.html> + and a new version of GPM at: + <http://www.geocities.com/dt_or/gpm/gpm.html> + to take advantage of the advanced features of the touchpad. + + If unsure, say Y. + + To compile this driver as a module, choose M here: the + module will be called psmouse. + +config MOUSE_SERIAL + tristate "Serial mouse" + select SERIO + ---help--- + Say Y here if you have a serial (RS-232, COM port) mouse connected + to your system. This includes Sun, MouseSystems, Microsoft, + Logitech and all other compatible serial mice. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called sermouse. + +config MOUSE_INPORT + tristate "InPort/MS/ATIXL busmouse" + depends on ISA + help + Say Y here if you have an InPort, Microsoft or ATI XL busmouse. + They are rather rare these days. + + To compile this driver as a module, choose M here: the + module will be called inport. + +config MOUSE_ATIXL + bool "ATI XL variant" + depends on MOUSE_INPORT + help + Say Y here if your mouse is of the ATI XL variety. + +config MOUSE_LOGIBM + tristate "Logitech busmouse" + depends on ISA + help + Say Y here if you have a Logitech busmouse. + They are rather rare these days. + + To compile this driver as a module, choose M here: the + module will be called logibm. + +config MOUSE_PC110PAD + tristate "IBM PC110 touchpad" + depends on ISA + help + Say Y if you have the IBM PC-110 micro-notebook and want its + touchpad supported. + + To compile this driver as a module, choose M here: the + module will be called pc110pad. + +config MOUSE_MAPLE + tristate "Maple bus mouse" + depends on SH_DREAMCAST && MAPLE + help + Say Y if you have a DreamCast console and a mouse attached to + its Maple bus. + + To compile this driver as a module, choose M here: the + module will be called maplemouse. + +config MOUSE_AMIGA + tristate "Amiga mouse" + depends on AMIGA + help + Say Y here if you have an Amiga and want its native mouse + supported by the kernel. + + To compile this driver as a module, choose M here: the + module will be called amimouse. + +config MOUSE_RISCPC + tristate "Acorn RiscPC mouse" + depends on ARCH_ACORN + help + Say Y here if you have the Acorn RiscPC computer and want its + native mouse supported. + + To compile this driver as a module, choose M here: the + module will be called rpcmouse. + +config MOUSE_VSXXXAA + tristate "DEC VSXXX-AA/GA mouse and VSXXX-AB tablet" + select SERIO + help + Say Y (or M) if you want to use a DEC VSXXX-AA (hockey + puck) or a VSXXX-GA (rectangular) mouse. Theses mice are + typically used on DECstations or VAXstations, but can also + be used on any box capable of RS232 (with some adaptor + described in the source file). This driver also works with the + digitizer (VSXXX-AB) DEC produced. + +config MOUSE_HIL + tristate "HIL pointers (mice etc)." + depends on GSC + select HP_SDC + select HIL_MLC + help + Say Y here to support HIL pointers. + +endif diff --git a/drivers/input/mouse/Makefile b/drivers/input/mouse/Makefile new file mode 100644 index 000000000000..a7864195806a --- /dev/null +++ b/drivers/input/mouse/Makefile @@ -0,0 +1,18 @@ +# +# Makefile for the mouse drivers. +# + +# Each configuration option enables a list of files. + +obj-$(CONFIG_MOUSE_AMIGA) += amimouse.o +obj-$(CONFIG_MOUSE_RISCPC) += rpcmouse.o +obj-$(CONFIG_MOUSE_INPORT) += inport.o +obj-$(CONFIG_MOUSE_LOGIBM) += logibm.o +obj-$(CONFIG_MOUSE_MAPLE) += maplemouse.o +obj-$(CONFIG_MOUSE_PC110PAD) += pc110pad.o +obj-$(CONFIG_MOUSE_PS2) += psmouse.o +obj-$(CONFIG_MOUSE_SERIAL) += sermouse.o +obj-$(CONFIG_MOUSE_HIL) += hil_ptr.o +obj-$(CONFIG_MOUSE_VSXXXAA) += vsxxxaa.o + +psmouse-objs := psmouse-base.o alps.o logips2pp.o synaptics.o diff --git a/drivers/input/mouse/alps.c b/drivers/input/mouse/alps.c new file mode 100644 index 000000000000..1f85a9718c89 --- /dev/null +++ b/drivers/input/mouse/alps.c @@ -0,0 +1,477 @@ +/* + * ALPS touchpad PS/2 mouse driver + * + * Copyright (c) 2003 Neil Brown <neilb@cse.unsw.edu.au> + * Copyright (c) 2003 Peter Osterlund <petero2@telia.com> + * Copyright (c) 2004 Dmitry Torokhov <dtor@mail.ru> + * Copyright (c) 2005 Vojtech Pavlik <vojtech@suse.cz> + * + * ALPS detection, tap switching and status querying info is taken from + * tpconfig utility (by C. Scott Ananian and Bruce Kall). + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +#include <linux/input.h> +#include <linux/serio.h> +#include <linux/libps2.h> + +#include "psmouse.h" +#include "alps.h" + +#undef DEBUG +#ifdef DEBUG +#define dbg(format, arg...) printk(KERN_INFO "alps.c: " format "\n", ## arg) +#else +#define dbg(format, arg...) do {} while (0) +#endif + +#define ALPS_DUALPOINT 0x01 +#define ALPS_WHEEL 0x02 +#define ALPS_FW_BK 0x04 +#define ALPS_4BTN 0x08 +#define ALPS_OLDPROTO 0x10 +#define ALPS_PASS 0x20 + +static struct alps_model_info alps_model_data[] = { + { { 0x33, 0x02, 0x0a }, 0x88, 0xf8, ALPS_OLDPROTO }, /* UMAX-530T */ + { { 0x53, 0x02, 0x0a }, 0xf8, 0xf8, 0 }, + { { 0x53, 0x02, 0x14 }, 0xf8, 0xf8, 0 }, + { { 0x63, 0x02, 0x0a }, 0xf8, 0xf8, 0 }, + { { 0x63, 0x02, 0x14 }, 0xf8, 0xf8, 0 }, + { { 0x63, 0x02, 0x28 }, 0xf8, 0xf8, 0 }, + { { 0x63, 0x02, 0x3c }, 0x8f, 0x8f, ALPS_WHEEL }, /* Toshiba Satellite S2400-103 */ + { { 0x63, 0x02, 0x50 }, 0xef, 0xef, ALPS_FW_BK }, /* NEC Versa L320 */ + { { 0x63, 0x02, 0x64 }, 0xf8, 0xf8, 0 }, + { { 0x63, 0x03, 0xc8 }, 0xf8, 0xf8, ALPS_PASS }, /* Dell Latitude D800 */ + { { 0x73, 0x02, 0x0a }, 0xf8, 0xf8, 0 }, + { { 0x73, 0x02, 0x14 }, 0xf8, 0xf8, 0 }, + { { 0x20, 0x02, 0x0e }, 0xf8, 0xf8, ALPS_PASS | ALPS_DUALPOINT }, /* XXX */ + { { 0x22, 0x02, 0x0a }, 0xf8, 0xf8, ALPS_PASS | ALPS_DUALPOINT }, + { { 0x22, 0x02, 0x14 }, 0xff, 0xff, ALPS_PASS | ALPS_DUALPOINT }, /* Dell Latitude D600 */ +}; + +/* + * XXX - this entry is suspicious. First byte has zero lower nibble, + * which is what a normal mouse would report. Also, the value 0x0e + * isn't valid per PS/2 spec. + */ + +/* + * ALPS abolute Mode - new format + * + * byte 0: 1 ? ? ? 1 ? ? ? + * byte 1: 0 x6 x5 x4 x3 x2 x1 x0 + * byte 2: 0 x10 x9 x8 x7 ? fin ges + * byte 3: 0 y9 y8 y7 1 M R L + * byte 4: 0 y6 y5 y4 y3 y2 y1 y0 + * byte 5: 0 z6 z5 z4 z3 z2 z1 z0 + * + * ?'s can have different meanings on different models, + * such as wheel rotation, extra buttons, stick buttons + * on a dualpoint, etc. + */ + +static void alps_process_packet(struct psmouse *psmouse, struct pt_regs *regs) +{ + struct alps_data *priv = psmouse->private; + unsigned char *packet = psmouse->packet; + struct input_dev *dev = &psmouse->dev; + struct input_dev *dev2 = &priv->dev2; + int x, y, z, ges, fin, left, right, middle; + + input_regs(dev, regs); + + if ((packet[0] & 0xc8) == 0x08) { /* 3-byte PS/2 packet */ + input_report_key(dev2, BTN_LEFT, packet[0] & 1); + input_report_key(dev2, BTN_RIGHT, packet[0] & 2); + input_report_key(dev2, BTN_MIDDLE, packet[0] & 4); + input_report_rel(dev2, REL_X, + packet[1] ? packet[1] - ((packet[0] << 4) & 0x100) : 0); + input_report_rel(dev2, REL_Y, + packet[2] ? ((packet[0] << 3) & 0x100) - packet[2] : 0); + input_sync(dev2); + return; + } + + if (priv->i->flags & ALPS_OLDPROTO) { + left = packet[2] & 0x08; + right = packet[2] & 0x10; + middle = 0; + x = packet[1] | ((packet[0] & 0x07) << 7); + y = packet[4] | ((packet[3] & 0x07) << 7); + z = packet[5]; + } else { + left = packet[3] & 1; + right = packet[3] & 2; + middle = packet[3] & 4; + x = packet[1] | ((packet[2] & 0x78) << (7 - 3)); + y = packet[4] | ((packet[3] & 0x70) << (7 - 4)); + z = packet[5]; + } + + ges = packet[2] & 1; + fin = packet[2] & 2; + + input_report_key(dev, BTN_LEFT, left); + input_report_key(dev, BTN_RIGHT, right); + input_report_key(dev, BTN_MIDDLE, middle); + + if ((priv->i->flags & ALPS_DUALPOINT) && z == 127) { + input_report_rel(dev2, REL_X, (x > 383 ? (x - 768) : x)); + input_report_rel(dev2, REL_Y, -(y > 255 ? (y - 512) : y)); + input_sync(dev); + input_sync(dev2); + return; + } + + /* Convert hardware tap to a reasonable Z value */ + if (ges && !fin) z = 40; + + /* + * A "tap and drag" operation is reported by the hardware as a transition + * from (!fin && ges) to (fin && ges). This should be translated to the + * sequence Z>0, Z==0, Z>0, so the Z==0 event has to be generated manually. + */ + if (ges && fin && !priv->prev_fin) { + input_report_abs(dev, ABS_X, x); + input_report_abs(dev, ABS_Y, y); + input_report_abs(dev, ABS_PRESSURE, 0); + input_report_key(dev, BTN_TOOL_FINGER, 0); + input_sync(dev); + } + priv->prev_fin = fin; + + if (z > 30) input_report_key(dev, BTN_TOUCH, 1); + if (z < 25) input_report_key(dev, BTN_TOUCH, 0); + + if (z > 0) { + input_report_abs(dev, ABS_X, x); + input_report_abs(dev, ABS_Y, y); + } + + input_report_abs(dev, ABS_PRESSURE, z); + input_report_key(dev, BTN_TOOL_FINGER, z > 0); + + + if (priv->i->flags & ALPS_WHEEL) + input_report_rel(dev, REL_WHEEL, ((packet[0] >> 4) & 0x07) | ((packet[2] >> 2) & 0x08)); + + if (priv->i->flags & ALPS_FW_BK) { + input_report_key(dev, BTN_FORWARD, packet[0] & 0x10); + input_report_key(dev, BTN_BACK, packet[2] & 0x04); + } + + input_sync(dev); +} + +static psmouse_ret_t alps_process_byte(struct psmouse *psmouse, struct pt_regs *regs) +{ + struct alps_data *priv = psmouse->private; + + if ((psmouse->packet[0] & 0xc8) == 0x08) { /* PS/2 packet */ + if (psmouse->pktcnt == 3) { + alps_process_packet(psmouse, regs); + return PSMOUSE_FULL_PACKET; + } + return PSMOUSE_GOOD_DATA; + } + + if ((psmouse->packet[0] & priv->i->mask0) != priv->i->byte0) + return PSMOUSE_BAD_DATA; + + /* Bytes 2 - 6 should have 0 in the highest bit */ + if (psmouse->pktcnt >= 2 && psmouse->pktcnt <= 6 && + (psmouse->packet[psmouse->pktcnt - 1] & 0x80)) + return PSMOUSE_BAD_DATA; + + if (psmouse->pktcnt == 6) { + alps_process_packet(psmouse, regs); + return PSMOUSE_FULL_PACKET; + } + + return PSMOUSE_GOOD_DATA; +} + +static struct alps_model_info *alps_get_model(struct psmouse *psmouse, int *version) +{ + struct ps2dev *ps2dev = &psmouse->ps2dev; + unsigned char rates[] = { 0, 10, 20, 40, 60, 80, 100, 200 }; + unsigned char param[4]; + int i; + + /* + * First try "E6 report". + * ALPS should return 0,0,10 or 0,0,100 + */ + param[0] = 0; + if (ps2_command(ps2dev, param, PSMOUSE_CMD_SETRES) || + ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSCALE11) || + ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSCALE11) || + ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSCALE11)) + return NULL; + + param[0] = param[1] = param[2] = 0xff; + if (ps2_command(ps2dev, param, PSMOUSE_CMD_GETINFO)) + return NULL; + + dbg("E6 report: %2.2x %2.2x %2.2x", param[0], param[1], param[2]); + + if (param[0] != 0 || param[1] != 0 || (param[2] != 10 && param[2] != 100)) + return NULL; + + /* + * Now try "E7 report". Allowed responses are in + * alps_model_data[].signature + */ + param[0] = 0; + if (ps2_command(ps2dev, param, PSMOUSE_CMD_SETRES) || + ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSCALE21) || + ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSCALE21) || + ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSCALE21)) + return NULL; + + param[0] = param[1] = param[2] = 0xff; + if (ps2_command(ps2dev, param, PSMOUSE_CMD_GETINFO)) + return NULL; + + dbg("E7 report: %2.2x %2.2x %2.2x", param[0], param[1], param[2]); + + for (i = 0; i < ARRAY_SIZE(rates) && param[2] != rates[i]; i++); + *version = (param[0] << 8) | (param[1] << 4) | i; + + for (i = 0; i < ARRAY_SIZE(alps_model_data); i++) + if (!memcmp(param, alps_model_data[i].signature, sizeof(alps_model_data[i].signature))) + return alps_model_data + i; + + return NULL; +} + +/* + * For DualPoint devices select the device that should respond to + * subsequent commands. It looks like glidepad is behind stickpointer, + * I'd thought it would be other way around... + */ +static int alps_passthrough_mode(struct psmouse *psmouse, int enable) +{ + struct ps2dev *ps2dev = &psmouse->ps2dev; + unsigned char param[3]; + int cmd = enable ? PSMOUSE_CMD_SETSCALE21 : PSMOUSE_CMD_SETSCALE11; + + if (ps2_command(ps2dev, NULL, cmd) || + ps2_command(ps2dev, NULL, cmd) || + ps2_command(ps2dev, NULL, cmd) || + ps2_command(ps2dev, NULL, PSMOUSE_CMD_DISABLE)) + return -1; + + /* we may get 3 more bytes, just ignore them */ + ps2_command(ps2dev, param, 0x0300); + + return 0; +} + +static int alps_absolute_mode(struct psmouse *psmouse) +{ + struct ps2dev *ps2dev = &psmouse->ps2dev; + + /* Try ALPS magic knock - 4 disable before enable */ + if (ps2_command(ps2dev, NULL, PSMOUSE_CMD_DISABLE) || + ps2_command(ps2dev, NULL, PSMOUSE_CMD_DISABLE) || + ps2_command(ps2dev, NULL, PSMOUSE_CMD_DISABLE) || + ps2_command(ps2dev, NULL, PSMOUSE_CMD_DISABLE) || + ps2_command(ps2dev, NULL, PSMOUSE_CMD_ENABLE)) + return -1; + + /* + * Switch mouse to poll (remote) mode so motion data will not + * get in our way + */ + return ps2_command(&psmouse->ps2dev, NULL, PSMOUSE_CMD_SETPOLL); +} + +static int alps_get_status(struct psmouse *psmouse, char *param) +{ + struct ps2dev *ps2dev = &psmouse->ps2dev; + + /* Get status: 0xF5 0xF5 0xF5 0xE9 */ + if (ps2_command(ps2dev, NULL, PSMOUSE_CMD_DISABLE) || + ps2_command(ps2dev, NULL, PSMOUSE_CMD_DISABLE) || + ps2_command(ps2dev, NULL, PSMOUSE_CMD_DISABLE) || + ps2_command(ps2dev, param, PSMOUSE_CMD_GETINFO)) + return -1; + + dbg("Status: %2.2x %2.2x %2.2x", param[0], param[1], param[2]); + + return 0; +} + +/* + * Turn touchpad tapping on or off. The sequences are: + * 0xE9 0xF5 0xF5 0xF3 0x0A to enable, + * 0xE9 0xF5 0xF5 0xE8 0x00 to disable. + * My guess that 0xE9 (GetInfo) is here as a sync point. + * For models that also have stickpointer (DualPoints) its tapping + * is controlled separately (0xE6 0xE6 0xE6 0xF3 0x14|0x0A) but + * we don't fiddle with it. + */ +static int alps_tap_mode(struct psmouse *psmouse, int enable) +{ + struct ps2dev *ps2dev = &psmouse->ps2dev; + int cmd = enable ? PSMOUSE_CMD_SETRATE : PSMOUSE_CMD_SETRES; + unsigned char tap_arg = enable ? 0x0A : 0x00; + unsigned char param[4]; + + if (ps2_command(ps2dev, param, PSMOUSE_CMD_GETINFO) || + ps2_command(ps2dev, NULL, PSMOUSE_CMD_DISABLE) || + ps2_command(ps2dev, NULL, PSMOUSE_CMD_DISABLE) || + ps2_command(ps2dev, &tap_arg, cmd)) + return -1; + + if (alps_get_status(psmouse, param)) + return -1; + + return 0; +} + +static int alps_reconnect(struct psmouse *psmouse) +{ + struct alps_data *priv = psmouse->private; + unsigned char param[4]; + int version; + + if (!(priv->i = alps_get_model(psmouse, &version))) + return -1; + + if (priv->i->flags & ALPS_PASS && alps_passthrough_mode(psmouse, 1)) + return -1; + + if (alps_get_status(psmouse, param)) + return -1; + + if (param[0] & 0x04) + alps_tap_mode(psmouse, 1); + + if (alps_absolute_mode(psmouse)) { + printk(KERN_ERR "alps.c: Failed to enable absolute mode\n"); + return -1; + } + + if (priv->i->flags == ALPS_PASS && alps_passthrough_mode(psmouse, 0)) + return -1; + + return 0; +} + +static void alps_disconnect(struct psmouse *psmouse) +{ + struct alps_data *priv = psmouse->private; + psmouse_reset(psmouse); + input_unregister_device(&priv->dev2); + kfree(priv); +} + +int alps_init(struct psmouse *psmouse) +{ + struct alps_data *priv; + unsigned char param[4]; + int version; + + psmouse->private = priv = kmalloc(sizeof(struct alps_data), GFP_KERNEL); + if (!priv) + goto init_fail; + memset(priv, 0, sizeof(struct alps_data)); + + if (!(priv->i = alps_get_model(psmouse, &version))) + goto init_fail; + + if ((priv->i->flags & ALPS_PASS) && alps_passthrough_mode(psmouse, 1)) + goto init_fail; + + if (alps_get_status(psmouse, param)) { + printk(KERN_ERR "alps.c: touchpad status report request failed\n"); + goto init_fail; + } + + if (param[0] & 0x04) { + printk(KERN_INFO " Enabling hardware tapping\n"); + if (alps_tap_mode(psmouse, 1)) + printk(KERN_WARNING "alps.c: Failed to enable hardware tapping\n"); + } + + if (alps_absolute_mode(psmouse)) { + printk(KERN_ERR "alps.c: Failed to enable absolute mode\n"); + goto init_fail; + } + + if ((priv->i->flags & ALPS_PASS) && alps_passthrough_mode(psmouse, 0)) + goto init_fail; + + psmouse->dev.evbit[LONG(EV_KEY)] |= BIT(EV_KEY); + psmouse->dev.keybit[LONG(BTN_TOUCH)] |= BIT(BTN_TOUCH); + psmouse->dev.keybit[LONG(BTN_TOOL_FINGER)] |= BIT(BTN_TOOL_FINGER); + psmouse->dev.keybit[LONG(BTN_LEFT)] |= BIT(BTN_LEFT) | BIT(BTN_MIDDLE) | BIT(BTN_RIGHT); + + psmouse->dev.evbit[LONG(EV_ABS)] |= BIT(EV_ABS); + input_set_abs_params(&psmouse->dev, ABS_X, 0, 1023, 0, 0); + input_set_abs_params(&psmouse->dev, ABS_Y, 0, 767, 0, 0); + input_set_abs_params(&psmouse->dev, ABS_PRESSURE, 0, 127, 0, 0); + + if (priv->i->flags & ALPS_WHEEL) { + psmouse->dev.evbit[LONG(EV_REL)] |= BIT(EV_REL); + psmouse->dev.relbit[LONG(REL_WHEEL)] |= BIT(REL_WHEEL); + } + + if (priv->i->flags & ALPS_FW_BK) { + psmouse->dev.keybit[LONG(BTN_FORWARD)] |= BIT(BTN_FORWARD); + psmouse->dev.keybit[LONG(BTN_BACK)] |= BIT(BTN_BACK); + } + + sprintf(priv->phys, "%s/input1", psmouse->ps2dev.serio->phys); + priv->dev2.phys = priv->phys; + priv->dev2.name = (priv->i->flags & ALPS_DUALPOINT) ? "DualPoint Stick" : "PS/2 Mouse"; + priv->dev2.id.bustype = BUS_I8042; + priv->dev2.id.vendor = 0x0002; + priv->dev2.id.product = PSMOUSE_ALPS; + priv->dev2.id.version = 0x0000; + + priv->dev2.evbit[0] = BIT(EV_KEY) | BIT(EV_REL); + priv->dev2.relbit[LONG(REL_X)] |= BIT(REL_X) | BIT(REL_Y); + priv->dev2.keybit[LONG(BTN_LEFT)] |= BIT(BTN_LEFT) | BIT(BTN_MIDDLE) | BIT(BTN_RIGHT); + + input_register_device(&priv->dev2); + + printk(KERN_INFO "input: %s on %s\n", priv->dev2.name, psmouse->ps2dev.serio->phys); + + psmouse->protocol_handler = alps_process_byte; + psmouse->disconnect = alps_disconnect; + psmouse->reconnect = alps_reconnect; + psmouse->pktsize = 6; + + return 0; + +init_fail: + kfree(priv); + return -1; +} + +int alps_detect(struct psmouse *psmouse, int set_properties) +{ + int version; + struct alps_model_info *model; + + if (!(model = alps_get_model(psmouse, &version))) + return -1; + + if (set_properties) { + psmouse->vendor = "ALPS"; + if (model->flags & ALPS_DUALPOINT) + psmouse->name = "DualPoint TouchPad"; + else + psmouse->name = "GlidePoint"; + psmouse->model = version; + } + return 0; +} + diff --git a/drivers/input/mouse/alps.h b/drivers/input/mouse/alps.h new file mode 100644 index 000000000000..aba103dd65b7 --- /dev/null +++ b/drivers/input/mouse/alps.h @@ -0,0 +1,32 @@ +/* + * ALPS touchpad PS/2 mouse driver + * + * Copyright (c) 2003 Peter Osterlund <petero2@telia.com> + * Copyright (c) 2005 Vojtech Pavlik <vojtech@suse.cz> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +#ifndef _ALPS_H +#define _ALPS_H + +int alps_detect(struct psmouse *psmouse, int set_properties); +int alps_init(struct psmouse *psmouse); + +struct alps_model_info { + unsigned char signature[3]; + unsigned char byte0, mask0; + unsigned char flags; +}; + +struct alps_data { + struct input_dev dev2; /* Relative device */ + char name[32]; /* Name */ + char phys[32]; /* Phys */ + struct alps_model_info *i; /* Info */ + int prev_fin; /* Finger bit from previous packet */ +}; + +#endif diff --git a/drivers/input/mouse/amimouse.c b/drivers/input/mouse/amimouse.c new file mode 100644 index 000000000000..7baa09cca7c5 --- /dev/null +++ b/drivers/input/mouse/amimouse.c @@ -0,0 +1,137 @@ +/* + * Amiga mouse driver for Linux/m68k + * + * Copyright (c) 2000-2002 Vojtech Pavlik + * + * Based on the work of: + * Michael Rausch James Banks + * Matther Dillon David Giller + * Nathan Laredo Linus Torvalds + * Johan Myreen Jes Sorensen + * Russell King + */ + +/* + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/interrupt.h> + +#include <asm/irq.h> +#include <asm/setup.h> +#include <asm/system.h> +#include <asm/uaccess.h> +#include <asm/amigahw.h> +#include <asm/amigaints.h> + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION("Amiga mouse driver"); +MODULE_LICENSE("GPL"); + +static int amimouse_used = 0; +static int amimouse_lastx, amimouse_lasty; +static struct input_dev amimouse_dev; + +static char *amimouse_name = "Amiga mouse"; +static char *amimouse_phys = "amimouse/input0"; + +static irqreturn_t amimouse_interrupt(int irq, void *dummy, struct pt_regs *fp) +{ + unsigned short joy0dat, potgor; + int nx, ny, dx, dy; + + joy0dat = custom.joy0dat; + + nx = joy0dat & 0xff; + ny = joy0dat >> 8; + + dx = nx - amimouse_lastx; + dy = ny - amimouse_lasty; + + if (dx < -127) dx = (256 + nx) - amimouse_lastx; + if (dx > 127) dx = (nx - 256) - amimouse_lastx; + if (dy < -127) dy = (256 + ny) - amimouse_lasty; + if (dy > 127) dy = (ny - 256) - amimouse_lasty; + + amimouse_lastx = nx; + amimouse_lasty = ny; + + potgor = custom.potgor; + + input_regs(&amimouse_dev, fp); + + input_report_rel(&amimouse_dev, REL_X, dx); + input_report_rel(&amimouse_dev, REL_Y, dy); + + input_report_key(&amimouse_dev, BTN_LEFT, ciaa.pra & 0x40); + input_report_key(&amimouse_dev, BTN_MIDDLE, potgor & 0x0100); + input_report_key(&amimouse_dev, BTN_RIGHT, potgor & 0x0400); + + input_sync(&amimouse_dev); + + return IRQ_HANDLED; +} + +static int amimouse_open(struct input_dev *dev) +{ + unsigned short joy0dat; + + if (amimouse_used++) + return 0; + + joy0dat = custom.joy0dat; + + amimouse_lastx = joy0dat & 0xff; + amimouse_lasty = joy0dat >> 8; + + if (request_irq(IRQ_AMIGA_VERTB, amimouse_interrupt, 0, "amimouse", amimouse_interrupt)) { + amimouse_used--; + printk(KERN_ERR "amimouse.c: Can't allocate irq %d\n", IRQ_AMIGA_VERTB); + return -EBUSY; + } + + return 0; +} + +static void amimouse_close(struct input_dev *dev) +{ + if (!--amimouse_used) + free_irq(IRQ_AMIGA_VERTB, amimouse_interrupt); +} + +static int __init amimouse_init(void) +{ + if (!MACH_IS_AMIGA || !AMIGAHW_PRESENT(AMI_MOUSE)) + return -ENODEV; + + amimouse_dev.evbit[0] = BIT(EV_KEY) | BIT(EV_REL); + amimouse_dev.relbit[0] = BIT(REL_X) | BIT(REL_Y); + amimouse_dev.keybit[LONG(BTN_LEFT)] = BIT(BTN_LEFT) | BIT(BTN_MIDDLE) | BIT(BTN_RIGHT); + amimouse_dev.open = amimouse_open; + amimouse_dev.close = amimouse_close; + + amimouse_dev.name = amimouse_name; + amimouse_dev.phys = amimouse_phys; + amimouse_dev.id.bustype = BUS_AMIGA; + amimouse_dev.id.vendor = 0x0001; + amimouse_dev.id.product = 0x0002; + amimouse_dev.id.version = 0x0100; + + input_register_device(&amimouse_dev); + + printk(KERN_INFO "input: %s at joy0dat\n", amimouse_name); + return 0; +} + +static void __exit amimouse_exit(void) +{ + input_unregister_device(&amimouse_dev); +} + +module_init(amimouse_init); +module_exit(amimouse_exit); diff --git a/drivers/input/mouse/hil_ptr.c b/drivers/input/mouse/hil_ptr.c new file mode 100644 index 000000000000..bc22849c6c79 --- /dev/null +++ b/drivers/input/mouse/hil_ptr.c @@ -0,0 +1,414 @@ +/* + * Generic linux-input device driver for axis-bearing devices + * + * Copyright (c) 2001 Brian S. Julin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * Alternatively, this software may be distributed under the terms of the + * GNU General Public License ("GPL"). + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * + * References: + * HP-HIL Technical Reference Manual. Hewlett Packard Product No. 45918A + * + */ + +#include <linux/hil.h> +#include <linux/input.h> +#include <linux/serio.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/pci_ids.h> + +#define PREFIX "HIL PTR: " +#define HIL_GENERIC_NAME "HIL pointer device" + +MODULE_AUTHOR("Brian S. Julin <bri@calyx.com>"); +MODULE_DESCRIPTION(HIL_GENERIC_NAME " driver"); +MODULE_LICENSE("Dual BSD/GPL"); + + +#define TABLET_SIMULATES_MOUSE /* allow tablet to be used as mouse */ +#undef TABLET_AUTOADJUST /* auto-adjust valid tablet ranges */ + + +#define HIL_PTR_MAX_LENGTH 16 + +struct hil_ptr { + struct input_dev dev; + struct serio *serio; + + /* Input buffer and index for packets from HIL bus. */ + hil_packet data[HIL_PTR_MAX_LENGTH]; + int idx4; /* four counts per packet */ + + /* Raw device info records from HIL bus, see hil.h for fields. */ + char idd[HIL_PTR_MAX_LENGTH]; /* DID byte and IDD record */ + char rsc[HIL_PTR_MAX_LENGTH]; /* RSC record */ + char exd[HIL_PTR_MAX_LENGTH]; /* EXD record */ + char rnm[HIL_PTR_MAX_LENGTH + 1]; /* RNM record + NULL term. */ + + /* Extra device details not contained in struct input_dev. */ + unsigned int nbtn, naxes; + unsigned int btnmap[7]; + + /* Something to sleep around with. */ + struct semaphore sem; +}; + +/* Process a complete packet after transfer from the HIL */ +static void hil_ptr_process_record(struct hil_ptr *ptr) +{ + struct input_dev *dev = &ptr->dev; + hil_packet *data = ptr->data; + hil_packet p; + int idx, i, cnt, laxis; + int ax16, absdev; + + idx = ptr->idx4/4; + p = data[idx - 1]; + + if ((p & ~HIL_CMDCT_POL) == + (HIL_ERR_INT | HIL_PKT_CMD | HIL_CMD_POL)) goto report; + if ((p & ~HIL_CMDCT_RPL) == + (HIL_ERR_INT | HIL_PKT_CMD | HIL_CMD_RPL)) goto report; + + /* Not a poll response. See if we are loading config records. */ + switch (p & HIL_PKT_DATA_MASK) { + case HIL_CMD_IDD: + for (i = 0; i < idx; i++) + ptr->idd[i] = ptr->data[i] & HIL_PKT_DATA_MASK; + for (; i < HIL_PTR_MAX_LENGTH; i++) + ptr->idd[i] = 0; + break; + case HIL_CMD_RSC: + for (i = 0; i < idx; i++) + ptr->rsc[i] = ptr->data[i] & HIL_PKT_DATA_MASK; + for (; i < HIL_PTR_MAX_LENGTH; i++) + ptr->rsc[i] = 0; + break; + case HIL_CMD_EXD: + for (i = 0; i < idx; i++) + ptr->exd[i] = ptr->data[i] & HIL_PKT_DATA_MASK; + for (; i < HIL_PTR_MAX_LENGTH; i++) + ptr->exd[i] = 0; + break; + case HIL_CMD_RNM: + for (i = 0; i < idx; i++) + ptr->rnm[i] = ptr->data[i] & HIL_PKT_DATA_MASK; + for (; i < HIL_PTR_MAX_LENGTH + 1; i++) + ptr->rnm[i] = '\0'; + break; + default: + /* These occur when device isn't present */ + if (p == (HIL_ERR_INT | HIL_PKT_CMD)) break; + /* Anything else we'd like to know about. */ + printk(KERN_WARNING PREFIX "Device sent unknown record %x\n", p); + break; + } + goto out; + + report: + if ((p & HIL_CMDCT_POL) != idx - 1) { + printk(KERN_WARNING PREFIX "Malformed poll packet %x (idx = %i)\n", p, idx); + goto out; + } + + i = (ptr->data[0] & HIL_POL_AXIS_ALT) ? 3 : 0; + laxis = ptr->data[0] & HIL_POL_NUM_AXES_MASK; + laxis += i; + + ax16 = ptr->idd[1] & HIL_IDD_HEADER_16BIT; /* 8 or 16bit resolution */ + absdev = ptr->idd[1] & HIL_IDD_HEADER_ABS; + + for (cnt = 1; i < laxis; i++) { + unsigned int lo,hi,val; + lo = ptr->data[cnt++] & HIL_PKT_DATA_MASK; + hi = ax16 ? (ptr->data[cnt++] & HIL_PKT_DATA_MASK) : 0; + if (absdev) { + val = lo + (hi<<8); +#ifdef TABLET_AUTOADJUST + if (val < ptr->dev.absmin[ABS_X + i]) + ptr->dev.absmin[ABS_X + i] = val; + if (val > ptr->dev.absmax[ABS_X + i]) + ptr->dev.absmax[ABS_X + i] = val; +#endif + if (i%3) val = ptr->dev.absmax[ABS_X + i] - val; + input_report_abs(dev, ABS_X + i, val); + } else { + val = (int) (((int8_t)lo) | ((int8_t)hi<<8)); + if (i%3) val *= -1; + input_report_rel(dev, REL_X + i, val); + } + } + + while (cnt < idx - 1) { + unsigned int btn; + int up; + btn = ptr->data[cnt++]; + up = btn & 1; + btn &= 0xfe; + if (btn == 0x8e) { + continue; /* TODO: proximity == touch? */ + } + else if ((btn > 0x8c) || (btn < 0x80)) continue; + btn = (btn - 0x80) >> 1; + btn = ptr->btnmap[btn]; + input_report_key(dev, btn, !up); + } + input_sync(dev); + out: + ptr->idx4 = 0; + up(&ptr->sem); +} + +static void hil_ptr_process_err(struct hil_ptr *ptr) { + printk(KERN_WARNING PREFIX "errored HIL packet\n"); + ptr->idx4 = 0; + up(&ptr->sem); + return; +} + +static irqreturn_t hil_ptr_interrupt(struct serio *serio, + unsigned char data, unsigned int flags, struct pt_regs *regs) +{ + struct hil_ptr *ptr; + hil_packet packet; + int idx; + + ptr = (struct hil_ptr *)serio->private; + if (ptr == NULL) { + BUG(); + return IRQ_HANDLED; + } + + if (ptr->idx4 >= (HIL_PTR_MAX_LENGTH * sizeof(hil_packet))) { + hil_ptr_process_err(ptr); + return IRQ_HANDLED; + } + idx = ptr->idx4/4; + if (!(ptr->idx4 % 4)) ptr->data[idx] = 0; + packet = ptr->data[idx]; + packet |= ((hil_packet)data) << ((3 - (ptr->idx4 % 4)) * 8); + ptr->data[idx] = packet; + + /* Records of N 4-byte hil_packets must terminate with a command. */ + if ((++(ptr->idx4)) % 4) return IRQ_HANDLED; + if ((packet & 0xffff0000) != HIL_ERR_INT) { + hil_ptr_process_err(ptr); + return IRQ_HANDLED; + } + if (packet & HIL_PKT_CMD) + hil_ptr_process_record(ptr); + return IRQ_HANDLED; +} + +static void hil_ptr_disconnect(struct serio *serio) +{ + struct hil_ptr *ptr; + + ptr = (struct hil_ptr *)serio->private; + if (ptr == NULL) { + BUG(); + return; + } + + input_unregister_device(&ptr->dev); + serio_close(serio); + kfree(ptr); +} + +static void hil_ptr_connect(struct serio *serio, struct serio_driver *driver) +{ + struct hil_ptr *ptr; + char *txt; + unsigned int i, naxsets, btntype; + uint8_t did, *idd; + + if (serio->type != (SERIO_HIL_MLC | SERIO_HIL)) return; + + if (!(ptr = kmalloc(sizeof(struct hil_ptr), GFP_KERNEL))) return; + memset(ptr, 0, sizeof(struct hil_ptr)); + + if (serio_open(serio, driver)) goto bail0; + + serio->private = ptr; + ptr->serio = serio; + ptr->dev.private = ptr; + + init_MUTEX_LOCKED(&(ptr->sem)); + + /* Get device info. MLC driver supplies devid/status/etc. */ + serio->write(serio, 0); + serio->write(serio, 0); + serio->write(serio, HIL_PKT_CMD >> 8); + serio->write(serio, HIL_CMD_IDD); + down(&(ptr->sem)); + + serio->write(serio, 0); + serio->write(serio, 0); + serio->write(serio, HIL_PKT_CMD >> 8); + serio->write(serio, HIL_CMD_RSC); + down(&(ptr->sem)); + + serio->write(serio, 0); + serio->write(serio, 0); + serio->write(serio, HIL_PKT_CMD >> 8); + serio->write(serio, HIL_CMD_RNM); + down(&(ptr->sem)); + + serio->write(serio, 0); + serio->write(serio, 0); + serio->write(serio, HIL_PKT_CMD >> 8); + serio->write(serio, HIL_CMD_EXD); + down(&(ptr->sem)); + + up(&(ptr->sem)); + + init_input_dev(&ptr->dev); + did = ptr->idd[0]; + idd = ptr->idd + 1; + txt = "unknown"; + if ((did & HIL_IDD_DID_TYPE_MASK) == HIL_IDD_DID_TYPE_REL) { + ptr->dev.evbit[0] = BIT(EV_REL); + txt = "relative"; + } + + if ((did & HIL_IDD_DID_TYPE_MASK) == HIL_IDD_DID_TYPE_ABS) { + ptr->dev.evbit[0] = BIT(EV_ABS); + txt = "absolute"; + } + if (!ptr->dev.evbit[0]) { + goto bail1; + } + + ptr->nbtn = HIL_IDD_NUM_BUTTONS(idd); + if (ptr->nbtn) ptr->dev.evbit[0] |= BIT(EV_KEY); + + naxsets = HIL_IDD_NUM_AXSETS(*idd); + ptr->naxes = HIL_IDD_NUM_AXES_PER_SET(*idd); + + printk(KERN_INFO PREFIX "HIL pointer device found (did: 0x%02x, axis: %s)\n", + did, txt); + printk(KERN_INFO PREFIX "HIL pointer has %i buttons and %i sets of %i axes\n", + ptr->nbtn, naxsets, ptr->naxes); + + btntype = BTN_MISC; + if ((did & HIL_IDD_DID_ABS_TABLET_MASK) == HIL_IDD_DID_ABS_TABLET) +#ifdef TABLET_SIMULATES_MOUSE + btntype = BTN_TOUCH; +#else + btntype = BTN_DIGI; +#endif + if ((did & HIL_IDD_DID_ABS_TSCREEN_MASK) == HIL_IDD_DID_ABS_TSCREEN) + btntype = BTN_TOUCH; + + if ((did & HIL_IDD_DID_REL_MOUSE_MASK) == HIL_IDD_DID_REL_MOUSE) + btntype = BTN_MOUSE; + + for (i = 0; i < ptr->nbtn; i++) { + set_bit(btntype | i, ptr->dev.keybit); + ptr->btnmap[i] = btntype | i; + } + + if (btntype == BTN_MOUSE) { + /* Swap buttons 2 and 3 */ + ptr->btnmap[1] = BTN_MIDDLE; + ptr->btnmap[2] = BTN_RIGHT; + } + + if ((did & HIL_IDD_DID_TYPE_MASK) == HIL_IDD_DID_TYPE_REL) { + for (i = 0; i < ptr->naxes; i++) { + set_bit(REL_X + i, ptr->dev.relbit); + } + for (i = 3; (i < ptr->naxes + 3) && (naxsets > 1); i++) { + set_bit(REL_X + i, ptr->dev.relbit); + } + } else { + for (i = 0; i < ptr->naxes; i++) { + set_bit(ABS_X + i, ptr->dev.absbit); + ptr->dev.absmin[ABS_X + i] = 0; + ptr->dev.absmax[ABS_X + i] = + HIL_IDD_AXIS_MAX((ptr->idd + 1), i); + } + for (i = 3; (i < ptr->naxes + 3) && (naxsets > 1); i++) { + set_bit(ABS_X + i, ptr->dev.absbit); + ptr->dev.absmin[ABS_X + i] = 0; + ptr->dev.absmax[ABS_X + i] = + HIL_IDD_AXIS_MAX((ptr->idd + 1), (i - 3)); + } +#ifdef TABLET_AUTOADJUST + for (i = 0; i < ABS_MAX; i++) { + int diff = ptr->dev.absmax[ABS_X + i] / 10; + ptr->dev.absmin[ABS_X + i] += diff; + ptr->dev.absmax[ABS_X + i] -= diff; + } +#endif + } + + ptr->dev.name = strlen(ptr->rnm) ? ptr->rnm : HIL_GENERIC_NAME; + + ptr->dev.id.bustype = BUS_HIL; + ptr->dev.id.vendor = PCI_VENDOR_ID_HP; + ptr->dev.id.product = 0x0001; /* TODO: get from ptr->rsc */ + ptr->dev.id.version = 0x0100; /* TODO: get from ptr->rsc */ + ptr->dev.dev = &serio->dev; + + input_register_device(&ptr->dev); + printk(KERN_INFO "input: %s (%s), ID: %d\n", + ptr->dev.name, + (btntype == BTN_MOUSE) ? "HIL mouse":"HIL tablet or touchpad", + did); + + return; + bail1: + serio_close(serio); + bail0: + kfree(ptr); + return; +} + + +static struct serio_driver hil_ptr_serio_driver = { + .driver = { + .name = "hil_ptr", + }, + .description = "HP HIL mouse/tablet driver", + .connect = hil_ptr_connect, + .disconnect = hil_ptr_disconnect, + .interrupt = hil_ptr_interrupt +}; + +static int __init hil_ptr_init(void) +{ + serio_register_driver(&hil_ptr_serio_driver); + return 0; +} + +static void __exit hil_ptr_exit(void) +{ + serio_unregister_driver(&hil_ptr_serio_driver); +} + +module_init(hil_ptr_init); +module_exit(hil_ptr_exit); diff --git a/drivers/input/mouse/inport.c b/drivers/input/mouse/inport.c new file mode 100644 index 000000000000..ca4e96886627 --- /dev/null +++ b/drivers/input/mouse/inport.c @@ -0,0 +1,196 @@ +/* + * $Id: inport.c,v 1.11 2001/09/25 10:12:07 vojtech Exp $ + * + * Copyright (c) 1999-2001 Vojtech Pavlik + * + * Based on the work of: + * Teemu Rantanen Derrick Cole + * Peter Cervasio Christoph Niemann + * Philip Blundell Russell King + * Bob Harris + */ + +/* + * Inport (ATI XL and Microsoft) busmouse driver for Linux + */ + +/* + * 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 either by + * e-mail - mail your message to <vojtech@ucw.cz>, or by paper mail: + * Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/config.h> +#include <linux/ioport.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/input.h> + +#include <asm/io.h> +#include <asm/irq.h> + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION("Inport (ATI XL and Microsoft) busmouse driver"); +MODULE_LICENSE("GPL"); + +#define INPORT_BASE 0x23c +#define INPORT_EXTENT 4 + +#define INPORT_CONTROL_PORT INPORT_BASE + 0 +#define INPORT_DATA_PORT INPORT_BASE + 1 +#define INPORT_SIGNATURE_PORT INPORT_BASE + 2 + +#define INPORT_REG_BTNS 0x00 +#define INPORT_REG_X 0x01 +#define INPORT_REG_Y 0x02 +#define INPORT_REG_MODE 0x07 +#define INPORT_RESET 0x80 + +#ifdef CONFIG_INPUT_ATIXL +#define INPORT_NAME "ATI XL Mouse" +#define INPORT_VENDOR 0x0002 +#define INPORT_SPEED_30HZ 0x01 +#define INPORT_SPEED_50HZ 0x02 +#define INPORT_SPEED_100HZ 0x03 +#define INPORT_SPEED_200HZ 0x04 +#define INPORT_MODE_BASE INPORT_SPEED_100HZ +#define INPORT_MODE_IRQ 0x08 +#else +#define INPORT_NAME "Microsoft InPort Mouse" +#define INPORT_VENDOR 0x0001 +#define INPORT_MODE_BASE 0x10 +#define INPORT_MODE_IRQ 0x01 +#endif +#define INPORT_MODE_HOLD 0x20 + +#define INPORT_IRQ 5 + +static int inport_irq = INPORT_IRQ; +module_param_named(irq, inport_irq, uint, 0); +MODULE_PARM_DESC(irq, "IRQ number (5=default)"); + +__obsolete_setup("inport_irq="); + +static int inport_used; + +static irqreturn_t inport_interrupt(int irq, void *dev_id, struct pt_regs *regs); + +static int inport_open(struct input_dev *dev) +{ + if (!inport_used++) { + if (request_irq(inport_irq, inport_interrupt, 0, "inport", NULL)) + return -EBUSY; + outb(INPORT_REG_MODE, INPORT_CONTROL_PORT); + outb(INPORT_MODE_IRQ | INPORT_MODE_BASE, INPORT_DATA_PORT); + } + + return 0; +} + +static void inport_close(struct input_dev *dev) +{ + if (!--inport_used) { + outb(INPORT_REG_MODE, INPORT_CONTROL_PORT); + outb(INPORT_MODE_BASE, INPORT_DATA_PORT); + free_irq(inport_irq, NULL); + } +} + +static struct input_dev inport_dev = { + .evbit = { BIT(EV_KEY) | BIT(EV_REL) }, + .keybit = { [LONG(BTN_LEFT)] = BIT(BTN_LEFT) | BIT(BTN_MIDDLE) | BIT(BTN_RIGHT) }, + .relbit = { BIT(REL_X) | BIT(REL_Y) }, + .open = inport_open, + .close = inport_close, + .name = INPORT_NAME, + .phys = "isa023c/input0", + .id = { + .bustype = BUS_ISA, + .vendor = INPORT_VENDOR, + .product = 0x0001, + .version = 0x0100, + }, +}; + +static irqreturn_t inport_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + unsigned char buttons; + + outb(INPORT_REG_MODE, INPORT_CONTROL_PORT); + outb(INPORT_MODE_HOLD | INPORT_MODE_IRQ | INPORT_MODE_BASE, INPORT_DATA_PORT); + + input_regs(&inport_dev, regs); + + outb(INPORT_REG_X, INPORT_CONTROL_PORT); + input_report_rel(&inport_dev, REL_X, inb(INPORT_DATA_PORT)); + + outb(INPORT_REG_Y, INPORT_CONTROL_PORT); + input_report_rel(&inport_dev, REL_Y, inb(INPORT_DATA_PORT)); + + outb(INPORT_REG_BTNS, INPORT_CONTROL_PORT); + buttons = inb(INPORT_DATA_PORT); + + input_report_key(&inport_dev, BTN_MIDDLE, buttons & 1); + input_report_key(&inport_dev, BTN_LEFT, buttons & 2); + input_report_key(&inport_dev, BTN_RIGHT, buttons & 4); + + outb(INPORT_REG_MODE, INPORT_CONTROL_PORT); + outb(INPORT_MODE_IRQ | INPORT_MODE_BASE, INPORT_DATA_PORT); + + input_sync(&inport_dev); + return IRQ_HANDLED; +} + +static int __init inport_init(void) +{ + unsigned char a,b,c; + + if (!request_region(INPORT_BASE, INPORT_EXTENT, "inport")) { + printk(KERN_ERR "inport.c: Can't allocate ports at %#x\n", INPORT_BASE); + return -EBUSY; + } + + a = inb(INPORT_SIGNATURE_PORT); + b = inb(INPORT_SIGNATURE_PORT); + c = inb(INPORT_SIGNATURE_PORT); + if (( a == b ) || ( a != c )) { + release_region(INPORT_BASE, INPORT_EXTENT); + printk(KERN_ERR "inport.c: Didn't find InPort mouse at %#x\n", INPORT_BASE); + return -ENODEV; + } + + outb(INPORT_RESET, INPORT_CONTROL_PORT); + outb(INPORT_REG_MODE, INPORT_CONTROL_PORT); + outb(INPORT_MODE_BASE, INPORT_DATA_PORT); + + input_register_device(&inport_dev); + + printk(KERN_INFO "input: " INPORT_NAME " at %#x irq %d\n", INPORT_BASE, inport_irq); + + return 0; +} + +static void __exit inport_exit(void) +{ + input_unregister_device(&inport_dev); + release_region(INPORT_BASE, INPORT_EXTENT); +} + +module_init(inport_init); +module_exit(inport_exit); diff --git a/drivers/input/mouse/logibm.c b/drivers/input/mouse/logibm.c new file mode 100644 index 000000000000..77eb83e87f61 --- /dev/null +++ b/drivers/input/mouse/logibm.c @@ -0,0 +1,183 @@ +/* + * $Id: logibm.c,v 1.11 2001/09/25 10:12:07 vojtech Exp $ + * + * Copyright (c) 1999-2001 Vojtech Pavlik + * + * Based on the work of: + * James Banks Matthew Dillon + * David Giller Nathan Laredo + * Linus Torvalds Johan Myreen + * Cliff Matthews Philip Blundell + * Russell King + */ + +/* + * Logitech Bus Mouse Driver for Linux + */ + +/* + * 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 either by + * e-mail - mail your message to <vojtech@ucw.cz>, or by paper mail: + * Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/delay.h> +#include <linux/ioport.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/interrupt.h> + +#include <asm/io.h> +#include <asm/irq.h> + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION("Logitech busmouse driver"); +MODULE_LICENSE("GPL"); + +#define LOGIBM_BASE 0x23c +#define LOGIBM_EXTENT 4 + +#define LOGIBM_DATA_PORT LOGIBM_BASE + 0 +#define LOGIBM_SIGNATURE_PORT LOGIBM_BASE + 1 +#define LOGIBM_CONTROL_PORT LOGIBM_BASE + 2 +#define LOGIBM_CONFIG_PORT LOGIBM_BASE + 3 + +#define LOGIBM_ENABLE_IRQ 0x00 +#define LOGIBM_DISABLE_IRQ 0x10 +#define LOGIBM_READ_X_LOW 0x80 +#define LOGIBM_READ_X_HIGH 0xa0 +#define LOGIBM_READ_Y_LOW 0xc0 +#define LOGIBM_READ_Y_HIGH 0xe0 + +#define LOGIBM_DEFAULT_MODE 0x90 +#define LOGIBM_CONFIG_BYTE 0x91 +#define LOGIBM_SIGNATURE_BYTE 0xa5 + +#define LOGIBM_IRQ 5 + +static int logibm_irq = LOGIBM_IRQ; +module_param_named(irq, logibm_irq, uint, 0); +MODULE_PARM_DESC(irq, "IRQ number (5=default)"); + +__obsolete_setup("logibm_irq="); + +static int logibm_used = 0; + +static irqreturn_t logibm_interrupt(int irq, void *dev_id, struct pt_regs *regs); + +static int logibm_open(struct input_dev *dev) +{ + if (logibm_used++) + return 0; + if (request_irq(logibm_irq, logibm_interrupt, 0, "logibm", NULL)) { + logibm_used--; + printk(KERN_ERR "logibm.c: Can't allocate irq %d\n", logibm_irq); + return -EBUSY; + } + outb(LOGIBM_ENABLE_IRQ, LOGIBM_CONTROL_PORT); + return 0; +} + +static void logibm_close(struct input_dev *dev) +{ + if (--logibm_used) + return; + outb(LOGIBM_DISABLE_IRQ, LOGIBM_CONTROL_PORT); + free_irq(logibm_irq, NULL); +} + +static struct input_dev logibm_dev = { + .evbit = { BIT(EV_KEY) | BIT(EV_REL) }, + .keybit = { [LONG(BTN_LEFT)] = BIT(BTN_LEFT) | BIT(BTN_MIDDLE) | BIT(BTN_RIGHT) }, + .relbit = { BIT(REL_X) | BIT(REL_Y) }, + .open = logibm_open, + .close = logibm_close, + .name = "Logitech bus mouse", + .phys = "isa023c/input0", + .id = { + .bustype = BUS_ISA, + .vendor = 0x0003, + .product = 0x0001, + .version = 0x0100, + }, +}; + +static irqreturn_t logibm_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + char dx, dy; + unsigned char buttons; + + outb(LOGIBM_READ_X_LOW, LOGIBM_CONTROL_PORT); + dx = (inb(LOGIBM_DATA_PORT) & 0xf); + outb(LOGIBM_READ_X_HIGH, LOGIBM_CONTROL_PORT); + dx |= (inb(LOGIBM_DATA_PORT) & 0xf) << 4; + outb(LOGIBM_READ_Y_LOW, LOGIBM_CONTROL_PORT); + dy = (inb(LOGIBM_DATA_PORT) & 0xf); + outb(LOGIBM_READ_Y_HIGH, LOGIBM_CONTROL_PORT); + buttons = inb(LOGIBM_DATA_PORT); + dy |= (buttons & 0xf) << 4; + buttons = ~buttons >> 5; + + input_regs(&logibm_dev, regs); + input_report_rel(&logibm_dev, REL_X, dx); + input_report_rel(&logibm_dev, REL_Y, dy); + input_report_key(&logibm_dev, BTN_RIGHT, buttons & 1); + input_report_key(&logibm_dev, BTN_MIDDLE, buttons & 2); + input_report_key(&logibm_dev, BTN_LEFT, buttons & 4); + input_sync(&logibm_dev); + + outb(LOGIBM_ENABLE_IRQ, LOGIBM_CONTROL_PORT); + return IRQ_HANDLED; +} + +static int __init logibm_init(void) +{ + if (!request_region(LOGIBM_BASE, LOGIBM_EXTENT, "logibm")) { + printk(KERN_ERR "logibm.c: Can't allocate ports at %#x\n", LOGIBM_BASE); + return -EBUSY; + } + + outb(LOGIBM_CONFIG_BYTE, LOGIBM_CONFIG_PORT); + outb(LOGIBM_SIGNATURE_BYTE, LOGIBM_SIGNATURE_PORT); + udelay(100); + + if (inb(LOGIBM_SIGNATURE_PORT) != LOGIBM_SIGNATURE_BYTE) { + release_region(LOGIBM_BASE, LOGIBM_EXTENT); + printk(KERN_ERR "logibm.c: Didn't find Logitech busmouse at %#x\n", LOGIBM_BASE); + return -ENODEV; + } + + outb(LOGIBM_DEFAULT_MODE, LOGIBM_CONFIG_PORT); + outb(LOGIBM_DISABLE_IRQ, LOGIBM_CONTROL_PORT); + + input_register_device(&logibm_dev); + + printk(KERN_INFO "input: Logitech bus mouse at %#x irq %d\n", LOGIBM_BASE, logibm_irq); + + return 0; +} + +static void __exit logibm_exit(void) +{ + input_unregister_device(&logibm_dev); + release_region(LOGIBM_BASE, LOGIBM_EXTENT); +} + +module_init(logibm_init); +module_exit(logibm_exit); diff --git a/drivers/input/mouse/logips2pp.c b/drivers/input/mouse/logips2pp.c new file mode 100644 index 000000000000..5ab1bd7d529d --- /dev/null +++ b/drivers/input/mouse/logips2pp.c @@ -0,0 +1,397 @@ +/* + * Logitech PS/2++ mouse driver + * + * Copyright (c) 1999-2003 Vojtech Pavlik <vojtech@suse.cz> + * Copyright (c) 2003 Eric Wong <eric@yhbt.net> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +#include <linux/input.h> +#include <linux/serio.h> +#include <linux/libps2.h> +#include "psmouse.h" +#include "logips2pp.h" + +/* Logitech mouse types */ +#define PS2PP_KIND_WHEEL 1 +#define PS2PP_KIND_MX 2 +#define PS2PP_KIND_TP3 3 + +/* Logitech mouse features */ +#define PS2PP_WHEEL 0x01 +#define PS2PP_HWHEEL 0x02 +#define PS2PP_SIDE_BTN 0x04 +#define PS2PP_EXTRA_BTN 0x08 +#define PS2PP_TASK_BTN 0x10 +#define PS2PP_NAV_BTN 0x20 + +struct ps2pp_info { + const int model; + unsigned const int kind; + unsigned const int features; +}; + +/* + * Process a PS2++ or PS2T++ packet. + */ + +static psmouse_ret_t ps2pp_process_byte(struct psmouse *psmouse, struct pt_regs *regs) +{ + struct input_dev *dev = &psmouse->dev; + unsigned char *packet = psmouse->packet; + + if (psmouse->pktcnt < 3) + return PSMOUSE_GOOD_DATA; + +/* + * Full packet accumulated, process it + */ + + input_regs(dev, regs); + + if ((packet[0] & 0x48) == 0x48 && (packet[1] & 0x02) == 0x02) { + + /* Logitech extended packet */ + switch ((packet[1] >> 4) | (packet[0] & 0x30)) { + + case 0x0d: /* Mouse extra info */ + + input_report_rel(dev, packet[2] & 0x80 ? REL_HWHEEL : REL_WHEEL, + (int) (packet[2] & 8) - (int) (packet[2] & 7)); + input_report_key(dev, BTN_SIDE, (packet[2] >> 4) & 1); + input_report_key(dev, BTN_EXTRA, (packet[2] >> 5) & 1); + + break; + + case 0x0e: /* buttons 4, 5, 6, 7, 8, 9, 10 info */ + + input_report_key(dev, BTN_SIDE, (packet[2]) & 1); + input_report_key(dev, BTN_EXTRA, (packet[2] >> 1) & 1); + input_report_key(dev, BTN_BACK, (packet[2] >> 3) & 1); + input_report_key(dev, BTN_FORWARD, (packet[2] >> 4) & 1); + input_report_key(dev, BTN_TASK, (packet[2] >> 2) & 1); + + break; + + case 0x0f: /* TouchPad extra info */ + + input_report_rel(dev, packet[2] & 0x08 ? REL_HWHEEL : REL_WHEEL, + (int) ((packet[2] >> 4) & 8) - (int) ((packet[2] >> 4) & 7)); + packet[0] = packet[2] | 0x08; + break; + +#ifdef DEBUG + default: + printk(KERN_WARNING "psmouse.c: Received PS2++ packet #%x, but don't know how to handle.\n", + (packet[1] >> 4) | (packet[0] & 0x30)); +#endif + } + } else { + /* Standard PS/2 motion data */ + input_report_rel(dev, REL_X, packet[1] ? (int) packet[1] - (int) ((packet[0] << 4) & 0x100) : 0); + input_report_rel(dev, REL_Y, packet[2] ? (int) ((packet[0] << 3) & 0x100) - (int) packet[2] : 0); + } + + input_report_key(dev, BTN_LEFT, packet[0] & 1); + input_report_key(dev, BTN_MIDDLE, (packet[0] >> 2) & 1); + input_report_key(dev, BTN_RIGHT, (packet[0] >> 1) & 1); + + input_sync(dev); + + return PSMOUSE_FULL_PACKET; + +} + +/* + * ps2pp_cmd() sends a PS2++ command, sliced into two bit + * pieces through the SETRES command. This is needed to send extended + * commands to mice on notebooks that try to understand the PS/2 protocol + * Ugly. + */ + +static int ps2pp_cmd(struct psmouse *psmouse, unsigned char *param, unsigned char command) +{ + if (psmouse_sliced_command(psmouse, command)) + return -1; + + if (ps2_command(&psmouse->ps2dev, param, PSMOUSE_CMD_POLL)) + return -1; + + return 0; +} + +/* + * SmartScroll / CruiseControl for some newer Logitech mice Defaults to + * enabled if we do nothing to it. Of course I put this in because I want it + * disabled :P + * 1 - enabled (if previously disabled, also default) + * 0 - disabled + */ + +static void ps2pp_set_smartscroll(struct psmouse *psmouse, unsigned int smartscroll) +{ + struct ps2dev *ps2dev = &psmouse->ps2dev; + unsigned char param[4]; + + if (smartscroll > 1) + smartscroll = 1; + + ps2pp_cmd(psmouse, param, 0x32); + + param[0] = 0; + ps2_command(ps2dev, param, PSMOUSE_CMD_SETRES); + ps2_command(ps2dev, param, PSMOUSE_CMD_SETRES); + ps2_command(ps2dev, param, PSMOUSE_CMD_SETRES); + + param[0] = smartscroll; + ps2_command(ps2dev, param, PSMOUSE_CMD_SETRES); +} + +static ssize_t psmouse_attr_show_smartscroll(struct psmouse *psmouse, char *buf) +{ + return sprintf(buf, "%d\n", psmouse->smartscroll ? 1 : 0); +} + +static ssize_t psmouse_attr_set_smartscroll(struct psmouse *psmouse, const char *buf, size_t count) +{ + unsigned long value; + char *rest; + + value = simple_strtoul(buf, &rest, 10); + if (*rest || value > 1) + return -EINVAL; + + ps2pp_set_smartscroll(psmouse, value); + psmouse->smartscroll = value; + return count; +} + +PSMOUSE_DEFINE_ATTR(smartscroll); + +/* + * Support 800 dpi resolution _only_ if the user wants it (there are good + * reasons to not use it even if the mouse supports it, and of course there are + * also good reasons to use it, let the user decide). + */ + +static void ps2pp_set_resolution(struct psmouse *psmouse, unsigned int resolution) +{ + if (resolution > 400) { + struct ps2dev *ps2dev = &psmouse->ps2dev; + unsigned char param = 3; + + ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSCALE11); + ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSCALE11); + ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSCALE11); + ps2_command(ps2dev, ¶m, PSMOUSE_CMD_SETRES); + psmouse->resolution = 800; + } else + psmouse_set_resolution(psmouse, resolution); +} + +static void ps2pp_disconnect(struct psmouse *psmouse) +{ + device_remove_file(&psmouse->ps2dev.serio->dev, &psmouse_attr_smartscroll); +} + +static struct ps2pp_info *get_model_info(unsigned char model) +{ + static struct ps2pp_info ps2pp_list[] = { + { 12, 0, PS2PP_SIDE_BTN}, + { 13, 0, 0 }, + { 15, PS2PP_KIND_MX, /* MX1000 */ + PS2PP_WHEEL | PS2PP_SIDE_BTN | PS2PP_TASK_BTN | + PS2PP_EXTRA_BTN | PS2PP_NAV_BTN | PS2PP_HWHEEL }, + { 40, 0, PS2PP_SIDE_BTN }, + { 41, 0, PS2PP_SIDE_BTN }, + { 42, 0, PS2PP_SIDE_BTN }, + { 43, 0, PS2PP_SIDE_BTN }, + { 50, 0, 0 }, + { 51, 0, 0 }, + { 52, PS2PP_KIND_WHEEL, PS2PP_SIDE_BTN | PS2PP_WHEEL }, + { 53, PS2PP_KIND_WHEEL, PS2PP_WHEEL }, + { 61, PS2PP_KIND_MX, /* MX700 */ + PS2PP_WHEEL | PS2PP_SIDE_BTN | PS2PP_TASK_BTN | + PS2PP_EXTRA_BTN | PS2PP_NAV_BTN }, + { 73, 0, PS2PP_SIDE_BTN }, + { 75, PS2PP_KIND_WHEEL, PS2PP_WHEEL }, + { 76, PS2PP_KIND_WHEEL, PS2PP_WHEEL }, + { 80, PS2PP_KIND_WHEEL, PS2PP_SIDE_BTN | PS2PP_WHEEL }, + { 81, PS2PP_KIND_WHEEL, PS2PP_WHEEL }, + { 83, PS2PP_KIND_WHEEL, PS2PP_WHEEL }, + { 88, PS2PP_KIND_WHEEL, PS2PP_WHEEL }, + { 96, 0, 0 }, + { 97, PS2PP_KIND_TP3, PS2PP_WHEEL | PS2PP_HWHEEL }, + { 100, PS2PP_KIND_MX, /* MX510 */ + PS2PP_WHEEL | PS2PP_SIDE_BTN | PS2PP_TASK_BTN | + PS2PP_EXTRA_BTN | PS2PP_NAV_BTN }, + { 111, PS2PP_KIND_MX, /* MX300 */ + PS2PP_WHEEL | PS2PP_EXTRA_BTN | PS2PP_TASK_BTN }, + { 112, PS2PP_KIND_MX, /* MX500 */ + PS2PP_WHEEL | PS2PP_SIDE_BTN | PS2PP_TASK_BTN | + PS2PP_EXTRA_BTN | PS2PP_NAV_BTN }, + { 114, PS2PP_KIND_MX, /* MX310 */ + PS2PP_WHEEL | PS2PP_SIDE_BTN | + PS2PP_TASK_BTN | PS2PP_EXTRA_BTN }, + { } + }; + int i; + + for (i = 0; ps2pp_list[i].model; i++) + if (model == ps2pp_list[i].model) + return &ps2pp_list[i]; + + printk(KERN_WARNING "logips2pp: Detected unknown logitech mouse model %d\n", model); + return NULL; +} + +/* + * Set up input device's properties based on the detected mouse model. + */ + +static void ps2pp_set_model_properties(struct psmouse *psmouse, struct ps2pp_info *model_info, + int using_ps2pp) +{ + if (model_info->features & PS2PP_SIDE_BTN) + set_bit(BTN_SIDE, psmouse->dev.keybit); + + if (model_info->features & PS2PP_EXTRA_BTN) + set_bit(BTN_EXTRA, psmouse->dev.keybit); + + if (model_info->features & PS2PP_TASK_BTN) + set_bit(BTN_TASK, psmouse->dev.keybit); + + if (model_info->features & PS2PP_NAV_BTN) { + set_bit(BTN_FORWARD, psmouse->dev.keybit); + set_bit(BTN_BACK, psmouse->dev.keybit); + } + + if (model_info->features & PS2PP_WHEEL) + set_bit(REL_WHEEL, psmouse->dev.relbit); + + if (model_info->features & PS2PP_HWHEEL) + set_bit(REL_HWHEEL, psmouse->dev.relbit); + + switch (model_info->kind) { + case PS2PP_KIND_WHEEL: + psmouse->name = "Wheel Mouse"; + break; + + case PS2PP_KIND_MX: + psmouse->name = "MX Mouse"; + break; + + case PS2PP_KIND_TP3: + psmouse->name = "TouchPad 3"; + break; + + default: + /* + * Set name to "Mouse" only when using PS2++, + * otherwise let other protocols define suitable + * name + */ + if (using_ps2pp) + psmouse->name = "Mouse"; + break; + } +} + + +/* + * Logitech magic init. Detect whether the mouse is a Logitech one + * and its exact model and try turning on extended protocol for ones + * that support it. + */ + +int ps2pp_init(struct psmouse *psmouse, int set_properties) +{ + struct ps2dev *ps2dev = &psmouse->ps2dev; + unsigned char param[4]; + unsigned char model, buttons; + struct ps2pp_info *model_info; + int use_ps2pp = 0; + + param[0] = 0; + ps2_command(ps2dev, param, PSMOUSE_CMD_SETRES); + ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSCALE11); + ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSCALE11); + ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSCALE11); + param[1] = 0; + ps2_command(ps2dev, param, PSMOUSE_CMD_GETINFO); + + if (!param[1]) + return -1; + + model = ((param[0] >> 4) & 0x07) | ((param[0] << 3) & 0x78); + buttons = param[1]; + + if ((model_info = get_model_info(model)) != NULL) { + +/* + * Do Logitech PS2++ / PS2T++ magic init. + */ + if (model == 97) { /* Touch Pad 3 */ + + /* Unprotect RAM */ + param[0] = 0x11; param[1] = 0x04; param[2] = 0x68; + ps2_command(ps2dev, param, 0x30d1); + /* Enable features */ + param[0] = 0x11; param[1] = 0x05; param[2] = 0x0b; + ps2_command(ps2dev, param, 0x30d1); + /* Enable PS2++ */ + param[0] = 0x11; param[1] = 0x09; param[2] = 0xc3; + ps2_command(ps2dev, param, 0x30d1); + + param[0] = 0; + if (!ps2_command(ps2dev, param, 0x13d1) && + param[0] == 0x06 && param[1] == 0x00 && param[2] == 0x14) { + use_ps2pp = 1; + } + + } else { + + param[0] = param[1] = param[2] = 0; + ps2pp_cmd(psmouse, param, 0x39); /* Magic knock */ + ps2pp_cmd(psmouse, param, 0xDB); + + if ((param[0] & 0x78) == 0x48 && + (param[1] & 0xf3) == 0xc2 && + (param[2] & 0x03) == ((param[1] >> 2) & 3)) { + ps2pp_set_smartscroll(psmouse, psmouse->smartscroll); + use_ps2pp = 1; + } + } + } + + if (set_properties) { + psmouse->vendor = "Logitech"; + psmouse->model = model; + + if (use_ps2pp) { + psmouse->protocol_handler = ps2pp_process_byte; + psmouse->pktsize = 3; + + if (model_info->kind != PS2PP_KIND_TP3) { + psmouse->set_resolution = ps2pp_set_resolution; + psmouse->disconnect = ps2pp_disconnect; + + device_create_file(&psmouse->ps2dev.serio->dev, &psmouse_attr_smartscroll); + } + } + + if (buttons < 3) + clear_bit(BTN_MIDDLE, psmouse->dev.keybit); + if (buttons < 2) + clear_bit(BTN_RIGHT, psmouse->dev.keybit); + + if (model_info) + ps2pp_set_model_properties(psmouse, model_info, use_ps2pp); + } + + return use_ps2pp ? 0 : -1; +} + diff --git a/drivers/input/mouse/logips2pp.h b/drivers/input/mouse/logips2pp.h new file mode 100644 index 000000000000..64a8ec52ea6d --- /dev/null +++ b/drivers/input/mouse/logips2pp.h @@ -0,0 +1,16 @@ +/* + * Logitech PS/2++ mouse driver header + * + * Copyright (c) 2003 Vojtech Pavlik <vojtech@suse.cz> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +#ifndef _LOGIPS2PP_H +#define _LOGIPS2PP_H + +int ps2pp_init(struct psmouse *psmouse, int set_properties); + +#endif diff --git a/drivers/input/mouse/maplemouse.c b/drivers/input/mouse/maplemouse.c new file mode 100644 index 000000000000..12dc0ef5020f --- /dev/null +++ b/drivers/input/mouse/maplemouse.c @@ -0,0 +1,134 @@ +/* + * $Id: maplemouse.c,v 1.2 2004/03/22 01:18:15 lethal Exp $ + * SEGA Dreamcast mouse driver + * Based on drivers/usb/usbmouse.c + */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/timer.h> +#include <linux/maple.h> + +MODULE_AUTHOR("YAEGASHI Takeshi <t@keshi.org>"); +MODULE_DESCRIPTION("SEGA Dreamcast mouse driver"); + +struct dc_mouse { + struct input_dev dev; + int open; +}; + + +static void dc_mouse_callback(struct mapleq *mq) +{ + int buttons, relx, rely, relz; + struct maple_device *mapledev = mq->dev; + struct dc_mouse *mouse = mapledev->private_data; + struct input_dev *dev = &mouse->dev; + unsigned char *res = mq->recvbuf; + + buttons = ~res[8]; + relx=*(unsigned short *)(res+12)-512; + rely=*(unsigned short *)(res+14)-512; + relz=*(unsigned short *)(res+16)-512; + + input_report_key(dev, BTN_LEFT, buttons&4); + input_report_key(dev, BTN_MIDDLE, buttons&9); + input_report_key(dev, BTN_RIGHT, buttons&2); + input_report_rel(dev, REL_X, relx); + input_report_rel(dev, REL_Y, rely); + input_report_rel(dev, REL_WHEEL, relz); + input_sync(dev); +} + + +static int dc_mouse_open(struct input_dev *dev) +{ + struct dc_mouse *mouse = dev->private; + mouse->open++; + return 0; +} + + +static void dc_mouse_close(struct input_dev *dev) +{ + struct dc_mouse *mouse = dev->private; + mouse->open--; +} + + +static int dc_mouse_connect(struct maple_device *dev) +{ + unsigned long data = be32_to_cpu(dev->devinfo.function_data[0]); + struct dc_mouse *mouse; + + if (!(mouse = kmalloc(sizeof(struct dc_mouse), GFP_KERNEL))) + return -1; + memset(mouse, 0, sizeof(struct dc_mouse)); + + dev->private_data = mouse; + + mouse->dev.evbit[0] = BIT(EV_KEY) | BIT(EV_REL); + mouse->dev.keybit[LONG(BTN_MOUSE)] = BIT(BTN_LEFT) | BIT(BTN_RIGHT) | BIT(BTN_MIDDLE); + mouse->dev.relbit[0] = BIT(REL_X) | BIT(REL_Y) | BIT(REL_WHEEL); + + init_input_dev(&mouse->dev); + + mouse->dev.private = mouse; + mouse->dev.open = dc_mouse_open; + mouse->dev.close = dc_mouse_close; + mouse->dev.event = NULL; + + mouse->dev.name = dev->product_name; + mouse->dev.id.bustype = BUS_MAPLE; + + input_register_device(&mouse->dev); + + maple_getcond_callback(dev, dc_mouse_callback, 1, MAPLE_FUNC_MOUSE); + + printk(KERN_INFO "input: mouse(0x%lx): %s\n", data, mouse->dev.name); + + return 0; +} + + +static void dc_mouse_disconnect(struct maple_device *dev) +{ + struct dc_mouse *mouse = dev->private_data; + + input_unregister_device(&mouse->dev); + kfree(mouse); +} + + +static struct maple_driver dc_mouse_driver = { + .function = MAPLE_FUNC_MOUSE, + .name = "Dreamcast mouse", + .connect = dc_mouse_connect, + .disconnect = dc_mouse_disconnect, +}; + + +static int __init dc_mouse_init(void) +{ + maple_register_driver(&dc_mouse_driver); + return 0; +} + + +static void __exit dc_mouse_exit(void) +{ + maple_unregister_driver(&dc_mouse_driver); +} + + +module_init(dc_mouse_init); +module_exit(dc_mouse_exit); + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/input/mouse/pc110pad.c b/drivers/input/mouse/pc110pad.c new file mode 100644 index 000000000000..0c74918fe254 --- /dev/null +++ b/drivers/input/mouse/pc110pad.c @@ -0,0 +1,178 @@ +/* + * $Id: pc110pad.c,v 1.12 2001/09/25 10:12:07 vojtech Exp $ + * + * Copyright (c) 2000-2001 Vojtech Pavlik + * + * Based on the work of: + * Alan Cox Robin O'Leary + */ + +/* + * IBM PC110 touchpad driver for Linux + */ + +/* + * 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 either by + * e-mail - mail your message to <vojtech@ucw.cz>, or by paper mail: + * Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/ioport.h> +#include <linux/input.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/pci.h> + +#include <asm/io.h> +#include <asm/irq.h> + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION("IBM PC110 touchpad driver"); +MODULE_LICENSE("GPL"); + +#define PC110PAD_OFF 0x30 +#define PC110PAD_ON 0x38 + +static int pc110pad_irq = 10; +static int pc110pad_io = 0x15e0; + +static struct input_dev pc110pad_dev; +static int pc110pad_data[3]; +static int pc110pad_count; +static int pc110pad_used; + +static char *pc110pad_name = "IBM PC110 TouchPad"; +static char *pc110pad_phys = "isa15e0/input0"; + +static irqreturn_t pc110pad_interrupt(int irq, void *ptr, struct pt_regs *regs) +{ + int value = inb_p(pc110pad_io); + int handshake = inb_p(pc110pad_io + 2); + + outb_p(handshake | 1, pc110pad_io + 2); + outb_p(handshake & ~1, pc110pad_io + 2); + inb_p(0x64); + + pc110pad_data[pc110pad_count++] = value; + + if (pc110pad_count < 3) + return IRQ_HANDLED; + + input_regs(&pc110pad_dev, regs); + input_report_key(&pc110pad_dev, BTN_TOUCH, + pc110pad_data[0] & 0x01); + input_report_abs(&pc110pad_dev, ABS_X, + pc110pad_data[1] | ((pc110pad_data[0] << 3) & 0x80) | ((pc110pad_data[0] << 1) & 0x100)); + input_report_abs(&pc110pad_dev, ABS_Y, + pc110pad_data[2] | ((pc110pad_data[0] << 4) & 0x80)); + input_sync(&pc110pad_dev); + + pc110pad_count = 0; + return IRQ_HANDLED; +} + +static void pc110pad_close(struct input_dev *dev) +{ + if (!--pc110pad_used) + outb(PC110PAD_OFF, pc110pad_io + 2); +} + +static int pc110pad_open(struct input_dev *dev) +{ + if (pc110pad_used++) + return 0; + + pc110pad_interrupt(0,NULL,NULL); + pc110pad_interrupt(0,NULL,NULL); + pc110pad_interrupt(0,NULL,NULL); + outb(PC110PAD_ON, pc110pad_io + 2); + pc110pad_count = 0; + + return 0; +} + +/* + * We try to avoid enabling the hardware if it's not + * there, but we don't know how to test. But we do know + * that the PC110 is not a PCI system. So if we find any + * PCI devices in the machine, we don't have a PC110. + */ +static int __init pc110pad_init(void) +{ + struct pci_dev *dev; + + dev = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, NULL); + if (dev) { + pci_dev_put(dev); + return -ENOENT; + } + + if (!request_region(pc110pad_io, 4, "pc110pad")) { + printk(KERN_ERR "pc110pad: I/O area %#x-%#x in use.\n", + pc110pad_io, pc110pad_io + 4); + return -EBUSY; + } + + outb(PC110PAD_OFF, pc110pad_io + 2); + + if (request_irq(pc110pad_irq, pc110pad_interrupt, 0, "pc110pad", NULL)) + { + release_region(pc110pad_io, 4); + printk(KERN_ERR "pc110pad: Unable to get irq %d.\n", pc110pad_irq); + return -EBUSY; + } + + pc110pad_dev.evbit[0] = BIT(EV_KEY) | BIT(EV_ABS); + pc110pad_dev.absbit[0] = BIT(ABS_X) | BIT(ABS_Y); + pc110pad_dev.keybit[LONG(BTN_TOUCH)] = BIT(BTN_TOUCH); + + pc110pad_dev.absmax[ABS_X] = 0x1ff; + pc110pad_dev.absmax[ABS_Y] = 0x0ff; + + pc110pad_dev.open = pc110pad_open; + pc110pad_dev.close = pc110pad_close; + + pc110pad_dev.name = pc110pad_name; + pc110pad_dev.phys = pc110pad_phys; + pc110pad_dev.id.bustype = BUS_ISA; + pc110pad_dev.id.vendor = 0x0003; + pc110pad_dev.id.product = 0x0001; + pc110pad_dev.id.version = 0x0100; + + input_register_device(&pc110pad_dev); + + printk(KERN_INFO "input: %s at %#x irq %d\n", + pc110pad_name, pc110pad_io, pc110pad_irq); + + return 0; +} + +static void __exit pc110pad_exit(void) +{ + input_unregister_device(&pc110pad_dev); + + outb(PC110PAD_OFF, pc110pad_io + 2); + + free_irq(pc110pad_irq, NULL); + release_region(pc110pad_io, 4); +} + +module_init(pc110pad_init); +module_exit(pc110pad_exit); diff --git a/drivers/input/mouse/psmouse-base.c b/drivers/input/mouse/psmouse-base.c new file mode 100644 index 000000000000..cd8509549eac --- /dev/null +++ b/drivers/input/mouse/psmouse-base.c @@ -0,0 +1,1011 @@ +/* + * PS/2 mouse driver + * + * Copyright (c) 1999-2002 Vojtech Pavlik + * Copyright (c) 2003-2004 Dmitry Torokhov + */ + +/* + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/input.h> +#include <linux/serio.h> +#include <linux/init.h> +#include <linux/libps2.h> +#include "psmouse.h" +#include "synaptics.h" +#include "logips2pp.h" +#include "alps.h" + +#define DRIVER_DESC "PS/2 mouse driver" + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@suse.cz>"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +static unsigned int psmouse_max_proto = -1U; +static int psmouse_set_maxproto(const char *val, struct kernel_param *kp); +static int psmouse_get_maxproto(char *buffer, struct kernel_param *kp); +static char *psmouse_proto_abbrev[] = { NULL, "bare", NULL, NULL, NULL, "imps", "exps", NULL, NULL, NULL }; +#define param_check_proto_abbrev(name, p) __param_check(name, p, unsigned int) +#define param_set_proto_abbrev psmouse_set_maxproto +#define param_get_proto_abbrev psmouse_get_maxproto +module_param_named(proto, psmouse_max_proto, proto_abbrev, 0644); +MODULE_PARM_DESC(proto, "Highest protocol extension to probe (bare, imps, exps, any). Useful for KVM switches."); + +static unsigned int psmouse_resolution = 200; +module_param_named(resolution, psmouse_resolution, uint, 0644); +MODULE_PARM_DESC(resolution, "Resolution, in dpi."); + +static unsigned int psmouse_rate = 100; +module_param_named(rate, psmouse_rate, uint, 0644); +MODULE_PARM_DESC(rate, "Report rate, in reports per second."); + +static unsigned int psmouse_smartscroll = 1; +module_param_named(smartscroll, psmouse_smartscroll, bool, 0644); +MODULE_PARM_DESC(smartscroll, "Logitech Smartscroll autorepeat, 1 = enabled (default), 0 = disabled."); + +static unsigned int psmouse_resetafter; +module_param_named(resetafter, psmouse_resetafter, uint, 0644); +MODULE_PARM_DESC(resetafter, "Reset device after so many bad packets (0 = never)."); + +PSMOUSE_DEFINE_ATTR(rate); +PSMOUSE_DEFINE_ATTR(resolution); +PSMOUSE_DEFINE_ATTR(resetafter); + +__obsolete_setup("psmouse_noext"); +__obsolete_setup("psmouse_resolution="); +__obsolete_setup("psmouse_smartscroll="); +__obsolete_setup("psmouse_resetafter="); +__obsolete_setup("psmouse_rate="); + +static char *psmouse_protocols[] = { "None", "PS/2", "PS2++", "ThinkPS/2", "GenPS/2", "ImPS/2", "ImExPS/2", "SynPS/2", "AlpsPS/2" }; + +/* + * psmouse_process_byte() analyzes the PS/2 data stream and reports + * relevant events to the input module once full packet has arrived. + */ + +static psmouse_ret_t psmouse_process_byte(struct psmouse *psmouse, struct pt_regs *regs) +{ + struct input_dev *dev = &psmouse->dev; + unsigned char *packet = psmouse->packet; + + if (psmouse->pktcnt < psmouse->pktsize) + return PSMOUSE_GOOD_DATA; + +/* + * Full packet accumulated, process it + */ + + input_regs(dev, regs); + +/* + * Scroll wheel on IntelliMice, scroll buttons on NetMice + */ + + if (psmouse->type == PSMOUSE_IMPS || psmouse->type == PSMOUSE_GENPS) + input_report_rel(dev, REL_WHEEL, -(signed char) packet[3]); + +/* + * Scroll wheel and buttons on IntelliMouse Explorer + */ + + if (psmouse->type == PSMOUSE_IMEX) { + input_report_rel(dev, REL_WHEEL, (int) (packet[3] & 8) - (int) (packet[3] & 7)); + input_report_key(dev, BTN_SIDE, (packet[3] >> 4) & 1); + input_report_key(dev, BTN_EXTRA, (packet[3] >> 5) & 1); + } + +/* + * Extra buttons on Genius NewNet 3D + */ + + if (psmouse->type == PSMOUSE_GENPS) { + input_report_key(dev, BTN_SIDE, (packet[0] >> 6) & 1); + input_report_key(dev, BTN_EXTRA, (packet[0] >> 7) & 1); + } + +/* + * Extra button on ThinkingMouse + */ + if (psmouse->type == PSMOUSE_THINKPS) { + input_report_key(dev, BTN_EXTRA, (packet[0] >> 3) & 1); + /* Without this bit of weirdness moving up gives wildly high Y changes. */ + packet[1] |= (packet[0] & 0x40) << 1; + } + +/* + * Generic PS/2 Mouse + */ + + input_report_key(dev, BTN_LEFT, packet[0] & 1); + input_report_key(dev, BTN_MIDDLE, (packet[0] >> 2) & 1); + input_report_key(dev, BTN_RIGHT, (packet[0] >> 1) & 1); + + input_report_rel(dev, REL_X, packet[1] ? (int) packet[1] - (int) ((packet[0] << 4) & 0x100) : 0); + input_report_rel(dev, REL_Y, packet[2] ? (int) ((packet[0] << 3) & 0x100) - (int) packet[2] : 0); + + input_sync(dev); + + return PSMOUSE_FULL_PACKET; +} + +/* + * psmouse_interrupt() handles incoming characters, either gathering them into + * packets or passing them to the command routine as command output. + */ + +static irqreturn_t psmouse_interrupt(struct serio *serio, + unsigned char data, unsigned int flags, struct pt_regs *regs) +{ + struct psmouse *psmouse = serio_get_drvdata(serio); + psmouse_ret_t rc; + + if (psmouse->state == PSMOUSE_IGNORE) + goto out; + + if (flags & (SERIO_PARITY|SERIO_TIMEOUT)) { + if (psmouse->state == PSMOUSE_ACTIVATED) + printk(KERN_WARNING "psmouse.c: bad data from KBC -%s%s\n", + flags & SERIO_TIMEOUT ? " timeout" : "", + flags & SERIO_PARITY ? " bad parity" : ""); + ps2_cmd_aborted(&psmouse->ps2dev); + goto out; + } + + if (unlikely(psmouse->ps2dev.flags & PS2_FLAG_ACK)) + if (ps2_handle_ack(&psmouse->ps2dev, data)) + goto out; + + if (unlikely(psmouse->ps2dev.flags & PS2_FLAG_CMD)) + if (ps2_handle_response(&psmouse->ps2dev, data)) + goto out; + + if (psmouse->state == PSMOUSE_INITIALIZING) + goto out; + + if (psmouse->state == PSMOUSE_ACTIVATED && + psmouse->pktcnt && time_after(jiffies, psmouse->last + HZ/2)) { + printk(KERN_WARNING "psmouse.c: %s at %s lost synchronization, throwing %d bytes away.\n", + psmouse->name, psmouse->phys, psmouse->pktcnt); + psmouse->pktcnt = 0; + } + + psmouse->last = jiffies; + psmouse->packet[psmouse->pktcnt++] = data; + + if (psmouse->packet[0] == PSMOUSE_RET_BAT) { + if (psmouse->pktcnt == 1) + goto out; + + if (psmouse->pktcnt == 2) { + if (psmouse->packet[1] == PSMOUSE_RET_ID) { + psmouse->state = PSMOUSE_IGNORE; + serio_reconnect(serio); + goto out; + } + if (psmouse->type == PSMOUSE_SYNAPTICS) { + /* neither 0xAA nor 0x00 are valid first bytes + * for a packet in absolute mode + */ + psmouse->pktcnt = 0; + goto out; + } + } + } + + rc = psmouse->protocol_handler(psmouse, regs); + + switch (rc) { + case PSMOUSE_BAD_DATA: + printk(KERN_WARNING "psmouse.c: %s at %s lost sync at byte %d\n", + psmouse->name, psmouse->phys, psmouse->pktcnt); + psmouse->pktcnt = 0; + + if (++psmouse->out_of_sync == psmouse->resetafter) { + psmouse->state = PSMOUSE_IGNORE; + printk(KERN_NOTICE "psmouse.c: issuing reconnect request\n"); + serio_reconnect(psmouse->ps2dev.serio); + } + break; + + case PSMOUSE_FULL_PACKET: + psmouse->pktcnt = 0; + if (psmouse->out_of_sync) { + psmouse->out_of_sync = 0; + printk(KERN_NOTICE "psmouse.c: %s at %s - driver resynched.\n", + psmouse->name, psmouse->phys); + } + break; + + case PSMOUSE_GOOD_DATA: + break; + } +out: + return IRQ_HANDLED; +} + + +/* + * psmouse_sliced_command() sends an extended PS/2 command to the mouse + * using sliced syntax, understood by advanced devices, such as Logitech + * or Synaptics touchpads. The command is encoded as: + * 0xE6 0xE8 rr 0xE8 ss 0xE8 tt 0xE8 uu where (rr*64)+(ss*16)+(tt*4)+uu + * is the command. + */ +int psmouse_sliced_command(struct psmouse *psmouse, unsigned char command) +{ + int i; + + if (ps2_command(&psmouse->ps2dev, NULL, PSMOUSE_CMD_SETSCALE11)) + return -1; + + for (i = 6; i >= 0; i -= 2) { + unsigned char d = (command >> i) & 3; + if (ps2_command(&psmouse->ps2dev, &d, PSMOUSE_CMD_SETRES)) + return -1; + } + + return 0; +} + + +/* + * psmouse_reset() resets the mouse into power-on state. + */ +int psmouse_reset(struct psmouse *psmouse) +{ + unsigned char param[2]; + + if (ps2_command(&psmouse->ps2dev, param, PSMOUSE_CMD_RESET_BAT)) + return -1; + + if (param[0] != PSMOUSE_RET_BAT && param[1] != PSMOUSE_RET_ID) + return -1; + + return 0; +} + + +/* + * Genius NetMouse magic init. + */ +static int genius_detect(struct psmouse *psmouse, int set_properties) +{ + struct ps2dev *ps2dev = &psmouse->ps2dev; + unsigned char param[4]; + + param[0] = 3; + ps2_command(ps2dev, param, PSMOUSE_CMD_SETRES); + ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSCALE11); + ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSCALE11); + ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSCALE11); + ps2_command(ps2dev, param, PSMOUSE_CMD_GETINFO); + + if (param[0] != 0x00 || param[1] != 0x33 || param[2] != 0x55) + return -1; + + if (set_properties) { + set_bit(BTN_EXTRA, psmouse->dev.keybit); + set_bit(BTN_SIDE, psmouse->dev.keybit); + set_bit(REL_WHEEL, psmouse->dev.relbit); + + psmouse->vendor = "Genius"; + psmouse->name = "Wheel Mouse"; + psmouse->pktsize = 4; + } + + return 0; +} + +/* + * IntelliMouse magic init. + */ +static int intellimouse_detect(struct psmouse *psmouse, int set_properties) +{ + struct ps2dev *ps2dev = &psmouse->ps2dev; + unsigned char param[2]; + + param[0] = 200; + ps2_command(ps2dev, param, PSMOUSE_CMD_SETRATE); + param[0] = 100; + ps2_command(ps2dev, param, PSMOUSE_CMD_SETRATE); + param[0] = 80; + ps2_command(ps2dev, param, PSMOUSE_CMD_SETRATE); + ps2_command(ps2dev, param, PSMOUSE_CMD_GETID); + + if (param[0] != 3) + return -1; + + if (set_properties) { + set_bit(REL_WHEEL, psmouse->dev.relbit); + + if (!psmouse->vendor) psmouse->vendor = "Generic"; + if (!psmouse->name) psmouse->name = "Wheel Mouse"; + psmouse->pktsize = 4; + } + + return 0; +} + +/* + * Try IntelliMouse/Explorer magic init. + */ +static int im_explorer_detect(struct psmouse *psmouse, int set_properties) +{ + struct ps2dev *ps2dev = &psmouse->ps2dev; + unsigned char param[2]; + + intellimouse_detect(psmouse, 0); + + param[0] = 200; + ps2_command(ps2dev, param, PSMOUSE_CMD_SETRATE); + param[0] = 200; + ps2_command(ps2dev, param, PSMOUSE_CMD_SETRATE); + param[0] = 80; + ps2_command(ps2dev, param, PSMOUSE_CMD_SETRATE); + ps2_command(ps2dev, param, PSMOUSE_CMD_GETID); + + if (param[0] != 4) + return -1; + + if (set_properties) { + set_bit(REL_WHEEL, psmouse->dev.relbit); + set_bit(BTN_SIDE, psmouse->dev.keybit); + set_bit(BTN_EXTRA, psmouse->dev.keybit); + + if (!psmouse->vendor) psmouse->vendor = "Generic"; + if (!psmouse->name) psmouse->name = "Explorer Mouse"; + psmouse->pktsize = 4; + } + + return 0; +} + +/* + * Kensington ThinkingMouse / ExpertMouse magic init. + */ +static int thinking_detect(struct psmouse *psmouse, int set_properties) +{ + struct ps2dev *ps2dev = &psmouse->ps2dev; + unsigned char param[2]; + unsigned char seq[] = { 20, 60, 40, 20, 20, 60, 40, 20, 20, 0 }; + int i; + + param[0] = 10; + ps2_command(ps2dev, param, PSMOUSE_CMD_SETRATE); + param[0] = 0; + ps2_command(ps2dev, param, PSMOUSE_CMD_SETRES); + for (i = 0; seq[i]; i++) + ps2_command(ps2dev, seq + i, PSMOUSE_CMD_SETRATE); + ps2_command(ps2dev, param, PSMOUSE_CMD_GETID); + + if (param[0] != 2) + return -1; + + if (set_properties) { + set_bit(BTN_EXTRA, psmouse->dev.keybit); + + psmouse->vendor = "Kensington"; + psmouse->name = "ThinkingMouse"; + } + + return 0; +} + +/* + * Bare PS/2 protocol "detection". Always succeeds. + */ +static int ps2bare_detect(struct psmouse *psmouse, int set_properties) +{ + if (!psmouse->vendor) psmouse->vendor = "Generic"; + if (!psmouse->name) psmouse->name = "Mouse"; + + return 0; +} + +/* + * psmouse_extensions() probes for any extensions to the basic PS/2 protocol + * the mouse may have. + */ + +static int psmouse_extensions(struct psmouse *psmouse, + unsigned int max_proto, int set_properties) +{ + int synaptics_hardware = 0; + +/* + * Try Kensington ThinkingMouse (we try first, because synaptics probe + * upsets the thinkingmouse). + */ + + if (max_proto > PSMOUSE_IMEX && thinking_detect(psmouse, set_properties) == 0) + return PSMOUSE_THINKPS; + +/* + * Try Synaptics TouchPad + */ + if (max_proto > PSMOUSE_PS2 && synaptics_detect(psmouse, set_properties) == 0) { + synaptics_hardware = 1; + + if (max_proto > PSMOUSE_IMEX) { + if (!set_properties || synaptics_init(psmouse) == 0) + return PSMOUSE_SYNAPTICS; +/* + * Some Synaptics touchpads can emulate extended protocols (like IMPS/2). + * Unfortunately Logitech/Genius probes confuse some firmware versions so + * we'll have to skip them. + */ + max_proto = PSMOUSE_IMEX; + } +/* + * Make sure that touchpad is in relative mode, gestures (taps) are enabled + */ + synaptics_reset(psmouse); + } + +/* + * Try ALPS TouchPad + */ + if (max_proto > PSMOUSE_IMEX) { + ps2_command(&psmouse->ps2dev, NULL, PSMOUSE_CMD_RESET_DIS); + if (alps_detect(psmouse, set_properties) == 0) { + if (!set_properties || alps_init(psmouse) == 0) + return PSMOUSE_ALPS; +/* + * Init failed, try basic relative protocols + */ + max_proto = PSMOUSE_IMEX; + } + } + + if (max_proto > PSMOUSE_IMEX && genius_detect(psmouse, set_properties) == 0) + return PSMOUSE_GENPS; + + if (max_proto > PSMOUSE_IMEX && ps2pp_init(psmouse, set_properties) == 0) + return PSMOUSE_PS2PP; + +/* + * Reset to defaults in case the device got confused by extended + * protocol probes. + */ + ps2_command(&psmouse->ps2dev, NULL, PSMOUSE_CMD_RESET_DIS); + + if (max_proto >= PSMOUSE_IMEX && im_explorer_detect(psmouse, set_properties) == 0) + return PSMOUSE_IMEX; + + if (max_proto >= PSMOUSE_IMPS && intellimouse_detect(psmouse, set_properties) == 0) + return PSMOUSE_IMPS; + +/* + * Okay, all failed, we have a standard mouse here. The number of the buttons + * is still a question, though. We assume 3. + */ + ps2bare_detect(psmouse, set_properties); + + if (synaptics_hardware) { +/* + * We detected Synaptics hardware but it did not respond to IMPS/2 probes. + * We need to reset the touchpad because if there is a track point on the + * pass through port it could get disabled while probing for protocol + * extensions. + */ + psmouse_reset(psmouse); + ps2_command(&psmouse->ps2dev, NULL, PSMOUSE_CMD_RESET_DIS); + } + + return PSMOUSE_PS2; +} + +/* + * psmouse_probe() probes for a PS/2 mouse. + */ + +static int psmouse_probe(struct psmouse *psmouse) +{ + struct ps2dev *ps2dev = &psmouse->ps2dev; + unsigned char param[2]; + +/* + * First, we check if it's a mouse. It should send 0x00 or 0x03 + * in case of an IntelliMouse in 4-byte mode or 0x04 for IM Explorer. + */ + + param[0] = 0xa5; + if (ps2_command(ps2dev, param, PSMOUSE_CMD_GETID)) + return -1; + + if (param[0] != 0x00 && param[0] != 0x03 && param[0] != 0x04) + return -1; + +/* + * Then we reset and disable the mouse so that it doesn't generate events. + */ + + if (ps2_command(ps2dev, NULL, PSMOUSE_CMD_RESET_DIS)) + printk(KERN_WARNING "psmouse.c: Failed to reset mouse on %s\n", ps2dev->serio->phys); + + return 0; +} + +/* + * Here we set the mouse resolution. + */ + +void psmouse_set_resolution(struct psmouse *psmouse, unsigned int resolution) +{ + unsigned char params[] = { 0, 1, 2, 2, 3 }; + + if (resolution == 0 || resolution > 200) + resolution = 200; + + ps2_command(&psmouse->ps2dev, ¶ms[resolution / 50], PSMOUSE_CMD_SETRES); + psmouse->resolution = 25 << params[resolution / 50]; +} + +/* + * Here we set the mouse report rate. + */ + +static void psmouse_set_rate(struct psmouse *psmouse, unsigned int rate) +{ + unsigned char rates[] = { 200, 100, 80, 60, 40, 20, 10, 0 }; + int i = 0; + + while (rates[i] > rate) i++; + ps2_command(&psmouse->ps2dev, &rates[i], PSMOUSE_CMD_SETRATE); + psmouse->rate = rates[i]; +} + +/* + * psmouse_initialize() initializes the mouse to a sane state. + */ + +static void psmouse_initialize(struct psmouse *psmouse) +{ +/* + * We set the mouse into streaming mode. + */ + + ps2_command(&psmouse->ps2dev, NULL, PSMOUSE_CMD_SETSTREAM); + +/* + * We set the mouse report rate, resolution and scaling. + */ + + if (psmouse_max_proto != PSMOUSE_PS2) { + psmouse->set_rate(psmouse, psmouse->rate); + psmouse->set_resolution(psmouse, psmouse->resolution); + ps2_command(&psmouse->ps2dev, NULL, PSMOUSE_CMD_SETSCALE11); + } +} + +/* + * psmouse_set_state() sets new psmouse state and resets all flags and + * counters while holding serio lock so fighting with interrupt handler + * is not a concern. + */ + +static void psmouse_set_state(struct psmouse *psmouse, enum psmouse_state new_state) +{ + serio_pause_rx(psmouse->ps2dev.serio); + psmouse->state = new_state; + psmouse->pktcnt = psmouse->out_of_sync = 0; + psmouse->ps2dev.flags = 0; + serio_continue_rx(psmouse->ps2dev.serio); +} + +/* + * psmouse_activate() enables the mouse so that we get motion reports from it. + */ + +static void psmouse_activate(struct psmouse *psmouse) +{ + if (ps2_command(&psmouse->ps2dev, NULL, PSMOUSE_CMD_ENABLE)) + printk(KERN_WARNING "psmouse.c: Failed to enable mouse on %s\n", + psmouse->ps2dev.serio->phys); + + psmouse_set_state(psmouse, PSMOUSE_ACTIVATED); +} + + +/* + * psmouse_deactivate() puts the mouse into poll mode so that we don't get motion + * reports from it unless we explicitely request it. + */ + +static void psmouse_deactivate(struct psmouse *psmouse) +{ + if (ps2_command(&psmouse->ps2dev, NULL, PSMOUSE_CMD_DISABLE)) + printk(KERN_WARNING "psmouse.c: Failed to deactivate mouse on %s\n", + psmouse->ps2dev.serio->phys); + + psmouse_set_state(psmouse, PSMOUSE_CMD_MODE); +} + + +/* + * psmouse_cleanup() resets the mouse into power-on state. + */ + +static void psmouse_cleanup(struct serio *serio) +{ + struct psmouse *psmouse = serio_get_drvdata(serio); + + psmouse_reset(psmouse); +} + +/* + * psmouse_disconnect() closes and frees. + */ + +static void psmouse_disconnect(struct serio *serio) +{ + struct psmouse *psmouse, *parent; + + device_remove_file(&serio->dev, &psmouse_attr_rate); + device_remove_file(&serio->dev, &psmouse_attr_resolution); + device_remove_file(&serio->dev, &psmouse_attr_resetafter); + + psmouse = serio_get_drvdata(serio); + psmouse_set_state(psmouse, PSMOUSE_CMD_MODE); + + if (serio->parent && serio->id.type == SERIO_PS_PSTHRU) { + parent = serio_get_drvdata(serio->parent); + if (parent->pt_deactivate) + parent->pt_deactivate(parent); + } + + if (psmouse->disconnect) + psmouse->disconnect(psmouse); + + psmouse_set_state(psmouse, PSMOUSE_IGNORE); + + input_unregister_device(&psmouse->dev); + serio_close(serio); + serio_set_drvdata(serio, NULL); + kfree(psmouse); +} + +/* + * psmouse_connect() is a callback from the serio module when + * an unhandled serio port is found. + */ +static int psmouse_connect(struct serio *serio, struct serio_driver *drv) +{ + struct psmouse *psmouse, *parent = NULL; + int retval; + + /* + * If this is a pass-through port deactivate parent so the device + * connected to this port can be successfully identified + */ + if (serio->parent && serio->id.type == SERIO_PS_PSTHRU) { + parent = serio_get_drvdata(serio->parent); + psmouse_deactivate(parent); + } + + if (!(psmouse = kmalloc(sizeof(struct psmouse), GFP_KERNEL))) { + retval = -ENOMEM; + goto out; + } + + memset(psmouse, 0, sizeof(struct psmouse)); + + ps2_init(&psmouse->ps2dev, serio); + sprintf(psmouse->phys, "%s/input0", serio->phys); + psmouse->dev.evbit[0] = BIT(EV_KEY) | BIT(EV_REL); + psmouse->dev.keybit[LONG(BTN_MOUSE)] = BIT(BTN_LEFT) | BIT(BTN_MIDDLE) | BIT(BTN_RIGHT); + psmouse->dev.relbit[0] = BIT(REL_X) | BIT(REL_Y); + psmouse->dev.private = psmouse; + psmouse->dev.dev = &serio->dev; + psmouse_set_state(psmouse, PSMOUSE_INITIALIZING); + + serio_set_drvdata(serio, psmouse); + + retval = serio_open(serio, drv); + if (retval) { + serio_set_drvdata(serio, NULL); + kfree(psmouse); + goto out; + } + + if (psmouse_probe(psmouse) < 0) { + serio_close(serio); + serio_set_drvdata(serio, NULL); + kfree(psmouse); + retval = -ENODEV; + goto out; + } + + psmouse->rate = psmouse_rate; + psmouse->resolution = psmouse_resolution; + psmouse->resetafter = psmouse_resetafter; + psmouse->smartscroll = psmouse_smartscroll; + psmouse->set_rate = psmouse_set_rate; + psmouse->set_resolution = psmouse_set_resolution; + psmouse->protocol_handler = psmouse_process_byte; + psmouse->pktsize = 3; + + psmouse->type = psmouse_extensions(psmouse, psmouse_max_proto, 1); + + sprintf(psmouse->devname, "%s %s %s", + psmouse_protocols[psmouse->type], psmouse->vendor, psmouse->name); + + psmouse->dev.name = psmouse->devname; + psmouse->dev.phys = psmouse->phys; + psmouse->dev.id.bustype = BUS_I8042; + psmouse->dev.id.vendor = 0x0002; + psmouse->dev.id.product = psmouse->type; + psmouse->dev.id.version = psmouse->model; + + input_register_device(&psmouse->dev); + + printk(KERN_INFO "input: %s on %s\n", psmouse->devname, serio->phys); + + psmouse_set_state(psmouse, PSMOUSE_CMD_MODE); + + psmouse_initialize(psmouse); + + if (parent && parent->pt_activate) + parent->pt_activate(parent); + + device_create_file(&serio->dev, &psmouse_attr_rate); + device_create_file(&serio->dev, &psmouse_attr_resolution); + device_create_file(&serio->dev, &psmouse_attr_resetafter); + + psmouse_activate(psmouse); + + retval = 0; + +out: + /* If this is a pass-through port the parent awaits to be activated */ + if (parent) + psmouse_activate(parent); + + return retval; +} + + +static int psmouse_reconnect(struct serio *serio) +{ + struct psmouse *psmouse = serio_get_drvdata(serio); + struct psmouse *parent = NULL; + struct serio_driver *drv = serio->drv; + int rc = -1; + + if (!drv || !psmouse) { + printk(KERN_DEBUG "psmouse: reconnect request, but serio is disconnected, ignoring...\n"); + return -1; + } + + if (serio->parent && serio->id.type == SERIO_PS_PSTHRU) { + parent = serio_get_drvdata(serio->parent); + psmouse_deactivate(parent); + } + + psmouse_set_state(psmouse, PSMOUSE_INITIALIZING); + + if (psmouse->reconnect) { + if (psmouse->reconnect(psmouse)) + goto out; + } else if (psmouse_probe(psmouse) < 0 || + psmouse->type != psmouse_extensions(psmouse, psmouse_max_proto, 0)) + goto out; + + /* ok, the device type (and capabilities) match the old one, + * we can continue using it, complete intialization + */ + psmouse_set_state(psmouse, PSMOUSE_CMD_MODE); + + psmouse_initialize(psmouse); + + if (parent && parent->pt_activate) + parent->pt_activate(parent); + + psmouse_activate(psmouse); + rc = 0; + +out: + /* If this is a pass-through port the parent waits to be activated */ + if (parent) + psmouse_activate(parent); + + return rc; +} + +static struct serio_device_id psmouse_serio_ids[] = { + { + .type = SERIO_8042, + .proto = SERIO_ANY, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { + .type = SERIO_PS_PSTHRU, + .proto = SERIO_ANY, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { 0 } +}; + +MODULE_DEVICE_TABLE(serio, psmouse_serio_ids); + +static struct serio_driver psmouse_drv = { + .driver = { + .name = "psmouse", + }, + .description = DRIVER_DESC, + .id_table = psmouse_serio_ids, + .interrupt = psmouse_interrupt, + .connect = psmouse_connect, + .reconnect = psmouse_reconnect, + .disconnect = psmouse_disconnect, + .cleanup = psmouse_cleanup, +}; + +ssize_t psmouse_attr_show_helper(struct device *dev, char *buf, + ssize_t (*handler)(struct psmouse *, char *)) +{ + struct serio *serio = to_serio_port(dev); + int retval; + + retval = serio_pin_driver(serio); + if (retval) + return retval; + + if (serio->drv != &psmouse_drv) { + retval = -ENODEV; + goto out; + } + + retval = handler(serio_get_drvdata(serio), buf); + +out: + serio_unpin_driver(serio); + return retval; +} + +ssize_t psmouse_attr_set_helper(struct device *dev, const char *buf, size_t count, + ssize_t (*handler)(struct psmouse *, const char *, size_t)) +{ + struct serio *serio = to_serio_port(dev); + struct psmouse *psmouse = serio_get_drvdata(serio); + struct psmouse *parent = NULL; + int retval; + + retval = serio_pin_driver(serio); + if (retval) + return retval; + + if (serio->drv != &psmouse_drv) { + retval = -ENODEV; + goto out; + } + + if (serio->parent && serio->id.type == SERIO_PS_PSTHRU) { + parent = serio_get_drvdata(serio->parent); + psmouse_deactivate(parent); + } + psmouse_deactivate(psmouse); + + retval = handler(psmouse, buf, count); + + psmouse_activate(psmouse); + if (parent) + psmouse_activate(parent); + +out: + serio_unpin_driver(serio); + return retval; +} + +static ssize_t psmouse_attr_show_rate(struct psmouse *psmouse, char *buf) +{ + return sprintf(buf, "%d\n", psmouse->rate); +} + +static ssize_t psmouse_attr_set_rate(struct psmouse *psmouse, const char *buf, size_t count) +{ + unsigned long value; + char *rest; + + value = simple_strtoul(buf, &rest, 10); + if (*rest) + return -EINVAL; + + psmouse->set_rate(psmouse, value); + return count; +} + +static ssize_t psmouse_attr_show_resolution(struct psmouse *psmouse, char *buf) +{ + return sprintf(buf, "%d\n", psmouse->resolution); +} + +static ssize_t psmouse_attr_set_resolution(struct psmouse *psmouse, const char *buf, size_t count) +{ + unsigned long value; + char *rest; + + value = simple_strtoul(buf, &rest, 10); + if (*rest) + return -EINVAL; + + psmouse->set_resolution(psmouse, value); + return count; +} + +static ssize_t psmouse_attr_show_resetafter(struct psmouse *psmouse, char *buf) +{ + return sprintf(buf, "%d\n", psmouse->resetafter); +} + +static ssize_t psmouse_attr_set_resetafter(struct psmouse *psmouse, const char *buf, size_t count) +{ + unsigned long value; + char *rest; + + value = simple_strtoul(buf, &rest, 10); + if (*rest) + return -EINVAL; + + psmouse->resetafter = value; + return count; +} + +static int psmouse_set_maxproto(const char *val, struct kernel_param *kp) +{ + int i; + + if (!val) + return -EINVAL; + + if (!strncmp(val, "any", 3)) { + *((unsigned int *)kp->arg) = -1UL; + return 0; + } + + for (i = 0; i < ARRAY_SIZE(psmouse_proto_abbrev); i++) { + if (!psmouse_proto_abbrev[i]) + continue; + + if (!strncmp(val, psmouse_proto_abbrev[i], strlen(psmouse_proto_abbrev[i]))) { + *((unsigned int *)kp->arg) = i; + return 0; + } + } + + return -EINVAL; \ +} + +static int psmouse_get_maxproto(char *buffer, struct kernel_param *kp) +{ + return sprintf(buffer, "%s\n", + psmouse_max_proto < ARRAY_SIZE(psmouse_proto_abbrev) ? + psmouse_proto_abbrev[psmouse_max_proto] : "any"); +} + +static int __init psmouse_init(void) +{ + serio_register_driver(&psmouse_drv); + return 0; +} + +static void __exit psmouse_exit(void) +{ + serio_unregister_driver(&psmouse_drv); +} + +module_init(psmouse_init); +module_exit(psmouse_exit); diff --git a/drivers/input/mouse/psmouse.h b/drivers/input/mouse/psmouse.h new file mode 100644 index 000000000000..bda5b065d03c --- /dev/null +++ b/drivers/input/mouse/psmouse.h @@ -0,0 +1,106 @@ +#ifndef _PSMOUSE_H +#define _PSMOUSE_H + +#define PSMOUSE_CMD_SETSCALE11 0x00e6 +#define PSMOUSE_CMD_SETSCALE21 0x00e7 +#define PSMOUSE_CMD_SETRES 0x10e8 +#define PSMOUSE_CMD_GETINFO 0x03e9 +#define PSMOUSE_CMD_SETSTREAM 0x00ea +#define PSMOUSE_CMD_SETPOLL 0x00f0 +#define PSMOUSE_CMD_POLL 0x03eb +#define PSMOUSE_CMD_GETID 0x02f2 +#define PSMOUSE_CMD_SETRATE 0x10f3 +#define PSMOUSE_CMD_ENABLE 0x00f4 +#define PSMOUSE_CMD_DISABLE 0x00f5 +#define PSMOUSE_CMD_RESET_DIS 0x00f6 +#define PSMOUSE_CMD_RESET_BAT 0x02ff + +#define PSMOUSE_RET_BAT 0xaa +#define PSMOUSE_RET_ID 0x00 +#define PSMOUSE_RET_ACK 0xfa +#define PSMOUSE_RET_NAK 0xfe + +enum psmouse_state { + PSMOUSE_IGNORE, + PSMOUSE_INITIALIZING, + PSMOUSE_CMD_MODE, + PSMOUSE_ACTIVATED, +}; + +/* psmouse protocol handler return codes */ +typedef enum { + PSMOUSE_BAD_DATA, + PSMOUSE_GOOD_DATA, + PSMOUSE_FULL_PACKET +} psmouse_ret_t; + +struct psmouse { + void *private; + struct input_dev dev; + struct ps2dev ps2dev; + char *vendor; + char *name; + unsigned char packet[8]; + unsigned char pktcnt; + unsigned char pktsize; + unsigned char type; + unsigned int model; + unsigned long last; + unsigned long out_of_sync; + enum psmouse_state state; + char devname[64]; + char phys[32]; + + unsigned int rate; + unsigned int resolution; + unsigned int resetafter; + unsigned int smartscroll; /* Logitech only */ + + psmouse_ret_t (*protocol_handler)(struct psmouse *psmouse, struct pt_regs *regs); + void (*set_rate)(struct psmouse *psmouse, unsigned int rate); + void (*set_resolution)(struct psmouse *psmouse, unsigned int resolution); + + int (*reconnect)(struct psmouse *psmouse); + void (*disconnect)(struct psmouse *psmouse); + + void (*pt_activate)(struct psmouse *psmouse); + void (*pt_deactivate)(struct psmouse *psmouse); +}; + +enum psmouse_type { + PSMOUSE_NONE, + PSMOUSE_PS2, + PSMOUSE_PS2PP, + PSMOUSE_THINKPS, + PSMOUSE_GENPS, + PSMOUSE_IMPS, + PSMOUSE_IMEX, + PSMOUSE_SYNAPTICS, + PSMOUSE_ALPS, +}; + +int psmouse_sliced_command(struct psmouse *psmouse, unsigned char command); +int psmouse_reset(struct psmouse *psmouse); +void psmouse_set_resolution(struct psmouse *psmouse, unsigned int resolution); + +ssize_t psmouse_attr_show_helper(struct device *dev, char *buf, + ssize_t (*handler)(struct psmouse *, char *)); +ssize_t psmouse_attr_set_helper(struct device *dev, const char *buf, size_t count, + ssize_t (*handler)(struct psmouse *, const char *, size_t)); + +#define PSMOUSE_DEFINE_ATTR(_name) \ +static ssize_t psmouse_attr_show_##_name(struct psmouse *, char *); \ +static ssize_t psmouse_attr_set_##_name(struct psmouse *, const char *, size_t);\ +static ssize_t psmouse_do_show_##_name(struct device *d, char *b) \ +{ \ + return psmouse_attr_show_helper(d, b, psmouse_attr_show_##_name); \ +} \ +static ssize_t psmouse_do_set_##_name(struct device *d, const char *b, size_t s)\ +{ \ + return psmouse_attr_set_helper(d, b, s, psmouse_attr_set_##_name); \ +} \ +static struct device_attribute psmouse_attr_##_name = \ + __ATTR(_name, S_IWUSR | S_IRUGO, \ + psmouse_do_show_##_name, psmouse_do_set_##_name); + +#endif /* _PSMOUSE_H */ diff --git a/drivers/input/mouse/rpcmouse.c b/drivers/input/mouse/rpcmouse.c new file mode 100644 index 000000000000..7280f68afcee --- /dev/null +++ b/drivers/input/mouse/rpcmouse.c @@ -0,0 +1,107 @@ +/* + * Acorn RiscPC mouse driver for Linux/ARM + * + * Copyright (c) 2000-2002 Vojtech Pavlik + * Copyright (C) 1996-2002 Russell King + * + */ + +/* + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This handles the Acorn RiscPCs mouse. We basically have a couple of + * hardware registers that track the sensor count for the X-Y movement and + * another register holding the button state. On every VSYNC interrupt we read + * the complete state and then work out if something has changed. + */ + +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/ptrace.h> +#include <linux/interrupt.h> +#include <linux/init.h> +#include <linux/input.h> + +#include <asm/hardware.h> +#include <asm/irq.h> +#include <asm/io.h> +#include <asm/hardware/iomd.h> + +MODULE_AUTHOR("Vojtech Pavlik, Russell King"); +MODULE_DESCRIPTION("Acorn RiscPC mouse driver"); +MODULE_LICENSE("GPL"); + +static short rpcmouse_lastx, rpcmouse_lasty; + +static struct input_dev rpcmouse_dev = { + .evbit = { BIT(EV_KEY) | BIT(EV_REL) }, + .keybit = { [LONG(BTN_LEFT)] = BIT(BTN_LEFT) | BIT(BTN_MIDDLE) | BIT(BTN_RIGHT) }, + .relbit = { BIT(REL_X) | BIT(REL_Y) }, + .name = "Acorn RiscPC Mouse", + .phys = "rpcmouse/input0", + .id = { + .bustype = BUS_HOST, + .vendor = 0x0005, + .product = 0x0001, + .version = 0x0100, + }, +}; + +static irqreturn_t rpcmouse_irq(int irq, void *dev_id, struct pt_regs *regs) +{ + struct input_dev *dev = dev_id; + short x, y, dx, dy, b; + + x = (short) iomd_readl(IOMD_MOUSEX); + y = (short) iomd_readl(IOMD_MOUSEY); + b = (short) (__raw_readl(0xe0310000) ^ 0x70); + + dx = x - rpcmouse_lastx; + dy = y - rpcmouse_lasty; + + rpcmouse_lastx = x; + rpcmouse_lasty = y; + + input_regs(dev, regs); + + input_report_rel(dev, REL_X, dx); + input_report_rel(dev, REL_Y, -dy); + + input_report_key(dev, BTN_LEFT, b & 0x40); + input_report_key(dev, BTN_MIDDLE, b & 0x20); + input_report_key(dev, BTN_RIGHT, b & 0x10); + + input_sync(dev); + + return IRQ_HANDLED; +} + +static int __init rpcmouse_init(void) +{ + init_input_dev(&rpcmouse_dev); + + rpcmouse_lastx = (short) iomd_readl(IOMD_MOUSEX); + rpcmouse_lasty = (short) iomd_readl(IOMD_MOUSEY); + + if (request_irq(IRQ_VSYNCPULSE, rpcmouse_irq, SA_SHIRQ, "rpcmouse", &rpcmouse_dev)) { + printk(KERN_ERR "rpcmouse: unable to allocate VSYNC interrupt\n"); + return -1; + } + + input_register_device(&rpcmouse_dev); + + printk(KERN_INFO "input: Acorn RiscPC mouse\n"); + + return 0; +} + +static void __exit rpcmouse_exit(void) +{ + input_unregister_device(&rpcmouse_dev); + free_irq(IRQ_VSYNCPULSE, &rpcmouse_dev); +} + +module_init(rpcmouse_init); +module_exit(rpcmouse_exit); diff --git a/drivers/input/mouse/sermouse.c b/drivers/input/mouse/sermouse.c new file mode 100644 index 000000000000..d12b93ae3900 --- /dev/null +++ b/drivers/input/mouse/sermouse.c @@ -0,0 +1,370 @@ +/* + * $Id: sermouse.c,v 1.17 2002/03/13 10:03:43 vojtech Exp $ + * + * Copyright (c) 1999-2001 Vojtech Pavlik + */ + +/* + * Serial mouse driver for Linux + */ + +/* + * 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 either by + * e-mail - mail your message to <vojtech@ucw.cz>, or by paper mail: + * Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic + */ + +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/input.h> +#include <linux/config.h> +#include <linux/serio.h> +#include <linux/init.h> + +#define DRIVER_DESC "Serial mouse driver" + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +static char *sermouse_protocols[] = { "None", "Mouse Systems Mouse", "Sun Mouse", "Microsoft Mouse", + "Logitech M+ Mouse", "Microsoft MZ Mouse", "Logitech MZ+ Mouse", + "Logitech MZ++ Mouse"}; + +struct sermouse { + struct input_dev dev; + signed char buf[8]; + unsigned char count; + unsigned char type; + unsigned long last; + char phys[32]; +}; + +/* + * sermouse_process_msc() analyzes the incoming MSC/Sun bytestream and + * applies some prediction to the data, resulting in 96 updates per + * second, which is as good as a PS/2 or USB mouse. + */ + +static void sermouse_process_msc(struct sermouse *sermouse, signed char data, struct pt_regs *regs) +{ + struct input_dev *dev = &sermouse->dev; + signed char *buf = sermouse->buf; + + input_regs(dev, regs); + + switch (sermouse->count) { + + case 0: + if ((data & 0xf8) != 0x80) return; + input_report_key(dev, BTN_LEFT, !(data & 4)); + input_report_key(dev, BTN_RIGHT, !(data & 1)); + input_report_key(dev, BTN_MIDDLE, !(data & 2)); + break; + + case 1: + case 3: + input_report_rel(dev, REL_X, data / 2); + input_report_rel(dev, REL_Y, -buf[1]); + buf[0] = data - data / 2; + break; + + case 2: + case 4: + input_report_rel(dev, REL_X, buf[0]); + input_report_rel(dev, REL_Y, buf[1] - data); + buf[1] = data / 2; + break; + } + + input_sync(dev); + + if (++sermouse->count == (5 - ((sermouse->type == SERIO_SUN) << 1))) + sermouse->count = 0; +} + +/* + * sermouse_process_ms() anlyzes the incoming MS(Z/+/++) bytestream and + * generates events. With prediction it gets 80 updates/sec, assuming + * standard 3-byte packets and 1200 bps. + */ + +static void sermouse_process_ms(struct sermouse *sermouse, signed char data, struct pt_regs *regs) +{ + struct input_dev *dev = &sermouse->dev; + signed char *buf = sermouse->buf; + + if (data & 0x40) sermouse->count = 0; + + input_regs(dev, regs); + + switch (sermouse->count) { + + case 0: + buf[1] = data; + input_report_key(dev, BTN_LEFT, (data >> 5) & 1); + input_report_key(dev, BTN_RIGHT, (data >> 4) & 1); + break; + + case 1: + buf[2] = data; + data = (signed char) (((buf[1] << 6) & 0xc0) | (data & 0x3f)); + input_report_rel(dev, REL_X, data / 2); + input_report_rel(dev, REL_Y, buf[4]); + buf[3] = data - data / 2; + break; + + case 2: + /* Guessing the state of the middle button on 3-button MS-protocol mice - ugly. */ + if ((sermouse->type == SERIO_MS) && !data && !buf[2] && !((buf[0] & 0xf0) ^ buf[1])) + input_report_key(dev, BTN_MIDDLE, !test_bit(BTN_MIDDLE, dev->key)); + buf[0] = buf[1]; + + data = (signed char) (((buf[1] << 4) & 0xc0) | (data & 0x3f)); + input_report_rel(dev, REL_X, buf[3]); + input_report_rel(dev, REL_Y, data - buf[4]); + buf[4] = data / 2; + break; + + case 3: + + switch (sermouse->type) { + + case SERIO_MS: + sermouse->type = SERIO_MP; + + case SERIO_MP: + if ((data >> 2) & 3) break; /* M++ Wireless Extension packet. */ + input_report_key(dev, BTN_MIDDLE, (data >> 5) & 1); + input_report_key(dev, BTN_SIDE, (data >> 4) & 1); + break; + + case SERIO_MZP: + case SERIO_MZPP: + input_report_key(dev, BTN_SIDE, (data >> 5) & 1); + + case SERIO_MZ: + input_report_key(dev, BTN_MIDDLE, (data >> 4) & 1); + input_report_rel(dev, REL_WHEEL, (data & 8) - (data & 7)); + break; + } + + break; + + case 4: + case 6: /* MZ++ packet type. We can get these bytes for M++ too but we ignore them later. */ + buf[1] = (data >> 2) & 0x0f; + break; + + case 5: + case 7: /* Ignore anything besides MZ++ */ + if (sermouse->type != SERIO_MZPP) break; + + switch (buf[1]) { + + case 1: /* Extra mouse info */ + + input_report_key(dev, BTN_SIDE, (data >> 4) & 1); + input_report_key(dev, BTN_EXTRA, (data >> 5) & 1); + input_report_rel(dev, data & 0x80 ? REL_HWHEEL : REL_WHEEL, (data & 7) - (data & 8)); + + break; + + default: /* We don't decode anything else yet. */ + + printk(KERN_WARNING + "sermouse.c: Received MZ++ packet %x, don't know how to handle.\n", buf[1]); + break; + } + + break; + } + + input_sync(dev); + + sermouse->count++; +} + +/* + * sermouse_interrupt() handles incoming characters, either gathering them into + * packets or passing them to the command routine as command output. + */ + +static irqreturn_t sermouse_interrupt(struct serio *serio, + unsigned char data, unsigned int flags, struct pt_regs *regs) +{ + struct sermouse *sermouse = serio_get_drvdata(serio); + + if (time_after(jiffies, sermouse->last + HZ/10)) sermouse->count = 0; + sermouse->last = jiffies; + + if (sermouse->type > SERIO_SUN) + sermouse_process_ms(sermouse, data, regs); + else + sermouse_process_msc(sermouse, data, regs); + return IRQ_HANDLED; +} + +/* + * sermouse_disconnect() cleans up after we don't want talk + * to the mouse anymore. + */ + +static void sermouse_disconnect(struct serio *serio) +{ + struct sermouse *sermouse = serio_get_drvdata(serio); + + input_unregister_device(&sermouse->dev); + serio_close(serio); + serio_set_drvdata(serio, NULL); + kfree(sermouse); +} + +/* + * sermouse_connect() is a callback form the serio module when + * an unhandled serio port is found. + */ + +static int sermouse_connect(struct serio *serio, struct serio_driver *drv) +{ + struct sermouse *sermouse; + unsigned char c; + int err; + + if (!serio->id.proto || serio->id.proto > SERIO_MZPP) + return -ENODEV; + + if (!(sermouse = kmalloc(sizeof(struct sermouse), GFP_KERNEL))) + return -ENOMEM; + + memset(sermouse, 0, sizeof(struct sermouse)); + + init_input_dev(&sermouse->dev); + sermouse->dev.evbit[0] = BIT(EV_KEY) | BIT(EV_REL); + sermouse->dev.keybit[LONG(BTN_MOUSE)] = BIT(BTN_LEFT) | BIT(BTN_RIGHT); + sermouse->dev.relbit[0] = BIT(REL_X) | BIT(REL_Y); + sermouse->dev.private = sermouse; + + sermouse->type = serio->id.proto; + c = serio->id.extra; + + if (c & 0x01) set_bit(BTN_MIDDLE, sermouse->dev.keybit); + if (c & 0x02) set_bit(BTN_SIDE, sermouse->dev.keybit); + if (c & 0x04) set_bit(BTN_EXTRA, sermouse->dev.keybit); + if (c & 0x10) set_bit(REL_WHEEL, sermouse->dev.relbit); + if (c & 0x20) set_bit(REL_HWHEEL, sermouse->dev.relbit); + + sprintf(sermouse->phys, "%s/input0", serio->phys); + + sermouse->dev.name = sermouse_protocols[sermouse->type]; + sermouse->dev.phys = sermouse->phys; + sermouse->dev.id.bustype = BUS_RS232; + sermouse->dev.id.vendor = sermouse->type; + sermouse->dev.id.product = c; + sermouse->dev.id.version = 0x0100; + sermouse->dev.dev = &serio->dev; + + serio_set_drvdata(serio, sermouse); + + err = serio_open(serio, drv); + if (err) { + serio_set_drvdata(serio, NULL); + kfree(sermouse); + return err; + } + + input_register_device(&sermouse->dev); + + printk(KERN_INFO "input: %s on %s\n", sermouse_protocols[sermouse->type], serio->phys); + + return 0; +} + +static struct serio_device_id sermouse_serio_ids[] = { + { + .type = SERIO_RS232, + .proto = SERIO_MSC, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { + .type = SERIO_RS232, + .proto = SERIO_SUN, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { + .type = SERIO_RS232, + .proto = SERIO_MS, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { + .type = SERIO_RS232, + .proto = SERIO_MP, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { + .type = SERIO_RS232, + .proto = SERIO_MZ, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { + .type = SERIO_RS232, + .proto = SERIO_MZP, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { + .type = SERIO_RS232, + .proto = SERIO_MZPP, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { 0 } +}; + +MODULE_DEVICE_TABLE(serio, sermouse_serio_ids); + +static struct serio_driver sermouse_drv = { + .driver = { + .name = "sermouse", + }, + .description = DRIVER_DESC, + .id_table = sermouse_serio_ids, + .interrupt = sermouse_interrupt, + .connect = sermouse_connect, + .disconnect = sermouse_disconnect, +}; + +static int __init sermouse_init(void) +{ + serio_register_driver(&sermouse_drv); + return 0; +} + +static void __exit sermouse_exit(void) +{ + serio_unregister_driver(&sermouse_drv); +} + +module_init(sermouse_init); +module_exit(sermouse_exit); diff --git a/drivers/input/mouse/synaptics.c b/drivers/input/mouse/synaptics.c new file mode 100644 index 000000000000..69832f8fb720 --- /dev/null +++ b/drivers/input/mouse/synaptics.c @@ -0,0 +1,700 @@ +/* + * Synaptics TouchPad PS/2 mouse driver + * + * 2003 Dmitry Torokhov <dtor@mail.ru> + * Added support for pass-through port. Special thanks to Peter Berg Larsen + * for explaining various Synaptics quirks. + * + * 2003 Peter Osterlund <petero2@telia.com> + * Ported to 2.5 input device infrastructure. + * + * Copyright (C) 2001 Stefan Gmeiner <riddlebox@freesurf.ch> + * start merging tpconfig and gpm code to a xfree-input module + * adding some changes and extensions (ex. 3rd and 4th button) + * + * Copyright (c) 1997 C. Scott Ananian <cananian@alumni.priceton.edu> + * Copyright (c) 1998-2000 Bruce Kalk <kall@compass.com> + * code for the special synaptics commands (from the tpconfig-source) + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * Trademarks are the property of their respective owners. + */ + +#include <linux/module.h> +#include <linux/input.h> +#include <linux/serio.h> +#include <linux/libps2.h> +#include "psmouse.h" +#include "synaptics.h" + +/* + * The x/y limits are taken from the Synaptics TouchPad interfacing Guide, + * section 2.3.2, which says that they should be valid regardless of the + * actual size of the sensor. + */ +#define XMIN_NOMINAL 1472 +#define XMAX_NOMINAL 5472 +#define YMIN_NOMINAL 1408 +#define YMAX_NOMINAL 4448 + +/***************************************************************************** + * Synaptics communications functions + ****************************************************************************/ + +/* + * Send a command to the synpatics touchpad by special commands + */ +static int synaptics_send_cmd(struct psmouse *psmouse, unsigned char c, unsigned char *param) +{ + if (psmouse_sliced_command(psmouse, c)) + return -1; + if (ps2_command(&psmouse->ps2dev, param, PSMOUSE_CMD_GETINFO)) + return -1; + return 0; +} + +/* + * Set the synaptics touchpad mode byte by special commands + */ +static int synaptics_mode_cmd(struct psmouse *psmouse, unsigned char mode) +{ + unsigned char param[1]; + + if (psmouse_sliced_command(psmouse, mode)) + return -1; + param[0] = SYN_PS_SET_MODE2; + if (ps2_command(&psmouse->ps2dev, param, PSMOUSE_CMD_SETRATE)) + return -1; + return 0; +} + +/* + * Read the model-id bytes from the touchpad + * see also SYN_MODEL_* macros + */ +static int synaptics_model_id(struct psmouse *psmouse) +{ + struct synaptics_data *priv = psmouse->private; + unsigned char mi[3]; + + if (synaptics_send_cmd(psmouse, SYN_QUE_MODEL, mi)) + return -1; + priv->model_id = (mi[0]<<16) | (mi[1]<<8) | mi[2]; + return 0; +} + +/* + * Read the capability-bits from the touchpad + * see also the SYN_CAP_* macros + */ +static int synaptics_capability(struct psmouse *psmouse) +{ + struct synaptics_data *priv = psmouse->private; + unsigned char cap[3]; + + if (synaptics_send_cmd(psmouse, SYN_QUE_CAPABILITIES, cap)) + return -1; + priv->capabilities = (cap[0] << 16) | (cap[1] << 8) | cap[2]; + priv->ext_cap = 0; + if (!SYN_CAP_VALID(priv->capabilities)) + return -1; + + /* + * Unless capExtended is set the rest of the flags should be ignored + */ + if (!SYN_CAP_EXTENDED(priv->capabilities)) + priv->capabilities = 0; + + if (SYN_EXT_CAP_REQUESTS(priv->capabilities) >= 1) { + if (synaptics_send_cmd(psmouse, SYN_QUE_EXT_CAPAB, cap)) { + printk(KERN_ERR "Synaptics claims to have extended capabilities," + " but I'm not able to read them."); + } else { + priv->ext_cap = (cap[0] << 16) | (cap[1] << 8) | cap[2]; + + /* + * if nExtBtn is greater than 8 it should be considered + * invalid and treated as 0 + */ + if (SYN_CAP_MULTI_BUTTON_NO(priv->ext_cap) > 8) + priv->ext_cap &= 0xff0fff; + } + } + return 0; +} + +/* + * Identify Touchpad + * See also the SYN_ID_* macros + */ +static int synaptics_identify(struct psmouse *psmouse) +{ + struct synaptics_data *priv = psmouse->private; + unsigned char id[3]; + + if (synaptics_send_cmd(psmouse, SYN_QUE_IDENTIFY, id)) + return -1; + priv->identity = (id[0]<<16) | (id[1]<<8) | id[2]; + if (SYN_ID_IS_SYNAPTICS(priv->identity)) + return 0; + return -1; +} + +static void print_ident(struct synaptics_data *priv) +{ + printk(KERN_INFO "Synaptics Touchpad, model: %ld\n", SYN_ID_MODEL(priv->identity)); + printk(KERN_INFO " Firmware: %ld.%ld\n", SYN_ID_MAJOR(priv->identity), + SYN_ID_MINOR(priv->identity)); + if (SYN_MODEL_ROT180(priv->model_id)) + printk(KERN_INFO " 180 degree mounted touchpad\n"); + if (SYN_MODEL_PORTRAIT(priv->model_id)) + printk(KERN_INFO " portrait touchpad\n"); + printk(KERN_INFO " Sensor: %ld\n", SYN_MODEL_SENSOR(priv->model_id)); + if (SYN_MODEL_NEWABS(priv->model_id)) + printk(KERN_INFO " new absolute packet format\n"); + if (SYN_MODEL_PEN(priv->model_id)) + printk(KERN_INFO " pen detection\n"); + + if (SYN_CAP_EXTENDED(priv->capabilities)) { + printk(KERN_INFO " Touchpad has extended capability bits\n"); + if (SYN_CAP_MULTI_BUTTON_NO(priv->ext_cap)) + printk(KERN_INFO " -> %d multi-buttons, i.e. besides standard buttons\n", + (int)(SYN_CAP_MULTI_BUTTON_NO(priv->ext_cap))); + if (SYN_CAP_MIDDLE_BUTTON(priv->capabilities)) + printk(KERN_INFO " -> middle button\n"); + if (SYN_CAP_FOUR_BUTTON(priv->capabilities)) + printk(KERN_INFO " -> four buttons\n"); + if (SYN_CAP_MULTIFINGER(priv->capabilities)) + printk(KERN_INFO " -> multifinger detection\n"); + if (SYN_CAP_PALMDETECT(priv->capabilities)) + printk(KERN_INFO " -> palm detection\n"); + if (SYN_CAP_PASS_THROUGH(priv->capabilities)) + printk(KERN_INFO " -> pass-through port\n"); + } +} + +static int synaptics_query_hardware(struct psmouse *psmouse) +{ + int retries = 0; + + while ((retries++ < 3) && psmouse_reset(psmouse)) + printk(KERN_ERR "synaptics reset failed\n"); + + if (synaptics_identify(psmouse)) + return -1; + if (synaptics_model_id(psmouse)) + return -1; + if (synaptics_capability(psmouse)) + return -1; + + return 0; +} + +static int synaptics_set_absolute_mode(struct psmouse *psmouse) +{ + struct synaptics_data *priv = psmouse->private; + + priv->mode = SYN_BIT_ABSOLUTE_MODE; + if (SYN_ID_MAJOR(priv->identity) >= 4) + priv->mode |= SYN_BIT_DISABLE_GESTURE; + if (SYN_CAP_EXTENDED(priv->capabilities)) + priv->mode |= SYN_BIT_W_MODE; + + if (synaptics_mode_cmd(psmouse, priv->mode)) + return -1; + + return 0; +} + +static void synaptics_set_rate(struct psmouse *psmouse, unsigned int rate) +{ + struct synaptics_data *priv = psmouse->private; + + if (rate >= 80) { + priv->mode |= SYN_BIT_HIGH_RATE; + psmouse->rate = 80; + } else { + priv->mode &= ~SYN_BIT_HIGH_RATE; + psmouse->rate = 40; + } + + synaptics_mode_cmd(psmouse, priv->mode); +} + +/***************************************************************************** + * Synaptics pass-through PS/2 port support + ****************************************************************************/ +static int synaptics_pt_write(struct serio *serio, unsigned char c) +{ + struct psmouse *parent = serio_get_drvdata(serio->parent); + char rate_param = SYN_PS_CLIENT_CMD; /* indicates that we want pass-through port */ + + if (psmouse_sliced_command(parent, c)) + return -1; + if (ps2_command(&parent->ps2dev, &rate_param, PSMOUSE_CMD_SETRATE)) + return -1; + return 0; +} + +static inline int synaptics_is_pt_packet(unsigned char *buf) +{ + return (buf[0] & 0xFC) == 0x84 && (buf[3] & 0xCC) == 0xC4; +} + +static void synaptics_pass_pt_packet(struct serio *ptport, unsigned char *packet) +{ + struct psmouse *child = serio_get_drvdata(ptport); + + if (child && child->state == PSMOUSE_ACTIVATED) { + serio_interrupt(ptport, packet[1], 0, NULL); + serio_interrupt(ptport, packet[4], 0, NULL); + serio_interrupt(ptport, packet[5], 0, NULL); + if (child->type >= PSMOUSE_GENPS) + serio_interrupt(ptport, packet[2], 0, NULL); + } else + serio_interrupt(ptport, packet[1], 0, NULL); +} + +static void synaptics_pt_activate(struct psmouse *psmouse) +{ + struct serio *ptport = psmouse->ps2dev.serio->child; + struct psmouse *child = serio_get_drvdata(ptport); + struct synaptics_data *priv = psmouse->private; + + /* adjust the touchpad to child's choice of protocol */ + if (child) { + if (child->type >= PSMOUSE_GENPS) + priv->mode |= SYN_BIT_FOUR_BYTE_CLIENT; + else + priv->mode &= ~SYN_BIT_FOUR_BYTE_CLIENT; + + if (synaptics_mode_cmd(psmouse, priv->mode)) + printk(KERN_INFO "synaptics: failed to switch guest protocol\n"); + } +} + +static void synaptics_pt_create(struct psmouse *psmouse) +{ + struct serio *serio; + + serio = kmalloc(sizeof(struct serio), GFP_KERNEL); + if (!serio) { + printk(KERN_ERR "synaptics: not enough memory to allocate pass-through port\n"); + return; + } + + memset(serio, 0, sizeof(struct serio)); + + serio->id.type = SERIO_PS_PSTHRU; + strlcpy(serio->name, "Synaptics pass-through", sizeof(serio->name)); + strlcpy(serio->phys, "synaptics-pt/serio0", sizeof(serio->name)); + serio->write = synaptics_pt_write; + serio->parent = psmouse->ps2dev.serio; + + psmouse->pt_activate = synaptics_pt_activate; + + printk(KERN_INFO "serio: %s port at %s\n", serio->name, psmouse->phys); + serio_register_port(serio); +} + +/***************************************************************************** + * Functions to interpret the absolute mode packets + ****************************************************************************/ + +static void synaptics_parse_hw_state(unsigned char buf[], struct synaptics_data *priv, struct synaptics_hw_state *hw) +{ + memset(hw, 0, sizeof(struct synaptics_hw_state)); + + if (SYN_MODEL_NEWABS(priv->model_id)) { + hw->x = (((buf[3] & 0x10) << 8) | + ((buf[1] & 0x0f) << 8) | + buf[4]); + hw->y = (((buf[3] & 0x20) << 7) | + ((buf[1] & 0xf0) << 4) | + buf[5]); + + hw->z = buf[2]; + hw->w = (((buf[0] & 0x30) >> 2) | + ((buf[0] & 0x04) >> 1) | + ((buf[3] & 0x04) >> 2)); + + hw->left = (buf[0] & 0x01) ? 1 : 0; + hw->right = (buf[0] & 0x02) ? 1 : 0; + + if (SYN_CAP_MIDDLE_BUTTON(priv->capabilities)) { + hw->middle = ((buf[0] ^ buf[3]) & 0x01) ? 1 : 0; + if (hw->w == 2) + hw->scroll = (signed char)(buf[1]); + } + + if (SYN_CAP_FOUR_BUTTON(priv->capabilities)) { + hw->up = ((buf[0] ^ buf[3]) & 0x01) ? 1 : 0; + hw->down = ((buf[0] ^ buf[3]) & 0x02) ? 1 : 0; + } + + if (SYN_CAP_MULTI_BUTTON_NO(priv->ext_cap) && + ((buf[0] ^ buf[3]) & 0x02)) { + switch (SYN_CAP_MULTI_BUTTON_NO(priv->ext_cap) & ~0x01) { + default: + /* + * if nExtBtn is greater than 8 it should be + * considered invalid and treated as 0 + */ + break; + case 8: + hw->ext_buttons |= ((buf[5] & 0x08)) ? 0x80 : 0; + hw->ext_buttons |= ((buf[4] & 0x08)) ? 0x40 : 0; + case 6: + hw->ext_buttons |= ((buf[5] & 0x04)) ? 0x20 : 0; + hw->ext_buttons |= ((buf[4] & 0x04)) ? 0x10 : 0; + case 4: + hw->ext_buttons |= ((buf[5] & 0x02)) ? 0x08 : 0; + hw->ext_buttons |= ((buf[4] & 0x02)) ? 0x04 : 0; + case 2: + hw->ext_buttons |= ((buf[5] & 0x01)) ? 0x02 : 0; + hw->ext_buttons |= ((buf[4] & 0x01)) ? 0x01 : 0; + } + } + } else { + hw->x = (((buf[1] & 0x1f) << 8) | buf[2]); + hw->y = (((buf[4] & 0x1f) << 8) | buf[5]); + + hw->z = (((buf[0] & 0x30) << 2) | (buf[3] & 0x3F)); + hw->w = (((buf[1] & 0x80) >> 4) | ((buf[0] & 0x04) >> 1)); + + hw->left = (buf[0] & 0x01) ? 1 : 0; + hw->right = (buf[0] & 0x02) ? 1 : 0; + } +} + +/* + * called for each full received packet from the touchpad + */ +static void synaptics_process_packet(struct psmouse *psmouse) +{ + struct input_dev *dev = &psmouse->dev; + struct synaptics_data *priv = psmouse->private; + struct synaptics_hw_state hw; + int num_fingers; + int finger_width; + int i; + + synaptics_parse_hw_state(psmouse->packet, priv, &hw); + + if (hw.scroll) { + priv->scroll += hw.scroll; + + while (priv->scroll >= 4) { + input_report_key(dev, BTN_BACK, !hw.down); + input_sync(dev); + input_report_key(dev, BTN_BACK, hw.down); + input_sync(dev); + priv->scroll -= 4; + } + while (priv->scroll <= -4) { + input_report_key(dev, BTN_FORWARD, !hw.up); + input_sync(dev); + input_report_key(dev, BTN_FORWARD, hw.up); + input_sync(dev); + priv->scroll += 4; + } + return; + } + + if (hw.z > 0) { + num_fingers = 1; + finger_width = 5; + if (SYN_CAP_EXTENDED(priv->capabilities)) { + switch (hw.w) { + case 0 ... 1: + if (SYN_CAP_MULTIFINGER(priv->capabilities)) + num_fingers = hw.w + 2; + break; + case 2: + if (SYN_MODEL_PEN(priv->model_id)) + ; /* Nothing, treat a pen as a single finger */ + break; + case 4 ... 15: + if (SYN_CAP_PALMDETECT(priv->capabilities)) + finger_width = hw.w; + break; + } + } + } else { + num_fingers = 0; + finger_width = 0; + } + + /* Post events + * BTN_TOUCH has to be first as mousedev relies on it when doing + * absolute -> relative conversion + */ + if (hw.z > 30) input_report_key(dev, BTN_TOUCH, 1); + if (hw.z < 25) input_report_key(dev, BTN_TOUCH, 0); + + if (hw.z > 0) { + input_report_abs(dev, ABS_X, hw.x); + input_report_abs(dev, ABS_Y, YMAX_NOMINAL + YMIN_NOMINAL - hw.y); + } + input_report_abs(dev, ABS_PRESSURE, hw.z); + + input_report_abs(dev, ABS_TOOL_WIDTH, finger_width); + input_report_key(dev, BTN_TOOL_FINGER, num_fingers == 1); + input_report_key(dev, BTN_TOOL_DOUBLETAP, num_fingers == 2); + input_report_key(dev, BTN_TOOL_TRIPLETAP, num_fingers == 3); + + input_report_key(dev, BTN_LEFT, hw.left); + input_report_key(dev, BTN_RIGHT, hw.right); + + if (SYN_CAP_MIDDLE_BUTTON(priv->capabilities)) + input_report_key(dev, BTN_MIDDLE, hw.middle); + + if (SYN_CAP_FOUR_BUTTON(priv->capabilities)) { + input_report_key(dev, BTN_FORWARD, hw.up); + input_report_key(dev, BTN_BACK, hw.down); + } + + for (i = 0; i < SYN_CAP_MULTI_BUTTON_NO(priv->ext_cap); i++) + input_report_key(dev, BTN_0 + i, hw.ext_buttons & (1 << i)); + + input_sync(dev); +} + +static int synaptics_validate_byte(unsigned char packet[], int idx, unsigned char pkt_type) +{ + static unsigned char newabs_mask[] = { 0xC8, 0x00, 0x00, 0xC8, 0x00 }; + static unsigned char newabs_rel_mask[] = { 0xC0, 0x00, 0x00, 0xC0, 0x00 }; + static unsigned char newabs_rslt[] = { 0x80, 0x00, 0x00, 0xC0, 0x00 }; + static unsigned char oldabs_mask[] = { 0xC0, 0x60, 0x00, 0xC0, 0x60 }; + static unsigned char oldabs_rslt[] = { 0xC0, 0x00, 0x00, 0x80, 0x00 }; + + if (idx < 0 || idx > 4) + return 0; + + switch (pkt_type) { + case SYN_NEWABS: + case SYN_NEWABS_RELAXED: + return (packet[idx] & newabs_rel_mask[idx]) == newabs_rslt[idx]; + + case SYN_NEWABS_STRICT: + return (packet[idx] & newabs_mask[idx]) == newabs_rslt[idx]; + + case SYN_OLDABS: + return (packet[idx] & oldabs_mask[idx]) == oldabs_rslt[idx]; + + default: + printk(KERN_ERR "synaptics: unknown packet type %d\n", pkt_type); + return 0; + } +} + +static unsigned char synaptics_detect_pkt_type(struct psmouse *psmouse) +{ + int i; + + for (i = 0; i < 5; i++) + if (!synaptics_validate_byte(psmouse->packet, i, SYN_NEWABS_STRICT)) { + printk(KERN_INFO "synaptics: using relaxed packet validation\n"); + return SYN_NEWABS_RELAXED; + } + + return SYN_NEWABS_STRICT; +} + +static psmouse_ret_t synaptics_process_byte(struct psmouse *psmouse, struct pt_regs *regs) +{ + struct input_dev *dev = &psmouse->dev; + struct synaptics_data *priv = psmouse->private; + + input_regs(dev, regs); + + if (psmouse->pktcnt >= 6) { /* Full packet received */ + if (unlikely(priv->pkt_type == SYN_NEWABS)) + priv->pkt_type = synaptics_detect_pkt_type(psmouse); + + if (SYN_CAP_PASS_THROUGH(priv->capabilities) && synaptics_is_pt_packet(psmouse->packet)) { + if (psmouse->ps2dev.serio->child) + synaptics_pass_pt_packet(psmouse->ps2dev.serio->child, psmouse->packet); + } else + synaptics_process_packet(psmouse); + + return PSMOUSE_FULL_PACKET; + } + + return synaptics_validate_byte(psmouse->packet, psmouse->pktcnt - 1, priv->pkt_type) ? + PSMOUSE_GOOD_DATA : PSMOUSE_BAD_DATA; +} + +/***************************************************************************** + * Driver initialization/cleanup functions + ****************************************************************************/ +static void set_input_params(struct input_dev *dev, struct synaptics_data *priv) +{ + int i; + + set_bit(EV_ABS, dev->evbit); + input_set_abs_params(dev, ABS_X, XMIN_NOMINAL, XMAX_NOMINAL, 0, 0); + input_set_abs_params(dev, ABS_Y, YMIN_NOMINAL, YMAX_NOMINAL, 0, 0); + input_set_abs_params(dev, ABS_PRESSURE, 0, 255, 0, 0); + set_bit(ABS_TOOL_WIDTH, dev->absbit); + + set_bit(EV_KEY, dev->evbit); + set_bit(BTN_TOUCH, dev->keybit); + set_bit(BTN_TOOL_FINGER, dev->keybit); + set_bit(BTN_TOOL_DOUBLETAP, dev->keybit); + set_bit(BTN_TOOL_TRIPLETAP, dev->keybit); + + set_bit(BTN_LEFT, dev->keybit); + set_bit(BTN_RIGHT, dev->keybit); + + if (SYN_CAP_MIDDLE_BUTTON(priv->capabilities)) + set_bit(BTN_MIDDLE, dev->keybit); + + if (SYN_CAP_FOUR_BUTTON(priv->capabilities) || + SYN_CAP_MIDDLE_BUTTON(priv->capabilities)) { + set_bit(BTN_FORWARD, dev->keybit); + set_bit(BTN_BACK, dev->keybit); + } + + for (i = 0; i < SYN_CAP_MULTI_BUTTON_NO(priv->ext_cap); i++) + set_bit(BTN_0 + i, dev->keybit); + + clear_bit(EV_REL, dev->evbit); + clear_bit(REL_X, dev->relbit); + clear_bit(REL_Y, dev->relbit); +} + +void synaptics_reset(struct psmouse *psmouse) +{ + /* reset touchpad back to relative mode, gestures enabled */ + synaptics_mode_cmd(psmouse, 0); +} + +static void synaptics_disconnect(struct psmouse *psmouse) +{ + synaptics_reset(psmouse); + kfree(psmouse->private); + psmouse->private = NULL; +} + +static int synaptics_reconnect(struct psmouse *psmouse) +{ + struct synaptics_data *priv = psmouse->private; + struct synaptics_data old_priv = *priv; + + if (synaptics_detect(psmouse, 0)) + return -1; + + if (synaptics_query_hardware(psmouse)) { + printk(KERN_ERR "Unable to query Synaptics hardware.\n"); + return -1; + } + + if (old_priv.identity != priv->identity || + old_priv.model_id != priv->model_id || + old_priv.capabilities != priv->capabilities || + old_priv.ext_cap != priv->ext_cap) + return -1; + + if (synaptics_set_absolute_mode(psmouse)) { + printk(KERN_ERR "Unable to initialize Synaptics hardware.\n"); + return -1; + } + + return 0; +} + +int synaptics_detect(struct psmouse *psmouse, int set_properties) +{ + struct ps2dev *ps2dev = &psmouse->ps2dev; + unsigned char param[4]; + + param[0] = 0; + + ps2_command(ps2dev, param, PSMOUSE_CMD_SETRES); + ps2_command(ps2dev, param, PSMOUSE_CMD_SETRES); + ps2_command(ps2dev, param, PSMOUSE_CMD_SETRES); + ps2_command(ps2dev, param, PSMOUSE_CMD_SETRES); + ps2_command(ps2dev, param, PSMOUSE_CMD_GETINFO); + + if (param[1] != 0x47) + return -1; + + if (set_properties) { + psmouse->vendor = "Synaptics"; + psmouse->name = "TouchPad"; + } + + return 0; +} + +#if defined(__i386__) +#include <linux/dmi.h> +static struct dmi_system_id toshiba_dmi_table[] = { + { + .ident = "Toshiba Satellite", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "TOSHIBA"), + DMI_MATCH(DMI_PRODUCT_NAME , "Satellite"), + }, + }, + { } +}; +#endif + +int synaptics_init(struct psmouse *psmouse) +{ + struct synaptics_data *priv; + + psmouse->private = priv = kmalloc(sizeof(struct synaptics_data), GFP_KERNEL); + if (!priv) + return -1; + memset(priv, 0, sizeof(struct synaptics_data)); + + if (synaptics_query_hardware(psmouse)) { + printk(KERN_ERR "Unable to query Synaptics hardware.\n"); + goto init_fail; + } + + if (synaptics_set_absolute_mode(psmouse)) { + printk(KERN_ERR "Unable to initialize Synaptics hardware.\n"); + goto init_fail; + } + + priv->pkt_type = SYN_MODEL_NEWABS(priv->model_id) ? SYN_NEWABS : SYN_OLDABS; + + print_ident(priv); + set_input_params(&psmouse->dev, priv); + + psmouse->protocol_handler = synaptics_process_byte; + psmouse->set_rate = synaptics_set_rate; + psmouse->disconnect = synaptics_disconnect; + psmouse->reconnect = synaptics_reconnect; + psmouse->pktsize = 6; + + if (SYN_CAP_PASS_THROUGH(priv->capabilities)) + synaptics_pt_create(psmouse); + +#if defined(__i386__) + /* + * Toshiba's KBC seems to have trouble handling data from + * Synaptics as full rate, switch to lower rate which is roughly + * thye same as rate of standard PS/2 mouse. + */ + if (psmouse->rate >= 80 && dmi_check_system(toshiba_dmi_table)) { + printk(KERN_INFO "synaptics: Toshiba Satellite detected, limiting rate to 40pps.\n"); + psmouse->rate = 40; + } +#endif + + return 0; + + init_fail: + kfree(priv); + return -1; +} + + diff --git a/drivers/input/mouse/synaptics.h b/drivers/input/mouse/synaptics.h new file mode 100644 index 000000000000..68fff1dcd7de --- /dev/null +++ b/drivers/input/mouse/synaptics.h @@ -0,0 +1,110 @@ +/* + * Synaptics TouchPad PS/2 mouse driver + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +#ifndef _SYNAPTICS_H +#define _SYNAPTICS_H + +extern int synaptics_detect(struct psmouse *psmouse, int set_properties); +extern int synaptics_init(struct psmouse *psmouse); +extern void synaptics_reset(struct psmouse *psmouse); + +/* synaptics queries */ +#define SYN_QUE_IDENTIFY 0x00 +#define SYN_QUE_MODES 0x01 +#define SYN_QUE_CAPABILITIES 0x02 +#define SYN_QUE_MODEL 0x03 +#define SYN_QUE_SERIAL_NUMBER_PREFIX 0x06 +#define SYN_QUE_SERIAL_NUMBER_SUFFIX 0x07 +#define SYN_QUE_RESOLUTION 0x08 +#define SYN_QUE_EXT_CAPAB 0x09 + +/* synatics modes */ +#define SYN_BIT_ABSOLUTE_MODE (1 << 7) +#define SYN_BIT_HIGH_RATE (1 << 6) +#define SYN_BIT_SLEEP_MODE (1 << 3) +#define SYN_BIT_DISABLE_GESTURE (1 << 2) +#define SYN_BIT_FOUR_BYTE_CLIENT (1 << 1) +#define SYN_BIT_W_MODE (1 << 0) + +/* synaptics model ID bits */ +#define SYN_MODEL_ROT180(m) ((m) & (1 << 23)) +#define SYN_MODEL_PORTRAIT(m) ((m) & (1 << 22)) +#define SYN_MODEL_SENSOR(m) (((m) >> 16) & 0x3f) +#define SYN_MODEL_HARDWARE(m) (((m) >> 9) & 0x7f) +#define SYN_MODEL_NEWABS(m) ((m) & (1 << 7)) +#define SYN_MODEL_PEN(m) ((m) & (1 << 6)) +#define SYN_MODEL_SIMPLIC(m) ((m) & (1 << 5)) +#define SYN_MODEL_GEOMETRY(m) ((m) & 0x0f) + +/* synaptics capability bits */ +#define SYN_CAP_EXTENDED(c) ((c) & (1 << 23)) +#define SYN_CAP_MIDDLE_BUTTON(c) ((c) & (1 << 18)) +#define SYN_CAP_PASS_THROUGH(c) ((c) & (1 << 7)) +#define SYN_CAP_SLEEP(c) ((c) & (1 << 4)) +#define SYN_CAP_FOUR_BUTTON(c) ((c) & (1 << 3)) +#define SYN_CAP_MULTIFINGER(c) ((c) & (1 << 1)) +#define SYN_CAP_PALMDETECT(c) ((c) & (1 << 0)) +#define SYN_CAP_VALID(c) ((((c) & 0x00ff00) >> 8) == 0x47) +#define SYN_EXT_CAP_REQUESTS(c) (((c) & 0x700000) >> 20) +#define SYN_CAP_MULTI_BUTTON_NO(ec) (((ec) & 0x00f000) >> 12) + +/* synaptics modes query bits */ +#define SYN_MODE_ABSOLUTE(m) ((m) & (1 << 7)) +#define SYN_MODE_RATE(m) ((m) & (1 << 6)) +#define SYN_MODE_BAUD_SLEEP(m) ((m) & (1 << 3)) +#define SYN_MODE_DISABLE_GESTURE(m) ((m) & (1 << 2)) +#define SYN_MODE_PACKSIZE(m) ((m) & (1 << 1)) +#define SYN_MODE_WMODE(m) ((m) & (1 << 0)) + +/* synaptics identify query bits */ +#define SYN_ID_MODEL(i) (((i) >> 4) & 0x0f) +#define SYN_ID_MAJOR(i) ((i) & 0x0f) +#define SYN_ID_MINOR(i) (((i) >> 16) & 0xff) +#define SYN_ID_IS_SYNAPTICS(i) ((((i) >> 8) & 0xff) == 0x47) + +/* synaptics special commands */ +#define SYN_PS_SET_MODE2 0x14 +#define SYN_PS_CLIENT_CMD 0x28 + +/* synaptics packet types */ +#define SYN_NEWABS 0 +#define SYN_NEWABS_STRICT 1 +#define SYN_NEWABS_RELAXED 2 +#define SYN_OLDABS 3 + +/* + * A structure to describe the state of the touchpad hardware (buttons and pad) + */ + +struct synaptics_hw_state { + int x; + int y; + int z; + int w; + unsigned int left:1; + unsigned int right:1; + unsigned int middle:1; + unsigned int up:1; + unsigned int down:1; + unsigned char ext_buttons; + signed char scroll; +}; + +struct synaptics_data { + /* Data read from the touchpad */ + unsigned long int model_id; /* Model-ID */ + unsigned long int capabilities; /* Capabilities */ + unsigned long int ext_cap; /* Extended Capabilities */ + unsigned long int identity; /* Identification */ + + unsigned char pkt_type; /* packet type - old, new, etc */ + unsigned char mode; /* current mode byte */ + int scroll; +}; + +#endif /* _SYNAPTICS_H */ diff --git a/drivers/input/mouse/vsxxxaa.c b/drivers/input/mouse/vsxxxaa.c new file mode 100644 index 000000000000..b2cb101c8110 --- /dev/null +++ b/drivers/input/mouse/vsxxxaa.c @@ -0,0 +1,591 @@ +/* + * Driver for DEC VSXXX-AA mouse (hockey-puck mouse, ball or two rollers) + * DEC VSXXX-GA mouse (rectangular mouse, with ball) + * DEC VSXXX-AB tablet (digitizer with hair cross or stylus) + * + * Copyright (C) 2003-2004 by Jan-Benedict Glaw <jbglaw@lug-owl.de> + * + * The packet format was initially taken from a patch to GPM which is (C) 2001 + * by Karsten Merker <merker@linuxtag.org> + * and Maciej W. Rozycki <macro@ds2.pg.gda.pl> + * Later on, I had access to the device's documentation (referenced below). + */ + +/* + * 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 + */ + +/* + * Building an adaptor to DE9 / DB25 RS232 + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * DISCLAIMER: Use this description AT YOUR OWN RISK! I'll not pay for + * anything if you break your mouse, your computer or whatever! + * + * In theory, this mouse is a simple RS232 device. In practice, it has got + * a quite uncommon plug and the requirement to additionally get a power + * supply at +5V and -12V. + * + * If you look at the socket/jack (_not_ at the plug), we use this pin + * numbering: + * _______ + * / 7 6 5 \ + * | 4 --- 3 | + * \ 2 1 / + * ------- + * + * DEC socket DE9 DB25 Note + * 1 (GND) 5 7 - + * 2 (RxD) 2 3 - + * 3 (TxD) 3 2 - + * 4 (-12V) - - Somewhere from the PSU. At ATX, it's + * the thin blue wire at pin 12 of the + * ATX power connector. Only required for + * VSXXX-AA/-GA mice. + * 5 (+5V) - - PSU (red wires of ATX power connector + * on pin 4, 6, 19 or 20) or HDD power + * connector (also red wire). + * 6 (+12V) - - HDD power connector, yellow wire. Only + * required for VSXXX-AB digitizer. + * 7 (dev. avail.) - - The mouse shorts this one to pin 1. + * This way, the host computer can detect + * the mouse. To use it with the adaptor, + * simply don't connect this pin. + * + * So to get a working adaptor, you need to connect the mouse with three + * wires to a RS232 port and two or three additional wires for +5V, +12V and + * -12V to the PSU. + * + * Flow specification for the link is 4800, 8o1. + * + * The mice and tablet are described in "VCB02 Video Subsystem - Technical + * Manual", DEC EK-104AA-TM-001. You'll find it at MANX, a search engine + * specific for DEC documentation. Try + * http://www.vt100.net/manx/details?pn=EK-104AA-TM-001;id=21;cp=1 + */ + +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/input.h> +#include <linux/config.h> +#include <linux/serio.h> +#include <linux/init.h> + +#define DRIVER_DESC "Driver for DEC VSXXX-AA and -GA mice and VSXXX-AB tablet" + +MODULE_AUTHOR ("Jan-Benedict Glaw <jbglaw@lug-owl.de>"); +MODULE_DESCRIPTION (DRIVER_DESC); +MODULE_LICENSE ("GPL"); + +#undef VSXXXAA_DEBUG +#ifdef VSXXXAA_DEBUG +#define DBG(x...) printk (x) +#else +#define DBG(x...) do {} while (0) +#endif + +#define VSXXXAA_INTRO_MASK 0x80 +#define VSXXXAA_INTRO_HEAD 0x80 +#define IS_HDR_BYTE(x) (((x) & VSXXXAA_INTRO_MASK) \ + == VSXXXAA_INTRO_HEAD) + +#define VSXXXAA_PACKET_MASK 0xe0 +#define VSXXXAA_PACKET_REL 0x80 +#define VSXXXAA_PACKET_ABS 0xc0 +#define VSXXXAA_PACKET_POR 0xa0 +#define MATCH_PACKET_TYPE(data, type) (((data) & VSXXXAA_PACKET_MASK) == (type)) + + + +struct vsxxxaa { + struct input_dev dev; + struct serio *serio; +#define BUFLEN 15 /* At least 5 is needed for a full tablet packet */ + unsigned char buf[BUFLEN]; + unsigned char count; + unsigned char version; + unsigned char country; + unsigned char type; + char name[64]; + char phys[32]; +}; + +static void +vsxxxaa_drop_bytes (struct vsxxxaa *mouse, int num) +{ + if (num >= mouse->count) + mouse->count = 0; + else { + memmove (mouse->buf, mouse->buf + num - 1, BUFLEN - num); + mouse->count -= num; + } +} + +static void +vsxxxaa_queue_byte (struct vsxxxaa *mouse, unsigned char byte) +{ + if (mouse->count == BUFLEN) { + printk (KERN_ERR "%s on %s: Dropping a byte of full buffer.\n", + mouse->name, mouse->phys); + vsxxxaa_drop_bytes (mouse, 1); + } + DBG (KERN_INFO "Queueing byte 0x%02x\n", byte); + + mouse->buf[mouse->count++] = byte; +} + +static void +vsxxxaa_detection_done (struct vsxxxaa *mouse) +{ + switch (mouse->type) { + case 0x02: + sprintf (mouse->name, "DEC VSXXX-AA/-GA mouse"); + break; + + case 0x04: + sprintf (mouse->name, "DEC VSXXX-AB digitizer"); + break; + + default: + sprintf (mouse->name, "unknown DEC pointer device " + "(type = 0x%02x)", mouse->type); + break; + } + + printk (KERN_INFO "Found %s version 0x%02x from country 0x%02x " + "on port %s\n", mouse->name, mouse->version, + mouse->country, mouse->phys); +} + +/* + * Returns number of bytes to be dropped, 0 if packet is okay. + */ +static int +vsxxxaa_check_packet (struct vsxxxaa *mouse, int packet_len) +{ + int i; + + /* First byte must be a header byte */ + if (!IS_HDR_BYTE (mouse->buf[0])) { + DBG ("vsck: len=%d, 1st=0x%02x\n", packet_len, mouse->buf[0]); + return 1; + } + + /* Check all following bytes */ + if (packet_len > 1) { + for (i = 1; i < packet_len; i++) { + if (IS_HDR_BYTE (mouse->buf[i])) { + printk (KERN_ERR "Need to drop %d bytes " + "of a broken packet.\n", + i - 1); + DBG (KERN_INFO "check: len=%d, b[%d]=0x%02x\n", + packet_len, i, mouse->buf[i]); + return i - 1; + } + } + } + + return 0; +} + +static __inline__ int +vsxxxaa_smells_like_packet (struct vsxxxaa *mouse, unsigned char type, size_t len) +{ + return (mouse->count >= len) && MATCH_PACKET_TYPE (mouse->buf[0], type); +} + +static void +vsxxxaa_handle_REL_packet (struct vsxxxaa *mouse, struct pt_regs *regs) +{ + struct input_dev *dev = &mouse->dev; + unsigned char *buf = mouse->buf; + int left, middle, right; + int dx, dy; + + /* + * Check for normal stream packets. This is three bytes, + * with the first byte's 3 MSB set to 100. + * + * [0]: 1 0 0 SignX SignY Left Middle Right + * [1]: 0 dx dx dx dx dx dx dx + * [2]: 0 dy dy dy dy dy dy dy + */ + + /* + * Low 7 bit of byte 1 are abs(dx), bit 7 is + * 0, bit 4 of byte 0 is direction. + */ + dx = buf[1] & 0x7f; + dx *= ((buf[0] >> 4) & 0x01)? 1: -1; + + /* + * Low 7 bit of byte 2 are abs(dy), bit 7 is + * 0, bit 3 of byte 0 is direction. + */ + dy = buf[2] & 0x7f; + dy *= ((buf[0] >> 3) & 0x01)? -1: 1; + + /* + * Get button state. It's the low three bits + * (for three buttons) of byte 0. + */ + left = (buf[0] & 0x04)? 1: 0; + middle = (buf[0] & 0x02)? 1: 0; + right = (buf[0] & 0x01)? 1: 0; + + vsxxxaa_drop_bytes (mouse, 3); + + DBG (KERN_INFO "%s on %s: dx=%d, dy=%d, buttons=%s%s%s\n", + mouse->name, mouse->phys, dx, dy, + left? "L": "l", middle? "M": "m", right? "R": "r"); + + /* + * Report what we've found so far... + */ + input_regs (dev, regs); + input_report_key (dev, BTN_LEFT, left); + input_report_key (dev, BTN_MIDDLE, middle); + input_report_key (dev, BTN_RIGHT, right); + input_report_key (dev, BTN_TOUCH, 0); + input_report_rel (dev, REL_X, dx); + input_report_rel (dev, REL_Y, dy); + input_sync (dev); +} + +static void +vsxxxaa_handle_ABS_packet (struct vsxxxaa *mouse, struct pt_regs *regs) +{ + struct input_dev *dev = &mouse->dev; + unsigned char *buf = mouse->buf; + int left, middle, right, touch; + int x, y; + + /* + * Tablet position / button packet + * + * [0]: 1 1 0 B4 B3 B2 B1 Pr + * [1]: 0 0 X5 X4 X3 X2 X1 X0 + * [2]: 0 0 X11 X10 X9 X8 X7 X6 + * [3]: 0 0 Y5 Y4 Y3 Y2 Y1 Y0 + * [4]: 0 0 Y11 Y10 Y9 Y8 Y7 Y6 + */ + + /* + * Get X/Y position. Y axis needs to be inverted since VSXXX-AB + * counts down->top while monitor counts top->bottom. + */ + x = ((buf[2] & 0x3f) << 6) | (buf[1] & 0x3f); + y = ((buf[4] & 0x3f) << 6) | (buf[3] & 0x3f); + y = 1023 - y; + + /* + * Get button state. It's bits <4..1> of byte 0. + */ + left = (buf[0] & 0x02)? 1: 0; + middle = (buf[0] & 0x04)? 1: 0; + right = (buf[0] & 0x08)? 1: 0; + touch = (buf[0] & 0x10)? 1: 0; + + vsxxxaa_drop_bytes (mouse, 5); + + DBG (KERN_INFO "%s on %s: x=%d, y=%d, buttons=%s%s%s%s\n", + mouse->name, mouse->phys, x, y, + left? "L": "l", middle? "M": "m", + right? "R": "r", touch? "T": "t"); + + /* + * Report what we've found so far... + */ + input_regs (dev, regs); + input_report_key (dev, BTN_LEFT, left); + input_report_key (dev, BTN_MIDDLE, middle); + input_report_key (dev, BTN_RIGHT, right); + input_report_key (dev, BTN_TOUCH, touch); + input_report_abs (dev, ABS_X, x); + input_report_abs (dev, ABS_Y, y); + input_sync (dev); +} + +static void +vsxxxaa_handle_POR_packet (struct vsxxxaa *mouse, struct pt_regs *regs) +{ + struct input_dev *dev = &mouse->dev; + unsigned char *buf = mouse->buf; + int left, middle, right; + unsigned char error; + + /* + * Check for Power-On-Reset packets. These are sent out + * after plugging the mouse in, or when explicitely + * requested by sending 'T'. + * + * [0]: 1 0 1 0 R3 R2 R1 R0 + * [1]: 0 M2 M1 M0 D3 D2 D1 D0 + * [2]: 0 E6 E5 E4 E3 E2 E1 E0 + * [3]: 0 0 0 0 0 Left Middle Right + * + * M: manufacturer location code + * R: revision code + * E: Error code. If it's in the range of 0x00..0x1f, only some + * minor problem occured. Errors >= 0x20 are considered bad + * and the device may not work properly... + * D: <0010> == mouse, <0100> == tablet + */ + + mouse->version = buf[0] & 0x0f; + mouse->country = (buf[1] >> 4) & 0x07; + mouse->type = buf[1] & 0x0f; + error = buf[2] & 0x7f; + + /* + * Get button state. It's the low three bits + * (for three buttons) of byte 0. Maybe even the bit <3> + * has some meaning if a tablet is attached. + */ + left = (buf[0] & 0x04)? 1: 0; + middle = (buf[0] & 0x02)? 1: 0; + right = (buf[0] & 0x01)? 1: 0; + + vsxxxaa_drop_bytes (mouse, 4); + vsxxxaa_detection_done (mouse); + + if (error <= 0x1f) { + /* No (serious) error. Report buttons */ + input_regs (dev, regs); + input_report_key (dev, BTN_LEFT, left); + input_report_key (dev, BTN_MIDDLE, middle); + input_report_key (dev, BTN_RIGHT, right); + input_report_key (dev, BTN_TOUCH, 0); + input_sync (dev); + + if (error != 0) + printk (KERN_INFO "Your %s on %s reports error=0x%02x\n", + mouse->name, mouse->phys, error); + + } + + /* + * If the mouse was hot-plugged, we need to force differential mode + * now... However, give it a second to recover from it's reset. + */ + printk (KERN_NOTICE "%s on %s: Forceing standard packet format, " + "incremental streaming mode and 72 samples/sec\n", + mouse->name, mouse->phys); + mouse->serio->write (mouse->serio, 'S'); /* Standard format */ + mdelay (50); + mouse->serio->write (mouse->serio, 'R'); /* Incremental */ + mdelay (50); + mouse->serio->write (mouse->serio, 'L'); /* 72 samples/sec */ +} + +static void +vsxxxaa_parse_buffer (struct vsxxxaa *mouse, struct pt_regs *regs) +{ + unsigned char *buf = mouse->buf; + int stray_bytes; + + /* + * Parse buffer to death... + */ + do { + /* + * Out of sync? Throw away what we don't understand. Each + * packet starts with a byte whose bit 7 is set. Unhandled + * packets (ie. which we don't know about or simply b0rk3d + * data...) will get shifted out of the buffer after some + * activity on the mouse. + */ + while (mouse->count > 0 && !IS_HDR_BYTE(buf[0])) { + printk (KERN_ERR "%s on %s: Dropping a byte to regain " + "sync with mouse data stream...\n", + mouse->name, mouse->phys); + vsxxxaa_drop_bytes (mouse, 1); + } + + /* + * Check for packets we know about. + */ + + if (vsxxxaa_smells_like_packet (mouse, VSXXXAA_PACKET_REL, 3)) { + /* Check for broken packet */ + stray_bytes = vsxxxaa_check_packet (mouse, 3); + if (stray_bytes > 0) { + printk (KERN_ERR "Dropping %d bytes now...\n", + stray_bytes); + vsxxxaa_drop_bytes (mouse, stray_bytes); + continue; + } + + vsxxxaa_handle_REL_packet (mouse, regs); + continue; /* More to parse? */ + } + + if (vsxxxaa_smells_like_packet (mouse, VSXXXAA_PACKET_ABS, 5)) { + /* Check for broken packet */ + stray_bytes = vsxxxaa_check_packet (mouse, 5); + if (stray_bytes > 0) { + printk (KERN_ERR "Dropping %d bytes now...\n", + stray_bytes); + vsxxxaa_drop_bytes (mouse, stray_bytes); + continue; + } + + vsxxxaa_handle_ABS_packet (mouse, regs); + continue; /* More to parse? */ + } + + if (vsxxxaa_smells_like_packet (mouse, VSXXXAA_PACKET_POR, 4)) { + /* Check for broken packet */ + stray_bytes = vsxxxaa_check_packet (mouse, 4); + if (stray_bytes > 0) { + printk (KERN_ERR "Dropping %d bytes now...\n", + stray_bytes); + vsxxxaa_drop_bytes (mouse, stray_bytes); + continue; + } + + vsxxxaa_handle_POR_packet (mouse, regs); + continue; /* More to parse? */ + } + + break; /* No REL, ABS or POR packet found */ + } while (1); +} + +static irqreturn_t +vsxxxaa_interrupt (struct serio *serio, unsigned char data, unsigned int flags, + struct pt_regs *regs) +{ + struct vsxxxaa *mouse = serio_get_drvdata (serio); + + vsxxxaa_queue_byte (mouse, data); + vsxxxaa_parse_buffer (mouse, regs); + + return IRQ_HANDLED; +} + +static void +vsxxxaa_disconnect (struct serio *serio) +{ + struct vsxxxaa *mouse = serio_get_drvdata (serio); + + input_unregister_device (&mouse->dev); + serio_close (serio); + serio_set_drvdata (serio, NULL); + kfree (mouse); +} + +static int +vsxxxaa_connect (struct serio *serio, struct serio_driver *drv) +{ + struct vsxxxaa *mouse; + int err; + + if (!(mouse = kmalloc (sizeof (struct vsxxxaa), GFP_KERNEL))) + return -ENOMEM; + + memset (mouse, 0, sizeof (struct vsxxxaa)); + + init_input_dev (&mouse->dev); + set_bit (EV_KEY, mouse->dev.evbit); /* We have buttons */ + set_bit (EV_REL, mouse->dev.evbit); + set_bit (EV_ABS, mouse->dev.evbit); + set_bit (BTN_LEFT, mouse->dev.keybit); /* We have 3 buttons */ + set_bit (BTN_MIDDLE, mouse->dev.keybit); + set_bit (BTN_RIGHT, mouse->dev.keybit); + set_bit (BTN_TOUCH, mouse->dev.keybit); /* ...and Tablet */ + set_bit (REL_X, mouse->dev.relbit); + set_bit (REL_Y, mouse->dev.relbit); + set_bit (ABS_X, mouse->dev.absbit); + set_bit (ABS_Y, mouse->dev.absbit); + + mouse->dev.absmin[ABS_X] = 0; + mouse->dev.absmax[ABS_X] = 1023; + mouse->dev.absmin[ABS_Y] = 0; + mouse->dev.absmax[ABS_Y] = 1023; + + mouse->dev.private = mouse; + + sprintf (mouse->name, "DEC VSXXX-AA/-GA mouse or VSXXX-AB digitizer"); + sprintf (mouse->phys, "%s/input0", serio->phys); + mouse->dev.name = mouse->name; + mouse->dev.phys = mouse->phys; + mouse->dev.id.bustype = BUS_RS232; + mouse->dev.dev = &serio->dev; + mouse->serio = serio; + + serio_set_drvdata (serio, mouse); + + err = serio_open (serio, drv); + if (err) { + serio_set_drvdata (serio, NULL); + kfree (mouse); + return err; + } + + /* + * Request selftest. Standard packet format and differential + * mode will be requested after the device ID'ed successfully. + */ + mouse->serio->write (mouse->serio, 'T'); /* Test */ + + input_register_device (&mouse->dev); + + printk (KERN_INFO "input: %s on %s\n", mouse->name, mouse->phys); + + return 0; +} + +static struct serio_device_id vsxxaa_serio_ids[] = { + { + .type = SERIO_RS232, + .proto = SERIO_VSXXXAA, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { 0 } +}; + +MODULE_DEVICE_TABLE(serio, vsxxaa_serio_ids); + +static struct serio_driver vsxxxaa_drv = { + .driver = { + .name = "vsxxxaa", + }, + .description = DRIVER_DESC, + .id_table = vsxxaa_serio_ids, + .connect = vsxxxaa_connect, + .interrupt = vsxxxaa_interrupt, + .disconnect = vsxxxaa_disconnect, +}; + +static int __init +vsxxxaa_init (void) +{ + serio_register_driver(&vsxxxaa_drv); + return 0; +} + +static void __exit +vsxxxaa_exit (void) +{ + serio_unregister_driver(&vsxxxaa_drv); +} + +module_init (vsxxxaa_init); +module_exit (vsxxxaa_exit); + diff --git a/drivers/input/mousedev.c b/drivers/input/mousedev.c new file mode 100644 index 000000000000..564974ce5793 --- /dev/null +++ b/drivers/input/mousedev.c @@ -0,0 +1,758 @@ +/* + * Input driver to ExplorerPS/2 device driver module. + * + * Copyright (c) 1999-2002 Vojtech Pavlik + * Copyright (c) 2004 Dmitry Torokhov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +#define MOUSEDEV_MINOR_BASE 32 +#define MOUSEDEV_MINORS 32 +#define MOUSEDEV_MIX 31 + +#include <linux/slab.h> +#include <linux/poll.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/config.h> +#include <linux/smp_lock.h> +#include <linux/random.h> +#include <linux/major.h> +#include <linux/device.h> +#include <linux/devfs_fs_kernel.h> +#ifdef CONFIG_INPUT_MOUSEDEV_PSAUX +#include <linux/miscdevice.h> +#endif + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION("Mouse (ExplorerPS/2) device interfaces"); +MODULE_LICENSE("GPL"); + +#ifndef CONFIG_INPUT_MOUSEDEV_SCREEN_X +#define CONFIG_INPUT_MOUSEDEV_SCREEN_X 1024 +#endif +#ifndef CONFIG_INPUT_MOUSEDEV_SCREEN_Y +#define CONFIG_INPUT_MOUSEDEV_SCREEN_Y 768 +#endif + +static int xres = CONFIG_INPUT_MOUSEDEV_SCREEN_X; +module_param(xres, uint, 0); +MODULE_PARM_DESC(xres, "Horizontal screen resolution"); + +static int yres = CONFIG_INPUT_MOUSEDEV_SCREEN_Y; +module_param(yres, uint, 0); +MODULE_PARM_DESC(yres, "Vertical screen resolution"); + +static unsigned tap_time = 200; +module_param(tap_time, uint, 0); +MODULE_PARM_DESC(tap_time, "Tap time for touchpads in absolute mode (msecs)"); + +struct mousedev_hw_data { + int dx, dy, dz; + int x, y; + int abs_event; + unsigned long buttons; +}; + +struct mousedev { + int exist; + int open; + int minor; + char name[16]; + wait_queue_head_t wait; + struct list_head list; + struct input_handle handle; + + struct mousedev_hw_data packet; + unsigned int pkt_count; + int old_x[4], old_y[4]; + int frac_dx, frac_dy; + unsigned long touch; +}; + +enum mousedev_emul { + MOUSEDEV_EMUL_PS2, + MOUSEDEV_EMUL_IMPS, + MOUSEDEV_EMUL_EXPS +}; + +struct mousedev_motion { + int dx, dy, dz; + unsigned long buttons; +}; + +#define PACKET_QUEUE_LEN 16 +struct mousedev_list { + struct fasync_struct *fasync; + struct mousedev *mousedev; + struct list_head node; + + struct mousedev_motion packets[PACKET_QUEUE_LEN]; + unsigned int head, tail; + spinlock_t packet_lock; + int pos_x, pos_y; + + signed char ps2[6]; + unsigned char ready, buffer, bufsiz; + unsigned char imexseq, impsseq; + enum mousedev_emul mode; +}; + +#define MOUSEDEV_SEQ_LEN 6 + +static unsigned char mousedev_imps_seq[] = { 0xf3, 200, 0xf3, 100, 0xf3, 80 }; +static unsigned char mousedev_imex_seq[] = { 0xf3, 200, 0xf3, 200, 0xf3, 80 }; + +static struct input_handler mousedev_handler; + +static struct mousedev *mousedev_table[MOUSEDEV_MINORS]; +static struct mousedev mousedev_mix; + +#define fx(i) (mousedev->old_x[(mousedev->pkt_count - (i)) & 03]) +#define fy(i) (mousedev->old_y[(mousedev->pkt_count - (i)) & 03]) + +static void mousedev_touchpad_event(struct input_dev *dev, struct mousedev *mousedev, unsigned int code, int value) +{ + int size, tmp; + enum { FRACTION_DENOM = 128 }; + + if (mousedev->touch) { + size = dev->absmax[ABS_X] - dev->absmin[ABS_X]; + if (size == 0) size = 256 * 2; + switch (code) { + case ABS_X: + fx(0) = value; + if (mousedev->pkt_count >= 2) { + tmp = ((value - fx(2)) * (256 * FRACTION_DENOM)) / size; + tmp += mousedev->frac_dx; + mousedev->packet.dx = tmp / FRACTION_DENOM; + mousedev->frac_dx = tmp - mousedev->packet.dx * FRACTION_DENOM; + } + break; + + case ABS_Y: + fy(0) = value; + if (mousedev->pkt_count >= 2) { + tmp = -((value - fy(2)) * (256 * FRACTION_DENOM)) / size; + tmp += mousedev->frac_dy; + mousedev->packet.dy = tmp / FRACTION_DENOM; + mousedev->frac_dy = tmp - mousedev->packet.dy * FRACTION_DENOM; + } + break; + } + } +} + +static void mousedev_abs_event(struct input_dev *dev, struct mousedev *mousedev, unsigned int code, int value) +{ + int size; + + switch (code) { + case ABS_X: + size = dev->absmax[ABS_X] - dev->absmin[ABS_X]; + if (size == 0) size = xres; + if (value > dev->absmax[ABS_X]) value = dev->absmax[ABS_X]; + if (value < dev->absmin[ABS_X]) value = dev->absmin[ABS_X]; + mousedev->packet.x = ((value - dev->absmin[ABS_X]) * xres) / size; + mousedev->packet.abs_event = 1; + break; + + case ABS_Y: + size = dev->absmax[ABS_Y] - dev->absmin[ABS_Y]; + if (size == 0) size = yres; + if (value > dev->absmax[ABS_Y]) value = dev->absmax[ABS_Y]; + if (value < dev->absmin[ABS_Y]) value = dev->absmin[ABS_Y]; + mousedev->packet.y = yres - ((value - dev->absmin[ABS_Y]) * yres) / size; + mousedev->packet.abs_event = 1; + break; + } +} + +static void mousedev_rel_event(struct mousedev *mousedev, unsigned int code, int value) +{ + switch (code) { + case REL_X: mousedev->packet.dx += value; break; + case REL_Y: mousedev->packet.dy -= value; break; + case REL_WHEEL: mousedev->packet.dz -= value; break; + } +} + +static void mousedev_key_event(struct mousedev *mousedev, unsigned int code, int value) +{ + int index; + + switch (code) { + case BTN_TOUCH: + case BTN_0: + case BTN_FORWARD: + case BTN_LEFT: index = 0; break; + case BTN_STYLUS: + case BTN_1: + case BTN_RIGHT: index = 1; break; + case BTN_2: + case BTN_STYLUS2: + case BTN_MIDDLE: index = 2; break; + case BTN_3: + case BTN_BACK: + case BTN_SIDE: index = 3; break; + case BTN_4: + case BTN_EXTRA: index = 4; break; + default: return; + } + + if (value) { + set_bit(index, &mousedev->packet.buttons); + set_bit(index, &mousedev_mix.packet.buttons); + } else { + clear_bit(index, &mousedev->packet.buttons); + clear_bit(index, &mousedev_mix.packet.buttons); + } +} + +static void mousedev_notify_readers(struct mousedev *mousedev, struct mousedev_hw_data *packet) +{ + struct mousedev_list *list; + struct mousedev_motion *p; + unsigned long flags; + + list_for_each_entry(list, &mousedev->list, node) { + spin_lock_irqsave(&list->packet_lock, flags); + + p = &list->packets[list->head]; + if (list->ready && p->buttons != packet->buttons) { + unsigned int new_head = (list->head + 1) % PACKET_QUEUE_LEN; + if (new_head != list->tail) { + p = &list->packets[list->head = new_head]; + memset(p, 0, sizeof(struct mousedev_motion)); + } + } + + if (packet->abs_event) { + p->dx += packet->x - list->pos_x; + p->dy += packet->y - list->pos_y; + list->pos_x = packet->x; + list->pos_y = packet->y; + } + + list->pos_x += packet->dx; + list->pos_x = list->pos_x < 0 ? 0 : (list->pos_x >= xres ? xres : list->pos_x); + list->pos_y += packet->dy; + list->pos_y = list->pos_y < 0 ? 0 : (list->pos_y >= yres ? yres : list->pos_y); + + p->dx += packet->dx; + p->dy += packet->dy; + p->dz += packet->dz; + p->buttons = mousedev->packet.buttons; + + list->ready = 1; + + spin_unlock_irqrestore(&list->packet_lock, flags); + kill_fasync(&list->fasync, SIGIO, POLL_IN); + } + + wake_up_interruptible(&mousedev->wait); +} + +static void mousedev_touchpad_touch(struct mousedev *mousedev, int value) +{ + if (!value) { + if (mousedev->touch && + time_before(jiffies, mousedev->touch + msecs_to_jiffies(tap_time))) { + /* + * Toggle left button to emulate tap. + * We rely on the fact that mousedev_mix always has 0 + * motion packet so we won't mess current position. + */ + set_bit(0, &mousedev->packet.buttons); + set_bit(0, &mousedev_mix.packet.buttons); + mousedev_notify_readers(mousedev, &mousedev_mix.packet); + mousedev_notify_readers(&mousedev_mix, &mousedev_mix.packet); + clear_bit(0, &mousedev->packet.buttons); + clear_bit(0, &mousedev_mix.packet.buttons); + } + mousedev->touch = mousedev->pkt_count = 0; + mousedev->frac_dx = 0; + mousedev->frac_dy = 0; + } + else + if (!mousedev->touch) + mousedev->touch = jiffies; +} + +static void mousedev_event(struct input_handle *handle, unsigned int type, unsigned int code, int value) +{ + struct mousedev *mousedev = handle->private; + + switch (type) { + case EV_ABS: + /* Ignore joysticks */ + if (test_bit(BTN_TRIGGER, handle->dev->keybit)) + return; + + if (test_bit(BTN_TOOL_FINGER, handle->dev->keybit)) + mousedev_touchpad_event(handle->dev, mousedev, code, value); + else + mousedev_abs_event(handle->dev, mousedev, code, value); + + break; + + case EV_REL: + mousedev_rel_event(mousedev, code, value); + break; + + case EV_KEY: + if (value != 2) { + if (code == BTN_TOUCH && test_bit(BTN_TOOL_FINGER, handle->dev->keybit)) + mousedev_touchpad_touch(mousedev, value); + else + mousedev_key_event(mousedev, code, value); + } + break; + + case EV_SYN: + if (code == SYN_REPORT) { + if (mousedev->touch) { + mousedev->pkt_count++; + /* Input system eats duplicate events, but we need all of them + * to do correct averaging so apply present one forward + */ + fx(0) = fx(1); + fy(0) = fy(1); + } + + mousedev_notify_readers(mousedev, &mousedev->packet); + mousedev_notify_readers(&mousedev_mix, &mousedev->packet); + + mousedev->packet.dx = mousedev->packet.dy = mousedev->packet.dz = 0; + mousedev->packet.abs_event = 0; + } + break; + } +} + +static int mousedev_fasync(int fd, struct file *file, int on) +{ + int retval; + struct mousedev_list *list = file->private_data; + retval = fasync_helper(fd, file, on, &list->fasync); + return retval < 0 ? retval : 0; +} + +static void mousedev_free(struct mousedev *mousedev) +{ + mousedev_table[mousedev->minor] = NULL; + kfree(mousedev); +} + +static int mixdev_release(void) +{ + struct input_handle *handle; + + list_for_each_entry(handle, &mousedev_handler.h_list, h_node) { + struct mousedev *mousedev = handle->private; + + if (!mousedev->open) { + if (mousedev->exist) + input_close_device(&mousedev->handle); + else + mousedev_free(mousedev); + } + } + + return 0; +} + +static int mousedev_release(struct inode * inode, struct file * file) +{ + struct mousedev_list *list = file->private_data; + + mousedev_fasync(-1, file, 0); + + list_del(&list->node); + + if (!--list->mousedev->open) { + if (list->mousedev->minor == MOUSEDEV_MIX) + return mixdev_release(); + + if (!mousedev_mix.open) { + if (list->mousedev->exist) + input_close_device(&list->mousedev->handle); + else + mousedev_free(list->mousedev); + } + } + + kfree(list); + return 0; +} + +static int mousedev_open(struct inode * inode, struct file * file) +{ + struct mousedev_list *list; + struct input_handle *handle; + struct mousedev *mousedev; + int i; + +#ifdef CONFIG_INPUT_MOUSEDEV_PSAUX + if (imajor(inode) == MISC_MAJOR) + i = MOUSEDEV_MIX; + else +#endif + i = iminor(inode) - MOUSEDEV_MINOR_BASE; + + if (i >= MOUSEDEV_MINORS || !mousedev_table[i]) + return -ENODEV; + + if (!(list = kmalloc(sizeof(struct mousedev_list), GFP_KERNEL))) + return -ENOMEM; + memset(list, 0, sizeof(struct mousedev_list)); + + spin_lock_init(&list->packet_lock); + list->pos_x = xres / 2; + list->pos_y = yres / 2; + list->mousedev = mousedev_table[i]; + list_add_tail(&list->node, &mousedev_table[i]->list); + file->private_data = list; + + if (!list->mousedev->open++) { + if (list->mousedev->minor == MOUSEDEV_MIX) { + list_for_each_entry(handle, &mousedev_handler.h_list, h_node) { + mousedev = handle->private; + if (!mousedev->open && mousedev->exist) + input_open_device(handle); + } + } else + if (!mousedev_mix.open && list->mousedev->exist) + input_open_device(&list->mousedev->handle); + } + + return 0; +} + +static inline int mousedev_limit_delta(int delta, int limit) +{ + return delta > limit ? limit : (delta < -limit ? -limit : delta); +} + +static void mousedev_packet(struct mousedev_list *list, signed char *ps2_data) +{ + struct mousedev_motion *p; + unsigned long flags; + + spin_lock_irqsave(&list->packet_lock, flags); + p = &list->packets[list->tail]; + + ps2_data[0] = 0x08 | ((p->dx < 0) << 4) | ((p->dy < 0) << 5) | (p->buttons & 0x07); + ps2_data[1] = mousedev_limit_delta(p->dx, 127); + ps2_data[2] = mousedev_limit_delta(p->dy, 127); + p->dx -= ps2_data[1]; + p->dy -= ps2_data[2]; + + switch (list->mode) { + case MOUSEDEV_EMUL_EXPS: + ps2_data[3] = mousedev_limit_delta(p->dz, 7); + p->dz -= ps2_data[3]; + ps2_data[3] = (ps2_data[3] & 0x0f) | ((p->buttons & 0x18) << 1); + list->bufsiz = 4; + break; + + case MOUSEDEV_EMUL_IMPS: + ps2_data[0] |= ((p->buttons & 0x10) >> 3) | ((p->buttons & 0x08) >> 1); + ps2_data[3] = mousedev_limit_delta(p->dz, 127); + p->dz -= ps2_data[3]; + list->bufsiz = 4; + break; + + case MOUSEDEV_EMUL_PS2: + default: + ps2_data[0] |= ((p->buttons & 0x10) >> 3) | ((p->buttons & 0x08) >> 1); + p->dz = 0; + list->bufsiz = 3; + break; + } + + if (!p->dx && !p->dy && !p->dz) { + if (list->tail == list->head) + list->ready = 0; + else + list->tail = (list->tail + 1) % PACKET_QUEUE_LEN; + } + + spin_unlock_irqrestore(&list->packet_lock, flags); +} + + +static ssize_t mousedev_write(struct file * file, const char __user * buffer, size_t count, loff_t *ppos) +{ + struct mousedev_list *list = file->private_data; + unsigned char c; + unsigned int i; + + for (i = 0; i < count; i++) { + + if (get_user(c, buffer + i)) + return -EFAULT; + + if (c == mousedev_imex_seq[list->imexseq]) { + if (++list->imexseq == MOUSEDEV_SEQ_LEN) { + list->imexseq = 0; + list->mode = MOUSEDEV_EMUL_EXPS; + } + } else list->imexseq = 0; + + if (c == mousedev_imps_seq[list->impsseq]) { + if (++list->impsseq == MOUSEDEV_SEQ_LEN) { + list->impsseq = 0; + list->mode = MOUSEDEV_EMUL_IMPS; + } + } else list->impsseq = 0; + + list->ps2[0] = 0xfa; + + switch (c) { + + case 0xeb: /* Poll */ + mousedev_packet(list, &list->ps2[1]); + list->bufsiz++; /* account for leading ACK */ + break; + + case 0xf2: /* Get ID */ + switch (list->mode) { + case MOUSEDEV_EMUL_PS2: list->ps2[1] = 0; break; + case MOUSEDEV_EMUL_IMPS: list->ps2[1] = 3; break; + case MOUSEDEV_EMUL_EXPS: list->ps2[1] = 4; break; + } + list->bufsiz = 2; + break; + + case 0xe9: /* Get info */ + list->ps2[1] = 0x60; list->ps2[2] = 3; list->ps2[3] = 200; + list->bufsiz = 4; + break; + + case 0xff: /* Reset */ + list->impsseq = list->imexseq = 0; + list->mode = MOUSEDEV_EMUL_PS2; + list->ps2[1] = 0xaa; list->ps2[2] = 0x00; + list->bufsiz = 3; + break; + + default: + list->bufsiz = 1; + break; + } + + list->buffer = list->bufsiz; + } + + kill_fasync(&list->fasync, SIGIO, POLL_IN); + + wake_up_interruptible(&list->mousedev->wait); + + return count; +} + +static ssize_t mousedev_read(struct file * file, char __user * buffer, size_t count, loff_t *ppos) +{ + struct mousedev_list *list = file->private_data; + int retval = 0; + + if (!list->ready && !list->buffer && (file->f_flags & O_NONBLOCK)) + return -EAGAIN; + + retval = wait_event_interruptible(list->mousedev->wait, + !list->mousedev->exist || list->ready || list->buffer); + + if (retval) + return retval; + + if (!list->mousedev->exist) + return -ENODEV; + + if (!list->buffer && list->ready) { + mousedev_packet(list, list->ps2); + list->buffer = list->bufsiz; + } + + if (count > list->buffer) + count = list->buffer; + + list->buffer -= count; + + if (copy_to_user(buffer, list->ps2 + list->bufsiz - list->buffer - count, count)) + return -EFAULT; + + return count; +} + +/* No kernel lock - fine */ +static unsigned int mousedev_poll(struct file *file, poll_table *wait) +{ + struct mousedev_list *list = file->private_data; + poll_wait(file, &list->mousedev->wait, wait); + return ((list->ready || list->buffer) ? (POLLIN | POLLRDNORM) : 0) | + (list->mousedev->exist ? 0 : (POLLHUP | POLLERR)); +} + +static struct file_operations mousedev_fops = { + .owner = THIS_MODULE, + .read = mousedev_read, + .write = mousedev_write, + .poll = mousedev_poll, + .open = mousedev_open, + .release = mousedev_release, + .fasync = mousedev_fasync, +}; + +static struct input_handle *mousedev_connect(struct input_handler *handler, struct input_dev *dev, struct input_device_id *id) +{ + struct mousedev *mousedev; + int minor = 0; + + for (minor = 0; minor < MOUSEDEV_MINORS && mousedev_table[minor]; minor++); + if (minor == MOUSEDEV_MINORS) { + printk(KERN_ERR "mousedev: no more free mousedev devices\n"); + return NULL; + } + + if (!(mousedev = kmalloc(sizeof(struct mousedev), GFP_KERNEL))) + return NULL; + memset(mousedev, 0, sizeof(struct mousedev)); + + INIT_LIST_HEAD(&mousedev->list); + init_waitqueue_head(&mousedev->wait); + + mousedev->minor = minor; + mousedev->exist = 1; + mousedev->handle.dev = dev; + mousedev->handle.name = mousedev->name; + mousedev->handle.handler = handler; + mousedev->handle.private = mousedev; + sprintf(mousedev->name, "mouse%d", minor); + + if (mousedev_mix.open) + input_open_device(&mousedev->handle); + + mousedev_table[minor] = mousedev; + + devfs_mk_cdev(MKDEV(INPUT_MAJOR, MOUSEDEV_MINOR_BASE + minor), + S_IFCHR|S_IRUGO|S_IWUSR, "input/mouse%d", minor); + class_simple_device_add(input_class, + MKDEV(INPUT_MAJOR, MOUSEDEV_MINOR_BASE + minor), + dev->dev, "mouse%d", minor); + + return &mousedev->handle; +} + +static void mousedev_disconnect(struct input_handle *handle) +{ + struct mousedev *mousedev = handle->private; + struct mousedev_list *list; + + class_simple_device_remove(MKDEV(INPUT_MAJOR, MOUSEDEV_MINOR_BASE + mousedev->minor)); + devfs_remove("input/mouse%d", mousedev->minor); + mousedev->exist = 0; + + if (mousedev->open) { + input_close_device(handle); + wake_up_interruptible(&mousedev->wait); + list_for_each_entry(list, &mousedev->list, node) + kill_fasync(&list->fasync, SIGIO, POLL_HUP); + } else { + if (mousedev_mix.open) + input_close_device(handle); + mousedev_free(mousedev); + } +} + +static struct input_device_id mousedev_ids[] = { + { + .flags = INPUT_DEVICE_ID_MATCH_EVBIT | INPUT_DEVICE_ID_MATCH_KEYBIT | INPUT_DEVICE_ID_MATCH_RELBIT, + .evbit = { BIT(EV_KEY) | BIT(EV_REL) }, + .keybit = { [LONG(BTN_LEFT)] = BIT(BTN_LEFT) }, + .relbit = { BIT(REL_X) | BIT(REL_Y) }, + }, /* A mouse like device, at least one button, two relative axes */ + { + .flags = INPUT_DEVICE_ID_MATCH_EVBIT | INPUT_DEVICE_ID_MATCH_RELBIT, + .evbit = { BIT(EV_KEY) | BIT(EV_REL) }, + .relbit = { BIT(REL_WHEEL) }, + }, /* A separate scrollwheel */ + { + .flags = INPUT_DEVICE_ID_MATCH_EVBIT | INPUT_DEVICE_ID_MATCH_KEYBIT | INPUT_DEVICE_ID_MATCH_ABSBIT, + .evbit = { BIT(EV_KEY) | BIT(EV_ABS) }, + .keybit = { [LONG(BTN_TOUCH)] = BIT(BTN_TOUCH) }, + .absbit = { BIT(ABS_X) | BIT(ABS_Y) }, + }, /* A tablet like device, at least touch detection, two absolute axes */ + { + .flags = INPUT_DEVICE_ID_MATCH_EVBIT | INPUT_DEVICE_ID_MATCH_KEYBIT | INPUT_DEVICE_ID_MATCH_ABSBIT, + .evbit = { BIT(EV_KEY) | BIT(EV_ABS) }, + .keybit = { [LONG(BTN_TOOL_FINGER)] = BIT(BTN_TOOL_FINGER) }, + .absbit = { BIT(ABS_X) | BIT(ABS_Y) | BIT(ABS_PRESSURE) | BIT(ABS_TOOL_WIDTH) }, + }, /* A touchpad */ + + { }, /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(input, mousedev_ids); + +static struct input_handler mousedev_handler = { + .event = mousedev_event, + .connect = mousedev_connect, + .disconnect = mousedev_disconnect, + .fops = &mousedev_fops, + .minor = MOUSEDEV_MINOR_BASE, + .name = "mousedev", + .id_table = mousedev_ids, +}; + +#ifdef CONFIG_INPUT_MOUSEDEV_PSAUX +static struct miscdevice psaux_mouse = { + PSMOUSE_MINOR, "psaux", &mousedev_fops +}; +static int psaux_registered; +#endif + +static int __init mousedev_init(void) +{ + input_register_handler(&mousedev_handler); + + memset(&mousedev_mix, 0, sizeof(struct mousedev)); + INIT_LIST_HEAD(&mousedev_mix.list); + init_waitqueue_head(&mousedev_mix.wait); + mousedev_table[MOUSEDEV_MIX] = &mousedev_mix; + mousedev_mix.exist = 1; + mousedev_mix.minor = MOUSEDEV_MIX; + + devfs_mk_cdev(MKDEV(INPUT_MAJOR, MOUSEDEV_MINOR_BASE + MOUSEDEV_MIX), + S_IFCHR|S_IRUGO|S_IWUSR, "input/mice"); + class_simple_device_add(input_class, MKDEV(INPUT_MAJOR, MOUSEDEV_MINOR_BASE + MOUSEDEV_MIX), + NULL, "mice"); + +#ifdef CONFIG_INPUT_MOUSEDEV_PSAUX + if (!(psaux_registered = !misc_register(&psaux_mouse))) + printk(KERN_WARNING "mice: could not misc_register the device\n"); +#endif + + printk(KERN_INFO "mice: PS/2 mouse device common for all mice\n"); + + return 0; +} + +static void __exit mousedev_exit(void) +{ +#ifdef CONFIG_INPUT_MOUSEDEV_PSAUX + if (psaux_registered) + misc_deregister(&psaux_mouse); +#endif + devfs_remove("input/mice"); + class_simple_device_remove(MKDEV(INPUT_MAJOR, MOUSEDEV_MINOR_BASE + MOUSEDEV_MIX)); + input_unregister_handler(&mousedev_handler); +} + +module_init(mousedev_init); +module_exit(mousedev_exit); diff --git a/drivers/input/power.c b/drivers/input/power.c new file mode 100644 index 000000000000..bfc5c63ebffe --- /dev/null +++ b/drivers/input/power.c @@ -0,0 +1,169 @@ +/* + * $Id: power.c,v 1.10 2001/09/25 09:17:15 vojtech Exp $ + * + * Copyright (c) 2001 "Crazy" James Simmons + * + * Input driver Power Management. + * + * Sponsored by Transvirtual Technology. + */ + +/* + * 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 <jsimmons@transvirtual.com>. + */ + +#include <linux/module.h> +#include <linux/config.h> +#include <linux/input.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/tty.h> +#include <linux/delay.h> +#include <linux/pm.h> + +static struct input_handler power_handler; + +/* + * Power management can't be done in a interrupt context. So we have to + * use keventd. + */ +static int suspend_button_pushed = 0; +static void suspend_button_task_handler(void *data) +{ + udelay(200); /* debounce */ + suspend_button_pushed = 0; +} + +static DECLARE_WORK(suspend_button_task, suspend_button_task_handler, NULL); + +static void power_event(struct input_handle *handle, unsigned int type, + unsigned int code, int down) +{ + struct input_dev *dev = handle->dev; + + printk("Entering power_event\n"); + + if (type == EV_PWR) { + switch (code) { + case KEY_SUSPEND: + printk("Powering down entire device\n"); + + if (!suspend_button_pushed) { + suspend_button_pushed = 1; + schedule_work(&suspend_button_task); + } + break; + case KEY_POWER: + /* Hum power down the machine. */ + break; + default: + return; + } + } + + if (type == EV_KEY) { + switch (code) { + case KEY_SUSPEND: + printk("Powering down input device\n"); + /* This is risky. See pm.h for details. */ + if (dev->state != PM_RESUME) + dev->state = PM_RESUME; + else + dev->state = PM_SUSPEND; + pm_send(dev->pm_dev, dev->state, dev); + break; + case KEY_POWER: + /* Turn the input device off completely ? */ + break; + default: + return; + } + } + return; +} + +static struct input_handle *power_connect(struct input_handler *handler, + struct input_dev *dev, + struct input_device_id *id) +{ + struct input_handle *handle; + + if (!(handle = kmalloc(sizeof(struct input_handle), GFP_KERNEL))) + return NULL; + memset(handle, 0, sizeof(struct input_handle)); + + handle->dev = dev; + handle->handler = handler; + + input_open_device(handle); + + printk(KERN_INFO "power.c: Adding power management to input layer\n"); + return handle; +} + +static void power_disconnect(struct input_handle *handle) +{ + input_close_device(handle); + kfree(handle); +} + +static struct input_device_id power_ids[] = { + { + .flags = INPUT_DEVICE_ID_MATCH_EVBIT | INPUT_DEVICE_ID_MATCH_KEYBIT, + .evbit = { BIT(EV_KEY) }, + .keybit = { [LONG(KEY_SUSPEND)] = BIT(KEY_SUSPEND) } + }, + { + .flags = INPUT_DEVICE_ID_MATCH_EVBIT | INPUT_DEVICE_ID_MATCH_KEYBIT, + .evbit = { BIT(EV_KEY) }, + .keybit = { [LONG(KEY_POWER)] = BIT(KEY_POWER) } + }, + { + .flags = INPUT_DEVICE_ID_MATCH_EVBIT, + .evbit = { BIT(EV_PWR) }, + }, + { }, /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(input, power_ids); + +static struct input_handler power_handler = { + .event = power_event, + .connect = power_connect, + .disconnect = power_disconnect, + .name = "power", + .id_table = power_ids, +}; + +static int __init power_init(void) +{ + input_register_handler(&power_handler); + return 0; +} + +static void __exit power_exit(void) +{ + input_unregister_handler(&power_handler); +} + +module_init(power_init); +module_exit(power_exit); + +MODULE_AUTHOR("James Simmons <jsimmons@transvirtual.com>"); +MODULE_DESCRIPTION("Input Power Management driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/serio/Kconfig b/drivers/input/serio/Kconfig new file mode 100644 index 000000000000..b3710733b36b --- /dev/null +++ b/drivers/input/serio/Kconfig @@ -0,0 +1,183 @@ +# +# Input core configuration +# +config SERIO + tristate "Serial I/O support" if EMBEDDED || !X86 + default y + ---help--- + Say Yes here if you have any input device that uses serial I/O to + communicate with the system. This includes the + * standard AT keyboard and PS/2 mouse * + as well as serial mice, Sun keyboards, some joysticks and 6dof + devices and more. + + If unsure, say Y. + + To compile this driver as a module, choose M here: the + module will be called serio. + +if SERIO + +config SERIO_I8042 + tristate "i8042 PC Keyboard controller" if EMBEDDED || !X86 + default y + depends on !PARISC && (!ARM || ARCH_SHARK || FOOTBRIDGE_HOST) && !M68K + ---help--- + i8042 is the chip over which the standard AT keyboard and PS/2 + mouse are connected to the computer. If you use these devices, + you'll need to say Y here. + + If unsure, say Y. + + To compile this driver as a module, choose M here: the + module will be called i8042. + +config SERIO_SERPORT + tristate "Serial port line discipline" + default y + ---help--- + Say Y here if you plan to use an input device (mouse, joystick, + tablet, 6dof) that communicates over the RS232 serial (COM) port. + + More information is available: <file:Documentation/input/input.txt> + + If unsure, say Y. + + To compile this driver as a module, choose M here: the + module will be called serport. + +config SERIO_CT82C710 + tristate "ct82c710 Aux port controller" + depends on X86 + ---help--- + Say Y here if you have a Texas Instruments TravelMate notebook + equipped with the ct82c710 chip and want to use a mouse connected + to the "QuickPort". + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called ct82c710. + +config SERIO_Q40KBD + tristate "Q40 keyboard controller" + depends on Q40 + +config SERIO_PARKBD + tristate "Parallel port keyboard adapter" + depends on PARPORT + ---help--- + Say Y here if you built a simple parallel port adapter to attach + an additional AT keyboard, XT keyboard or PS/2 mouse. + + More information is available: <file:Documentation/input/input.txt> + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called parkbd. + +config SERIO_RPCKBD + tristate "Acorn RiscPC keyboard controller" + depends on ARCH_ACORN || ARCH_CLPS7500 + default y + help + Say Y here if you have the Acorn RiscPC and want to use an AT + keyboard connected to its keyboard controller. + + To compile this driver as a module, choose M here: the + module will be called rpckbd. + +config SERIO_AMBAKMI + tristate "AMBA KMI keyboard controller" + depends on ARM_AMBA + +config SERIO_SA1111 + tristate "Intel SA1111 keyboard controller" + depends on SA1111 + +config SERIO_GSCPS2 + tristate "HP GSC PS/2 keyboard and PS/2 mouse controller" + depends on GSC + default y + help + This driver provides support for the PS/2 ports on PA-RISC machines + over which HP PS/2 keyboards and PS/2 mice may be connected. + If you use these devices, you'll need to say Y here. + + It's safe to enable this driver, so if unsure, say Y. + + To compile this driver as a module, choose M here: the + module will be called gscps2. + +config HP_SDC + tristate "HP System Device Controller i8042 Support" + depends on GSC && SERIO + default y + ---help--- + This option enables supports for the the "System Device + Controller", an i8042 carrying microcode to manage a + few miscellanous devices on some Hewlett Packard systems. + The SDC itself contains a 10ms resolution timer/clock capable + of delivering interrupts on a periodic and one-shot basis. + The SDC may also be connected to a battery-backed real-time + clock, a basic audio waveform generator, and an HP-HIL Master + Link Controller serving up to seven input devices. + + By itself this option is rather useless, but enabling it will + enable selection of drivers for the abovementioned devices. + It is, however, incompatible with the old, reliable HIL keyboard + driver, and the new HIL driver is experimental, so if you plan + to use a HIL keyboard as your primary keyboard, you may wish + to keep using that driver until the new HIL drivers have had + more testing. + +config HIL_MLC + tristate "HIL MLC Support (needed for HIL input devices)" + depends on HP_SDC + +config SERIO_PCIPS2 + tristate "PCI PS/2 keyboard and PS/2 mouse controller" + depends on PCI + help + Say Y here if you have a Mobility Docking station with PS/2 + keyboard and mice ports. + + To compile this driver as a module, choose M here: the + module will be called pcips2. + +config SERIO_MACEPS2 + tristate "SGI O2 MACE PS/2 controller" + depends on SGI_IP32 + help + Say Y here if you have SGI O2 workstation and want to use its + PS/2 ports. + + To compile this driver as a module, choose M here: the + module will be called maceps2. + +config SERIO_LIBPS2 + tristate "PS/2 driver library" if EMBEDDED + help + Say Y here if you are using a driver for device connected + to a PS/2 port, such as PS/2 mouse or standard AT keyboard. + + To compile this driver as a module, choose M here: the + module will be called libps2. + +config SERIO_RAW + tristate "Raw access to serio ports" + help + Say Y here if you want to have raw access to serio ports, such as + AUX ports on i8042 keyboard controller. Each serio port that is + bound to this driver will be accessible via a char device with + major 10 and dynamically allocated minor. The driver will try + allocating minor 1 (that historically corresponds to /dev/psaux) + first. To bind this driver to a serio port use sysfs interface: + + echo -n "serio_raw" > /sys/bus/serio/devices/serioX/driver + + To compile this driver as a module, choose M here: the + module will be called serio_raw. + +endif diff --git a/drivers/input/serio/Makefile b/drivers/input/serio/Makefile new file mode 100644 index 000000000000..678a8599f9ff --- /dev/null +++ b/drivers/input/serio/Makefile @@ -0,0 +1,23 @@ +# +# Makefile for the input core drivers. +# + +# Each configuration option enables a list of files. + +obj-$(CONFIG_SERIO) += serio.o +obj-$(CONFIG_SERIO_I8042) += i8042.o +obj-$(CONFIG_SERIO_PARKBD) += parkbd.o +obj-$(CONFIG_SERIO_SERPORT) += serport.o +obj-$(CONFIG_SERIO_CT82C710) += ct82c710.o +obj-$(CONFIG_SERIO_RPCKBD) += rpckbd.o +obj-$(CONFIG_SERIO_SA1111) += sa1111ps2.o +obj-$(CONFIG_SERIO_AMBAKMI) += ambakmi.o +obj-$(CONFIG_SERIO_Q40KBD) += q40kbd.o +obj-$(CONFIG_SERIO_98KBD) += 98kbd-io.o +obj-$(CONFIG_SERIO_GSCPS2) += gscps2.o +obj-$(CONFIG_HP_SDC) += hp_sdc.o +obj-$(CONFIG_HIL_MLC) += hp_sdc_mlc.o hil_mlc.o +obj-$(CONFIG_SERIO_PCIPS2) += pcips2.o +obj-$(CONFIG_SERIO_MACEPS2) += maceps2.o +obj-$(CONFIG_SERIO_LIBPS2) += libps2.o +obj-$(CONFIG_SERIO_RAW) += serio_raw.o diff --git a/drivers/input/serio/ambakmi.c b/drivers/input/serio/ambakmi.c new file mode 100644 index 000000000000..9b1ab5e7a98d --- /dev/null +++ b/drivers/input/serio/ambakmi.c @@ -0,0 +1,231 @@ +/* + * linux/drivers/input/serio/ambakmi.c + * + * Copyright (C) 2000-2003 Deep Blue Solutions Ltd. + * Copyright (C) 2002 Russell King. + * + * 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. + */ +#include <linux/module.h> +#include <linux/init.h> +#include <linux/serio.h> +#include <linux/errno.h> +#include <linux/interrupt.h> +#include <linux/ioport.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/err.h> + +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/hardware/amba.h> +#include <asm/hardware/amba_kmi.h> +#include <asm/hardware/clock.h> + +#define KMI_BASE (kmi->base) + +struct amba_kmi_port { + struct serio *io; + struct clk *clk; + void __iomem *base; + unsigned int irq; + unsigned int divisor; + unsigned int open; +}; + +static irqreturn_t amba_kmi_int(int irq, void *dev_id, struct pt_regs *regs) +{ + struct amba_kmi_port *kmi = dev_id; + unsigned int status = readb(KMIIR); + int handled = IRQ_NONE; + + while (status & KMIIR_RXINTR) { + serio_interrupt(kmi->io, readb(KMIDATA), 0, regs); + status = readb(KMIIR); + handled = IRQ_HANDLED; + } + + return handled; +} + +static int amba_kmi_write(struct serio *io, unsigned char val) +{ + struct amba_kmi_port *kmi = io->port_data; + unsigned int timeleft = 10000; /* timeout in 100ms */ + + while ((readb(KMISTAT) & KMISTAT_TXEMPTY) == 0 && timeleft--) + udelay(10); + + if (timeleft) + writeb(val, KMIDATA); + + return timeleft ? 0 : SERIO_TIMEOUT; +} + +static int amba_kmi_open(struct serio *io) +{ + struct amba_kmi_port *kmi = io->port_data; + unsigned int divisor; + int ret; + + ret = clk_use(kmi->clk); + if (ret) + goto out; + + ret = clk_enable(kmi->clk); + if (ret) + goto clk_unuse; + + divisor = clk_get_rate(kmi->clk) / 8000000 - 1; + writeb(divisor, KMICLKDIV); + writeb(KMICR_EN, KMICR); + + ret = request_irq(kmi->irq, amba_kmi_int, 0, "kmi-pl050", kmi); + if (ret) { + printk(KERN_ERR "kmi: failed to claim IRQ%d\n", kmi->irq); + writeb(0, KMICR); + goto clk_disable; + } + + writeb(KMICR_EN | KMICR_RXINTREN, KMICR); + + return 0; + + clk_disable: + clk_disable(kmi->clk); + clk_unuse: + clk_unuse(kmi->clk); + out: + return ret; +} + +static void amba_kmi_close(struct serio *io) +{ + struct amba_kmi_port *kmi = io->port_data; + + writeb(0, KMICR); + + free_irq(kmi->irq, kmi); + clk_disable(kmi->clk); + clk_unuse(kmi->clk); +} + +static int amba_kmi_probe(struct amba_device *dev, void *id) +{ + struct amba_kmi_port *kmi; + struct serio *io; + int ret; + + ret = amba_request_regions(dev, NULL); + if (ret) + return ret; + + kmi = kmalloc(sizeof(struct amba_kmi_port), GFP_KERNEL); + io = kmalloc(sizeof(struct serio), GFP_KERNEL); + if (!kmi || !io) { + ret = -ENOMEM; + goto out; + } + + memset(kmi, 0, sizeof(struct amba_kmi_port)); + memset(io, 0, sizeof(struct serio)); + + io->id.type = SERIO_8042; + io->write = amba_kmi_write; + io->open = amba_kmi_open; + io->close = amba_kmi_close; + strlcpy(io->name, dev->dev.bus_id, sizeof(io->name)); + strlcpy(io->phys, dev->dev.bus_id, sizeof(io->phys)); + io->port_data = kmi; + io->dev.parent = &dev->dev; + + kmi->io = io; + kmi->base = ioremap(dev->res.start, KMI_SIZE); + if (!kmi->base) { + ret = -ENOMEM; + goto out; + } + + kmi->clk = clk_get(&dev->dev, "KMIREFCLK"); + if (IS_ERR(kmi->clk)) { + ret = PTR_ERR(kmi->clk); + goto unmap; + } + + kmi->irq = dev->irq[0]; + amba_set_drvdata(dev, kmi); + + serio_register_port(kmi->io); + return 0; + + unmap: + iounmap(kmi->base); + out: + kfree(kmi); + kfree(io); + amba_release_regions(dev); + return ret; +} + +static int amba_kmi_remove(struct amba_device *dev) +{ + struct amba_kmi_port *kmi = amba_get_drvdata(dev); + + amba_set_drvdata(dev, NULL); + + serio_unregister_port(kmi->io); + clk_put(kmi->clk); + iounmap(kmi->base); + kfree(kmi); + amba_release_regions(dev); + return 0; +} + +static int amba_kmi_resume(struct amba_device *dev) +{ + struct amba_kmi_port *kmi = amba_get_drvdata(dev); + + /* kick the serio layer to rescan this port */ + serio_reconnect(kmi->io); + + return 0; +} + +static struct amba_id amba_kmi_idtable[] = { + { + .id = 0x00041050, + .mask = 0x000fffff, + }, + { 0, 0 } +}; + +static struct amba_driver ambakmi_driver = { + .drv = { + .name = "kmi-pl050", + }, + .id_table = amba_kmi_idtable, + .probe = amba_kmi_probe, + .remove = amba_kmi_remove, + .resume = amba_kmi_resume, +}; + +static int __init amba_kmi_init(void) +{ + return amba_driver_register(&ambakmi_driver); +} + +static void __exit amba_kmi_exit(void) +{ + amba_driver_unregister(&ambakmi_driver); +} + +module_init(amba_kmi_init); +module_exit(amba_kmi_exit); + +MODULE_AUTHOR("Russell King <rmk@arm.linux.org.uk>"); +MODULE_DESCRIPTION("AMBA KMI controller driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/serio/ct82c710.c b/drivers/input/serio/ct82c710.c new file mode 100644 index 000000000000..dd0f5bd90241 --- /dev/null +++ b/drivers/input/serio/ct82c710.c @@ -0,0 +1,225 @@ +/* + * $Id: ct82c710.c,v 1.11 2001/09/25 10:12:07 vojtech Exp $ + * + * Copyright (c) 1999-2001 Vojtech Pavlik + */ + +/* + * 82C710 C&T mouse port chip driver for Linux + */ + +/* + * 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 either by + * e-mail - mail your message to <vojtech@ucw.cz>, or by paper mail: + * Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic + */ + +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/ioport.h> +#include <linux/config.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/serio.h> +#include <linux/errno.h> +#include <linux/err.h> + +#include <asm/io.h> + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION("82C710 C&T mouse port chip driver"); +MODULE_LICENSE("GPL"); + +/* + * ct82c710 interface + */ + +#define CT82C710_DEV_IDLE 0x01 /* Device Idle */ +#define CT82C710_RX_FULL 0x02 /* Device Char received */ +#define CT82C710_TX_IDLE 0x04 /* Device XMIT Idle */ +#define CT82C710_RESET 0x08 /* Device Reset */ +#define CT82C710_INTS_ON 0x10 /* Device Interrupt On */ +#define CT82C710_ERROR_FLAG 0x20 /* Device Error */ +#define CT82C710_CLEAR 0x40 /* Device Clear */ +#define CT82C710_ENABLE 0x80 /* Device Enable */ + +#define CT82C710_IRQ 12 + +#define CT82C710_DATA ct82c710_iores.start +#define CT82C710_STATUS (ct82c710_iores.start + 1) + +static struct serio *ct82c710_port; +static struct platform_device *ct82c710_device; +static struct resource ct82c710_iores; + +/* + * Interrupt handler for the 82C710 mouse port. A character + * is waiting in the 82C710. + */ + +static irqreturn_t ct82c710_interrupt(int cpl, void *dev_id, struct pt_regs * regs) +{ + return serio_interrupt(ct82c710_port, inb(CT82C710_DATA), 0, regs); +} + +/* + * Wait for device to send output char and flush any input char. + */ + +static int ct82c170_wait(void) +{ + int timeout = 60000; + + while ((inb(CT82C710_STATUS) & (CT82C710_RX_FULL | CT82C710_TX_IDLE | CT82C710_DEV_IDLE)) + != (CT82C710_DEV_IDLE | CT82C710_TX_IDLE) && timeout) { + + if (inb_p(CT82C710_STATUS) & CT82C710_RX_FULL) inb_p(CT82C710_DATA); + + udelay(1); + timeout--; + } + + return !timeout; +} + +static void ct82c710_close(struct serio *serio) +{ + if (ct82c170_wait()) + printk(KERN_WARNING "ct82c710.c: Device busy in close()\n"); + + outb_p(inb_p(CT82C710_STATUS) & ~(CT82C710_ENABLE | CT82C710_INTS_ON), CT82C710_STATUS); + + if (ct82c170_wait()) + printk(KERN_WARNING "ct82c710.c: Device busy in close()\n"); + + free_irq(CT82C710_IRQ, NULL); +} + +static int ct82c710_open(struct serio *serio) +{ + unsigned char status; + + if (request_irq(CT82C710_IRQ, ct82c710_interrupt, 0, "ct82c710", NULL)) + return -1; + + status = inb_p(CT82C710_STATUS); + + status |= (CT82C710_ENABLE | CT82C710_RESET); + outb_p(status, CT82C710_STATUS); + + status &= ~(CT82C710_RESET); + outb_p(status, CT82C710_STATUS); + + status |= CT82C710_INTS_ON; + outb_p(status, CT82C710_STATUS); /* Enable interrupts */ + + while (ct82c170_wait()) { + printk(KERN_ERR "ct82c710: Device busy in open()\n"); + status &= ~(CT82C710_ENABLE | CT82C710_INTS_ON); + outb_p(status, CT82C710_STATUS); + free_irq(CT82C710_IRQ, NULL); + return -1; + } + + return 0; +} + +/* + * Write to the 82C710 mouse device. + */ + +static int ct82c710_write(struct serio *port, unsigned char c) +{ + if (ct82c170_wait()) return -1; + outb_p(c, CT82C710_DATA); + return 0; +} + +/* + * See if we can find a 82C710 device. Read mouse address. + */ + +static int __init ct82c710_probe(void) +{ + outb_p(0x55, 0x2fa); /* Any value except 9, ff or 36 */ + outb_p(0xaa, 0x3fa); /* Inverse of 55 */ + outb_p(0x36, 0x3fa); /* Address the chip */ + outb_p(0xe4, 0x3fa); /* 390/4; 390 = config address */ + outb_p(0x1b, 0x2fa); /* Inverse of e4 */ + outb_p(0x0f, 0x390); /* Write index */ + if (inb_p(0x391) != 0xe4) /* Config address found? */ + return -1; /* No: no 82C710 here */ + + outb_p(0x0d, 0x390); /* Write index */ + ct82c710_iores.start = inb_p(0x391) << 2; /* Get mouse I/O address */ + ct82c710_iores.end = ct82c710_iores.start + 1; + ct82c710_iores.flags = IORESOURCE_IO; + outb_p(0x0f, 0x390); + outb_p(0x0f, 0x391); /* Close config mode */ + + return 0; +} + +static struct serio * __init ct82c710_allocate_port(void) +{ + struct serio *serio; + + serio = kmalloc(sizeof(struct serio), GFP_KERNEL); + if (serio) { + memset(serio, 0, sizeof(struct serio)); + serio->id.type = SERIO_8042; + serio->open = ct82c710_open; + serio->close = ct82c710_close; + serio->write = ct82c710_write; + serio->dev.parent = &ct82c710_device->dev; + strlcpy(serio->name, "C&T 82c710 mouse port", sizeof(serio->name)); + snprintf(serio->phys, sizeof(serio->phys), "isa%04lx/serio0", CT82C710_DATA); + } + + return serio; +} + +static int __init ct82c710_init(void) +{ + if (ct82c710_probe()) + return -ENODEV; + + ct82c710_device = platform_device_register_simple("ct82c710", -1, &ct82c710_iores, 1); + if (IS_ERR(ct82c710_device)) + return PTR_ERR(ct82c710_device); + + if (!(ct82c710_port = ct82c710_allocate_port())) { + platform_device_unregister(ct82c710_device); + return -ENOMEM; + } + + serio_register_port(ct82c710_port); + + printk(KERN_INFO "serio: C&T 82c710 mouse port at %#lx irq %d\n", + CT82C710_DATA, CT82C710_IRQ); + + return 0; +} + +static void __exit ct82c710_exit(void) +{ + serio_unregister_port(ct82c710_port); + platform_device_unregister(ct82c710_device); +} + +module_init(ct82c710_init); +module_exit(ct82c710_exit); diff --git a/drivers/input/serio/gscps2.c b/drivers/input/serio/gscps2.c new file mode 100644 index 000000000000..897e4c12b642 --- /dev/null +++ b/drivers/input/serio/gscps2.c @@ -0,0 +1,467 @@ +/* + * drivers/input/serio/gscps2.c + * + * Copyright (c) 2004 Helge Deller <deller@gmx.de> + * Copyright (c) 2002 Laurent Canet <canetl@esiee.fr> + * Copyright (c) 2002 Thibaut Varene <varenet@parisc-linux.org> + * + * Pieces of code based on linux-2.4's hp_mouse.c & hp_keyb.c + * Copyright (c) 1999 Alex deVries <alex@onefishtwo.ca> + * Copyright (c) 1999-2000 Philipp Rumpf <prumpf@tux.org> + * Copyright (c) 2000 Xavier Debacker <debackex@esiee.fr> + * Copyright (c) 2000-2001 Thomas Marteau <marteaut@esiee.fr> + * + * HP GSC PS/2 port driver, found in PA/RISC Workstations + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + * + * TODO: + * - Dino testing (did HP ever shipped a machine on which this port + * was usable/enabled ?) + */ + +#include <linux/config.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/serio.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/spinlock.h> +#include <linux/delay.h> +#include <linux/ioport.h> +#include <linux/pci_ids.h> + +#include <asm/irq.h> +#include <asm/io.h> +#include <asm/parisc-device.h> + +MODULE_AUTHOR("Laurent Canet <canetl@esiee.fr>, Thibaut Varene <varenet@parisc-linux.org>, Helge Deller <deller@gmx.de>"); +MODULE_DESCRIPTION("HP GSC PS2 port driver"); +MODULE_LICENSE("GPL"); +MODULE_DEVICE_TABLE(parisc, gscps2_device_tbl); + +#define PFX "gscps2.c: " + +/* + * Driver constants + */ + +/* various constants */ +#define ENABLE 1 +#define DISABLE 0 + +#define GSC_DINO_OFFSET 0x0800 /* offset for DINO controller versus LASI one */ + +/* PS/2 IO port offsets */ +#define GSC_ID 0x00 /* device ID offset (see: GSC_ID_XXX) */ +#define GSC_RESET 0x00 /* reset port offset */ +#define GSC_RCVDATA 0x04 /* receive port offset */ +#define GSC_XMTDATA 0x04 /* transmit port offset */ +#define GSC_CONTROL 0x08 /* see: Control register bits */ +#define GSC_STATUS 0x0C /* see: Status register bits */ + +/* Control register bits */ +#define GSC_CTRL_ENBL 0x01 /* enable interface */ +#define GSC_CTRL_LPBXR 0x02 /* loopback operation */ +#define GSC_CTRL_DIAG 0x20 /* directly control clock/data line */ +#define GSC_CTRL_DATDIR 0x40 /* data line direct control */ +#define GSC_CTRL_CLKDIR 0x80 /* clock line direct control */ + +/* Status register bits */ +#define GSC_STAT_RBNE 0x01 /* Receive Buffer Not Empty */ +#define GSC_STAT_TBNE 0x02 /* Transmit Buffer Not Empty */ +#define GSC_STAT_TERR 0x04 /* Timeout Error */ +#define GSC_STAT_PERR 0x08 /* Parity Error */ +#define GSC_STAT_CMPINTR 0x10 /* Composite Interrupt = irq on any port */ +#define GSC_STAT_DATSHD 0x40 /* Data Line Shadow */ +#define GSC_STAT_CLKSHD 0x80 /* Clock Line Shadow */ + +/* IDs returned by GSC_ID port register */ +#define GSC_ID_KEYBOARD 0 /* device ID values */ +#define GSC_ID_MOUSE 1 + + +static irqreturn_t gscps2_interrupt(int irq, void *dev, struct pt_regs *regs); + +#define BUFFER_SIZE 0x0f + +/* GSC PS/2 port device struct */ +struct gscps2port { + struct list_head node; + struct parisc_device *padev; + struct serio *port; + spinlock_t lock; + char *addr; + u8 act, append; /* position in buffer[] */ + struct { + u8 data; + u8 str; + } buffer[BUFFER_SIZE+1]; + int id; +}; + +/* + * Various HW level routines + */ + +#define gscps2_readb_input(x) readb((x)+GSC_RCVDATA) +#define gscps2_readb_control(x) readb((x)+GSC_CONTROL) +#define gscps2_readb_status(x) readb((x)+GSC_STATUS) +#define gscps2_writeb_control(x, y) writeb((x), (y)+GSC_CONTROL) + + +/* + * wait_TBE() - wait for Transmit Buffer Empty + */ + +static int wait_TBE(char *addr) +{ + int timeout = 25000; /* device is expected to react within 250 msec */ + while (gscps2_readb_status(addr) & GSC_STAT_TBNE) { + if (!--timeout) + return 0; /* This should not happen */ + udelay(10); + } + return 1; +} + + +/* + * gscps2_flush() - flush the receive buffer + */ + +static void gscps2_flush(struct gscps2port *ps2port) +{ + while (gscps2_readb_status(ps2port->addr) & GSC_STAT_RBNE) + gscps2_readb_input(ps2port->addr); + ps2port->act = ps2port->append = 0; +} + +/* + * gscps2_writeb_output() - write a byte to the port + * + * returns 1 on sucess, 0 on error + */ + +static inline int gscps2_writeb_output(struct gscps2port *ps2port, u8 data) +{ + unsigned long flags; + char *addr = ps2port->addr; + + if (!wait_TBE(addr)) { + printk(KERN_DEBUG PFX "timeout - could not write byte %#x\n", data); + return 0; + } + + while (gscps2_readb_status(ps2port->addr) & GSC_STAT_RBNE) + /* wait */; + + spin_lock_irqsave(&ps2port->lock, flags); + writeb(data, addr+GSC_XMTDATA); + spin_unlock_irqrestore(&ps2port->lock, flags); + + /* this is ugly, but due to timing of the port it seems to be necessary. */ + mdelay(6); + + /* make sure any received data is returned as fast as possible */ + /* this is important e.g. when we set the LEDs on the keyboard */ + gscps2_interrupt(0, NULL, NULL); + + return 1; +} + + +/* + * gscps2_enable() - enables or disables the port + */ + +static void gscps2_enable(struct gscps2port *ps2port, int enable) +{ + unsigned long flags; + u8 data; + + /* now enable/disable the port */ + spin_lock_irqsave(&ps2port->lock, flags); + gscps2_flush(ps2port); + data = gscps2_readb_control(ps2port->addr); + if (enable) + data |= GSC_CTRL_ENBL; + else + data &= ~GSC_CTRL_ENBL; + gscps2_writeb_control(data, ps2port->addr); + spin_unlock_irqrestore(&ps2port->lock, flags); + wait_TBE(ps2port->addr); + gscps2_flush(ps2port); +} + +/* + * gscps2_reset() - resets the PS/2 port + */ + +static void gscps2_reset(struct gscps2port *ps2port) +{ + char *addr = ps2port->addr; + unsigned long flags; + + /* reset the interface */ + spin_lock_irqsave(&ps2port->lock, flags); + gscps2_flush(ps2port); + writeb(0xff, addr+GSC_RESET); + gscps2_flush(ps2port); + spin_unlock_irqrestore(&ps2port->lock, flags); + + /* enable it */ + gscps2_enable(ps2port, ENABLE); +} + +static LIST_HEAD(ps2port_list); + +/** + * gscps2_interrupt() - Interruption service routine + * + * This function reads received PS/2 bytes and processes them on + * all interfaces. + * The problematic part here is, that the keyboard and mouse PS/2 port + * share the same interrupt and it's not possible to send data if any + * one of them holds input data. To solve this problem we try to receive + * the data as fast as possible and handle the reporting to the upper layer + * later. + */ + +static irqreturn_t gscps2_interrupt(int irq, void *dev, struct pt_regs *regs) +{ + struct gscps2port *ps2port; + + list_for_each_entry(ps2port, &ps2port_list, node) { + + unsigned long flags; + spin_lock_irqsave(&ps2port->lock, flags); + + while ( (ps2port->buffer[ps2port->append].str = + gscps2_readb_status(ps2port->addr)) & GSC_STAT_RBNE ) { + ps2port->buffer[ps2port->append].data = + gscps2_readb_input(ps2port->addr); + ps2port->append = ((ps2port->append+1) & BUFFER_SIZE); + } + + spin_unlock_irqrestore(&ps2port->lock, flags); + + } /* list_for_each_entry */ + + /* all data was read from the ports - now report the data to upper layer */ + + list_for_each_entry(ps2port, &ps2port_list, node) { + + while (ps2port->act != ps2port->append) { + + unsigned int rxflags; + u8 data, status; + + /* Did new data arrived while we read existing data ? + If yes, exit now and let the new irq handler start over again */ + if (gscps2_readb_status(ps2port->addr) & GSC_STAT_CMPINTR) + return IRQ_HANDLED; + + status = ps2port->buffer[ps2port->act].str; + data = ps2port->buffer[ps2port->act].data; + + ps2port->act = ((ps2port->act+1) & BUFFER_SIZE); + rxflags = ((status & GSC_STAT_TERR) ? SERIO_TIMEOUT : 0 ) | + ((status & GSC_STAT_PERR) ? SERIO_PARITY : 0 ); + + serio_interrupt(ps2port->port, data, rxflags, regs); + + } /* while() */ + + } /* list_for_each_entry */ + + return IRQ_HANDLED; +} + + +/* + * gscps2_write() - send a byte out through the aux interface. + */ + +static int gscps2_write(struct serio *port, unsigned char data) +{ + struct gscps2port *ps2port = port->port_data; + + if (!gscps2_writeb_output(ps2port, data)) { + printk(KERN_DEBUG PFX "sending byte %#x failed.\n", data); + return -1; + } + return 0; +} + +/* + * gscps2_open() is called when a port is opened by the higher layer. + * It resets and enables the port. + */ + +static int gscps2_open(struct serio *port) +{ + struct gscps2port *ps2port = port->port_data; + + gscps2_reset(ps2port); + + gscps2_interrupt(0, NULL, NULL); + + return 0; +} + +/* + * gscps2_close() disables the port + */ + +static void gscps2_close(struct serio *port) +{ + struct gscps2port *ps2port = port->port_data; + gscps2_enable(ps2port, DISABLE); +} + +/** + * gscps2_probe() - Probes PS2 devices + * @return: success/error report + */ + +static int __init gscps2_probe(struct parisc_device *dev) +{ + struct gscps2port *ps2port; + struct serio *serio; + unsigned long hpa = dev->hpa; + int ret; + + if (!dev->irq) + return -ENODEV; + + /* Offset for DINO PS/2. Works with LASI even */ + if (dev->id.sversion == 0x96) + hpa += GSC_DINO_OFFSET; + + ps2port = kmalloc(sizeof(struct gscps2port), GFP_KERNEL); + serio = kmalloc(sizeof(struct serio), GFP_KERNEL); + if (!ps2port || !serio) { + ret = -ENOMEM; + goto fail_nomem; + } + + dev_set_drvdata(&dev->dev, ps2port); + + memset(ps2port, 0, sizeof(struct gscps2port)); + memset(serio, 0, sizeof(struct serio)); + ps2port->port = serio; + ps2port->padev = dev; + ps2port->addr = ioremap(hpa, GSC_STATUS + 4); + spin_lock_init(&ps2port->lock); + + gscps2_reset(ps2port); + ps2port->id = readb(ps2port->addr + GSC_ID) & 0x0f; + + snprintf(serio->name, sizeof(serio->name), "GSC PS/2 %s", + (ps2port->id == GSC_ID_KEYBOARD) ? "keyboard" : "mouse"); + strlcpy(serio->phys, dev->dev.bus_id, sizeof(serio->phys)); + serio->id.type = SERIO_8042; + serio->write = gscps2_write; + serio->open = gscps2_open; + serio->close = gscps2_close; + serio->port_data = ps2port; + serio->dev.parent = &dev->dev; + + list_add_tail(&ps2port->node, &ps2port_list); + + ret = -EBUSY; + if (request_irq(dev->irq, gscps2_interrupt, SA_SHIRQ, ps2port->port->name, ps2port)) + goto fail_miserably; + + if (ps2port->id != GSC_ID_KEYBOARD && ps2port->id != GSC_ID_MOUSE) { + printk(KERN_WARNING PFX "Unsupported PS/2 port at 0x%08lx (id=%d) ignored\n", + hpa, ps2port->id); + ret = -ENODEV; + goto fail; + } + +#if 0 + if (!request_mem_region(hpa, GSC_STATUS + 4, ps2port->port.name)) + goto fail; +#endif + + printk(KERN_INFO "serio: %s port at 0x%p irq %d @ %s\n", + ps2port->port->name, + ps2port->addr, + ps2port->padev->irq, + ps2port->port->phys); + + serio_register_port(ps2port->port); + + return 0; + +fail: + free_irq(dev->irq, ps2port); + +fail_miserably: + list_del(&ps2port->node); + iounmap(ps2port->addr); + release_mem_region(dev->hpa, GSC_STATUS + 4); + +fail_nomem: + kfree(ps2port); + kfree(serio); + return ret; +} + +/** + * gscps2_remove() - Removes PS2 devices + * @return: success/error report + */ + +static int __devexit gscps2_remove(struct parisc_device *dev) +{ + struct gscps2port *ps2port = dev_get_drvdata(&dev->dev); + + serio_unregister_port(ps2port->port); + free_irq(dev->irq, ps2port); + gscps2_flush(ps2port); + list_del(&ps2port->node); + iounmap(ps2port->addr); +#if 0 + release_mem_region(dev->hpa, GSC_STATUS + 4); +#endif + dev_set_drvdata(&dev->dev, NULL); + kfree(ps2port); + return 0; +} + + +static struct parisc_device_id gscps2_device_tbl[] = { + { HPHW_FIO, HVERSION_REV_ANY_ID, HVERSION_ANY_ID, 0x00084 }, /* LASI PS/2 */ +#ifdef DINO_TESTED + { HPHW_FIO, HVERSION_REV_ANY_ID, HVERSION_ANY_ID, 0x00096 }, /* DINO PS/2 */ +#endif + { 0, } /* 0 terminated list */ +}; + +static struct parisc_driver parisc_ps2_driver = { + .name = "GSC PS2", + .id_table = gscps2_device_tbl, + .probe = gscps2_probe, + .remove = gscps2_remove, +}; + +static int __init gscps2_init(void) +{ + register_parisc_driver(&parisc_ps2_driver); + return 0; +} + +static void __exit gscps2_exit(void) +{ + unregister_parisc_driver(&parisc_ps2_driver); +} + + +module_init(gscps2_init); +module_exit(gscps2_exit); + diff --git a/drivers/input/serio/hil_mlc.c b/drivers/input/serio/hil_mlc.c new file mode 100644 index 000000000000..c243cb6fdfc4 --- /dev/null +++ b/drivers/input/serio/hil_mlc.c @@ -0,0 +1,949 @@ +/* + * HIL MLC state machine and serio interface driver + * + * Copyright (c) 2001 Brian S. Julin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * Alternatively, this software may be distributed under the terms of the + * GNU General Public License ("GPL"). + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * + * References: + * HP-HIL Technical Reference Manual. Hewlett Packard Product No. 45918A + * + * + * Driver theory of operation: + * + * Some access methods and an ISR is defined by the sub-driver + * (e.g. hp_sdc_mlc.c). These methods are expected to provide a + * few bits of logic in addition to raw access to the HIL MLC, + * specifically, the ISR, which is entirely registered by the + * sub-driver and invoked directly, must check for record + * termination or packet match, at which point a semaphore must + * be cleared and then the hil_mlcs_tasklet must be scheduled. + * + * The hil_mlcs_tasklet processes the state machine for all MLCs + * each time it runs, checking each MLC's progress at the current + * node in the state machine, and moving the MLC to subsequent nodes + * in the state machine when appropriate. It will reschedule + * itself if output is pending. (This rescheduling should be replaced + * at some point with a sub-driver-specific mechanism.) + * + * A timer task prods the tasklet once per second to prevent + * hangups when attached devices do not return expected data + * and to initiate probes of the loop for new devices. + */ + +#include <linux/hil_mlc.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/timer.h> +#include <linux/sched.h> +#include <linux/list.h> + +MODULE_AUTHOR("Brian S. Julin <bri@calyx.com>"); +MODULE_DESCRIPTION("HIL MLC serio"); +MODULE_LICENSE("Dual BSD/GPL"); + +EXPORT_SYMBOL(hil_mlc_register); +EXPORT_SYMBOL(hil_mlc_unregister); + +#define PREFIX "HIL MLC: " + +static LIST_HEAD(hil_mlcs); +static DEFINE_RWLOCK(hil_mlcs_lock); +static struct timer_list hil_mlcs_kicker; +static int hil_mlcs_probe; + +static void hil_mlcs_process(unsigned long unused); +DECLARE_TASKLET_DISABLED(hil_mlcs_tasklet, hil_mlcs_process, 0); + + +/* #define HIL_MLC_DEBUG */ + +/********************** Device info/instance management **********************/ + +static void hil_mlc_clear_di_map (hil_mlc *mlc, int val) { + int j; + for (j = val; j < 7 ; j++) { + mlc->di_map[j] = -1; + } +} + +static void hil_mlc_clear_di_scratch (hil_mlc *mlc) { + memset(&(mlc->di_scratch), 0, sizeof(mlc->di_scratch)); +} + +static void hil_mlc_copy_di_scratch (hil_mlc *mlc, int idx) { + memcpy(&(mlc->di[idx]), &(mlc->di_scratch), sizeof(mlc->di_scratch)); +} + +static int hil_mlc_match_di_scratch (hil_mlc *mlc) { + int idx; + + for (idx = 0; idx < HIL_MLC_DEVMEM; idx++) { + int j, found; + + /* In-use slots are not eligible. */ + found = 0; + for (j = 0; j < 7 ; j++) { + if (mlc->di_map[j] == idx) found++; + } + if (found) continue; + if (!memcmp(mlc->di + idx, + &(mlc->di_scratch), + sizeof(mlc->di_scratch))) break; + } + return((idx >= HIL_MLC_DEVMEM) ? -1 : idx); +} + +static int hil_mlc_find_free_di(hil_mlc *mlc) { + int idx; + /* TODO: Pick all-zero slots first, failing that, + * randomize the slot picked among those eligible. + */ + for (idx = 0; idx < HIL_MLC_DEVMEM; idx++) { + int j, found; + found = 0; + for (j = 0; j < 7 ; j++) { + if (mlc->di_map[j] == idx) found++; + } + if (!found) break; + } + return(idx); /* Note: It is guaranteed at least one above will match */ +} + +static inline void hil_mlc_clean_serio_map(hil_mlc *mlc) { + int idx; + for (idx = 0; idx < HIL_MLC_DEVMEM; idx++) { + int j, found; + found = 0; + for (j = 0; j < 7 ; j++) { + if (mlc->di_map[j] == idx) found++; + } + if (!found) mlc->serio_map[idx].di_revmap = -1; + } +} + +static void hil_mlc_send_polls(hil_mlc *mlc) { + int did, i, cnt; + struct serio *serio; + struct serio_driver *drv; + + i = cnt = 0; + did = (mlc->ipacket[0] & HIL_PKT_ADDR_MASK) >> 8; + serio = did ? mlc->serio[mlc->di_map[did - 1]] : NULL; + drv = (serio != NULL) ? serio->drv : NULL; + + while (mlc->icount < 15 - i) { + hil_packet p; + p = mlc->ipacket[i]; + if (did != (p & HIL_PKT_ADDR_MASK) >> 8) { + if (drv == NULL || drv->interrupt == NULL) goto skip; + + drv->interrupt(serio, 0, 0, NULL); + drv->interrupt(serio, HIL_ERR_INT >> 16, 0, NULL); + drv->interrupt(serio, HIL_PKT_CMD >> 8, 0, NULL); + drv->interrupt(serio, HIL_CMD_POL + cnt, 0, NULL); + skip: + did = (p & HIL_PKT_ADDR_MASK) >> 8; + serio = did ? mlc->serio[mlc->di_map[did-1]] : NULL; + drv = (serio != NULL) ? serio->drv : NULL; + cnt = 0; + } + cnt++; i++; + if (drv == NULL || drv->interrupt == NULL) continue; + drv->interrupt(serio, (p >> 24), 0, NULL); + drv->interrupt(serio, (p >> 16) & 0xff, 0, NULL); + drv->interrupt(serio, (p >> 8) & ~HIL_PKT_ADDR_MASK, 0, NULL); + drv->interrupt(serio, p & 0xff, 0, NULL); + } +} + +/*************************** State engine *********************************/ + +#define HILSEN_SCHED 0x000100 /* Schedule the tasklet */ +#define HILSEN_BREAK 0x000200 /* Wait until next pass */ +#define HILSEN_UP 0x000400 /* relative node#, decrement */ +#define HILSEN_DOWN 0x000800 /* relative node#, increment */ +#define HILSEN_FOLLOW 0x001000 /* use retval as next node# */ + +#define HILSEN_MASK 0x0000ff +#define HILSEN_START 0 +#define HILSEN_RESTART 1 +#define HILSEN_DHR 9 +#define HILSEN_DHR2 10 +#define HILSEN_IFC 14 +#define HILSEN_HEAL0 16 +#define HILSEN_HEAL 18 +#define HILSEN_ACF 21 +#define HILSEN_ACF2 22 +#define HILSEN_DISC0 25 +#define HILSEN_DISC 27 +#define HILSEN_MATCH 40 +#define HILSEN_OPERATE 41 +#define HILSEN_PROBE 44 +#define HILSEN_DSR 52 +#define HILSEN_REPOLL 55 +#define HILSEN_IFCACF 58 +#define HILSEN_END 60 + +#define HILSEN_NEXT (HILSEN_DOWN | 1) +#define HILSEN_SAME (HILSEN_DOWN | 0) +#define HILSEN_LAST (HILSEN_UP | 1) + +#define HILSEN_DOZE (HILSEN_SAME | HILSEN_SCHED | HILSEN_BREAK) +#define HILSEN_SLEEP (HILSEN_SAME | HILSEN_BREAK) + +static int hilse_match(hil_mlc *mlc, int unused) { + int rc; + rc = hil_mlc_match_di_scratch(mlc); + if (rc == -1) { + rc = hil_mlc_find_free_di(mlc); + if (rc == -1) goto err; +#ifdef HIL_MLC_DEBUG + printk(KERN_DEBUG PREFIX "new in slot %i\n", rc); +#endif + hil_mlc_copy_di_scratch(mlc, rc); + mlc->di_map[mlc->ddi] = rc; + mlc->serio_map[rc].di_revmap = mlc->ddi; + hil_mlc_clean_serio_map(mlc); + serio_rescan(mlc->serio[rc]); + return -1; + } + mlc->di_map[mlc->ddi] = rc; +#ifdef HIL_MLC_DEBUG + printk(KERN_DEBUG PREFIX "same in slot %i\n", rc); +#endif + mlc->serio_map[rc].di_revmap = mlc->ddi; + hil_mlc_clean_serio_map(mlc); + return 0; + err: + printk(KERN_ERR PREFIX "Residual device slots exhausted, close some serios!\n"); + return 1; +} + +/* An LCV used to prevent runaway loops, forces 5 second sleep when reset. */ +static int hilse_init_lcv(hil_mlc *mlc, int unused) { + struct timeval tv; + + do_gettimeofday(&tv); + + if(mlc->lcv == 0) goto restart; /* First init, no need to dally */ + if(tv.tv_sec - mlc->lcv_tv.tv_sec < 5) return -1; + restart: + mlc->lcv_tv = tv; + mlc->lcv = 0; + return 0; +} + +static int hilse_inc_lcv(hil_mlc *mlc, int lim) { + if (mlc->lcv++ >= lim) return -1; + return 0; +} + +#if 0 +static int hilse_set_lcv(hil_mlc *mlc, int val) { + mlc->lcv = val; + return 0; +} +#endif + +/* Management of the discovered device index (zero based, -1 means no devs) */ +static int hilse_set_ddi(hil_mlc *mlc, int val) { + mlc->ddi = val; + hil_mlc_clear_di_map(mlc, val + 1); + return 0; +} + +static int hilse_dec_ddi(hil_mlc *mlc, int unused) { + mlc->ddi--; + if (mlc->ddi <= -1) { + mlc->ddi = -1; + hil_mlc_clear_di_map(mlc, 0); + return -1; + } + hil_mlc_clear_di_map(mlc, mlc->ddi + 1); + return 0; +} + +static int hilse_inc_ddi(hil_mlc *mlc, int unused) { + if (mlc->ddi >= 6) { + BUG(); + return -1; + } + mlc->ddi++; + return 0; +} + +static int hilse_take_idd(hil_mlc *mlc, int unused) { + int i; + + /* Help the state engine: + * Is this a real IDD response or just an echo? + * + * Real IDD response does not start with a command. + */ + if (mlc->ipacket[0] & HIL_PKT_CMD) goto bail; + /* Should have the command echoed further down. */ + for (i = 1; i < 16; i++) { + if (((mlc->ipacket[i] & HIL_PKT_ADDR_MASK) == + (mlc->ipacket[0] & HIL_PKT_ADDR_MASK)) && + (mlc->ipacket[i] & HIL_PKT_CMD) && + ((mlc->ipacket[i] & HIL_PKT_DATA_MASK) == HIL_CMD_IDD)) + break; + } + if (i > 15) goto bail; + /* And the rest of the packets should still be clear. */ + while (++i < 16) { + if (mlc->ipacket[i]) break; + } + if (i < 16) goto bail; + for (i = 0; i < 16; i++) { + mlc->di_scratch.idd[i] = + mlc->ipacket[i] & HIL_PKT_DATA_MASK; + } + /* Next step is to see if RSC supported */ + if (mlc->di_scratch.idd[1] & HIL_IDD_HEADER_RSC) + return HILSEN_NEXT; + if (mlc->di_scratch.idd[1] & HIL_IDD_HEADER_EXD) + return HILSEN_DOWN | 4; + return 0; + bail: + mlc->ddi--; + return -1; /* This should send us off to ACF */ +} + +static int hilse_take_rsc(hil_mlc *mlc, int unused) { + int i; + + for (i = 0; i < 16; i++) { + mlc->di_scratch.rsc[i] = + mlc->ipacket[i] & HIL_PKT_DATA_MASK; + } + /* Next step is to see if EXD supported (IDD has already been read) */ + if (mlc->di_scratch.idd[1] & HIL_IDD_HEADER_EXD) + return HILSEN_NEXT; + return 0; +} + +static int hilse_take_exd(hil_mlc *mlc, int unused) { + int i; + + for (i = 0; i < 16; i++) { + mlc->di_scratch.exd[i] = + mlc->ipacket[i] & HIL_PKT_DATA_MASK; + } + /* Next step is to see if RNM supported. */ + if (mlc->di_scratch.exd[0] & HIL_EXD_HEADER_RNM) + return HILSEN_NEXT; + return 0; +} + +static int hilse_take_rnm(hil_mlc *mlc, int unused) { + int i; + + for (i = 0; i < 16; i++) { + mlc->di_scratch.rnm[i] = + mlc->ipacket[i] & HIL_PKT_DATA_MASK; + } + do { + char nam[17]; + snprintf(nam, 16, "%s", mlc->di_scratch.rnm); + nam[16] = '\0'; + printk(KERN_INFO PREFIX "Device name gotten: %s\n", nam); + } while (0); + return 0; +} + +static int hilse_operate(hil_mlc *mlc, int repoll) { + + if (mlc->opercnt == 0) hil_mlcs_probe = 0; + mlc->opercnt = 1; + + hil_mlc_send_polls(mlc); + + if (!hil_mlcs_probe) return 0; + hil_mlcs_probe = 0; + mlc->opercnt = 0; + return 1; +} + +#define FUNC(funct, funct_arg, zero_rc, neg_rc, pos_rc) \ +{ HILSE_FUNC, { func: &funct }, funct_arg, zero_rc, neg_rc, pos_rc }, +#define OUT(pack) \ +{ HILSE_OUT, { packet: pack }, 0, HILSEN_NEXT, HILSEN_DOZE, 0 }, +#define CTS \ +{ HILSE_CTS, { packet: 0 }, 0, HILSEN_NEXT | HILSEN_SCHED | HILSEN_BREAK, HILSEN_DOZE, 0 }, +#define EXPECT(comp, to, got, got_wrong, timed_out) \ +{ HILSE_EXPECT, { packet: comp }, to, got, got_wrong, timed_out }, +#define EXPECT_LAST(comp, to, got, got_wrong, timed_out) \ +{ HILSE_EXPECT_LAST, { packet: comp }, to, got, got_wrong, timed_out }, +#define EXPECT_DISC(comp, to, got, got_wrong, timed_out) \ +{ HILSE_EXPECT_DISC, { packet: comp }, to, got, got_wrong, timed_out }, +#define IN(to, got, got_error, timed_out) \ +{ HILSE_IN, { packet: 0 }, to, got, got_error, timed_out }, +#define OUT_DISC(pack) \ +{ HILSE_OUT_DISC, { packet: pack }, 0, 0, 0, 0 }, +#define OUT_LAST(pack) \ +{ HILSE_OUT_LAST, { packet: pack }, 0, 0, 0, 0 }, + +struct hilse_node hil_mlc_se[HILSEN_END] = { + + /* 0 HILSEN_START */ + FUNC(hilse_init_lcv, 0, HILSEN_NEXT, HILSEN_SLEEP, 0) + + /* 1 HILSEN_RESTART */ + FUNC(hilse_inc_lcv, 10, HILSEN_NEXT, HILSEN_START, 0) + OUT(HIL_CTRL_ONLY) /* Disable APE */ + CTS + +#define TEST_PACKET(x) \ +(HIL_PKT_CMD | (x << HIL_PKT_ADDR_SHIFT) | x << 4 | x) + + OUT(HIL_DO_ALTER_CTRL | HIL_CTRL_TEST | TEST_PACKET(0x5)) + EXPECT(HIL_ERR_INT | TEST_PACKET(0x5), + 2000, HILSEN_NEXT, HILSEN_RESTART, HILSEN_RESTART) + OUT(HIL_DO_ALTER_CTRL | HIL_CTRL_TEST | TEST_PACKET(0xa)) + EXPECT(HIL_ERR_INT | TEST_PACKET(0xa), + 2000, HILSEN_NEXT, HILSEN_RESTART, HILSEN_RESTART) + OUT(HIL_CTRL_ONLY | 0) /* Disable test mode */ + + /* 9 HILSEN_DHR */ + FUNC(hilse_init_lcv, 0, HILSEN_NEXT, HILSEN_SLEEP, 0) + + /* 10 HILSEN_DHR2 */ + FUNC(hilse_inc_lcv, 10, HILSEN_NEXT, HILSEN_START, 0) + FUNC(hilse_set_ddi, -1, HILSEN_NEXT, 0, 0) + OUT(HIL_PKT_CMD | HIL_CMD_DHR) + IN(300000, HILSEN_DHR2, HILSEN_DHR2, HILSEN_NEXT) + + /* 14 HILSEN_IFC */ + OUT(HIL_PKT_CMD | HIL_CMD_IFC) + EXPECT(HIL_PKT_CMD | HIL_CMD_IFC | HIL_ERR_INT, + 20000, HILSEN_DISC, HILSEN_DHR2, HILSEN_NEXT ) + + /* If devices are there, they weren't in PUP or other loopback mode. + * We're more concerned at this point with restoring operation + * to devices than discovering new ones, so we try to salvage + * the loop configuration by closing off the loop. + */ + + /* 16 HILSEN_HEAL0 */ + FUNC(hilse_dec_ddi, 0, HILSEN_NEXT, HILSEN_ACF, 0) + FUNC(hilse_inc_ddi, 0, HILSEN_NEXT, 0, 0) + + /* 18 HILSEN_HEAL */ + OUT_LAST(HIL_CMD_ELB) + EXPECT_LAST(HIL_CMD_ELB | HIL_ERR_INT, + 20000, HILSEN_REPOLL, HILSEN_DSR, HILSEN_NEXT) + FUNC(hilse_dec_ddi, 0, HILSEN_HEAL, HILSEN_NEXT, 0) + + /* 21 HILSEN_ACF */ + FUNC(hilse_init_lcv, 0, HILSEN_NEXT, HILSEN_DOZE, 0) + + /* 22 HILSEN_ACF2 */ + FUNC(hilse_inc_lcv, 10, HILSEN_NEXT, HILSEN_START, 0) + OUT(HIL_PKT_CMD | HIL_CMD_ACF | 1) + IN(20000, HILSEN_NEXT, HILSEN_DSR, HILSEN_NEXT) + + /* 25 HILSEN_DISC0 */ + OUT_DISC(HIL_PKT_CMD | HIL_CMD_ELB) + EXPECT_DISC(HIL_PKT_CMD | HIL_CMD_ELB | HIL_ERR_INT, + 20000, HILSEN_NEXT, HILSEN_DSR, HILSEN_DSR) + + /* Only enter here if response just received */ + /* 27 HILSEN_DISC */ + OUT_DISC(HIL_PKT_CMD | HIL_CMD_IDD) + EXPECT_DISC(HIL_PKT_CMD | HIL_CMD_IDD | HIL_ERR_INT, + 20000, HILSEN_NEXT, HILSEN_DSR, HILSEN_START) + FUNC(hilse_inc_ddi, 0, HILSEN_NEXT, HILSEN_START, 0) + FUNC(hilse_take_idd, 0, HILSEN_MATCH, HILSEN_IFCACF, HILSEN_FOLLOW) + OUT_LAST(HIL_PKT_CMD | HIL_CMD_RSC) + EXPECT_LAST(HIL_PKT_CMD | HIL_CMD_RSC | HIL_ERR_INT, + 30000, HILSEN_NEXT, HILSEN_DSR, HILSEN_DSR) + FUNC(hilse_take_rsc, 0, HILSEN_MATCH, 0, HILSEN_FOLLOW) + OUT_LAST(HIL_PKT_CMD | HIL_CMD_EXD) + EXPECT_LAST(HIL_PKT_CMD | HIL_CMD_EXD | HIL_ERR_INT, + 30000, HILSEN_NEXT, HILSEN_DSR, HILSEN_DSR) + FUNC(hilse_take_exd, 0, HILSEN_MATCH, 0, HILSEN_FOLLOW) + OUT_LAST(HIL_PKT_CMD | HIL_CMD_RNM) + EXPECT_LAST(HIL_PKT_CMD | HIL_CMD_RNM | HIL_ERR_INT, + 30000, HILSEN_NEXT, HILSEN_DSR, HILSEN_DSR) + FUNC(hilse_take_rnm, 0, HILSEN_MATCH, 0, 0) + + /* 40 HILSEN_MATCH */ + FUNC(hilse_match, 0, HILSEN_NEXT, HILSEN_NEXT, /* TODO */ 0) + + /* 41 HILSEN_OPERATE */ + OUT(HIL_PKT_CMD | HIL_CMD_POL) + EXPECT(HIL_PKT_CMD | HIL_CMD_POL | HIL_ERR_INT, + 20000, HILSEN_NEXT, HILSEN_DSR, HILSEN_NEXT) + FUNC(hilse_operate, 0, HILSEN_OPERATE, HILSEN_IFC, HILSEN_NEXT) + + /* 44 HILSEN_PROBE */ + OUT_LAST(HIL_PKT_CMD | HIL_CMD_EPT) + IN(10000, HILSEN_DISC, HILSEN_DSR, HILSEN_NEXT) + OUT_DISC(HIL_PKT_CMD | HIL_CMD_ELB) + IN(10000, HILSEN_DISC, HILSEN_DSR, HILSEN_NEXT) + OUT(HIL_PKT_CMD | HIL_CMD_ACF | 1) + IN(10000, HILSEN_DISC0, HILSEN_DSR, HILSEN_NEXT) + OUT_LAST(HIL_PKT_CMD | HIL_CMD_ELB) + IN(10000, HILSEN_OPERATE, HILSEN_DSR, HILSEN_DSR) + + /* 52 HILSEN_DSR */ + FUNC(hilse_set_ddi, -1, HILSEN_NEXT, 0, 0) + OUT(HIL_PKT_CMD | HIL_CMD_DSR) + IN(20000, HILSEN_DHR, HILSEN_DHR, HILSEN_IFC) + + /* 55 HILSEN_REPOLL */ + OUT(HIL_PKT_CMD | HIL_CMD_RPL) + EXPECT(HIL_PKT_CMD | HIL_CMD_RPL | HIL_ERR_INT, + 20000, HILSEN_NEXT, HILSEN_DSR, HILSEN_NEXT) + FUNC(hilse_operate, 1, HILSEN_OPERATE, HILSEN_IFC, HILSEN_PROBE) + + /* 58 HILSEN_IFCACF */ + OUT(HIL_PKT_CMD | HIL_CMD_IFC) + EXPECT(HIL_PKT_CMD | HIL_CMD_IFC | HIL_ERR_INT, + 20000, HILSEN_ACF2, HILSEN_DHR2, HILSEN_HEAL) + + /* 60 HILSEN_END */ +}; + +static inline void hilse_setup_input(hil_mlc *mlc, struct hilse_node *node) { + + switch (node->act) { + case HILSE_EXPECT_DISC: + mlc->imatch = node->object.packet; + mlc->imatch |= ((mlc->ddi + 2) << HIL_PKT_ADDR_SHIFT); + break; + case HILSE_EXPECT_LAST: + mlc->imatch = node->object.packet; + mlc->imatch |= ((mlc->ddi + 1) << HIL_PKT_ADDR_SHIFT); + break; + case HILSE_EXPECT: + mlc->imatch = node->object.packet; + break; + case HILSE_IN: + mlc->imatch = 0; + break; + default: + BUG(); + } + mlc->istarted = 1; + mlc->intimeout = node->arg; + do_gettimeofday(&(mlc->instart)); + mlc->icount = 15; + memset(mlc->ipacket, 0, 16 * sizeof(hil_packet)); + if (down_trylock(&(mlc->isem))) BUG(); + + return; +} + +#ifdef HIL_MLC_DEBUG +static int doze = 0; +static int seidx; /* For debug */ +static int kick = 1; +#endif + +static int hilse_donode (hil_mlc *mlc) { + struct hilse_node *node; + int nextidx = 0; + int sched_long = 0; + unsigned long flags; + +#ifdef HIL_MLC_DEBUG + if (mlc->seidx && (mlc->seidx != seidx) && mlc->seidx != 41 && mlc->seidx != 42 && mlc->seidx != 43) { + printk(KERN_DEBUG PREFIX "z%i \n%s {%i}", doze, kick ? "K" : "", mlc->seidx); + doze = 0; + } + kick = 0; + + seidx = mlc->seidx; +#endif + node = hil_mlc_se + mlc->seidx; + + switch (node->act) { + int rc; + hil_packet pack; + + case HILSE_FUNC: + if (node->object.func == NULL) break; + rc = node->object.func(mlc, node->arg); + nextidx = (rc > 0) ? node->ugly : + ((rc < 0) ? node->bad : node->good); + if (nextidx == HILSEN_FOLLOW) nextidx = rc; + break; + case HILSE_EXPECT_LAST: + case HILSE_EXPECT_DISC: + case HILSE_EXPECT: + case HILSE_IN: + /* Already set up from previous HILSE_OUT_* */ + write_lock_irqsave(&(mlc->lock), flags); + rc = mlc->in(mlc, node->arg); + if (rc == 2) { + nextidx = HILSEN_DOZE; + sched_long = 1; + write_unlock_irqrestore(&(mlc->lock), flags); + break; + } + if (rc == 1) nextidx = node->ugly; + else if (rc == 0) nextidx = node->good; + else nextidx = node->bad; + mlc->istarted = 0; + write_unlock_irqrestore(&(mlc->lock), flags); + break; + case HILSE_OUT_LAST: + write_lock_irqsave(&(mlc->lock), flags); + pack = node->object.packet; + pack |= ((mlc->ddi + 1) << HIL_PKT_ADDR_SHIFT); + goto out; + case HILSE_OUT_DISC: + write_lock_irqsave(&(mlc->lock), flags); + pack = node->object.packet; + pack |= ((mlc->ddi + 2) << HIL_PKT_ADDR_SHIFT); + goto out; + case HILSE_OUT: + write_lock_irqsave(&(mlc->lock), flags); + pack = node->object.packet; + out: + if (mlc->istarted) goto out2; + /* Prepare to receive input */ + if ((node + 1)->act & HILSE_IN) + hilse_setup_input(mlc, node + 1); + + out2: + write_unlock_irqrestore(&(mlc->lock), flags); + + if (down_trylock(&mlc->osem)) { + nextidx = HILSEN_DOZE; + break; + } + up(&mlc->osem); + + write_lock_irqsave(&(mlc->lock), flags); + if (!(mlc->ostarted)) { + mlc->ostarted = 1; + mlc->opacket = pack; + mlc->out(mlc); + nextidx = HILSEN_DOZE; + write_unlock_irqrestore(&(mlc->lock), flags); + break; + } + mlc->ostarted = 0; + do_gettimeofday(&(mlc->instart)); + write_unlock_irqrestore(&(mlc->lock), flags); + nextidx = HILSEN_NEXT; + break; + case HILSE_CTS: + nextidx = mlc->cts(mlc) ? node->bad : node->good; + break; + default: + BUG(); + nextidx = 0; + break; + } + +#ifdef HIL_MLC_DEBUG + if (nextidx == HILSEN_DOZE) doze++; +#endif + + while (nextidx & HILSEN_SCHED) { + struct timeval tv; + + if (!sched_long) goto sched; + + do_gettimeofday(&tv); + tv.tv_usec += 1000000 * (tv.tv_sec - mlc->instart.tv_sec); + tv.tv_usec -= mlc->instart.tv_usec; + if (tv.tv_usec >= mlc->intimeout) goto sched; + tv.tv_usec = (mlc->intimeout - tv.tv_usec) * HZ / 1000000; + if (!tv.tv_usec) goto sched; + mod_timer(&hil_mlcs_kicker, jiffies + tv.tv_usec); + break; + sched: + tasklet_schedule(&hil_mlcs_tasklet); + break; + } + if (nextidx & HILSEN_DOWN) mlc->seidx += nextidx & HILSEN_MASK; + else if (nextidx & HILSEN_UP) mlc->seidx -= nextidx & HILSEN_MASK; + else mlc->seidx = nextidx & HILSEN_MASK; + + if (nextidx & HILSEN_BREAK) return 1; + return 0; +} + +/******************** tasklet context functions **************************/ +static void hil_mlcs_process(unsigned long unused) { + struct list_head *tmp; + + read_lock(&hil_mlcs_lock); + list_for_each(tmp, &hil_mlcs) { + struct hil_mlc *mlc = list_entry(tmp, hil_mlc, list); + while (hilse_donode(mlc) == 0) { +#ifdef HIL_MLC_DEBUG + if (mlc->seidx != 41 && + mlc->seidx != 42 && + mlc->seidx != 43) + printk(KERN_DEBUG PREFIX " + "); +#endif + }; + } + read_unlock(&hil_mlcs_lock); +} + +/************************* Keepalive timer task *********************/ + +void hil_mlcs_timer (unsigned long data) { + hil_mlcs_probe = 1; + tasklet_schedule(&hil_mlcs_tasklet); + /* Re-insert the periodic task. */ + if (!timer_pending(&hil_mlcs_kicker)) + mod_timer(&hil_mlcs_kicker, jiffies + HZ); +} + +/******************** user/kernel context functions **********************/ + +static int hil_mlc_serio_write(struct serio *serio, unsigned char c) { + struct hil_mlc_serio_map *map; + struct hil_mlc *mlc; + struct serio_driver *drv; + uint8_t *idx, *last; + + map = serio->port_data; + if (map == NULL) { + BUG(); + return -EIO; + } + mlc = map->mlc; + if (mlc == NULL) { + BUG(); + return -EIO; + } + mlc->serio_opacket[map->didx] |= + ((hil_packet)c) << (8 * (3 - mlc->serio_oidx[map->didx])); + + if (mlc->serio_oidx[map->didx] >= 3) { + /* for now only commands */ + if (!(mlc->serio_opacket[map->didx] & HIL_PKT_CMD)) + return -EIO; + switch (mlc->serio_opacket[map->didx] & HIL_PKT_DATA_MASK) { + case HIL_CMD_IDD: + idx = mlc->di[map->didx].idd; + goto emu; + case HIL_CMD_RSC: + idx = mlc->di[map->didx].rsc; + goto emu; + case HIL_CMD_EXD: + idx = mlc->di[map->didx].exd; + goto emu; + case HIL_CMD_RNM: + idx = mlc->di[map->didx].rnm; + goto emu; + default: + break; + } + mlc->serio_oidx[map->didx] = 0; + mlc->serio_opacket[map->didx] = 0; + } + + mlc->serio_oidx[map->didx]++; + return -EIO; + emu: + drv = serio->drv; + if (drv == NULL) { + BUG(); + return -EIO; + } + last = idx + 15; + while ((last != idx) && (*last == 0)) last--; + + while (idx != last) { + drv->interrupt(serio, 0, 0, NULL); + drv->interrupt(serio, HIL_ERR_INT >> 16, 0, NULL); + drv->interrupt(serio, 0, 0, NULL); + drv->interrupt(serio, *idx, 0, NULL); + idx++; + } + drv->interrupt(serio, 0, 0, NULL); + drv->interrupt(serio, HIL_ERR_INT >> 16, 0, NULL); + drv->interrupt(serio, HIL_PKT_CMD >> 8, 0, NULL); + drv->interrupt(serio, *idx, 0, NULL); + + mlc->serio_oidx[map->didx] = 0; + mlc->serio_opacket[map->didx] = 0; + + return 0; +} + +static int hil_mlc_serio_open(struct serio *serio) { + struct hil_mlc_serio_map *map; + struct hil_mlc *mlc; + + if (serio->private != NULL) return -EBUSY; + + map = serio->port_data; + if (map == NULL) { + BUG(); + return -ENODEV; + } + mlc = map->mlc; + if (mlc == NULL) { + BUG(); + return -ENODEV; + } + + return 0; +} + +static void hil_mlc_serio_close(struct serio *serio) { + struct hil_mlc_serio_map *map; + struct hil_mlc *mlc; + + map = serio->port_data; + if (map == NULL) { + BUG(); + return; + } + mlc = map->mlc; + if (mlc == NULL) { + BUG(); + return; + } + + serio->private = NULL; + serio->drv = NULL; + /* TODO wake up interruptable */ +} + +int hil_mlc_register(hil_mlc *mlc) { + int i; + unsigned long flags; + + if (mlc == NULL) { + return -EINVAL; + } + + mlc->istarted = 0; + mlc->ostarted = 0; + + rwlock_init(&mlc->lock); + init_MUTEX(&(mlc->osem)); + + init_MUTEX(&(mlc->isem)); + mlc->icount = -1; + mlc->imatch = 0; + + mlc->opercnt = 0; + + init_MUTEX_LOCKED(&(mlc->csem)); + + hil_mlc_clear_di_scratch(mlc); + hil_mlc_clear_di_map(mlc, 0); + for (i = 0; i < HIL_MLC_DEVMEM; i++) { + struct serio *mlc_serio; + hil_mlc_copy_di_scratch(mlc, i); + mlc_serio = kmalloc(sizeof(*mlc_serio), GFP_KERNEL); + mlc->serio[i] = mlc_serio; + memset(mlc_serio, 0, sizeof(*mlc_serio)); + mlc_serio->type = SERIO_HIL | SERIO_HIL_MLC; + mlc_serio->write = hil_mlc_serio_write; + mlc_serio->open = hil_mlc_serio_open; + mlc_serio->close = hil_mlc_serio_close; + mlc_serio->port_data = &(mlc->serio_map[i]); + mlc->serio_map[i].mlc = mlc; + mlc->serio_map[i].didx = i; + mlc->serio_map[i].di_revmap = -1; + mlc->serio_opacket[i] = 0; + mlc->serio_oidx[i] = 0; + serio_register_port(mlc_serio); + } + + mlc->tasklet = &hil_mlcs_tasklet; + + write_lock_irqsave(&hil_mlcs_lock, flags); + list_add_tail(&mlc->list, &hil_mlcs); + mlc->seidx = HILSEN_START; + write_unlock_irqrestore(&hil_mlcs_lock, flags); + + tasklet_schedule(&hil_mlcs_tasklet); + return 0; +} + +int hil_mlc_unregister(hil_mlc *mlc) { + struct list_head *tmp; + unsigned long flags; + int i; + + if (mlc == NULL) + return -EINVAL; + + write_lock_irqsave(&hil_mlcs_lock, flags); + list_for_each(tmp, &hil_mlcs) { + if (list_entry(tmp, hil_mlc, list) == mlc) + goto found; + } + + /* not found in list */ + write_unlock_irqrestore(&hil_mlcs_lock, flags); + tasklet_schedule(&hil_mlcs_tasklet); + return -ENODEV; + + found: + list_del(tmp); + write_unlock_irqrestore(&hil_mlcs_lock, flags); + + for (i = 0; i < HIL_MLC_DEVMEM; i++) { + serio_unregister_port(mlc->serio[i]); + mlc->serio[i] = NULL; + } + + tasklet_schedule(&hil_mlcs_tasklet); + return 0; +} + +/**************************** Module interface *************************/ + +static int __init hil_mlc_init(void) +{ + init_timer(&hil_mlcs_kicker); + hil_mlcs_kicker.expires = jiffies + HZ; + hil_mlcs_kicker.function = &hil_mlcs_timer; + add_timer(&hil_mlcs_kicker); + + tasklet_enable(&hil_mlcs_tasklet); + + return 0; +} + +static void __exit hil_mlc_exit(void) +{ + del_timer(&hil_mlcs_kicker); + + tasklet_disable(&hil_mlcs_tasklet); + tasklet_kill(&hil_mlcs_tasklet); +} + +module_init(hil_mlc_init); +module_exit(hil_mlc_exit); diff --git a/drivers/input/serio/hp_sdc.c b/drivers/input/serio/hp_sdc.c new file mode 100644 index 000000000000..7629452dd64b --- /dev/null +++ b/drivers/input/serio/hp_sdc.c @@ -0,0 +1,1054 @@ +/* + * HP i8042-based System Device Controller driver. + * + * Copyright (c) 2001 Brian S. Julin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * Alternatively, this software may be distributed under the terms of the + * GNU General Public License ("GPL"). + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * + * References: + * System Device Controller Microprocessor Firmware Theory of Operation + * for Part Number 1820-4784 Revision B. Dwg No. A-1820-4784-2 + * Helge Deller's original hilkbd.c port for PA-RISC. + * + * + * Driver theory of operation: + * + * hp_sdc_put does all writing to the SDC. ISR can run on a different + * CPU than hp_sdc_put, but only one CPU runs hp_sdc_put at a time + * (it cannot really benefit from SMP anyway.) A tasket fit this perfectly. + * + * All data coming back from the SDC is sent via interrupt and can be read + * fully in the ISR, so there are no latency/throughput problems there. + * The problem is with output, due to the slow clock speed of the SDC + * compared to the CPU. This should not be too horrible most of the time, + * but if used with HIL devices that support the multibyte transfer command, + * keeping outbound throughput flowing at the 6500KBps that the HIL is + * capable of is more than can be done at HZ=100. + * + * Busy polling for IBF clear wastes CPU cycles and bus cycles. hp_sdc.ibf + * is set to 0 when the IBF flag in the status register has cleared. ISR + * may do this, and may also access the parts of queued transactions related + * to reading data back from the SDC, but otherwise will not touch the + * hp_sdc state. Whenever a register is written hp_sdc.ibf is set to 1. + * + * The i8042 write index and the values in the 4-byte input buffer + * starting at 0x70 are kept track of in hp_sdc.wi, and .r7[], respectively, + * to minimize the amount of IO needed to the SDC. However these values + * do not need to be locked since they are only ever accessed by hp_sdc_put. + * + * A timer task schedules the tasklet once per second just to make + * sure it doesn't freeze up and to allow for bad reads to time out. + */ + +#include <linux/hp_sdc.h> +#include <linux/sched.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/ioport.h> +#include <linux/time.h> +#include <linux/slab.h> +#include <linux/hil.h> +#include <asm/io.h> +#include <asm/system.h> + +/* Machine-specific abstraction */ + +#if defined(__hppa__) +# include <asm/parisc-device.h> +# define sdc_readb(p) gsc_readb(p) +# define sdc_writeb(v,p) gsc_writeb((v),(p)) +#elif defined(__mc68000__) +# include <asm/uaccess.h> +# define sdc_readb(p) in_8(p) +# define sdc_writeb(v,p) out_8((p),(v)) +#else +# error "HIL is not supported on this platform" +#endif + +#define PREFIX "HP SDC: " + +MODULE_AUTHOR("Brian S. Julin <bri@calyx.com>"); +MODULE_DESCRIPTION("HP i8042-based SDC Driver"); +MODULE_LICENSE("Dual BSD/GPL"); + +EXPORT_SYMBOL(hp_sdc_request_timer_irq); +EXPORT_SYMBOL(hp_sdc_request_hil_irq); +EXPORT_SYMBOL(hp_sdc_request_cooked_irq); + +EXPORT_SYMBOL(hp_sdc_release_timer_irq); +EXPORT_SYMBOL(hp_sdc_release_hil_irq); +EXPORT_SYMBOL(hp_sdc_release_cooked_irq); + +EXPORT_SYMBOL(hp_sdc_enqueue_transaction); +EXPORT_SYMBOL(hp_sdc_dequeue_transaction); + +static hp_i8042_sdc hp_sdc; /* All driver state is kept in here. */ + +/*************** primitives for use in any context *********************/ +static inline uint8_t hp_sdc_status_in8 (void) { + uint8_t status; + unsigned long flags; + + write_lock_irqsave(&hp_sdc.ibf_lock, flags); + status = sdc_readb(hp_sdc.status_io); + if (!(status & HP_SDC_STATUS_IBF)) hp_sdc.ibf = 0; + write_unlock_irqrestore(&hp_sdc.ibf_lock, flags); + + return status; +} + +static inline uint8_t hp_sdc_data_in8 (void) { + return sdc_readb(hp_sdc.data_io); +} + +static inline void hp_sdc_status_out8 (uint8_t val) { + unsigned long flags; + + write_lock_irqsave(&hp_sdc.ibf_lock, flags); + hp_sdc.ibf = 1; + if ((val & 0xf0) == 0xe0) hp_sdc.wi = 0xff; + sdc_writeb(val, hp_sdc.status_io); + write_unlock_irqrestore(&hp_sdc.ibf_lock, flags); +} + +static inline void hp_sdc_data_out8 (uint8_t val) { + unsigned long flags; + + write_lock_irqsave(&hp_sdc.ibf_lock, flags); + hp_sdc.ibf = 1; + sdc_writeb(val, hp_sdc.data_io); + write_unlock_irqrestore(&hp_sdc.ibf_lock, flags); +} + +/* Care must be taken to only invoke hp_sdc_spin_ibf when + * absolutely needed, or in rarely invoked subroutines. + * Not only does it waste CPU cycles, it also wastes bus cycles. + */ +static inline void hp_sdc_spin_ibf(void) { + unsigned long flags; + rwlock_t *lock; + + lock = &hp_sdc.ibf_lock; + + read_lock_irqsave(lock, flags); + if (!hp_sdc.ibf) { + read_unlock_irqrestore(lock, flags); + return; + } + read_unlock(lock); + write_lock(lock); + while (sdc_readb(hp_sdc.status_io) & HP_SDC_STATUS_IBF) {}; + hp_sdc.ibf = 0; + write_unlock_irqrestore(lock, flags); +} + + +/************************ Interrupt context functions ************************/ +static void hp_sdc_take (int irq, void *dev_id, uint8_t status, uint8_t data) { + hp_sdc_transaction *curr; + + read_lock(&hp_sdc.rtq_lock); + if (hp_sdc.rcurr < 0) { + read_unlock(&hp_sdc.rtq_lock); + return; + } + curr = hp_sdc.tq[hp_sdc.rcurr]; + read_unlock(&hp_sdc.rtq_lock); + + curr->seq[curr->idx++] = status; + curr->seq[curr->idx++] = data; + hp_sdc.rqty -= 2; + do_gettimeofday(&hp_sdc.rtv); + + if (hp_sdc.rqty <= 0) { + /* All data has been gathered. */ + if(curr->seq[curr->actidx] & HP_SDC_ACT_SEMAPHORE) { + if (curr->act.semaphore) up(curr->act.semaphore); + } + if(curr->seq[curr->actidx] & HP_SDC_ACT_CALLBACK) { + if (curr->act.irqhook) + curr->act.irqhook(irq, dev_id, status, data); + } + curr->actidx = curr->idx; + curr->idx++; + /* Return control of this transaction */ + write_lock(&hp_sdc.rtq_lock); + hp_sdc.rcurr = -1; + hp_sdc.rqty = 0; + write_unlock(&hp_sdc.rtq_lock); + tasklet_schedule(&hp_sdc.task); + } +} + +static irqreturn_t hp_sdc_isr(int irq, void *dev_id, struct pt_regs * regs) { + uint8_t status, data; + + status = hp_sdc_status_in8(); + /* Read data unconditionally to advance i8042. */ + data = hp_sdc_data_in8(); + + /* For now we are ignoring these until we get the SDC to behave. */ + if (((status & 0xf1) == 0x51) && data == 0x82) { + return IRQ_HANDLED; + } + + switch(status & HP_SDC_STATUS_IRQMASK) { + case 0: /* This case is not documented. */ + break; + case HP_SDC_STATUS_USERTIMER: + case HP_SDC_STATUS_PERIODIC: + case HP_SDC_STATUS_TIMER: + read_lock(&hp_sdc.hook_lock); + if (hp_sdc.timer != NULL) + hp_sdc.timer(irq, dev_id, status, data); + read_unlock(&hp_sdc.hook_lock); + break; + case HP_SDC_STATUS_REG: + hp_sdc_take(irq, dev_id, status, data); + break; + case HP_SDC_STATUS_HILCMD: + case HP_SDC_STATUS_HILDATA: + read_lock(&hp_sdc.hook_lock); + if (hp_sdc.hil != NULL) + hp_sdc.hil(irq, dev_id, status, data); + read_unlock(&hp_sdc.hook_lock); + break; + case HP_SDC_STATUS_PUP: + read_lock(&hp_sdc.hook_lock); + if (hp_sdc.pup != NULL) + hp_sdc.pup(irq, dev_id, status, data); + else printk(KERN_INFO PREFIX "HP SDC reports successful PUP.\n"); + read_unlock(&hp_sdc.hook_lock); + break; + default: + read_lock(&hp_sdc.hook_lock); + if (hp_sdc.cooked != NULL) + hp_sdc.cooked(irq, dev_id, status, data); + read_unlock(&hp_sdc.hook_lock); + break; + } + return IRQ_HANDLED; +} + + +static irqreturn_t hp_sdc_nmisr(int irq, void *dev_id, struct pt_regs * regs) { + int status; + + status = hp_sdc_status_in8(); + printk(KERN_WARNING PREFIX "NMI !\n"); + +#if 0 + if (status & HP_SDC_NMISTATUS_FHS) { + read_lock(&hp_sdc.hook_lock); + if (hp_sdc.timer != NULL) + hp_sdc.timer(irq, dev_id, status, 0); + read_unlock(&hp_sdc.hook_lock); + } + else { + /* TODO: pass this on to the HIL handler, or do SAK here? */ + printk(KERN_WARNING PREFIX "HIL NMI\n"); + } +#endif + return IRQ_HANDLED; +} + + +/***************** Kernel (tasklet) context functions ****************/ + +unsigned long hp_sdc_put(void); + +static void hp_sdc_tasklet(unsigned long foo) { + + write_lock_irq(&hp_sdc.rtq_lock); + if (hp_sdc.rcurr >= 0) { + struct timeval tv; + do_gettimeofday(&tv); + if (tv.tv_sec > hp_sdc.rtv.tv_sec) tv.tv_usec += 1000000; + if (tv.tv_usec - hp_sdc.rtv.tv_usec > HP_SDC_MAX_REG_DELAY) { + hp_sdc_transaction *curr; + uint8_t tmp; + + curr = hp_sdc.tq[hp_sdc.rcurr]; + /* If this turns out to be a normal failure mode + * we'll need to figure out a way to communicate + * it back to the application. and be less verbose. + */ + printk(KERN_WARNING PREFIX "read timeout (%ius)!\n", + tv.tv_usec - hp_sdc.rtv.tv_usec); + curr->idx += hp_sdc.rqty; + hp_sdc.rqty = 0; + tmp = curr->seq[curr->actidx]; + curr->seq[curr->actidx] |= HP_SDC_ACT_DEAD; + if(tmp & HP_SDC_ACT_SEMAPHORE) { + if (curr->act.semaphore) + up(curr->act.semaphore); + } + if(tmp & HP_SDC_ACT_CALLBACK) { + /* Note this means that irqhooks may be called + * in tasklet/bh context. + */ + if (curr->act.irqhook) + curr->act.irqhook(0, 0, 0, 0); + } + curr->actidx = curr->idx; + curr->idx++; + hp_sdc.rcurr = -1; + } + } + write_unlock_irq(&hp_sdc.rtq_lock); + hp_sdc_put(); +} + +unsigned long hp_sdc_put(void) { + hp_sdc_transaction *curr; + uint8_t act; + int idx, curridx; + + int limit = 0; + + write_lock(&hp_sdc.lock); + + /* If i8042 buffers are full, we cannot do anything that + requires output, so we skip to the administrativa. */ + if (hp_sdc.ibf) { + hp_sdc_status_in8(); + if (hp_sdc.ibf) goto finish; + } + + anew: + /* See if we are in the middle of a sequence. */ + if (hp_sdc.wcurr < 0) hp_sdc.wcurr = 0; + read_lock_irq(&hp_sdc.rtq_lock); + if (hp_sdc.rcurr == hp_sdc.wcurr) hp_sdc.wcurr++; + read_unlock_irq(&hp_sdc.rtq_lock); + if (hp_sdc.wcurr >= HP_SDC_QUEUE_LEN) hp_sdc.wcurr = 0; + curridx = hp_sdc.wcurr; + + if (hp_sdc.tq[curridx] != NULL) goto start; + + while (++curridx != hp_sdc.wcurr) { + if (curridx >= HP_SDC_QUEUE_LEN) { + curridx = -1; /* Wrap to top */ + continue; + } + read_lock_irq(&hp_sdc.rtq_lock); + if (hp_sdc.rcurr == curridx) { + read_unlock_irq(&hp_sdc.rtq_lock); + continue; + } + read_unlock_irq(&hp_sdc.rtq_lock); + if (hp_sdc.tq[curridx] != NULL) break; /* Found one. */ + } + if (curridx == hp_sdc.wcurr) { /* There's nothing queued to do. */ + curridx = -1; + } + hp_sdc.wcurr = curridx; + + start: + + /* Check to see if the interrupt mask needs to be set. */ + if (hp_sdc.set_im) { + hp_sdc_status_out8(hp_sdc.im | HP_SDC_CMD_SET_IM); + hp_sdc.set_im = 0; + goto finish; + } + + if (hp_sdc.wcurr == -1) goto done; + + curr = hp_sdc.tq[curridx]; + idx = curr->actidx; + + if (curr->actidx >= curr->endidx) { + hp_sdc.tq[curridx] = NULL; + /* Interleave outbound data between the transactions. */ + hp_sdc.wcurr++; + if (hp_sdc.wcurr >= HP_SDC_QUEUE_LEN) hp_sdc.wcurr = 0; + goto finish; + } + + act = curr->seq[idx]; + idx++; + + if (curr->idx >= curr->endidx) { + if (act & HP_SDC_ACT_DEALLOC) kfree(curr); + hp_sdc.tq[curridx] = NULL; + /* Interleave outbound data between the transactions. */ + hp_sdc.wcurr++; + if (hp_sdc.wcurr >= HP_SDC_QUEUE_LEN) hp_sdc.wcurr = 0; + goto finish; + } + + while (act & HP_SDC_ACT_PRECMD) { + if (curr->idx != idx) { + idx++; + act &= ~HP_SDC_ACT_PRECMD; + break; + } + hp_sdc_status_out8(curr->seq[idx]); + curr->idx++; + /* act finished? */ + if ((act & HP_SDC_ACT_DURING) == HP_SDC_ACT_PRECMD) + goto actdone; + /* skip quantity field if data-out sequence follows. */ + if (act & HP_SDC_ACT_DATAOUT) curr->idx++; + goto finish; + } + if (act & HP_SDC_ACT_DATAOUT) { + int qty; + + qty = curr->seq[idx]; + idx++; + if (curr->idx - idx < qty) { + hp_sdc_data_out8(curr->seq[curr->idx]); + curr->idx++; + /* act finished? */ + if ((curr->idx - idx >= qty) && + ((act & HP_SDC_ACT_DURING) == HP_SDC_ACT_DATAOUT)) + goto actdone; + goto finish; + } + idx += qty; + act &= ~HP_SDC_ACT_DATAOUT; + } + else while (act & HP_SDC_ACT_DATAREG) { + int mask; + uint8_t w7[4]; + + mask = curr->seq[idx]; + if (idx != curr->idx) { + idx++; + idx += !!(mask & 1); + idx += !!(mask & 2); + idx += !!(mask & 4); + idx += !!(mask & 8); + act &= ~HP_SDC_ACT_DATAREG; + break; + } + + w7[0] = (mask & 1) ? curr->seq[++idx] : hp_sdc.r7[0]; + w7[1] = (mask & 2) ? curr->seq[++idx] : hp_sdc.r7[1]; + w7[2] = (mask & 4) ? curr->seq[++idx] : hp_sdc.r7[2]; + w7[3] = (mask & 8) ? curr->seq[++idx] : hp_sdc.r7[3]; + + if (hp_sdc.wi > 0x73 || hp_sdc.wi < 0x70 || + w7[hp_sdc.wi-0x70] == hp_sdc.r7[hp_sdc.wi-0x70]) { + int i = 0; + + /* Need to point the write index register */ + while ((i < 4) && w7[i] == hp_sdc.r7[i]) i++; + if (i < 4) { + hp_sdc_status_out8(HP_SDC_CMD_SET_D0 + i); + hp_sdc.wi = 0x70 + i; + goto finish; + } + idx++; + if ((act & HP_SDC_ACT_DURING) == HP_SDC_ACT_DATAREG) + goto actdone; + curr->idx = idx; + act &= ~HP_SDC_ACT_DATAREG; + break; + } + + hp_sdc_data_out8(w7[hp_sdc.wi - 0x70]); + hp_sdc.r7[hp_sdc.wi - 0x70] = w7[hp_sdc.wi - 0x70]; + hp_sdc.wi++; /* write index register autoincrements */ + { + int i = 0; + + while ((i < 4) && w7[i] == hp_sdc.r7[i]) i++; + if (i >= 4) { + curr->idx = idx + 1; + if ((act & HP_SDC_ACT_DURING) == + HP_SDC_ACT_DATAREG) + goto actdone; + } + } + goto finish; + } + /* We don't go any further in the command if there is a pending read, + because we don't want interleaved results. */ + read_lock_irq(&hp_sdc.rtq_lock); + if (hp_sdc.rcurr >= 0) { + read_unlock_irq(&hp_sdc.rtq_lock); + goto finish; + } + read_unlock_irq(&hp_sdc.rtq_lock); + + + if (act & HP_SDC_ACT_POSTCMD) { + uint8_t postcmd; + + /* curr->idx should == idx at this point. */ + postcmd = curr->seq[idx]; + curr->idx++; + if (act & HP_SDC_ACT_DATAIN) { + + /* Start a new read */ + hp_sdc.rqty = curr->seq[curr->idx]; + do_gettimeofday(&hp_sdc.rtv); + curr->idx++; + /* Still need to lock here in case of spurious irq. */ + write_lock_irq(&hp_sdc.rtq_lock); + hp_sdc.rcurr = curridx; + write_unlock_irq(&hp_sdc.rtq_lock); + hp_sdc_status_out8(postcmd); + goto finish; + } + hp_sdc_status_out8(postcmd); + goto actdone; + } + +actdone: + if (act & HP_SDC_ACT_SEMAPHORE) { + up(curr->act.semaphore); + } + else if (act & HP_SDC_ACT_CALLBACK) { + curr->act.irqhook(0,0,0,0); + } + if (curr->idx >= curr->endidx) { /* This transaction is over. */ + if (act & HP_SDC_ACT_DEALLOC) kfree(curr); + hp_sdc.tq[curridx] = NULL; + } + else { + curr->actidx = idx + 1; + curr->idx = idx + 2; + } + /* Interleave outbound data between the transactions. */ + hp_sdc.wcurr++; + if (hp_sdc.wcurr >= HP_SDC_QUEUE_LEN) hp_sdc.wcurr = 0; + + finish: + /* If by some quirk IBF has cleared and our ISR has run to + see that that has happened, do it all again. */ + if (!hp_sdc.ibf && limit++ < 20) goto anew; + + done: + if (hp_sdc.wcurr >= 0) tasklet_schedule(&hp_sdc.task); + write_unlock(&hp_sdc.lock); + return 0; +} + +/******* Functions called in either user or kernel context ****/ +int hp_sdc_enqueue_transaction(hp_sdc_transaction *this) { + unsigned long flags; + int i; + + if (this == NULL) { + tasklet_schedule(&hp_sdc.task); + return -EINVAL; + }; + + write_lock_irqsave(&hp_sdc.lock, flags); + + /* Can't have same transaction on queue twice */ + for (i=0; i < HP_SDC_QUEUE_LEN; i++) + if (hp_sdc.tq[i] == this) goto fail; + + this->actidx = 0; + this->idx = 1; + + /* Search for empty slot */ + for (i=0; i < HP_SDC_QUEUE_LEN; i++) { + if (hp_sdc.tq[i] == NULL) { + hp_sdc.tq[i] = this; + write_unlock_irqrestore(&hp_sdc.lock, flags); + tasklet_schedule(&hp_sdc.task); + return 0; + } + } + write_unlock_irqrestore(&hp_sdc.lock, flags); + printk(KERN_WARNING PREFIX "No free slot to add transaction.\n"); + return -EBUSY; + + fail: + write_unlock_irqrestore(&hp_sdc.lock,flags); + printk(KERN_WARNING PREFIX "Transaction add failed: transaction already queued?\n"); + return -EINVAL; +} + +int hp_sdc_dequeue_transaction(hp_sdc_transaction *this) { + unsigned long flags; + int i; + + write_lock_irqsave(&hp_sdc.lock, flags); + + /* TODO: don't remove it if it's not done. */ + + for (i=0; i < HP_SDC_QUEUE_LEN; i++) + if (hp_sdc.tq[i] == this) hp_sdc.tq[i] = NULL; + + write_unlock_irqrestore(&hp_sdc.lock, flags); + return 0; +} + + + +/********************** User context functions **************************/ +int hp_sdc_request_timer_irq(hp_sdc_irqhook *callback) { + + if (callback == NULL || hp_sdc.dev == NULL) { + return -EINVAL; + } + write_lock_irq(&hp_sdc.hook_lock); + if (hp_sdc.timer != NULL) { + write_unlock_irq(&hp_sdc.hook_lock); + return -EBUSY; + } + + hp_sdc.timer = callback; + /* Enable interrupts from the timers */ + hp_sdc.im &= ~HP_SDC_IM_FH; + hp_sdc.im &= ~HP_SDC_IM_PT; + hp_sdc.im &= ~HP_SDC_IM_TIMERS; + hp_sdc.set_im = 1; + write_unlock_irq(&hp_sdc.hook_lock); + + tasklet_schedule(&hp_sdc.task); + + return 0; +} + +int hp_sdc_request_hil_irq(hp_sdc_irqhook *callback) { + + if (callback == NULL || hp_sdc.dev == NULL) { + return -EINVAL; + } + write_lock_irq(&hp_sdc.hook_lock); + if (hp_sdc.hil != NULL) { + write_unlock_irq(&hp_sdc.hook_lock); + return -EBUSY; + } + + hp_sdc.hil = callback; + hp_sdc.im &= ~(HP_SDC_IM_HIL | HP_SDC_IM_RESET); + hp_sdc.set_im = 1; + write_unlock_irq(&hp_sdc.hook_lock); + + tasklet_schedule(&hp_sdc.task); + + return 0; +} + +int hp_sdc_request_cooked_irq(hp_sdc_irqhook *callback) { + + if (callback == NULL || hp_sdc.dev == NULL) { + return -EINVAL; + } + write_lock_irq(&hp_sdc.hook_lock); + if (hp_sdc.cooked != NULL) { + write_unlock_irq(&hp_sdc.hook_lock); + return -EBUSY; + } + + /* Enable interrupts from the HIL MLC */ + hp_sdc.cooked = callback; + hp_sdc.im &= ~(HP_SDC_IM_HIL | HP_SDC_IM_RESET); + hp_sdc.set_im = 1; + write_unlock_irq(&hp_sdc.hook_lock); + + tasklet_schedule(&hp_sdc.task); + + return 0; +} + +int hp_sdc_release_timer_irq(hp_sdc_irqhook *callback) { + + + write_lock_irq(&hp_sdc.hook_lock); + if ((callback != hp_sdc.timer) || + (hp_sdc.timer == NULL)) { + write_unlock_irq(&hp_sdc.hook_lock); + return -EINVAL; + } + + /* Disable interrupts from the timers */ + hp_sdc.timer = NULL; + hp_sdc.im |= HP_SDC_IM_TIMERS; + hp_sdc.im |= HP_SDC_IM_FH; + hp_sdc.im |= HP_SDC_IM_PT; + hp_sdc.set_im = 1; + write_unlock_irq(&hp_sdc.hook_lock); + tasklet_schedule(&hp_sdc.task); + + return 0; +} + +int hp_sdc_release_hil_irq(hp_sdc_irqhook *callback) { + + write_lock_irq(&hp_sdc.hook_lock); + if ((callback != hp_sdc.hil) || + (hp_sdc.hil == NULL)) { + write_unlock_irq(&hp_sdc.hook_lock); + return -EINVAL; + } + + hp_sdc.hil = NULL; + /* Disable interrupts from HIL only if there is no cooked driver. */ + if(hp_sdc.cooked == NULL) { + hp_sdc.im |= (HP_SDC_IM_HIL | HP_SDC_IM_RESET); + hp_sdc.set_im = 1; + } + write_unlock_irq(&hp_sdc.hook_lock); + tasklet_schedule(&hp_sdc.task); + + return 0; +} + +int hp_sdc_release_cooked_irq(hp_sdc_irqhook *callback) { + + write_lock_irq(&hp_sdc.hook_lock); + if ((callback != hp_sdc.cooked) || + (hp_sdc.cooked == NULL)) { + write_unlock_irq(&hp_sdc.hook_lock); + return -EINVAL; + } + + hp_sdc.cooked = NULL; + /* Disable interrupts from HIL only if there is no raw HIL driver. */ + if(hp_sdc.hil == NULL) { + hp_sdc.im |= (HP_SDC_IM_HIL | HP_SDC_IM_RESET); + hp_sdc.set_im = 1; + } + write_unlock_irq(&hp_sdc.hook_lock); + tasklet_schedule(&hp_sdc.task); + + return 0; +} + +/************************* Keepalive timer task *********************/ + +void hp_sdc_kicker (unsigned long data) { + tasklet_schedule(&hp_sdc.task); + /* Re-insert the periodic task. */ + mod_timer(&hp_sdc.kicker, jiffies + HZ); +} + +/************************** Module Initialization ***************************/ + +#if defined(__hppa__) + +static struct parisc_device_id hp_sdc_tbl[] = { + { + .hw_type = HPHW_FIO, + .hversion_rev = HVERSION_REV_ANY_ID, + .hversion = HVERSION_ANY_ID, + .sversion = 0x73, + }, + { 0, } +}; + +MODULE_DEVICE_TABLE(parisc, hp_sdc_tbl); + +static int __init hp_sdc_init_hppa(struct parisc_device *d); + +static struct parisc_driver hp_sdc_driver = { + .name = "HP SDC", + .id_table = hp_sdc_tbl, + .probe = hp_sdc_init_hppa, +}; + +#endif /* __hppa__ */ + +static int __init hp_sdc_init(void) +{ + int i; + char *errstr; + hp_sdc_transaction t_sync; + uint8_t ts_sync[6]; + struct semaphore s_sync; + + rwlock_init(&hp_sdc.lock); + rwlock_init(&hp_sdc.ibf_lock); + rwlock_init(&hp_sdc.rtq_lock); + rwlock_init(&hp_sdc.hook_lock); + + hp_sdc.timer = NULL; + hp_sdc.hil = NULL; + hp_sdc.pup = NULL; + hp_sdc.cooked = NULL; + hp_sdc.im = HP_SDC_IM_MASK; /* Mask maskable irqs */ + hp_sdc.set_im = 1; + hp_sdc.wi = 0xff; + hp_sdc.r7[0] = 0xff; + hp_sdc.r7[1] = 0xff; + hp_sdc.r7[2] = 0xff; + hp_sdc.r7[3] = 0xff; + hp_sdc.ibf = 1; + + for (i = 0; i < HP_SDC_QUEUE_LEN; i++) hp_sdc.tq[i] = NULL; + hp_sdc.wcurr = -1; + hp_sdc.rcurr = -1; + hp_sdc.rqty = 0; + + hp_sdc.dev_err = -ENODEV; + + errstr = "IO not found for"; + if (!hp_sdc.base_io) goto err0; + + errstr = "IRQ not found for"; + if (!hp_sdc.irq) goto err0; + + hp_sdc.dev_err = -EBUSY; + +#if defined(__hppa__) + errstr = "IO not available for"; + if (request_region(hp_sdc.data_io, 2, hp_sdc_driver.name)) goto err0; +#endif + + errstr = "IRQ not available for"; + if(request_irq(hp_sdc.irq, &hp_sdc_isr, 0, "HP SDC", + (void *) hp_sdc.base_io)) goto err1; + + errstr = "NMI not available for"; + if (request_irq(hp_sdc.nmi, &hp_sdc_nmisr, 0, "HP SDC NMI", + (void *) hp_sdc.base_io)) goto err2; + + printk(KERN_INFO PREFIX "HP SDC at 0x%p, IRQ %d (NMI IRQ %d)\n", + (void *)hp_sdc.base_io, hp_sdc.irq, hp_sdc.nmi); + + hp_sdc_status_in8(); + hp_sdc_data_in8(); + + tasklet_init(&hp_sdc.task, hp_sdc_tasklet, 0); + + /* Sync the output buffer registers, thus scheduling hp_sdc_tasklet. */ + t_sync.actidx = 0; + t_sync.idx = 1; + t_sync.endidx = 6; + t_sync.seq = ts_sync; + ts_sync[0] = HP_SDC_ACT_DATAREG | HP_SDC_ACT_SEMAPHORE; + ts_sync[1] = 0x0f; + ts_sync[2] = ts_sync[3] = ts_sync[4] = ts_sync[5] = 0; + t_sync.act.semaphore = &s_sync; + init_MUTEX_LOCKED(&s_sync); + hp_sdc_enqueue_transaction(&t_sync); + down(&s_sync); /* Wait for t_sync to complete */ + + /* Create the keepalive task */ + init_timer(&hp_sdc.kicker); + hp_sdc.kicker.expires = jiffies + HZ; + hp_sdc.kicker.function = &hp_sdc_kicker; + add_timer(&hp_sdc.kicker); + + hp_sdc.dev_err = 0; + return 0; + err2: + free_irq(hp_sdc.irq, NULL); + err1: + release_region(hp_sdc.data_io, 2); + err0: + printk(KERN_WARNING PREFIX ": %s SDC IO=0x%p IRQ=0x%x NMI=0x%x\n", + errstr, (void *)hp_sdc.base_io, hp_sdc.irq, hp_sdc.nmi); + hp_sdc.dev = NULL; + return hp_sdc.dev_err; +} + +#if defined(__hppa__) + +static int __init hp_sdc_init_hppa(struct parisc_device *d) +{ + if (!d) return 1; + if (hp_sdc.dev != NULL) return 1; /* We only expect one SDC */ + + hp_sdc.dev = d; + hp_sdc.irq = d->irq; + hp_sdc.nmi = d->aux_irq; + hp_sdc.base_io = d->hpa; + hp_sdc.data_io = d->hpa + 0x800; + hp_sdc.status_io = d->hpa + 0x801; + + return hp_sdc_init(); +} + +#endif /* __hppa__ */ + +#if !defined(__mc68000__) /* Link error on m68k! */ +static void __exit hp_sdc_exit(void) +#else +static void hp_sdc_exit(void) +#endif +{ + write_lock_irq(&hp_sdc.lock); + + /* Turn off all maskable "sub-function" irq's. */ + hp_sdc_spin_ibf(); + sdc_writeb(HP_SDC_CMD_SET_IM | HP_SDC_IM_MASK, hp_sdc.status_io); + + /* Wait until we know this has been processed by the i8042 */ + hp_sdc_spin_ibf(); + + free_irq(hp_sdc.nmi, NULL); + free_irq(hp_sdc.irq, NULL); + write_unlock_irq(&hp_sdc.lock); + + del_timer(&hp_sdc.kicker); + + tasklet_kill(&hp_sdc.task); + +/* release_region(hp_sdc.data_io, 2); */ + +#if defined(__hppa__) + if (unregister_parisc_driver(&hp_sdc_driver)) + printk(KERN_WARNING PREFIX "Error unregistering HP SDC"); +#endif +} + +static int __init hp_sdc_register(void) +{ + hp_sdc_transaction tq_init; + uint8_t tq_init_seq[5]; + struct semaphore tq_init_sem; +#if defined(__mc68000__) + mm_segment_t fs; + unsigned char i; +#endif + + hp_sdc.dev = NULL; + hp_sdc.dev_err = 0; +#if defined(__hppa__) + if (register_parisc_driver(&hp_sdc_driver)) { + printk(KERN_WARNING PREFIX "Error registering SDC with system bus tree.\n"); + return -ENODEV; + } +#elif defined(__mc68000__) + if (!MACH_IS_HP300) + return -ENODEV; + + hp_sdc.irq = 1; + hp_sdc.nmi = 7; + hp_sdc.base_io = (unsigned long) 0xf0428000; + hp_sdc.data_io = (unsigned long) hp_sdc.base_io + 1; + hp_sdc.status_io = (unsigned long) hp_sdc.base_io + 3; + fs = get_fs(); + set_fs(KERNEL_DS); + if (!get_user(i, (unsigned char *)hp_sdc.data_io)) + hp_sdc.dev = (void *)1; + set_fs(fs); + hp_sdc.dev_err = hp_sdc_init(); +#endif + if (hp_sdc.dev == NULL) { + printk(KERN_WARNING PREFIX "No SDC found.\n"); + return hp_sdc.dev_err; + } + + init_MUTEX_LOCKED(&tq_init_sem); + + tq_init.actidx = 0; + tq_init.idx = 1; + tq_init.endidx = 5; + tq_init.seq = tq_init_seq; + tq_init.act.semaphore = &tq_init_sem; + + tq_init_seq[0] = + HP_SDC_ACT_POSTCMD | HP_SDC_ACT_DATAIN | HP_SDC_ACT_SEMAPHORE; + tq_init_seq[1] = HP_SDC_CMD_READ_KCC; + tq_init_seq[2] = 1; + tq_init_seq[3] = 0; + tq_init_seq[4] = 0; + + hp_sdc_enqueue_transaction(&tq_init); + + down(&tq_init_sem); + up(&tq_init_sem); + + if ((tq_init_seq[0] & HP_SDC_ACT_DEAD) == HP_SDC_ACT_DEAD) { + printk(KERN_WARNING PREFIX "Error reading config byte.\n"); + hp_sdc_exit(); + return -ENODEV; + } + hp_sdc.r11 = tq_init_seq[4]; + if (hp_sdc.r11 & HP_SDC_CFG_NEW) { + char *str; + printk(KERN_INFO PREFIX "New style SDC\n"); + tq_init_seq[1] = HP_SDC_CMD_READ_XTD; + tq_init.actidx = 0; + tq_init.idx = 1; + down(&tq_init_sem); + hp_sdc_enqueue_transaction(&tq_init); + down(&tq_init_sem); + up(&tq_init_sem); + if ((tq_init_seq[0] & HP_SDC_ACT_DEAD) == HP_SDC_ACT_DEAD) { + printk(KERN_WARNING PREFIX "Error reading extended config byte.\n"); + return -ENODEV; + } + hp_sdc.r7e = tq_init_seq[4]; + HP_SDC_XTD_REV_STRINGS(hp_sdc.r7e & HP_SDC_XTD_REV, str) + printk(KERN_INFO PREFIX "Revision: %s\n", str); + if (hp_sdc.r7e & HP_SDC_XTD_BEEPER) { + printk(KERN_INFO PREFIX "TI SN76494 beeper present\n"); + } + if (hp_sdc.r7e & HP_SDC_XTD_BBRTC) { + printk(KERN_INFO PREFIX "OKI MSM-58321 BBRTC present\n"); + } + printk(KERN_INFO PREFIX "Spunking the self test register to force PUP " + "on next firmware reset.\n"); + tq_init_seq[0] = HP_SDC_ACT_PRECMD | + HP_SDC_ACT_DATAOUT | HP_SDC_ACT_SEMAPHORE; + tq_init_seq[1] = HP_SDC_CMD_SET_STR; + tq_init_seq[2] = 1; + tq_init_seq[3] = 0; + tq_init.actidx = 0; + tq_init.idx = 1; + tq_init.endidx = 4; + down(&tq_init_sem); + hp_sdc_enqueue_transaction(&tq_init); + down(&tq_init_sem); + up(&tq_init_sem); + } + else { + printk(KERN_INFO PREFIX "Old style SDC (1820-%s).\n", + (hp_sdc.r11 & HP_SDC_CFG_REV) ? "3300" : "2564/3087"); + } + + return 0; +} + +module_init(hp_sdc_register); +module_exit(hp_sdc_exit); + +/* Timing notes: These measurements taken on my 64MHz 7100-LC (715/64) + * cycles cycles-adj time + * between two consecutive mfctl(16)'s: 4 n/a 63ns + * hp_sdc_spin_ibf when idle: 119 115 1.7us + * gsc_writeb status register: 83 79 1.2us + * IBF to clear after sending SET_IM: 6204 6006 93us + * IBF to clear after sending LOAD_RT: 4467 4352 68us + * IBF to clear after sending two LOAD_RTs: 18974 18859 295us + * READ_T1, read status/data, IRQ, call handler: 35564 n/a 556us + * cmd to ~IBF READ_T1 2nd time right after: 5158403 n/a 81ms + * between IRQ received and ~IBF for above: 2578877 n/a 40ms + * + * Performance stats after a run of this module configuring HIL and + * receiving a few mouse events: + * + * status in8 282508 cycles 7128 calls + * status out8 8404 cycles 341 calls + * data out8 1734 cycles 78 calls + * isr 174324 cycles 617 calls (includes take) + * take 1241 cycles 2 calls + * put 1411504 cycles 6937 calls + * task 1655209 cycles 6937 calls (includes put) + * + */ diff --git a/drivers/input/serio/hp_sdc_mlc.c b/drivers/input/serio/hp_sdc_mlc.c new file mode 100644 index 000000000000..e3c44ffae674 --- /dev/null +++ b/drivers/input/serio/hp_sdc_mlc.c @@ -0,0 +1,358 @@ +/* + * Access to HP-HIL MLC through HP System Device Controller. + * + * Copyright (c) 2001 Brian S. Julin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * Alternatively, this software may be distributed under the terms of the + * GNU General Public License ("GPL"). + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * + * References: + * HP-HIL Technical Reference Manual. Hewlett Packard Product No. 45918A + * System Device Controller Microprocessor Firmware Theory of Operation + * for Part Number 1820-4784 Revision B. Dwg No. A-1820-4784-2 + * + */ + +#include <linux/hil_mlc.h> +#include <linux/hp_sdc.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/string.h> + +#define PREFIX "HP SDC MLC: " + +static hil_mlc hp_sdc_mlc; + +MODULE_AUTHOR("Brian S. Julin <bri@calyx.com>"); +MODULE_DESCRIPTION("Glue for onboard HIL MLC in HP-PARISC machines"); +MODULE_LICENSE("Dual BSD/GPL"); + +struct hp_sdc_mlc_priv_s { + int emtestmode; + hp_sdc_transaction trans; + u8 tseq[16]; + int got5x; +} hp_sdc_mlc_priv; + +/************************* Interrupt context ******************************/ +static void hp_sdc_mlc_isr (int irq, void *dev_id, + uint8_t status, uint8_t data) { + int idx; + hil_mlc *mlc = &hp_sdc_mlc; + + write_lock(&(mlc->lock)); + if (mlc->icount < 0) { + printk(KERN_WARNING PREFIX "HIL Overflow!\n"); + up(&mlc->isem); + goto out; + } + idx = 15 - mlc->icount; + if ((status & HP_SDC_STATUS_IRQMASK) == HP_SDC_STATUS_HILDATA) { + mlc->ipacket[idx] |= data | HIL_ERR_INT; + mlc->icount--; + if (hp_sdc_mlc_priv.got5x) goto check; + if (!idx) goto check; + if ((mlc->ipacket[idx-1] & HIL_PKT_ADDR_MASK) != + (mlc->ipacket[idx] & HIL_PKT_ADDR_MASK)) { + mlc->ipacket[idx] &= ~HIL_PKT_ADDR_MASK; + mlc->ipacket[idx] |= (mlc->ipacket[idx-1] + & HIL_PKT_ADDR_MASK); + } + goto check; + } + /* We know status is 5X */ + if (data & HP_SDC_HIL_ISERR) goto err; + mlc->ipacket[idx] = + (data & HP_SDC_HIL_R1MASK) << HIL_PKT_ADDR_SHIFT; + hp_sdc_mlc_priv.got5x = 1; + goto out; + + check: + hp_sdc_mlc_priv.got5x = 0; + if (mlc->imatch == 0) goto done; + if ((mlc->imatch == (HIL_ERR_INT | HIL_PKT_CMD | HIL_CMD_POL)) + && (mlc->ipacket[idx] == (mlc->imatch | idx))) goto done; + if (mlc->ipacket[idx] == mlc->imatch) goto done; + goto out; + + err: + printk(KERN_DEBUG PREFIX "err code %x\n", data); + switch (data) { + case HP_SDC_HIL_RC_DONE: + printk(KERN_WARNING PREFIX "Bastard SDC reconfigured loop!\n"); + break; + case HP_SDC_HIL_ERR: + mlc->ipacket[idx] |= HIL_ERR_INT | HIL_ERR_PERR | + HIL_ERR_FERR | HIL_ERR_FOF; + break; + case HP_SDC_HIL_TO: + mlc->ipacket[idx] |= HIL_ERR_INT | HIL_ERR_LERR; + break; + case HP_SDC_HIL_RC: + printk(KERN_WARNING PREFIX "Bastard SDC decided to reconfigure loop!\n"); + break; + default: + printk(KERN_WARNING PREFIX "Unkown HIL Error status (%x)!\n", data); + break; + } + /* No more data will be coming due to an error. */ + done: + tasklet_schedule(mlc->tasklet); + up(&(mlc->isem)); + out: + write_unlock(&(mlc->lock)); +} + + +/******************** Tasklet or userspace context functions ****************/ + +static int hp_sdc_mlc_in (hil_mlc *mlc, suseconds_t timeout) { + unsigned long flags; + struct hp_sdc_mlc_priv_s *priv; + int rc = 2; + + priv = mlc->priv; + + write_lock_irqsave(&(mlc->lock), flags); + + /* Try to down the semaphore */ + if (down_trylock(&(mlc->isem))) { + struct timeval tv; + if (priv->emtestmode) { + mlc->ipacket[0] = + HIL_ERR_INT | (mlc->opacket & + (HIL_PKT_CMD | + HIL_PKT_ADDR_MASK | + HIL_PKT_DATA_MASK)); + mlc->icount = 14; + /* printk(KERN_DEBUG PREFIX ">[%x]\n", mlc->ipacket[0]); */ + goto wasup; + } + do_gettimeofday(&tv); + tv.tv_usec += 1000000 * (tv.tv_sec - mlc->instart.tv_sec); + if (tv.tv_usec - mlc->instart.tv_usec > mlc->intimeout) { + /* printk("!%i %i", + tv.tv_usec - mlc->instart.tv_usec, + mlc->intimeout); + */ + rc = 1; + up(&(mlc->isem)); + } + goto done; + } + wasup: + up(&(mlc->isem)); + rc = 0; + goto done; + done: + write_unlock_irqrestore(&(mlc->lock), flags); + return rc; +} + +static int hp_sdc_mlc_cts (hil_mlc *mlc) { + struct hp_sdc_mlc_priv_s *priv; + unsigned long flags; + + priv = mlc->priv; + + write_lock_irqsave(&(mlc->lock), flags); + + /* Try to down the semaphores -- they should be up. */ + if (down_trylock(&(mlc->isem))) { + BUG(); + goto busy; + } + if (down_trylock(&(mlc->osem))) { + BUG(); + up(&(mlc->isem)); + goto busy; + } + up(&(mlc->isem)); + up(&(mlc->osem)); + + if (down_trylock(&(mlc->csem))) { + if (priv->trans.act.semaphore != &(mlc->csem)) goto poll; + goto busy; + } + if (!(priv->tseq[4] & HP_SDC_USE_LOOP)) goto done; + + poll: + priv->trans.act.semaphore = &(mlc->csem); + priv->trans.actidx = 0; + priv->trans.idx = 1; + priv->trans.endidx = 5; + priv->tseq[0] = + HP_SDC_ACT_POSTCMD | HP_SDC_ACT_DATAIN | HP_SDC_ACT_SEMAPHORE; + priv->tseq[1] = HP_SDC_CMD_READ_USE; + priv->tseq[2] = 1; + priv->tseq[3] = 0; + priv->tseq[4] = 0; + hp_sdc_enqueue_transaction(&(priv->trans)); + busy: + write_unlock_irqrestore(&(mlc->lock), flags); + return 1; + done: + priv->trans.act.semaphore = &(mlc->osem); + up(&(mlc->csem)); + write_unlock_irqrestore(&(mlc->lock), flags); + return 0; +} + +static void hp_sdc_mlc_out (hil_mlc *mlc) { + struct hp_sdc_mlc_priv_s *priv; + unsigned long flags; + + priv = mlc->priv; + + write_lock_irqsave(&(mlc->lock), flags); + + /* Try to down the semaphore -- it should be up. */ + if (down_trylock(&(mlc->osem))) { + BUG(); + goto done; + } + + if (mlc->opacket & HIL_DO_ALTER_CTRL) goto do_control; + + do_data: + if (priv->emtestmode) { + up(&(mlc->osem)); + goto done; + } + /* Shouldn't be sending commands when loop may be busy */ + if (down_trylock(&(mlc->csem))) { + BUG(); + goto done; + } + up(&(mlc->csem)); + + priv->trans.actidx = 0; + priv->trans.idx = 1; + priv->trans.act.semaphore = &(mlc->osem); + priv->trans.endidx = 6; + priv->tseq[0] = + HP_SDC_ACT_DATAREG | HP_SDC_ACT_POSTCMD | HP_SDC_ACT_SEMAPHORE; + priv->tseq[1] = 0x7; + priv->tseq[2] = + (mlc->opacket & + (HIL_PKT_ADDR_MASK | HIL_PKT_CMD)) + >> HIL_PKT_ADDR_SHIFT; + priv->tseq[3] = + (mlc->opacket & HIL_PKT_DATA_MASK) + >> HIL_PKT_DATA_SHIFT; + priv->tseq[4] = 0; /* No timeout */ + if (priv->tseq[3] == HIL_CMD_DHR) priv->tseq[4] = 1; + priv->tseq[5] = HP_SDC_CMD_DO_HIL; + goto enqueue; + + do_control: + priv->emtestmode = mlc->opacket & HIL_CTRL_TEST; + if ((mlc->opacket & (HIL_CTRL_APE | HIL_CTRL_IPF)) == HIL_CTRL_APE) { + BUG(); /* we cannot emulate this, it should not be used. */ + } + if ((mlc->opacket & HIL_CTRL_ONLY) == HIL_CTRL_ONLY) goto control_only; + if (mlc->opacket & HIL_CTRL_APE) { + BUG(); /* Should not send command/data after engaging APE */ + goto done; + } + /* Disengaging APE this way would not be valid either since + * the loop must be allowed to idle. + * + * So, it works out that we really never actually send control + * and data when using SDC, we just send the data. + */ + goto do_data; + + control_only: + priv->trans.actidx = 0; + priv->trans.idx = 1; + priv->trans.act.semaphore = &(mlc->osem); + priv->trans.endidx = 4; + priv->tseq[0] = + HP_SDC_ACT_PRECMD | HP_SDC_ACT_DATAOUT | HP_SDC_ACT_SEMAPHORE; + priv->tseq[1] = HP_SDC_CMD_SET_LPC; + priv->tseq[2] = 1; + // priv->tseq[3] = (mlc->ddc + 1) | HP_SDC_LPS_ACSUCC; + priv->tseq[3] = 0; + if (mlc->opacket & HIL_CTRL_APE) { + priv->tseq[3] |= HP_SDC_LPC_APE_IPF; + down_trylock(&(mlc->csem)); + } + enqueue: + hp_sdc_enqueue_transaction(&(priv->trans)); + done: + write_unlock_irqrestore(&(mlc->lock), flags); +} + +static int __init hp_sdc_mlc_init(void) +{ + hil_mlc *mlc = &hp_sdc_mlc; + + printk(KERN_INFO PREFIX "Registering the System Domain Controller's HIL MLC.\n"); + + hp_sdc_mlc_priv.emtestmode = 0; + hp_sdc_mlc_priv.trans.seq = hp_sdc_mlc_priv.tseq; + hp_sdc_mlc_priv.trans.act.semaphore = &(mlc->osem); + hp_sdc_mlc_priv.got5x = 0; + + mlc->cts = &hp_sdc_mlc_cts; + mlc->in = &hp_sdc_mlc_in; + mlc->out = &hp_sdc_mlc_out; + + if (hil_mlc_register(mlc)) { + printk(KERN_WARNING PREFIX "Failed to register MLC structure with hil_mlc\n"); + goto err0; + } + mlc->priv = &hp_sdc_mlc_priv; + + if (hp_sdc_request_hil_irq(&hp_sdc_mlc_isr)) { + printk(KERN_WARNING PREFIX "Request for raw HIL ISR hook denied\n"); + goto err1; + } + return 0; + err1: + if (hil_mlc_unregister(mlc)) { + printk(KERN_ERR PREFIX "Failed to unregister MLC structure with hil_mlc.\n" + "This is bad. Could cause an oops.\n"); + } + err0: + return -EBUSY; +} + +static void __exit hp_sdc_mlc_exit(void) +{ + hil_mlc *mlc = &hp_sdc_mlc; + if (hp_sdc_release_hil_irq(&hp_sdc_mlc_isr)) { + printk(KERN_ERR PREFIX "Failed to release the raw HIL ISR hook.\n" + "This is bad. Could cause an oops.\n"); + } + if (hil_mlc_unregister(mlc)) { + printk(KERN_ERR PREFIX "Failed to unregister MLC structure with hil_mlc.\n" + "This is bad. Could cause an oops.\n"); + } +} + +module_init(hp_sdc_mlc_init); +module_exit(hp_sdc_mlc_exit); diff --git a/drivers/input/serio/i8042-io.h b/drivers/input/serio/i8042-io.h new file mode 100644 index 000000000000..c9e633d21d90 --- /dev/null +++ b/drivers/input/serio/i8042-io.h @@ -0,0 +1,93 @@ +#ifndef _I8042_IO_H +#define _I8042_IO_H + +/* + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +/* + * Names. + */ + +#define I8042_KBD_PHYS_DESC "isa0060/serio0" +#define I8042_AUX_PHYS_DESC "isa0060/serio1" +#define I8042_MUX_PHYS_DESC "isa0060/serio%d" + +/* + * IRQs. + */ + +#ifdef __alpha__ +# define I8042_KBD_IRQ 1 +# define I8042_AUX_IRQ (RTC_PORT(0) == 0x170 ? 9 : 12) /* Jensen is special */ +#elif defined(__arm__) +/* defined in include/asm-arm/arch-xxx/irqs.h */ +#include <asm/irq.h> +#elif defined(CONFIG_SUPERH64) +#include <asm/irq.h> +#else +# define I8042_KBD_IRQ 1 +# define I8042_AUX_IRQ 12 +#endif + + +/* + * Register numbers. + */ + +#define I8042_COMMAND_REG 0x64 +#define I8042_STATUS_REG 0x64 +#define I8042_DATA_REG 0x60 + +static inline int i8042_read_data(void) +{ + return inb(I8042_DATA_REG); +} + +static inline int i8042_read_status(void) +{ + return inb(I8042_STATUS_REG); +} + +static inline void i8042_write_data(int val) +{ + outb(val, I8042_DATA_REG); +} + +static inline void i8042_write_command(int val) +{ + outb(val, I8042_COMMAND_REG); +} + +static inline int i8042_platform_init(void) +{ +/* + * On some platforms touching the i8042 data register region can do really + * bad things. Because of this the region is always reserved on such boxes. + */ +#if !defined(__sh__) && !defined(__alpha__) && !defined(__mips__) && !defined(CONFIG_PPC64) + if (!request_region(I8042_DATA_REG, 16, "i8042")) + return -1; +#endif + + i8042_reset = 1; + +#if defined(CONFIG_PPC64) + if (check_legacy_ioport(I8042_DATA_REG)) + return -1; + if (!request_region(I8042_DATA_REG, 16, "i8042")) + return -1; +#endif + return 0; +} + +static inline void i8042_platform_exit(void) +{ +#if !defined(__sh__) && !defined(__alpha__) && !defined(CONFIG_PPC64) + release_region(I8042_DATA_REG, 16); +#endif +} + +#endif /* _I8042_IO_H */ diff --git a/drivers/input/serio/i8042-ip22io.h b/drivers/input/serio/i8042-ip22io.h new file mode 100644 index 000000000000..863b9c95fbb8 --- /dev/null +++ b/drivers/input/serio/i8042-ip22io.h @@ -0,0 +1,76 @@ +#ifndef _I8042_IP22_H +#define _I8042_IP22_H + +#include <asm/sgi/ioc.h> +#include <asm/sgi/ip22.h> + +/* + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +/* + * Names. + */ + +#define I8042_KBD_PHYS_DESC "hpc3ps2/serio0" +#define I8042_AUX_PHYS_DESC "hpc3ps2/serio1" +#define I8042_MUX_PHYS_DESC "hpc3ps2/serio%d" + +/* + * IRQs. + */ + +#define I8042_KBD_IRQ SGI_KEYBD_IRQ +#define I8042_AUX_IRQ SGI_KEYBD_IRQ + +/* + * Register numbers. + */ + +#define I8042_COMMAND_REG ((unsigned long)&sgioc->kbdmouse.command) +#define I8042_STATUS_REG ((unsigned long)&sgioc->kbdmouse.command) +#define I8042_DATA_REG ((unsigned long)&sgioc->kbdmouse.data) + +static inline int i8042_read_data(void) +{ + return sgioc->kbdmouse.data; +} + +static inline int i8042_read_status(void) +{ + return sgioc->kbdmouse.command; +} + +static inline void i8042_write_data(int val) +{ + sgioc->kbdmouse.data = val; +} + +static inline void i8042_write_command(int val) +{ + sgioc->kbdmouse.command = val; +} + +static inline int i8042_platform_init(void) +{ +#if 0 + /* XXX sgi_kh is a virtual address */ + if (!request_mem_region(sgi_kh, sizeof(struct hpc_keyb), "i8042")) + return 1; +#endif + + i8042_reset = 1; + + return 0; +} + +static inline void i8042_platform_exit(void) +{ +#if 0 + release_mem_region(JAZZ_KEYBOARD_ADDRESS, sizeof(struct hpc_keyb)); +#endif +} + +#endif /* _I8042_IP22_H */ diff --git a/drivers/input/serio/i8042-jazzio.h b/drivers/input/serio/i8042-jazzio.h new file mode 100644 index 000000000000..5c20ab131488 --- /dev/null +++ b/drivers/input/serio/i8042-jazzio.h @@ -0,0 +1,69 @@ +#ifndef _I8042_JAZZ_H +#define _I8042_JAZZ_H + +#include <asm/jazz.h> + +/* + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +/* + * Names. + */ + +#define I8042_KBD_PHYS_DESC "R4030/serio0" +#define I8042_AUX_PHYS_DESC "R4030/serio1" +#define I8042_MUX_PHYS_DESC "R4030/serio%d" + +/* + * IRQs. + */ + +#define I8042_KBD_IRQ JAZZ_KEYBOARD_IRQ +#define I8042_AUX_IRQ JAZZ_MOUSE_IRQ + +#define I8042_COMMAND_REG ((unsigned long)&jazz_kh->command) +#define I8042_STATUS_REG ((unsigned long)&jazz_kh->command) +#define I8042_DATA_REG ((unsigned long)&jazz_kh->data) + +static inline int i8042_read_data(void) +{ + return jazz_kh->data; +} + +static inline int i8042_read_status(void) +{ + return jazz_kh->command; +} + +static inline void i8042_write_data(int val) +{ + jazz_kh->data = val; +} + +static inline void i8042_write_command(int val) +{ + jazz_kh->command = val; +} + +static inline int i8042_platform_init(void) +{ +#if 0 + /* XXX JAZZ_KEYBOARD_ADDRESS is a virtual address */ + if (!request_mem_region(JAZZ_KEYBOARD_ADDRESS, 2, "i8042")) + return 1; +#endif + + return 0; +} + +static inline void i8042_platform_exit(void) +{ +#if 0 + release_mem_region(JAZZ_KEYBOARD_ADDRESS, 2); +#endif +} + +#endif /* _I8042_JAZZ_H */ diff --git a/drivers/input/serio/i8042-ppcio.h b/drivers/input/serio/i8042-ppcio.h new file mode 100644 index 000000000000..2906e1b60c04 --- /dev/null +++ b/drivers/input/serio/i8042-ppcio.h @@ -0,0 +1,136 @@ +#ifndef _I8042_PPCIO_H +#define _I8042_PPCIO_H + +/* + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +#if defined(CONFIG_WALNUT) + +#define I8042_KBD_IRQ 25 +#define I8042_AUX_IRQ 26 + +#define I8042_KBD_PHYS_DESC "walnutps2/serio0" +#define I8042_AUX_PHYS_DESC "walnutps2/serio1" +#define I8042_MUX_PHYS_DESC "walnutps2/serio%d" + +extern void *kb_cs; +extern void *kb_data; + +#define I8042_COMMAND_REG (*(int *)kb_cs) +#define I8042_DATA_REG (*(int *)kb_data) + +static inline int i8042_read_data(void) +{ + return readb(kb_data); +} + +static inline int i8042_read_status(void) +{ + return readb(kb_cs); +} + +static inline void i8042_write_data(int val) +{ + writeb(val, kb_data); +} + +static inline void i8042_write_command(int val) +{ + writeb(val, kb_cs); +} + +static inline int i8042_platform_init(void) +{ + i8042_reset = 1; + return 0; +} + +static inline void i8042_platform_exit(void) +{ +} + +#elif defined(CONFIG_SPRUCE) + +#define I8042_KBD_IRQ 22 +#define I8042_AUX_IRQ 21 + +#define I8042_KBD_PHYS_DESC "spruceps2/serio0" +#define I8042_AUX_PHYS_DESC "spruceps2/serio1" +#define I8042_MUX_PHYS_DESC "spruceps2/serio%d" + +#define I8042_COMMAND_REG 0xff810000 +#define I8042_DATA_REG 0xff810001 + +static inline int i8042_read_data(void) +{ + unsigned long kbd_data; + + __raw_writel(0x00000088, 0xff500008); + eieio(); + + __raw_writel(0x03000000, 0xff50000c); + eieio(); + + asm volatile("lis 7,0xff88 \n\ + lswi 6,7,0x8 \n\ + mr %0,6" + : "=r" (kbd_data) :: "6", "7"); + + __raw_writel(0x00000000, 0xff50000c); + eieio(); + + return (unsigned char)(kbd_data >> 24); +} + +static inline int i8042_read_status(void) +{ + unsigned long kbd_status; + + __raw_writel(0x00000088, 0xff500008); + eieio(); + + __raw_writel(0x03000000, 0xff50000c); + eieio(); + + asm volatile("lis 7,0xff88 \n\ + ori 7,7,0x8 \n\ + lswi 6,7,0x8 \n\ + mr %0,6" + : "=r" (kbd_status) :: "6", "7"); + + __raw_writel(0x00000000, 0xff50000c); + eieio(); + + return (unsigned char)(kbd_status >> 24); +} + +static inline void i8042_write_data(int val) +{ + *((unsigned char *)0xff810000) = (char)val; +} + +static inline void i8042_write_command(int val) +{ + *((unsigned char *)0xff810001) = (char)val; +} + +static inline int i8042_platform_init(void) +{ + i8042_reset = 1; + return 0; +} + +static inline void i8042_platform_exit(void) +{ +} + +#else + +#include "i8042-io.h" + +#endif + +#endif /* _I8042_PPCIO_H */ diff --git a/drivers/input/serio/i8042-sparcio.h b/drivers/input/serio/i8042-sparcio.h new file mode 100644 index 000000000000..da2a19812485 --- /dev/null +++ b/drivers/input/serio/i8042-sparcio.h @@ -0,0 +1,116 @@ +#ifndef _I8042_SPARCIO_H +#define _I8042_SPARCIO_H + +#include <linux/config.h> +#include <asm/io.h> + +#ifdef CONFIG_PCI +#include <asm/oplib.h> +#include <asm/ebus.h> +#endif + +static int i8042_kbd_irq = -1; +static int i8042_aux_irq = -1; +#define I8042_KBD_IRQ i8042_kbd_irq +#define I8042_AUX_IRQ i8042_aux_irq + +#define I8042_KBD_PHYS_DESC "sparcps2/serio0" +#define I8042_AUX_PHYS_DESC "sparcps2/serio1" +#define I8042_MUX_PHYS_DESC "sparcps2/serio%d" + +static void __iomem *kbd_iobase; + +#define I8042_COMMAND_REG (kbd_iobase + 0x64UL) +#define I8042_DATA_REG (kbd_iobase + 0x60UL) + +static inline int i8042_read_data(void) +{ + return readb(kbd_iobase + 0x60UL); +} + +static inline int i8042_read_status(void) +{ + return readb(kbd_iobase + 0x64UL); +} + +static inline void i8042_write_data(int val) +{ + writeb(val, kbd_iobase + 0x60UL); +} + +static inline void i8042_write_command(int val) +{ + writeb(val, kbd_iobase + 0x64UL); +} + +#define OBP_PS2KBD_NAME1 "kb_ps2" +#define OBP_PS2KBD_NAME2 "keyboard" +#define OBP_PS2MS_NAME1 "kdmouse" +#define OBP_PS2MS_NAME2 "mouse" + +static int i8042_platform_init(void) +{ +#ifndef CONFIG_PCI + return -1; +#else + char prop[128]; + int len; + + len = prom_getproperty(prom_root_node, "name", prop, sizeof(prop)); + if (len < 0) { + printk("i8042: Cannot get name property of root OBP node.\n"); + return -1; + } + if (strncmp(prop, "SUNW,JavaStation-1", len) == 0) { + /* Hardcoded values for MrCoffee. */ + i8042_kbd_irq = i8042_aux_irq = 13 | 0x20; + kbd_iobase = ioremap(0x71300060, 8); + if (!kbd_iobase) + return -1; + } else { + struct linux_ebus *ebus; + struct linux_ebus_device *edev; + struct linux_ebus_child *child; + + for_each_ebus(ebus) { + for_each_ebusdev(edev, ebus) { + if (!strcmp(edev->prom_name, "8042")) + goto edev_found; + } + } + return -1; + + edev_found: + for_each_edevchild(edev, child) { + if (!strcmp(child->prom_name, OBP_PS2KBD_NAME1) || + !strcmp(child->prom_name, OBP_PS2KBD_NAME2)) { + i8042_kbd_irq = child->irqs[0]; + kbd_iobase = + ioremap(child->resource[0].start, 8); + } + if (!strcmp(child->prom_name, OBP_PS2MS_NAME1) || + !strcmp(child->prom_name, OBP_PS2MS_NAME2)) + i8042_aux_irq = child->irqs[0]; + } + if (i8042_kbd_irq == -1 || + i8042_aux_irq == -1) { + printk("i8042: Error, 8042 device lacks both kbd and " + "mouse nodes.\n"); + return -1; + } + } + + i8042_reset = 1; + + return 0; +#endif /* CONFIG_PCI */ +} + +static inline void i8042_platform_exit(void) +{ +#ifdef CONFIG_PCI + iounmap(kbd_iobase); +#endif +} + +#endif /* _I8042_SPARCIO_H */ diff --git a/drivers/input/serio/i8042-x86ia64io.h b/drivers/input/serio/i8042-x86ia64io.h new file mode 100644 index 000000000000..f64867808fea --- /dev/null +++ b/drivers/input/serio/i8042-x86ia64io.h @@ -0,0 +1,333 @@ +#ifndef _I8042_X86IA64IO_H +#define _I8042_X86IA64IO_H + +/* + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +/* + * Names. + */ + +#define I8042_KBD_PHYS_DESC "isa0060/serio0" +#define I8042_AUX_PHYS_DESC "isa0060/serio1" +#define I8042_MUX_PHYS_DESC "isa0060/serio%d" + +/* + * IRQs. + */ + +#if defined(__ia64__) +# define I8042_MAP_IRQ(x) isa_irq_to_vector((x)) +#else +# define I8042_MAP_IRQ(x) (x) +#endif + +#define I8042_KBD_IRQ i8042_kbd_irq +#define I8042_AUX_IRQ i8042_aux_irq + +static int i8042_kbd_irq; +static int i8042_aux_irq; + +/* + * Register numbers. + */ + +#define I8042_COMMAND_REG i8042_command_reg +#define I8042_STATUS_REG i8042_command_reg +#define I8042_DATA_REG i8042_data_reg + +static int i8042_command_reg = 0x64; +static int i8042_data_reg = 0x60; + + +static inline int i8042_read_data(void) +{ + return inb(I8042_DATA_REG); +} + +static inline int i8042_read_status(void) +{ + return inb(I8042_STATUS_REG); +} + +static inline void i8042_write_data(int val) +{ + outb(val, I8042_DATA_REG); +} + +static inline void i8042_write_command(int val) +{ + outb(val, I8042_COMMAND_REG); +} + +#if defined(__i386__) + +#include <linux/dmi.h> + +static struct dmi_system_id __initdata i8042_dmi_noloop_table[] = { + { + .ident = "Compaq Proliant 8500", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Compaq"), + DMI_MATCH(DMI_PRODUCT_NAME , "ProLiant"), + DMI_MATCH(DMI_PRODUCT_VERSION, "8500"), + }, + }, + { + .ident = "Compaq Proliant DL760", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Compaq"), + DMI_MATCH(DMI_PRODUCT_NAME , "ProLiant"), + DMI_MATCH(DMI_PRODUCT_VERSION, "DL760"), + }, + }, + { } +}; + +/* + * Some Fujitsu notebooks are ahving trouble with touhcpads if + * active multiplexing mode is activated. Luckily they don't have + * external PS/2 ports so we can safely disable it. + */ +static struct dmi_system_id __initdata i8042_dmi_nomux_table[] = { + { + .ident = "Fujitsu Lifebook P7010/P7010D", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"), + DMI_MATCH(DMI_PRODUCT_NAME, "P7010"), + }, + }, + { + .ident = "Fujitsu Lifebook P5020D", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"), + DMI_MATCH(DMI_PRODUCT_NAME, "LifeBook P Series"), + }, + }, + { + .ident = "Fujitsu Lifebook S2000", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"), + DMI_MATCH(DMI_PRODUCT_NAME, "LifeBook S Series"), + }, + }, + { + .ident = "Fujitsu T70H", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"), + DMI_MATCH(DMI_PRODUCT_NAME, "FMVLT70H"), + }, + }, + { } +}; + + + +#endif + + +#ifdef CONFIG_PNP +#include <linux/pnp.h> + +static int i8042_pnp_kbd_registered; +static int i8042_pnp_aux_registered; + +static int i8042_pnp_command_reg; +static int i8042_pnp_data_reg; +static int i8042_pnp_kbd_irq; +static int i8042_pnp_aux_irq; + +static char i8042_pnp_kbd_name[32]; +static char i8042_pnp_aux_name[32]; + +static int i8042_pnp_kbd_probe(struct pnp_dev *dev, const struct pnp_device_id *did) +{ + if (pnp_port_valid(dev, 0) && pnp_port_len(dev, 0) == 1) + i8042_pnp_data_reg = pnp_port_start(dev,0); + + if (pnp_port_valid(dev, 1) && pnp_port_len(dev, 1) == 1) + i8042_pnp_command_reg = pnp_port_start(dev, 1); + + if (pnp_irq_valid(dev,0)) + i8042_pnp_kbd_irq = pnp_irq(dev, 0); + + strncpy(i8042_pnp_kbd_name, did->id, sizeof(i8042_pnp_kbd_name)); + if (strlen(pnp_dev_name(dev))) { + strncat(i8042_pnp_kbd_name, ":", sizeof(i8042_pnp_kbd_name)); + strncat(i8042_pnp_kbd_name, pnp_dev_name(dev), sizeof(i8042_pnp_kbd_name)); + } + + return 0; +} + +static int i8042_pnp_aux_probe(struct pnp_dev *dev, const struct pnp_device_id *did) +{ + if (pnp_port_valid(dev, 0) && pnp_port_len(dev, 0) == 1) + i8042_pnp_data_reg = pnp_port_start(dev,0); + + if (pnp_port_valid(dev, 1) && pnp_port_len(dev, 1) == 1) + i8042_pnp_command_reg = pnp_port_start(dev, 1); + + if (pnp_irq_valid(dev, 0)) + i8042_pnp_aux_irq = pnp_irq(dev, 0); + + strncpy(i8042_pnp_aux_name, did->id, sizeof(i8042_pnp_aux_name)); + if (strlen(pnp_dev_name(dev))) { + strncat(i8042_pnp_aux_name, ":", sizeof(i8042_pnp_aux_name)); + strncat(i8042_pnp_aux_name, pnp_dev_name(dev), sizeof(i8042_pnp_aux_name)); + } + + return 0; +} + +static struct pnp_device_id pnp_kbd_devids[] = { + { .id = "PNP0303", .driver_data = 0 }, + { .id = "PNP030b", .driver_data = 0 }, + { .id = "", }, +}; + +static struct pnp_driver i8042_pnp_kbd_driver = { + .name = "i8042 kbd", + .id_table = pnp_kbd_devids, + .probe = i8042_pnp_kbd_probe, +}; + +static struct pnp_device_id pnp_aux_devids[] = { + { .id = "PNP0f03", .driver_data = 0 }, + { .id = "PNP0f0b", .driver_data = 0 }, + { .id = "PNP0f0e", .driver_data = 0 }, + { .id = "PNP0f12", .driver_data = 0 }, + { .id = "PNP0f13", .driver_data = 0 }, + { .id = "PNP0f19", .driver_data = 0 }, + { .id = "PNP0f1c", .driver_data = 0 }, + { .id = "SYN0801", .driver_data = 0 }, + { .id = "", }, +}; + +static struct pnp_driver i8042_pnp_aux_driver = { + .name = "i8042 aux", + .id_table = pnp_aux_devids, + .probe = i8042_pnp_aux_probe, +}; + +static void i8042_pnp_exit(void) +{ + if (i8042_pnp_kbd_registered) + pnp_unregister_driver(&i8042_pnp_kbd_driver); + + if (i8042_pnp_aux_registered) + pnp_unregister_driver(&i8042_pnp_aux_driver); +} + +static int i8042_pnp_init(void) +{ + int result_kbd, result_aux; + + if (i8042_nopnp) { + printk("i8042: PNP detection disabled\n"); + return 0; + } + + if ((result_kbd = pnp_register_driver(&i8042_pnp_kbd_driver)) >= 0) + i8042_pnp_kbd_registered = 1; + if ((result_aux = pnp_register_driver(&i8042_pnp_aux_driver)) >= 0) + i8042_pnp_aux_registered = 1; + + if (result_kbd <= 0 && result_aux <= 0) { + i8042_pnp_exit(); +#if defined(__ia64__) + return -ENODEV; +#else + printk(KERN_WARNING "PNP: No PS/2 controller found. Probing ports directly.\n"); + return 0; +#endif + } + + if (((i8042_pnp_data_reg & ~0xf) == (i8042_data_reg & ~0xf) && + i8042_pnp_data_reg != i8042_data_reg) || !i8042_pnp_data_reg) { + printk(KERN_WARNING "PNP: PS/2 controller has invalid data port %#x; using default %#x\n", + i8042_pnp_data_reg, i8042_data_reg); + i8042_pnp_data_reg = i8042_data_reg; + } + + if (((i8042_pnp_command_reg & ~0xf) == (i8042_command_reg & ~0xf) && + i8042_pnp_command_reg != i8042_command_reg) || !i8042_pnp_command_reg) { + printk(KERN_WARNING "PNP: PS/2 controller has invalid command port %#x; using default %#x\n", + i8042_pnp_command_reg, i8042_command_reg); + i8042_pnp_command_reg = i8042_command_reg; + } + + if (!i8042_pnp_kbd_irq) { + printk(KERN_WARNING "PNP: PS/2 controller doesn't have KBD irq; using default %#x\n", i8042_kbd_irq); + i8042_pnp_kbd_irq = i8042_kbd_irq; + } + + if (result_aux > 0 && !i8042_pnp_aux_irq) { + printk(KERN_WARNING "PNP: PS/2 controller doesn't have AUX irq; using default %#x\n", i8042_aux_irq); + i8042_pnp_aux_irq = i8042_aux_irq; + } + +#if defined(__ia64__) + if (result_aux <= 0) + i8042_noaux = 1; +#endif + + i8042_data_reg = i8042_pnp_data_reg; + i8042_command_reg = i8042_pnp_command_reg; + i8042_kbd_irq = i8042_pnp_kbd_irq; + i8042_aux_irq = i8042_pnp_aux_irq; + + printk(KERN_INFO "PNP: PS/2 Controller [%s%s%s] at %#x,%#x irq %d%s%d\n", + i8042_pnp_kbd_name, (result_kbd > 0 && result_aux > 0) ? "," : "", i8042_pnp_aux_name, + i8042_data_reg, i8042_command_reg, i8042_kbd_irq, + (result_aux > 0) ? "," : "", i8042_aux_irq); + + return 0; +} + +#endif + +static inline int i8042_platform_init(void) +{ +/* + * On ix86 platforms touching the i8042 data register region can do really + * bad things. Because of this the region is always reserved on ix86 boxes. + * + * if (!request_region(I8042_DATA_REG, 16, "i8042")) + * return -1; + */ + + i8042_kbd_irq = I8042_MAP_IRQ(1); + i8042_aux_irq = I8042_MAP_IRQ(12); + +#ifdef CONFIG_PNP + if (i8042_pnp_init()) + return -1; +#endif + +#if defined(__ia64__) + i8042_reset = 1; +#endif + +#if defined(__i386__) + if (dmi_check_system(i8042_dmi_noloop_table)) + i8042_noloop = 1; + + if (dmi_check_system(i8042_dmi_nomux_table)) + i8042_nomux = 1; +#endif + + return 0; +} + +static inline void i8042_platform_exit(void) +{ +#ifdef CONFIG_PNP + i8042_pnp_exit(); +#endif +} + +#endif /* _I8042_X86IA64IO_H */ diff --git a/drivers/input/serio/i8042.c b/drivers/input/serio/i8042.c new file mode 100644 index 000000000000..8e63e464d361 --- /dev/null +++ b/drivers/input/serio/i8042.c @@ -0,0 +1,1116 @@ +/* + * i8042 keyboard and mouse controller driver for Linux + * + * Copyright (c) 1999-2004 Vojtech Pavlik + */ + +/* + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/interrupt.h> +#include <linux/ioport.h> +#include <linux/config.h> +#include <linux/init.h> +#include <linux/serio.h> +#include <linux/err.h> +#include <linux/rcupdate.h> + +#include <asm/io.h> + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@suse.cz>"); +MODULE_DESCRIPTION("i8042 keyboard and mouse controller driver"); +MODULE_LICENSE("GPL"); + +static unsigned int i8042_noaux; +module_param_named(noaux, i8042_noaux, bool, 0); +MODULE_PARM_DESC(noaux, "Do not probe or use AUX (mouse) port."); + +static unsigned int i8042_nomux; +module_param_named(nomux, i8042_nomux, bool, 0); +MODULE_PARM_DESC(nomux, "Do not check whether an active multiplexing conrtoller is present."); + +static unsigned int i8042_unlock; +module_param_named(unlock, i8042_unlock, bool, 0); +MODULE_PARM_DESC(unlock, "Ignore keyboard lock."); + +static unsigned int i8042_reset; +module_param_named(reset, i8042_reset, bool, 0); +MODULE_PARM_DESC(reset, "Reset controller during init and cleanup."); + +static unsigned int i8042_direct; +module_param_named(direct, i8042_direct, bool, 0); +MODULE_PARM_DESC(direct, "Put keyboard port into non-translated mode."); + +static unsigned int i8042_dumbkbd; +module_param_named(dumbkbd, i8042_dumbkbd, bool, 0); +MODULE_PARM_DESC(dumbkbd, "Pretend that controller can only read data from keyboard"); + +static unsigned int i8042_noloop; +module_param_named(noloop, i8042_noloop, bool, 0); +MODULE_PARM_DESC(noloop, "Disable the AUX Loopback command while probing for the AUX port"); + +static unsigned int i8042_blink_frequency = 500; +module_param_named(panicblink, i8042_blink_frequency, uint, 0600); +MODULE_PARM_DESC(panicblink, "Frequency with which keyboard LEDs should blink when kernel panics"); + +#ifdef CONFIG_PNP +static int i8042_nopnp; +module_param_named(nopnp, i8042_nopnp, bool, 0); +MODULE_PARM_DESC(nopnp, "Do not use PNP to detect controller settings"); +#endif + +#define DEBUG +#ifdef DEBUG +static int i8042_debug; +module_param_named(debug, i8042_debug, bool, 0600); +MODULE_PARM_DESC(debug, "Turn i8042 debugging mode on and off"); +#endif + +__obsolete_setup("i8042_noaux"); +__obsolete_setup("i8042_nomux"); +__obsolete_setup("i8042_unlock"); +__obsolete_setup("i8042_reset"); +__obsolete_setup("i8042_direct"); +__obsolete_setup("i8042_dumbkbd"); + +#include "i8042.h" + +static DEFINE_SPINLOCK(i8042_lock); + +struct i8042_port { + struct serio *serio; + int irq; + unsigned char disable; + unsigned char irqen; + unsigned char exists; + signed char mux; + char name[8]; +}; + +#define I8042_KBD_PORT_NO 0 +#define I8042_AUX_PORT_NO 1 +#define I8042_MUX_PORT_NO 2 +#define I8042_NUM_PORTS (I8042_NUM_MUX_PORTS + 2) +static struct i8042_port i8042_ports[I8042_NUM_PORTS] = { + { + .disable = I8042_CTR_KBDDIS, + .irqen = I8042_CTR_KBDINT, + .mux = -1, + .name = "KBD", + }, + { + .disable = I8042_CTR_AUXDIS, + .irqen = I8042_CTR_AUXINT, + .mux = -1, + .name = "AUX", + } +}; + +static unsigned char i8042_initial_ctr; +static unsigned char i8042_ctr; +static unsigned char i8042_mux_open; +static unsigned char i8042_mux_present; +static struct timer_list i8042_timer; +static struct platform_device *i8042_platform_device; + + +/* + * Shared IRQ's require a device pointer, but this driver doesn't support + * multiple devices + */ +#define i8042_request_irq_cookie (&i8042_timer) + +static irqreturn_t i8042_interrupt(int irq, void *dev_id, struct pt_regs *regs); + +/* + * The i8042_wait_read() and i8042_wait_write functions wait for the i8042 to + * be ready for reading values from it / writing values to it. + * Called always with i8042_lock held. + */ + +static int i8042_wait_read(void) +{ + int i = 0; + while ((~i8042_read_status() & I8042_STR_OBF) && (i < I8042_CTL_TIMEOUT)) { + udelay(50); + i++; + } + return -(i == I8042_CTL_TIMEOUT); +} + +static int i8042_wait_write(void) +{ + int i = 0; + while ((i8042_read_status() & I8042_STR_IBF) && (i < I8042_CTL_TIMEOUT)) { + udelay(50); + i++; + } + return -(i == I8042_CTL_TIMEOUT); +} + +/* + * i8042_flush() flushes all data that may be in the keyboard and mouse buffers + * of the i8042 down the toilet. + */ + +static int i8042_flush(void) +{ + unsigned long flags; + unsigned char data, str; + int i = 0; + + spin_lock_irqsave(&i8042_lock, flags); + + while (((str = i8042_read_status()) & I8042_STR_OBF) && (i < I8042_BUFFER_SIZE)) { + udelay(50); + data = i8042_read_data(); + i++; + dbg("%02x <- i8042 (flush, %s)", data, + str & I8042_STR_AUXDATA ? "aux" : "kbd"); + } + + spin_unlock_irqrestore(&i8042_lock, flags); + + return i; +} + +/* + * i8042_command() executes a command on the i8042. It also sends the input + * parameter(s) of the commands to it, and receives the output value(s). The + * parameters are to be stored in the param array, and the output is placed + * into the same array. The number of the parameters and output values is + * encoded in bits 8-11 of the command number. + */ + +static int i8042_command(unsigned char *param, int command) +{ + unsigned long flags; + int retval = 0, i = 0; + + if (i8042_noloop && command == I8042_CMD_AUX_LOOP) + return -1; + + spin_lock_irqsave(&i8042_lock, flags); + + retval = i8042_wait_write(); + if (!retval) { + dbg("%02x -> i8042 (command)", command & 0xff); + i8042_write_command(command & 0xff); + } + + if (!retval) + for (i = 0; i < ((command >> 12) & 0xf); i++) { + if ((retval = i8042_wait_write())) break; + dbg("%02x -> i8042 (parameter)", param[i]); + i8042_write_data(param[i]); + } + + if (!retval) + for (i = 0; i < ((command >> 8) & 0xf); i++) { + if ((retval = i8042_wait_read())) break; + if (i8042_read_status() & I8042_STR_AUXDATA) + param[i] = ~i8042_read_data(); + else + param[i] = i8042_read_data(); + dbg("%02x <- i8042 (return)", param[i]); + } + + spin_unlock_irqrestore(&i8042_lock, flags); + + if (retval) + dbg(" -- i8042 (timeout)"); + + return retval; +} + +/* + * i8042_kbd_write() sends a byte out through the keyboard interface. + */ + +static int i8042_kbd_write(struct serio *port, unsigned char c) +{ + unsigned long flags; + int retval = 0; + + spin_lock_irqsave(&i8042_lock, flags); + + if(!(retval = i8042_wait_write())) { + dbg("%02x -> i8042 (kbd-data)", c); + i8042_write_data(c); + } + + spin_unlock_irqrestore(&i8042_lock, flags); + + return retval; +} + +/* + * i8042_aux_write() sends a byte out through the aux interface. + */ + +static int i8042_aux_write(struct serio *serio, unsigned char c) +{ + struct i8042_port *port = serio->port_data; + int retval; + +/* + * Send the byte out. + */ + + if (port->mux == -1) + retval = i8042_command(&c, I8042_CMD_AUX_SEND); + else + retval = i8042_command(&c, I8042_CMD_MUX_SEND + port->mux); + +/* + * Make sure the interrupt happens and the character is received even + * in the case the IRQ isn't wired, so that we can receive further + * characters later. + */ + + i8042_interrupt(0, NULL, NULL); + return retval; +} + +/* + * i8042_activate_port() enables port on a chip. + */ + +static int i8042_activate_port(struct i8042_port *port) +{ + if (!port->serio) + return -1; + + i8042_flush(); + + /* + * Enable port again here because it is disabled if we are + * resuming (normally it is enabled already). + */ + i8042_ctr &= ~port->disable; + + i8042_ctr |= port->irqen; + + if (i8042_command(&i8042_ctr, I8042_CMD_CTL_WCTR)) { + i8042_ctr &= ~port->irqen; + return -1; + } + + return 0; +} + + +/* + * i8042_open() is called when a port is open by the higher layer. + * It allocates the interrupt and calls i8042_enable_port. + */ + +static int i8042_open(struct serio *serio) +{ + struct i8042_port *port = serio->port_data; + + if (port->mux != -1) + if (i8042_mux_open++) + return 0; + + if (request_irq(port->irq, i8042_interrupt, + SA_SHIRQ, "i8042", i8042_request_irq_cookie)) { + printk(KERN_ERR "i8042.c: Can't get irq %d for %s, unregistering the port.\n", port->irq, port->name); + goto irq_fail; + } + + if (i8042_activate_port(port)) { + printk(KERN_ERR "i8042.c: Can't activate %s, unregistering the port\n", port->name); + goto activate_fail; + } + + i8042_interrupt(0, NULL, NULL); + + return 0; + +activate_fail: + free_irq(port->irq, i8042_request_irq_cookie); + +irq_fail: + serio_unregister_port_delayed(serio); + + return -1; +} + +/* + * i8042_close() frees the interrupt, so that it can possibly be used + * by another driver. We never know - if the user doesn't have a mouse, + * the BIOS could have used the AUX interrupt for PCI. + */ + +static void i8042_close(struct serio *serio) +{ + struct i8042_port *port = serio->port_data; + + if (port->mux != -1) + if (--i8042_mux_open) + return; + + i8042_ctr &= ~port->irqen; + + if (i8042_command(&i8042_ctr, I8042_CMD_CTL_WCTR)) { + printk(KERN_WARNING "i8042.c: Can't write CTR while closing %s.\n", port->name); +/* + * We still want to continue and free IRQ so if more data keeps coming in + * kernel will just ignore the irq. + */ + } + + free_irq(port->irq, i8042_request_irq_cookie); + + i8042_flush(); +} + +/* + * i8042_start() is called by serio core when port is about to finish + * registering. It will mark port as existing so i8042_interrupt can + * start sending data through it. + */ +static int i8042_start(struct serio *serio) +{ + struct i8042_port *port = serio->port_data; + + port->exists = 1; + mb(); + return 0; +} + +/* + * i8042_stop() marks serio port as non-existing so i8042_interrupt + * will not try to send data to the port that is about to go away. + * The function is called by serio core as part of unregister procedure. + */ +static void i8042_stop(struct serio *serio) +{ + struct i8042_port *port = serio->port_data; + + port->exists = 0; + synchronize_kernel(); + port->serio = NULL; +} + +/* + * i8042_interrupt() is the most important function in this driver - + * it handles the interrupts from the i8042, and sends incoming bytes + * to the upper layers. + */ + +static irqreturn_t i8042_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + struct i8042_port *port; + unsigned long flags; + unsigned char str, data; + unsigned int dfl; + unsigned int port_no; + int ret; + + mod_timer(&i8042_timer, jiffies + I8042_POLL_PERIOD); + + spin_lock_irqsave(&i8042_lock, flags); + str = i8042_read_status(); + if (unlikely(~str & I8042_STR_OBF)) { + spin_unlock_irqrestore(&i8042_lock, flags); + if (irq) dbg("Interrupt %d, without any data", irq); + ret = 0; + goto out; + } + data = i8042_read_data(); + spin_unlock_irqrestore(&i8042_lock, flags); + + if (i8042_mux_present && (str & I8042_STR_AUXDATA)) { + static unsigned long last_transmit; + static unsigned char last_str; + + dfl = 0; + if (str & I8042_STR_MUXERR) { + dbg("MUX error, status is %02x, data is %02x", str, data); + switch (data) { + default: +/* + * When MUXERR condition is signalled the data register can only contain + * 0xfd, 0xfe or 0xff if implementation follows the spec. Unfortunately + * it is not always the case. Some KBC just get confused which port the + * data came from and signal error leaving the data intact. They _do not_ + * revert to legacy mode (actually I've never seen KBC reverting to legacy + * mode yet, when we see one we'll add proper handling). + * Anyway, we will assume that the data came from the same serio last byte + * was transmitted (if transmission happened not too long ago). + */ + if (time_before(jiffies, last_transmit + HZ/10)) { + str = last_str; + break; + } + /* fall through - report timeout */ + case 0xfd: + case 0xfe: dfl = SERIO_TIMEOUT; data = 0xfe; break; + case 0xff: dfl = SERIO_PARITY; data = 0xfe; break; + } + } + + port_no = I8042_MUX_PORT_NO + ((str >> 6) & 3); + last_str = str; + last_transmit = jiffies; + } else { + + dfl = ((str & I8042_STR_PARITY) ? SERIO_PARITY : 0) | + ((str & I8042_STR_TIMEOUT) ? SERIO_TIMEOUT : 0); + + port_no = (str & I8042_STR_AUXDATA) ? + I8042_AUX_PORT_NO : I8042_KBD_PORT_NO; + } + + port = &i8042_ports[port_no]; + + dbg("%02x <- i8042 (interrupt, %s, %d%s%s)", + data, port->name, irq, + dfl & SERIO_PARITY ? ", bad parity" : "", + dfl & SERIO_TIMEOUT ? ", timeout" : ""); + + if (likely(port->exists)) + serio_interrupt(port->serio, data, dfl, regs); + + ret = 1; +out: + return IRQ_RETVAL(ret); +} + +/* + * i8042_set_mux_mode checks whether the controller has an active + * multiplexor and puts the chip into Multiplexed (1) or Legacy (0) mode. + */ + +static int i8042_set_mux_mode(unsigned int mode, unsigned char *mux_version) +{ + + unsigned char param; +/* + * Get rid of bytes in the queue. + */ + + i8042_flush(); + +/* + * Internal loopback test - send three bytes, they should come back from the + * mouse interface, the last should be version. Note that we negate mouseport + * command responses for the i8042_check_aux() routine. + */ + + param = 0xf0; + if (i8042_command(¶m, I8042_CMD_AUX_LOOP) || param != 0x0f) + return -1; + param = mode ? 0x56 : 0xf6; + if (i8042_command(¶m, I8042_CMD_AUX_LOOP) || param != (mode ? 0xa9 : 0x09)) + return -1; + param = mode ? 0xa4 : 0xa5; + if (i8042_command(¶m, I8042_CMD_AUX_LOOP) || param == (mode ? 0x5b : 0x5a)) + return -1; + + if (mux_version) + *mux_version = ~param; + + return 0; +} + + +/* + * i8042_enable_mux_ports enables 4 individual AUX ports after + * the controller has been switched into Multiplexed mode + */ + +static int i8042_enable_mux_ports(void) +{ + unsigned char param; + int i; +/* + * Disable all muxed ports by disabling AUX. + */ + + i8042_ctr |= I8042_CTR_AUXDIS; + i8042_ctr &= ~I8042_CTR_AUXINT; + + if (i8042_command(&i8042_ctr, I8042_CMD_CTL_WCTR)) { + printk(KERN_ERR "i8042.c: Failed to disable AUX port, can't use MUX.\n"); + return -1; + } + +/* + * Enable all muxed ports. + */ + + for (i = 0; i < 4; i++) { + i8042_command(¶m, I8042_CMD_MUX_PFX + i); + i8042_command(¶m, I8042_CMD_AUX_ENABLE); + } + + return 0; +} + + +/* + * i8042_check_mux() checks whether the controller supports the PS/2 Active + * Multiplexing specification by Synaptics, Phoenix, Insyde and + * LCS/Telegraphics. + */ + +static int __init i8042_check_mux(void) +{ + unsigned char mux_version; + + if (i8042_set_mux_mode(1, &mux_version)) + return -1; + + /* Workaround for interference with USB Legacy emulation */ + /* that causes a v10.12 MUX to be found. */ + if (mux_version == 0xAC) + return -1; + + printk(KERN_INFO "i8042.c: Detected active multiplexing controller, rev %d.%d.\n", + (mux_version >> 4) & 0xf, mux_version & 0xf); + + if (i8042_enable_mux_ports()) + return -1; + + i8042_mux_present = 1; + return 0; +} + + +/* + * i8042_check_aux() applies as much paranoia as it can at detecting + * the presence of an AUX interface. + */ + +static int __init i8042_check_aux(void) +{ + unsigned char param; + static int i8042_check_aux_cookie; + +/* + * Check if AUX irq is available. If it isn't, then there is no point + * in trying to detect AUX presence. + */ + + if (request_irq(i8042_ports[I8042_AUX_PORT_NO].irq, i8042_interrupt, + SA_SHIRQ, "i8042", &i8042_check_aux_cookie)) + return -1; + free_irq(i8042_ports[I8042_AUX_PORT_NO].irq, &i8042_check_aux_cookie); + +/* + * Get rid of bytes in the queue. + */ + + i8042_flush(); + +/* + * Internal loopback test - filters out AT-type i8042's. Unfortunately + * SiS screwed up and their 5597 doesn't support the LOOP command even + * though it has an AUX port. + */ + + param = 0x5a; + if (i8042_command(¶m, I8042_CMD_AUX_LOOP) || param != 0xa5) { + +/* + * External connection test - filters out AT-soldered PS/2 i8042's + * 0x00 - no error, 0x01-0x03 - clock/data stuck, 0xff - general error + * 0xfa - no error on some notebooks which ignore the spec + * Because it's common for chipsets to return error on perfectly functioning + * AUX ports, we test for this only when the LOOP command failed. + */ + + if (i8042_command(¶m, I8042_CMD_AUX_TEST) + || (param && param != 0xfa && param != 0xff)) + return -1; + } + +/* + * Bit assignment test - filters out PS/2 i8042's in AT mode + */ + + if (i8042_command(¶m, I8042_CMD_AUX_DISABLE)) + return -1; + if (i8042_command(¶m, I8042_CMD_CTL_RCTR) || (~param & I8042_CTR_AUXDIS)) { + printk(KERN_WARNING "Failed to disable AUX port, but continuing anyway... Is this a SiS?\n"); + printk(KERN_WARNING "If AUX port is really absent please use the 'i8042.noaux' option.\n"); + } + + if (i8042_command(¶m, I8042_CMD_AUX_ENABLE)) + return -1; + if (i8042_command(¶m, I8042_CMD_CTL_RCTR) || (param & I8042_CTR_AUXDIS)) + return -1; + +/* + * Disable the interface. + */ + + i8042_ctr |= I8042_CTR_AUXDIS; + i8042_ctr &= ~I8042_CTR_AUXINT; + + if (i8042_command(&i8042_ctr, I8042_CMD_CTL_WCTR)) + return -1; + + return 0; +} + + +/* + * i8042_port_register() marks the device as existing, + * registers it, and reports to the user. + */ + +static int __init i8042_port_register(struct i8042_port *port) +{ + i8042_ctr &= ~port->disable; + + if (i8042_command(&i8042_ctr, I8042_CMD_CTL_WCTR)) { + printk(KERN_WARNING "i8042.c: Can't write CTR while registering.\n"); + kfree(port->serio); + port->serio = NULL; + i8042_ctr |= port->disable; + return -1; + } + + printk(KERN_INFO "serio: i8042 %s port at %#lx,%#lx irq %d\n", + port->name, + (unsigned long) I8042_DATA_REG, + (unsigned long) I8042_COMMAND_REG, + port->irq); + + serio_register_port(port->serio); + + return 0; +} + + +static void i8042_timer_func(unsigned long data) +{ + i8042_interrupt(0, NULL, NULL); +} + + +/* + * i8042_controller init initializes the i8042 controller, and, + * most importantly, sets it into non-xlated mode if that's + * desired. + */ + +static int i8042_controller_init(void) +{ + unsigned long flags; + +/* + * Test the i8042. We need to know if it thinks it's working correctly + * before doing anything else. + */ + + if (i8042_flush() == I8042_BUFFER_SIZE) { + printk(KERN_ERR "i8042.c: No controller found.\n"); + return -1; + } + + if (i8042_reset) { + + unsigned char param; + + if (i8042_command(¶m, I8042_CMD_CTL_TEST)) { + printk(KERN_ERR "i8042.c: i8042 controller self test timeout.\n"); + return -1; + } + + if (param != I8042_RET_CTL_TEST) { + printk(KERN_ERR "i8042.c: i8042 controller selftest failed. (%#x != %#x)\n", + param, I8042_RET_CTL_TEST); + return -1; + } + } + +/* + * Save the CTR for restoral on unload / reboot. + */ + + if (i8042_command(&i8042_ctr, I8042_CMD_CTL_RCTR)) { + printk(KERN_ERR "i8042.c: Can't read CTR while initializing i8042.\n"); + return -1; + } + + i8042_initial_ctr = i8042_ctr; + +/* + * Disable the keyboard interface and interrupt. + */ + + i8042_ctr |= I8042_CTR_KBDDIS; + i8042_ctr &= ~I8042_CTR_KBDINT; + +/* + * Handle keylock. + */ + + spin_lock_irqsave(&i8042_lock, flags); + if (~i8042_read_status() & I8042_STR_KEYLOCK) { + if (i8042_unlock) + i8042_ctr |= I8042_CTR_IGNKEYLOCK; + else + printk(KERN_WARNING "i8042.c: Warning: Keylock active.\n"); + } + spin_unlock_irqrestore(&i8042_lock, flags); + +/* + * If the chip is configured into nontranslated mode by the BIOS, don't + * bother enabling translating and be happy. + */ + + if (~i8042_ctr & I8042_CTR_XLATE) + i8042_direct = 1; + +/* + * Set nontranslated mode for the kbd interface if requested by an option. + * After this the kbd interface becomes a simple serial in/out, like the aux + * interface is. We don't do this by default, since it can confuse notebook + * BIOSes. + */ + + if (i8042_direct) + i8042_ctr &= ~I8042_CTR_XLATE; + +/* + * Write CTR back. + */ + + if (i8042_command(&i8042_ctr, I8042_CMD_CTL_WCTR)) { + printk(KERN_ERR "i8042.c: Can't write CTR while initializing i8042.\n"); + return -1; + } + + return 0; +} + + +/* + * Reset the controller. + */ +static void i8042_controller_reset(void) +{ + unsigned char param; + +/* + * Reset the controller if requested. + */ + + if (i8042_reset) + if (i8042_command(¶m, I8042_CMD_CTL_TEST)) + printk(KERN_ERR "i8042.c: i8042 controller reset timeout.\n"); + +/* + * Disable MUX mode if present. + */ + + if (i8042_mux_present) + i8042_set_mux_mode(0, NULL); + +/* + * Restore the original control register setting. + */ + + i8042_ctr = i8042_initial_ctr; + + if (i8042_command(&i8042_ctr, I8042_CMD_CTL_WCTR)) + printk(KERN_WARNING "i8042.c: Can't restore CTR.\n"); +} + + +/* + * Here we try to reset everything back to a state in which the BIOS will be + * able to talk to the hardware when rebooting. + */ + +static void i8042_controller_cleanup(void) +{ + int i; + + i8042_flush(); + +/* + * Reset anything that is connected to the ports. + */ + + for (i = 0; i < I8042_NUM_PORTS; i++) + if (i8042_ports[i].exists) + serio_cleanup(i8042_ports[i].serio); + + i8042_controller_reset(); +} + + +/* + * i8042_panic_blink() will flash the keyboard LEDs and is called when + * kernel panics. Flashing LEDs is useful for users running X who may + * not see the console and will help distingushing panics from "real" + * lockups. + * + * Note that DELAY has a limit of 10ms so we will not get stuck here + * waiting for KBC to free up even if KBD interrupt is off + */ + +#define DELAY do { mdelay(1); if (++delay > 10) return delay; } while(0) + +static long i8042_panic_blink(long count) +{ + long delay = 0; + static long last_blink; + static char led; + + /* + * We expect frequency to be about 1/2s. KDB uses about 1s. + * Make sure they are different. + */ + if (!i8042_blink_frequency) + return 0; + if (count - last_blink < i8042_blink_frequency) + return 0; + + led ^= 0x01 | 0x04; + while (i8042_read_status() & I8042_STR_IBF) + DELAY; + i8042_write_data(0xed); /* set leds */ + DELAY; + while (i8042_read_status() & I8042_STR_IBF) + DELAY; + DELAY; + i8042_write_data(led); + DELAY; + last_blink = count; + return delay; +} + +#undef DELAY + +/* + * Here we try to restore the original BIOS settings + */ + +static int i8042_suspend(struct device *dev, pm_message_t state, u32 level) +{ + if (level == SUSPEND_DISABLE) { + del_timer_sync(&i8042_timer); + i8042_controller_reset(); + } + + return 0; +} + + +/* + * Here we try to reset everything back to a state in which suspended + */ + +static int i8042_resume(struct device *dev, u32 level) +{ + int i; + + if (level != RESUME_ENABLE) + return 0; + + if (i8042_controller_init()) { + printk(KERN_ERR "i8042: resume failed\n"); + return -1; + } + + if (i8042_mux_present) + if (i8042_set_mux_mode(1, NULL) || i8042_enable_mux_ports()) + printk(KERN_WARNING "i8042: failed to resume active multiplexor, mouse won't work.\n"); + +/* + * Activate all ports. + */ + + for (i = 0; i < I8042_NUM_PORTS; i++) + i8042_activate_port(&i8042_ports[i]); + +/* + * Restart timer (for polling "stuck" data) + */ + mod_timer(&i8042_timer, jiffies + I8042_POLL_PERIOD); + + panic_blink = i8042_panic_blink; + + return 0; + +} + +/* + * We need to reset the 8042 back to original mode on system shutdown, + * because otherwise BIOSes will be confused. + */ + +static void i8042_shutdown(struct device *dev) +{ + i8042_controller_cleanup(); +} + +static struct device_driver i8042_driver = { + .name = "i8042", + .bus = &platform_bus_type, + .suspend = i8042_suspend, + .resume = i8042_resume, + .shutdown = i8042_shutdown, +}; + +static void __init i8042_create_kbd_port(void) +{ + struct serio *serio; + struct i8042_port *port = &i8042_ports[I8042_KBD_PORT_NO]; + + serio = kmalloc(sizeof(struct serio), GFP_KERNEL); + if (serio) { + memset(serio, 0, sizeof(struct serio)); + serio->id.type = i8042_direct ? SERIO_8042 : SERIO_8042_XL; + serio->write = i8042_dumbkbd ? NULL : i8042_kbd_write; + serio->open = i8042_open; + serio->close = i8042_close; + serio->start = i8042_start; + serio->stop = i8042_stop; + serio->port_data = port; + serio->dev.parent = &i8042_platform_device->dev; + strlcpy(serio->name, "i8042 Kbd Port", sizeof(serio->name)); + strlcpy(serio->phys, I8042_KBD_PHYS_DESC, sizeof(serio->phys)); + + port->serio = serio; + i8042_port_register(port); + } +} + +static void __init i8042_create_aux_port(void) +{ + struct serio *serio; + struct i8042_port *port = &i8042_ports[I8042_AUX_PORT_NO]; + + serio = kmalloc(sizeof(struct serio), GFP_KERNEL); + if (serio) { + memset(serio, 0, sizeof(struct serio)); + serio->id.type = SERIO_8042; + serio->write = i8042_aux_write; + serio->open = i8042_open; + serio->close = i8042_close; + serio->start = i8042_start; + serio->stop = i8042_stop; + serio->port_data = port; + serio->dev.parent = &i8042_platform_device->dev; + strlcpy(serio->name, "i8042 Aux Port", sizeof(serio->name)); + strlcpy(serio->phys, I8042_AUX_PHYS_DESC, sizeof(serio->phys)); + + port->serio = serio; + i8042_port_register(port); + } +} + +static void __init i8042_create_mux_port(int index) +{ + struct serio *serio; + struct i8042_port *port = &i8042_ports[I8042_MUX_PORT_NO + index]; + + serio = kmalloc(sizeof(struct serio), GFP_KERNEL); + if (serio) { + memset(serio, 0, sizeof(struct serio)); + serio->id.type = SERIO_8042; + serio->write = i8042_aux_write; + serio->open = i8042_open; + serio->close = i8042_close; + serio->start = i8042_start; + serio->stop = i8042_stop; + serio->port_data = port; + serio->dev.parent = &i8042_platform_device->dev; + snprintf(serio->name, sizeof(serio->name), "i8042 Aux-%d Port", index); + snprintf(serio->phys, sizeof(serio->phys), I8042_MUX_PHYS_DESC, index + 1); + + *port = i8042_ports[I8042_AUX_PORT_NO]; + port->exists = 0; + snprintf(port->name, sizeof(port->name), "AUX%d", index); + port->mux = index; + port->serio = serio; + i8042_port_register(port); + } +} + +static int __init i8042_init(void) +{ + int i; + int err; + + dbg_init(); + + init_timer(&i8042_timer); + i8042_timer.function = i8042_timer_func; + + if (i8042_platform_init()) + return -EBUSY; + + i8042_ports[I8042_AUX_PORT_NO].irq = I8042_AUX_IRQ; + i8042_ports[I8042_KBD_PORT_NO].irq = I8042_KBD_IRQ; + + if (i8042_controller_init()) { + i8042_platform_exit(); + return -ENODEV; + } + + err = driver_register(&i8042_driver); + if (err) { + i8042_platform_exit(); + return err; + } + + i8042_platform_device = platform_device_register_simple("i8042", -1, NULL, 0); + if (IS_ERR(i8042_platform_device)) { + driver_unregister(&i8042_driver); + i8042_platform_exit(); + return PTR_ERR(i8042_platform_device); + } + + if (!i8042_noaux && !i8042_check_aux()) { + if (!i8042_nomux && !i8042_check_mux()) + for (i = 0; i < I8042_NUM_MUX_PORTS; i++) + i8042_create_mux_port(i); + else + i8042_create_aux_port(); + } + + i8042_create_kbd_port(); + + mod_timer(&i8042_timer, jiffies + I8042_POLL_PERIOD); + + return 0; +} + +static void __exit i8042_exit(void) +{ + int i; + + i8042_controller_cleanup(); + + for (i = 0; i < I8042_NUM_PORTS; i++) + if (i8042_ports[i].exists) + serio_unregister_port(i8042_ports[i].serio); + + del_timer_sync(&i8042_timer); + + platform_device_unregister(i8042_platform_device); + driver_unregister(&i8042_driver); + + i8042_platform_exit(); + + panic_blink = NULL; +} + +module_init(i8042_init); +module_exit(i8042_exit); diff --git a/drivers/input/serio/i8042.h b/drivers/input/serio/i8042.h new file mode 100644 index 000000000000..13835039a2a7 --- /dev/null +++ b/drivers/input/serio/i8042.h @@ -0,0 +1,133 @@ +#ifndef _I8042_H +#define _I8042_H + +#include <linux/config.h> + +/* + * Copyright (c) 1999-2002 Vojtech Pavlik + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +/* + * Arch-dependent inline functions and defines. + */ + +#if defined(CONFIG_MACH_JAZZ) +#include "i8042-jazzio.h" +#elif defined(CONFIG_SGI_IP22) +#include "i8042-ip22io.h" +#elif defined(CONFIG_PPC) +#include "i8042-ppcio.h" +#elif defined(CONFIG_SPARC32) || defined(CONFIG_SPARC64) +#include "i8042-sparcio.h" +#elif defined(CONFIG_X86) || defined(CONFIG_IA64) +#include "i8042-x86ia64io.h" +#else +#include "i8042-io.h" +#endif + +/* + * This is in 50us units, the time we wait for the i8042 to react. This + * has to be long enough for the i8042 itself to timeout on sending a byte + * to a non-existent mouse. + */ + +#define I8042_CTL_TIMEOUT 10000 + +/* + * When the device isn't opened and it's interrupts aren't used, we poll it at + * regular intervals to see if any characters arrived. If yes, we can start + * probing for any mouse / keyboard connected. This is the period of the + * polling. + */ + +#define I8042_POLL_PERIOD HZ/20 + +/* + * Status register bits. + */ + +#define I8042_STR_PARITY 0x80 +#define I8042_STR_TIMEOUT 0x40 +#define I8042_STR_AUXDATA 0x20 +#define I8042_STR_KEYLOCK 0x10 +#define I8042_STR_CMDDAT 0x08 +#define I8042_STR_MUXERR 0x04 +#define I8042_STR_IBF 0x02 +#define I8042_STR_OBF 0x01 + +/* + * Control register bits. + */ + +#define I8042_CTR_KBDINT 0x01 +#define I8042_CTR_AUXINT 0x02 +#define I8042_CTR_IGNKEYLOCK 0x08 +#define I8042_CTR_KBDDIS 0x10 +#define I8042_CTR_AUXDIS 0x20 +#define I8042_CTR_XLATE 0x40 + +/* + * Commands. + */ + +#define I8042_CMD_CTL_RCTR 0x0120 +#define I8042_CMD_CTL_WCTR 0x1060 +#define I8042_CMD_CTL_TEST 0x01aa + +#define I8042_CMD_KBD_DISABLE 0x00ad +#define I8042_CMD_KBD_ENABLE 0x00ae +#define I8042_CMD_KBD_TEST 0x01ab +#define I8042_CMD_KBD_LOOP 0x11d2 + +#define I8042_CMD_AUX_DISABLE 0x00a7 +#define I8042_CMD_AUX_ENABLE 0x00a8 +#define I8042_CMD_AUX_TEST 0x01a9 +#define I8042_CMD_AUX_SEND 0x10d4 +#define I8042_CMD_AUX_LOOP 0x11d3 + +#define I8042_CMD_MUX_PFX 0x0090 +#define I8042_CMD_MUX_SEND 0x1090 + +/* + * Return codes. + */ + +#define I8042_RET_CTL_TEST 0x55 + +/* + * Expected maximum internal i8042 buffer size. This is used for flushing + * the i8042 buffers. + */ + +#define I8042_BUFFER_SIZE 16 + +/* + * Number of AUX ports on controllers supporting active multiplexing + * specification + */ + +#define I8042_NUM_MUX_PORTS 4 + +/* + * Debug. + */ + +#ifdef DEBUG +static unsigned long i8042_start_time; +#define dbg_init() do { i8042_start_time = jiffies; } while (0) +#define dbg(format, arg...) \ + do { \ + if (i8042_debug) \ + printk(KERN_DEBUG __FILE__ ": " format " [%d]\n" , \ + ## arg, (int) (jiffies - i8042_start_time)); \ + } while (0) +#else +#define dbg_init() do { } while (0) +#define dbg(format, arg...) do {} while (0) +#endif + +#endif /* _I8042_H */ diff --git a/drivers/input/serio/libps2.c b/drivers/input/serio/libps2.c new file mode 100644 index 000000000000..c978657068c5 --- /dev/null +++ b/drivers/input/serio/libps2.c @@ -0,0 +1,305 @@ +/* + * PS/2 driver library + * + * Copyright (c) 1999-2002 Vojtech Pavlik + * Copyright (c) 2004 Dmitry Torokhov + */ + +/* + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/input.h> +#include <linux/serio.h> +#include <linux/init.h> +#include <linux/libps2.h> + +#define DRIVER_DESC "PS/2 driver library" + +MODULE_AUTHOR("Dmitry Torokhov <dtor@mail.ru>"); +MODULE_DESCRIPTION("PS/2 driver library"); +MODULE_LICENSE("GPL"); + +EXPORT_SYMBOL(ps2_init); +EXPORT_SYMBOL(ps2_sendbyte); +EXPORT_SYMBOL(ps2_command); +EXPORT_SYMBOL(ps2_schedule_command); +EXPORT_SYMBOL(ps2_handle_ack); +EXPORT_SYMBOL(ps2_handle_response); +EXPORT_SYMBOL(ps2_cmd_aborted); + +/* Work structure to schedule execution of a command */ +struct ps2work { + struct work_struct work; + struct ps2dev *ps2dev; + int command; + unsigned char param[0]; +}; + + +/* + * ps2_sendbyte() sends a byte to the mouse, and waits for acknowledge. + * It doesn't handle retransmission, though it could - because when there would + * be need for retransmissions, the mouse has to be replaced anyway. + * + * ps2_sendbyte() can only be called from a process context + */ + +int ps2_sendbyte(struct ps2dev *ps2dev, unsigned char byte, int timeout) +{ + serio_pause_rx(ps2dev->serio); + ps2dev->nak = 1; + ps2dev->flags |= PS2_FLAG_ACK; + serio_continue_rx(ps2dev->serio); + + if (serio_write(ps2dev->serio, byte) == 0) + wait_event_timeout(ps2dev->wait, + !(ps2dev->flags & PS2_FLAG_ACK), + msecs_to_jiffies(timeout)); + + serio_pause_rx(ps2dev->serio); + ps2dev->flags &= ~PS2_FLAG_ACK; + serio_continue_rx(ps2dev->serio); + + return -ps2dev->nak; +} + +/* + * ps2_command() sends a command and its parameters to the mouse, + * then waits for the response and puts it in the param array. + * + * ps2_command() can only be called from a process context + */ + +int ps2_command(struct ps2dev *ps2dev, unsigned char *param, int command) +{ + int timeout; + int send = (command >> 12) & 0xf; + int receive = (command >> 8) & 0xf; + int rc = -1; + int i; + + down(&ps2dev->cmd_sem); + + serio_pause_rx(ps2dev->serio); + ps2dev->flags = command == PS2_CMD_GETID ? PS2_FLAG_WAITID : 0; + ps2dev->cmdcnt = receive; + if (receive && param) + for (i = 0; i < receive; i++) + ps2dev->cmdbuf[(receive - 1) - i] = param[i]; + serio_continue_rx(ps2dev->serio); + + /* + * Some devices (Synaptics) peform the reset before + * ACKing the reset command, and so it can take a long + * time before the ACK arrrives. + */ + if (command & 0xff) + if (ps2_sendbyte(ps2dev, command & 0xff, + command == PS2_CMD_RESET_BAT ? 1000 : 200)) + goto out; + + for (i = 0; i < send; i++) + if (ps2_sendbyte(ps2dev, param[i], 200)) + goto out; + + /* + * The reset command takes a long time to execute. + */ + timeout = msecs_to_jiffies(command == PS2_CMD_RESET_BAT ? 4000 : 500); + + timeout = wait_event_timeout(ps2dev->wait, + !(ps2dev->flags & PS2_FLAG_CMD1), timeout); + + if (ps2dev->cmdcnt && timeout > 0) { + + if (command == PS2_CMD_RESET_BAT && timeout > msecs_to_jiffies(100)) { + /* + * Device has sent the first response byte + * after a reset command, reset is thus done, + * shorten the timeout. The next byte will come + * soon (keyboard) or not at all (mouse). + */ + timeout = msecs_to_jiffies(100); + } + + if (command == PS2_CMD_GETID && + ps2dev->cmdbuf[receive - 1] != 0xab && /* Regular keyboards */ + ps2dev->cmdbuf[receive - 1] != 0xac && /* NCD Sun keyboard */ + ps2dev->cmdbuf[receive - 1] != 0x2b && /* Trust keyboard, translated */ + ps2dev->cmdbuf[receive - 1] != 0x5d && /* Trust keyboard */ + ps2dev->cmdbuf[receive - 1] != 0x60 && /* NMB SGI keyboard, translated */ + ps2dev->cmdbuf[receive - 1] != 0x47) { /* NMB SGI keyboard */ + /* + * Device behind the port is not a keyboard + * so we don't need to wait for the 2nd byte + * of ID response. + */ + serio_pause_rx(ps2dev->serio); + ps2dev->flags = ps2dev->cmdcnt = 0; + serio_continue_rx(ps2dev->serio); + } + + wait_event_timeout(ps2dev->wait, + !(ps2dev->flags & PS2_FLAG_CMD), timeout); + } + + if (param) + for (i = 0; i < receive; i++) + param[i] = ps2dev->cmdbuf[(receive - 1) - i]; + + if (ps2dev->cmdcnt && (command != PS2_CMD_RESET_BAT || ps2dev->cmdcnt != 1)) + goto out; + + rc = 0; + +out: + serio_pause_rx(ps2dev->serio); + ps2dev->flags = 0; + serio_continue_rx(ps2dev->serio); + + up(&ps2dev->cmd_sem); + return rc; +} + +/* + * ps2_execute_scheduled_command() sends a command, previously scheduled by + * ps2_schedule_command(), to a PS/2 device (keyboard, mouse, etc.) + */ + +static void ps2_execute_scheduled_command(void *data) +{ + struct ps2work *ps2work = data; + + ps2_command(ps2work->ps2dev, ps2work->param, ps2work->command); + kfree(ps2work); +} + +/* + * ps2_schedule_command() allows to schedule delayed execution of a PS/2 + * command and can be used to issue a command from an interrupt or softirq + * context. + */ + +int ps2_schedule_command(struct ps2dev *ps2dev, unsigned char *param, int command) +{ + struct ps2work *ps2work; + int send = (command >> 12) & 0xf; + int receive = (command >> 8) & 0xf; + + if (!(ps2work = kmalloc(sizeof(struct ps2work) + max(send, receive), GFP_ATOMIC))) + return -1; + + memset(ps2work, 0, sizeof(struct ps2work)); + ps2work->ps2dev = ps2dev; + ps2work->command = command; + memcpy(ps2work->param, param, send); + INIT_WORK(&ps2work->work, ps2_execute_scheduled_command, ps2work); + + if (!schedule_work(&ps2work->work)) { + kfree(ps2work); + return -1; + } + + return 0; +} + +/* + * ps2_init() initializes ps2dev structure + */ + +void ps2_init(struct ps2dev *ps2dev, struct serio *serio) +{ + init_MUTEX(&ps2dev->cmd_sem); + init_waitqueue_head(&ps2dev->wait); + ps2dev->serio = serio; +} + +/* + * ps2_handle_ack() is supposed to be used in interrupt handler + * to properly process ACK/NAK of a command from a PS/2 device. + */ + +int ps2_handle_ack(struct ps2dev *ps2dev, unsigned char data) +{ + switch (data) { + case PS2_RET_ACK: + ps2dev->nak = 0; + break; + + case PS2_RET_NAK: + ps2dev->nak = 1; + break; + + /* + * Workaround for mice which don't ACK the Get ID command. + * These are valid mouse IDs that we recognize. + */ + case 0x00: + case 0x03: + case 0x04: + if (ps2dev->flags & PS2_FLAG_WAITID) { + ps2dev->nak = 0; + break; + } + /* Fall through */ + default: + return 0; + } + + + if (!ps2dev->nak && ps2dev->cmdcnt) + ps2dev->flags |= PS2_FLAG_CMD | PS2_FLAG_CMD1; + + ps2dev->flags &= ~PS2_FLAG_ACK; + wake_up(&ps2dev->wait); + + if (data != PS2_RET_ACK) + ps2_handle_response(ps2dev, data); + + return 1; +} + +/* + * ps2_handle_response() is supposed to be used in interrupt handler + * to properly store device's response to a command and notify process + * waiting for completion of the command. + */ + +int ps2_handle_response(struct ps2dev *ps2dev, unsigned char data) +{ + if (ps2dev->cmdcnt) + ps2dev->cmdbuf[--ps2dev->cmdcnt] = data; + + if (ps2dev->flags & PS2_FLAG_CMD1) { + ps2dev->flags &= ~PS2_FLAG_CMD1; + if (ps2dev->cmdcnt) + wake_up(&ps2dev->wait); + } + + if (!ps2dev->cmdcnt) { + ps2dev->flags &= ~PS2_FLAG_CMD; + wake_up(&ps2dev->wait); + } + + return 1; +} + +void ps2_cmd_aborted(struct ps2dev *ps2dev) +{ + if (ps2dev->flags & PS2_FLAG_ACK) + ps2dev->nak = 1; + + if (ps2dev->flags & (PS2_FLAG_ACK | PS2_FLAG_CMD)) + wake_up(&ps2dev->wait); + + ps2dev->flags = 0; +} + diff --git a/drivers/input/serio/maceps2.c b/drivers/input/serio/maceps2.c new file mode 100644 index 000000000000..9880fc145d90 --- /dev/null +++ b/drivers/input/serio/maceps2.c @@ -0,0 +1,176 @@ +/* + * SGI O2 MACE PS2 controller driver for linux + * + * Copyright (C) 2002 Vivien Chappelier + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation + */ +#include <linux/module.h> +#include <linux/init.h> +#include <linux/serio.h> +#include <linux/errno.h> +#include <linux/interrupt.h> +#include <linux/ioport.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/err.h> + +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/system.h> +#include <asm/ip32/mace.h> +#include <asm/ip32/ip32_ints.h> + +MODULE_AUTHOR("Vivien Chappelier <vivien.chappelier@linux-mips.org"); +MODULE_DESCRIPTION("SGI O2 MACE PS2 controller driver"); +MODULE_LICENSE("GPL"); + +#define MACE_PS2_TIMEOUT 10000 /* in 50us unit */ + +#define PS2_STATUS_CLOCK_SIGNAL BIT(0) /* external clock signal */ +#define PS2_STATUS_CLOCK_INHIBIT BIT(1) /* clken output signal */ +#define PS2_STATUS_TX_INPROGRESS BIT(2) /* transmission in progress */ +#define PS2_STATUS_TX_EMPTY BIT(3) /* empty transmit buffer */ +#define PS2_STATUS_RX_FULL BIT(4) /* full receive buffer */ +#define PS2_STATUS_RX_INPROGRESS BIT(5) /* reception in progress */ +#define PS2_STATUS_ERROR_PARITY BIT(6) /* parity error */ +#define PS2_STATUS_ERROR_FRAMING BIT(7) /* framing error */ + +#define PS2_CONTROL_TX_CLOCK_DISABLE BIT(0) /* inhibit clock signal after TX */ +#define PS2_CONTROL_TX_ENABLE BIT(1) /* transmit enable */ +#define PS2_CONTROL_TX_INT_ENABLE BIT(2) /* enable transmit interrupt */ +#define PS2_CONTROL_RX_INT_ENABLE BIT(3) /* enable receive interrupt */ +#define PS2_CONTROL_RX_CLOCK_ENABLE BIT(4) /* pause reception if set to 0 */ +#define PS2_CONTROL_RESET BIT(5) /* reset */ + +struct maceps2_data { + struct mace_ps2port *port; + int irq; +}; + +static struct maceps2_data port_data[2]; +static struct serio *maceps2_port[2]; +static struct platform_device *maceps2_device; + +static int maceps2_write(struct serio *dev, unsigned char val) +{ + struct mace_ps2port *port = ((struct maceps2_data *)dev->port_data)->port; + unsigned int timeout = MACE_PS2_TIMEOUT; + + do { + if (port->status & PS2_STATUS_TX_EMPTY) { + port->tx = val; + return 0; + } + udelay(50); + } while (timeout--); + + return -1; +} + +static irqreturn_t maceps2_interrupt(int irq, void *dev_id, + struct pt_regs *regs) +{ + struct serio *dev = dev_id; + struct mace_ps2port *port = ((struct maceps2_data *)dev->port_data)->port; + unsigned long byte; + + if (port->status & PS2_STATUS_RX_FULL) { + byte = port->rx; + serio_interrupt(dev, byte & 0xff, 0, regs); + } + + return IRQ_HANDLED; +} + +static int maceps2_open(struct serio *dev) +{ + struct maceps2_data *data = (struct maceps2_data *)dev->port_data; + + if (request_irq(data->irq, maceps2_interrupt, 0, "PS2 port", dev)) { + printk(KERN_ERR "Could not allocate PS/2 IRQ\n"); + return -EBUSY; + } + + /* Reset port */ + data->port->control = PS2_CONTROL_TX_CLOCK_DISABLE | PS2_CONTROL_RESET; + udelay(100); + + /* Enable interrupts */ + data->port->control = PS2_CONTROL_RX_CLOCK_ENABLE | + PS2_CONTROL_TX_ENABLE | + PS2_CONTROL_RX_INT_ENABLE; + + return 0; +} + +static void maceps2_close(struct serio *dev) +{ + struct maceps2_data *data = (struct maceps2_data *)dev->port_data; + + data->port->control = PS2_CONTROL_TX_CLOCK_DISABLE | PS2_CONTROL_RESET; + udelay(100); + free_irq(data->irq, dev); +} + + +static struct serio * __init maceps2_allocate_port(int idx) +{ + struct serio *serio; + + serio = kmalloc(sizeof(struct serio), GFP_KERNEL); + if (serio) { + memset(serio, 0, sizeof(struct serio)); + serio->id.type = SERIO_8042; + serio->write = maceps2_write; + serio->open = maceps2_open; + serio->close = maceps2_close; + snprintf(serio->name, sizeof(serio->name), "MACE PS/2 port%d", idx); + snprintf(serio->phys, sizeof(serio->phys), "mace/serio%d", idx); + serio->port_data = &port_data[idx]; + serio->dev.parent = &maceps2_device->dev; + } + + return serio; +} + + +static int __init maceps2_init(void) +{ + maceps2_device = platform_device_register_simple("maceps2", -1, NULL, 0); + if (IS_ERR(maceps2_device)) + return PTR_ERR(maceps2_device); + + port_data[0].port = &mace->perif.ps2.keyb; + port_data[0].irq = MACEISA_KEYB_IRQ; + port_data[1].port = &mace->perif.ps2.mouse; + port_data[1].irq = MACEISA_MOUSE_IRQ; + + maceps2_port[0] = maceps2_allocate_port(0); + maceps2_port[1] = maceps2_allocate_port(1); + if (!maceps2_port[0] || !maceps2_port[1]) { + kfree(maceps2_port[0]); + kfree(maceps2_port[1]); + platform_device_unregister(maceps2_device); + return -ENOMEM; + } + + serio_register_port(maceps2_port[0]); + serio_register_port(maceps2_port[1]); + + return 0; +} + +static void __exit maceps2_exit(void) +{ + serio_unregister_port(maceps2_port[0]); + serio_unregister_port(maceps2_port[1]); + platform_device_unregister(maceps2_device); +} + +module_init(maceps2_init); +module_exit(maceps2_exit); diff --git a/drivers/input/serio/parkbd.c b/drivers/input/serio/parkbd.c new file mode 100644 index 000000000000..1d15c2819818 --- /dev/null +++ b/drivers/input/serio/parkbd.c @@ -0,0 +1,218 @@ +/* + * Parallel port to Keyboard port adapter driver for Linux + * + * Copyright (c) 1999-2004 Vojtech Pavlik + */ + +/* + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +/* + * To connect an AT or XT keyboard to the parallel port, a fairly simple adapter + * can be made: + * + * Parallel port Keyboard port + * + * +5V --------------------- +5V (4) + * + * ______ + * +5V -------|______|--. + * | + * ACK (10) ------------| + * |--- KBD CLOCK (5) + * STROBE (1) ---|<|----' + * + * ______ + * +5V -------|______|--. + * | + * BUSY (11) -----------| + * |--- KBD DATA (1) + * AUTOFD (14) --|<|----' + * + * GND (18-25) ------------- GND (3) + * + * The diodes can be fairly any type, and the resistors should be somewhere + * around 5 kOhm, but the adapter will likely work without the resistors, + * too. + * + * The +5V source can be taken either from USB, from mouse or keyboard ports, + * or from a joystick port. Unfortunately, the parallel port of a PC doesn't + * have a +5V pin, and feeding the keyboard from signal pins is out of question + * with 300 mA power reqirement of a typical AT keyboard. + */ + +#include <linux/module.h> +#include <linux/parport.h> +#include <linux/init.h> +#include <linux/serio.h> + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION("Parallel port to Keyboard port adapter driver"); +MODULE_LICENSE("GPL"); + +static unsigned int parkbd_pp_no; +module_param_named(port, parkbd_pp_no, int, 0); +MODULE_PARM_DESC(port, "Parallel port the adapter is connected to (default is 0)"); + +static unsigned int parkbd_mode = SERIO_8042; +module_param_named(mode, parkbd_mode, uint, 0); +MODULE_PARM_DESC(mode, "Mode of operation: XT = 0/AT = 1 (default)"); + +#define PARKBD_CLOCK 0x01 /* Strobe & Ack */ +#define PARKBD_DATA 0x02 /* AutoFd & Busy */ + +static int parkbd_buffer; +static int parkbd_counter; +static unsigned long parkbd_last; +static int parkbd_writing; +static unsigned long parkbd_start; + +static struct pardevice *parkbd_dev; +static struct serio *parkbd_port; + +static int parkbd_readlines(void) +{ + return (parport_read_status(parkbd_dev->port) >> 6) ^ 2; +} + +static void parkbd_writelines(int data) +{ + parport_write_control(parkbd_dev->port, (~data & 3) | 0x10); +} + +static int parkbd_write(struct serio *port, unsigned char c) +{ + unsigned char p; + + if (!parkbd_mode) return -1; + + p = c ^ (c >> 4); + p = p ^ (p >> 2); + p = p ^ (p >> 1); + + parkbd_counter = 0; + parkbd_writing = 1; + parkbd_buffer = c | (((int) (~p & 1)) << 8) | 0x600; + + parkbd_writelines(2); + + return 0; +} + +static void parkbd_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + + if (parkbd_writing) { + + if (parkbd_counter && ((parkbd_counter == 11) || time_after(jiffies, parkbd_last + HZ/100))) { + parkbd_counter = 0; + parkbd_buffer = 0; + parkbd_writing = 0; + parkbd_writelines(3); + return; + } + + parkbd_writelines(((parkbd_buffer >> parkbd_counter++) & 1) | 2); + + if (parkbd_counter == 11) { + parkbd_counter = 0; + parkbd_buffer = 0; + parkbd_writing = 0; + parkbd_writelines(3); + } + + } else { + + if ((parkbd_counter == parkbd_mode + 10) || time_after(jiffies, parkbd_last + HZ/100)) { + parkbd_counter = 0; + parkbd_buffer = 0; + } + + parkbd_buffer |= (parkbd_readlines() >> 1) << parkbd_counter++; + + if (parkbd_counter == parkbd_mode + 10) + serio_interrupt(parkbd_port, (parkbd_buffer >> (2 - parkbd_mode)) & 0xff, 0, regs); + } + + parkbd_last = jiffies; +} + +static int parkbd_getport(void) +{ + struct parport *pp; + + pp = parport_find_number(parkbd_pp_no); + + if (pp == NULL) { + printk(KERN_ERR "parkbd: no such parport\n"); + return -ENODEV; + } + + parkbd_dev = parport_register_device(pp, "parkbd", NULL, NULL, parkbd_interrupt, PARPORT_DEV_EXCL, NULL); + parport_put_port(pp); + + if (!parkbd_dev) + return -ENODEV; + + if (parport_claim(parkbd_dev)) { + parport_unregister_device(parkbd_dev); + return -EBUSY; + } + + parkbd_start = jiffies; + + return 0; +} + +static struct serio * __init parkbd_allocate_serio(void) +{ + struct serio *serio; + + serio = kmalloc(sizeof(struct serio), GFP_KERNEL); + if (serio) { + memset(serio, 0, sizeof(struct serio)); + serio->id.type = parkbd_mode; + serio->write = parkbd_write, + strlcpy(serio->name, "PARKBD AT/XT keyboard adapter", sizeof(serio->name)); + snprintf(serio->phys, sizeof(serio->phys), "%s/serio0", parkbd_dev->port->name); + } + + return serio; +} + +static int __init parkbd_init(void) +{ + int err; + + err = parkbd_getport(); + if (err) + return err; + + parkbd_port = parkbd_allocate_serio(); + if (!parkbd_port) { + parport_release(parkbd_dev); + return -ENOMEM; + } + + parkbd_writelines(3); + + serio_register_port(parkbd_port); + + printk(KERN_INFO "serio: PARKBD %s adapter on %s\n", + parkbd_mode ? "AT" : "XT", parkbd_dev->port->name); + + return 0; +} + +static void __exit parkbd_exit(void) +{ + parport_release(parkbd_dev); + serio_unregister_port(parkbd_port); + parport_unregister_device(parkbd_dev); +} + +module_init(parkbd_init); +module_exit(parkbd_exit); diff --git a/drivers/input/serio/pcips2.c b/drivers/input/serio/pcips2.c new file mode 100644 index 000000000000..1e139c5e59de --- /dev/null +++ b/drivers/input/serio/pcips2.c @@ -0,0 +1,234 @@ +/* + * linux/drivers/input/serio/pcips2.c + * + * Copyright (C) 2003 Russell King, All Rights Reserved. + * + * 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. + * + * I'm not sure if this is a generic PS/2 PCI interface or specific to + * the Mobility Electronics docking station. + */ +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/ioport.h> +#include <linux/input.h> +#include <linux/pci.h> +#include <linux/init.h> +#include <linux/serio.h> +#include <linux/delay.h> +#include <asm/io.h> + +#define PS2_CTRL (0) +#define PS2_STATUS (1) +#define PS2_DATA (2) + +#define PS2_CTRL_CLK (1<<0) +#define PS2_CTRL_DAT (1<<1) +#define PS2_CTRL_TXIRQ (1<<2) +#define PS2_CTRL_ENABLE (1<<3) +#define PS2_CTRL_RXIRQ (1<<4) + +#define PS2_STAT_CLK (1<<0) +#define PS2_STAT_DAT (1<<1) +#define PS2_STAT_PARITY (1<<2) +#define PS2_STAT_RXFULL (1<<5) +#define PS2_STAT_TXBUSY (1<<6) +#define PS2_STAT_TXEMPTY (1<<7) + +struct pcips2_data { + struct serio *io; + unsigned int base; + struct pci_dev *dev; +}; + +static int pcips2_write(struct serio *io, unsigned char val) +{ + struct pcips2_data *ps2if = io->port_data; + unsigned int stat; + + do { + stat = inb(ps2if->base + PS2_STATUS); + cpu_relax(); + } while (!(stat & PS2_STAT_TXEMPTY)); + + outb(val, ps2if->base + PS2_DATA); + + return 0; +} + +static irqreturn_t pcips2_interrupt(int irq, void *devid, struct pt_regs *regs) +{ + struct pcips2_data *ps2if = devid; + unsigned char status, scancode; + int handled = 0; + + do { + unsigned int flag; + + status = inb(ps2if->base + PS2_STATUS); + if (!(status & PS2_STAT_RXFULL)) + break; + handled = 1; + scancode = inb(ps2if->base + PS2_DATA); + if (status == 0xff && scancode == 0xff) + break; + + flag = (status & PS2_STAT_PARITY) ? 0 : SERIO_PARITY; + + if (hweight8(scancode) & 1) + flag ^= SERIO_PARITY; + + serio_interrupt(ps2if->io, scancode, flag, regs); + } while (1); + return IRQ_RETVAL(handled); +} + +static void pcips2_flush_input(struct pcips2_data *ps2if) +{ + unsigned char status, scancode; + + do { + status = inb(ps2if->base + PS2_STATUS); + if (!(status & PS2_STAT_RXFULL)) + break; + scancode = inb(ps2if->base + PS2_DATA); + if (status == 0xff && scancode == 0xff) + break; + } while (1); +} + +static int pcips2_open(struct serio *io) +{ + struct pcips2_data *ps2if = io->port_data; + int ret, val = 0; + + outb(PS2_CTRL_ENABLE, ps2if->base); + pcips2_flush_input(ps2if); + + ret = request_irq(ps2if->dev->irq, pcips2_interrupt, SA_SHIRQ, + "pcips2", ps2if); + if (ret == 0) + val = PS2_CTRL_ENABLE | PS2_CTRL_RXIRQ; + + outb(val, ps2if->base); + + return ret; +} + +static void pcips2_close(struct serio *io) +{ + struct pcips2_data *ps2if = io->port_data; + + outb(0, ps2if->base); + + free_irq(ps2if->dev->irq, ps2if); +} + +static int __devinit pcips2_probe(struct pci_dev *dev, const struct pci_device_id *id) +{ + struct pcips2_data *ps2if; + struct serio *serio; + int ret; + + ret = pci_enable_device(dev); + if (ret) + goto out; + + ret = pci_request_regions(dev, "pcips2"); + if (ret) + goto disable; + + ps2if = kmalloc(sizeof(struct pcips2_data), GFP_KERNEL); + serio = kmalloc(sizeof(struct serio), GFP_KERNEL); + if (!ps2if || !serio) { + ret = -ENOMEM; + goto release; + } + + memset(ps2if, 0, sizeof(struct pcips2_data)); + memset(serio, 0, sizeof(struct serio)); + + serio->id.type = SERIO_8042; + serio->write = pcips2_write; + serio->open = pcips2_open; + serio->close = pcips2_close; + strlcpy(serio->name, pci_name(dev), sizeof(serio->name)); + strlcpy(serio->phys, dev->dev.bus_id, sizeof(serio->phys)); + serio->port_data = ps2if; + serio->dev.parent = &dev->dev; + ps2if->io = serio; + ps2if->dev = dev; + ps2if->base = pci_resource_start(dev, 0); + + pci_set_drvdata(dev, ps2if); + + serio_register_port(ps2if->io); + return 0; + + release: + kfree(ps2if); + kfree(serio); + pci_release_regions(dev); + disable: + pci_disable_device(dev); + out: + return ret; +} + +static void __devexit pcips2_remove(struct pci_dev *dev) +{ + struct pcips2_data *ps2if = pci_get_drvdata(dev); + + serio_unregister_port(ps2if->io); + pci_set_drvdata(dev, NULL); + kfree(ps2if); + pci_release_regions(dev); + pci_disable_device(dev); +} + +static struct pci_device_id pcips2_ids[] = { + { + .vendor = 0x14f2, /* MOBILITY */ + .device = 0x0123, /* Keyboard */ + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .class = PCI_CLASS_INPUT_KEYBOARD << 8, + .class_mask = 0xffff00, + }, + { + .vendor = 0x14f2, /* MOBILITY */ + .device = 0x0124, /* Mouse */ + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .class = PCI_CLASS_INPUT_MOUSE << 8, + .class_mask = 0xffff00, + }, + { 0, } +}; + +static struct pci_driver pcips2_driver = { + .name = "pcips2", + .id_table = pcips2_ids, + .probe = pcips2_probe, + .remove = __devexit_p(pcips2_remove), +}; + +static int __init pcips2_init(void) +{ + return pci_register_driver(&pcips2_driver); +} + +static void __exit pcips2_exit(void) +{ + pci_unregister_driver(&pcips2_driver); +} + +module_init(pcips2_init); +module_exit(pcips2_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Russell King <rmk@arm.linux.org.uk>"); +MODULE_DESCRIPTION("PCI PS/2 keyboard/mouse driver"); +MODULE_DEVICE_TABLE(pci, pcips2_ids); diff --git a/drivers/input/serio/q40kbd.c b/drivers/input/serio/q40kbd.c new file mode 100644 index 000000000000..46093c507988 --- /dev/null +++ b/drivers/input/serio/q40kbd.c @@ -0,0 +1,163 @@ +/* + * $Id: q40kbd.c,v 1.12 2002/02/02 22:26:44 vojtech Exp $ + * + * Copyright (c) 2000-2001 Vojtech Pavlik + * + * Based on the work of: + * Richard Zidlicky <Richard.Zidlicky@stud.informatik.uni-erlangen.de> + */ + +/* + * Q40 PS/2 keyboard controller driver for Linux/m68k + */ + +/* + * 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 either by + * e-mail - mail your message to <vojtech@ucw.cz>, or by paper mail: + * Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/serio.h> +#include <linux/interrupt.h> +#include <linux/err.h> +#include <linux/bitops.h> + +#include <asm/io.h> +#include <asm/uaccess.h> +#include <asm/q40_master.h> +#include <asm/irq.h> +#include <asm/q40ints.h> + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION("Q40 PS/2 keyboard controller driver"); +MODULE_LICENSE("GPL"); + +DEFINE_SPINLOCK(q40kbd_lock); +static struct serio *q40kbd_port; +static struct platform_device *q40kbd_device; + +static irqreturn_t q40kbd_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + unsigned long flags; + + spin_lock_irqsave(&q40kbd_lock, flags); + + if (Q40_IRQ_KEYB_MASK & master_inb(INTERRUPT_REG)) + serio_interrupt(q40kbd_port, master_inb(KEYCODE_REG), 0, regs); + + master_outb(-1, KEYBOARD_UNLOCK_REG); + + spin_unlock_irqrestore(&q40kbd_lock, flags); + + return IRQ_HANDLED; +} + +/* + * q40kbd_flush() flushes all data that may be in the keyboard buffers + */ + +static void q40kbd_flush(void) +{ + int maxread = 100; + unsigned long flags; + + spin_lock_irqsave(&q40kbd_lock, flags); + + while (maxread-- && (Q40_IRQ_KEYB_MASK & master_inb(INTERRUPT_REG))) + master_inb(KEYCODE_REG); + + spin_unlock_irqrestore(&q40kbd_lock, flags); +} + +/* + * q40kbd_open() is called when a port is open by the higher layer. + * It allocates the interrupt and enables in in the chip. + */ + +static int q40kbd_open(struct serio *port) +{ + q40kbd_flush(); + + if (request_irq(Q40_IRQ_KEYBOARD, q40kbd_interrupt, 0, "q40kbd", NULL)) { + printk(KERN_ERR "q40kbd.c: Can't get irq %d.\n", Q40_IRQ_KEYBOARD); + return -1; + } + + /* off we go */ + master_outb(-1, KEYBOARD_UNLOCK_REG); + master_outb(1, KEY_IRQ_ENABLE_REG); + + return 0; +} + +static void q40kbd_close(struct serio *port) +{ + master_outb(0, KEY_IRQ_ENABLE_REG); + master_outb(-1, KEYBOARD_UNLOCK_REG); + free_irq(Q40_IRQ_KEYBOARD, NULL); + + q40kbd_flush(); +} + +static struct serio * __init q40kbd_allocate_port(void) +{ + struct serio *serio; + + serio = kmalloc(sizeof(struct serio), GFP_KERNEL); + if (serio) { + memset(serio, 0, sizeof(struct serio)); + serio->id.type = SERIO_8042; + serio->open = q40kbd_open; + serio->close = q40kbd_close; + serio->dev.parent = &q40kbd_device->dev; + strlcpy(serio->name, "Q40 Kbd Port", sizeof(serio->name)); + strlcpy(serio->phys, "Q40", sizeof(serio->phys)); + } + + return serio; +} + +static int __init q40kbd_init(void) +{ + if (!MACH_IS_Q40) + return -EIO; + + q40kbd_device = platform_device_register_simple("q40kbd", -1, NULL, 0); + if (IS_ERR(q40kbd_device)) + return PTR_ERR(q40kbd_device); + + if (!(q40kbd_port = q40kbd_allocate_port())) { + platform_device_unregister(q40kbd_device); + return -ENOMEM; + } + + serio_register_port(q40kbd_port); + printk(KERN_INFO "serio: Q40 kbd registered\n"); + + return 0; +} + +static void __exit q40kbd_exit(void) +{ + serio_unregister_port(q40kbd_port); + platform_device_unregister(q40kbd_device); +} + +module_init(q40kbd_init); +module_exit(q40kbd_exit); diff --git a/drivers/input/serio/rpckbd.c b/drivers/input/serio/rpckbd.c new file mode 100644 index 000000000000..106f5eefd89a --- /dev/null +++ b/drivers/input/serio/rpckbd.c @@ -0,0 +1,156 @@ +/* + * $Id: rpckbd.c,v 1.7 2001/09/25 10:12:07 vojtech Exp $ + * + * Copyright (c) 2000-2001 Vojtech Pavlik + * Copyright (c) 2002 Russell King + */ + +/* + * Acorn RiscPC PS/2 keyboard controller driver for Linux/ARM + */ + +/* + * 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 either by + * e-mail - mail your message to <vojtech@ucw.cz>, or by paper mail: + * Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic + */ + +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/init.h> +#include <linux/serio.h> +#include <linux/err.h> + +#include <asm/irq.h> +#include <asm/hardware.h> +#include <asm/io.h> +#include <asm/hardware/iomd.h> +#include <asm/system.h> + +MODULE_AUTHOR("Vojtech Pavlik, Russell King"); +MODULE_DESCRIPTION("Acorn RiscPC PS/2 keyboard controller driver"); +MODULE_LICENSE("GPL"); + +static int rpckbd_write(struct serio *port, unsigned char val) +{ + while (!(iomd_readb(IOMD_KCTRL) & (1 << 7))) + cpu_relax(); + + iomd_writeb(val, IOMD_KARTTX); + + return 0; +} + +static irqreturn_t rpckbd_rx(int irq, void *dev_id, struct pt_regs *regs) +{ + struct serio *port = dev_id; + unsigned int byte; + int handled = IRQ_NONE; + + while (iomd_readb(IOMD_KCTRL) & (1 << 5)) { + byte = iomd_readb(IOMD_KARTRX); + + serio_interrupt(port, byte, 0, regs); + handled = IRQ_HANDLED; + } + return handled; +} + +static irqreturn_t rpckbd_tx(int irq, void *dev_id, struct pt_regs *regs) +{ + return IRQ_HANDLED; +} + +static int rpckbd_open(struct serio *port) +{ + /* Reset the keyboard state machine. */ + iomd_writeb(0, IOMD_KCTRL); + iomd_writeb(8, IOMD_KCTRL); + iomd_readb(IOMD_KARTRX); + + if (request_irq(IRQ_KEYBOARDRX, rpckbd_rx, 0, "rpckbd", port) != 0) { + printk(KERN_ERR "rpckbd.c: Could not allocate keyboard receive IRQ\n"); + return -EBUSY; + } + + if (request_irq(IRQ_KEYBOARDTX, rpckbd_tx, 0, "rpckbd", port) != 0) { + printk(KERN_ERR "rpckbd.c: Could not allocate keyboard transmit IRQ\n"); + free_irq(IRQ_KEYBOARDRX, NULL); + return -EBUSY; + } + + return 0; +} + +static void rpckbd_close(struct serio *port) +{ + free_irq(IRQ_KEYBOARDRX, port); + free_irq(IRQ_KEYBOARDTX, port); +} + +/* + * Allocate and initialize serio structure for subsequent registration + * with serio core. + */ +static int __devinit rpckbd_probe(struct device *dev) +{ + struct serio *serio; + + serio = kmalloc(sizeof(struct serio), GFP_KERNEL); + if (!serio) + return -ENOMEM; + + memset(serio, 0, sizeof(struct serio)); + serio->id.type = SERIO_8042; + serio->write = rpckbd_write; + serio->open = rpckbd_open; + serio->close = rpckbd_close; + serio->dev.parent = dev; + strlcpy(serio->name, "RiscPC PS/2 kbd port", sizeof(serio->name)); + strlcpy(serio->phys, "rpckbd/serio0", sizeof(serio->phys)); + + dev_set_drvdata(dev, serio); + serio_register_port(serio); + return 0; +} + +static int __devexit rpckbd_remove(struct device *dev) +{ + struct serio *serio = dev_get_drvdata(dev); + serio_unregister_port(serio); + return 0; +} + +static struct device_driver rpckbd_driver = { + .name = "kart", + .bus = &platform_bus_type, + .probe = rpckbd_probe, + .remove = __devexit_p(rpckbd_remove), +}; + +static int __init rpckbd_init(void) +{ + return driver_register(&rpckbd_driver); +} + +static void __exit rpckbd_exit(void) +{ + driver_unregister(&rpckbd_driver); +} + +module_init(rpckbd_init); +module_exit(rpckbd_exit); diff --git a/drivers/input/serio/sa1111ps2.c b/drivers/input/serio/sa1111ps2.c new file mode 100644 index 000000000000..3f0df3330fb2 --- /dev/null +++ b/drivers/input/serio/sa1111ps2.c @@ -0,0 +1,359 @@ +/* + * linux/drivers/input/serio/sa1111ps2.c + * + * Copyright (C) 2002 Russell King + * + * 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. + */ +#include <linux/module.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/serio.h> +#include <linux/errno.h> +#include <linux/interrupt.h> +#include <linux/ioport.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/slab.h> +#include <linux/spinlock.h> + +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/system.h> + +#include <asm/hardware/sa1111.h> + +struct ps2if { + struct serio *io; + struct sa1111_dev *dev; + void __iomem *base; + unsigned int open; + spinlock_t lock; + unsigned int head; + unsigned int tail; + unsigned char buf[4]; +}; + +/* + * Read all bytes waiting in the PS2 port. There should be + * at the most one, but we loop for safety. If there was a + * framing error, we have to manually clear the status. + */ +static irqreturn_t ps2_rxint(int irq, void *dev_id, struct pt_regs *regs) +{ + struct ps2if *ps2if = dev_id; + unsigned int scancode, flag, status; + + status = sa1111_readl(ps2if->base + SA1111_PS2STAT); + while (status & PS2STAT_RXF) { + if (status & PS2STAT_STP) + sa1111_writel(PS2STAT_STP, ps2if->base + SA1111_PS2STAT); + + flag = (status & PS2STAT_STP ? SERIO_FRAME : 0) | + (status & PS2STAT_RXP ? 0 : SERIO_PARITY); + + scancode = sa1111_readl(ps2if->base + SA1111_PS2DATA) & 0xff; + + if (hweight8(scancode) & 1) + flag ^= SERIO_PARITY; + + serio_interrupt(ps2if->io, scancode, flag, regs); + + status = sa1111_readl(ps2if->base + SA1111_PS2STAT); + } + + return IRQ_HANDLED; +} + +/* + * Completion of ps2 write + */ +static irqreturn_t ps2_txint(int irq, void *dev_id, struct pt_regs *regs) +{ + struct ps2if *ps2if = dev_id; + unsigned int status; + + spin_lock(&ps2if->lock); + status = sa1111_readl(ps2if->base + SA1111_PS2STAT); + if (ps2if->head == ps2if->tail) { + disable_irq(irq); + /* done */ + } else if (status & PS2STAT_TXE) { + sa1111_writel(ps2if->buf[ps2if->tail], ps2if->base + SA1111_PS2DATA); + ps2if->tail = (ps2if->tail + 1) & (sizeof(ps2if->buf) - 1); + } + spin_unlock(&ps2if->lock); + + return IRQ_HANDLED; +} + +/* + * Write a byte to the PS2 port. We have to wait for the + * port to indicate that the transmitter is empty. + */ +static int ps2_write(struct serio *io, unsigned char val) +{ + struct ps2if *ps2if = io->port_data; + unsigned long flags; + unsigned int head; + + spin_lock_irqsave(&ps2if->lock, flags); + + /* + * If the TX register is empty, we can go straight out. + */ + if (sa1111_readl(ps2if->base + SA1111_PS2STAT) & PS2STAT_TXE) { + sa1111_writel(val, ps2if->base + SA1111_PS2DATA); + } else { + if (ps2if->head == ps2if->tail) + enable_irq(ps2if->dev->irq[1]); + head = (ps2if->head + 1) & (sizeof(ps2if->buf) - 1); + if (head != ps2if->tail) { + ps2if->buf[ps2if->head] = val; + ps2if->head = head; + } + } + + spin_unlock_irqrestore(&ps2if->lock, flags); + return 0; +} + +static int ps2_open(struct serio *io) +{ + struct ps2if *ps2if = io->port_data; + int ret; + + sa1111_enable_device(ps2if->dev); + + ret = request_irq(ps2if->dev->irq[0], ps2_rxint, 0, + SA1111_DRIVER_NAME(ps2if->dev), ps2if); + if (ret) { + printk(KERN_ERR "sa1111ps2: could not allocate IRQ%d: %d\n", + ps2if->dev->irq[0], ret); + return ret; + } + + ret = request_irq(ps2if->dev->irq[1], ps2_txint, 0, + SA1111_DRIVER_NAME(ps2if->dev), ps2if); + if (ret) { + printk(KERN_ERR "sa1111ps2: could not allocate IRQ%d: %d\n", + ps2if->dev->irq[1], ret); + free_irq(ps2if->dev->irq[0], ps2if); + return ret; + } + + ps2if->open = 1; + + enable_irq_wake(ps2if->dev->irq[0]); + + sa1111_writel(PS2CR_ENA, ps2if->base + SA1111_PS2CR); + return 0; +} + +static void ps2_close(struct serio *io) +{ + struct ps2if *ps2if = io->port_data; + + sa1111_writel(0, ps2if->base + SA1111_PS2CR); + + disable_irq_wake(ps2if->dev->irq[0]); + + ps2if->open = 0; + + free_irq(ps2if->dev->irq[1], ps2if); + free_irq(ps2if->dev->irq[0], ps2if); + + sa1111_disable_device(ps2if->dev); +} + +/* + * Clear the input buffer. + */ +static void __init ps2_clear_input(struct ps2if *ps2if) +{ + int maxread = 100; + + while (maxread--) { + if ((sa1111_readl(ps2if->base + SA1111_PS2DATA) & 0xff) == 0xff) + break; + } +} + +static inline unsigned int +ps2_test_one(struct ps2if *ps2if, unsigned int mask) +{ + unsigned int val; + + sa1111_writel(PS2CR_ENA | mask, ps2if->base + SA1111_PS2CR); + + udelay(2); + + val = sa1111_readl(ps2if->base + SA1111_PS2STAT); + return val & (PS2STAT_KBC | PS2STAT_KBD); +} + +/* + * Test the keyboard interface. We basically check to make sure that + * we can drive each line to the keyboard independently of each other. + */ +static int __init ps2_test(struct ps2if *ps2if) +{ + unsigned int stat; + int ret = 0; + + stat = ps2_test_one(ps2if, PS2CR_FKC); + if (stat != PS2STAT_KBD) { + printk("PS/2 interface test failed[1]: %02x\n", stat); + ret = -ENODEV; + } + + stat = ps2_test_one(ps2if, 0); + if (stat != (PS2STAT_KBC | PS2STAT_KBD)) { + printk("PS/2 interface test failed[2]: %02x\n", stat); + ret = -ENODEV; + } + + stat = ps2_test_one(ps2if, PS2CR_FKD); + if (stat != PS2STAT_KBC) { + printk("PS/2 interface test failed[3]: %02x\n", stat); + ret = -ENODEV; + } + + sa1111_writel(0, ps2if->base + SA1111_PS2CR); + + return ret; +} + +/* + * Add one device to this driver. + */ +static int ps2_probe(struct sa1111_dev *dev) +{ + struct ps2if *ps2if; + struct serio *serio; + int ret; + + ps2if = kmalloc(sizeof(struct ps2if), GFP_KERNEL); + serio = kmalloc(sizeof(struct serio), GFP_KERNEL); + if (!ps2if || !serio) { + ret = -ENOMEM; + goto free; + } + + memset(ps2if, 0, sizeof(struct ps2if)); + memset(serio, 0, sizeof(struct serio)); + + serio->id.type = SERIO_8042; + serio->write = ps2_write; + serio->open = ps2_open; + serio->close = ps2_close; + strlcpy(serio->name, dev->dev.bus_id, sizeof(serio->name)); + strlcpy(serio->phys, dev->dev.bus_id, sizeof(serio->phys)); + serio->port_data = ps2if; + serio->dev.parent = &dev->dev; + ps2if->io = serio; + ps2if->dev = dev; + sa1111_set_drvdata(dev, ps2if); + + spin_lock_init(&ps2if->lock); + + /* + * Request the physical region for this PS2 port. + */ + if (!request_mem_region(dev->res.start, + dev->res.end - dev->res.start + 1, + SA1111_DRIVER_NAME(dev))) { + ret = -EBUSY; + goto free; + } + + /* + * Our parent device has already mapped the region. + */ + ps2if->base = dev->mapbase; + + sa1111_enable_device(ps2if->dev); + + /* Incoming clock is 8MHz */ + sa1111_writel(0, ps2if->base + SA1111_PS2CLKDIV); + sa1111_writel(127, ps2if->base + SA1111_PS2PRECNT); + + /* + * Flush any pending input. + */ + ps2_clear_input(ps2if); + + /* + * Test the keyboard interface. + */ + ret = ps2_test(ps2if); + if (ret) + goto out; + + /* + * Flush any pending input. + */ + ps2_clear_input(ps2if); + + sa1111_disable_device(ps2if->dev); + serio_register_port(ps2if->io); + return 0; + + out: + sa1111_disable_device(ps2if->dev); + release_mem_region(dev->res.start, + dev->res.end - dev->res.start + 1); + free: + sa1111_set_drvdata(dev, NULL); + kfree(ps2if); + kfree(serio); + return ret; +} + +/* + * Remove one device from this driver. + */ +static int ps2_remove(struct sa1111_dev *dev) +{ + struct ps2if *ps2if = sa1111_get_drvdata(dev); + + serio_unregister_port(ps2if->io); + release_mem_region(dev->res.start, + dev->res.end - dev->res.start + 1); + sa1111_set_drvdata(dev, NULL); + + kfree(ps2if); + + return 0; +} + +/* + * Our device driver structure + */ +static struct sa1111_driver ps2_driver = { + .drv = { + .name = "sa1111-ps2", + }, + .devid = SA1111_DEVID_PS2, + .probe = ps2_probe, + .remove = ps2_remove, +}; + +static int __init ps2_init(void) +{ + return sa1111_driver_register(&ps2_driver); +} + +static void __exit ps2_exit(void) +{ + sa1111_driver_unregister(&ps2_driver); +} + +module_init(ps2_init); +module_exit(ps2_exit); + +MODULE_AUTHOR("Russell King <rmk@arm.linux.org.uk>"); +MODULE_DESCRIPTION("SA1111 PS2 controller driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/serio/serio.c b/drivers/input/serio/serio.c new file mode 100644 index 000000000000..3313e2daeab0 --- /dev/null +++ b/drivers/input/serio/serio.c @@ -0,0 +1,859 @@ +/* + * The Serio abstraction module + * + * Copyright (c) 1999-2004 Vojtech Pavlik + * Copyright (c) 2004 Dmitry Torokhov + * Copyright (c) 2003 Daniele Bellucci + */ + +/* + * 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 either by + * e-mail - mail your message to <vojtech@ucw.cz>, or by paper mail: + * Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic + */ + +#include <linux/stddef.h> +#include <linux/module.h> +#include <linux/serio.h> +#include <linux/errno.h> +#include <linux/wait.h> +#include <linux/completion.h> +#include <linux/sched.h> +#include <linux/smp_lock.h> +#include <linux/slab.h> + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION("Serio abstraction core"); +MODULE_LICENSE("GPL"); + +EXPORT_SYMBOL(serio_interrupt); +EXPORT_SYMBOL(__serio_register_port); +EXPORT_SYMBOL(serio_unregister_port); +EXPORT_SYMBOL(__serio_unregister_port_delayed); +EXPORT_SYMBOL(__serio_register_driver); +EXPORT_SYMBOL(serio_unregister_driver); +EXPORT_SYMBOL(serio_open); +EXPORT_SYMBOL(serio_close); +EXPORT_SYMBOL(serio_rescan); +EXPORT_SYMBOL(serio_reconnect); + +/* + * serio_sem protects entire serio subsystem and is taken every time + * serio port or driver registrered or unregistered. + */ +static DECLARE_MUTEX(serio_sem); + +static LIST_HEAD(serio_list); + +static struct bus_type serio_bus = { + .name = "serio", +}; + +static void serio_add_port(struct serio *serio); +static void serio_destroy_port(struct serio *serio); +static void serio_reconnect_port(struct serio *serio); +static void serio_disconnect_port(struct serio *serio); + +static int serio_match_port(const struct serio_device_id *ids, struct serio *serio) +{ + while (ids->type || ids->proto) { + if ((ids->type == SERIO_ANY || ids->type == serio->id.type) && + (ids->proto == SERIO_ANY || ids->proto == serio->id.proto) && + (ids->extra == SERIO_ANY || ids->extra == serio->id.extra) && + (ids->id == SERIO_ANY || ids->id == serio->id.id)) + return 1; + ids++; + } + return 0; +} + +/* + * Basic serio -> driver core mappings + */ + +static void serio_bind_driver(struct serio *serio, struct serio_driver *drv) +{ + down_write(&serio_bus.subsys.rwsem); + + if (serio_match_port(drv->id_table, serio)) { + serio->dev.driver = &drv->driver; + if (drv->connect(serio, drv)) { + serio->dev.driver = NULL; + goto out; + } + device_bind_driver(&serio->dev); + } +out: + up_write(&serio_bus.subsys.rwsem); +} + +static void serio_release_driver(struct serio *serio) +{ + down_write(&serio_bus.subsys.rwsem); + device_release_driver(&serio->dev); + up_write(&serio_bus.subsys.rwsem); +} + +static void serio_find_driver(struct serio *serio) +{ + down_write(&serio_bus.subsys.rwsem); + device_attach(&serio->dev); + up_write(&serio_bus.subsys.rwsem); +} + + +/* + * Serio event processing. + */ + +enum serio_event_type { + SERIO_RESCAN, + SERIO_RECONNECT, + SERIO_REGISTER_PORT, + SERIO_UNREGISTER_PORT, + SERIO_REGISTER_DRIVER, +}; + +struct serio_event { + enum serio_event_type type; + void *object; + struct module *owner; + struct list_head node; +}; + +static DEFINE_SPINLOCK(serio_event_lock); /* protects serio_event_list */ +static LIST_HEAD(serio_event_list); +static DECLARE_WAIT_QUEUE_HEAD(serio_wait); +static DECLARE_COMPLETION(serio_exited); +static int serio_pid; + +static void serio_queue_event(void *object, struct module *owner, + enum serio_event_type event_type) +{ + unsigned long flags; + struct serio_event *event; + + spin_lock_irqsave(&serio_event_lock, flags); + + /* + * Scan event list for the other events for the same serio port, + * starting with the most recent one. If event is the same we + * do not need add new one. If event is of different type we + * need to add this event and should not look further because + * we need to preseve sequence of distinct events. + */ + list_for_each_entry_reverse(event, &serio_event_list, node) { + if (event->object == object) { + if (event->type == event_type) + goto out; + break; + } + } + + if ((event = kmalloc(sizeof(struct serio_event), GFP_ATOMIC))) { + if (!try_module_get(owner)) { + printk(KERN_WARNING "serio: Can't get module reference, dropping event %d\n", event_type); + goto out; + } + + event->type = event_type; + event->object = object; + event->owner = owner; + + list_add_tail(&event->node, &serio_event_list); + wake_up(&serio_wait); + } else { + printk(KERN_ERR "serio: Not enough memory to queue event %d\n", event_type); + } +out: + spin_unlock_irqrestore(&serio_event_lock, flags); +} + +static void serio_free_event(struct serio_event *event) +{ + module_put(event->owner); + kfree(event); +} + +static void serio_remove_duplicate_events(struct serio_event *event) +{ + struct list_head *node, *next; + struct serio_event *e; + unsigned long flags; + + spin_lock_irqsave(&serio_event_lock, flags); + + list_for_each_safe(node, next, &serio_event_list) { + e = list_entry(node, struct serio_event, node); + if (event->object == e->object) { + /* + * If this event is of different type we should not + * look further - we only suppress duplicate events + * that were sent back-to-back. + */ + if (event->type != e->type) + break; + + list_del_init(node); + serio_free_event(e); + } + } + + spin_unlock_irqrestore(&serio_event_lock, flags); +} + + +static struct serio_event *serio_get_event(void) +{ + struct serio_event *event; + struct list_head *node; + unsigned long flags; + + spin_lock_irqsave(&serio_event_lock, flags); + + if (list_empty(&serio_event_list)) { + spin_unlock_irqrestore(&serio_event_lock, flags); + return NULL; + } + + node = serio_event_list.next; + event = list_entry(node, struct serio_event, node); + list_del_init(node); + + spin_unlock_irqrestore(&serio_event_lock, flags); + + return event; +} + +static void serio_handle_events(void) +{ + struct serio_event *event; + struct serio_driver *serio_drv; + + down(&serio_sem); + + while ((event = serio_get_event())) { + + switch (event->type) { + case SERIO_REGISTER_PORT: + serio_add_port(event->object); + break; + + case SERIO_UNREGISTER_PORT: + serio_disconnect_port(event->object); + serio_destroy_port(event->object); + break; + + case SERIO_RECONNECT: + serio_reconnect_port(event->object); + break; + + case SERIO_RESCAN: + serio_disconnect_port(event->object); + serio_find_driver(event->object); + break; + + case SERIO_REGISTER_DRIVER: + serio_drv = event->object; + driver_register(&serio_drv->driver); + break; + + default: + break; + } + + serio_remove_duplicate_events(event); + serio_free_event(event); + } + + up(&serio_sem); +} + +/* + * Remove all events that have been submitted for a given serio port. + */ +static void serio_remove_pending_events(struct serio *serio) +{ + struct list_head *node, *next; + struct serio_event *event; + unsigned long flags; + + spin_lock_irqsave(&serio_event_lock, flags); + + list_for_each_safe(node, next, &serio_event_list) { + event = list_entry(node, struct serio_event, node); + if (event->object == serio) { + list_del_init(node); + serio_free_event(event); + } + } + + spin_unlock_irqrestore(&serio_event_lock, flags); +} + +/* + * Destroy child serio port (if any) that has not been fully registered yet. + * + * Note that we rely on the fact that port can have only one child and therefore + * only one child registration request can be pending. Additionally, children + * are registered by driver's connect() handler so there can't be a grandchild + * pending registration together with a child. + */ +static struct serio *serio_get_pending_child(struct serio *parent) +{ + struct serio_event *event; + struct serio *serio, *child = NULL; + unsigned long flags; + + spin_lock_irqsave(&serio_event_lock, flags); + + list_for_each_entry(event, &serio_event_list, node) { + if (event->type == SERIO_REGISTER_PORT) { + serio = event->object; + if (serio->parent == parent) { + child = serio; + break; + } + } + } + + spin_unlock_irqrestore(&serio_event_lock, flags); + return child; +} + +static int serio_thread(void *nothing) +{ + lock_kernel(); + daemonize("kseriod"); + allow_signal(SIGTERM); + + do { + serio_handle_events(); + wait_event_interruptible(serio_wait, !list_empty(&serio_event_list)); + try_to_freeze(PF_FREEZE); + } while (!signal_pending(current)); + + printk(KERN_DEBUG "serio: kseriod exiting\n"); + + unlock_kernel(); + complete_and_exit(&serio_exited, 0); +} + + +/* + * Serio port operations + */ + +static ssize_t serio_show_description(struct device *dev, char *buf) +{ + struct serio *serio = to_serio_port(dev); + return sprintf(buf, "%s\n", serio->name); +} + +static ssize_t serio_show_id_type(struct device *dev, char *buf) +{ + struct serio *serio = to_serio_port(dev); + return sprintf(buf, "%02x\n", serio->id.type); +} + +static ssize_t serio_show_id_proto(struct device *dev, char *buf) +{ + struct serio *serio = to_serio_port(dev); + return sprintf(buf, "%02x\n", serio->id.proto); +} + +static ssize_t serio_show_id_id(struct device *dev, char *buf) +{ + struct serio *serio = to_serio_port(dev); + return sprintf(buf, "%02x\n", serio->id.id); +} + +static ssize_t serio_show_id_extra(struct device *dev, char *buf) +{ + struct serio *serio = to_serio_port(dev); + return sprintf(buf, "%02x\n", serio->id.extra); +} + +static ssize_t serio_rebind_driver(struct device *dev, const char *buf, size_t count) +{ + struct serio *serio = to_serio_port(dev); + struct device_driver *drv; + int retval; + + retval = down_interruptible(&serio_sem); + if (retval) + return retval; + + retval = count; + if (!strncmp(buf, "none", count)) { + serio_disconnect_port(serio); + } else if (!strncmp(buf, "reconnect", count)) { + serio_reconnect_port(serio); + } else if (!strncmp(buf, "rescan", count)) { + serio_disconnect_port(serio); + serio_find_driver(serio); + } else if ((drv = driver_find(buf, &serio_bus)) != NULL) { + serio_disconnect_port(serio); + serio_bind_driver(serio, to_serio_driver(drv)); + put_driver(drv); + } else { + retval = -EINVAL; + } + + up(&serio_sem); + + return retval; +} + +static ssize_t serio_show_bind_mode(struct device *dev, char *buf) +{ + struct serio *serio = to_serio_port(dev); + return sprintf(buf, "%s\n", serio->manual_bind ? "manual" : "auto"); +} + +static ssize_t serio_set_bind_mode(struct device *dev, const char *buf, size_t count) +{ + struct serio *serio = to_serio_port(dev); + int retval; + + retval = count; + if (!strncmp(buf, "manual", count)) { + serio->manual_bind = 1; + } else if (!strncmp(buf, "auto", count)) { + serio->manual_bind = 0; + } else { + retval = -EINVAL; + } + + return retval; +} + +static struct device_attribute serio_device_attrs[] = { + __ATTR(description, S_IRUGO, serio_show_description, NULL), + __ATTR(id_type, S_IRUGO, serio_show_id_type, NULL), + __ATTR(id_proto, S_IRUGO, serio_show_id_proto, NULL), + __ATTR(id_id, S_IRUGO, serio_show_id_id, NULL), + __ATTR(id_extra, S_IRUGO, serio_show_id_extra, NULL), + __ATTR(drvctl, S_IWUSR, NULL, serio_rebind_driver), + __ATTR(bind_mode, S_IWUSR | S_IRUGO, serio_show_bind_mode, serio_set_bind_mode), + __ATTR_NULL +}; + + +static void serio_release_port(struct device *dev) +{ + struct serio *serio = to_serio_port(dev); + + kfree(serio); + module_put(THIS_MODULE); +} + +/* + * Prepare serio port for registration. + */ +static void serio_init_port(struct serio *serio) +{ + static atomic_t serio_no = ATOMIC_INIT(0); + + __module_get(THIS_MODULE); + + spin_lock_init(&serio->lock); + init_MUTEX(&serio->drv_sem); + device_initialize(&serio->dev); + snprintf(serio->dev.bus_id, sizeof(serio->dev.bus_id), + "serio%ld", (long)atomic_inc_return(&serio_no) - 1); + serio->dev.bus = &serio_bus; + serio->dev.release = serio_release_port; + if (serio->parent) + serio->dev.parent = &serio->parent->dev; +} + +/* + * Complete serio port registration. + * Driver core will attempt to find appropriate driver for the port. + */ +static void serio_add_port(struct serio *serio) +{ + if (serio->parent) { + serio_pause_rx(serio->parent); + serio->parent->child = serio; + serio_continue_rx(serio->parent); + } + + list_add_tail(&serio->node, &serio_list); + if (serio->start) + serio->start(serio); + device_add(&serio->dev); + serio->registered = 1; +} + +/* + * serio_destroy_port() completes deregistration process and removes + * port from the system + */ +static void serio_destroy_port(struct serio *serio) +{ + struct serio *child; + + child = serio_get_pending_child(serio); + if (child) { + serio_remove_pending_events(child); + put_device(&child->dev); + } + + if (serio->stop) + serio->stop(serio); + + if (serio->parent) { + serio_pause_rx(serio->parent); + serio->parent->child = NULL; + serio_continue_rx(serio->parent); + serio->parent = NULL; + } + + if (serio->registered) { + device_del(&serio->dev); + list_del_init(&serio->node); + serio->registered = 0; + } + + serio_remove_pending_events(serio); + put_device(&serio->dev); +} + +/* + * Reconnect serio port and all its children (re-initialize attached devices) + */ +static void serio_reconnect_port(struct serio *serio) +{ + do { + if (!serio->drv || !serio->drv->reconnect || serio->drv->reconnect(serio)) { + serio_disconnect_port(serio); + serio_find_driver(serio); + /* Ok, old children are now gone, we are done */ + break; + } + serio = serio->child; + } while (serio); +} + +/* + * serio_disconnect_port() unbinds a port from its driver. As a side effect + * all child ports are unbound and destroyed. + */ +static void serio_disconnect_port(struct serio *serio) +{ + struct serio *s, *parent; + + if (serio->child) { + /* + * Children ports should be disconnected and destroyed + * first, staring with the leaf one, since we don't want + * to do recursion + */ + for (s = serio; s->child; s = s->child) + /* empty */; + + do { + parent = s->parent; + + serio_release_driver(s); + serio_destroy_port(s); + } while ((s = parent) != serio); + } + + /* + * Ok, no children left, now disconnect this port + */ + serio_release_driver(serio); +} + +void serio_rescan(struct serio *serio) +{ + serio_queue_event(serio, NULL, SERIO_RESCAN); +} + +void serio_reconnect(struct serio *serio) +{ + serio_queue_event(serio, NULL, SERIO_RECONNECT); +} + +/* + * Submits register request to kseriod for subsequent execution. + * Note that port registration is always asynchronous. + */ +void __serio_register_port(struct serio *serio, struct module *owner) +{ + serio_init_port(serio); + serio_queue_event(serio, owner, SERIO_REGISTER_PORT); +} + +/* + * Synchronously unregisters serio port. + */ +void serio_unregister_port(struct serio *serio) +{ + down(&serio_sem); + serio_disconnect_port(serio); + serio_destroy_port(serio); + up(&serio_sem); +} + +/* + * Submits register request to kseriod for subsequent execution. + * Can be used when it is not obvious whether the serio_sem is + * taken or not and when delayed execution is feasible. + */ +void __serio_unregister_port_delayed(struct serio *serio, struct module *owner) +{ + serio_queue_event(serio, owner, SERIO_UNREGISTER_PORT); +} + + +/* + * Serio driver operations + */ + +static ssize_t serio_driver_show_description(struct device_driver *drv, char *buf) +{ + struct serio_driver *driver = to_serio_driver(drv); + return sprintf(buf, "%s\n", driver->description ? driver->description : "(none)"); +} + +static ssize_t serio_driver_show_bind_mode(struct device_driver *drv, char *buf) +{ + struct serio_driver *serio_drv = to_serio_driver(drv); + return sprintf(buf, "%s\n", serio_drv->manual_bind ? "manual" : "auto"); +} + +static ssize_t serio_driver_set_bind_mode(struct device_driver *drv, const char *buf, size_t count) +{ + struct serio_driver *serio_drv = to_serio_driver(drv); + int retval; + + retval = count; + if (!strncmp(buf, "manual", count)) { + serio_drv->manual_bind = 1; + } else if (!strncmp(buf, "auto", count)) { + serio_drv->manual_bind = 0; + } else { + retval = -EINVAL; + } + + return retval; +} + + +static struct driver_attribute serio_driver_attrs[] = { + __ATTR(description, S_IRUGO, serio_driver_show_description, NULL), + __ATTR(bind_mode, S_IWUSR | S_IRUGO, + serio_driver_show_bind_mode, serio_driver_set_bind_mode), + __ATTR_NULL +}; + +static int serio_driver_probe(struct device *dev) +{ + struct serio *serio = to_serio_port(dev); + struct serio_driver *drv = to_serio_driver(dev->driver); + + return drv->connect(serio, drv); +} + +static int serio_driver_remove(struct device *dev) +{ + struct serio *serio = to_serio_port(dev); + struct serio_driver *drv = to_serio_driver(dev->driver); + + drv->disconnect(serio); + return 0; +} + +void __serio_register_driver(struct serio_driver *drv, struct module *owner) +{ + drv->driver.bus = &serio_bus; + drv->driver.probe = serio_driver_probe; + drv->driver.remove = serio_driver_remove; + + serio_queue_event(drv, owner, SERIO_REGISTER_DRIVER); +} + +void serio_unregister_driver(struct serio_driver *drv) +{ + struct serio *serio; + + down(&serio_sem); + drv->manual_bind = 1; /* so serio_find_driver ignores it */ + +start_over: + list_for_each_entry(serio, &serio_list, node) { + if (serio->drv == drv) { + serio_disconnect_port(serio); + serio_find_driver(serio); + /* we could've deleted some ports, restart */ + goto start_over; + } + } + + driver_unregister(&drv->driver); + up(&serio_sem); +} + +static void serio_set_drv(struct serio *serio, struct serio_driver *drv) +{ + down(&serio->drv_sem); + serio_pause_rx(serio); + serio->drv = drv; + serio_continue_rx(serio); + up(&serio->drv_sem); +} + +static int serio_bus_match(struct device *dev, struct device_driver *drv) +{ + struct serio *serio = to_serio_port(dev); + struct serio_driver *serio_drv = to_serio_driver(drv); + + if (serio->manual_bind || serio_drv->manual_bind) + return 0; + + return serio_match_port(serio_drv->id_table, serio); +} + +#ifdef CONFIG_HOTPLUG + +#define PUT_ENVP(fmt, val) \ +do { \ + envp[i++] = buffer; \ + length += snprintf(buffer, buffer_size - length, fmt, val); \ + if (buffer_size - length <= 0 || i >= num_envp) \ + return -ENOMEM; \ + length++; \ + buffer += length; \ +} while (0) +static int serio_hotplug(struct device *dev, char **envp, int num_envp, char *buffer, int buffer_size) +{ + struct serio *serio; + int i = 0; + int length = 0; + + if (!dev) + return -ENODEV; + + serio = to_serio_port(dev); + + PUT_ENVP("SERIO_TYPE=%02x", serio->id.type); + PUT_ENVP("SERIO_PROTO=%02x", serio->id.proto); + PUT_ENVP("SERIO_ID=%02x", serio->id.id); + PUT_ENVP("SERIO_EXTRA=%02x", serio->id.extra); + + envp[i] = NULL; + + return 0; +} +#undef PUT_ENVP + +#else + +static int serio_hotplug(struct device *dev, char **envp, int num_envp, char *buffer, int buffer_size) +{ + return -ENODEV; +} + +#endif /* CONFIG_HOTPLUG */ + +static int serio_resume(struct device *dev) +{ + struct serio *serio = to_serio_port(dev); + + if (!serio->drv || !serio->drv->reconnect || serio->drv->reconnect(serio)) { + serio_disconnect_port(serio); + /* + * Driver re-probing can take a while, so better let kseriod + * deal with it. + */ + serio_rescan(serio); + } + + return 0; +} + +/* called from serio_driver->connect/disconnect methods under serio_sem */ +int serio_open(struct serio *serio, struct serio_driver *drv) +{ + serio_set_drv(serio, drv); + + if (serio->open && serio->open(serio)) { + serio_set_drv(serio, NULL); + return -1; + } + return 0; +} + +/* called from serio_driver->connect/disconnect methods under serio_sem */ +void serio_close(struct serio *serio) +{ + if (serio->close) + serio->close(serio); + + serio_set_drv(serio, NULL); +} + +irqreturn_t serio_interrupt(struct serio *serio, + unsigned char data, unsigned int dfl, struct pt_regs *regs) +{ + unsigned long flags; + irqreturn_t ret = IRQ_NONE; + + spin_lock_irqsave(&serio->lock, flags); + + if (likely(serio->drv)) { + ret = serio->drv->interrupt(serio, data, dfl, regs); + } else if (!dfl && serio->registered) { + serio_rescan(serio); + ret = IRQ_HANDLED; + } + + spin_unlock_irqrestore(&serio->lock, flags); + + return ret; +} + +static int __init serio_init(void) +{ + if (!(serio_pid = kernel_thread(serio_thread, NULL, CLONE_KERNEL))) { + printk(KERN_ERR "serio: Failed to start kseriod\n"); + return -1; + } + + serio_bus.dev_attrs = serio_device_attrs; + serio_bus.drv_attrs = serio_driver_attrs; + serio_bus.match = serio_bus_match; + serio_bus.hotplug = serio_hotplug; + serio_bus.resume = serio_resume; + bus_register(&serio_bus); + + return 0; +} + +static void __exit serio_exit(void) +{ + bus_unregister(&serio_bus); + kill_proc(serio_pid, SIGTERM, 1); + wait_for_completion(&serio_exited); +} + +module_init(serio_init); +module_exit(serio_exit); diff --git a/drivers/input/serio/serio_raw.c b/drivers/input/serio/serio_raw.c new file mode 100644 index 000000000000..d914e7e93db4 --- /dev/null +++ b/drivers/input/serio/serio_raw.c @@ -0,0 +1,403 @@ +/* + * Raw serio device providing access to a raw byte stream from underlying + * serio port. Closely emulates behavior of pre-2.6 /dev/psaux device + * + * Copyright (c) 2004 Dmitry Torokhov + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +#include <linux/slab.h> +#include <linux/poll.h> +#include <linux/module.h> +#include <linux/serio.h> +#include <linux/init.h> +#include <linux/major.h> +#include <linux/device.h> +#include <linux/devfs_fs_kernel.h> +#include <linux/miscdevice.h> +#include <linux/wait.h> + +#define DRIVER_DESC "Raw serio driver" + +MODULE_AUTHOR("Dmitry Torokhov <dtor@mail.ru>"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +#define SERIO_RAW_QUEUE_LEN 64 +struct serio_raw { + unsigned char queue[SERIO_RAW_QUEUE_LEN]; + unsigned int tail, head; + + char name[16]; + unsigned int refcnt; + struct serio *serio; + struct miscdevice dev; + wait_queue_head_t wait; + struct list_head list; + struct list_head node; +}; + +struct serio_raw_list { + struct fasync_struct *fasync; + struct serio_raw *serio_raw; + struct list_head node; +}; + +static DECLARE_MUTEX(serio_raw_sem); +static LIST_HEAD(serio_raw_list); +static unsigned int serio_raw_no; + +/********************************************************************* + * Interface with userspace (file operations) * + *********************************************************************/ + +static int serio_raw_fasync(int fd, struct file *file, int on) +{ + struct serio_raw_list *list = file->private_data; + int retval; + + retval = fasync_helper(fd, file, on, &list->fasync); + return retval < 0 ? retval : 0; +} + +static struct serio_raw *serio_raw_locate(int minor) +{ + struct serio_raw *serio_raw; + + list_for_each_entry(serio_raw, &serio_raw_list, node) { + if (serio_raw->dev.minor == minor) + return serio_raw; + } + + return NULL; +} + +static int serio_raw_open(struct inode *inode, struct file *file) +{ + struct serio_raw *serio_raw; + struct serio_raw_list *list; + int retval = 0; + + retval = down_interruptible(&serio_raw_sem); + if (retval) + return retval; + + if (!(serio_raw = serio_raw_locate(iminor(inode)))) { + retval = -ENODEV; + goto out; + } + + if (!serio_raw->serio) { + retval = -ENODEV; + goto out; + } + + if (!(list = kmalloc(sizeof(struct serio_raw_list), GFP_KERNEL))) { + retval = -ENOMEM; + goto out; + } + + memset(list, 0, sizeof(struct serio_raw_list)); + list->serio_raw = serio_raw; + file->private_data = list; + + serio_raw->refcnt++; + list_add_tail(&list->node, &serio_raw->list); + +out: + up(&serio_raw_sem); + return retval; +} + +static int serio_raw_cleanup(struct serio_raw *serio_raw) +{ + if (--serio_raw->refcnt == 0) { + misc_deregister(&serio_raw->dev); + list_del_init(&serio_raw->node); + kfree(serio_raw); + + return 1; + } + + return 0; +} + +static int serio_raw_release(struct inode *inode, struct file *file) +{ + struct serio_raw_list *list = file->private_data; + struct serio_raw *serio_raw = list->serio_raw; + + down(&serio_raw_sem); + + serio_raw_fasync(-1, file, 0); + serio_raw_cleanup(serio_raw); + + up(&serio_raw_sem); + return 0; +} + +static int serio_raw_fetch_byte(struct serio_raw *serio_raw, char *c) +{ + unsigned long flags; + int empty; + + spin_lock_irqsave(&serio_raw->serio->lock, flags); + + empty = serio_raw->head == serio_raw->tail; + if (!empty) { + *c = serio_raw->queue[serio_raw->tail]; + serio_raw->tail = (serio_raw->tail + 1) % SERIO_RAW_QUEUE_LEN; + } + + spin_unlock_irqrestore(&serio_raw->serio->lock, flags); + + return !empty; +} + +static ssize_t serio_raw_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos) +{ + struct serio_raw_list *list = file->private_data; + struct serio_raw *serio_raw = list->serio_raw; + char c; + ssize_t retval = 0; + + if (!serio_raw->serio) + return -ENODEV; + + if (serio_raw->head == serio_raw->tail && (file->f_flags & O_NONBLOCK)) + return -EAGAIN; + + retval = wait_event_interruptible(list->serio_raw->wait, + serio_raw->head != serio_raw->tail || !serio_raw->serio); + if (retval) + return retval; + + if (!serio_raw->serio) + return -ENODEV; + + while (retval < count && serio_raw_fetch_byte(serio_raw, &c)) { + if (put_user(c, buffer++)) + return -EFAULT; + retval++; + } + + return retval; +} + +static ssize_t serio_raw_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos) +{ + struct serio_raw_list *list = file->private_data; + ssize_t written = 0; + int retval; + unsigned char c; + + retval = down_interruptible(&serio_raw_sem); + if (retval) + return retval; + + if (!list->serio_raw->serio) { + retval = -ENODEV; + goto out; + } + + if (count > 32) + count = 32; + + while (count--) { + if (get_user(c, buffer++)) { + retval = -EFAULT; + goto out; + } + if (serio_write(list->serio_raw->serio, c)) { + retval = -EIO; + goto out; + } + written++; + }; + +out: + up(&serio_raw_sem); + return written; +} + +static unsigned int serio_raw_poll(struct file *file, poll_table *wait) +{ + struct serio_raw_list *list = file->private_data; + + poll_wait(file, &list->serio_raw->wait, wait); + + if (list->serio_raw->head != list->serio_raw->tail) + return POLLIN | POLLRDNORM; + + return 0; +} + +static struct file_operations serio_raw_fops = { + .owner = THIS_MODULE, + .open = serio_raw_open, + .release = serio_raw_release, + .read = serio_raw_read, + .write = serio_raw_write, + .poll = serio_raw_poll, + .fasync = serio_raw_fasync, +}; + + +/********************************************************************* + * Interface with serio port * + *********************************************************************/ + +static irqreturn_t serio_raw_interrupt(struct serio *serio, unsigned char data, + unsigned int dfl, struct pt_regs *regs) +{ + struct serio_raw *serio_raw = serio_get_drvdata(serio); + struct serio_raw_list *list; + unsigned int head = serio_raw->head; + + /* we are holding serio->lock here so we are prootected */ + serio_raw->queue[head] = data; + head = (head + 1) % SERIO_RAW_QUEUE_LEN; + if (likely(head != serio_raw->tail)) { + serio_raw->head = head; + list_for_each_entry(list, &serio_raw->list, node) + kill_fasync(&list->fasync, SIGIO, POLL_IN); + wake_up_interruptible(&serio_raw->wait); + } + + return IRQ_HANDLED; +} + +static int serio_raw_connect(struct serio *serio, struct serio_driver *drv) +{ + struct serio_raw *serio_raw; + int err; + + if (!(serio_raw = kmalloc(sizeof(struct serio_raw), GFP_KERNEL))) { + printk(KERN_ERR "serio_raw.c: can't allocate memory for a device\n"); + return -ENOMEM; + } + + down(&serio_raw_sem); + + memset(serio_raw, 0, sizeof(struct serio_raw)); + snprintf(serio_raw->name, sizeof(serio_raw->name), "serio_raw%d", serio_raw_no++); + serio_raw->refcnt = 1; + serio_raw->serio = serio; + INIT_LIST_HEAD(&serio_raw->list); + init_waitqueue_head(&serio_raw->wait); + + serio_set_drvdata(serio, serio_raw); + + err = serio_open(serio, drv); + if (err) + goto out_free; + + list_add_tail(&serio_raw->node, &serio_raw_list); + + serio_raw->dev.minor = PSMOUSE_MINOR; + serio_raw->dev.name = serio_raw->name; + serio_raw->dev.fops = &serio_raw_fops; + + err = misc_register(&serio_raw->dev); + if (err) { + serio_raw->dev.minor = MISC_DYNAMIC_MINOR; + err = misc_register(&serio_raw->dev); + } + + if (err) { + printk(KERN_INFO "serio_raw: failed to register raw access device for %s\n", + serio->phys); + goto out_close; + } + + printk(KERN_INFO "serio_raw: raw access enabled on %s (%s, minor %d)\n", + serio->phys, serio_raw->name, serio_raw->dev.minor); + goto out; + +out_close: + serio_close(serio); + list_del_init(&serio_raw->node); +out_free: + serio_set_drvdata(serio, NULL); + kfree(serio_raw); +out: + up(&serio_raw_sem); + return err; +} + +static int serio_raw_reconnect(struct serio *serio) +{ + struct serio_raw *serio_raw = serio_get_drvdata(serio); + struct serio_driver *drv = serio->drv; + + if (!drv || !serio_raw) { + printk(KERN_DEBUG "serio_raw: reconnect request, but serio is disconnected, ignoring...\n"); + return -1; + } + + /* + * Nothing needs to be done here, we just need this method to + * keep the same device. + */ + return 0; +} + +static void serio_raw_disconnect(struct serio *serio) +{ + struct serio_raw *serio_raw; + + down(&serio_raw_sem); + + serio_raw = serio_get_drvdata(serio); + + serio_close(serio); + serio_set_drvdata(serio, NULL); + + serio_raw->serio = NULL; + if (!serio_raw_cleanup(serio_raw)) + wake_up_interruptible(&serio_raw->wait); + + up(&serio_raw_sem); +} + +static struct serio_device_id serio_raw_serio_ids[] = { + { + .type = SERIO_8042, + .proto = SERIO_ANY, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { 0 } +}; + +MODULE_DEVICE_TABLE(serio, serio_raw_serio_ids); + +static struct serio_driver serio_raw_drv = { + .driver = { + .name = "serio_raw", + }, + .description = DRIVER_DESC, + .id_table = serio_raw_serio_ids, + .interrupt = serio_raw_interrupt, + .connect = serio_raw_connect, + .reconnect = serio_raw_reconnect, + .disconnect = serio_raw_disconnect, + .manual_bind = 1, +}; + +static int __init serio_raw_init(void) +{ + serio_register_driver(&serio_raw_drv); + return 0; +} + +static void __exit serio_raw_exit(void) +{ + serio_unregister_driver(&serio_raw_drv); +} + +module_init(serio_raw_init); +module_exit(serio_raw_exit); diff --git a/drivers/input/serio/serport.c b/drivers/input/serio/serport.c new file mode 100644 index 000000000000..22f73683952b --- /dev/null +++ b/drivers/input/serio/serport.c @@ -0,0 +1,226 @@ +/* + * Input device TTY line discipline + * + * Copyright (c) 1999-2002 Vojtech Pavlik + * + * This is a module that converts a tty line into a much simpler + * 'serial io port' abstraction that the input device drivers use. + */ + +/* + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +#include <asm/uaccess.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/serio.h> +#include <linux/tty.h> + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION("Input device TTY line discipline"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_LDISC(N_MOUSE); + +#define SERPORT_BUSY 1 + +struct serport { + struct tty_struct *tty; + wait_queue_head_t wait; + struct serio *serio; + unsigned long flags; +}; + +/* + * Callback functions from the serio code. + */ + +static int serport_serio_write(struct serio *serio, unsigned char data) +{ + struct serport *serport = serio->port_data; + return -(serport->tty->driver->write(serport->tty, &data, 1) != 1); +} + +static void serport_serio_close(struct serio *serio) +{ + struct serport *serport = serio->port_data; + + serport->serio->id.type = 0; + wake_up_interruptible(&serport->wait); +} + +/* + * serport_ldisc_open() is the routine that is called upon setting our line + * discipline on a tty. It prepares the serio struct. + */ + +static int serport_ldisc_open(struct tty_struct *tty) +{ + struct serport *serport; + struct serio *serio; + char name[64]; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + serport = kmalloc(sizeof(struct serport), GFP_KERNEL); + serio = kmalloc(sizeof(struct serio), GFP_KERNEL); + if (unlikely(!serport || !serio)) { + kfree(serport); + kfree(serio); + return -ENOMEM; + } + + memset(serport, 0, sizeof(struct serport)); + serport->serio = serio; + set_bit(TTY_DO_WRITE_WAKEUP, &tty->flags); + serport->tty = tty; + tty->disc_data = serport; + + memset(serio, 0, sizeof(struct serio)); + strlcpy(serio->name, "Serial port", sizeof(serio->name)); + snprintf(serio->phys, sizeof(serio->phys), "%s/serio0", tty_name(tty, name)); + serio->id.type = SERIO_RS232; + serio->write = serport_serio_write; + serio->close = serport_serio_close; + serio->port_data = serport; + + init_waitqueue_head(&serport->wait); + + return 0; +} + +/* + * serport_ldisc_close() is the opposite of serport_ldisc_open() + */ + +static void serport_ldisc_close(struct tty_struct *tty) +{ + struct serport *serport = (struct serport*) tty->disc_data; + kfree(serport); +} + +/* + * serport_ldisc_receive() is called by the low level tty driver when characters + * are ready for us. We forward the characters, one by one to the 'interrupt' + * routine. + * + * FIXME: We should get pt_regs from the tty layer and forward them to + * serio_interrupt here. + */ + +static void serport_ldisc_receive(struct tty_struct *tty, const unsigned char *cp, char *fp, int count) +{ + struct serport *serport = (struct serport*) tty->disc_data; + int i; + for (i = 0; i < count; i++) + serio_interrupt(serport->serio, cp[i], 0, NULL); +} + +/* + * serport_ldisc_room() reports how much room we do have for receiving data. + * Although we in fact have infinite room, we need to specify some value + * here, and 256 seems to be reasonable. + */ + +static int serport_ldisc_room(struct tty_struct *tty) +{ + return 256; +} + +/* + * serport_ldisc_read() just waits indefinitely if everything goes well. + * However, when the serio driver closes the serio port, it finishes, + * returning 0 characters. + */ + +static ssize_t serport_ldisc_read(struct tty_struct * tty, struct file * file, unsigned char __user * buf, size_t nr) +{ + struct serport *serport = (struct serport*) tty->disc_data; + char name[64]; + + if (test_and_set_bit(SERPORT_BUSY, &serport->flags)) + return -EBUSY; + + serio_register_port(serport->serio); + printk(KERN_INFO "serio: Serial port %s\n", tty_name(tty, name)); + wait_event_interruptible(serport->wait, !serport->serio->id.type); + serio_unregister_port(serport->serio); + + clear_bit(SERPORT_BUSY, &serport->flags); + + return 0; +} + +/* + * serport_ldisc_ioctl() allows to set the port protocol, and device ID + */ + +static int serport_ldisc_ioctl(struct tty_struct * tty, struct file * file, unsigned int cmd, unsigned long arg) +{ + struct serport *serport = (struct serport*) tty->disc_data; + struct serio *serio = serport->serio; + unsigned long type; + + if (cmd == SPIOCSTYPE) { + if (get_user(type, (unsigned long __user *) arg)) + return -EFAULT; + + serio->id.proto = type & 0x000000ff; + serio->id.id = (type & 0x0000ff00) >> 8; + serio->id.extra = (type & 0x00ff0000) >> 16; + + return 0; + } + + return -EINVAL; +} + +static void serport_ldisc_write_wakeup(struct tty_struct * tty) +{ + struct serport *sp = (struct serport *) tty->disc_data; + + serio_drv_write_wakeup(sp->serio); +} + +/* + * The line discipline structure. + */ + +static struct tty_ldisc serport_ldisc = { + .owner = THIS_MODULE, + .name = "input", + .open = serport_ldisc_open, + .close = serport_ldisc_close, + .read = serport_ldisc_read, + .ioctl = serport_ldisc_ioctl, + .receive_buf = serport_ldisc_receive, + .receive_room = serport_ldisc_room, + .write_wakeup = serport_ldisc_write_wakeup +}; + +/* + * The functions for insering/removing us as a module. + */ + +static int __init serport_init(void) +{ + int retval; + retval = tty_register_ldisc(N_MOUSE, &serport_ldisc); + if (retval) + printk(KERN_ERR "serport.c: Error registering line discipline.\n"); + + return retval; +} + +static void __exit serport_exit(void) +{ + tty_register_ldisc(N_MOUSE, NULL); +} + +module_init(serport_init); +module_exit(serport_exit); diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig new file mode 100644 index 000000000000..7e991274ea40 --- /dev/null +++ b/drivers/input/touchscreen/Kconfig @@ -0,0 +1,98 @@ +# +# Mouse driver configuration +# +menuconfig INPUT_TOUCHSCREEN + bool "Touchscreens" + help + Say Y here, and a list of supported touchscreens will be displayed. + This option doesn't affect the kernel. + + If unsure, say Y. + +if INPUT_TOUCHSCREEN + +config TOUCHSCREEN_BITSY + tristate "Compaq iPAQ H3600 (Bitsy) touchscreen" + depends on SA1100_BITSY + select SERIO + help + Say Y here if you have the h3600 (Bitsy) touchscreen. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called h3600_ts_input. + +config TOUCHSCREEN_CORGI + tristate "Corgi touchscreen (for Sharp SL-C7xx)" + depends on PXA_SHARPSL + default y + help + Say Y here to enable the driver for the touchscreen on the + Sharp SL-C7xx series of PDAs. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called ads7846_ts. + +config TOUCHSCREEN_GUNZE + tristate "Gunze AHL-51S touchscreen" + select SERIO + help + Say Y here if you have the Gunze AHL-51 touchscreen connected to + your system. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called gunze. + +config TOUCHSCREEN_ELO + tristate "Elo serial touchscreens" + select SERIO + help + Say Y here if you have an Elo serial touchscreen connected to + your system. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called gunze. + +config TOUCHSCREEN_MTOUCH + tristate "MicroTouch serial touchscreens" + select SERIO + help + Say Y here if you have a MicroTouch (3M) serial touchscreen connected to + your system. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called mtouch. + +config TOUCHSCREEN_MK712 + tristate "ICS MicroClock MK712 touchscreen" + help + Say Y here if you have the ICS MicroClock MK712 touchscreen + controller chip in your system. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called mk712. + +config TOUCHSCREEN_HP600 + tristate "HP Jornada 680/690 touchscreen" + depends on SH_HP600 && SH_ADC + help + Say Y here if you have a HP Jornada 680 or 690 and want to + support the built-in touchscreen. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called hp680_ts_input. + +endif diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile new file mode 100644 index 000000000000..6842869c9a26 --- /dev/null +++ b/drivers/input/touchscreen/Makefile @@ -0,0 +1,13 @@ +# +# Makefile for the mouse drivers. +# + +# Each configuration option enables a list of files. + +obj-$(CONFIG_TOUCHSCREEN_BITSY) += h3600_ts_input.o +obj-$(CONFIG_TOUCHSCREEN_CORGI) += corgi_ts.o +obj-$(CONFIG_TOUCHSCREEN_GUNZE) += gunze.o +obj-$(CONFIG_TOUCHSCREEN_ELO) += elo.o +obj-$(CONFIG_TOUCHSCREEN_MTOUCH) += mtouch.o +obj-$(CONFIG_TOUCHSCREEN_MK712) += mk712.o +obj-$(CONFIG_TOUCHSCREEN_HP600) += hp680_ts_input.o diff --git a/drivers/input/touchscreen/corgi_ts.c b/drivers/input/touchscreen/corgi_ts.c new file mode 100644 index 000000000000..3f8b61cfbc37 --- /dev/null +++ b/drivers/input/touchscreen/corgi_ts.c @@ -0,0 +1,380 @@ +/* + * Touchscreen driver for Sharp Corgi models (SL-C7xx) + * + * Copyright (c) 2004-2005 Richard Purdie + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + + +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <asm/irq.h> + +#include <asm/arch/corgi.h> +#include <asm/arch/hardware.h> +#include <asm/arch/pxa-regs.h> + + +#define PWR_MODE_ACTIVE 0 +#define PWR_MODE_SUSPEND 1 + +#define X_AXIS_MAX 3830 +#define X_AXIS_MIN 150 +#define Y_AXIS_MAX 3830 +#define Y_AXIS_MIN 190 +#define PRESSURE_MIN 0 +#define PRESSURE_MAX 15000 + +struct ts_event { + short pressure; + short x; + short y; +}; + +struct corgi_ts { + char phys[32]; + struct input_dev input; + struct timer_list timer; + struct ts_event tc; + int pendown; + int power_mode; +}; + +#define STATUS_HSYNC (GPLR(CORGI_GPIO_HSYNC) & GPIO_bit(CORGI_GPIO_HSYNC)) + +#define SyncHS() while((STATUS_HSYNC) == 0); while((STATUS_HSYNC) != 0); +#define CCNT(a) asm volatile ("mrc p14, 0, %0, C1, C0, 0" : "=r"(a)) +#define CCNT_ON() {int pmnc = 1; asm volatile ("mcr p14, 0, %0, C0, C0, 0" : : "r"(pmnc));} +#define CCNT_OFF() {int pmnc = 0; asm volatile ("mcr p14, 0, %0, C0, C0, 0" : : "r"(pmnc));} + +#define WAIT_HS_400_VGA 7013U // 17.615us +#define WAIT_HS_400_QVGA 16622U // 41.750us + + +/* ADS7846 Touch Screen Controller bit definitions */ +#define ADSCTRL_PD0 (1u << 0) /* PD0 */ +#define ADSCTRL_PD1 (1u << 1) /* PD1 */ +#define ADSCTRL_DFR (1u << 2) /* SER/DFR */ +#define ADSCTRL_MOD (1u << 3) /* Mode */ +#define ADSCTRL_ADR_SH 4 /* Address setting */ +#define ADSCTRL_STS (1u << 7) /* Start Bit */ + +/* External Functions */ +extern int w100fb_get_xres(void); +extern int w100fb_get_blanking(void); +extern int w100fb_get_fastsysclk(void); +extern unsigned int get_clk_frequency_khz(int info); + +static unsigned long calc_waittime(void) +{ + int w100fb_xres = w100fb_get_xres(); + unsigned int waittime = 0; + + if (w100fb_xres == 480 || w100fb_xres == 640) { + waittime = WAIT_HS_400_VGA * get_clk_frequency_khz(0) / 398131U; + + if (w100fb_get_fastsysclk() == 100) + waittime = waittime * 75 / 100; + + if (w100fb_xres == 640) + waittime *= 3; + + return waittime; + } + + return WAIT_HS_400_QVGA * get_clk_frequency_khz(0) / 398131U; +} + +static int sync_receive_data_send_cmd(int doRecive, int doSend, unsigned int address, unsigned long wait_time) +{ + int pos = 0; + unsigned long timer1 = 0, timer2; + int dosleep; + + dosleep = !w100fb_get_blanking(); + + if (dosleep && doSend) { + CCNT_ON(); + /* polling HSync */ + SyncHS(); + /* get CCNT */ + CCNT(timer1); + } + + if (doRecive) + pos = corgi_ssp_ads7846_get(); + + if (doSend) { + int cmd = ADSCTRL_PD0 | ADSCTRL_PD1 | (address << ADSCTRL_ADR_SH) | ADSCTRL_STS; + /* dummy command */ + corgi_ssp_ads7846_put(cmd); + corgi_ssp_ads7846_get(); + + if (dosleep) { + /* Wait after HSync */ + CCNT(timer2); + if (timer2-timer1 > wait_time) { + /* timeout */ + SyncHS(); + /* get OSCR */ + CCNT(timer1); + /* Wait after HSync */ + CCNT(timer2); + } + while (timer2 - timer1 < wait_time) + CCNT(timer2); + } + corgi_ssp_ads7846_put(cmd); + if (dosleep) + CCNT_OFF(); + } + return pos; +} + +static int read_xydata(struct corgi_ts *corgi_ts) +{ + unsigned int x, y, z1, z2; + unsigned long flags, wait_time; + + /* critical section */ + local_irq_save(flags); + corgi_ssp_ads7846_lock(); + wait_time=calc_waittime(); + + /* Y-axis */ + sync_receive_data_send_cmd(0, 1, 1u, wait_time); + + /* Y-axis */ + sync_receive_data_send_cmd(1, 1, 1u, wait_time); + + /* X-axis */ + y = sync_receive_data_send_cmd(1, 1, 5u, wait_time); + + /* Z1 */ + x = sync_receive_data_send_cmd(1, 1, 3u, wait_time); + + /* Z2 */ + z1 = sync_receive_data_send_cmd(1, 1, 4u, wait_time); + z2 = sync_receive_data_send_cmd(1, 0, 4u, wait_time); + + /* Power-Down Enable */ + corgi_ssp_ads7846_put((1u << ADSCTRL_ADR_SH) | ADSCTRL_STS); + corgi_ssp_ads7846_get(); + + corgi_ssp_ads7846_unlock(); + local_irq_restore(flags); + + if (x== 0 || y == 0 || z1 == 0 || (x * (z2 - z1) / z1) >= 15000) { + corgi_ts->tc.pressure = 0; + return 0; + } + + corgi_ts->tc.x = x; + corgi_ts->tc.y = y; + corgi_ts->tc.pressure = (x * (z2 - z1)) / z1; + return 1; +} + +static void new_data(struct corgi_ts *corgi_ts, struct pt_regs *regs) +{ + if (corgi_ts->power_mode != PWR_MODE_ACTIVE) + return; + + if (!corgi_ts->tc.pressure && corgi_ts->pendown == 0) + return; + + if (regs) + input_regs(&corgi_ts->input, regs); + + input_report_abs(&corgi_ts->input, ABS_X, corgi_ts->tc.x); + input_report_abs(&corgi_ts->input, ABS_Y, corgi_ts->tc.y); + input_report_abs(&corgi_ts->input, ABS_PRESSURE, corgi_ts->tc.pressure); + input_report_key(&corgi_ts->input, BTN_TOUCH, (corgi_ts->pendown != 0)); + input_sync(&corgi_ts->input); +} + +static void ts_interrupt_main(struct corgi_ts *corgi_ts, int isTimer, struct pt_regs *regs) +{ + if ((GPLR(CORGI_GPIO_TP_INT) & GPIO_bit(CORGI_GPIO_TP_INT)) == 0) { + /* Disable Interrupt */ + set_irq_type(CORGI_IRQ_GPIO_TP_INT, IRQT_NOEDGE); + if (read_xydata(corgi_ts)) { + corgi_ts->pendown = 1; + new_data(corgi_ts, regs); + } + mod_timer(&corgi_ts->timer, jiffies + HZ / 100); + } else { + if (corgi_ts->pendown == 1 || corgi_ts->pendown == 2) { + mod_timer(&corgi_ts->timer, jiffies + HZ / 100); + corgi_ts->pendown++; + return; + } + + if (corgi_ts->pendown) { + corgi_ts->tc.pressure = 0; + new_data(corgi_ts, regs); + } + + /* Enable Falling Edge */ + set_irq_type(CORGI_IRQ_GPIO_TP_INT, IRQT_FALLING); + corgi_ts->pendown = 0; + } +} + +static void corgi_ts_timer(unsigned long data) +{ + struct corgi_ts *corgits_data = (struct corgi_ts *) data; + ts_interrupt_main(corgits_data, 1, NULL); +} + +static irqreturn_t ts_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + struct corgi_ts *corgits_data = dev_id; + ts_interrupt_main(corgits_data, 0, regs); + return IRQ_HANDLED; +} + +#ifdef CONFIG_PM +static int corgits_suspend(struct device *dev, uint32_t state, uint32_t level) +{ + if (level == SUSPEND_POWER_DOWN) { + struct corgi_ts *corgi_ts = dev_get_drvdata(dev); + + if (corgi_ts->pendown) { + del_timer_sync(&corgi_ts->timer); + corgi_ts->tc.pressure = 0; + new_data(corgi_ts, NULL); + corgi_ts->pendown = 0; + } + corgi_ts->power_mode = PWR_MODE_SUSPEND; + + corgi_ssp_ads7846_putget((1u << ADSCTRL_ADR_SH) | ADSCTRL_STS); + } + return 0; +} + +static int corgits_resume(struct device *dev, uint32_t level) +{ + if (level == RESUME_POWER_ON) { + struct corgi_ts *corgi_ts = dev_get_drvdata(dev); + + corgi_ssp_ads7846_putget((4u << ADSCTRL_ADR_SH) | ADSCTRL_STS); + /* Enable Falling Edge */ + set_irq_type(CORGI_IRQ_GPIO_TP_INT, IRQT_FALLING); + corgi_ts->power_mode = PWR_MODE_ACTIVE; + } + return 0; +} +#else +#define corgits_suspend NULL +#define corgits_resume NULL +#endif + +static int __init corgits_probe(struct device *dev) +{ + struct corgi_ts *corgi_ts; + + if (!(corgi_ts = kmalloc(sizeof(struct corgi_ts), GFP_KERNEL))) + return -ENOMEM; + + dev_set_drvdata(dev, corgi_ts); + + memset(corgi_ts, 0, sizeof(struct corgi_ts)); + + init_input_dev(&corgi_ts->input); + corgi_ts->input.evbit[0] = BIT(EV_KEY) | BIT(EV_ABS); + corgi_ts->input.keybit[LONG(BTN_TOUCH)] = BIT(BTN_TOUCH); + input_set_abs_params(&corgi_ts->input, ABS_X, X_AXIS_MIN, X_AXIS_MAX, 0, 0); + input_set_abs_params(&corgi_ts->input, ABS_Y, Y_AXIS_MIN, Y_AXIS_MAX, 0, 0); + input_set_abs_params(&corgi_ts->input, ABS_PRESSURE, PRESSURE_MIN, PRESSURE_MAX, 0, 0); + + strcpy(corgi_ts->phys, "corgits/input0"); + + corgi_ts->input.private = corgi_ts; + corgi_ts->input.name = "Corgi Touchscreen"; + corgi_ts->input.dev = dev; + corgi_ts->input.phys = corgi_ts->phys; + corgi_ts->input.id.bustype = BUS_HOST; + corgi_ts->input.id.vendor = 0x0001; + corgi_ts->input.id.product = 0x0002; + corgi_ts->input.id.version = 0x0100; + + pxa_gpio_mode(CORGI_GPIO_TP_INT | GPIO_IN); + pxa_gpio_mode(CORGI_GPIO_HSYNC | GPIO_IN); + + /* Initiaize ADS7846 Difference Reference mode */ + corgi_ssp_ads7846_putget((1u << ADSCTRL_ADR_SH) | ADSCTRL_STS); + mdelay(5); + corgi_ssp_ads7846_putget((3u << ADSCTRL_ADR_SH) | ADSCTRL_STS); + mdelay(5); + corgi_ssp_ads7846_putget((4u << ADSCTRL_ADR_SH) | ADSCTRL_STS); + mdelay(5); + corgi_ssp_ads7846_putget((5u << ADSCTRL_ADR_SH) | ADSCTRL_STS); + mdelay(5); + + init_timer(&corgi_ts->timer); + corgi_ts->timer.data = (unsigned long) corgi_ts; + corgi_ts->timer.function = corgi_ts_timer; + + input_register_device(&corgi_ts->input); + corgi_ts->power_mode = PWR_MODE_ACTIVE; + + if (request_irq(CORGI_IRQ_GPIO_TP_INT, ts_interrupt, SA_INTERRUPT, "ts", corgi_ts)) { + input_unregister_device(&corgi_ts->input); + kfree(corgi_ts); + return -EBUSY; + } + + /* Enable Falling Edge */ + set_irq_type(CORGI_IRQ_GPIO_TP_INT, IRQT_FALLING); + + printk(KERN_INFO "input: Corgi Touchscreen Registered\n"); + + return 0; +} + +static int corgits_remove(struct device *dev) +{ + struct corgi_ts *corgi_ts = dev_get_drvdata(dev); + + free_irq(CORGI_IRQ_GPIO_TP_INT, NULL); + del_timer_sync(&corgi_ts->timer); + input_unregister_device(&corgi_ts->input); + kfree(corgi_ts); + return 0; +} + +static struct device_driver corgits_driver = { + .name = "corgi-ts", + .bus = &platform_bus_type, + .probe = corgits_probe, + .remove = corgits_remove, + .suspend = corgits_suspend, + .resume = corgits_resume, +}; + +static int __devinit corgits_init(void) +{ + return driver_register(&corgits_driver); +} + +static void __exit corgits_exit(void) +{ + driver_unregister(&corgits_driver); +} + +module_init(corgits_init); +module_exit(corgits_exit); + +MODULE_AUTHOR("Richard Purdie <rpurdie@rpsys.net>"); +MODULE_DESCRIPTION("Corgi TouchScreen Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/touchscreen/elo.c b/drivers/input/touchscreen/elo.c new file mode 100644 index 000000000000..546ce599334e --- /dev/null +++ b/drivers/input/touchscreen/elo.c @@ -0,0 +1,315 @@ +/* + * Elo serial touchscreen driver + * + * Copyright (c) 2004 Vojtech Pavlik + */ + +/* + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +/* + * This driver can handle serial Elo touchscreens using either the Elo standard + * 'E271-2210' 10-byte protocol, Elo legacy 'E281A-4002' 6-byte protocol, Elo + * legacy 'E271-140' 4-byte protocol and Elo legacy 'E261-280' 3-byte protocol. + */ + +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/serio.h> +#include <linux/init.h> + +#define DRIVER_DESC "Elo serial touchscreen driver" + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +/* + * Definitions & global arrays. + */ + +#define ELO_MAX_LENGTH 10 + +static char *elo_name = "Elo Serial TouchScreen"; + +/* + * Per-touchscreen data. + */ + +struct elo { + struct input_dev dev; + struct serio *serio; + int id; + int idx; + unsigned char csum; + unsigned char data[ELO_MAX_LENGTH]; + char phys[32]; +}; + +static void elo_process_data_10(struct elo* elo, unsigned char data, struct pt_regs *regs) +{ + struct input_dev *dev = &elo->dev; + + elo->csum += elo->data[elo->idx] = data; + + switch (elo->idx++) { + + case 0: + if (data != 'U') { + elo->idx = 0; + elo->csum = 0; + } + break; + + case 1: + if (data != 'T') { + elo->idx = 0; + elo->csum = 0; + } + break; + + case 9: + if (elo->csum) { + input_regs(dev, regs); + input_report_abs(dev, ABS_X, (elo->data[4] << 8) | elo->data[3]); + input_report_abs(dev, ABS_Y, (elo->data[6] << 8) | elo->data[5]); + input_report_abs(dev, ABS_PRESSURE, (elo->data[8] << 8) | elo->data[7]); + input_report_key(dev, BTN_TOUCH, elo->data[2] & 3); + input_sync(dev); + } + elo->idx = 0; + elo->csum = 0; + break; + } +} + +static void elo_process_data_6(struct elo* elo, unsigned char data, struct pt_regs *regs) +{ + struct input_dev *dev = &elo->dev; + + elo->data[elo->idx] = data; + + switch (elo->idx++) { + + case 0: if ((data & 0xc0) != 0xc0) elo->idx = 0; break; + case 1: if ((data & 0xc0) != 0x80) elo->idx = 0; break; + case 2: if ((data & 0xc0) != 0x40) elo->idx = 0; break; + + case 3: + if (data & 0xc0) { + elo->idx = 0; + break; + } + + input_regs(dev, regs); + input_report_abs(dev, ABS_X, ((elo->data[0] & 0x3f) << 6) | (elo->data[1] & 0x3f)); + input_report_abs(dev, ABS_Y, ((elo->data[2] & 0x3f) << 6) | (elo->data[3] & 0x3f)); + + if (elo->id == 2) { + input_report_key(dev, BTN_TOUCH, 1); + input_sync(dev); + elo->idx = 0; + } + + break; + + case 4: + if (data) { + input_sync(dev); + elo->idx = 0; + } + break; + + case 5: + if ((data & 0xf0) == 0) { + input_report_abs(dev, ABS_PRESSURE, elo->data[5]); + input_report_key(dev, BTN_TOUCH, elo->data[5]); + } + input_sync(dev); + elo->idx = 0; + break; + } +} + +static void elo_process_data_3(struct elo* elo, unsigned char data, struct pt_regs *regs) +{ + struct input_dev *dev = &elo->dev; + + elo->data[elo->idx] = data; + + switch (elo->idx++) { + + case 0: + if ((data & 0x7f) != 0x01) + elo->idx = 0; + break; + case 2: + input_regs(dev, regs); + input_report_key(dev, BTN_TOUCH, !(elo->data[1] & 0x80)); + input_report_abs(dev, ABS_X, elo->data[1]); + input_report_abs(dev, ABS_Y, elo->data[2]); + input_sync(dev); + elo->idx = 0; + break; + } +} + +static irqreturn_t elo_interrupt(struct serio *serio, + unsigned char data, unsigned int flags, struct pt_regs *regs) +{ + struct elo* elo = serio_get_drvdata(serio); + + switch(elo->id) { + case 0: + elo_process_data_10(elo, data, regs); + break; + + case 1: + case 2: + elo_process_data_6(elo, data, regs); + break; + + case 3: + elo_process_data_3(elo, data, regs); + break; + } + + return IRQ_HANDLED; +} + +/* + * elo_disconnect() is the opposite of elo_connect() + */ + +static void elo_disconnect(struct serio *serio) +{ + struct elo* elo = serio_get_drvdata(serio); + + input_unregister_device(&elo->dev); + serio_close(serio); + serio_set_drvdata(serio, NULL); + kfree(elo); +} + +/* + * elo_connect() is the routine that is called when someone adds a + * new serio device that supports Gunze protocol and registers it as + * an input device. + */ + +static int elo_connect(struct serio *serio, struct serio_driver *drv) +{ + struct elo *elo; + int err; + + if (!(elo = kmalloc(sizeof(struct elo), GFP_KERNEL))) + return -ENOMEM; + + memset(elo, 0, sizeof(struct elo)); + + init_input_dev(&elo->dev); + elo->dev.evbit[0] = BIT(EV_KEY) | BIT(EV_ABS); + elo->dev.keybit[LONG(BTN_TOUCH)] = BIT(BTN_TOUCH); + + elo->id = serio->id.id; + + switch (elo->id) { + + case 0: /* 10-byte protocol */ + input_set_abs_params(&elo->dev, ABS_X, 96, 4000, 0, 0); + input_set_abs_params(&elo->dev, ABS_Y, 96, 4000, 0, 0); + input_set_abs_params(&elo->dev, ABS_PRESSURE, 0, 255, 0, 0); + break; + + case 1: /* 6-byte protocol */ + input_set_abs_params(&elo->dev, ABS_PRESSURE, 0, 15, 0, 0); + + case 2: /* 4-byte protocol */ + input_set_abs_params(&elo->dev, ABS_X, 96, 4000, 0, 0); + input_set_abs_params(&elo->dev, ABS_Y, 96, 4000, 0, 0); + break; + + case 3: /* 3-byte protocol */ + input_set_abs_params(&elo->dev, ABS_X, 0, 255, 0, 0); + input_set_abs_params(&elo->dev, ABS_Y, 0, 255, 0, 0); + break; + } + + elo->serio = serio; + + sprintf(elo->phys, "%s/input0", serio->phys); + + elo->dev.private = elo; + elo->dev.name = elo_name; + elo->dev.phys = elo->phys; + elo->dev.id.bustype = BUS_RS232; + elo->dev.id.vendor = SERIO_ELO; + elo->dev.id.product = elo->id; + elo->dev.id.version = 0x0100; + + serio_set_drvdata(serio, elo); + + err = serio_open(serio, drv); + if (err) { + serio_set_drvdata(serio, NULL); + kfree(elo); + return err; + } + + input_register_device(&elo->dev); + + printk(KERN_INFO "input: %s on %s\n", elo_name, serio->phys); + + return 0; +} + +/* + * The serio driver structure. + */ + +static struct serio_device_id elo_serio_ids[] = { + { + .type = SERIO_RS232, + .proto = SERIO_ELO, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { 0 } +}; + +MODULE_DEVICE_TABLE(serio, elo_serio_ids); + +static struct serio_driver elo_drv = { + .driver = { + .name = "elo", + }, + .description = DRIVER_DESC, + .id_table = elo_serio_ids, + .interrupt = elo_interrupt, + .connect = elo_connect, + .disconnect = elo_disconnect, +}; + +/* + * The functions for inserting/removing us as a module. + */ + +static int __init elo_init(void) +{ + serio_register_driver(&elo_drv); + return 0; +} + +static void __exit elo_exit(void) +{ + serio_unregister_driver(&elo_drv); +} + +module_init(elo_init); +module_exit(elo_exit); diff --git a/drivers/input/touchscreen/gunze.c b/drivers/input/touchscreen/gunze.c new file mode 100644 index 000000000000..c9d0a153671c --- /dev/null +++ b/drivers/input/touchscreen/gunze.c @@ -0,0 +1,205 @@ +/* + * $Id: gunze.c,v 1.12 2001/09/25 10:12:07 vojtech Exp $ + * + * Copyright (c) 2000-2001 Vojtech Pavlik + */ + +/* + * Gunze AHL-51S touchscreen driver for Linux + */ + +/* + * 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 either by + * e-mail - mail your message to <vojtech@ucw.cz>, or by paper mail: + * Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic + */ + +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/serio.h> +#include <linux/init.h> + +#define DRIVER_DESC "Gunze AHL-51S touchscreen driver" + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +/* + * Definitions & global arrays. + */ + +#define GUNZE_MAX_LENGTH 10 + +static char *gunze_name = "Gunze AHL-51S TouchScreen"; + +/* + * Per-touchscreen data. + */ + +struct gunze { + struct input_dev dev; + struct serio *serio; + int idx; + unsigned char data[GUNZE_MAX_LENGTH]; + char phys[32]; +}; + +static void gunze_process_packet(struct gunze* gunze, struct pt_regs *regs) +{ + struct input_dev *dev = &gunze->dev; + + if (gunze->idx != GUNZE_MAX_LENGTH || gunze->data[5] != ',' || + (gunze->data[0] != 'T' && gunze->data[0] != 'R')) { + gunze->data[10] = 0; + printk(KERN_WARNING "gunze.c: bad packet: >%s<\n", gunze->data); + return; + } + + input_regs(dev, regs); + input_report_abs(dev, ABS_X, simple_strtoul(gunze->data + 1, NULL, 10)); + input_report_abs(dev, ABS_Y, 1024 - simple_strtoul(gunze->data + 6, NULL, 10)); + input_report_key(dev, BTN_TOUCH, gunze->data[0] == 'T'); + input_sync(dev); +} + +static irqreturn_t gunze_interrupt(struct serio *serio, + unsigned char data, unsigned int flags, struct pt_regs *regs) +{ + struct gunze* gunze = serio_get_drvdata(serio); + + if (data == '\r') { + gunze_process_packet(gunze, regs); + gunze->idx = 0; + } else { + if (gunze->idx < GUNZE_MAX_LENGTH) + gunze->data[gunze->idx++] = data; + } + return IRQ_HANDLED; +} + +/* + * gunze_disconnect() is the opposite of gunze_connect() + */ + +static void gunze_disconnect(struct serio *serio) +{ + struct gunze* gunze = serio_get_drvdata(serio); + + input_unregister_device(&gunze->dev); + serio_close(serio); + serio_set_drvdata(serio, NULL); + kfree(gunze); +} + +/* + * gunze_connect() is the routine that is called when someone adds a + * new serio device that supports Gunze protocol and registers it as + * an input device. + */ + +static int gunze_connect(struct serio *serio, struct serio_driver *drv) +{ + struct gunze *gunze; + int err; + + if (!(gunze = kmalloc(sizeof(struct gunze), GFP_KERNEL))) + return -ENOMEM; + + memset(gunze, 0, sizeof(struct gunze)); + + init_input_dev(&gunze->dev); + gunze->dev.evbit[0] = BIT(EV_KEY) | BIT(EV_ABS); + gunze->dev.keybit[LONG(BTN_TOUCH)] = BIT(BTN_TOUCH); + input_set_abs_params(&gunze->dev, ABS_X, 24, 1000, 0, 0); + input_set_abs_params(&gunze->dev, ABS_Y, 24, 1000, 0, 0); + + gunze->serio = serio; + + sprintf(gunze->phys, "%s/input0", serio->phys); + + gunze->dev.private = gunze; + gunze->dev.name = gunze_name; + gunze->dev.phys = gunze->phys; + gunze->dev.id.bustype = BUS_RS232; + gunze->dev.id.vendor = SERIO_GUNZE; + gunze->dev.id.product = 0x0051; + gunze->dev.id.version = 0x0100; + + serio_set_drvdata(serio, gunze); + + err = serio_open(serio, drv); + if (err) { + serio_set_drvdata(serio, NULL); + kfree(gunze); + return err; + } + + input_register_device(&gunze->dev); + + printk(KERN_INFO "input: %s on %s\n", gunze_name, serio->phys); + + return 0; +} + +/* + * The serio driver structure. + */ + +static struct serio_device_id gunze_serio_ids[] = { + { + .type = SERIO_RS232, + .proto = SERIO_GUNZE, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { 0 } +}; + +MODULE_DEVICE_TABLE(serio, gunze_serio_ids); + +static struct serio_driver gunze_drv = { + .driver = { + .name = "gunze", + }, + .description = DRIVER_DESC, + .id_table = gunze_serio_ids, + .interrupt = gunze_interrupt, + .connect = gunze_connect, + .disconnect = gunze_disconnect, +}; + +/* + * The functions for inserting/removing us as a module. + */ + +static int __init gunze_init(void) +{ + serio_register_driver(&gunze_drv); + return 0; +} + +static void __exit gunze_exit(void) +{ + serio_unregister_driver(&gunze_drv); +} + +module_init(gunze_init); +module_exit(gunze_exit); diff --git a/drivers/input/touchscreen/h3600_ts_input.c b/drivers/input/touchscreen/h3600_ts_input.c new file mode 100644 index 000000000000..acb9137a0226 --- /dev/null +++ b/drivers/input/touchscreen/h3600_ts_input.c @@ -0,0 +1,528 @@ +/* + * $Id: h3600_ts_input.c,v 1.4 2002/01/23 06:39:37 jsimmons Exp $ + * + * Copyright (c) 2001 "Crazy" James Simmons jsimmons@transvirtual.com + * + * Sponsored by Transvirtual Technology. + * + * Derived from the code in h3600_ts.[ch] by Charles Flynn + */ + +/* + * Driver for the h3600 Touch Screen and other Atmel controlled devices. + */ + +/* + * 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 <jsimmons@transvirtual.com>. + */ + +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/serio.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> + +/* SA1100 serial defines */ +#include <asm/arch/hardware.h> +#include <asm/arch/irqs.h> + +#define DRIVER_DESC "H3600 touchscreen driver" + +MODULE_AUTHOR("James Simmons <jsimmons@transvirtual.com>"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +/* + * Definitions & global arrays. + */ + +/* The start and end of frame characters SOF and EOF */ +#define CHAR_SOF 0x02 +#define CHAR_EOF 0x03 +#define FRAME_OVERHEAD 3 /* CHAR_SOF,CHAR_EOF,LENGTH = 3 */ + +/* + Atmel events and response IDs contained in frame. + Programmer has no control over these numbers. + TODO there are holes - specifically 1,7,0x0a +*/ +#define VERSION_ID 0 /* Get Version (request/respose) */ +#define KEYBD_ID 2 /* Keyboard (event) */ +#define TOUCHS_ID 3 /* Touch Screen (event)*/ +#define EEPROM_READ_ID 4 /* (request/response) */ +#define EEPROM_WRITE_ID 5 /* (request/response) */ +#define THERMAL_ID 6 /* (request/response) */ +#define NOTIFY_LED_ID 8 /* (request/response) */ +#define BATTERY_ID 9 /* (request/response) */ +#define SPI_READ_ID 0x0b /* ( request/response) */ +#define SPI_WRITE_ID 0x0c /* ( request/response) */ +#define FLITE_ID 0x0d /* backlight ( request/response) */ +#define STX_ID 0xa1 /* extension pack status (req/resp) */ + +#define MAX_ID 14 + +#define H3600_MAX_LENGTH 16 +#define H3600_KEY 0xf + +#define H3600_SCANCODE_RECORD 1 /* 1 -> record button */ +#define H3600_SCANCODE_CALENDAR 2 /* 2 -> calendar */ +#define H3600_SCANCODE_CONTACTS 3 /* 3 -> contact */ +#define H3600_SCANCODE_Q 4 /* 4 -> Q button */ +#define H3600_SCANCODE_START 5 /* 5 -> start menu */ +#define H3600_SCANCODE_UP 6 /* 6 -> up */ +#define H3600_SCANCODE_RIGHT 7 /* 7 -> right */ +#define H3600_SCANCODE_LEFT 8 /* 8 -> left */ +#define H3600_SCANCODE_DOWN 9 /* 9 -> down */ + +static char *h3600_name = "H3600 TouchScreen"; + +/* + * Per-touchscreen data. + */ +struct h3600_dev { + struct input_dev dev; + struct pm_dev *pm_dev; + struct serio *serio; + struct pm_dev *pm_dev; + unsigned char event; /* event ID from packet */ + unsigned char chksum; + unsigned char len; + unsigned char idx; + unsigned char buf[H3600_MAX_LENGTH]; + char phys[32]; +}; + +static irqreturn_t action_button_handler(int irq, void *dev_id, struct pt_regs *regs) +{ + int down = (GPLR & GPIO_BITSY_ACTION_BUTTON) ? 0 : 1; + struct input_dev *dev = (struct input_dev *) dev_id; + + input_regs(dev, regs); + input_report_key(dev, KEY_ENTER, down); + input_sync(dev); + + return IRQ_HANDLED; +} + +static irqreturn_t npower_button_handler(int irq, void *dev_id, struct pt_regs *regs) +{ + int down = (GPLR & GPIO_BITSY_NPOWER_BUTTON) ? 0 : 1; + struct input_dev *dev = (struct input_dev *) dev_id; + + /* + * This interrupt is only called when we release the key. So we have + * to fake a key press. + */ + input_regs(dev, regs); + input_report_key(dev, KEY_SUSPEND, 1); + input_report_key(dev, KEY_SUSPEND, down); + input_sync(dev); + + return IRQ_HANDLED; +} + +#ifdef CONFIG_PM + +static int flite_brightness = 25; + +enum flite_pwr { + FLITE_PWR_OFF = 0, + FLITE_PWR_ON = 1 +}; + +/* + * h3600_flite_power: enables or disables power to frontlight, using last bright */ +unsigned int h3600_flite_power(struct input_dev *dev, enum flite_pwr pwr) +{ + unsigned char brightness = (pwr == FLITE_PWR_OFF) ? 0 : flite_brightness; + struct h3600_dev *ts = dev->private; + + /* Must be in this order */ + ts->serio->write(ts->serio, 1); + ts->serio->write(ts->serio, pwr); + ts->serio->write(ts->serio, brightness); + return 0; +} + +static int suspended = 0; +static int h3600ts_pm_callback(struct pm_dev *pm_dev, pm_request_t req, + void *data) +{ + struct input_dev *dev = (struct input_dev *) data; + + switch (req) { + case PM_SUSPEND: /* enter D1-D3 */ + suspended = 1; + h3600_flite_power(dev, FLITE_PWR_OFF); + break; + case PM_BLANK: + if (!suspended) + h3600_flite_power(dev, FLITE_PWR_OFF); + break; + case PM_RESUME: /* enter D0 */ + /* same as unblank */ + case PM_UNBLANK: + if (suspended) { + //initSerial(); + suspended = 0; + } + h3600_flite_power(dev, FLITE_PWR_ON); + break; + } + return 0; +} +#endif + +/* + * This function translates the native event packets to linux input event + * packets. Some packets coming from serial are not touchscreen related. In + * this case we send them off to be processed elsewhere. + */ +static void h3600ts_process_packet(struct h3600_dev *ts, struct pt_regs *regs) +{ + struct input_dev *dev = &ts->dev; + static int touched = 0; + int key, down = 0; + + input_regs(dev, regs); + + switch (ts->event) { + /* + Buttons - returned as a single byte + 7 6 5 4 3 2 1 0 + S x x x N N N N + + S switch state ( 0=pressed 1=released) + x Unused. + NNNN switch number 0-15 + + Note: This is true for non interrupt generated key events. + */ + case KEYBD_ID: + down = (ts->buf[0] & 0x80) ? 0 : 1; + + switch (ts->buf[0] & 0x7f) { + case H3600_SCANCODE_RECORD: + key = KEY_RECORD; + break; + case H3600_SCANCODE_CALENDAR: + key = KEY_PROG1; + break; + case H3600_SCANCODE_CONTACTS: + key = KEY_PROG2; + break; + case H3600_SCANCODE_Q: + key = KEY_Q; + break; + case H3600_SCANCODE_START: + key = KEY_PROG3; + break; + case H3600_SCANCODE_UP: + key = KEY_UP; + break; + case H3600_SCANCODE_RIGHT: + key = KEY_RIGHT; + break; + case H3600_SCANCODE_LEFT: + key = KEY_LEFT; + break; + case H3600_SCANCODE_DOWN: + key = KEY_DOWN; + break; + default: + key = 0; + } + if (key) + input_report_key(dev, key, down); + break; + /* + * Native touchscreen event data is formatted as shown below:- + * + * +-------+-------+-------+-------+ + * | Xmsb | Xlsb | Ymsb | Ylsb | + * +-------+-------+-------+-------+ + * byte 0 1 2 3 + */ + case TOUCHS_ID: + if (!touched) { + input_report_key(dev, BTN_TOUCH, 1); + touched = 1; + } + + if (ts->len) { + unsigned short x, y; + + x = ts->buf[0]; x <<= 8; x += ts->buf[1]; + y = ts->buf[2]; y <<= 8; y += ts->buf[3]; + + input_report_abs(dev, ABS_X, x); + input_report_abs(dev, ABS_Y, y); + } else { + input_report_key(dev, BTN_TOUCH, 0); + touched = 0; + } + break; + default: + /* Send a non input event elsewhere */ + break; + } + + input_sync(dev); +} + +/* + * h3600ts_event() handles events from the input module. + */ +static int h3600ts_event(struct input_dev *dev, unsigned int type, + unsigned int code, int value) +{ + struct h3600_dev *ts = dev->private; + + switch (type) { + case EV_LED: { + // ts->serio->write(ts->serio, SOME_CMD); + return 0; + } + } + return -1; +} + +/* + Frame format + byte 1 2 3 len + 4 + +-------+---------------+---------------+--=------------+ + |SOF |id |len | len bytes | Chksum | + +-------+---------------+---------------+--=------------+ + bit 0 7 8 11 12 15 16 + + +-------+---------------+-------+ + |SOF |id |0 |Chksum | - Note Chksum does not include SOF + +-------+---------------+-------+ + bit 0 7 8 11 12 15 16 + +*/ + +static int state; + +/* decode States */ +#define STATE_SOF 0 /* start of FRAME */ +#define STATE_ID 1 /* state where we decode the ID & len */ +#define STATE_DATA 2 /* state where we decode data */ +#define STATE_EOF 3 /* state where we decode checksum or EOF */ + +static irqreturn_t h3600ts_interrupt(struct serio *serio, unsigned char data, + unsigned int flags, struct pt_regs *regs) +{ + struct h3600_dev *ts = serio_get_drvdata(serio); + + /* + * We have a new frame coming in. + */ + switch (state) { + case STATE_SOF: + if (data == CHAR_SOF) + state = STATE_ID; + break; + case STATE_ID: + ts->event = (data & 0xf0) >> 4; + ts->len = (data & 0xf); + ts->idx = 0; + if (ts->event >= MAX_ID) { + state = STATE_SOF; + break; + } + ts->chksum = data; + state = (ts->len > 0) ? STATE_DATA : STATE_EOF; + break; + case STATE_DATA: + ts->chksum += data; + ts->buf[ts->idx]= data; + if(++ts->idx == ts->len) + state = STATE_EOF; + break; + case STATE_EOF: + state = STATE_SOF; + if (data == CHAR_EOF || data == ts->chksum) + h3600ts_process_packet(ts, regs); + break; + default: + printk("Error3\n"); + break; + } + + return IRQ_HANDLED; +} + +/* + * h3600ts_connect() is the routine that is called when someone adds a + * new serio device that supports H3600 protocol and registers it as + * an input device. + */ +static int h3600ts_connect(struct serio *serio, struct serio_driver *drv) +{ + struct h3600_dev *ts; + int err; + + if (!(ts = kmalloc(sizeof(struct h3600_dev), GFP_KERNEL))) + return -ENOMEM; + + memset(ts, 0, sizeof(struct h3600_dev)); + + init_input_dev(&ts->dev); + + /* Device specific stuff */ + set_GPIO_IRQ_edge(GPIO_BITSY_ACTION_BUTTON, GPIO_BOTH_EDGES); + set_GPIO_IRQ_edge(GPIO_BITSY_NPOWER_BUTTON, GPIO_RISING_EDGE); + + if (request_irq(IRQ_GPIO_BITSY_ACTION_BUTTON, action_button_handler, + SA_SHIRQ | SA_INTERRUPT | SA_SAMPLE_RANDOM, + "h3600_action", &ts->dev)) { + printk(KERN_ERR "h3600ts.c: Could not allocate Action Button IRQ!\n"); + kfree(ts); + return -EBUSY; + } + + if (request_irq(IRQ_GPIO_BITSY_NPOWER_BUTTON, npower_button_handler, + SA_SHIRQ | SA_INTERRUPT | SA_SAMPLE_RANDOM, + "h3600_suspend", &ts->dev)) { + free_irq(IRQ_GPIO_BITSY_ACTION_BUTTON, &ts->dev); + printk(KERN_ERR "h3600ts.c: Could not allocate Power Button IRQ!\n"); + kfree(ts); + return -EBUSY; + } + + /* Now we have things going we setup our input device */ + ts->dev.evbit[0] = BIT(EV_KEY) | BIT(EV_ABS) | BIT(EV_LED) | BIT(EV_PWR); + ts->dev.ledbit[0] = BIT(LED_SLEEP); + input_set_abs_params(&ts->dev, ABS_X, 60, 985, 0, 0); + input_set_abs_params(&ts->dev, ABS_Y, 35, 1024, 0, 0); + + set_bit(KEY_RECORD, ts->dev.keybit); + set_bit(KEY_Q, ts->dev.keybit); + set_bit(KEY_PROG1, ts->dev.keybit); + set_bit(KEY_PROG2, ts->dev.keybit); + set_bit(KEY_PROG3, ts->dev.keybit); + set_bit(KEY_UP, ts->dev.keybit); + set_bit(KEY_RIGHT, ts->dev.keybit); + set_bit(KEY_LEFT, ts->dev.keybit); + set_bit(KEY_DOWN, ts->dev.keybit); + set_bit(KEY_ENTER, ts->dev.keybit); + ts->dev.keybit[LONG(BTN_TOUCH)] |= BIT(BTN_TOUCH); + ts->dev.keybit[LONG(KEY_SUSPEND)] |= BIT(KEY_SUSPEND); + + ts->serio = serio; + + sprintf(ts->phys, "%s/input0", serio->phys); + + ts->dev.event = h3600ts_event; + ts->dev.private = ts; + ts->dev.name = h3600_name; + ts->dev.phys = ts->phys; + ts->dev.id.bustype = BUS_RS232; + ts->dev.id.vendor = SERIO_H3600; + ts->dev.id.product = 0x0666; /* FIXME !!! We can ask the hardware */ + ts->dev.id.version = 0x0100; + + serio_set_drvdata(serio, ts); + + err = serio_open(serio, drv); + if (err) { + free_irq(IRQ_GPIO_BITSY_ACTION_BUTTON, ts); + free_irq(IRQ_GPIO_BITSY_NPOWER_BUTTON, ts); + serio_set_drvdata(serio, NULL); + kfree(ts); + return err; + } + + //h3600_flite_control(1, 25); /* default brightness */ +#ifdef CONFIG_PM + ts->pm_dev = pm_register(PM_ILLUMINATION_DEV, PM_SYS_LIGHT, + h3600ts_pm_callback); + printk("registered pm callback\n"); +#endif + input_register_device(&ts->dev); + + printk(KERN_INFO "input: %s on %s\n", h3600_name, serio->phys); + + return 0; +} + +/* + * h3600ts_disconnect() is the opposite of h3600ts_connect() + */ + +static void h3600ts_disconnect(struct serio *serio) +{ + struct h3600_dev *ts = serio_get_drvdata(serio); + + free_irq(IRQ_GPIO_BITSY_ACTION_BUTTON, &ts->dev); + free_irq(IRQ_GPIO_BITSY_NPOWER_BUTTON, &ts->dev); + input_unregister_device(&ts->dev); + serio_close(serio); + serio_set_drvdata(serio, NULL); + kfree(ts); +} + +/* + * The serio driver structure. + */ + +static struct serio_device_id h3600ts_serio_ids[] = { + { + .type = SERIO_RS232, + .proto = SERIO_H3600, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { 0 } +}; + +MODULE_DEVICE_TABLE(serio, h3600ts_serio_ids); + +static struct serio_driver h3600ts_drv = { + .driver = { + .name = "h3600ts", + }, + .description = DRIVER_DESC, + .id_table = h3600ts_serio_ids, + .interrupt = h3600ts_interrupt, + .connect = h3600ts_connect, + .disconnect = h3600ts_disconnect, +}; + +/* + * The functions for inserting/removing us as a module. + */ + +static int __init h3600ts_init(void) +{ + serio_register_driver(&h3600ts_drv); + return 0; +} + +static void __exit h3600ts_exit(void) +{ + serio_unregister_driver(&h3600ts_drv); +} + +module_init(h3600ts_init); +module_exit(h3600ts_exit); diff --git a/drivers/input/touchscreen/hp680_ts_input.c b/drivers/input/touchscreen/hp680_ts_input.c new file mode 100644 index 000000000000..7e1404441eca --- /dev/null +++ b/drivers/input/touchscreen/hp680_ts_input.c @@ -0,0 +1,135 @@ +#include <linux/input.h> +#include <linux/module.h> +#include <linux/init.h> + +#include <linux/interrupt.h> +#include <asm/io.h> +#include <asm/delay.h> +#include <asm/adc.h> +#include <asm/hp6xx/hp6xx.h> + +#define MODNAME "hp680_ts_input" + +#define HP680_TS_ABS_X_MIN 40 +#define HP680_TS_ABS_X_MAX 950 +#define HP680_TS_ABS_Y_MIN 80 +#define HP680_TS_ABS_Y_MAX 910 + +#define SCPCR 0xa4000116 +#define PHDR 0xa400012e +#define SCPDR 0xa4000136 + +static void do_softint(void *data); + +static struct input_dev hp680_ts_dev; +static DECLARE_WORK(work, do_softint, 0); +static char *hp680_ts_name = "HP Jornada touchscreen"; +static char *hp680_ts_phys = "input0"; + +static void do_softint(void *data) +{ + int absx = 0, absy = 0; + u8 scpdr; + int touched = 0; + + if (ctrl_inb(PHDR) & PHDR_TS_PEN_DOWN) { + scpdr = ctrl_inb(SCPDR); + scpdr |= SCPDR_TS_SCAN_ENABLE; + scpdr &= ~SCPDR_TS_SCAN_Y; + ctrl_outb(scpdr, SCPDR); + udelay(30); + + absy = adc_single(ADC_CHANNEL_TS_Y); + + scpdr = ctrl_inb(SCPDR); + scpdr |= SCPDR_TS_SCAN_Y; + scpdr &= ~SCPDR_TS_SCAN_X; + ctrl_outb(scpdr, SCPDR); + udelay(30); + + absx = adc_single(ADC_CHANNEL_TS_X); + + scpdr = ctrl_inb(SCPDR); + scpdr |= SCPDR_TS_SCAN_X; + scpdr &= ~SCPDR_TS_SCAN_ENABLE; + ctrl_outb(scpdr, SCPDR); + udelay(100); + touched = ctrl_inb(PHDR) & PHDR_TS_PEN_DOWN; + } + + if (touched) { + input_report_key(&hp680_ts_dev, BTN_TOUCH, 1); + input_report_abs(&hp680_ts_dev, ABS_X, absx); + input_report_abs(&hp680_ts_dev, ABS_Y, absy); + } else { + input_report_key(&hp680_ts_dev, BTN_TOUCH, 0); + } + + input_sync(&hp680_ts_dev); + enable_irq(HP680_TS_IRQ); +} + +static irqreturn_t hp680_ts_interrupt(int irq, void *dev, struct pt_regs *regs) +{ + disable_irq_nosync(irq); + schedule_delayed_work(&work, HZ / 20); + + return IRQ_HANDLED; +} + +static int __init hp680_ts_init(void) +{ + u8 scpdr; + u16 scpcr; + + scpdr = ctrl_inb(SCPDR); + scpdr |= SCPDR_TS_SCAN_X | SCPDR_TS_SCAN_Y; + scpdr &= ~SCPDR_TS_SCAN_ENABLE; + ctrl_outb(scpdr, SCPDR); + + scpcr = ctrl_inw(SCPCR); + scpcr &= ~SCPCR_TS_MASK; + scpcr |= SCPCR_TS_ENABLE; + ctrl_outw(scpcr, SCPCR); + + memset(&hp680_ts_dev, 0, sizeof(hp680_ts_dev)); + init_input_dev(&hp680_ts_dev); + + hp680_ts_dev.evbit[0] = BIT(EV_ABS) | BIT(EV_KEY); + hp680_ts_dev.absbit[0] = BIT(ABS_X) | BIT(ABS_Y); + hp680_ts_dev.keybit[LONG(BTN_TOUCH)] = BIT(BTN_TOUCH); + + hp680_ts_dev.absmin[ABS_X] = HP680_TS_ABS_X_MIN; + hp680_ts_dev.absmin[ABS_Y] = HP680_TS_ABS_Y_MIN; + hp680_ts_dev.absmax[ABS_X] = HP680_TS_ABS_X_MAX; + hp680_ts_dev.absmax[ABS_Y] = HP680_TS_ABS_Y_MAX; + + hp680_ts_dev.name = hp680_ts_name; + hp680_ts_dev.phys = hp680_ts_phys; + input_register_device(&hp680_ts_dev); + + if (request_irq + (HP680_TS_IRQ, hp680_ts_interrupt, SA_INTERRUPT, MODNAME, 0) < 0) { + printk(KERN_ERR "hp680_touchscreen.c : Can't allocate irq %d\n", + HP680_TS_IRQ); + input_unregister_device(&hp680_ts_dev); + return -EBUSY; + } + + return 0; +} + +static void __exit hp680_ts_exit(void) +{ + free_irq(HP680_TS_IRQ, 0); + cancel_delayed_work(&work); + flush_scheduled_work(); + input_unregister_device(&hp680_ts_dev); +} + +module_init(hp680_ts_init); +module_exit(hp680_ts_exit); + +MODULE_AUTHOR("Andriy Skulysh, askulysh@image.kiev.ua"); +MODULE_DESCRIPTION("HP Jornada 680 touchscreen driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/touchscreen/mk712.c b/drivers/input/touchscreen/mk712.c new file mode 100644 index 000000000000..2d14a57a05e5 --- /dev/null +++ b/drivers/input/touchscreen/mk712.c @@ -0,0 +1,222 @@ +/* + * ICS MK712 touchscreen controller driver + * + * Copyright (c) 1999-2002 Transmeta Corporation + * Copyright (c) 2005 Rick Koch <n1gp@hotmail.com> + * Copyright (c) 2005 Vojtech Pavlik <vojtech@suse.cz> + */ + +/* + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +/* + * This driver supports the ICS MicroClock MK712 TouchScreen controller, + * found in Gateway AOL Connected Touchpad computers. + * + * Documentation for ICS MK712 can be found at: + * http://www.icst.com/pdf/mk712.pdf + */ + +/* + * 1999-12-18: original version, Daniel Quinlan + * 1999-12-19: added anti-jitter code, report pen-up events, fixed mk712_poll + * to use queue_empty, Nathan Laredo + * 1999-12-20: improved random point rejection, Nathan Laredo + * 2000-01-05: checked in new anti-jitter code, changed mouse protocol, fixed + * queue code, added module options, other fixes, Daniel Quinlan + * 2002-03-15: Clean up for kernel merge <alan@redhat.com> + * Fixed multi open race, fixed memory checks, fixed resource + * allocation, fixed close/powerdown bug, switched to new init + * 2005-01-18: Ported to 2.6 from 2.4.28, Rick Koch + * 2005-02-05: Rewritten for the input layer, Vojtech Pavlik + * + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/errno.h> +#include <linux/delay.h> +#include <linux/ioport.h> +#include <linux/interrupt.h> +#include <linux/input.h> +#include <asm/io.h> + +MODULE_AUTHOR("Daniel Quinlan <quinlan@pathname.com>, Vojtech Pavlik <vojtech@suse.cz>"); +MODULE_DESCRIPTION("ICS MicroClock MK712 TouchScreen driver"); +MODULE_LICENSE("GPL"); + +static unsigned int mk712_io = 0x260; /* Also 0x200, 0x208, 0x300 */ +module_param_named(io, mk712_io, uint, 0); +MODULE_PARM_DESC(io, "I/O base address of MK712 touchscreen controller"); + +static unsigned int mk712_irq = 10; /* Also 12, 14, 15 */ +module_param_named(irq, mk712_irq, uint, 0); +MODULE_PARM_DESC(irq, "IRQ of MK712 touchscreen controller"); + +/* eight 8-bit registers */ +#define MK712_STATUS 0 +#define MK712_X 2 +#define MK712_Y 4 +#define MK712_CONTROL 6 +#define MK712_RATE 7 + +/* status */ +#define MK712_STATUS_TOUCH 0x10 +#define MK712_CONVERSION_COMPLETE 0x80 + +/* control */ +#define MK712_ENABLE_INT 0x01 +#define MK712_INT_ON_CONVERSION_COMPLETE 0x02 +#define MK712_INT_ON_CHANGE_IN_TOUCH_STATUS 0x04 +#define MK712_ENABLE_PERIODIC_CONVERSIONS 0x10 +#define MK712_READ_ONE_POINT 0x20 +#define MK712_POWERUP 0x40 + +static int mk712_used = 0; +static struct input_dev mk712_dev; +static DEFINE_SPINLOCK(mk712_lock); + +static irqreturn_t mk712_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + unsigned char status; + static int debounce = 1; + static unsigned short last_x; + static unsigned short last_y; + + spin_lock(&mk712_lock); + input_regs(&mk712_dev, regs); + + status = inb(mk712_io + MK712_STATUS); + + if (~status & MK712_CONVERSION_COMPLETE) { + debounce = 1; + goto end; + } + + if (~status & MK712_STATUS_TOUCH) + { + debounce = 1; + input_report_key(&mk712_dev, BTN_TOUCH, 0); + goto end; + } + + if (debounce) + { + debounce = 0; + goto end; + } + + input_report_key(&mk712_dev, BTN_TOUCH, 1); + input_report_abs(&mk712_dev, ABS_X, last_x); + input_report_abs(&mk712_dev, ABS_Y, last_y); + +end: + + last_x = inw(mk712_io + MK712_X) & 0x0fff; + last_y = inw(mk712_io + MK712_Y) & 0x0fff; + input_sync(&mk712_dev); + spin_unlock(&mk712_lock); + return IRQ_HANDLED; +} + +static int mk712_open(struct input_dev *dev) +{ + unsigned long flags; + + spin_lock_irqsave(&mk712_lock, flags); + + if (!mk712_used++) { + + outb(0, mk712_io + MK712_CONTROL); /* Reset */ + + outb(MK712_ENABLE_INT | MK712_INT_ON_CONVERSION_COMPLETE | + MK712_INT_ON_CHANGE_IN_TOUCH_STATUS | + MK712_ENABLE_PERIODIC_CONVERSIONS | + MK712_POWERUP, mk712_io + MK712_CONTROL); + + outb(10, mk712_io + MK712_RATE); /* 187 points per second */ + } + + spin_unlock_irqrestore(&mk712_lock, flags); + + return 0; +} + +static void mk712_close(struct input_dev *dev) +{ + unsigned long flags; + + spin_lock_irqsave(&mk712_lock, flags); + + if (!--mk712_used) + outb(0, mk712_io + MK712_CONTROL); + + spin_unlock_irqrestore(&mk712_lock, flags); +} + +static struct input_dev mk712_dev = { + .evbit = { BIT(EV_KEY) | BIT(EV_ABS) }, + .keybit = { [LONG(BTN_TOUCH)] = BIT(BTN_TOUCH) }, + .absbit = { BIT(ABS_X) | BIT(ABS_Y) }, + .open = mk712_open, + .close = mk712_close, + .name = "ICS MicroClock MK712 TouchScreen", + .phys = "isa0260/input0", + .absmin = { [ABS_X] = 0, [ABS_Y] = 0 }, + .absmax = { [ABS_X] = 0xfff, [ABS_Y] = 0xfff }, + .absfuzz = { [ABS_X] = 88, [ABS_Y] = 88 }, + .id = { + .bustype = BUS_ISA, + .vendor = 0x0005, + .product = 0x0001, + .version = 0x0100, + }, +}; + +int __init mk712_init(void) +{ + + if(!request_region(mk712_io, 8, "mk712")) + { + printk(KERN_WARNING "mk712: unable to get IO region\n"); + return -ENODEV; + } + + outb(0, mk712_io + MK712_CONTROL); + + if ((inw(mk712_io + MK712_X) & 0xf000) || /* Sanity check */ + (inw(mk712_io + MK712_Y) & 0xf000) || + (inw(mk712_io + MK712_STATUS) & 0xf333)) { + printk(KERN_WARNING "mk712: device not present\n"); + release_region(mk712_io, 8); + return -ENODEV; + } + + if(request_irq(mk712_irq, mk712_interrupt, 0, "mk712", &mk712_dev)) + { + printk(KERN_WARNING "mk712: unable to get IRQ\n"); + release_region(mk712_io, 8); + return -EBUSY; + } + + input_register_device(&mk712_dev); + + printk(KERN_INFO "input: ICS MicroClock MK712 TouchScreen at %#x irq %d\n", mk712_io, mk712_irq); + + return 0; +} + +static void __exit mk712_exit(void) +{ + input_unregister_device(&mk712_dev); + free_irq(mk712_irq, &mk712_dev); + release_region(mk712_io, 8); +} + +module_init(mk712_init); +module_exit(mk712_exit); diff --git a/drivers/input/touchscreen/mtouch.c b/drivers/input/touchscreen/mtouch.c new file mode 100644 index 000000000000..aa8ee7842179 --- /dev/null +++ b/drivers/input/touchscreen/mtouch.c @@ -0,0 +1,219 @@ +/* + * MicroTouch (3M) serial touchscreen driver + * + * Copyright (c) 2004 Vojtech Pavlik + */ + +/* + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +/* + * 2005/02/19 Dan Streetman <ddstreet@ieee.org> + * Copied elo.c and edited for MicroTouch protocol + */ + +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/serio.h> +#include <linux/init.h> + +#define DRIVER_DESC "MicroTouch serial touchscreen driver" + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +/* + * Definitions & global arrays. + */ + +#define MTOUCH_FORMAT_TABLET_STATUS_BIT 0x80 +#define MTOUCH_FORMAT_TABLET_TOUCH_BIT 0x40 +#define MTOUCH_FORMAT_TABLET_LENGTH 5 +#define MTOUCH_RESPONSE_BEGIN_BYTE 0x01 +#define MTOUCH_RESPONSE_END_BYTE 0x0d + +/* todo: check specs for max length of all responses */ +#define MTOUCH_MAX_LENGTH 16 + +#define MTOUCH_MIN_XC 0 +#define MTOUCH_MAX_XC 0x3fff +#define MTOUCH_MIN_YC 0 +#define MTOUCH_MAX_YC 0x3fff + +#define MTOUCH_GET_XC(data) (((data[2])<<7) | data[1]) +#define MTOUCH_GET_YC(data) (((data[4])<<7) | data[3]) +#define MTOUCH_GET_TOUCHED(data) (MTOUCH_FORMAT_TABLET_TOUCH_BIT & data[0]) + +static char *mtouch_name = "MicroTouch Serial TouchScreen"; + +/* + * Per-touchscreen data. + */ + +struct mtouch { + struct input_dev dev; + struct serio *serio; + int idx; + unsigned char data[MTOUCH_MAX_LENGTH]; + char phys[32]; +}; + +static void mtouch_process_format_tablet(struct mtouch *mtouch, struct pt_regs *regs) +{ + struct input_dev *dev = &mtouch->dev; + + if (MTOUCH_FORMAT_TABLET_LENGTH == ++mtouch->idx) { + input_regs(dev, regs); + input_report_abs(dev, ABS_X, MTOUCH_GET_XC(mtouch->data)); + input_report_abs(dev, ABS_Y, MTOUCH_MAX_YC - MTOUCH_GET_YC(mtouch->data)); + input_report_key(dev, BTN_TOUCH, MTOUCH_GET_TOUCHED(mtouch->data)); + input_sync(dev); + + mtouch->idx = 0; + } +} + +static void mtouch_process_response(struct mtouch *mtouch, struct pt_regs *regs) +{ + if (MTOUCH_RESPONSE_END_BYTE == mtouch->data[mtouch->idx++]) { + /* FIXME - process response */ + mtouch->idx = 0; + } else if (MTOUCH_MAX_LENGTH == mtouch->idx) { + printk(KERN_ERR "mtouch.c: too many response bytes\n"); + mtouch->idx = 0; + } +} + +static irqreturn_t mtouch_interrupt(struct serio *serio, + unsigned char data, unsigned int flags, struct pt_regs *regs) +{ + struct mtouch* mtouch = serio_get_drvdata(serio); + + mtouch->data[mtouch->idx] = data; + + if (MTOUCH_FORMAT_TABLET_STATUS_BIT & mtouch->data[0]) + mtouch_process_format_tablet(mtouch, regs); + else if (MTOUCH_RESPONSE_BEGIN_BYTE == mtouch->data[0]) + mtouch_process_response(mtouch, regs); + else + printk(KERN_DEBUG "mtouch.c: unknown/unsynchronized data from device, byte %x\n",mtouch->data[0]); + + return IRQ_HANDLED; +} + +/* + * mtouch_disconnect() is the opposite of mtouch_connect() + */ + +static void mtouch_disconnect(struct serio *serio) +{ + struct mtouch* mtouch = serio_get_drvdata(serio); + + input_unregister_device(&mtouch->dev); + serio_close(serio); + serio_set_drvdata(serio, NULL); + kfree(mtouch); +} + +/* + * mtouch_connect() is the routine that is called when someone adds a + * new serio device that supports MicroTouch (Format Tablet) protocol and registers it as + * an input device. + */ + +static int mtouch_connect(struct serio *serio, struct serio_driver *drv) +{ + struct mtouch *mtouch; + int err; + + if (!(mtouch = kmalloc(sizeof(*mtouch), GFP_KERNEL))) + return -ENOMEM; + + memset(mtouch, 0, sizeof(*mtouch)); + + init_input_dev(&mtouch->dev); + mtouch->dev.evbit[0] = BIT(EV_KEY) | BIT(EV_ABS); + mtouch->dev.keybit[LONG(BTN_TOUCH)] = BIT(BTN_TOUCH); + + input_set_abs_params(&mtouch->dev, ABS_X, MTOUCH_MIN_XC, MTOUCH_MAX_XC, 0, 0); + input_set_abs_params(&mtouch->dev, ABS_Y, MTOUCH_MIN_YC, MTOUCH_MAX_YC, 0, 0); + + mtouch->serio = serio; + + sprintf(mtouch->phys, "%s/input0", serio->phys); + + mtouch->dev.private = mtouch; + mtouch->dev.name = mtouch_name; + mtouch->dev.phys = mtouch->phys; + mtouch->dev.id.bustype = BUS_RS232; + mtouch->dev.id.vendor = SERIO_MICROTOUCH; + mtouch->dev.id.product = 0; + mtouch->dev.id.version = 0x0100; + + serio_set_drvdata(serio, mtouch); + + err = serio_open(serio, drv); + if (err) { + serio_set_drvdata(serio, NULL); + kfree(mtouch); + return err; + } + + input_register_device(&mtouch->dev); + + printk(KERN_INFO "input: %s on %s\n", mtouch->dev.name, serio->phys); + + return 0; +} + +/* + * The serio driver structure. + */ + +static struct serio_device_id mtouch_serio_ids[] = { + { + .type = SERIO_RS232, + .proto = SERIO_MICROTOUCH, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { 0 } +}; + +MODULE_DEVICE_TABLE(serio, mtouch_serio_ids); + +static struct serio_driver mtouch_drv = { + .driver = { + .name = "mtouch", + }, + .description = DRIVER_DESC, + .id_table = mtouch_serio_ids, + .interrupt = mtouch_interrupt, + .connect = mtouch_connect, + .disconnect = mtouch_disconnect, +}; + +/* + * The functions for inserting/removing us as a module. + */ + +static int __init mtouch_init(void) +{ + serio_register_driver(&mtouch_drv); + return 0; +} + +static void __exit mtouch_exit(void) +{ + serio_unregister_driver(&mtouch_drv); +} + +module_init(mtouch_init); +module_exit(mtouch_exit); diff --git a/drivers/input/tsdev.c b/drivers/input/tsdev.c new file mode 100644 index 000000000000..d0afba85720b --- /dev/null +++ b/drivers/input/tsdev.c @@ -0,0 +1,492 @@ +/* + * $Id: tsdev.c,v 1.15 2002/04/10 16:50:19 jsimmons Exp $ + * + * Copyright (c) 2001 "Crazy" james Simmons + * + * Compaq touchscreen protocol driver. The protocol emulated by this driver + * is obsolete; for new programs use the tslib library which can read directly + * from evdev and perform dejittering, variance filtering and calibration - + * all in user space, not at kernel level. The meaning of this driver is + * to allow usage of newer input drivers with old applications that use the + * old /dev/h3600_ts and /dev/h3600_tsraw devices. + * + * 09-Apr-2004: Andrew Zabolotny <zap@homelink.ru> + * Fixed to actually work, not just output random numbers. + * Added support for both h3600_ts and h3600_tsraw protocol + * emulation. + */ + +/* + * 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 either by + * e-mail - mail your message to <jsimmons@infradead.org>. + */ + +#define TSDEV_MINOR_BASE 128 +#define TSDEV_MINORS 32 +/* First 16 devices are h3600_ts compatible; second 16 are h3600_tsraw */ +#define TSDEV_MINOR_MASK 15 +#define TSDEV_BUFFER_SIZE 64 + +#include <linux/slab.h> +#include <linux/poll.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/major.h> +#include <linux/config.h> +#include <linux/smp_lock.h> +#include <linux/random.h> +#include <linux/time.h> +#include <linux/device.h> +#include <linux/devfs_fs_kernel.h> + +#ifndef CONFIG_INPUT_TSDEV_SCREEN_X +#define CONFIG_INPUT_TSDEV_SCREEN_X 240 +#endif +#ifndef CONFIG_INPUT_TSDEV_SCREEN_Y +#define CONFIG_INPUT_TSDEV_SCREEN_Y 320 +#endif + +/* This driver emulates both protocols of the old h3600_ts and h3600_tsraw + * devices. The first one must output X/Y data in 'cooked' format, e.g. + * filtered, dejittered and calibrated. Second device just outputs raw + * data received from the hardware. + * + * This driver doesn't support filtering and dejittering; it supports only + * calibration. Filtering and dejittering must be done in the low-level + * driver, if needed, because it may gain additional benefits from knowing + * the low-level details, the nature of noise and so on. + * + * The driver precomputes a calibration matrix given the initial xres and + * yres values (quite innacurate for most touchscreens) that will result + * in a more or less expected range of output values. The driver supports + * the TS_SET_CAL ioctl, which will replace the calibration matrix with a + * new one, supposedly generated from the values taken from the raw device. + */ + +MODULE_AUTHOR("James Simmons <jsimmons@transvirtual.com>"); +MODULE_DESCRIPTION("Input driver to touchscreen converter"); +MODULE_LICENSE("GPL"); + +static int xres = CONFIG_INPUT_TSDEV_SCREEN_X; +module_param(xres, uint, 0); +MODULE_PARM_DESC(xres, "Horizontal screen resolution (can be negative for X-mirror)"); + +static int yres = CONFIG_INPUT_TSDEV_SCREEN_Y; +module_param(yres, uint, 0); +MODULE_PARM_DESC(yres, "Vertical screen resolution (can be negative for Y-mirror)"); + +/* From Compaq's Touch Screen Specification version 0.2 (draft) */ +struct ts_event { + short pressure; + short x; + short y; + short millisecs; +}; + +struct ts_calibration { + int xscale; + int xtrans; + int yscale; + int ytrans; + int xyswap; +}; + +struct tsdev { + int exist; + int open; + int minor; + char name[8]; + wait_queue_head_t wait; + struct list_head list; + struct input_handle handle; + int x, y, pressure; + struct ts_calibration cal; +}; + +struct tsdev_list { + struct fasync_struct *fasync; + struct list_head node; + struct tsdev *tsdev; + int head, tail; + struct ts_event event[TSDEV_BUFFER_SIZE]; + int raw; +}; + +/* The following ioctl codes are defined ONLY for backward compatibility. + * Don't use tsdev for new developement; use the tslib library instead. + * Touchscreen calibration is a fully userspace task. + */ +/* Use 'f' as magic number */ +#define IOC_H3600_TS_MAGIC 'f' +#define TS_GET_CAL _IOR(IOC_H3600_TS_MAGIC, 10, struct ts_calibration) +#define TS_SET_CAL _IOW(IOC_H3600_TS_MAGIC, 11, struct ts_calibration) + +static struct input_handler tsdev_handler; + +static struct tsdev *tsdev_table[TSDEV_MINORS/2]; + +static int tsdev_fasync(int fd, struct file *file, int on) +{ + struct tsdev_list *list = file->private_data; + int retval; + + retval = fasync_helper(fd, file, on, &list->fasync); + return retval < 0 ? retval : 0; +} + +static int tsdev_open(struct inode *inode, struct file *file) +{ + int i = iminor(inode) - TSDEV_MINOR_BASE; + struct tsdev_list *list; + + if (i >= TSDEV_MINORS || !tsdev_table[i & TSDEV_MINOR_MASK]) + return -ENODEV; + + if (!(list = kmalloc(sizeof(struct tsdev_list), GFP_KERNEL))) + return -ENOMEM; + memset(list, 0, sizeof(struct tsdev_list)); + + list->raw = (i >= TSDEV_MINORS/2) ? 1 : 0; + + i &= TSDEV_MINOR_MASK; + list->tsdev = tsdev_table[i]; + list_add_tail(&list->node, &tsdev_table[i]->list); + file->private_data = list; + + if (!list->tsdev->open++) + if (list->tsdev->exist) + input_open_device(&list->tsdev->handle); + return 0; +} + +static void tsdev_free(struct tsdev *tsdev) +{ + tsdev_table[tsdev->minor] = NULL; + kfree(tsdev); +} + +static int tsdev_release(struct inode *inode, struct file *file) +{ + struct tsdev_list *list = file->private_data; + + tsdev_fasync(-1, file, 0); + list_del(&list->node); + + if (!--list->tsdev->open) { + if (list->tsdev->exist) + input_close_device(&list->tsdev->handle); + else + tsdev_free(list->tsdev); + } + kfree(list); + return 0; +} + +static ssize_t tsdev_read(struct file *file, char __user *buffer, size_t count, + loff_t * ppos) +{ + struct tsdev_list *list = file->private_data; + int retval = 0; + + if (list->head == list->tail && list->tsdev->exist && (file->f_flags & O_NONBLOCK)) + return -EAGAIN; + + retval = wait_event_interruptible(list->tsdev->wait, + list->head != list->tail || !list->tsdev->exist); + + if (retval) + return retval; + + if (!list->tsdev->exist) + return -ENODEV; + + while (list->head != list->tail && + retval + sizeof (struct ts_event) <= count) { + if (copy_to_user (buffer + retval, list->event + list->tail, + sizeof (struct ts_event))) + return -EFAULT; + list->tail = (list->tail + 1) & (TSDEV_BUFFER_SIZE - 1); + retval += sizeof (struct ts_event); + } + + return retval; +} + +/* No kernel lock - fine */ +static unsigned int tsdev_poll(struct file *file, poll_table * wait) +{ + struct tsdev_list *list = file->private_data; + poll_wait(file, &list->tsdev->wait, wait); + return ((list->head == list->tail) ? 0 : (POLLIN | POLLRDNORM)) | + (list->tsdev->exist ? 0 : (POLLHUP | POLLERR)); +} + +static int tsdev_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct tsdev_list *list = file->private_data; + struct tsdev *tsdev = list->tsdev; + int retval = 0; + + switch (cmd) { + case TS_GET_CAL: + if (copy_to_user ((void __user *)arg, &tsdev->cal, + sizeof (struct ts_calibration))) + retval = -EFAULT; + break; + case TS_SET_CAL: + if (copy_from_user (&tsdev->cal, (void __user *)arg, + sizeof (struct ts_calibration))) + retval = -EFAULT; + break; + default: + retval = -EINVAL; + break; + } + + return retval; +} + +static struct file_operations tsdev_fops = { + .owner = THIS_MODULE, + .open = tsdev_open, + .release = tsdev_release, + .read = tsdev_read, + .poll = tsdev_poll, + .fasync = tsdev_fasync, + .ioctl = tsdev_ioctl, +}; + +static void tsdev_event(struct input_handle *handle, unsigned int type, + unsigned int code, int value) +{ + struct tsdev *tsdev = handle->private; + struct tsdev_list *list; + struct timeval time; + + switch (type) { + case EV_ABS: + switch (code) { + case ABS_X: + tsdev->x = value; + break; + case ABS_Y: + tsdev->y = value; + break; + case ABS_PRESSURE: + if (value > handle->dev->absmax[ABS_PRESSURE]) + value = handle->dev->absmax[ABS_PRESSURE]; + value -= handle->dev->absmin[ABS_PRESSURE]; + if (value < 0) + value = 0; + tsdev->pressure = value; + break; + } + break; + + case EV_REL: + switch (code) { + case REL_X: + tsdev->x += value; + if (tsdev->x < 0) + tsdev->x = 0; + else if (tsdev->x > xres) + tsdev->x = xres; + break; + case REL_Y: + tsdev->y += value; + if (tsdev->y < 0) + tsdev->y = 0; + else if (tsdev->y > yres) + tsdev->y = yres; + break; + } + break; + + case EV_KEY: + if (code == BTN_TOUCH || code == BTN_MOUSE) { + switch (value) { + case 0: + tsdev->pressure = 0; + break; + case 1: + if (!tsdev->pressure) + tsdev->pressure = 1; + break; + } + } + break; + } + + if (type != EV_SYN || code != SYN_REPORT) + return; + + list_for_each_entry(list, &tsdev->list, node) { + int x, y, tmp; + + do_gettimeofday(&time); + list->event[list->head].millisecs = time.tv_usec / 100; + list->event[list->head].pressure = tsdev->pressure; + + x = tsdev->x; + y = tsdev->y; + + /* Calibration */ + if (!list->raw) { + x = ((x * tsdev->cal.xscale) >> 8) + tsdev->cal.xtrans; + y = ((y * tsdev->cal.yscale) >> 8) + tsdev->cal.ytrans; + if (tsdev->cal.xyswap) { + tmp = x; x = y; y = tmp; + } + } + + list->event[list->head].x = x; + list->event[list->head].y = y; + list->head = (list->head + 1) & (TSDEV_BUFFER_SIZE - 1); + kill_fasync(&list->fasync, SIGIO, POLL_IN); + } + wake_up_interruptible(&tsdev->wait); +} + +static struct input_handle *tsdev_connect(struct input_handler *handler, + struct input_dev *dev, + struct input_device_id *id) +{ + struct tsdev *tsdev; + int minor, delta; + + for (minor = 0; minor < TSDEV_MINORS/2 && tsdev_table[minor]; + minor++); + if (minor >= TSDEV_MINORS/2) { + printk(KERN_ERR + "tsdev: You have way too many touchscreens\n"); + return NULL; + } + + if (!(tsdev = kmalloc(sizeof(struct tsdev), GFP_KERNEL))) + return NULL; + memset(tsdev, 0, sizeof(struct tsdev)); + + INIT_LIST_HEAD(&tsdev->list); + init_waitqueue_head(&tsdev->wait); + + sprintf(tsdev->name, "ts%d", minor); + + tsdev->exist = 1; + tsdev->minor = minor; + tsdev->handle.dev = dev; + tsdev->handle.name = tsdev->name; + tsdev->handle.handler = handler; + tsdev->handle.private = tsdev; + + /* Precompute the rough calibration matrix */ + delta = dev->absmax [ABS_X] - dev->absmin [ABS_X] + 1; + if (delta == 0) + delta = 1; + tsdev->cal.xscale = (xres << 8) / delta; + tsdev->cal.xtrans = - ((dev->absmin [ABS_X] * tsdev->cal.xscale) >> 8); + + delta = dev->absmax [ABS_Y] - dev->absmin [ABS_Y] + 1; + if (delta == 0) + delta = 1; + tsdev->cal.yscale = (yres << 8) / delta; + tsdev->cal.ytrans = - ((dev->absmin [ABS_Y] * tsdev->cal.yscale) >> 8); + + tsdev_table[minor] = tsdev; + + devfs_mk_cdev(MKDEV(INPUT_MAJOR, TSDEV_MINOR_BASE + minor), + S_IFCHR|S_IRUGO|S_IWUSR, "input/ts%d", minor); + devfs_mk_cdev(MKDEV(INPUT_MAJOR, TSDEV_MINOR_BASE + minor + TSDEV_MINORS/2), + S_IFCHR|S_IRUGO|S_IWUSR, "input/tsraw%d", minor); + class_simple_device_add(input_class, + MKDEV(INPUT_MAJOR, TSDEV_MINOR_BASE + minor), + dev->dev, "ts%d", minor); + + return &tsdev->handle; +} + +static void tsdev_disconnect(struct input_handle *handle) +{ + struct tsdev *tsdev = handle->private; + struct tsdev_list *list; + + class_simple_device_remove(MKDEV(INPUT_MAJOR, TSDEV_MINOR_BASE + tsdev->minor)); + devfs_remove("input/ts%d", tsdev->minor); + devfs_remove("input/tsraw%d", tsdev->minor); + tsdev->exist = 0; + + if (tsdev->open) { + input_close_device(handle); + wake_up_interruptible(&tsdev->wait); + list_for_each_entry(list, &tsdev->list, node) + kill_fasync(&list->fasync, SIGIO, POLL_HUP); + } else + tsdev_free(tsdev); +} + +static struct input_device_id tsdev_ids[] = { + { + .flags = INPUT_DEVICE_ID_MATCH_EVBIT | INPUT_DEVICE_ID_MATCH_KEYBIT | INPUT_DEVICE_ID_MATCH_RELBIT, + .evbit = { BIT(EV_KEY) | BIT(EV_REL) }, + .keybit = { [LONG(BTN_LEFT)] = BIT(BTN_LEFT) }, + .relbit = { BIT(REL_X) | BIT(REL_Y) }, + },/* A mouse like device, at least one button, two relative axes */ + + { + .flags = INPUT_DEVICE_ID_MATCH_EVBIT | INPUT_DEVICE_ID_MATCH_KEYBIT | INPUT_DEVICE_ID_MATCH_ABSBIT, + .evbit = { BIT(EV_KEY) | BIT(EV_ABS) }, + .keybit = { [LONG(BTN_TOUCH)] = BIT(BTN_TOUCH) }, + .absbit = { BIT(ABS_X) | BIT(ABS_Y) }, + },/* A tablet like device, at least touch detection, two absolute axes */ + + { + .flags = INPUT_DEVICE_ID_MATCH_EVBIT | INPUT_DEVICE_ID_MATCH_ABSBIT, + .evbit = { BIT(EV_ABS) }, + .absbit = { BIT(ABS_X) | BIT(ABS_Y) | BIT(ABS_PRESSURE) }, + },/* A tablet like device with several gradations of pressure */ + + {},/* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(input, tsdev_ids); + +static struct input_handler tsdev_handler = { + .event = tsdev_event, + .connect = tsdev_connect, + .disconnect = tsdev_disconnect, + .fops = &tsdev_fops, + .minor = TSDEV_MINOR_BASE, + .name = "tsdev", + .id_table = tsdev_ids, +}; + +static int __init tsdev_init(void) +{ + input_register_handler(&tsdev_handler); + printk(KERN_INFO "ts: Compaq touchscreen protocol output\n"); + return 0; +} + +static void __exit tsdev_exit(void) +{ + input_unregister_handler(&tsdev_handler); +} + +module_init(tsdev_init); +module_exit(tsdev_exit); |