diff options
author | Timothy Pearson <tpearson@raptorengineering.com> | 2017-08-23 14:45:25 -0500 |
---|---|---|
committer | Timothy Pearson <tpearson@raptorengineering.com> | 2017-08-23 14:45:25 -0500 |
commit | fcbb27b0ec6dcbc5a5108cb8fb19eae64593d204 (patch) | |
tree | 22962a4387943edc841c72a4e636a068c66d58fd /drivers/input | |
download | ast2050-linux-kernel-fcbb27b0ec6dcbc5a5108cb8fb19eae64593d204.zip ast2050-linux-kernel-fcbb27b0ec6dcbc5a5108cb8fb19eae64593d204.tar.gz |
Initial import of modified Linux 2.6.28 tree
Original upstream URL:
git://git.kernel.org/pub/scm/linux/kernel/git/stable/linux-stable.git | branch linux-2.6.28.y
Diffstat (limited to 'drivers/input')
198 files changed, 76733 insertions, 0 deletions
diff --git a/drivers/input/Kconfig b/drivers/input/Kconfig new file mode 100644 index 0000000..5f9d860 --- /dev/null +++ b/drivers/input/Kconfig @@ -0,0 +1,186 @@ +# +# Input device configuration +# + +menu "Input device support" + depends on !S390 + +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 + +config INPUT_FF_MEMLESS + tristate "Support for memoryless force-feedback devices" + default n + ---help--- + Say Y here if you have memoryless force-feedback input device + such as Logitech WingMan Force 3D, ThrustMaster FireStorm Dual + Power 2, or similar. You will also need to enable hardware-specific + driver. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called ff-memless. + +config INPUT_POLLDEV + tristate "Polled input device skeleton" + help + Say Y here if you are using a driver for an input + device that periodically polls hardware state. This + option is only useful for out-of-tree drivers since + in-tree drivers select it automatically. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called input-polldev. + +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_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. + +config INPUT_APMPOWER + tristate "Input Power Event -> APM Bridge" if EMBEDDED + depends on INPUT && APM_EMULATION + ---help--- + Say Y here if you want suspend key events to trigger a user + requested suspend through APM. This is useful on embedded + systems where such behviour is desired without userspace + interaction. If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called apm-power. + +config XEN_KBDDEV_FRONTEND + tristate "Xen virtual keyboard and mouse support" + depends on XEN_FBDEV_FRONTEND + default y + help + This driver implements the front-end of the Xen virtual + keyboard and mouse device driver. It communicates with a back-end + in another domain. + +comment "Input Device Drivers" + +source "drivers/input/keyboard/Kconfig" + +source "drivers/input/mouse/Kconfig" + +source "drivers/input/joystick/Kconfig" + +source "drivers/input/tablet/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 0000000..98c4f9a --- /dev/null +++ b/drivers/input/Makefile @@ -0,0 +1,27 @@ +# +# Makefile for the input core drivers. +# + +# Each configuration option enables a list of files. + +obj-$(CONFIG_INPUT) += input-core.o +input-core-objs := input.o ff-core.o + +obj-$(CONFIG_INPUT_FF_MEMLESS) += ff-memless.o +obj-$(CONFIG_INPUT_POLLDEV) += input-polldev.o + +obj-$(CONFIG_INPUT_MOUSEDEV) += mousedev.o +obj-$(CONFIG_INPUT_JOYDEV) += joydev.o +obj-$(CONFIG_INPUT_EVDEV) += evdev.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_TABLET) += tablet/ +obj-$(CONFIG_INPUT_TOUCHSCREEN) += touchscreen/ +obj-$(CONFIG_INPUT_MISC) += misc/ + +obj-$(CONFIG_INPUT_APMPOWER) += apm-power.o + +obj-$(CONFIG_XEN_KBDDEV_FRONTEND) += xen-kbdfront.o diff --git a/drivers/input/apm-power.c b/drivers/input/apm-power.c new file mode 100644 index 0000000..7d61a96 --- /dev/null +++ b/drivers/input/apm-power.c @@ -0,0 +1,128 @@ +/* + * Input Power Event -> APM Bridge + * + * Copyright (c) 2007 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/module.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> +#include <linux/apm-emulation.h> + +static void system_power_event(unsigned int keycode) +{ + switch (keycode) { + case KEY_SUSPEND: + apm_queue_event(APM_USER_SUSPEND); + + printk(KERN_INFO "apm-power: Requesting system suspend...\n"); + break; + default: + break; + } +} + +static void apmpower_event(struct input_handle *handle, unsigned int type, + unsigned int code, int value) +{ + /* only react on key down events */ + if (value != 1) + return; + + switch (type) { + case EV_PWR: + system_power_event(code); + break; + + default: + break; + } +} + +static int apmpower_connect(struct input_handler *handler, + struct input_dev *dev, + const struct input_device_id *id) +{ + struct input_handle *handle; + int error; + + handle = kzalloc(sizeof(struct input_handle), GFP_KERNEL); + if (!handle) + return -ENOMEM; + + handle->dev = dev; + handle->handler = handler; + handle->name = "apm-power"; + + error = input_register_handle(handle); + if (error) { + printk(KERN_ERR + "apm-power: Failed to register input power handler, " + "error %d\n", error); + kfree(handle); + return error; + } + + error = input_open_device(handle); + if (error) { + printk(KERN_ERR + "apm-power: Failed to open input power device, " + "error %d\n", error); + input_unregister_handle(handle); + kfree(handle); + return error; + } + + return 0; +} + +static void apmpower_disconnect(struct input_handle *handle) +{ + input_close_device(handle); + input_unregister_handle(handle); + kfree(handle); +} + +static const struct input_device_id apmpower_ids[] = { + { + .flags = INPUT_DEVICE_ID_MATCH_EVBIT, + .evbit = { BIT_MASK(EV_PWR) }, + }, + { }, +}; + +MODULE_DEVICE_TABLE(input, apmpower_ids); + +static struct input_handler apmpower_handler = { + .event = apmpower_event, + .connect = apmpower_connect, + .disconnect = apmpower_disconnect, + .name = "apm-power", + .id_table = apmpower_ids, +}; + +static int __init apmpower_init(void) +{ + return input_register_handler(&apmpower_handler); +} + +static void __exit apmpower_exit(void) +{ + input_unregister_handler(&apmpower_handler); +} + +module_init(apmpower_init); +module_exit(apmpower_exit); + +MODULE_AUTHOR("Richard Purdie <rpurdie@rpsys.net>"); +MODULE_DESCRIPTION("Input Power Event -> APM Bridge"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/evbug.c b/drivers/input/evbug.c new file mode 100644 index 0000000..0353601 --- /dev/null +++ b/drivers/input/evbug.c @@ -0,0 +1,117 @@ +/* + * 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 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->dev.bus_id, type, code, value); +} + +static int evbug_connect(struct input_handler *handler, struct input_dev *dev, + const struct input_device_id *id) +{ + struct input_handle *handle; + int error; + + handle = kzalloc(sizeof(struct input_handle), GFP_KERNEL); + if (!handle) + return -ENOMEM; + + handle->dev = dev; + handle->handler = handler; + handle->name = "evbug"; + + error = input_register_handle(handle); + if (error) + goto err_free_handle; + + error = input_open_device(handle); + if (error) + goto err_unregister_handle; + + printk(KERN_DEBUG "evbug.c: Connected device: %s (%s at %s)\n", + dev->dev.bus_id, + dev->name ?: "unknown", + dev->phys ?: "unknown"); + + return 0; + + err_unregister_handle: + input_unregister_handle(handle); + err_free_handle: + kfree(handle); + return error; +} + +static void evbug_disconnect(struct input_handle *handle) +{ + printk(KERN_DEBUG "evbug.c: Disconnected device: %s\n", + handle->dev->dev.bus_id); + + input_close_device(handle); + input_unregister_handle(handle); + kfree(handle); +} + +static const 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) +{ + return input_register_handler(&evbug_handler); +} + +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 0000000..1070db3 --- /dev/null +++ b/drivers/input/evdev.c @@ -0,0 +1,1075 @@ +/* + * 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/device.h> +#include <linux/compat.h> + +struct evdev { + int exist; + int open; + int minor; + char name[16]; + struct input_handle handle; + wait_queue_head_t wait; + struct evdev_client *grab; + struct list_head client_list; + spinlock_t client_lock; /* protects client_list */ + struct mutex mutex; + struct device dev; +}; + +struct evdev_client { + struct input_event buffer[EVDEV_BUFFER_SIZE]; + int head; + int tail; + spinlock_t buffer_lock; /* protects access to buffer, head and tail */ + struct fasync_struct *fasync; + struct evdev *evdev; + struct list_head node; +}; + +static struct evdev *evdev_table[EVDEV_MINORS]; +static DEFINE_MUTEX(evdev_table_mutex); + +static void evdev_pass_event(struct evdev_client *client, + struct input_event *event) +{ + /* + * Interrupts are disabled, just acquire the lock + */ + spin_lock(&client->buffer_lock); + client->buffer[client->head++] = *event; + client->head &= EVDEV_BUFFER_SIZE - 1; + spin_unlock(&client->buffer_lock); + + kill_fasync(&client->fasync, SIGIO, POLL_IN); +} + +/* + * Pass incoming event to all connected clients. + */ +static void evdev_event(struct input_handle *handle, + unsigned int type, unsigned int code, int value) +{ + struct evdev *evdev = handle->private; + struct evdev_client *client; + struct input_event event; + + do_gettimeofday(&event.time); + event.type = type; + event.code = code; + event.value = value; + + rcu_read_lock(); + + client = rcu_dereference(evdev->grab); + if (client) + evdev_pass_event(client, &event); + else + list_for_each_entry_rcu(client, &evdev->client_list, node) + evdev_pass_event(client, &event); + + rcu_read_unlock(); + + wake_up_interruptible(&evdev->wait); +} + +static int evdev_fasync(int fd, struct file *file, int on) +{ + struct evdev_client *client = file->private_data; + int retval; + + retval = fasync_helper(fd, file, on, &client->fasync); + + return retval < 0 ? retval : 0; +} + +static int evdev_flush(struct file *file, fl_owner_t id) +{ + struct evdev_client *client = file->private_data; + struct evdev *evdev = client->evdev; + int retval; + + retval = mutex_lock_interruptible(&evdev->mutex); + if (retval) + return retval; + + if (!evdev->exist) + retval = -ENODEV; + else + retval = input_flush_device(&evdev->handle, file); + + mutex_unlock(&evdev->mutex); + return retval; +} + +static void evdev_free(struct device *dev) +{ + struct evdev *evdev = container_of(dev, struct evdev, dev); + + input_put_device(evdev->handle.dev); + kfree(evdev); +} + +/* + * Grabs an event device (along with underlying input device). + * This function is called with evdev->mutex taken. + */ +static int evdev_grab(struct evdev *evdev, struct evdev_client *client) +{ + int error; + + if (evdev->grab) + return -EBUSY; + + error = input_grab_device(&evdev->handle); + if (error) + return error; + + rcu_assign_pointer(evdev->grab, client); + synchronize_rcu(); + + return 0; +} + +static int evdev_ungrab(struct evdev *evdev, struct evdev_client *client) +{ + if (evdev->grab != client) + return -EINVAL; + + rcu_assign_pointer(evdev->grab, NULL); + synchronize_rcu(); + input_release_device(&evdev->handle); + + return 0; +} + +static void evdev_attach_client(struct evdev *evdev, + struct evdev_client *client) +{ + spin_lock(&evdev->client_lock); + list_add_tail_rcu(&client->node, &evdev->client_list); + spin_unlock(&evdev->client_lock); + synchronize_rcu(); +} + +static void evdev_detach_client(struct evdev *evdev, + struct evdev_client *client) +{ + spin_lock(&evdev->client_lock); + list_del_rcu(&client->node); + spin_unlock(&evdev->client_lock); + synchronize_rcu(); +} + +static int evdev_open_device(struct evdev *evdev) +{ + int retval; + + retval = mutex_lock_interruptible(&evdev->mutex); + if (retval) + return retval; + + if (!evdev->exist) + retval = -ENODEV; + else if (!evdev->open++) { + retval = input_open_device(&evdev->handle); + if (retval) + evdev->open--; + } + + mutex_unlock(&evdev->mutex); + return retval; +} + +static void evdev_close_device(struct evdev *evdev) +{ + mutex_lock(&evdev->mutex); + + if (evdev->exist && !--evdev->open) + input_close_device(&evdev->handle); + + mutex_unlock(&evdev->mutex); +} + +/* + * Wake up users waiting for IO so they can disconnect from + * dead device. + */ +static void evdev_hangup(struct evdev *evdev) +{ + struct evdev_client *client; + + spin_lock(&evdev->client_lock); + list_for_each_entry(client, &evdev->client_list, node) + kill_fasync(&client->fasync, SIGIO, POLL_HUP); + spin_unlock(&evdev->client_lock); + + wake_up_interruptible(&evdev->wait); +} + +static int evdev_release(struct inode *inode, struct file *file) +{ + struct evdev_client *client = file->private_data; + struct evdev *evdev = client->evdev; + + mutex_lock(&evdev->mutex); + if (evdev->grab == client) + evdev_ungrab(evdev, client); + mutex_unlock(&evdev->mutex); + + evdev_detach_client(evdev, client); + kfree(client); + + evdev_close_device(evdev); + put_device(&evdev->dev); + + return 0; +} + +static int evdev_open(struct inode *inode, struct file *file) +{ + struct evdev *evdev; + struct evdev_client *client; + int i = iminor(inode) - EVDEV_MINOR_BASE; + int error; + + if (i >= EVDEV_MINORS) + return -ENODEV; + + error = mutex_lock_interruptible(&evdev_table_mutex); + if (error) + return error; + evdev = evdev_table[i]; + if (evdev) + get_device(&evdev->dev); + mutex_unlock(&evdev_table_mutex); + + if (!evdev) + return -ENODEV; + + client = kzalloc(sizeof(struct evdev_client), GFP_KERNEL); + if (!client) { + error = -ENOMEM; + goto err_put_evdev; + } + + spin_lock_init(&client->buffer_lock); + client->evdev = evdev; + evdev_attach_client(evdev, client); + + error = evdev_open_device(evdev); + if (error) + goto err_free_client; + + file->private_data = client; + return 0; + + err_free_client: + evdev_detach_client(evdev, client); + kfree(client); + err_put_evdev: + put_device(&evdev->dev); + return error; +} + +#ifdef CONFIG_COMPAT + +struct input_event_compat { + struct compat_timeval time; + __u16 type; + __u16 code; + __s32 value; +}; + +struct ff_periodic_effect_compat { + __u16 waveform; + __u16 period; + __s16 magnitude; + __s16 offset; + __u16 phase; + + struct ff_envelope envelope; + + __u32 custom_len; + compat_uptr_t custom_data; +}; + +struct ff_effect_compat { + __u16 type; + __s16 id; + __u16 direction; + struct ff_trigger trigger; + struct ff_replay replay; + + union { + struct ff_constant_effect constant; + struct ff_ramp_effect ramp; + struct ff_periodic_effect_compat periodic; + struct ff_condition_effect condition[2]; /* One for each axis */ + struct ff_rumble_effect rumble; + } u; +}; + +/* Note to the author of this code: did it ever occur to + you why the ifdefs are needed? Think about it again. -AK */ +#ifdef CONFIG_X86_64 +# define COMPAT_TEST is_compat_task() +#elif defined(CONFIG_IA64) +# define COMPAT_TEST IS_IA32_PROCESS(task_pt_regs(current)) +#elif defined(CONFIG_S390) +# define COMPAT_TEST test_thread_flag(TIF_31BIT) +#elif defined(CONFIG_MIPS) +# define COMPAT_TEST test_thread_flag(TIF_32BIT_ADDR) +#else +# define COMPAT_TEST test_thread_flag(TIF_32BIT) +#endif + +static inline size_t evdev_event_size(void) +{ + return COMPAT_TEST ? + sizeof(struct input_event_compat) : sizeof(struct input_event); +} + +static int evdev_event_from_user(const char __user *buffer, + struct input_event *event) +{ + if (COMPAT_TEST) { + struct input_event_compat compat_event; + + if (copy_from_user(&compat_event, buffer, + sizeof(struct input_event_compat))) + return -EFAULT; + + event->time.tv_sec = compat_event.time.tv_sec; + event->time.tv_usec = compat_event.time.tv_usec; + event->type = compat_event.type; + event->code = compat_event.code; + event->value = compat_event.value; + + } else { + if (copy_from_user(event, buffer, sizeof(struct input_event))) + return -EFAULT; + } + + return 0; +} + +static int evdev_event_to_user(char __user *buffer, + const struct input_event *event) +{ + if (COMPAT_TEST) { + struct input_event_compat compat_event; + + compat_event.time.tv_sec = event->time.tv_sec; + compat_event.time.tv_usec = event->time.tv_usec; + compat_event.type = event->type; + compat_event.code = event->code; + compat_event.value = event->value; + + if (copy_to_user(buffer, &compat_event, + sizeof(struct input_event_compat))) + return -EFAULT; + + } else { + if (copy_to_user(buffer, event, sizeof(struct input_event))) + return -EFAULT; + } + + return 0; +} + +static int evdev_ff_effect_from_user(const char __user *buffer, size_t size, + struct ff_effect *effect) +{ + if (COMPAT_TEST) { + struct ff_effect_compat *compat_effect; + + if (size != sizeof(struct ff_effect_compat)) + return -EINVAL; + + /* + * It so happens that the pointer which needs to be changed + * is the last field in the structure, so we can copy the + * whole thing and replace just the pointer. + */ + + compat_effect = (struct ff_effect_compat *)effect; + + if (copy_from_user(compat_effect, buffer, + sizeof(struct ff_effect_compat))) + return -EFAULT; + + if (compat_effect->type == FF_PERIODIC && + compat_effect->u.periodic.waveform == FF_CUSTOM) + effect->u.periodic.custom_data = + compat_ptr(compat_effect->u.periodic.custom_data); + } else { + if (size != sizeof(struct ff_effect)) + return -EINVAL; + + if (copy_from_user(effect, buffer, sizeof(struct ff_effect))) + return -EFAULT; + } + + return 0; +} + +#else + +static inline size_t evdev_event_size(void) +{ + return sizeof(struct input_event); +} + +static int evdev_event_from_user(const char __user *buffer, + struct input_event *event) +{ + if (copy_from_user(event, buffer, sizeof(struct input_event))) + return -EFAULT; + + return 0; +} + +static int evdev_event_to_user(char __user *buffer, + const struct input_event *event) +{ + if (copy_to_user(buffer, event, sizeof(struct input_event))) + return -EFAULT; + + return 0; +} + +static int evdev_ff_effect_from_user(const char __user *buffer, size_t size, + struct ff_effect *effect) +{ + if (size != sizeof(struct ff_effect)) + return -EINVAL; + + if (copy_from_user(effect, buffer, sizeof(struct ff_effect))) + return -EFAULT; + + return 0; +} + +#endif /* CONFIG_COMPAT */ + +static ssize_t evdev_write(struct file *file, const char __user *buffer, + size_t count, loff_t *ppos) +{ + struct evdev_client *client = file->private_data; + struct evdev *evdev = client->evdev; + struct input_event event; + int retval; + + retval = mutex_lock_interruptible(&evdev->mutex); + if (retval) + return retval; + + if (!evdev->exist) { + retval = -ENODEV; + goto out; + } + + while (retval < count) { + + if (evdev_event_from_user(buffer + retval, &event)) { + retval = -EFAULT; + goto out; + } + + input_inject_event(&evdev->handle, + event.type, event.code, event.value); + retval += evdev_event_size(); + } + + out: + mutex_unlock(&evdev->mutex); + return retval; +} + +static int evdev_fetch_next_event(struct evdev_client *client, + struct input_event *event) +{ + int have_event; + + spin_lock_irq(&client->buffer_lock); + + have_event = client->head != client->tail; + if (have_event) { + *event = client->buffer[client->tail++]; + client->tail &= EVDEV_BUFFER_SIZE - 1; + } + + spin_unlock_irq(&client->buffer_lock); + + return have_event; +} + +static ssize_t evdev_read(struct file *file, char __user *buffer, + size_t count, loff_t *ppos) +{ + struct evdev_client *client = file->private_data; + struct evdev *evdev = client->evdev; + struct input_event event; + int retval; + + if (count < evdev_event_size()) + return -EINVAL; + + if (client->head == client->tail && evdev->exist && + (file->f_flags & O_NONBLOCK)) + return -EAGAIN; + + retval = wait_event_interruptible(evdev->wait, + client->head != client->tail || !evdev->exist); + if (retval) + return retval; + + if (!evdev->exist) + return -ENODEV; + + while (retval + evdev_event_size() <= count && + evdev_fetch_next_event(client, &event)) { + + if (evdev_event_to_user(buffer + retval, &event)) + return -EFAULT; + + retval += evdev_event_size(); + } + + return retval; +} + +/* No kernel lock - fine */ +static unsigned int evdev_poll(struct file *file, poll_table *wait) +{ + struct evdev_client *client = file->private_data; + struct evdev *evdev = client->evdev; + + poll_wait(file, &evdev->wait, wait); + return ((client->head == client->tail) ? 0 : (POLLIN | POLLRDNORM)) | + (evdev->exist ? 0 : (POLLHUP | POLLERR)); +} + +#ifdef CONFIG_COMPAT + +#define BITS_PER_LONG_COMPAT (sizeof(compat_long_t) * 8) +#define BITS_TO_LONGS_COMPAT(x) ((((x) - 1) / BITS_PER_LONG_COMPAT) + 1) + +#ifdef __BIG_ENDIAN +static int bits_to_user(unsigned long *bits, unsigned int maxbit, + unsigned int maxlen, void __user *p, int compat) +{ + int len, i; + + if (compat) { + len = BITS_TO_LONGS_COMPAT(maxbit) * sizeof(compat_long_t); + if (len > maxlen) + len = maxlen; + + for (i = 0; i < len / sizeof(compat_long_t); i++) + if (copy_to_user((compat_long_t __user *) p + i, + (compat_long_t *) bits + + i + 1 - ((i % 2) << 1), + sizeof(compat_long_t))) + return -EFAULT; + } else { + len = BITS_TO_LONGS(maxbit) * sizeof(long); + if (len > maxlen) + len = maxlen; + + if (copy_to_user(p, bits, len)) + return -EFAULT; + } + + return len; +} +#else +static int bits_to_user(unsigned long *bits, unsigned int maxbit, + unsigned int maxlen, void __user *p, int compat) +{ + int len = compat ? + BITS_TO_LONGS_COMPAT(maxbit) * sizeof(compat_long_t) : + BITS_TO_LONGS(maxbit) * sizeof(long); + + if (len > maxlen) + len = maxlen; + + return copy_to_user(p, bits, len) ? -EFAULT : len; +} +#endif /* __BIG_ENDIAN */ + +#else + +static int bits_to_user(unsigned long *bits, unsigned int maxbit, + unsigned int maxlen, void __user *p, int compat) +{ + int len = BITS_TO_LONGS(maxbit) * sizeof(long); + + if (len > maxlen) + len = maxlen; + + return copy_to_user(p, bits, len) ? -EFAULT : len; +} + +#endif /* CONFIG_COMPAT */ + +static int str_to_user(const char *str, unsigned int maxlen, void __user *p) +{ + int len; + + if (!str) + return -ENOENT; + + len = strlen(str) + 1; + if (len > maxlen) + len = maxlen; + + return copy_to_user(p, str, len) ? -EFAULT : len; +} + +#define OLD_KEY_MAX 0x1ff +static int handle_eviocgbit(struct input_dev *dev, unsigned int cmd, void __user *p, int compat_mode) +{ + static unsigned long keymax_warn_time; + unsigned 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; + case EV_SW: bits = dev->swbit; len = SW_MAX; break; + default: return -EINVAL; + } + + /* + * Work around bugs in userspace programs that like to do + * EVIOCGBIT(EV_KEY, KEY_MAX) and not realize that 'len' + * should be in bytes, not in bits. + */ + if ((_IOC_NR(cmd) & EV_MAX) == EV_KEY && _IOC_SIZE(cmd) == OLD_KEY_MAX) { + len = OLD_KEY_MAX; + if (printk_timed_ratelimit(&keymax_warn_time, 10 * 1000)) + printk(KERN_WARNING + "evdev.c(EVIOCGBIT): Suspicious buffer size %u, " + "limiting output to %zu bytes. See " + "http://userweb.kernel.org/~dtor/eviocgbit-bug.html\n", + OLD_KEY_MAX, + BITS_TO_LONGS(OLD_KEY_MAX) * sizeof(long)); + } + + return bits_to_user(bits, len, _IOC_SIZE(cmd), p, compat_mode); +} +#undef OLD_KEY_MAX + +static long evdev_do_ioctl(struct file *file, unsigned int cmd, + void __user *p, int compat_mode) +{ + struct evdev_client *client = file->private_data; + struct evdev *evdev = client->evdev; + struct input_dev *dev = evdev->handle.dev; + struct input_absinfo abs; + struct ff_effect effect; + int __user *ip = (int __user *)p; + int i, t, u, v; + int error; + + switch (cmd) { + + case EVIOCGVERSION: + return put_user(EV_VERSION, ip); + + case EVIOCGID: + if (copy_to_user(p, &dev->id, sizeof(struct input_id))) + return -EFAULT; + return 0; + + case EVIOCGREP: + if (!test_bit(EV_REP, dev->evbit)) + return -ENOSYS; + if (put_user(dev->rep[REP_DELAY], ip)) + return -EFAULT; + if (put_user(dev->rep[REP_PERIOD], ip + 1)) + return -EFAULT; + return 0; + + case EVIOCSREP: + if (!test_bit(EV_REP, dev->evbit)) + return -ENOSYS; + if (get_user(u, ip)) + return -EFAULT; + if (get_user(v, ip + 1)) + return -EFAULT; + + input_inject_event(&evdev->handle, EV_REP, REP_DELAY, u); + input_inject_event(&evdev->handle, EV_REP, REP_PERIOD, v); + + return 0; + + case EVIOCGKEYCODE: + if (get_user(t, ip)) + return -EFAULT; + + error = input_get_keycode(dev, t, &v); + if (error) + return error; + + if (put_user(v, ip + 1)) + return -EFAULT; + + return 0; + + case EVIOCSKEYCODE: + if (get_user(t, ip) || get_user(v, ip + 1)) + return -EFAULT; + + return input_set_keycode(dev, t, v); + + case EVIOCRMFF: + return input_ff_erase(dev, (int)(unsigned long) p, file); + + case EVIOCGEFFECTS: + i = test_bit(EV_FF, dev->evbit) ? + dev->ff->max_effects : 0; + if (put_user(i, ip)) + return -EFAULT; + return 0; + + case EVIOCGRAB: + if (p) + return evdev_grab(evdev, client); + else + return evdev_ungrab(evdev, client); + + default: + + if (_IOC_TYPE(cmd) != 'E') + return -EINVAL; + + if (_IOC_DIR(cmd) == _IOC_READ) { + + if ((_IOC_NR(cmd) & ~EV_MAX) == _IOC_NR(EVIOCGBIT(0, 0))) + return handle_eviocgbit(dev, cmd, p, compat_mode); + + if (_IOC_NR(cmd) == _IOC_NR(EVIOCGKEY(0))) + return bits_to_user(dev->key, KEY_MAX, _IOC_SIZE(cmd), + p, compat_mode); + + if (_IOC_NR(cmd) == _IOC_NR(EVIOCGLED(0))) + return bits_to_user(dev->led, LED_MAX, _IOC_SIZE(cmd), + p, compat_mode); + + if (_IOC_NR(cmd) == _IOC_NR(EVIOCGSND(0))) + return bits_to_user(dev->snd, SND_MAX, _IOC_SIZE(cmd), + p, compat_mode); + + if (_IOC_NR(cmd) == _IOC_NR(EVIOCGSW(0))) + return bits_to_user(dev->sw, SW_MAX, _IOC_SIZE(cmd), + p, compat_mode); + + if (_IOC_NR(cmd) == _IOC_NR(EVIOCGNAME(0))) + return str_to_user(dev->name, _IOC_SIZE(cmd), p); + + if (_IOC_NR(cmd) == _IOC_NR(EVIOCGPHYS(0))) + return str_to_user(dev->phys, _IOC_SIZE(cmd), p); + + if (_IOC_NR(cmd) == _IOC_NR(EVIOCGUNIQ(0))) + return str_to_user(dev->uniq, _IOC_SIZE(cmd), p); + + if ((_IOC_NR(cmd) & ~ABS_MAX) == _IOC_NR(EVIOCGABS(0))) { + + 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_DIR(cmd) == _IOC_WRITE) { + + if (_IOC_NR(cmd) == _IOC_NR(EVIOCSFF)) { + + if (evdev_ff_effect_from_user(p, _IOC_SIZE(cmd), &effect)) + return -EFAULT; + + error = input_ff_upload(dev, &effect, file); + + if (put_user(effect.id, &(((struct ff_effect __user *)p)->id))) + return -EFAULT; + + return error; + } + + if ((_IOC_NR(cmd) & ~ABS_MAX) == _IOC_NR(EVIOCSABS(0))) { + + t = _IOC_NR(cmd) & ABS_MAX; + + if (copy_from_user(&abs, p, + sizeof(struct input_absinfo))) + return -EFAULT; + + /* + * Take event lock to ensure that we are not + * changing device parameters in the middle + * of event. + */ + spin_lock_irq(&dev->event_lock); + + 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; + + spin_unlock_irq(&dev->event_lock); + + return 0; + } + } + } + return -EINVAL; +} + +static long evdev_ioctl_handler(struct file *file, unsigned int cmd, + void __user *p, int compat_mode) +{ + struct evdev_client *client = file->private_data; + struct evdev *evdev = client->evdev; + int retval; + + retval = mutex_lock_interruptible(&evdev->mutex); + if (retval) + return retval; + + if (!evdev->exist) { + retval = -ENODEV; + goto out; + } + + retval = evdev_do_ioctl(file, cmd, p, compat_mode); + + out: + mutex_unlock(&evdev->mutex); + return retval; +} + +static long evdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + return evdev_ioctl_handler(file, cmd, (void __user *)arg, 0); +} + +#ifdef CONFIG_COMPAT +static long evdev_ioctl_compat(struct file *file, + unsigned int cmd, unsigned long arg) +{ + return evdev_ioctl_handler(file, cmd, compat_ptr(arg), 1); +} +#endif + +static const struct file_operations evdev_fops = { + .owner = THIS_MODULE, + .read = evdev_read, + .write = evdev_write, + .poll = evdev_poll, + .open = evdev_open, + .release = evdev_release, + .unlocked_ioctl = evdev_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = evdev_ioctl_compat, +#endif + .fasync = evdev_fasync, + .flush = evdev_flush +}; + +static int evdev_install_chrdev(struct evdev *evdev) +{ + /* + * No need to do any locking here as calls to connect and + * disconnect are serialized by the input core + */ + evdev_table[evdev->minor] = evdev; + return 0; +} + +static void evdev_remove_chrdev(struct evdev *evdev) +{ + /* + * Lock evdev table to prevent race with evdev_open() + */ + mutex_lock(&evdev_table_mutex); + evdev_table[evdev->minor] = NULL; + mutex_unlock(&evdev_table_mutex); +} + +/* + * Mark device non-existent. This disables writes, ioctls and + * prevents new users from opening the device. Already posted + * blocking reads will stay, however new ones will fail. + */ +static void evdev_mark_dead(struct evdev *evdev) +{ + mutex_lock(&evdev->mutex); + evdev->exist = 0; + mutex_unlock(&evdev->mutex); +} + +static void evdev_cleanup(struct evdev *evdev) +{ + struct input_handle *handle = &evdev->handle; + + evdev_mark_dead(evdev); + evdev_hangup(evdev); + evdev_remove_chrdev(evdev); + + /* evdev is marked dead so no one else accesses evdev->open */ + if (evdev->open) { + input_flush_device(handle, NULL); + input_close_device(handle); + } +} + +/* + * Create new evdev device. Note that input core serializes calls + * to connect and disconnect so we don't need to lock evdev_table here. + */ +static int evdev_connect(struct input_handler *handler, struct input_dev *dev, + const struct input_device_id *id) +{ + struct evdev *evdev; + int minor; + int error; + + for (minor = 0; minor < EVDEV_MINORS; minor++) + if (!evdev_table[minor]) + break; + + if (minor == EVDEV_MINORS) { + printk(KERN_ERR "evdev: no more free evdev devices\n"); + return -ENFILE; + } + + evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL); + if (!evdev) + return -ENOMEM; + + INIT_LIST_HEAD(&evdev->client_list); + spin_lock_init(&evdev->client_lock); + mutex_init(&evdev->mutex); + init_waitqueue_head(&evdev->wait); + + snprintf(evdev->name, sizeof(evdev->name), "event%d", minor); + evdev->exist = 1; + evdev->minor = minor; + + evdev->handle.dev = input_get_device(dev); + evdev->handle.name = evdev->name; + evdev->handle.handler = handler; + evdev->handle.private = evdev; + + strlcpy(evdev->dev.bus_id, evdev->name, sizeof(evdev->dev.bus_id)); + evdev->dev.devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor); + evdev->dev.class = &input_class; + evdev->dev.parent = &dev->dev; + evdev->dev.release = evdev_free; + device_initialize(&evdev->dev); + + error = input_register_handle(&evdev->handle); + if (error) + goto err_free_evdev; + + error = evdev_install_chrdev(evdev); + if (error) + goto err_unregister_handle; + + error = device_add(&evdev->dev); + if (error) + goto err_cleanup_evdev; + + return 0; + + err_cleanup_evdev: + evdev_cleanup(evdev); + err_unregister_handle: + input_unregister_handle(&evdev->handle); + err_free_evdev: + put_device(&evdev->dev); + return error; +} + +static void evdev_disconnect(struct input_handle *handle) +{ + struct evdev *evdev = handle->private; + + device_del(&evdev->dev); + evdev_cleanup(evdev); + input_unregister_handle(handle); + put_device(&evdev->dev); +} + +static const 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) +{ + return input_register_handler(&evdev_handler); +} + +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/ff-core.c b/drivers/input/ff-core.c new file mode 100644 index 0000000..72c63e5 --- /dev/null +++ b/drivers/input/ff-core.c @@ -0,0 +1,374 @@ +/* + * Force feedback support for Linux input subsystem + * + * Copyright (c) 2006 Anssi Hannula <anssi.hannula@gmail.com> + * Copyright (c) 2006 Dmitry Torokhov <dtor@mail.ru> + */ + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* #define DEBUG */ + +#define debug(format, arg...) pr_debug("ff-core: " format "\n", ## arg) + +#include <linux/input.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/sched.h> + +/* + * Check that the effect_id is a valid effect and whether the user + * is the owner + */ +static int check_effect_access(struct ff_device *ff, int effect_id, + struct file *file) +{ + if (effect_id < 0 || effect_id >= ff->max_effects || + !ff->effect_owners[effect_id]) + return -EINVAL; + + if (file && ff->effect_owners[effect_id] != file) + return -EACCES; + + return 0; +} + +/* + * Checks whether 2 effects can be combined together + */ +static inline int check_effects_compatible(struct ff_effect *e1, + struct ff_effect *e2) +{ + return e1->type == e2->type && + (e1->type != FF_PERIODIC || + e1->u.periodic.waveform == e2->u.periodic.waveform); +} + +/* + * Convert an effect into compatible one + */ +static int compat_effect(struct ff_device *ff, struct ff_effect *effect) +{ + int magnitude; + + switch (effect->type) { + case FF_RUMBLE: + if (!test_bit(FF_PERIODIC, ff->ffbit)) + return -EINVAL; + + /* + * calculate manginude of sine wave as average of rumble's + * 2/3 of strong magnitude and 1/3 of weak magnitude + */ + magnitude = effect->u.rumble.strong_magnitude / 3 + + effect->u.rumble.weak_magnitude / 6; + + effect->type = FF_PERIODIC; + effect->u.periodic.waveform = FF_SINE; + effect->u.periodic.period = 50; + effect->u.periodic.magnitude = max(magnitude, 0x7fff); + effect->u.periodic.offset = 0; + effect->u.periodic.phase = 0; + effect->u.periodic.envelope.attack_length = 0; + effect->u.periodic.envelope.attack_level = 0; + effect->u.periodic.envelope.fade_length = 0; + effect->u.periodic.envelope.fade_level = 0; + + return 0; + + default: + /* Let driver handle conversion */ + return 0; + } +} + +/** + * input_ff_upload() - upload effect into force-feedback device + * @dev: input device + * @effect: effect to be uploaded + * @file: owner of the effect + */ +int input_ff_upload(struct input_dev *dev, struct ff_effect *effect, + struct file *file) +{ + struct ff_device *ff = dev->ff; + struct ff_effect *old; + int ret = 0; + int id; + + if (!test_bit(EV_FF, dev->evbit)) + return -ENOSYS; + + if (effect->type < FF_EFFECT_MIN || effect->type > FF_EFFECT_MAX || + !test_bit(effect->type, dev->ffbit)) { + debug("invalid or not supported effect type in upload"); + return -EINVAL; + } + + if (effect->type == FF_PERIODIC && + (effect->u.periodic.waveform < FF_WAVEFORM_MIN || + effect->u.periodic.waveform > FF_WAVEFORM_MAX || + !test_bit(effect->u.periodic.waveform, dev->ffbit))) { + debug("invalid or not supported wave form in upload"); + return -EINVAL; + } + + if (!test_bit(effect->type, ff->ffbit)) { + ret = compat_effect(ff, effect); + if (ret) + return ret; + } + + mutex_lock(&ff->mutex); + + if (effect->id == -1) { + for (id = 0; id < ff->max_effects; id++) + if (!ff->effect_owners[id]) + break; + + if (id >= ff->max_effects) { + ret = -ENOSPC; + goto out; + } + + effect->id = id; + old = NULL; + + } else { + id = effect->id; + + ret = check_effect_access(ff, id, file); + if (ret) + goto out; + + old = &ff->effects[id]; + + if (!check_effects_compatible(effect, old)) { + ret = -EINVAL; + goto out; + } + } + + ret = ff->upload(dev, effect, old); + if (ret) + goto out; + + spin_lock_irq(&dev->event_lock); + ff->effects[id] = *effect; + ff->effect_owners[id] = file; + spin_unlock_irq(&dev->event_lock); + + out: + mutex_unlock(&ff->mutex); + return ret; +} +EXPORT_SYMBOL_GPL(input_ff_upload); + +/* + * Erases the effect if the requester is also the effect owner. The mutex + * should already be locked before calling this function. + */ +static int erase_effect(struct input_dev *dev, int effect_id, + struct file *file) +{ + struct ff_device *ff = dev->ff; + int error; + + error = check_effect_access(ff, effect_id, file); + if (error) + return error; + + spin_lock_irq(&dev->event_lock); + ff->playback(dev, effect_id, 0); + ff->effect_owners[effect_id] = NULL; + spin_unlock_irq(&dev->event_lock); + + if (ff->erase) { + error = ff->erase(dev, effect_id); + if (error) { + spin_lock_irq(&dev->event_lock); + ff->effect_owners[effect_id] = file; + spin_unlock_irq(&dev->event_lock); + + return error; + } + } + + return 0; +} + +/** + * input_ff_erase - erase a force-feedback effect from device + * @dev: input device to erase effect from + * @effect_id: id of the ffect to be erased + * @file: purported owner of the request + * + * This function erases a force-feedback effect from specified device. + * The effect will only be erased if it was uploaded through the same + * file handle that is requesting erase. + */ +int input_ff_erase(struct input_dev *dev, int effect_id, struct file *file) +{ + struct ff_device *ff = dev->ff; + int ret; + + if (!test_bit(EV_FF, dev->evbit)) + return -ENOSYS; + + mutex_lock(&ff->mutex); + ret = erase_effect(dev, effect_id, file); + mutex_unlock(&ff->mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(input_ff_erase); + +/* + * flush_effects - erase all effects owned by a file handle + */ +static int flush_effects(struct input_dev *dev, struct file *file) +{ + struct ff_device *ff = dev->ff; + int i; + + debug("flushing now"); + + mutex_lock(&ff->mutex); + + for (i = 0; i < ff->max_effects; i++) + erase_effect(dev, i, file); + + mutex_unlock(&ff->mutex); + + return 0; +} + +/** + * input_ff_event() - generic handler for force-feedback events + * @dev: input device to send the effect to + * @type: event type (anything but EV_FF is ignored) + * @code: event code + * @value: event value + */ +int input_ff_event(struct input_dev *dev, unsigned int type, + unsigned int code, int value) +{ + struct ff_device *ff = dev->ff; + + if (type != EV_FF) + return 0; + + switch (code) { + case FF_GAIN: + if (!test_bit(FF_GAIN, dev->ffbit) || value > 0xffff) + break; + + ff->set_gain(dev, value); + break; + + case FF_AUTOCENTER: + if (!test_bit(FF_AUTOCENTER, dev->ffbit) || value > 0xffff) + break; + + ff->set_autocenter(dev, value); + break; + + default: + if (check_effect_access(ff, code, NULL) == 0) + ff->playback(dev, code, value); + break; + } + + return 0; +} +EXPORT_SYMBOL_GPL(input_ff_event); + +/** + * input_ff_create() - create force-feedback device + * @dev: input device supporting force-feedback + * @max_effects: maximum number of effects supported by the device + * + * This function allocates all necessary memory for a force feedback + * portion of an input device and installs all default handlers. + * @dev->ffbit should be already set up before calling this function. + * Once ff device is created you need to setup its upload, erase, + * playback and other handlers before registering input device + */ +int input_ff_create(struct input_dev *dev, int max_effects) +{ + struct ff_device *ff; + int i; + + if (!max_effects) { + printk(KERN_ERR + "ff-core: cannot allocate device without any effects\n"); + return -EINVAL; + } + + ff = kzalloc(sizeof(struct ff_device) + + max_effects * sizeof(struct file *), GFP_KERNEL); + if (!ff) + return -ENOMEM; + + ff->effects = kcalloc(max_effects, sizeof(struct ff_effect), + GFP_KERNEL); + if (!ff->effects) { + kfree(ff); + return -ENOMEM; + } + + ff->max_effects = max_effects; + mutex_init(&ff->mutex); + + dev->ff = ff; + dev->flush = flush_effects; + dev->event = input_ff_event; + set_bit(EV_FF, dev->evbit); + + /* Copy "true" bits into ff device bitmap */ + for (i = 0; i <= FF_MAX; i++) + if (test_bit(i, dev->ffbit)) + set_bit(i, ff->ffbit); + + /* we can emulate RUMBLE with periodic effects */ + if (test_bit(FF_PERIODIC, ff->ffbit)) + set_bit(FF_RUMBLE, dev->ffbit); + + return 0; +} +EXPORT_SYMBOL_GPL(input_ff_create); + +/** + * input_ff_free() - frees force feedback portion of input device + * @dev: input device supporting force feedback + * + * This function is only needed in error path as input core will + * automatically free force feedback structures when device is + * destroyed. + */ +void input_ff_destroy(struct input_dev *dev) +{ + clear_bit(EV_FF, dev->evbit); + if (dev->ff) { + if (dev->ff->destroy) + dev->ff->destroy(dev->ff); + kfree(dev->ff->private); + kfree(dev->ff); + dev->ff = NULL; + } +} +EXPORT_SYMBOL_GPL(input_ff_destroy); diff --git a/drivers/input/ff-memless.c b/drivers/input/ff-memless.c new file mode 100644 index 0000000..bc4e40f --- /dev/null +++ b/drivers/input/ff-memless.c @@ -0,0 +1,516 @@ +/* + * Force feedback support for memoryless devices + * + * Copyright (c) 2006 Anssi Hannula <anssi.hannula@gmail.com> + * Copyright (c) 2006 Dmitry Torokhov <dtor@mail.ru> + */ + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* #define DEBUG */ + +#define debug(format, arg...) pr_debug("ff-memless: " format "\n", ## arg) + +#include <linux/input.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/spinlock.h> +#include <linux/jiffies.h> + +#include "fixp-arith.h" + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Anssi Hannula <anssi.hannula@gmail.com>"); +MODULE_DESCRIPTION("Force feedback support for memoryless devices"); + +/* Number of effects handled with memoryless devices */ +#define FF_MEMLESS_EFFECTS 16 + +/* Envelope update interval in ms */ +#define FF_ENVELOPE_INTERVAL 50 + +#define FF_EFFECT_STARTED 0 +#define FF_EFFECT_PLAYING 1 +#define FF_EFFECT_ABORTING 2 + +struct ml_effect_state { + struct ff_effect *effect; + unsigned long flags; /* effect state (STARTED, PLAYING, etc) */ + int count; /* loop count of the effect */ + unsigned long play_at; /* start time */ + unsigned long stop_at; /* stop time */ + unsigned long adj_at; /* last time the effect was sent */ +}; + +struct ml_device { + void *private; + struct ml_effect_state states[FF_MEMLESS_EFFECTS]; + int gain; + struct timer_list timer; + spinlock_t timer_lock; + struct input_dev *dev; + + int (*play_effect)(struct input_dev *dev, void *data, + struct ff_effect *effect); +}; + +static const struct ff_envelope *get_envelope(const struct ff_effect *effect) +{ + static const struct ff_envelope empty_envelope; + + switch (effect->type) { + case FF_PERIODIC: + return &effect->u.periodic.envelope; + case FF_CONSTANT: + return &effect->u.constant.envelope; + default: + return &empty_envelope; + } +} + +/* + * Check for the next time envelope requires an update on memoryless devices + */ +static unsigned long calculate_next_time(struct ml_effect_state *state) +{ + const struct ff_envelope *envelope = get_envelope(state->effect); + unsigned long attack_stop, fade_start, next_fade; + + if (envelope->attack_length) { + attack_stop = state->play_at + + msecs_to_jiffies(envelope->attack_length); + if (time_before(state->adj_at, attack_stop)) + return state->adj_at + + msecs_to_jiffies(FF_ENVELOPE_INTERVAL); + } + + if (state->effect->replay.length) { + if (envelope->fade_length) { + /* check when fading should start */ + fade_start = state->stop_at - + msecs_to_jiffies(envelope->fade_length); + + if (time_before(state->adj_at, fade_start)) + return fade_start; + + /* already fading, advance to next checkpoint */ + next_fade = state->adj_at + + msecs_to_jiffies(FF_ENVELOPE_INTERVAL); + if (time_before(next_fade, state->stop_at)) + return next_fade; + } + + return state->stop_at; + } + + return state->play_at; +} + +static void ml_schedule_timer(struct ml_device *ml) +{ + struct ml_effect_state *state; + unsigned long now = jiffies; + unsigned long earliest = 0; + unsigned long next_at; + int events = 0; + int i; + + debug("calculating next timer"); + + for (i = 0; i < FF_MEMLESS_EFFECTS; i++) { + + state = &ml->states[i]; + + if (!test_bit(FF_EFFECT_STARTED, &state->flags)) + continue; + + if (test_bit(FF_EFFECT_PLAYING, &state->flags)) + next_at = calculate_next_time(state); + else + next_at = state->play_at; + + if (time_before_eq(now, next_at) && + (++events == 1 || time_before(next_at, earliest))) + earliest = next_at; + } + + if (!events) { + debug("no actions"); + del_timer(&ml->timer); + } else { + debug("timer set"); + mod_timer(&ml->timer, earliest); + } +} + +/* + * Apply an envelope to a value + */ +static int apply_envelope(struct ml_effect_state *state, int value, + struct ff_envelope *envelope) +{ + struct ff_effect *effect = state->effect; + unsigned long now = jiffies; + int time_from_level; + int time_of_envelope; + int envelope_level; + int difference; + + if (envelope->attack_length && + time_before(now, + state->play_at + msecs_to_jiffies(envelope->attack_length))) { + debug("value = 0x%x, attack_level = 0x%x", value, + envelope->attack_level); + time_from_level = jiffies_to_msecs(now - state->play_at); + time_of_envelope = envelope->attack_length; + envelope_level = min_t(__s16, envelope->attack_level, 0x7fff); + + } else if (envelope->fade_length && effect->replay.length && + time_after(now, + state->stop_at - msecs_to_jiffies(envelope->fade_length)) && + time_before(now, state->stop_at)) { + time_from_level = jiffies_to_msecs(state->stop_at - now); + time_of_envelope = envelope->fade_length; + envelope_level = min_t(__s16, envelope->fade_level, 0x7fff); + } else + return value; + + difference = abs(value) - envelope_level; + + debug("difference = %d", difference); + debug("time_from_level = 0x%x", time_from_level); + debug("time_of_envelope = 0x%x", time_of_envelope); + + difference = difference * time_from_level / time_of_envelope; + + debug("difference = %d", difference); + + return value < 0 ? + -(difference + envelope_level) : (difference + envelope_level); +} + +/* + * Return the type the effect has to be converted into (memless devices) + */ +static int get_compatible_type(struct ff_device *ff, int effect_type) +{ + + if (test_bit(effect_type, ff->ffbit)) + return effect_type; + + if (effect_type == FF_PERIODIC && test_bit(FF_RUMBLE, ff->ffbit)) + return FF_RUMBLE; + + printk(KERN_ERR + "ff-memless: invalid type in get_compatible_type()\n"); + + return 0; +} + +/* + * Combine two effects and apply gain. + */ +static void ml_combine_effects(struct ff_effect *effect, + struct ml_effect_state *state, + int gain) +{ + struct ff_effect *new = state->effect; + unsigned int strong, weak, i; + int x, y; + fixp_t level; + + switch (new->type) { + case FF_CONSTANT: + i = new->direction * 360 / 0xffff; + level = fixp_new16(apply_envelope(state, + new->u.constant.level, + &new->u.constant.envelope)); + x = fixp_mult(fixp_sin(i), level) * gain / 0xffff; + y = fixp_mult(-fixp_cos(i), level) * gain / 0xffff; + /* + * here we abuse ff_ramp to hold x and y of constant force + * If in future any driver wants something else than x and y + * in s8, this should be changed to something more generic + */ + effect->u.ramp.start_level = + clamp_val(effect->u.ramp.start_level + x, -0x80, 0x7f); + effect->u.ramp.end_level = + clamp_val(effect->u.ramp.end_level + y, -0x80, 0x7f); + break; + + case FF_RUMBLE: + strong = new->u.rumble.strong_magnitude * gain / 0xffff; + weak = new->u.rumble.weak_magnitude * gain / 0xffff; + effect->u.rumble.strong_magnitude = + min(strong + effect->u.rumble.strong_magnitude, + 0xffffU); + effect->u.rumble.weak_magnitude = + min(weak + effect->u.rumble.weak_magnitude, 0xffffU); + break; + + case FF_PERIODIC: + i = apply_envelope(state, abs(new->u.periodic.magnitude), + &new->u.periodic.envelope); + + /* here we also scale it 0x7fff => 0xffff */ + i = i * gain / 0x7fff; + + effect->u.rumble.strong_magnitude = + min(i + effect->u.rumble.strong_magnitude, 0xffffU); + effect->u.rumble.weak_magnitude = + min(i + effect->u.rumble.weak_magnitude, 0xffffU); + break; + + default: + printk(KERN_ERR "ff-memless: invalid type in ml_combine_effects()\n"); + break; + } + +} + + +/* + * Because memoryless devices have only one effect per effect type active + * at one time we have to combine multiple effects into one + */ +static int ml_get_combo_effect(struct ml_device *ml, + unsigned long *effect_handled, + struct ff_effect *combo_effect) +{ + struct ff_effect *effect; + struct ml_effect_state *state; + int effect_type; + int i; + + memset(combo_effect, 0, sizeof(struct ff_effect)); + + for (i = 0; i < FF_MEMLESS_EFFECTS; i++) { + if (__test_and_set_bit(i, effect_handled)) + continue; + + state = &ml->states[i]; + effect = state->effect; + + if (!test_bit(FF_EFFECT_STARTED, &state->flags)) + continue; + + if (time_before(jiffies, state->play_at)) + continue; + + /* + * here we have started effects that are either + * currently playing (and may need be aborted) + * or need to start playing. + */ + effect_type = get_compatible_type(ml->dev->ff, effect->type); + if (combo_effect->type != effect_type) { + if (combo_effect->type != 0) { + __clear_bit(i, effect_handled); + continue; + } + combo_effect->type = effect_type; + } + + if (__test_and_clear_bit(FF_EFFECT_ABORTING, &state->flags)) { + __clear_bit(FF_EFFECT_PLAYING, &state->flags); + __clear_bit(FF_EFFECT_STARTED, &state->flags); + } else if (effect->replay.length && + time_after_eq(jiffies, state->stop_at)) { + + __clear_bit(FF_EFFECT_PLAYING, &state->flags); + + if (--state->count <= 0) { + __clear_bit(FF_EFFECT_STARTED, &state->flags); + } else { + state->play_at = jiffies + + msecs_to_jiffies(effect->replay.delay); + state->stop_at = state->play_at + + msecs_to_jiffies(effect->replay.length); + } + } else { + __set_bit(FF_EFFECT_PLAYING, &state->flags); + state->adj_at = jiffies; + ml_combine_effects(combo_effect, state, ml->gain); + } + } + + return combo_effect->type != 0; +} + +static void ml_play_effects(struct ml_device *ml) +{ + struct ff_effect effect; + DECLARE_BITMAP(handled_bm, FF_MEMLESS_EFFECTS); + + memset(handled_bm, 0, sizeof(handled_bm)); + + while (ml_get_combo_effect(ml, handled_bm, &effect)) + ml->play_effect(ml->dev, ml->private, &effect); + + ml_schedule_timer(ml); +} + +static void ml_effect_timer(unsigned long timer_data) +{ + struct input_dev *dev = (struct input_dev *)timer_data; + struct ml_device *ml = dev->ff->private; + + debug("timer: updating effects"); + + spin_lock(&ml->timer_lock); + ml_play_effects(ml); + spin_unlock(&ml->timer_lock); +} + +static void ml_ff_set_gain(struct input_dev *dev, u16 gain) +{ + struct ml_device *ml = dev->ff->private; + int i; + + spin_lock_bh(&ml->timer_lock); + + ml->gain = gain; + + for (i = 0; i < FF_MEMLESS_EFFECTS; i++) + __clear_bit(FF_EFFECT_PLAYING, &ml->states[i].flags); + + ml_play_effects(ml); + + spin_unlock_bh(&ml->timer_lock); +} + +static int ml_ff_playback(struct input_dev *dev, int effect_id, int value) +{ + struct ml_device *ml = dev->ff->private; + struct ml_effect_state *state = &ml->states[effect_id]; + unsigned long flags; + + spin_lock_irqsave(&ml->timer_lock, flags); + + if (value > 0) { + debug("initiated play"); + + __set_bit(FF_EFFECT_STARTED, &state->flags); + state->count = value; + state->play_at = jiffies + + msecs_to_jiffies(state->effect->replay.delay); + state->stop_at = state->play_at + + msecs_to_jiffies(state->effect->replay.length); + state->adj_at = state->play_at; + + ml_schedule_timer(ml); + + } else { + debug("initiated stop"); + + if (test_bit(FF_EFFECT_PLAYING, &state->flags)) + __set_bit(FF_EFFECT_ABORTING, &state->flags); + else + __clear_bit(FF_EFFECT_STARTED, &state->flags); + + ml_play_effects(ml); + } + + spin_unlock_irqrestore(&ml->timer_lock, flags); + + return 0; +} + +static int ml_ff_upload(struct input_dev *dev, + struct ff_effect *effect, struct ff_effect *old) +{ + struct ml_device *ml = dev->ff->private; + struct ml_effect_state *state = &ml->states[effect->id]; + + spin_lock_bh(&ml->timer_lock); + + if (test_bit(FF_EFFECT_STARTED, &state->flags)) { + __clear_bit(FF_EFFECT_PLAYING, &state->flags); + state->play_at = jiffies + + msecs_to_jiffies(state->effect->replay.delay); + state->stop_at = state->play_at + + msecs_to_jiffies(state->effect->replay.length); + state->adj_at = state->play_at; + ml_schedule_timer(ml); + } + + spin_unlock_bh(&ml->timer_lock); + + return 0; +} + +static void ml_ff_destroy(struct ff_device *ff) +{ + struct ml_device *ml = ff->private; + + kfree(ml->private); +} + +/** + * input_ff_create_memless() - create memoryless force-feedback device + * @dev: input device supporting force-feedback + * @data: driver-specific data to be passed into @play_effect + * @play_effect: driver-specific method for playing FF effect + */ +int input_ff_create_memless(struct input_dev *dev, void *data, + int (*play_effect)(struct input_dev *, void *, struct ff_effect *)) +{ + struct ml_device *ml; + struct ff_device *ff; + int error; + int i; + + ml = kzalloc(sizeof(struct ml_device), GFP_KERNEL); + if (!ml) + return -ENOMEM; + + ml->dev = dev; + ml->private = data; + ml->play_effect = play_effect; + ml->gain = 0xffff; + spin_lock_init(&ml->timer_lock); + setup_timer(&ml->timer, ml_effect_timer, (unsigned long)dev); + + set_bit(FF_GAIN, dev->ffbit); + + error = input_ff_create(dev, FF_MEMLESS_EFFECTS); + if (error) { + kfree(ml); + return error; + } + + ff = dev->ff; + ff->private = ml; + ff->upload = ml_ff_upload; + ff->playback = ml_ff_playback; + ff->set_gain = ml_ff_set_gain; + ff->destroy = ml_ff_destroy; + + /* we can emulate periodic effects with RUMBLE */ + if (test_bit(FF_RUMBLE, ff->ffbit)) { + set_bit(FF_PERIODIC, dev->ffbit); + set_bit(FF_SINE, dev->ffbit); + set_bit(FF_TRIANGLE, dev->ffbit); + set_bit(FF_SQUARE, dev->ffbit); + } + + for (i = 0; i < FF_MEMLESS_EFFECTS; i++) + ml->states[i].effect = &ff->effects[i]; + + return 0; +} +EXPORT_SYMBOL_GPL(input_ff_create_memless); diff --git a/drivers/input/fixp-arith.h b/drivers/input/fixp-arith.h new file mode 100644 index 0000000..3089d73 --- /dev/null +++ b/drivers/input/fixp-arith.h @@ -0,0 +1,87 @@ +#ifndef _FIXP_ARITH_H +#define _FIXP_ARITH_H + +/* + * Simplistic fixed-point arithmetics. + * Hmm, I'm probably duplicating some code :( + * + * Copyright (c) 2002 Johann Deneux + */ + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Should you need to contact me, the author, you can do so by + * e-mail - mail your message to <johann.deneux@gmail.com> + */ + +#include <linux/types.h> + +/* The type representing fixed-point values */ +typedef s16 fixp_t; + +#define FRAC_N 8 +#define FRAC_MASK ((1<<FRAC_N)-1) + +/* Not to be used directly. Use fixp_{cos,sin} */ +static const fixp_t cos_table[46] = { + 0x0100, 0x00FF, 0x00FF, 0x00FE, 0x00FD, 0x00FC, 0x00FA, 0x00F8, + 0x00F6, 0x00F3, 0x00F0, 0x00ED, 0x00E9, 0x00E6, 0x00E2, 0x00DD, + 0x00D9, 0x00D4, 0x00CF, 0x00C9, 0x00C4, 0x00BE, 0x00B8, 0x00B1, + 0x00AB, 0x00A4, 0x009D, 0x0096, 0x008F, 0x0087, 0x0080, 0x0078, + 0x0070, 0x0068, 0x005F, 0x0057, 0x004F, 0x0046, 0x003D, 0x0035, + 0x002C, 0x0023, 0x001A, 0x0011, 0x0008, 0x0000 +}; + + +/* a: 123 -> 123.0 */ +static inline fixp_t fixp_new(s16 a) +{ + return a<<FRAC_N; +} + +/* a: 0xFFFF -> -1.0 + 0x8000 -> 1.0 + 0x0000 -> 0.0 +*/ +static inline fixp_t fixp_new16(s16 a) +{ + return ((s32)a)>>(16-FRAC_N); +} + +static inline fixp_t fixp_cos(unsigned int degrees) +{ + int quadrant = (degrees / 90) & 3; + unsigned int i = degrees % 90; + + if (quadrant == 1 || quadrant == 3) + i = 90 - i; + + i >>= 1; + + return (quadrant == 1 || quadrant == 2)? -cos_table[i] : cos_table[i]; +} + +static inline fixp_t fixp_sin(unsigned int degrees) +{ + return -fixp_cos(degrees + 90); +} + +static inline fixp_t fixp_mult(fixp_t a, fixp_t b) +{ + return ((s32)(a*b))>>FRAC_N; +} + +#endif diff --git a/drivers/input/gameport/Kconfig b/drivers/input/gameport/Kconfig new file mode 100644 index 0000000..d279454 --- /dev/null +++ b/drivers/input/gameport/Kconfig @@ -0,0 +1,63 @@ +# +# 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_FM801 + tristate "ForteMedia FM801 gameport support" + depends on PCI + help + Say Y here if you have ForteMedia FM801 PCI audio controller + (Abit AU10, Genius Sound Maker, HP Workstation zx2000, + and others), and want to use its gameport. + + To compile this driver as a module, choose M here: the + module will be called fm801-gp. + +endif diff --git a/drivers/input/gameport/Makefile b/drivers/input/gameport/Makefile new file mode 100644 index 0000000..b6f6097 --- /dev/null +++ b/drivers/input/gameport/Makefile @@ -0,0 +1,11 @@ +# +# Makefile for the gameport drivers. +# + +# Each configuration option enables a list of files. + +obj-$(CONFIG_GAMEPORT) += gameport.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 diff --git a/drivers/input/gameport/emu10k1-gp.c b/drivers/input/gameport/emu10k1-gp.c new file mode 100644 index 0000000..b04930f --- /dev/null +++ b/drivers/input/gameport/emu10k1-gp.c @@ -0,0 +1,129 @@ +/* + * 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/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 = kzalloc(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 0000000..1dec00e --- /dev/null +++ b/drivers/input/gameport/fm801-gp.c @@ -0,0 +1,171 @@ +/* + * 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; + int error; + + gp = kzalloc(sizeof(struct fm801_gp), GFP_KERNEL); + port = gameport_allocate_port(); + if (!gp || !port) { + printk(KERN_ERR "fm801-gp: Memory allocation failed\n"); + error = -ENOMEM; + goto err_out_free; + } + + error = pci_enable_device(pci); + if (error) + goto err_out_free; + + 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) { + printk(KERN_DEBUG "fm801-gp: unable to grab region 0x%x-0x%x\n", + port->io, port->io + 0x0f); + error = -EBUSY; + goto err_out_disable_dev; + } + + pci_set_drvdata(pci, gp); + + outb(0x60, port->io + 0x0d); /* enable joystick 1 and 2 */ + gameport_register_port(port); + + return 0; + + err_out_disable_dev: + pci_disable_device(pci); + err_out_free: + gameport_free_port(port); + kfree(gp); + return error; +} + +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 0000000..2880eaa --- /dev/null +++ b/drivers/input/gameport/gameport.c @@ -0,0 +1,857 @@ +/* + * 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/slab.h> +#include <linux/delay.h> +#include <linux/kthread.h> +#include <linux/sched.h> /* HZ */ +#include <linux/mutex.h> +#include <linux/freezer.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_set_phys); +EXPORT_SYMBOL(gameport_start_polling); +EXPORT_SYMBOL(gameport_stop_polling); + +/* + * gameport_mutex protects entire gameport subsystem and is taken + * every time gameport port or driver registrered or unregistered. + */ +static DEFINE_MUTEX(gameport_mutex); + +static LIST_HEAD(gameport_list); + +static struct bus_type gameport_bus; + +static void gameport_add_driver(struct gameport_driver *drv); +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__) + +#include <asm/i8253.h> + +#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) +{ + 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(raw_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 int gameport_bind_driver(struct gameport *gameport, struct gameport_driver *drv) +{ + int error; + + gameport->dev.driver = &drv->driver; + if (drv->connect(gameport, drv)) { + gameport->dev.driver = NULL; + return -ENODEV; + } + + error = device_bind_driver(&gameport->dev); + if (error) { + printk(KERN_WARNING + "gameport: device_bind_driver() failed " + "for %s (%s) and %s, error: %d\n", + gameport->phys, gameport->name, + drv->description, error); + drv->disconnect(gameport); + gameport->dev.driver = NULL; + return error; + } + + return 0; +} + +static void gameport_find_driver(struct gameport *gameport) +{ + int error; + + error = device_attach(&gameport->dev); + if (error < 0) + printk(KERN_WARNING + "gameport: device_attach() failed for %s (%s), error: %d\n", + gameport->phys, gameport->name, error); +} + + +/* + * Gameport event processing. + */ + +enum gameport_event_type { + GAMEPORT_REGISTER_PORT, + GAMEPORT_REGISTER_DRIVER, + GAMEPORT_ATTACH_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 struct task_struct *gameport_task; + +static int gameport_queue_event(void *object, struct module *owner, + enum gameport_event_type event_type) +{ + unsigned long flags; + struct gameport_event *event; + int retval = 0; + + 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; + } + } + + event = kmalloc(sizeof(struct gameport_event), GFP_ATOMIC); + if (!event) { + printk(KERN_ERR + "gameport: Not enough memory to queue event %d\n", + event_type); + retval = -ENOMEM; + goto out; + } + + if (!try_module_get(owner)) { + printk(KERN_WARNING + "gameport: Can't get module reference, dropping event %d\n", + event_type); + kfree(event); + retval = -EINVAL; + goto out; + } + + event->type = event_type; + event->object = object; + event->owner = owner; + + list_add_tail(&event->node, &gameport_event_list); + wake_up(&gameport_wait); + +out: + spin_unlock_irqrestore(&gameport_event_lock, flags); + return retval; +} + +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_event(void) +{ + struct gameport_event *event; + + mutex_lock(&gameport_mutex); + + /* + * Note that we handle only one event here to give swsusp + * a chance to freeze kgameportd thread. Gameport events + * should be pretty rare so we are not concerned about + * taking performance hit. + */ + if ((event = gameport_get_event())) { + + switch (event->type) { + case GAMEPORT_REGISTER_PORT: + gameport_add_port(event->object); + break; + + case GAMEPORT_REGISTER_DRIVER: + gameport_add_driver(event->object); + break; + + default: + break; + } + + gameport_remove_duplicate_events(event); + gameport_free_event(event); + } + + mutex_unlock(&gameport_mutex); +} + +/* + * Remove all events that have been submitted for a given object, + * be it a gameport port or a driver. + */ +static void gameport_remove_pending_events(void *object) +{ + 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 == object) { + 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) +{ + set_freezable(); + do { + gameport_handle_event(); + wait_event_freezable(gameport_wait, + kthread_should_stop() || !list_empty(&gameport_event_list)); + } while (!kthread_should_stop()); + + printk(KERN_DEBUG "gameport: kgameportd exiting\n"); + return 0; +} + + +/* + * Gameport port operations + */ + +static ssize_t gameport_show_description(struct device *dev, struct device_attribute *attr, 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, struct device_attribute *attr, const char *buf, size_t count) +{ + struct gameport *gameport = to_gameport_port(dev); + struct device_driver *drv; + int error; + + error = mutex_lock_interruptible(&gameport_mutex); + if (error) + return error; + + 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); + error = gameport_bind_driver(gameport, to_gameport_driver(drv)); + put_driver(drv); + } else { + error = -EINVAL; + } + + mutex_unlock(&gameport_mutex); + + return error ? error : count; +} + +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); + + mutex_init(&gameport->drv_mutex); + 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; + + INIT_LIST_HEAD(&gameport->node); + 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) +{ + int error; + + 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); + + error = device_add(&gameport->dev); + if (error) + printk(KERN_ERR + "gameport: device_add() failed for %s (%s), error: %d\n", + gameport->phys, gameport->name, error); + else + 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); + gameport->registered = 0; + } + + list_del_init(&gameport->node); + + 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; + + device_release_driver(&s->dev); + gameport_destroy_port(s); + } while ((s = parent) != gameport); + } + + /* + * Ok, no children left, now disconnect this port + */ + device_release_driver(&gameport->dev); +} + +/* + * 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) +{ + mutex_lock(&gameport_mutex); + gameport_disconnect_port(gameport); + gameport_destroy_port(gameport); + mutex_unlock(&gameport_mutex); +} + + +/* + * 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; +} + +static void gameport_add_driver(struct gameport_driver *drv) +{ + int error; + + error = driver_register(&drv->driver); + if (error) + printk(KERN_ERR + "gameport: driver_register() failed for %s, error: %d\n", + drv->driver.name, error); +} + +int __gameport_register_driver(struct gameport_driver *drv, struct module *owner, + const char *mod_name) +{ + int error; + + drv->driver.bus = &gameport_bus; + drv->driver.owner = owner; + drv->driver.mod_name = mod_name; + + /* + * Temporarily disable automatic binding because probing + * takes long time and we are better off doing it in kgameportd + */ + drv->ignore = 1; + + error = driver_register(&drv->driver); + if (error) { + printk(KERN_ERR + "gameport: driver_register() failed for %s, error: %d\n", + drv->driver.name, error); + return error; + } + + /* + * Reset ignore flag and let kgameportd bind the driver to free ports + */ + drv->ignore = 0; + error = gameport_queue_event(drv, NULL, GAMEPORT_ATTACH_DRIVER); + if (error) { + driver_unregister(&drv->driver); + return error; + } + + return 0; +} + +void gameport_unregister_driver(struct gameport_driver *drv) +{ + struct gameport *gameport; + + mutex_lock(&gameport_mutex); + + drv->ignore = 1; /* so gameport_find_driver ignores it */ + gameport_remove_pending_events(drv); + +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); + + mutex_unlock(&gameport_mutex); +} + +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 struct bus_type gameport_bus = { + .name = "gameport", + .dev_attrs = gameport_device_attrs, + .drv_attrs = gameport_driver_attrs, + .match = gameport_bus_match, + .probe = gameport_driver_probe, + .remove = gameport_driver_remove, +}; + +static void gameport_set_drv(struct gameport *gameport, struct gameport_driver *drv) +{ + mutex_lock(&gameport->drv_mutex); + gameport->drv = drv; + mutex_unlock(&gameport->drv_mutex); +} + +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) +{ + int error; + + error = bus_register(&gameport_bus); + if (error) { + printk(KERN_ERR "gameport: failed to register gameport bus, error: %d\n", error); + return error; + } + + gameport_task = kthread_run(gameport_thread, NULL, "kgameportd"); + if (IS_ERR(gameport_task)) { + bus_unregister(&gameport_bus); + error = PTR_ERR(gameport_task); + printk(KERN_ERR "gameport: Failed to start kgameportd, error: %d\n", error); + return error; + } + + return 0; +} + +static void __exit gameport_exit(void) +{ + bus_unregister(&gameport_bus); + kthread_stop(gameport_task); +} + +subsys_initcall(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 0000000..06ad36e --- /dev/null +++ b/drivers/input/gameport/lightning.c @@ -0,0 +1,342 @@ +/* + * 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 -EBUSY; + + 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 -ENODEV; + } + + 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 0000000..2b282cd --- /dev/null +++ b/drivers/input/gameport/ns558.c @@ -0,0 +1,286 @@ +/* + * 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/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 = kzalloc(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; + } + + 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 = kzalloc(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 __init ns558_init(void) +{ + int i = 0; + int error; + + error = pnp_register_driver(&ns558_pnp_driver); + if (error && error != -ENODEV) /* should be ENOSYS really */ + return error; + +/* + * Probe ISA ports after PnP, so that PnP ports that are already + * enabled get detected as PnP. This may be suboptimal in multi-device + * configurations, but saves hassle with simple setups. + */ + + while (ns558_isa_portlist[i]) + ns558_isa_probe(ns558_isa_portlist[i++]); + + return list_empty(&ns558_list) && error ? -ENODEV : 0; +} + +static void __exit ns558_exit(void) +{ + struct ns558 *ns558, *safe; + + list_for_each_entry_safe(ns558, safe, &ns558_list, node) { + gameport_unregister_port(ns558->gameport); + release_region(ns558->io & ~(ns558->size - 1), ns558->size); + kfree(ns558); + } + + pnp_unregister_driver(&ns558_pnp_driver); +} + +module_init(ns558_init); +module_exit(ns558_exit); diff --git a/drivers/input/input-polldev.c b/drivers/input/input-polldev.c new file mode 100644 index 0000000..0d3ce7a --- /dev/null +++ b/drivers/input/input-polldev.c @@ -0,0 +1,181 @@ +/* + * Generic implementation of a polled input device + + * Copyright (c) 2007 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/jiffies.h> +#include <linux/mutex.h> +#include <linux/input-polldev.h> + +MODULE_AUTHOR("Dmitry Torokhov <dtor@mail.ru>"); +MODULE_DESCRIPTION("Generic implementation of a polled input device"); +MODULE_LICENSE("GPL v2"); +MODULE_VERSION("0.1"); + +static DEFINE_MUTEX(polldev_mutex); +static int polldev_users; +static struct workqueue_struct *polldev_wq; + +static int input_polldev_start_workqueue(void) +{ + int retval; + + retval = mutex_lock_interruptible(&polldev_mutex); + if (retval) + return retval; + + if (!polldev_users) { + polldev_wq = create_singlethread_workqueue("ipolldevd"); + if (!polldev_wq) { + printk(KERN_ERR "input-polldev: failed to create " + "ipolldevd workqueue\n"); + retval = -ENOMEM; + goto out; + } + } + + polldev_users++; + + out: + mutex_unlock(&polldev_mutex); + return retval; +} + +static void input_polldev_stop_workqueue(void) +{ + mutex_lock(&polldev_mutex); + + if (!--polldev_users) + destroy_workqueue(polldev_wq); + + mutex_unlock(&polldev_mutex); +} + +static void input_polled_device_work(struct work_struct *work) +{ + struct input_polled_dev *dev = + container_of(work, struct input_polled_dev, work.work); + unsigned long delay; + + dev->poll(dev); + + delay = msecs_to_jiffies(dev->poll_interval); + if (delay >= HZ) + delay = round_jiffies_relative(delay); + + queue_delayed_work(polldev_wq, &dev->work, delay); +} + +static int input_open_polled_device(struct input_dev *input) +{ + struct input_polled_dev *dev = input_get_drvdata(input); + int error; + + error = input_polldev_start_workqueue(); + if (error) + return error; + + if (dev->flush) + dev->flush(dev); + + queue_delayed_work(polldev_wq, &dev->work, + msecs_to_jiffies(dev->poll_interval)); + + return 0; +} + +static void input_close_polled_device(struct input_dev *input) +{ + struct input_polled_dev *dev = input_get_drvdata(input); + + cancel_delayed_work_sync(&dev->work); + input_polldev_stop_workqueue(); +} + +/** + * input_allocate_polled_device - allocated memory polled device + * + * The function allocates memory for a polled device and also + * for an input device associated with this polled device. + */ +struct input_polled_dev *input_allocate_polled_device(void) +{ + struct input_polled_dev *dev; + + dev = kzalloc(sizeof(struct input_polled_dev), GFP_KERNEL); + if (!dev) + return NULL; + + dev->input = input_allocate_device(); + if (!dev->input) { + kfree(dev); + return NULL; + } + + return dev; +} +EXPORT_SYMBOL(input_allocate_polled_device); + +/** + * input_free_polled_device - free memory allocated for polled device + * @dev: device to free + * + * The function frees memory allocated for polling device and drops + * reference to the associated input device (if present). + */ +void input_free_polled_device(struct input_polled_dev *dev) +{ + if (dev) { + input_free_device(dev->input); + kfree(dev); + } +} +EXPORT_SYMBOL(input_free_polled_device); + +/** + * input_register_polled_device - register polled device + * @dev: device to register + * + * The function registers previously initialized polled input device + * with input layer. The device should be allocated with call to + * input_allocate_polled_device(). Callers should also set up poll() + * method and set up capabilities (id, name, phys, bits) of the + * corresponing input_dev structure. + */ +int input_register_polled_device(struct input_polled_dev *dev) +{ + struct input_dev *input = dev->input; + + input_set_drvdata(input, dev); + INIT_DELAYED_WORK(&dev->work, input_polled_device_work); + if (!dev->poll_interval) + dev->poll_interval = 500; + input->open = input_open_polled_device; + input->close = input_close_polled_device; + + return input_register_device(input); +} +EXPORT_SYMBOL(input_register_polled_device); + +/** + * input_unregister_polled_device - unregister polled device + * @dev: device to unregister + * + * The function unregisters previously registered polled input + * device from input layer. Polling is stopped and device is + * ready to be freed with call to input_free_polled_device(). + * Callers should not attempt to access dev->input pointer + * after calling this function. + */ +void input_unregister_polled_device(struct input_polled_dev *dev) +{ + input_unregister_device(dev->input); + dev->input = NULL; +} +EXPORT_SYMBOL(input_unregister_polled_device); + diff --git a/drivers/input/input.c b/drivers/input/input.c new file mode 100644 index 0000000..c13ced3 --- /dev/null +++ b/drivers/input/input.c @@ -0,0 +1,1668 @@ +/* + * 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/input.h> +#include <linux/module.h> +#include <linux/random.h> +#include <linux/major.h> +#include <linux/proc_fs.h> +#include <linux/seq_file.h> +#include <linux/poll.h> +#include <linux/device.h> +#include <linux/mutex.h> +#include <linux/rcupdate.h> +#include <linux/smp_lock.h> + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@suse.cz>"); +MODULE_DESCRIPTION("Input core"); +MODULE_LICENSE("GPL"); + +#define INPUT_DEVICES 256 + +static LIST_HEAD(input_dev_list); +static LIST_HEAD(input_handler_list); + +/* + * input_mutex protects access to both input_dev_list and input_handler_list. + * This also causes input_[un]register_device and input_[un]register_handler + * be mutually exclusive which simplifies locking in drivers implementing + * input handlers. + */ +static DEFINE_MUTEX(input_mutex); + +static struct input_handler *input_table[8]; + +static inline int is_event_supported(unsigned int code, + unsigned long *bm, unsigned int max) +{ + return code <= max && test_bit(code, bm); +} + +static int input_defuzz_abs_event(int value, int old_val, int fuzz) +{ + if (fuzz) { + if (value > old_val - fuzz / 2 && value < old_val + fuzz / 2) + return old_val; + + if (value > old_val - fuzz && value < old_val + fuzz) + return (old_val * 3 + value) / 4; + + if (value > old_val - fuzz * 2 && value < old_val + fuzz * 2) + return (old_val + value) / 2; + } + + return value; +} + +/* + * Pass event through all open handles. This function is called with + * dev->event_lock held and interrupts disabled. + */ +static void input_pass_event(struct input_dev *dev, + unsigned int type, unsigned int code, int value) +{ + struct input_handle *handle; + + rcu_read_lock(); + + handle = rcu_dereference(dev->grab); + if (handle) + handle->handler->event(handle, type, code, value); + else + list_for_each_entry_rcu(handle, &dev->h_list, d_node) + if (handle->open) + handle->handler->event(handle, + type, code, value); + rcu_read_unlock(); +} + +/* + * Generate software autorepeat event. Note that we take + * dev->event_lock here to avoid racing with input_event + * which may cause keys get "stuck". + */ +static void input_repeat_key(unsigned long data) +{ + struct input_dev *dev = (void *) data; + unsigned long flags; + + spin_lock_irqsave(&dev->event_lock, flags); + + if (test_bit(dev->repeat_key, dev->key) && + is_event_supported(dev->repeat_key, dev->keybit, KEY_MAX)) { + + input_pass_event(dev, EV_KEY, dev->repeat_key, 2); + + if (dev->sync) { + /* + * Only send SYN_REPORT if we are not in a middle + * of driver parsing a new hardware packet. + * Otherwise assume that the driver will send + * SYN_REPORT once it's done. + */ + input_pass_event(dev, EV_SYN, SYN_REPORT, 1); + } + + if (dev->rep[REP_PERIOD]) + mod_timer(&dev->timer, jiffies + + msecs_to_jiffies(dev->rep[REP_PERIOD])); + } + + spin_unlock_irqrestore(&dev->event_lock, flags); +} + +static void input_start_autorepeat(struct input_dev *dev, int code) +{ + if (test_bit(EV_REP, dev->evbit) && + dev->rep[REP_PERIOD] && dev->rep[REP_DELAY] && + dev->timer.data) { + dev->repeat_key = code; + mod_timer(&dev->timer, + jiffies + msecs_to_jiffies(dev->rep[REP_DELAY])); + } +} + +#define INPUT_IGNORE_EVENT 0 +#define INPUT_PASS_TO_HANDLERS 1 +#define INPUT_PASS_TO_DEVICE 2 +#define INPUT_PASS_TO_ALL (INPUT_PASS_TO_HANDLERS | INPUT_PASS_TO_DEVICE) + +static void input_handle_event(struct input_dev *dev, + unsigned int type, unsigned int code, int value) +{ + int disposition = INPUT_IGNORE_EVENT; + + switch (type) { + + case EV_SYN: + switch (code) { + case SYN_CONFIG: + disposition = INPUT_PASS_TO_ALL; + break; + + case SYN_REPORT: + if (!dev->sync) { + dev->sync = 1; + disposition = INPUT_PASS_TO_HANDLERS; + } + break; + } + break; + + case EV_KEY: + if (is_event_supported(code, dev->keybit, KEY_MAX) && + !!test_bit(code, dev->key) != value) { + + if (value != 2) { + __change_bit(code, dev->key); + if (value) + input_start_autorepeat(dev, code); + } + + disposition = INPUT_PASS_TO_HANDLERS; + } + break; + + case EV_SW: + if (is_event_supported(code, dev->swbit, SW_MAX) && + !!test_bit(code, dev->sw) != value) { + + __change_bit(code, dev->sw); + disposition = INPUT_PASS_TO_HANDLERS; + } + break; + + case EV_ABS: + if (is_event_supported(code, dev->absbit, ABS_MAX)) { + + value = input_defuzz_abs_event(value, + dev->abs[code], dev->absfuzz[code]); + + if (dev->abs[code] != value) { + dev->abs[code] = value; + disposition = INPUT_PASS_TO_HANDLERS; + } + } + break; + + case EV_REL: + if (is_event_supported(code, dev->relbit, REL_MAX) && value) + disposition = INPUT_PASS_TO_HANDLERS; + + break; + + case EV_MSC: + if (is_event_supported(code, dev->mscbit, MSC_MAX)) + disposition = INPUT_PASS_TO_ALL; + + break; + + case EV_LED: + if (is_event_supported(code, dev->ledbit, LED_MAX) && + !!test_bit(code, dev->led) != value) { + + __change_bit(code, dev->led); + disposition = INPUT_PASS_TO_ALL; + } + break; + + case EV_SND: + if (is_event_supported(code, dev->sndbit, SND_MAX)) { + + if (!!test_bit(code, dev->snd) != !!value) + __change_bit(code, dev->snd); + disposition = INPUT_PASS_TO_ALL; + } + break; + + case EV_REP: + if (code <= REP_MAX && value >= 0 && dev->rep[code] != value) { + dev->rep[code] = value; + disposition = INPUT_PASS_TO_ALL; + } + break; + + case EV_FF: + if (value >= 0) + disposition = INPUT_PASS_TO_ALL; + break; + + case EV_PWR: + disposition = INPUT_PASS_TO_ALL; + break; + } + + if (disposition != INPUT_IGNORE_EVENT && type != EV_SYN) + dev->sync = 0; + + if ((disposition & INPUT_PASS_TO_DEVICE) && dev->event) + dev->event(dev, type, code, value); + + if (disposition & INPUT_PASS_TO_HANDLERS) + input_pass_event(dev, type, code, value); +} + +/** + * input_event() - report new input event + * @dev: device that generated the event + * @type: type of the event + * @code: event code + * @value: value of the event + * + * This function should be used by drivers implementing various input + * devices. See also input_inject_event(). + */ + +void input_event(struct input_dev *dev, + unsigned int type, unsigned int code, int value) +{ + unsigned long flags; + + if (is_event_supported(type, dev->evbit, EV_MAX)) { + + spin_lock_irqsave(&dev->event_lock, flags); + add_input_randomness(type, code, value); + input_handle_event(dev, type, code, value); + spin_unlock_irqrestore(&dev->event_lock, flags); + } +} +EXPORT_SYMBOL(input_event); + +/** + * input_inject_event() - send input event from input handler + * @handle: input handle to send event through + * @type: type of the event + * @code: event code + * @value: value of the event + * + * Similar to input_event() but will ignore event if device is + * "grabbed" and handle injecting event is not the one that owns + * the device. + */ +void input_inject_event(struct input_handle *handle, + unsigned int type, unsigned int code, int value) +{ + struct input_dev *dev = handle->dev; + struct input_handle *grab; + unsigned long flags; + + if (is_event_supported(type, dev->evbit, EV_MAX)) { + spin_lock_irqsave(&dev->event_lock, flags); + + rcu_read_lock(); + grab = rcu_dereference(dev->grab); + if (!grab || grab == handle) + input_handle_event(dev, type, code, value); + rcu_read_unlock(); + + spin_unlock_irqrestore(&dev->event_lock, flags); + } +} +EXPORT_SYMBOL(input_inject_event); + +/** + * input_grab_device - grabs device for exclusive use + * @handle: input handle that wants to own the device + * + * When a device is grabbed by an input handle all events generated by + * the device are delivered only to this handle. Also events injected + * by other input handles are ignored while device is grabbed. + */ +int input_grab_device(struct input_handle *handle) +{ + struct input_dev *dev = handle->dev; + int retval; + + retval = mutex_lock_interruptible(&dev->mutex); + if (retval) + return retval; + + if (dev->grab) { + retval = -EBUSY; + goto out; + } + + rcu_assign_pointer(dev->grab, handle); + synchronize_rcu(); + + out: + mutex_unlock(&dev->mutex); + return retval; +} +EXPORT_SYMBOL(input_grab_device); + +static void __input_release_device(struct input_handle *handle) +{ + struct input_dev *dev = handle->dev; + + if (dev->grab == handle) { + rcu_assign_pointer(dev->grab, NULL); + /* Make sure input_pass_event() notices that grab is gone */ + synchronize_rcu(); + + list_for_each_entry(handle, &dev->h_list, d_node) + if (handle->open && handle->handler->start) + handle->handler->start(handle); + } +} + +/** + * input_release_device - release previously grabbed device + * @handle: input handle that owns the device + * + * Releases previously grabbed device so that other input handles can + * start receiving input events. Upon release all handlers attached + * to the device have their start() method called so they have a change + * to synchronize device state with the rest of the system. + */ +void input_release_device(struct input_handle *handle) +{ + struct input_dev *dev = handle->dev; + + mutex_lock(&dev->mutex); + __input_release_device(handle); + mutex_unlock(&dev->mutex); +} +EXPORT_SYMBOL(input_release_device); + +/** + * input_open_device - open input device + * @handle: handle through which device is being accessed + * + * This function should be called by input handlers when they + * want to start receive events from given input device. + */ +int input_open_device(struct input_handle *handle) +{ + struct input_dev *dev = handle->dev; + int retval; + + retval = mutex_lock_interruptible(&dev->mutex); + if (retval) + return retval; + + if (dev->going_away) { + retval = -ENODEV; + goto out; + } + + handle->open++; + + if (!dev->users++ && dev->open) + retval = dev->open(dev); + + if (retval) { + dev->users--; + if (!--handle->open) { + /* + * Make sure we are not delivering any more events + * through this handle + */ + synchronize_rcu(); + } + } + + out: + mutex_unlock(&dev->mutex); + return retval; +} +EXPORT_SYMBOL(input_open_device); + +int input_flush_device(struct input_handle *handle, struct file *file) +{ + struct input_dev *dev = handle->dev; + int retval; + + retval = mutex_lock_interruptible(&dev->mutex); + if (retval) + return retval; + + if (dev->flush) + retval = dev->flush(dev, file); + + mutex_unlock(&dev->mutex); + return retval; +} +EXPORT_SYMBOL(input_flush_device); + +/** + * input_close_device - close input device + * @handle: handle through which device is being accessed + * + * This function should be called by input handlers when they + * want to stop receive events from given input device. + */ +void input_close_device(struct input_handle *handle) +{ + struct input_dev *dev = handle->dev; + + mutex_lock(&dev->mutex); + + __input_release_device(handle); + + if (!--dev->users && dev->close) + dev->close(dev); + + if (!--handle->open) { + /* + * synchronize_rcu() makes sure that input_pass_event() + * completed and that no more input events are delivered + * through this handle + */ + synchronize_rcu(); + } + + mutex_unlock(&dev->mutex); +} +EXPORT_SYMBOL(input_close_device); + +/* + * Prepare device for unregistering + */ +static void input_disconnect_device(struct input_dev *dev) +{ + struct input_handle *handle; + int code; + + /* + * Mark device as going away. Note that we take dev->mutex here + * not to protect access to dev->going_away but rather to ensure + * that there are no threads in the middle of input_open_device() + */ + mutex_lock(&dev->mutex); + dev->going_away = 1; + mutex_unlock(&dev->mutex); + + spin_lock_irq(&dev->event_lock); + + /* + * Simulate keyup events for all pressed keys so that handlers + * are not left with "stuck" keys. The driver may continue + * generate events even after we done here but they will not + * reach any handlers. + */ + if (is_event_supported(EV_KEY, dev->evbit, EV_MAX)) { + for (code = 0; code <= KEY_MAX; code++) { + if (is_event_supported(code, dev->keybit, KEY_MAX) && + __test_and_clear_bit(code, dev->key)) { + input_pass_event(dev, EV_KEY, code, 0); + } + } + input_pass_event(dev, EV_SYN, SYN_REPORT, 1); + } + + list_for_each_entry(handle, &dev->h_list, d_node) + handle->open = 0; + + spin_unlock_irq(&dev->event_lock); +} + +static int input_fetch_keycode(struct input_dev *dev, int scancode) +{ + switch (dev->keycodesize) { + case 1: + return ((u8 *)dev->keycode)[scancode]; + + case 2: + return ((u16 *)dev->keycode)[scancode]; + + default: + return ((u32 *)dev->keycode)[scancode]; + } +} + +static int input_default_getkeycode(struct input_dev *dev, + int scancode, int *keycode) +{ + if (!dev->keycodesize) + return -EINVAL; + + if (scancode >= dev->keycodemax) + return -EINVAL; + + *keycode = input_fetch_keycode(dev, scancode); + + return 0; +} + +static int input_default_setkeycode(struct input_dev *dev, + int scancode, int keycode) +{ + int old_keycode; + int i; + + if (scancode >= dev->keycodemax) + return -EINVAL; + + if (!dev->keycodesize) + return -EINVAL; + + if (dev->keycodesize < sizeof(keycode) && (keycode >> (dev->keycodesize * 8))) + return -EINVAL; + + switch (dev->keycodesize) { + case 1: { + u8 *k = (u8 *)dev->keycode; + old_keycode = k[scancode]; + k[scancode] = keycode; + break; + } + case 2: { + u16 *k = (u16 *)dev->keycode; + old_keycode = k[scancode]; + k[scancode] = keycode; + break; + } + default: { + u32 *k = (u32 *)dev->keycode; + old_keycode = k[scancode]; + k[scancode] = keycode; + break; + } + } + + clear_bit(old_keycode, dev->keybit); + set_bit(keycode, dev->keybit); + + for (i = 0; i < dev->keycodemax; i++) { + if (input_fetch_keycode(dev, i) == old_keycode) { + set_bit(old_keycode, dev->keybit); + break; /* Setting the bit twice is useless, so break */ + } + } + + return 0; +} + +/** + * input_get_keycode - retrieve keycode currently mapped to a given scancode + * @dev: input device which keymap is being queried + * @scancode: scancode (or its equivalent for device in question) for which + * keycode is needed + * @keycode: result + * + * This function should be called by anyone interested in retrieving current + * keymap. Presently keyboard and evdev handlers use it. + */ +int input_get_keycode(struct input_dev *dev, int scancode, int *keycode) +{ + if (scancode < 0) + return -EINVAL; + + return dev->getkeycode(dev, scancode, keycode); +} +EXPORT_SYMBOL(input_get_keycode); + +/** + * input_get_keycode - assign new keycode to a given scancode + * @dev: input device which keymap is being updated + * @scancode: scancode (or its equivalent for device in question) + * @keycode: new keycode to be assigned to the scancode + * + * This function should be called by anyone needing to update current + * keymap. Presently keyboard and evdev handlers use it. + */ +int input_set_keycode(struct input_dev *dev, int scancode, int keycode) +{ + unsigned long flags; + int old_keycode; + int retval; + + if (scancode < 0) + return -EINVAL; + + if (keycode < 0 || keycode > KEY_MAX) + return -EINVAL; + + spin_lock_irqsave(&dev->event_lock, flags); + + retval = dev->getkeycode(dev, scancode, &old_keycode); + if (retval) + goto out; + + retval = dev->setkeycode(dev, scancode, keycode); + if (retval) + goto out; + + /* + * Simulate keyup event if keycode is not present + * in the keymap anymore + */ + if (test_bit(EV_KEY, dev->evbit) && + !is_event_supported(old_keycode, dev->keybit, KEY_MAX) && + __test_and_clear_bit(old_keycode, dev->key)) { + + input_pass_event(dev, EV_KEY, old_keycode, 0); + if (dev->sync) + input_pass_event(dev, EV_SYN, SYN_REPORT, 1); + } + + out: + spin_unlock_irqrestore(&dev->event_lock, flags); + + return retval; +} +EXPORT_SYMBOL(input_set_keycode); + +#define MATCH_BIT(bit, max) \ + for (i = 0; i < BITS_TO_LONGS(max); i++) \ + if ((id->bit[i] & dev->bit[i]) != id->bit[i]) \ + break; \ + if (i != BITS_TO_LONGS(max)) \ + continue; + +static const struct input_device_id *input_match_device(const 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->bustype != dev->id.bustype) + continue; + + if (id->flags & INPUT_DEVICE_ID_MATCH_VENDOR) + if (id->vendor != dev->id.vendor) + continue; + + if (id->flags & INPUT_DEVICE_ID_MATCH_PRODUCT) + if (id->product != dev->id.product) + continue; + + if (id->flags & INPUT_DEVICE_ID_MATCH_VERSION) + if (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); + MATCH_BIT(swbit, SW_MAX); + + return id; + } + + return NULL; +} + +static int input_attach_handler(struct input_dev *dev, struct input_handler *handler) +{ + const struct input_device_id *id; + int error; + + if (handler->blacklist && input_match_device(handler->blacklist, dev)) + return -ENODEV; + + id = input_match_device(handler->id_table, dev); + if (!id) + return -ENODEV; + + error = handler->connect(handler, dev, id); + if (error && error != -ENODEV) + printk(KERN_ERR + "input: failed to attach handler %s to device %s, " + "error: %d\n", + handler->name, kobject_name(&dev->dev.kobj), error); + + return error; +} + + +#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; + +static inline void input_wakeup_procfs_readers(void) +{ + input_devices_state++; + wake_up(&input_devices_poll_wait); +} + +static unsigned int input_proc_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 void *input_devices_seq_start(struct seq_file *seq, loff_t *pos) +{ + if (mutex_lock_interruptible(&input_mutex)) + return NULL; + + return seq_list_start(&input_dev_list, *pos); +} + +static void *input_devices_seq_next(struct seq_file *seq, void *v, loff_t *pos) +{ + return seq_list_next(v, &input_dev_list, pos); +} + +static void input_devices_seq_stop(struct seq_file *seq, void *v) +{ + mutex_unlock(&input_mutex); +} + +static void input_seq_print_bitmap(struct seq_file *seq, const char *name, + unsigned long *bitmap, int max) +{ + int i; + + for (i = BITS_TO_LONGS(max) - 1; i > 0; i--) + if (bitmap[i]) + break; + + seq_printf(seq, "B: %s=", name); + for (; i >= 0; i--) + seq_printf(seq, "%lx%s", bitmap[i], i > 0 ? " " : ""); + seq_putc(seq, '\n'); +} + +static int input_devices_seq_show(struct seq_file *seq, void *v) +{ + struct input_dev *dev = container_of(v, struct input_dev, node); + const char *path = kobject_get_path(&dev->dev.kobj, GFP_KERNEL); + struct input_handle *handle; + + seq_printf(seq, "I: Bus=%04x Vendor=%04x Product=%04x Version=%04x\n", + dev->id.bustype, dev->id.vendor, dev->id.product, dev->id.version); + + seq_printf(seq, "N: Name=\"%s\"\n", dev->name ? dev->name : ""); + seq_printf(seq, "P: Phys=%s\n", dev->phys ? dev->phys : ""); + seq_printf(seq, "S: Sysfs=%s\n", path ? path : ""); + seq_printf(seq, "U: Uniq=%s\n", dev->uniq ? dev->uniq : ""); + seq_printf(seq, "H: Handlers="); + + list_for_each_entry(handle, &dev->h_list, d_node) + seq_printf(seq, "%s ", handle->name); + seq_putc(seq, '\n'); + + input_seq_print_bitmap(seq, "EV", dev->evbit, EV_MAX); + if (test_bit(EV_KEY, dev->evbit)) + input_seq_print_bitmap(seq, "KEY", dev->keybit, KEY_MAX); + if (test_bit(EV_REL, dev->evbit)) + input_seq_print_bitmap(seq, "REL", dev->relbit, REL_MAX); + if (test_bit(EV_ABS, dev->evbit)) + input_seq_print_bitmap(seq, "ABS", dev->absbit, ABS_MAX); + if (test_bit(EV_MSC, dev->evbit)) + input_seq_print_bitmap(seq, "MSC", dev->mscbit, MSC_MAX); + if (test_bit(EV_LED, dev->evbit)) + input_seq_print_bitmap(seq, "LED", dev->ledbit, LED_MAX); + if (test_bit(EV_SND, dev->evbit)) + input_seq_print_bitmap(seq, "SND", dev->sndbit, SND_MAX); + if (test_bit(EV_FF, dev->evbit)) + input_seq_print_bitmap(seq, "FF", dev->ffbit, FF_MAX); + if (test_bit(EV_SW, dev->evbit)) + input_seq_print_bitmap(seq, "SW", dev->swbit, SW_MAX); + + seq_putc(seq, '\n'); + + kfree(path); + return 0; +} + +static const struct seq_operations input_devices_seq_ops = { + .start = input_devices_seq_start, + .next = input_devices_seq_next, + .stop = input_devices_seq_stop, + .show = input_devices_seq_show, +}; + +static int input_proc_devices_open(struct inode *inode, struct file *file) +{ + return seq_open(file, &input_devices_seq_ops); +} + +static const struct file_operations input_devices_fileops = { + .owner = THIS_MODULE, + .open = input_proc_devices_open, + .poll = input_proc_devices_poll, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; + +static void *input_handlers_seq_start(struct seq_file *seq, loff_t *pos) +{ + if (mutex_lock_interruptible(&input_mutex)) + return NULL; + + seq->private = (void *)(unsigned long)*pos; + return seq_list_start(&input_handler_list, *pos); +} + +static void *input_handlers_seq_next(struct seq_file *seq, void *v, loff_t *pos) +{ + seq->private = (void *)(unsigned long)(*pos + 1); + return seq_list_next(v, &input_handler_list, pos); +} + +static void input_handlers_seq_stop(struct seq_file *seq, void *v) +{ + mutex_unlock(&input_mutex); +} + +static int input_handlers_seq_show(struct seq_file *seq, void *v) +{ + struct input_handler *handler = container_of(v, struct input_handler, node); + + seq_printf(seq, "N: Number=%ld Name=%s", + (unsigned long)seq->private, handler->name); + if (handler->fops) + seq_printf(seq, " Minor=%d", handler->minor); + seq_putc(seq, '\n'); + + return 0; +} +static const struct seq_operations input_handlers_seq_ops = { + .start = input_handlers_seq_start, + .next = input_handlers_seq_next, + .stop = input_handlers_seq_stop, + .show = input_handlers_seq_show, +}; + +static int input_proc_handlers_open(struct inode *inode, struct file *file) +{ + return seq_open(file, &input_handlers_seq_ops); +} + +static const struct file_operations input_handlers_fileops = { + .owner = THIS_MODULE, + .open = input_proc_handlers_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; + +static int __init input_proc_init(void) +{ + struct proc_dir_entry *entry; + + proc_bus_input_dir = proc_mkdir("bus/input", NULL); + if (!proc_bus_input_dir) + return -ENOMEM; + + proc_bus_input_dir->owner = THIS_MODULE; + + entry = proc_create("devices", 0, proc_bus_input_dir, + &input_devices_fileops); + if (!entry) + goto fail1; + + entry = proc_create("handlers", 0, proc_bus_input_dir, + &input_handlers_fileops); + if (!entry) + goto fail2; + + return 0; + + fail2: remove_proc_entry("devices", proc_bus_input_dir); + fail1: remove_proc_entry("bus/input", NULL); + return -ENOMEM; +} + +static void input_proc_exit(void) +{ + remove_proc_entry("devices", proc_bus_input_dir); + remove_proc_entry("handlers", proc_bus_input_dir); + remove_proc_entry("bus/input", NULL); +} + +#else /* !CONFIG_PROC_FS */ +static inline void input_wakeup_procfs_readers(void) { } +static inline int input_proc_init(void) { return 0; } +static inline void input_proc_exit(void) { } +#endif + +#define INPUT_DEV_STRING_ATTR_SHOW(name) \ +static ssize_t input_dev_show_##name(struct device *dev, \ + struct device_attribute *attr, \ + char *buf) \ +{ \ + struct input_dev *input_dev = to_input_dev(dev); \ + \ + return scnprintf(buf, PAGE_SIZE, "%s\n", \ + input_dev->name ? input_dev->name : ""); \ +} \ +static DEVICE_ATTR(name, S_IRUGO, input_dev_show_##name, NULL) + +INPUT_DEV_STRING_ATTR_SHOW(name); +INPUT_DEV_STRING_ATTR_SHOW(phys); +INPUT_DEV_STRING_ATTR_SHOW(uniq); + +static int input_print_modalias_bits(char *buf, int size, + char name, unsigned long *bm, + unsigned int min_bit, unsigned int max_bit) +{ + int len = 0, i; + + len += snprintf(buf, max(size, 0), "%c", name); + for (i = min_bit; i < max_bit; i++) + if (bm[BIT_WORD(i)] & BIT_MASK(i)) + len += snprintf(buf + len, max(size - len, 0), "%X,", i); + return len; +} + +static int input_print_modalias(char *buf, int size, struct input_dev *id, + int add_cr) +{ + int len; + + len = snprintf(buf, max(size, 0), + "input:b%04Xv%04Xp%04Xe%04X-", + id->id.bustype, id->id.vendor, + id->id.product, id->id.version); + + len += input_print_modalias_bits(buf + len, size - len, + 'e', id->evbit, 0, EV_MAX); + len += input_print_modalias_bits(buf + len, size - len, + 'k', id->keybit, KEY_MIN_INTERESTING, KEY_MAX); + len += input_print_modalias_bits(buf + len, size - len, + 'r', id->relbit, 0, REL_MAX); + len += input_print_modalias_bits(buf + len, size - len, + 'a', id->absbit, 0, ABS_MAX); + len += input_print_modalias_bits(buf + len, size - len, + 'm', id->mscbit, 0, MSC_MAX); + len += input_print_modalias_bits(buf + len, size - len, + 'l', id->ledbit, 0, LED_MAX); + len += input_print_modalias_bits(buf + len, size - len, + 's', id->sndbit, 0, SND_MAX); + len += input_print_modalias_bits(buf + len, size - len, + 'f', id->ffbit, 0, FF_MAX); + len += input_print_modalias_bits(buf + len, size - len, + 'w', id->swbit, 0, SW_MAX); + + if (add_cr) + len += snprintf(buf + len, max(size - len, 0), "\n"); + + return len; +} + +static ssize_t input_dev_show_modalias(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct input_dev *id = to_input_dev(dev); + ssize_t len; + + len = input_print_modalias(buf, PAGE_SIZE, id, 1); + + return min_t(int, len, PAGE_SIZE); +} +static DEVICE_ATTR(modalias, S_IRUGO, input_dev_show_modalias, NULL); + +static struct attribute *input_dev_attrs[] = { + &dev_attr_name.attr, + &dev_attr_phys.attr, + &dev_attr_uniq.attr, + &dev_attr_modalias.attr, + NULL +}; + +static struct attribute_group input_dev_attr_group = { + .attrs = input_dev_attrs, +}; + +#define INPUT_DEV_ID_ATTR(name) \ +static ssize_t input_dev_show_id_##name(struct device *dev, \ + struct device_attribute *attr, \ + char *buf) \ +{ \ + struct input_dev *input_dev = to_input_dev(dev); \ + return scnprintf(buf, PAGE_SIZE, "%04x\n", input_dev->id.name); \ +} \ +static DEVICE_ATTR(name, S_IRUGO, input_dev_show_id_##name, NULL) + +INPUT_DEV_ID_ATTR(bustype); +INPUT_DEV_ID_ATTR(vendor); +INPUT_DEV_ID_ATTR(product); +INPUT_DEV_ID_ATTR(version); + +static struct attribute *input_dev_id_attrs[] = { + &dev_attr_bustype.attr, + &dev_attr_vendor.attr, + &dev_attr_product.attr, + &dev_attr_version.attr, + NULL +}; + +static struct attribute_group input_dev_id_attr_group = { + .name = "id", + .attrs = input_dev_id_attrs, +}; + +static int input_print_bitmap(char *buf, int buf_size, unsigned long *bitmap, + int max, int add_cr) +{ + int i; + int len = 0; + + for (i = BITS_TO_LONGS(max) - 1; i > 0; i--) + if (bitmap[i]) + break; + + for (; i >= 0; i--) + len += snprintf(buf + len, max(buf_size - len, 0), + "%lx%s", bitmap[i], i > 0 ? " " : ""); + + if (add_cr) + len += snprintf(buf + len, max(buf_size - len, 0), "\n"); + + return len; +} + +#define INPUT_DEV_CAP_ATTR(ev, bm) \ +static ssize_t input_dev_show_cap_##bm(struct device *dev, \ + struct device_attribute *attr, \ + char *buf) \ +{ \ + struct input_dev *input_dev = to_input_dev(dev); \ + int len = input_print_bitmap(buf, PAGE_SIZE, \ + input_dev->bm##bit, ev##_MAX, 1); \ + return min_t(int, len, PAGE_SIZE); \ +} \ +static DEVICE_ATTR(bm, S_IRUGO, input_dev_show_cap_##bm, NULL) + +INPUT_DEV_CAP_ATTR(EV, ev); +INPUT_DEV_CAP_ATTR(KEY, key); +INPUT_DEV_CAP_ATTR(REL, rel); +INPUT_DEV_CAP_ATTR(ABS, abs); +INPUT_DEV_CAP_ATTR(MSC, msc); +INPUT_DEV_CAP_ATTR(LED, led); +INPUT_DEV_CAP_ATTR(SND, snd); +INPUT_DEV_CAP_ATTR(FF, ff); +INPUT_DEV_CAP_ATTR(SW, sw); + +static struct attribute *input_dev_caps_attrs[] = { + &dev_attr_ev.attr, + &dev_attr_key.attr, + &dev_attr_rel.attr, + &dev_attr_abs.attr, + &dev_attr_msc.attr, + &dev_attr_led.attr, + &dev_attr_snd.attr, + &dev_attr_ff.attr, + &dev_attr_sw.attr, + NULL +}; + +static struct attribute_group input_dev_caps_attr_group = { + .name = "capabilities", + .attrs = input_dev_caps_attrs, +}; + +static struct attribute_group *input_dev_attr_groups[] = { + &input_dev_attr_group, + &input_dev_id_attr_group, + &input_dev_caps_attr_group, + NULL +}; + +static void input_dev_release(struct device *device) +{ + struct input_dev *dev = to_input_dev(device); + + input_ff_destroy(dev); + kfree(dev); + + module_put(THIS_MODULE); +} + +/* + * Input uevent interface - loading event handlers based on + * device bitfields. + */ +static int input_add_uevent_bm_var(struct kobj_uevent_env *env, + const char *name, unsigned long *bitmap, int max) +{ + int len; + + if (add_uevent_var(env, "%s=", name)) + return -ENOMEM; + + len = input_print_bitmap(&env->buf[env->buflen - 1], + sizeof(env->buf) - env->buflen, + bitmap, max, 0); + if (len >= (sizeof(env->buf) - env->buflen)) + return -ENOMEM; + + env->buflen += len; + return 0; +} + +static int input_add_uevent_modalias_var(struct kobj_uevent_env *env, + struct input_dev *dev) +{ + int len; + + if (add_uevent_var(env, "MODALIAS=")) + return -ENOMEM; + + len = input_print_modalias(&env->buf[env->buflen - 1], + sizeof(env->buf) - env->buflen, + dev, 0); + if (len >= (sizeof(env->buf) - env->buflen)) + return -ENOMEM; + + env->buflen += len; + return 0; +} + +#define INPUT_ADD_HOTPLUG_VAR(fmt, val...) \ + do { \ + int err = add_uevent_var(env, fmt, val); \ + if (err) \ + return err; \ + } while (0) + +#define INPUT_ADD_HOTPLUG_BM_VAR(name, bm, max) \ + do { \ + int err = input_add_uevent_bm_var(env, name, bm, max); \ + if (err) \ + return err; \ + } while (0) + +#define INPUT_ADD_HOTPLUG_MODALIAS_VAR(dev) \ + do { \ + int err = input_add_uevent_modalias_var(env, dev); \ + if (err) \ + return err; \ + } while (0) + +static int input_dev_uevent(struct device *device, struct kobj_uevent_env *env) +{ + struct input_dev *dev = to_input_dev(device); + + INPUT_ADD_HOTPLUG_VAR("PRODUCT=%x/%x/%x/%x", + dev->id.bustype, dev->id.vendor, + dev->id.product, dev->id.version); + if (dev->name) + INPUT_ADD_HOTPLUG_VAR("NAME=\"%s\"", dev->name); + if (dev->phys) + INPUT_ADD_HOTPLUG_VAR("PHYS=\"%s\"", dev->phys); + if (dev->uniq) + INPUT_ADD_HOTPLUG_VAR("UNIQ=\"%s\"", dev->uniq); + + INPUT_ADD_HOTPLUG_BM_VAR("EV=", dev->evbit, EV_MAX); + if (test_bit(EV_KEY, dev->evbit)) + INPUT_ADD_HOTPLUG_BM_VAR("KEY=", dev->keybit, KEY_MAX); + if (test_bit(EV_REL, dev->evbit)) + INPUT_ADD_HOTPLUG_BM_VAR("REL=", dev->relbit, REL_MAX); + if (test_bit(EV_ABS, dev->evbit)) + INPUT_ADD_HOTPLUG_BM_VAR("ABS=", dev->absbit, ABS_MAX); + if (test_bit(EV_MSC, dev->evbit)) + INPUT_ADD_HOTPLUG_BM_VAR("MSC=", dev->mscbit, MSC_MAX); + if (test_bit(EV_LED, dev->evbit)) + INPUT_ADD_HOTPLUG_BM_VAR("LED=", dev->ledbit, LED_MAX); + if (test_bit(EV_SND, dev->evbit)) + INPUT_ADD_HOTPLUG_BM_VAR("SND=", dev->sndbit, SND_MAX); + if (test_bit(EV_FF, dev->evbit)) + INPUT_ADD_HOTPLUG_BM_VAR("FF=", dev->ffbit, FF_MAX); + if (test_bit(EV_SW, dev->evbit)) + INPUT_ADD_HOTPLUG_BM_VAR("SW=", dev->swbit, SW_MAX); + + INPUT_ADD_HOTPLUG_MODALIAS_VAR(dev); + + return 0; +} + +static struct device_type input_dev_type = { + .groups = input_dev_attr_groups, + .release = input_dev_release, + .uevent = input_dev_uevent, +}; + +struct class input_class = { + .name = "input", +}; +EXPORT_SYMBOL_GPL(input_class); + +/** + * input_allocate_device - allocate memory for new input device + * + * Returns prepared struct input_dev or NULL. + * + * NOTE: Use input_free_device() to free devices that have not been + * registered; input_unregister_device() should be used for already + * registered devices. + */ +struct input_dev *input_allocate_device(void) +{ + struct input_dev *dev; + + dev = kzalloc(sizeof(struct input_dev), GFP_KERNEL); + if (dev) { + dev->dev.type = &input_dev_type; + dev->dev.class = &input_class; + device_initialize(&dev->dev); + mutex_init(&dev->mutex); + spin_lock_init(&dev->event_lock); + INIT_LIST_HEAD(&dev->h_list); + INIT_LIST_HEAD(&dev->node); + + __module_get(THIS_MODULE); + } + + return dev; +} +EXPORT_SYMBOL(input_allocate_device); + +/** + * input_free_device - free memory occupied by input_dev structure + * @dev: input device to free + * + * This function should only be used if input_register_device() + * was not called yet or if it failed. Once device was registered + * use input_unregister_device() and memory will be freed once last + * reference to the device is dropped. + * + * Device should be allocated by input_allocate_device(). + * + * NOTE: If there are references to the input device then memory + * will not be freed until last reference is dropped. + */ +void input_free_device(struct input_dev *dev) +{ + if (dev) + input_put_device(dev); +} +EXPORT_SYMBOL(input_free_device); + +/** + * input_set_capability - mark device as capable of a certain event + * @dev: device that is capable of emitting or accepting event + * @type: type of the event (EV_KEY, EV_REL, etc...) + * @code: event code + * + * In addition to setting up corresponding bit in appropriate capability + * bitmap the function also adjusts dev->evbit. + */ +void input_set_capability(struct input_dev *dev, unsigned int type, unsigned int code) +{ + switch (type) { + case EV_KEY: + __set_bit(code, dev->keybit); + break; + + case EV_REL: + __set_bit(code, dev->relbit); + break; + + case EV_ABS: + __set_bit(code, dev->absbit); + break; + + case EV_MSC: + __set_bit(code, dev->mscbit); + break; + + case EV_SW: + __set_bit(code, dev->swbit); + break; + + case EV_LED: + __set_bit(code, dev->ledbit); + break; + + case EV_SND: + __set_bit(code, dev->sndbit); + break; + + case EV_FF: + __set_bit(code, dev->ffbit); + break; + + case EV_PWR: + /* do nothing */ + break; + + default: + printk(KERN_ERR + "input_set_capability: unknown type %u (code %u)\n", + type, code); + dump_stack(); + return; + } + + __set_bit(type, dev->evbit); +} +EXPORT_SYMBOL(input_set_capability); + +/** + * input_register_device - register device with input core + * @dev: device to be registered + * + * This function registers device with input core. The device must be + * allocated with input_allocate_device() and all it's capabilities + * set up before registering. + * If function fails the device must be freed with input_free_device(). + * Once device has been successfully registered it can be unregistered + * with input_unregister_device(); input_free_device() should not be + * called in this case. + */ +int input_register_device(struct input_dev *dev) +{ + static atomic_t input_no = ATOMIC_INIT(0); + struct input_handler *handler; + const char *path; + int error; + + __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; + } + + if (!dev->getkeycode) + dev->getkeycode = input_default_getkeycode; + + if (!dev->setkeycode) + dev->setkeycode = input_default_setkeycode; + + snprintf(dev->dev.bus_id, sizeof(dev->dev.bus_id), + "input%ld", (unsigned long) atomic_inc_return(&input_no) - 1); + + error = device_add(&dev->dev); + if (error) + return error; + + path = kobject_get_path(&dev->dev.kobj, GFP_KERNEL); + printk(KERN_INFO "input: %s as %s\n", + dev->name ? dev->name : "Unspecified device", path ? path : "N/A"); + kfree(path); + + error = mutex_lock_interruptible(&input_mutex); + if (error) { + device_del(&dev->dev); + return error; + } + + list_add_tail(&dev->node, &input_dev_list); + + list_for_each_entry(handler, &input_handler_list, node) + input_attach_handler(dev, handler); + + input_wakeup_procfs_readers(); + + mutex_unlock(&input_mutex); + + return 0; +} +EXPORT_SYMBOL(input_register_device); + +/** + * input_unregister_device - unregister previously registered device + * @dev: device to be unregistered + * + * This function unregisters an input device. Once device is unregistered + * the caller should not try to access it as it may get freed at any moment. + */ +void input_unregister_device(struct input_dev *dev) +{ + struct input_handle *handle, *next; + + input_disconnect_device(dev); + + mutex_lock(&input_mutex); + + list_for_each_entry_safe(handle, next, &dev->h_list, d_node) + handle->handler->disconnect(handle); + WARN_ON(!list_empty(&dev->h_list)); + + del_timer_sync(&dev->timer); + list_del_init(&dev->node); + + input_wakeup_procfs_readers(); + + mutex_unlock(&input_mutex); + + device_unregister(&dev->dev); +} +EXPORT_SYMBOL(input_unregister_device); + +/** + * input_register_handler - register a new input handler + * @handler: handler to be registered + * + * This function registers a new input handler (interface) for input + * devices in the system and attaches it to all input devices that + * are compatible with the handler. + */ +int input_register_handler(struct input_handler *handler) +{ + struct input_dev *dev; + int retval; + + retval = mutex_lock_interruptible(&input_mutex); + if (retval) + return retval; + + INIT_LIST_HEAD(&handler->h_list); + + if (handler->fops != NULL) { + if (input_table[handler->minor >> 5]) { + retval = -EBUSY; + goto out; + } + input_table[handler->minor >> 5] = handler; + } + + list_add_tail(&handler->node, &input_handler_list); + + list_for_each_entry(dev, &input_dev_list, node) + input_attach_handler(dev, handler); + + input_wakeup_procfs_readers(); + + out: + mutex_unlock(&input_mutex); + return retval; +} +EXPORT_SYMBOL(input_register_handler); + +/** + * input_unregister_handler - unregisters an input handler + * @handler: handler to be unregistered + * + * This function disconnects a handler from its input devices and + * removes it from lists of known handlers. + */ +void input_unregister_handler(struct input_handler *handler) +{ + struct input_handle *handle, *next; + + mutex_lock(&input_mutex); + + list_for_each_entry_safe(handle, next, &handler->h_list, h_node) + handler->disconnect(handle); + WARN_ON(!list_empty(&handler->h_list)); + + list_del_init(&handler->node); + + if (handler->fops != NULL) + input_table[handler->minor >> 5] = NULL; + + input_wakeup_procfs_readers(); + + mutex_unlock(&input_mutex); +} +EXPORT_SYMBOL(input_unregister_handler); + +/** + * input_register_handle - register a new input handle + * @handle: handle to register + * + * This function puts a new input handle onto device's + * and handler's lists so that events can flow through + * it once it is opened using input_open_device(). + * + * This function is supposed to be called from handler's + * connect() method. + */ +int input_register_handle(struct input_handle *handle) +{ + struct input_handler *handler = handle->handler; + struct input_dev *dev = handle->dev; + int error; + + /* + * We take dev->mutex here to prevent race with + * input_release_device(). + */ + error = mutex_lock_interruptible(&dev->mutex); + if (error) + return error; + list_add_tail_rcu(&handle->d_node, &dev->h_list); + mutex_unlock(&dev->mutex); + synchronize_rcu(); + + /* + * Since we are supposed to be called from ->connect() + * which is mutually exclusive with ->disconnect() + * we can't be racing with input_unregister_handle() + * and so separate lock is not needed here. + */ + list_add_tail(&handle->h_node, &handler->h_list); + + if (handler->start) + handler->start(handle); + + return 0; +} +EXPORT_SYMBOL(input_register_handle); + +/** + * input_unregister_handle - unregister an input handle + * @handle: handle to unregister + * + * This function removes input handle from device's + * and handler's lists. + * + * This function is supposed to be called from handler's + * disconnect() method. + */ +void input_unregister_handle(struct input_handle *handle) +{ + struct input_dev *dev = handle->dev; + + list_del_init(&handle->h_node); + + /* + * Take dev->mutex to prevent race with input_release_device(). + */ + mutex_lock(&dev->mutex); + list_del_rcu(&handle->d_node); + mutex_unlock(&dev->mutex); + synchronize_rcu(); +} +EXPORT_SYMBOL(input_unregister_handle); + +static int input_open_file(struct inode *inode, struct file *file) +{ + struct input_handler *handler; + const struct file_operations *old_fops, *new_fops = NULL; + int err; + + lock_kernel(); + /* No load-on-demand here? */ + handler = input_table[iminor(inode) >> 5]; + if (!handler || !(new_fops = fops_get(handler->fops))) { + err = -ENODEV; + goto out; + } + + /* + * That's _really_ odd. Usually NULL ->open means "nothing special", + * not "no device". Oh, well... + */ + if (!new_fops->open) { + fops_put(new_fops); + err = -ENODEV; + goto out; + } + 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); +out: + unlock_kernel(); + return err; +} + +static const struct file_operations input_fops = { + .owner = THIS_MODULE, + .open = input_open_file, +}; + +static int __init input_init(void) +{ + int err; + + err = class_register(&input_class); + if (err) { + printk(KERN_ERR "input: unable to register input_dev class\n"); + return err; + } + + err = input_proc_init(); + if (err) + goto fail1; + + err = register_chrdev(INPUT_MAJOR, "input", &input_fops); + if (err) { + printk(KERN_ERR "input: unable to register char major %d", INPUT_MAJOR); + goto fail2; + } + + return 0; + + fail2: input_proc_exit(); + fail1: class_unregister(&input_class); + return err; +} + +static void __exit input_exit(void) +{ + input_proc_exit(); + unregister_chrdev(INPUT_MAJOR, "input"); + class_unregister(&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 0000000..a85b148 --- /dev/null +++ b/drivers/input/joydev.c @@ -0,0 +1,899 @@ +/* + * 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/device.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 + +struct joydev { + int exist; + int open; + int minor; + char name[16]; + struct input_handle handle; + wait_queue_head_t wait; + struct list_head client_list; + spinlock_t client_lock; /* protects client_list */ + struct mutex mutex; + struct device dev; + + 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_client { + struct js_event buffer[JOYDEV_BUFFER_SIZE]; + int head; + int tail; + int startup; + spinlock_t buffer_lock; /* protects access to buffer, head and tail */ + struct fasync_struct *fasync; + struct joydev *joydev; + struct list_head node; +}; + +static struct joydev *joydev_table[JOYDEV_MINORS]; +static DEFINE_MUTEX(joydev_table_mutex); + +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; + } + + return value < -32767 ? -32767 : (value > 32767 ? 32767 : value); +} + +static void joydev_pass_event(struct joydev_client *client, + struct js_event *event) +{ + struct joydev *joydev = client->joydev; + + /* + * IRQs already disabled, just acquire the lock + */ + spin_lock(&client->buffer_lock); + + client->buffer[client->head] = *event; + + if (client->startup == joydev->nabs + joydev->nkey) { + client->head++; + client->head &= JOYDEV_BUFFER_SIZE - 1; + if (client->tail == client->head) + client->startup = 0; + } + + spin_unlock(&client->buffer_lock); + + kill_fasync(&client->fasync, SIGIO, POLL_IN); +} + +static void joydev_event(struct input_handle *handle, + unsigned int type, unsigned int code, int value) +{ + struct joydev *joydev = handle->private; + struct joydev_client *client; + 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 = jiffies_to_msecs(jiffies); + + rcu_read_lock(); + list_for_each_entry_rcu(client, &joydev->client_list, node) + joydev_pass_event(client, &event); + rcu_read_unlock(); + + wake_up_interruptible(&joydev->wait); +} + +static int joydev_fasync(int fd, struct file *file, int on) +{ + int retval; + struct joydev_client *client = file->private_data; + + retval = fasync_helper(fd, file, on, &client->fasync); + + return retval < 0 ? retval : 0; +} + +static void joydev_free(struct device *dev) +{ + struct joydev *joydev = container_of(dev, struct joydev, dev); + + input_put_device(joydev->handle.dev); + kfree(joydev); +} + +static void joydev_attach_client(struct joydev *joydev, + struct joydev_client *client) +{ + spin_lock(&joydev->client_lock); + list_add_tail_rcu(&client->node, &joydev->client_list); + spin_unlock(&joydev->client_lock); + synchronize_rcu(); +} + +static void joydev_detach_client(struct joydev *joydev, + struct joydev_client *client) +{ + spin_lock(&joydev->client_lock); + list_del_rcu(&client->node); + spin_unlock(&joydev->client_lock); + synchronize_rcu(); +} + +static int joydev_open_device(struct joydev *joydev) +{ + int retval; + + retval = mutex_lock_interruptible(&joydev->mutex); + if (retval) + return retval; + + if (!joydev->exist) + retval = -ENODEV; + else if (!joydev->open++) { + retval = input_open_device(&joydev->handle); + if (retval) + joydev->open--; + } + + mutex_unlock(&joydev->mutex); + return retval; +} + +static void joydev_close_device(struct joydev *joydev) +{ + mutex_lock(&joydev->mutex); + + if (joydev->exist && !--joydev->open) + input_close_device(&joydev->handle); + + mutex_unlock(&joydev->mutex); +} + +/* + * Wake up users waiting for IO so they can disconnect from + * dead device. + */ +static void joydev_hangup(struct joydev *joydev) +{ + struct joydev_client *client; + + spin_lock(&joydev->client_lock); + list_for_each_entry(client, &joydev->client_list, node) + kill_fasync(&client->fasync, SIGIO, POLL_HUP); + spin_unlock(&joydev->client_lock); + + wake_up_interruptible(&joydev->wait); +} + +static int joydev_release(struct inode *inode, struct file *file) +{ + struct joydev_client *client = file->private_data; + struct joydev *joydev = client->joydev; + + joydev_detach_client(joydev, client); + kfree(client); + + joydev_close_device(joydev); + put_device(&joydev->dev); + + return 0; +} + +static int joydev_open(struct inode *inode, struct file *file) +{ + struct joydev_client *client; + struct joydev *joydev; + int i = iminor(inode) - JOYDEV_MINOR_BASE; + int error; + + if (i >= JOYDEV_MINORS) + return -ENODEV; + + error = mutex_lock_interruptible(&joydev_table_mutex); + if (error) + return error; + joydev = joydev_table[i]; + if (joydev) + get_device(&joydev->dev); + mutex_unlock(&joydev_table_mutex); + + if (!joydev) + return -ENODEV; + + client = kzalloc(sizeof(struct joydev_client), GFP_KERNEL); + if (!client) { + error = -ENOMEM; + goto err_put_joydev; + } + + spin_lock_init(&client->buffer_lock); + client->joydev = joydev; + joydev_attach_client(joydev, client); + + error = joydev_open_device(joydev); + if (error) + goto err_free_client; + + file->private_data = client; + return 0; + + err_free_client: + joydev_detach_client(joydev, client); + kfree(client); + err_put_joydev: + put_device(&joydev->dev); + return error; +} + +static int joydev_generate_startup_event(struct joydev_client *client, + struct input_dev *input, + struct js_event *event) +{ + struct joydev *joydev = client->joydev; + int have_event; + + spin_lock_irq(&client->buffer_lock); + + have_event = client->startup < joydev->nabs + joydev->nkey; + + if (have_event) { + + event->time = jiffies_to_msecs(jiffies); + if (client->startup < joydev->nkey) { + event->type = JS_EVENT_BUTTON | JS_EVENT_INIT; + event->number = client->startup; + event->value = !!test_bit(joydev->keypam[event->number], + input->key); + } else { + event->type = JS_EVENT_AXIS | JS_EVENT_INIT; + event->number = client->startup - joydev->nkey; + event->value = joydev->abs[event->number]; + } + client->startup++; + } + + spin_unlock_irq(&client->buffer_lock); + + return have_event; +} + +static int joydev_fetch_next_event(struct joydev_client *client, + struct js_event *event) +{ + int have_event; + + spin_lock_irq(&client->buffer_lock); + + have_event = client->head != client->tail; + if (have_event) { + *event = client->buffer[client->tail++]; + client->tail &= JOYDEV_BUFFER_SIZE - 1; + } + + spin_unlock_irq(&client->buffer_lock); + + return have_event; +} + +/* + * Old joystick interface + */ +static ssize_t joydev_0x_read(struct joydev_client *client, + struct input_dev *input, + char __user *buf) +{ + struct joydev *joydev = client->joydev; + struct JS_DATA_TYPE data; + int i; + + spin_lock_irq(&input->event_lock); + + /* + * Get device state + */ + 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; + + /* + * Reset reader's event queue + */ + spin_lock(&client->buffer_lock); + client->startup = 0; + client->tail = client->head; + spin_unlock(&client->buffer_lock); + + spin_unlock_irq(&input->event_lock); + + if (copy_to_user(buf, &data, sizeof(struct JS_DATA_TYPE))) + return -EFAULT; + + return sizeof(struct JS_DATA_TYPE); +} + +static inline int joydev_data_pending(struct joydev_client *client) +{ + struct joydev *joydev = client->joydev; + + return client->startup < joydev->nabs + joydev->nkey || + client->head != client->tail; +} + +static ssize_t joydev_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + struct joydev_client *client = file->private_data; + struct joydev *joydev = client->joydev; + struct input_dev *input = joydev->handle.dev; + struct js_event event; + int retval; + + if (!joydev->exist) + return -ENODEV; + + if (count < sizeof(struct js_event)) + return -EINVAL; + + if (count == sizeof(struct JS_DATA_TYPE)) + return joydev_0x_read(client, input, buf); + + if (!joydev_data_pending(client) && (file->f_flags & O_NONBLOCK)) + return -EAGAIN; + + retval = wait_event_interruptible(joydev->wait, + !joydev->exist || joydev_data_pending(client)); + if (retval) + return retval; + + if (!joydev->exist) + return -ENODEV; + + while (retval + sizeof(struct js_event) <= count && + joydev_generate_startup_event(client, input, &event)) { + + if (copy_to_user(buf + retval, &event, sizeof(struct js_event))) + return -EFAULT; + + retval += sizeof(struct js_event); + } + + while (retval + sizeof(struct js_event) <= count && + joydev_fetch_next_event(client, &event)) { + + if (copy_to_user(buf + retval, &event, sizeof(struct js_event))) + return -EFAULT; + + retval += sizeof(struct js_event); + } + + return retval; +} + +/* No kernel lock - fine */ +static unsigned int joydev_poll(struct file *file, poll_table *wait) +{ + struct joydev_client *client = file->private_data; + struct joydev *joydev = client->joydev; + + poll_wait(file, &joydev->wait, wait); + return (joydev_data_pending(client) ? (POLLIN | POLLRDNORM) : 0) | + (joydev->exist ? 0 : (POLLHUP | POLLERR)); +} + +static int joydev_ioctl_common(struct joydev *joydev, + unsigned int cmd, void __user *argp) +{ + struct input_dev *dev = joydev->handle.dev; + int i, j; + + switch (cmd) { + + case JS_SET_CAL: + return copy_from_user(&joydev->glue.JS_CORR, argp, + sizeof(joydev->glue.JS_CORR)) ? -EFAULT : 0; + + case JS_GET_CAL: + return copy_to_user(argp, &joydev->glue.JS_CORR, + sizeof(joydev->glue.JS_CORR)) ? -EFAULT : 0; + + case JS_SET_TIMEOUT: + return get_user(joydev->glue.JS_TIMEOUT, (s32 __user *) argp); + + case JS_GET_TIMEOUT: + return put_user(joydev->glue.JS_TIMEOUT, (s32 __user *) argp); + + case JSIOCGVERSION: + return put_user(JS_VERSION, (__u32 __user *) argp); + + case JSIOCGAXES: + return put_user(joydev->nabs, (__u8 __user *) argp); + + case JSIOCGBUTTONS: + return put_user(joydev->nkey, (__u8 __user *) argp); + + case JSIOCSCORR: + if (copy_from_user(joydev->corr, argp, + sizeof(joydev->corr[0]) * 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(joydev->corr[0]) * 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 & ~IOCSIZE_MASK) == 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; +} + +#ifdef CONFIG_COMPAT +static long joydev_compat_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct joydev_client *client = file->private_data; + struct joydev *joydev = client->joydev; + void __user *argp = (void __user *)arg; + s32 tmp32; + struct JS_DATA_SAVE_TYPE_32 ds32; + int retval; + + retval = mutex_lock_interruptible(&joydev->mutex); + if (retval) + return retval; + + if (!joydev->exist) { + retval = -ENODEV; + goto out; + } + + switch (cmd) { + + case JS_SET_TIMELIMIT: + retval = get_user(tmp32, (s32 __user *) arg); + if (retval == 0) + joydev->glue.JS_TIMELIMIT = tmp32; + break; + + case JS_GET_TIMELIMIT: + tmp32 = joydev->glue.JS_TIMELIMIT; + retval = put_user(tmp32, (s32 __user *) arg); + break; + + case JS_SET_ALL: + retval = copy_from_user(&ds32, argp, + sizeof(ds32)) ? -EFAULT : 0; + if (retval == 0) { + joydev->glue.JS_TIMEOUT = ds32.JS_TIMEOUT; + joydev->glue.BUSY = ds32.BUSY; + joydev->glue.JS_EXPIRETIME = ds32.JS_EXPIRETIME; + joydev->glue.JS_TIMELIMIT = ds32.JS_TIMELIMIT; + joydev->glue.JS_SAVE = ds32.JS_SAVE; + joydev->glue.JS_CORR = ds32.JS_CORR; + } + break; + + case JS_GET_ALL: + ds32.JS_TIMEOUT = joydev->glue.JS_TIMEOUT; + ds32.BUSY = joydev->glue.BUSY; + ds32.JS_EXPIRETIME = joydev->glue.JS_EXPIRETIME; + ds32.JS_TIMELIMIT = joydev->glue.JS_TIMELIMIT; + ds32.JS_SAVE = joydev->glue.JS_SAVE; + ds32.JS_CORR = joydev->glue.JS_CORR; + + retval = copy_to_user(argp, &ds32, sizeof(ds32)) ? -EFAULT : 0; + break; + + default: + retval = joydev_ioctl_common(joydev, cmd, argp); + break; + } + + out: + mutex_unlock(&joydev->mutex); + return retval; +} +#endif /* CONFIG_COMPAT */ + +static long joydev_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct joydev_client *client = file->private_data; + struct joydev *joydev = client->joydev; + void __user *argp = (void __user *)arg; + int retval; + + retval = mutex_lock_interruptible(&joydev->mutex); + if (retval) + return retval; + + if (!joydev->exist) { + retval = -ENODEV; + goto out; + } + + switch (cmd) { + + case JS_SET_TIMELIMIT: + retval = get_user(joydev->glue.JS_TIMELIMIT, + (long __user *) arg); + break; + + case JS_GET_TIMELIMIT: + retval = put_user(joydev->glue.JS_TIMELIMIT, + (long __user *) arg); + break; + + case JS_SET_ALL: + retval = copy_from_user(&joydev->glue, argp, + sizeof(joydev->glue)) ? -EFAULT: 0; + break; + + case JS_GET_ALL: + retval = copy_to_user(argp, &joydev->glue, + sizeof(joydev->glue)) ? -EFAULT : 0; + break; + + default: + retval = joydev_ioctl_common(joydev, cmd, argp); + break; + } + out: + mutex_unlock(&joydev->mutex); + return retval; +} + +static const struct file_operations joydev_fops = { + .owner = THIS_MODULE, + .read = joydev_read, + .poll = joydev_poll, + .open = joydev_open, + .release = joydev_release, + .unlocked_ioctl = joydev_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = joydev_compat_ioctl, +#endif + .fasync = joydev_fasync, +}; + +static int joydev_install_chrdev(struct joydev *joydev) +{ + joydev_table[joydev->minor] = joydev; + return 0; +} + +static void joydev_remove_chrdev(struct joydev *joydev) +{ + mutex_lock(&joydev_table_mutex); + joydev_table[joydev->minor] = NULL; + mutex_unlock(&joydev_table_mutex); +} + +/* + * Mark device non-existant. This disables writes, ioctls and + * prevents new users from opening the device. Already posted + * blocking reads will stay, however new ones will fail. + */ +static void joydev_mark_dead(struct joydev *joydev) +{ + mutex_lock(&joydev->mutex); + joydev->exist = 0; + mutex_unlock(&joydev->mutex); +} + +static void joydev_cleanup(struct joydev *joydev) +{ + struct input_handle *handle = &joydev->handle; + + joydev_mark_dead(joydev); + joydev_hangup(joydev); + joydev_remove_chrdev(joydev); + + /* joydev is marked dead so noone else accesses joydev->open */ + if (joydev->open) + input_close_device(handle); +} + +static int joydev_connect(struct input_handler *handler, struct input_dev *dev, + const struct input_device_id *id) +{ + struct joydev *joydev; + int i, j, t, minor; + int error; + + for (minor = 0; minor < JOYDEV_MINORS; minor++) + if (!joydev_table[minor]) + break; + + if (minor == JOYDEV_MINORS) { + printk(KERN_ERR "joydev: no more free joydev devices\n"); + return -ENFILE; + } + + joydev = kzalloc(sizeof(struct joydev), GFP_KERNEL); + if (!joydev) + return -ENOMEM; + + INIT_LIST_HEAD(&joydev->client_list); + spin_lock_init(&joydev->client_lock); + mutex_init(&joydev->mutex); + init_waitqueue_head(&joydev->wait); + + snprintf(joydev->name, sizeof(joydev->name), "js%d", minor); + joydev->exist = 1; + joydev->minor = minor; + + joydev->exist = 1; + joydev->handle.dev = input_get_device(dev); + joydev->handle.name = joydev->name; + joydev->handle.handler = handler; + joydev->handle.private = joydev; + + 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; 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]; + + t = (dev->absmax[j] - dev->absmin[j]) / 2 - 2 * dev->absflat[j]; + if (t) { + 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); + } + } + + strlcpy(joydev->dev.bus_id, joydev->name, sizeof(joydev->dev.bus_id)); + joydev->dev.devt = MKDEV(INPUT_MAJOR, JOYDEV_MINOR_BASE + minor); + joydev->dev.class = &input_class; + joydev->dev.parent = &dev->dev; + joydev->dev.release = joydev_free; + device_initialize(&joydev->dev); + + error = input_register_handle(&joydev->handle); + if (error) + goto err_free_joydev; + + error = joydev_install_chrdev(joydev); + if (error) + goto err_unregister_handle; + + error = device_add(&joydev->dev); + if (error) + goto err_cleanup_joydev; + + return 0; + + err_cleanup_joydev: + joydev_cleanup(joydev); + err_unregister_handle: + input_unregister_handle(&joydev->handle); + err_free_joydev: + put_device(&joydev->dev); + return error; +} + +static void joydev_disconnect(struct input_handle *handle) +{ + struct joydev *joydev = handle->private; + + device_del(&joydev->dev); + joydev_cleanup(joydev); + input_unregister_handle(handle); + put_device(&joydev->dev); +} + +static const struct input_device_id joydev_blacklist[] = { + { + .flags = INPUT_DEVICE_ID_MATCH_EVBIT | + INPUT_DEVICE_ID_MATCH_KEYBIT, + .evbit = { BIT_MASK(EV_KEY) }, + .keybit = { [BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH) }, + }, /* Avoid itouchpads, touchscreens and tablets */ + { } /* Terminating entry */ +}; + +static const struct input_device_id joydev_ids[] = { + { + .flags = INPUT_DEVICE_ID_MATCH_EVBIT | + INPUT_DEVICE_ID_MATCH_ABSBIT, + .evbit = { BIT_MASK(EV_ABS) }, + .absbit = { BIT_MASK(ABS_X) }, + }, + { + .flags = INPUT_DEVICE_ID_MATCH_EVBIT | + INPUT_DEVICE_ID_MATCH_ABSBIT, + .evbit = { BIT_MASK(EV_ABS) }, + .absbit = { BIT_MASK(ABS_WHEEL) }, + }, + { + .flags = INPUT_DEVICE_ID_MATCH_EVBIT | + INPUT_DEVICE_ID_MATCH_ABSBIT, + .evbit = { BIT_MASK(EV_ABS) }, + .absbit = { BIT_MASK(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) +{ + return input_register_handler(&joydev_handler); +} + +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 0000000..be5c14a --- /dev/null +++ b/drivers/input/joystick/Kconfig @@ -0,0 +1,297 @@ +# +# Joystick driver configuration +# +menuconfig INPUT_JOYSTICK + bool "Joysticks/Gamepads" + 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 "Assassin 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_ZHENHUA + tristate "5-byte Zhenhua RC transmitter" + select SERIO + help + Say Y here if you have a Zhen Hua PPM-4CH transmitter which is + supplied with a ready to fly micro electric indoor helicopters + such as EasyCopter, Lama, MiniCopter, DragonFly or Jabo and want + to use it via serial cable as a joystick. + + To compile this driver as a module, choose M here: the + module will be called zhenhua. + +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. + +config JOYSTICK_XPAD + tristate "X-Box gamepad support" + depends on USB_ARCH_HAS_HCD + select USB + help + Say Y here if you want to use the X-Box pad with your computer. + Make sure to say Y to "Joystick support" (CONFIG_INPUT_JOYDEV) + and/or "Event interface support" (CONFIG_INPUT_EVDEV) as well. + + For information about how to connect the X-Box pad to USB, see + <file:Documentation/input/xpad.txt>. + + To compile this driver as a module, choose M here: the + module will be called xpad. + +config JOYSTICK_XPAD_FF + bool "X-Box gamepad rumble support" + depends on JOYSTICK_XPAD && INPUT + select INPUT_FF_MEMLESS + ---help--- + Say Y here if you want to take advantage of xbox 360 rumble features. + +config JOYSTICK_XPAD_LEDS + bool "LED Support for Xbox360 controller 'BigX' LED" + depends on JOYSTICK_XPAD && (LEDS_CLASS=y || LEDS_CLASS=JOYSTICK_XPAD) + ---help--- + This option enables support for the LED which surrounds the Big X on + XBox 360 controller. + +endif diff --git a/drivers/input/joystick/Makefile b/drivers/input/joystick/Makefile new file mode 100644 index 0000000..fdbf8c4 --- /dev/null +++ b/drivers/input/joystick/Makefile @@ -0,0 +1,32 @@ +# +# 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_IFORCE) += iforce/ +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_XPAD) += xpad.o +obj-$(CONFIG_JOYSTICK_ZHENHUA) += zhenhua.o + diff --git a/drivers/input/joystick/a3d.c b/drivers/input/joystick/a3d.c new file mode 100644 index 0000000..6489f40 --- /dev/null +++ b/drivers/input/joystick/a3d.c @@ -0,0 +1,426 @@ +/* + * 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> +#include <linux/jiffies.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 = input_get_drvdata(dev); + + 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 = input_get_drvdata(dev); + + 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 input_dev *input_dev; + struct gameport *adc; + unsigned char data[A3D_MAX_LENGTH]; + int i; + int err; + + a3d = kzalloc(sizeof(struct a3d), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!a3d || !input_dev) { + err = -ENOMEM; + goto fail1; + } + + a3d->dev = input_dev; + 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); + + snprintf(a3d->phys, sizeof(a3d->phys), "%s/input0", gameport->phys); + + input_dev->name = a3d_names[a3d->mode]; + input_dev->phys = a3d->phys; + input_dev->id.bustype = BUS_GAMEPORT; + input_dev->id.vendor = GAMEPORT_ID_VENDOR_MADCATZ; + input_dev->id.product = a3d->mode; + input_dev->id.version = 0x0100; + input_dev->dev.parent = &gameport->dev; + input_dev->open = a3d_open; + input_dev->close = a3d_close; + + input_set_drvdata(input_dev, a3d); + + if (a3d->mode == A3D_MODE_PXL) { + + int axes[] = { ABS_X, ABS_Y, ABS_THROTTLE, ABS_RUDDER }; + + a3d->length = 33; + + input_dev->evbit[0] |= BIT_MASK(EV_ABS) | BIT_MASK(EV_KEY) | + BIT_MASK(EV_REL); + input_dev->relbit[0] |= BIT_MASK(REL_X) | BIT_MASK(REL_Y); + input_dev->absbit[0] |= BIT_MASK(ABS_X) | BIT_MASK(ABS_Y) | + BIT_MASK(ABS_THROTTLE) | BIT_MASK(ABS_RUDDER) | + BIT_MASK(ABS_HAT0X) | BIT_MASK(ABS_HAT0Y) | + BIT_MASK(ABS_HAT1X) | BIT_MASK(ABS_HAT1Y); + input_dev->keybit[BIT_WORD(BTN_MOUSE)] |= BIT_MASK(BTN_RIGHT) | + BIT_MASK(BTN_LEFT) | BIT_MASK(BTN_MIDDLE) | + BIT_MASK(BTN_SIDE) | BIT_MASK(BTN_EXTRA); + input_dev->keybit[BIT_WORD(BTN_JOYSTICK)] |= + BIT_MASK(BTN_TRIGGER) | BIT_MASK(BTN_THUMB) | + BIT_MASK(BTN_TOP) | BIT_MASK(BTN_PINKIE); + + a3d_read(a3d, data); + + for (i = 0; i < 4; i++) { + if (i < 2) + input_set_abs_params(input_dev, axes[i], 48, input_dev->abs[axes[i]] * 2 - 48, 0, 8); + else + input_set_abs_params(input_dev, axes[i], 2, 253, 0, 0); + input_set_abs_params(input_dev, ABS_HAT0X + i, -1, 1, 0, 0); + } + + } else { + a3d->length = 29; + + input_dev->evbit[0] |= BIT_MASK(EV_KEY) | BIT_MASK(EV_REL); + input_dev->relbit[0] |= BIT_MASK(REL_X) | BIT_MASK(REL_Y); + input_dev->keybit[BIT_WORD(BTN_MOUSE)] |= BIT_MASK(BTN_RIGHT) | + BIT_MASK(BTN_LEFT) | BIT_MASK(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); + } + } + + err = input_register_device(a3d->dev); + if (err) + goto fail3; + + return 0; + + fail3: if (a3d->adc) + gameport_unregister_port(a3d->adc); + fail2: gameport_close(gameport); + fail1: gameport_set_drvdata(gameport, NULL); + input_free_device(input_dev); + 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); + gameport_close(gameport); + gameport_set_drvdata(gameport, NULL); + kfree(a3d); +} + +static struct gameport_driver a3d_drv = { + .driver = { + .name = "adc", + .owner = THIS_MODULE, + }, + .description = DRIVER_DESC, + .connect = a3d_connect, + .disconnect = a3d_disconnect, +}; + +static int __init a3d_init(void) +{ + return gameport_register_driver(&a3d_drv); +} + +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 0000000..89c4c08 --- /dev/null +++ b/drivers/input/joystick/adi.c @@ -0,0 +1,584 @@ +/* + * 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> +#include <linux/jiffies.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 64 +#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 = input_get_drvdata(dev); + + 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 = input_get_drvdata(dev); + + 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 int adi_init_input(struct adi *adi, struct adi_port *port, int half) +{ + struct input_dev *input_dev; + char buf[ADI_MAX_NAME_LENGTH]; + int i, t; + + adi->dev = input_dev = input_allocate_device(); + if (!input_dev) + return -ENOMEM; + + 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 [%s]", buf, adi->cname); + snprintf(adi->phys, ADI_MAX_PHYS_LENGTH, "%s/input%d", port->gameport->phys, half); + + adi->abs = adi_abs[t]; + adi->key = adi_key[t]; + + input_dev->name = adi->name; + input_dev->phys = adi->phys; + input_dev->id.bustype = BUS_GAMEPORT; + input_dev->id.vendor = GAMEPORT_ID_VENDOR_LOGITECH; + input_dev->id.product = adi->id; + input_dev->id.version = 0x0100; + input_dev->dev.parent = &port->gameport->dev; + + input_set_drvdata(input_dev, port); + + input_dev->open = adi_open; + input_dev->close = adi_close; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + + for (i = 0; i < adi->axes10 + adi->axes8 + (adi->hats + (adi->pad != -1)) * 2; i++) + set_bit(adi->abs[i], input_dev->absbit); + + for (i = 0; i < adi->buttons; i++) + set_bit(adi->key[i], input_dev->keybit); + + return 0; +} + +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; + + port = kzalloc(sizeof(struct adi_port), GFP_KERNEL); + if (!port) + return -ENOMEM; + + port->gameport = gameport; + + gameport_set_drvdata(gameport, port); + + err = gameport_open(gameport, drv, GAMEPORT_MODE_RAW); + if (err) + goto fail1; + + 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); + + if (!port->adi[i].length) + continue; + + err = adi_init_input(port->adi + i, port, i); + if (err) + goto fail2; + } + + if (!port->adi[0].length && !port->adi[1].length) { + err = -ENODEV; + goto fail2; + } + + 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); + err = input_register_device(port->adi[i].dev); + if (err) + goto fail3; + } + + return 0; + + fail3: while (--i >= 0) { + if (port->adi[i].length > 0) { + input_unregister_device(port->adi[i].dev); + port->adi[i].dev = NULL; + } + } + fail2: for (i = 0; i < 2; i++) + if (port->adi[i].dev) + input_free_device(port->adi[i].dev); + gameport_close(gameport); + fail1: gameport_set_drvdata(gameport, NULL); + kfree(port); + return err; +} + +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) +{ + return gameport_register_driver(&adi_drv); +} + +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 0000000..05022f0 --- /dev/null +++ b/drivers/input/joystick/amijoy.c @@ -0,0 +1,174 @@ +/* + * 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/init.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/mutex.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)"); + +static int amijoy_used; +static DEFINE_MUTEX(amijoy_mutex); +static struct input_dev *amijoy_dev[2]; +static char *amijoy_phys[2] = { "amijoy/input0", "amijoy/input1" }; + +static irqreturn_t amijoy_interrupt(int irq, void *dummy) +{ + int i, data = 0, button = 0; + + for (i = 0; i < 2; i++) + if (amijoy[i]) { + + switch (i) { + case 0: data = ~amiga_custom.joy0dat; button = (~ciaa.pra >> 6) & 1; break; + case 1: data = ~amiga_custom.joy1dat; button = (~ciaa.pra >> 7) & 1; break; + } + + 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 err; + + err = mutex_lock_interruptible(&amijoy_mutex); + if (err) + return err; + + if (!amijoy_used && request_irq(IRQ_AMIGA_VERTB, amijoy_interrupt, 0, "amijoy", amijoy_interrupt)) { + printk(KERN_ERR "amijoy.c: Can't allocate irq %d\n", IRQ_AMIGA_VERTB); + err = -EBUSY; + goto out; + } + + amijoy_used++; +out: + mutex_unlock(&amijoy_mutex); + return err; +} + +static void amijoy_close(struct input_dev *dev) +{ + mutex_lock(&amijoy_mutex); + if (!--amijoy_used) + free_irq(IRQ_AMIGA_VERTB, amijoy_interrupt); + mutex_unlock(&amijoy_mutex); +} + +static int __init amijoy_init(void) +{ + int i, j; + int err; + + for (i = 0; i < 2; i++) { + if (!amijoy[i]) + continue; + + amijoy_dev[i] = input_allocate_device(); + if (!amijoy_dev[i]) { + err = -ENOMEM; + goto fail; + } + + if (!request_mem_region(CUSTOM_PHYSADDR + 10 + i * 2, 2, "amijoy [Denise]")) { + input_free_device(amijoy_dev[i]); + err = -EBUSY; + goto fail; + } + + amijoy_dev[i]->name = "Amiga joystick"; + 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]->open = amijoy_open; + amijoy_dev[i]->close = amijoy_close; + + amijoy_dev[i]->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + amijoy_dev[i]->absbit[0] = BIT_MASK(ABS_X) | BIT_MASK(ABS_Y); + amijoy_dev[i]->keybit[BIT_WORD(BTN_LEFT)] = BIT_MASK(BTN_LEFT) | + BIT_MASK(BTN_MIDDLE) | BIT_MASK(BTN_RIGHT); + for (j = 0; j < 2; j++) { + amijoy_dev[i]->absmin[ABS_X + j] = -1; + amijoy_dev[i]->absmax[ABS_X + j] = 1; + } + + err = input_register_device(amijoy_dev[i]); + if (err) { + input_free_device(amijoy_dev[i]); + goto fail; + } + } + return 0; + + fail: while (--i >= 0) + if (amijoy[i]) { + input_unregister_device(amijoy_dev[i]); + release_mem_region(CUSTOM_PHYSADDR + 10 + i * 2, 2); + } + return err; +} + +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 0000000..356b3a2 --- /dev/null +++ b/drivers/input/joystick/analog.c @@ -0,0 +1,773 @@ +/* + * 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/delay.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/bitops.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/gameport.h> +#include <linux/jiffies.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 unsigned 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"); + +/* + * 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__ + +#include <asm/i8253.h> + +#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) +{ + 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" +#elif defined(CONFIG_MN10300) +#define GET_TIME(x) do { x = get_cycles(); } while (0) +#define DELTA(x, y) ((x) - (y)) +#define TIME_NAME "TSC" +#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 = input_get_drvdata(dev); + + 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 = input_get_drvdata(dev); + + 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) +{ + snprintf(analog->name, sizeof(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) + snprintf(analog->name, sizeof(analog->name), "%s %d-hat", + analog->name, hweight16(analog->mask & ANALOG_HATS_ALL)); + + if (analog->mask & ANALOG_HAT_FCS) + strlcat(analog->name, " FCS", sizeof(analog->name)); + if (analog->mask & ANALOG_ANY_CHF) + strlcat(analog->name, (analog->mask & ANALOG_SAITEK) ? " Saitek" : " CHF", + sizeof(analog->name)); + + strlcat(analog->name, (analog->mask & ANALOG_GAMEPAD) ? " gamepad": " joystick", + sizeof(analog->name)); +} + +/* + * analog_init_device() + */ + +static int analog_init_device(struct analog_port *port, struct analog *analog, int index) +{ + struct input_dev *input_dev; + int i, j, t, v, w, x, y, z; + int error; + + analog_name(analog); + snprintf(analog->phys, sizeof(analog->phys), + "%s/input%d", port->gameport->phys, index); + analog->buttons = (analog->mask & ANALOG_GAMEPAD) ? analog_pad_btn : analog_joy_btn; + + analog->dev = input_dev = input_allocate_device(); + if (!input_dev) + return -ENOMEM; + + input_dev->name = analog->name; + input_dev->phys = analog->phys; + input_dev->id.bustype = BUS_GAMEPORT; + input_dev->id.vendor = GAMEPORT_ID_VENDOR_ANALOG; + input_dev->id.product = analog->mask >> 4; + input_dev->id.version = 0x0100; + input_dev->dev.parent = &port->gameport->dev; + + input_set_drvdata(input_dev, port); + + input_dev->open = analog_open; + input_dev->close = analog_close; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(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); + + 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); + } + + input_set_abs_params(input_dev, t, v, (x << 1) - v, port->fuzz, 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++]; + input_set_abs_params(input_dev, t, -1, 1, 0, 0); + } + + for (i = j = 0; i < 4; i++) + if (analog->mask & (0x10 << i)) + set_bit(analog->buttons[j++], input_dev->keybit); + + if (analog->mask & ANALOG_BTNS_CHF) + for (i = 0; i < 2; i++) + set_bit(analog->buttons[j++], input_dev->keybit); + + if (analog->mask & ANALOG_HBTN_CHF) + for (i = 0; i < 4; i++) + set_bit(analog->buttons[j++], input_dev->keybit); + + for (i = 0; i < 4; i++) + if (analog->mask & (ANALOG_BTN_TL << i)) + set_bit(analog_pads[i], input_dev->keybit); + + analog_decode(analog, port->axes, port->initial, port->buttons); + + error = input_register_device(analog->dev); + if (error) { + input_free_device(analog->dev); + return error; + } + + return 0; +} + +/* + * 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 = kzalloc(sizeof(struct analog_port), GFP_KERNEL))) + return - ENOMEM; + + err = analog_init_port(gameport, drv, port); + if (err) + goto fail1; + + err = analog_init_masks(port); + if (err) + goto fail2; + + gameport_set_poll_handler(gameport, analog_poll); + gameport_set_poll_interval(gameport, 10); + + for (i = 0; i < 2; i++) + if (port->analog[i].mask) { + err = analog_init_device(port, port->analog + i, i); + if (err) + goto fail3; + } + + return 0; + + fail3: while (--i >= 0) + if (port->analog[i].mask) + input_unregister_device(port->analog[i].dev); + fail2: gameport_close(gameport); + fail1: gameport_set_drvdata(gameport, NULL); + kfree(port); + return err; +} + +static void analog_disconnect(struct gameport *gameport) +{ + struct analog_port *port = gameport_get_drvdata(gameport); + int i; + + 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(); + return gameport_register_driver(&analog_drv); +} + +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 0000000..3497b87 --- /dev/null +++ b/drivers/input/joystick/cobra.c @@ -0,0 +1,275 @@ +/* + * 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> +#include <linux/jiffies.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 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 = input_get_drvdata(dev); + + gameport_start_polling(cobra->gameport); + return 0; +} + +static void cobra_close(struct input_dev *dev) +{ + struct cobra *cobra = input_get_drvdata(dev); + + gameport_stop_polling(cobra->gameport); +} + +static int cobra_connect(struct gameport *gameport, struct gameport_driver *drv) +{ + struct cobra *cobra; + struct input_dev *input_dev; + unsigned int data[2]; + int i, j; + int err; + + cobra = kzalloc(sizeof(struct cobra), GFP_KERNEL); + if (!cobra) + 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) + continue; + + cobra->dev[i] = input_dev = input_allocate_device(); + if (!input_dev) { + err = -ENOMEM; + goto fail3; + } + + snprintf(cobra->phys[i], sizeof(cobra->phys[i]), + "%s/input%d", gameport->phys, i); + + input_dev->name = "Creative Labs Blaster GamePad Cobra"; + input_dev->phys = cobra->phys[i]; + input_dev->id.bustype = BUS_GAMEPORT; + input_dev->id.vendor = GAMEPORT_ID_VENDOR_CREATIVE; + input_dev->id.product = 0x0008; + input_dev->id.version = 0x0100; + input_dev->dev.parent = &gameport->dev; + + input_set_drvdata(input_dev, cobra); + + input_dev->open = cobra_open; + input_dev->close = cobra_close; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + input_set_abs_params(input_dev, ABS_X, -1, 1, 0, 0); + input_set_abs_params(input_dev, ABS_Y, -1, 1, 0, 0); + for (j = 0; cobra_btn[j]; j++) + set_bit(cobra_btn[j], input_dev->keybit); + + err = input_register_device(cobra->dev[i]); + if (err) + goto fail4; + } + + return 0; + + fail4: input_free_device(cobra->dev[i]); + fail3: while (--i >= 0) + if (cobra->dev[i]) + input_unregister_device(cobra->dev[i]); + 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) +{ + return gameport_register_driver(&cobra_drv); +} + +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 0000000..5239594 --- /dev/null +++ b/drivers/input/joystick/db9.c @@ -0,0 +1,719 @@ +/* + * 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/delay.h> +#include <linux/init.h> +#include <linux/parport.h> +#include <linux/input.h> +#include <linux/mutex.h> + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION("Atari, Amstrad, Commodore, Amiga, Sega, etc. joystick driver"); +MODULE_LICENSE("GPL"); + +struct db9_config { + int args[2]; + unsigned int nargs; +}; + +#define DB9_MAX_PORTS 3 +static struct db9_config db9_cfg[DB9_MAX_PORTS] __initdata; + +module_param_array_named(dev, db9_cfg[0].args, int, &db9_cfg[0].nargs, 0); +MODULE_PARM_DESC(dev, "Describes first attached device (<parport#>,<type>)"); +module_param_array_named(dev2, db9_cfg[1].args, int, &db9_cfg[1].nargs, 0); +MODULE_PARM_DESC(dev2, "Describes second attached device (<parport#>,<type>)"); +module_param_array_named(dev3, db9_cfg[2].args, int, &db9_cfg[2].nargs, 0); +MODULE_PARM_DESC(dev3, "Describes third attached device (<parport#>,<type>)"); + +#define DB9_ARG_PARPORT 0 +#define DB9_ARG_MODE 1 + +#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_GENESIS6_DELAY 14 +#define DB9_REFRESH_TIME HZ/100 + +#define DB9_MAX_DEVICES 2 + +struct db9_mode_data { + const char *name; + const short *buttons; + int n_buttons; + int n_pads; + int n_axis; + int bidirectional; + int reverse; +}; + +struct db9 { + struct input_dev *dev[DB9_MAX_DEVICES]; + struct timer_list timer; + struct pardevice *pd; + int mode; + int used; + struct mutex mutex; + char phys[DB9_MAX_DEVICES][32]; +}; + +static struct db9 *db9_base[3]; + +static const short db9_multi_btn[] = { BTN_TRIGGER, BTN_THUMB }; +static const short db9_genesis_btn[] = { BTN_START, BTN_A, BTN_B, BTN_C, BTN_X, BTN_Y, BTN_Z, BTN_MODE }; +static const short db9_cd32_btn[] = { BTN_A, BTN_B, BTN_C, BTN_X, BTN_Y, BTN_Z, BTN_TL, BTN_TR, BTN_START }; +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 struct db9_mode_data db9_modes[] = { + { NULL, NULL, 0, 0, 0, 0, 0 }, + { "Multisystem joystick", db9_multi_btn, 1, 1, 2, 1, 1 }, + { "Multisystem joystick (2 fire)", db9_multi_btn, 2, 1, 2, 1, 1 }, + { "Genesis pad", db9_genesis_btn, 4, 1, 2, 1, 1 }, + { NULL, NULL, 0, 0, 0, 0, 0 }, + { "Genesis 5 pad", db9_genesis_btn, 6, 1, 2, 1, 1 }, + { "Genesis 6 pad", db9_genesis_btn, 8, 1, 2, 1, 1 }, + { "Saturn pad", db9_cd32_btn, 9, 6, 7, 0, 1 }, + { "Multisystem (0.8.0.2) joystick", db9_multi_btn, 1, 1, 2, 1, 1 }, + { "Multisystem (0.8.0.2-dual) joystick", db9_multi_btn, 1, 2, 2, 1, 1 }, + { "Amiga CD-32 pad", db9_cd32_btn, 7, 1, 2, 1, 1 }, + { "Saturn dpp", db9_cd32_btn, 9, 6, 7, 0, 0 }, + { "Saturn dpp dual", db9_cd32_btn, 9, 12, 7, 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 *devs[], int n, int max_pads) +{ + struct input_dev *dev; + int tmp, i, j; + + tmp = (id == 0x41) ? 60 : 10; + for (j = 0; j < tmp && n < max_pads; j += 10, n++) { + dev = devs[n]; + switch (data[j]) { + case 0x16: /* multi controller (analog 4 axis) */ + input_report_abs(dev, db9_abs[5], data[j + 6]); + case 0x15: /* mission stick (analog 3 axis) */ + input_report_abs(dev, db9_abs[3], data[j + 4]); + input_report_abs(dev, db9_abs[4], data[j + 5]); + case 0x13: /* racing controller (analog 1 axis) */ + input_report_abs(dev, 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, db9_abs[0], !(data[j + 1] & 128) - !(data[j + 1] & 64)); + input_report_abs(dev, db9_abs[1], !(data[j + 1] & 32) - !(data[j + 1] & 16)); + for (i = 0; i < 9; i++) + input_report_key(dev, 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, db9_abs[0], !(data[j + 1] & 128) - !(data[j + 1] & 64)); + input_report_abs(dev, db9_abs[1], !(data[j + 1] & 32) - !(data[j + 1] & 16)); + for (i = 0; i < 9; i++) + input_report_key(dev, db9_cd32_btn[i], ~data[j + db9_saturn_byte[i]] & db9_saturn_mask[i]); + input_report_abs(dev, db9_abs[2], data[j + 3]); + input_report_abs(dev, db9_abs[3], data[j + 4]); + input_report_abs(dev, db9_abs[4], data[j + 5]); + /* + input_report_abs(dev, db9_abs[8], (data[j + 6] & 128 ? 0 : 1) - (data[j + 6] & 64 ? 0 : 1)); + input_report_abs(dev, db9_abs[9], (data[j + 6] & 32 ? 0 : 1) - (data[j + 6] & 16 ? 0 : 1)); + */ + input_report_abs(dev, db9_abs[6], data[j + 7]); + input_report_abs(dev, db9_abs[7], data[j + 8]); + input_report_abs(dev, db9_abs[5], data[j + 9]); + break; + case 0xd3: /* sankyo ff (analog 1 axis + stop btn) */ + input_report_key(dev, BTN_A, data[j + 3] & 0x80); + input_report_abs(dev, db9_abs[2], data[j + 3] & 0x7f); + break; + case 0xe3: /* shuttle mouse (analog 2 axis + buttons. signed value) */ + input_report_key(dev, BTN_START, data[j + 1] & 0x08); + input_report_key(dev, BTN_A, data[j + 1] & 0x04); + input_report_key(dev, BTN_C, data[j + 1] & 0x02); + input_report_key(dev, BTN_B, data[j + 1] & 0x01); + input_report_abs(dev, db9_abs[2], data[j + 2] ^ 0x80); + input_report_abs(dev, db9_abs[3], (0xff-(data[j + 3] ^ 0x80))+1); /* */ + break; + case 0xff: + default: /* no pad */ + input_report_abs(dev, db9_abs[0], 0); + input_report_abs(dev, db9_abs[1], 0); + for (i = 0; i < 9; i++) + input_report_key(dev, db9_cd32_btn[i], 0); + break; + } + } + return n; +} + +static int db9_saturn(int mode, struct parport *port, struct input_dev *devs[]) +{ + 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_modes[mode].n_pads, 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, devs, 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[0]; + struct input_dev *dev2 = db9->dev[1]; + int data, i; + + switch (db9->mode) { + case DB9_MULTI_0802_2: + + data = parport_read_data(port) >> 3; + + input_report_abs(dev2, ABS_X, (data & DB9_RIGHT ? 0 : 1) - (data & DB9_LEFT ? 0 : 1)); + input_report_abs(dev2, ABS_Y, (data & DB9_DOWN ? 0 : 1) - (data & DB9_UP ? 0 : 1)); + input_report_key(dev2, 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, db9->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 = input_get_drvdata(dev); + struct parport *port = db9->pd->port; + int err; + + err = mutex_lock_interruptible(&db9->mutex); + if (err) + return err; + + if (!db9->used++) { + parport_claim(db9->pd); + parport_write_data(port, 0xff); + if (db9_modes[db9->mode].reverse) { + parport_data_reverse(port); + parport_write_control(port, DB9_NORMAL); + } + mod_timer(&db9->timer, jiffies + DB9_REFRESH_TIME); + } + + mutex_unlock(&db9->mutex); + return 0; +} + +static void db9_close(struct input_dev *dev) +{ + struct db9 *db9 = input_get_drvdata(dev); + struct parport *port = db9->pd->port; + + mutex_lock(&db9->mutex); + if (!--db9->used) { + del_timer_sync(&db9->timer); + parport_write_control(port, 0x00); + parport_data_forward(port); + parport_release(db9->pd); + } + mutex_unlock(&db9->mutex); +} + +static struct db9 __init *db9_probe(int parport, int mode) +{ + struct db9 *db9; + const struct db9_mode_data *db9_mode; + struct parport *pp; + struct pardevice *pd; + struct input_dev *input_dev; + int i, j; + int err; + + if (mode < 1 || mode >= DB9_MAX_PAD || !db9_modes[mode].n_buttons) { + printk(KERN_ERR "db9.c: Bad device type %d\n", mode); + err = -EINVAL; + goto err_out; + } + + db9_mode = &db9_modes[mode]; + + pp = parport_find_number(parport); + if (!pp) { + printk(KERN_ERR "db9.c: no such parport\n"); + err = -ENODEV; + goto err_out; + } + + if (db9_mode->bidirectional && !(pp->modes & PARPORT_MODE_TRISTATE)) { + printk(KERN_ERR "db9.c: specified parport is not bidirectional\n"); + err = -EINVAL; + goto err_put_pp; + } + + pd = parport_register_device(pp, "db9", NULL, NULL, NULL, PARPORT_DEV_EXCL, NULL); + if (!pd) { + printk(KERN_ERR "db9.c: parport busy already - lp.o loaded?\n"); + err = -EBUSY; + goto err_put_pp; + } + + db9 = kzalloc(sizeof(struct db9), GFP_KERNEL); + if (!db9) { + printk(KERN_ERR "db9.c: Not enough memory\n"); + err = -ENOMEM; + goto err_unreg_pardev; + } + + mutex_init(&db9->mutex); + db9->pd = pd; + db9->mode = mode; + init_timer(&db9->timer); + db9->timer.data = (long) db9; + db9->timer.function = db9_timer; + + for (i = 0; i < (min(db9_mode->n_pads, DB9_MAX_DEVICES)); i++) { + + db9->dev[i] = input_dev = input_allocate_device(); + if (!input_dev) { + printk(KERN_ERR "db9.c: Not enough memory for input device\n"); + err = -ENOMEM; + goto err_unreg_devs; + } + + snprintf(db9->phys[i], sizeof(db9->phys[i]), + "%s/input%d", db9->pd->port->name, i); + + input_dev->name = db9_mode->name; + input_dev->phys = db9->phys[i]; + input_dev->id.bustype = BUS_PARPORT; + input_dev->id.vendor = 0x0002; + input_dev->id.product = mode; + input_dev->id.version = 0x0100; + + input_set_drvdata(input_dev, db9); + + input_dev->open = db9_open; + input_dev->close = db9_close; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + for (j = 0; j < db9_mode->n_buttons; j++) + set_bit(db9_mode->buttons[j], input_dev->keybit); + for (j = 0; j < db9_mode->n_axis; j++) { + if (j < 2) + input_set_abs_params(input_dev, db9_abs[j], -1, 1, 0, 0); + else + input_set_abs_params(input_dev, db9_abs[j], 1, 255, 0, 0); + } + + err = input_register_device(input_dev); + if (err) + goto err_free_dev; + } + + parport_put_port(pp); + return db9; + + err_free_dev: + input_free_device(db9->dev[i]); + err_unreg_devs: + while (--i >= 0) + input_unregister_device(db9->dev[i]); + kfree(db9); + err_unreg_pardev: + parport_unregister_device(pd); + err_put_pp: + parport_put_port(pp); + err_out: + return ERR_PTR(err); +} + +static void db9_remove(struct db9 *db9) +{ + int i; + + for (i = 0; i < min(db9_modes[db9->mode].n_pads, DB9_MAX_DEVICES); i++) + input_unregister_device(db9->dev[i]); + parport_unregister_device(db9->pd); + kfree(db9); +} + +static int __init db9_init(void) +{ + int i; + int have_dev = 0; + int err = 0; + + for (i = 0; i < DB9_MAX_PORTS; i++) { + if (db9_cfg[i].nargs == 0 || db9_cfg[i].args[DB9_ARG_PARPORT] < 0) + continue; + + if (db9_cfg[i].nargs < 2) { + printk(KERN_ERR "db9.c: Device type must be specified.\n"); + err = -EINVAL; + break; + } + + db9_base[i] = db9_probe(db9_cfg[i].args[DB9_ARG_PARPORT], + db9_cfg[i].args[DB9_ARG_MODE]); + if (IS_ERR(db9_base[i])) { + err = PTR_ERR(db9_base[i]); + break; + } + + have_dev = 1; + } + + if (err) { + while (--i >= 0) + if (db9_base[i]) + db9_remove(db9_base[i]); + return err; + } + + return have_dev ? 0 : -ENODEV; +} + +static void __exit db9_exit(void) +{ + int i; + + for (i = 0; i < DB9_MAX_PORTS; i++) + if (db9_base[i]) + db9_remove(db9_base[i]); +} + +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 0000000..07a32af --- /dev/null +++ b/drivers/input/joystick/gamecon.c @@ -0,0 +1,851 @@ +/* + * 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 + * Raphael Assenat + */ + +/* + * 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/init.h> +#include <linux/parport.h> +#include <linux/input.h> +#include <linux/mutex.h> + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION("NES, SNES, N64, MultiSystem, PSX gamepad driver"); +MODULE_LICENSE("GPL"); + +#define GC_MAX_PORTS 3 +#define GC_MAX_DEVICES 5 + +struct gc_config { + int args[GC_MAX_DEVICES + 1]; + unsigned int nargs; +}; + +static struct gc_config gc_cfg[GC_MAX_PORTS] __initdata; + +module_param_array_named(map, gc_cfg[0].args, int, &gc_cfg[0].nargs, 0); +MODULE_PARM_DESC(map, "Describes first set of devices (<parport#>,<pad1>,<pad2>,..<pad5>)"); +module_param_array_named(map2, gc_cfg[1].args, int, &gc_cfg[1].nargs, 0); +MODULE_PARM_DESC(map2, "Describes second set of devices"); +module_param_array_named(map3, gc_cfg[2].args, int, &gc_cfg[2].nargs, 0); +MODULE_PARM_DESC(map3, "Describes third set of devices"); + +/* 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_SNESMOUSE 9 + +#define GC_MAX 9 + +#define GC_REFRESH_TIME HZ/100 + +struct gc { + struct pardevice *pd; + struct input_dev *dev[GC_MAX_DEVICES]; + struct timer_list timer; + unsigned char pads[GC_MAX + 1]; + int used; + struct mutex mutex; + char phys[GC_MAX_DEVICES][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", "SNES mouse" }; +/* + * 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. + */ + +} + +static void gc_n64_process_packet(struct gc *gc) +{ + unsigned char data[GC_N64_LENGTH]; + signed char axes[2]; + struct input_dev *dev; + int i, j, s; + + gc_n64_read_packet(gc, data); + + for (i = 0; i < GC_MAX_DEVICES; i++) { + + dev = gc->dev[i]; + if (!dev) + continue; + + s = gc_status_bit[i]; + + if (s & gc->pads[GC_N64] & ~(data[8] | data[9])) { + + 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, ABS_X, axes[0]); + input_report_abs(dev, ABS_Y, -axes[1]); + + input_report_abs(dev, ABS_HAT0X, !(s & data[6]) - !(s & data[7])); + input_report_abs(dev, ABS_HAT0Y, !(s & data[4]) - !(s & data[5])); + + for (j = 0; j < 10; j++) + input_report_key(dev, gc_n64_btn[j], s & data[gc_n64_bytes[j]]); + + input_sync(dev); + } + } +} + +/* + * 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_SNESMOUSE_LENGTH 32 /* The SNES mouse uses 32 bits, the first + 16 bits are equivalent to a gamepad */ + +#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); + } +} + +static void gc_nes_process_packet(struct gc *gc) +{ + unsigned char data[GC_SNESMOUSE_LENGTH]; + struct input_dev *dev; + int i, j, s, len; + char x_rel, y_rel; + + len = gc->pads[GC_SNESMOUSE] ? GC_SNESMOUSE_LENGTH : + (gc->pads[GC_SNES] ? GC_SNES_LENGTH : GC_NES_LENGTH); + + gc_nes_read_packet(gc, len, data); + + for (i = 0; i < GC_MAX_DEVICES; i++) { + + dev = gc->dev[i]; + if (!dev) + continue; + + s = gc_status_bit[i]; + + if (s & (gc->pads[GC_NES] | gc->pads[GC_SNES])) { + input_report_abs(dev, ABS_X, !(s & data[6]) - !(s & data[7])); + input_report_abs(dev, ABS_Y, !(s & data[4]) - !(s & data[5])); + } + + if (s & gc->pads[GC_NES]) + for (j = 0; j < 4; j++) + input_report_key(dev, 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, gc_snes_btn[j], s & data[gc_snes_bytes[j]]); + + if (s & gc->pads[GC_SNESMOUSE]) { + /* + * The 4 unused bits from SNES controllers appear to be ID bits + * so use them to make sure iwe are dealing with a mouse. + * gamepad is connected. This is important since + * my SNES gamepad sends 1's for bits 16-31, which + * cause the mouse pointer to quickly move to the + * upper left corner of the screen. + */ + if (!(s & data[12]) && !(s & data[13]) && + !(s & data[14]) && (s & data[15])) { + input_report_key(dev, BTN_LEFT, s & data[9]); + input_report_key(dev, BTN_RIGHT, s & data[8]); + + x_rel = y_rel = 0; + for (j = 0; j < 7; j++) { + x_rel <<= 1; + if (data[25 + j] & s) + x_rel |= 1; + + y_rel <<= 1; + if (data[17 + j] & s) + y_rel |= 1; + } + + if (x_rel) { + if (data[24] & s) + x_rel = -x_rel; + input_report_rel(dev, REL_X, x_rel); + } + + if (y_rel) { + if (data[16] & s) + y_rel = -y_rel; + input_report_rel(dev, REL_Y, y_rel); + } + } + } + input_sync(dev); + } +} + +/* + * 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; + } +} + +static void gc_multi_process_packet(struct gc *gc) +{ + unsigned char data[GC_MULTI2_LENGTH]; + struct input_dev *dev; + int i, s; + + gc_multi_read_packet(gc, gc->pads[GC_MULTI2] ? GC_MULTI2_LENGTH : GC_MULTI_LENGTH, data); + + for (i = 0; i < GC_MAX_DEVICES; i++) { + + dev = gc->dev[i]; + if (!dev) + continue; + + s = gc_status_bit[i]; + + if (s & (gc->pads[GC_MULTI] | gc->pads[GC_MULTI2])) { + input_report_abs(dev, ABS_X, !(s & data[2]) - !(s & data[3])); + input_report_abs(dev, ABS_Y, !(s & data[0]) - !(s & data[1])); + input_report_key(dev, BTN_TRIGGER, s & data[4]); + } + + if (s & gc->pads[GC_MULTI2]) + input_report_key(dev, BTN_THUMB, s & data[5]); + + input_sync(dev); + } +} + +/* + * 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)"); + +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[GC_MAX_DEVICES]) +{ + int i, j, cmd, read; + + for (i = 0; i < GC_MAX_DEVICES; 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 < GC_MAX_DEVICES; 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[GC_MAX_DEVICES][GC_PSX_BYTES], + unsigned char id[GC_MAX_DEVICES]) +{ + int i, j, max_len = 0; + unsigned long flags; + unsigned char data2[GC_MAX_DEVICES]; + + 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 < GC_MAX_DEVICES; 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 < GC_MAX_DEVICES; 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 < GC_MAX_DEVICES; i++) /* Set id's to the real value */ + id[i] = GC_PSX_ID(id[i]); +} + +static void gc_psx_process_packet(struct gc *gc) +{ + unsigned char data[GC_MAX_DEVICES][GC_PSX_BYTES]; + unsigned char id[GC_MAX_DEVICES]; + struct input_dev *dev; + int i, j; + + gc_psx_read_packet(gc, data, id); + + for (i = 0; i < GC_MAX_DEVICES; i++) { + + dev = gc->dev[i]; + if (!dev) + continue; + + switch (id[i]) { + + case GC_PSX_RUMBLE: + + input_report_key(dev, BTN_THUMBL, ~data[i][0] & 0x04); + input_report_key(dev, BTN_THUMBR, ~data[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, gc_psx_ddr_btn[j], ~data[i][0] & (0x10 << j)); + } else { + for (j = 0; j < 4; j++) + input_report_abs(dev, gc_psx_abs[j + 2], data[i][j + 2]); + + input_report_abs(dev, ABS_X, 128 + !(data[i][0] & 0x20) * 127 - !(data[i][0] & 0x80) * 128); + input_report_abs(dev, ABS_Y, 128 + !(data[i][0] & 0x40) * 127 - !(data[i][0] & 0x10) * 128); + } + + for (j = 0; j < 8; j++) + input_report_key(dev, gc_psx_btn[j], ~data[i][1] & (1 << j)); + + input_report_key(dev, BTN_START, ~data[i][0] & 0x08); + input_report_key(dev, BTN_SELECT, ~data[i][0] & 0x01); + + input_sync(dev); + + break; + + case GC_PSX_NORMAL: + if (gc->pads[GC_DDR] & gc_status_bit[i]) { + for(j = 0; j < 4; j++) + input_report_key(dev, gc_psx_ddr_btn[j], ~data[i][0] & (0x10 << j)); + } else { + input_report_abs(dev, ABS_X, 128 + !(data[i][0] & 0x20) * 127 - !(data[i][0] & 0x80) * 128); + input_report_abs(dev, ABS_Y, 128 + !(data[i][0] & 0x40) * 127 - !(data[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, 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, gc_psx_btn[j], ~data[i][1] & (1 << j)); + + input_report_key(dev, BTN_START, ~data[i][0] & 0x08); + input_report_key(dev, BTN_SELECT, ~data[i][0] & 0x01); + + input_sync(dev); + + break; + + case 0: /* not a pad, ignore */ + break; + } + } +} + +/* + * gc_timer() initiates reads of console pads data. + */ + +static void gc_timer(unsigned long private) +{ + struct gc *gc = (void *) private; + +/* + * N64 pads - must be read first, any read confuses them for 200 us + */ + + if (gc->pads[GC_N64]) + gc_n64_process_packet(gc); + +/* + * NES and SNES pads or mouse + */ + + if (gc->pads[GC_NES] || gc->pads[GC_SNES] || gc->pads[GC_SNESMOUSE]) + gc_nes_process_packet(gc); + +/* + * Multi and Multi2 joysticks + */ + + if (gc->pads[GC_MULTI] || gc->pads[GC_MULTI2]) + gc_multi_process_packet(gc); + +/* + * PSX controllers + */ + + if (gc->pads[GC_PSX] || gc->pads[GC_DDR]) + gc_psx_process_packet(gc); + + mod_timer(&gc->timer, jiffies + GC_REFRESH_TIME); +} + +static int gc_open(struct input_dev *dev) +{ + struct gc *gc = input_get_drvdata(dev); + int err; + + err = mutex_lock_interruptible(&gc->mutex); + if (err) + return err; + + if (!gc->used++) { + parport_claim(gc->pd); + parport_write_control(gc->pd->port, 0x04); + mod_timer(&gc->timer, jiffies + GC_REFRESH_TIME); + } + + mutex_unlock(&gc->mutex); + return 0; +} + +static void gc_close(struct input_dev *dev) +{ + struct gc *gc = input_get_drvdata(dev); + + mutex_lock(&gc->mutex); + if (!--gc->used) { + del_timer_sync(&gc->timer); + parport_write_control(gc->pd->port, 0x00); + parport_release(gc->pd); + } + mutex_unlock(&gc->mutex); +} + +static int __init gc_setup_pad(struct gc *gc, int idx, int pad_type) +{ + struct input_dev *input_dev; + int i; + + if (!pad_type) + return 0; + + if (pad_type < 1 || pad_type > GC_MAX) { + printk(KERN_WARNING "gamecon.c: Pad type %d unknown\n", pad_type); + return -EINVAL; + } + + gc->dev[idx] = input_dev = input_allocate_device(); + if (!input_dev) { + printk(KERN_ERR "gamecon.c: Not enough memory for input device\n"); + return -ENOMEM; + } + + input_dev->name = gc_names[pad_type]; + input_dev->phys = gc->phys[idx]; + input_dev->id.bustype = BUS_PARPORT; + input_dev->id.vendor = 0x0001; + input_dev->id.product = pad_type; + input_dev->id.version = 0x0100; + + input_set_drvdata(input_dev, gc); + + input_dev->open = gc_open; + input_dev->close = gc_close; + + if (pad_type != GC_SNESMOUSE) { + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + + for (i = 0; i < 2; i++) + input_set_abs_params(input_dev, ABS_X + i, -1, 1, 0, 0); + } else + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REL); + + gc->pads[0] |= gc_status_bit[idx]; + gc->pads[pad_type] |= gc_status_bit[idx]; + + switch (pad_type) { + + case GC_N64: + for (i = 0; i < 10; i++) + set_bit(gc_n64_btn[i], input_dev->keybit); + + for (i = 0; i < 2; i++) { + input_set_abs_params(input_dev, ABS_X + i, -127, 126, 0, 2); + input_set_abs_params(input_dev, ABS_HAT0X + i, -1, 1, 0, 0); + } + + break; + + case GC_SNESMOUSE: + set_bit(BTN_LEFT, input_dev->keybit); + set_bit(BTN_RIGHT, input_dev->keybit); + set_bit(REL_X, input_dev->relbit); + set_bit(REL_Y, input_dev->relbit); + break; + + case GC_SNES: + for (i = 4; i < 8; i++) + set_bit(gc_snes_btn[i], input_dev->keybit); + case GC_NES: + for (i = 0; i < 4; i++) + set_bit(gc_snes_btn[i], input_dev->keybit); + break; + + case GC_MULTI2: + set_bit(BTN_THUMB, input_dev->keybit); + case GC_MULTI: + set_bit(BTN_TRIGGER, input_dev->keybit); + break; + + case GC_PSX: + for (i = 0; i < 6; i++) + input_set_abs_params(input_dev, gc_psx_abs[i], 4, 252, 0, 2); + for (i = 0; i < 12; i++) + set_bit(gc_psx_btn[i], input_dev->keybit); + + break; + + case GC_DDR: + for (i = 0; i < 4; i++) + set_bit(gc_psx_ddr_btn[i], input_dev->keybit); + for (i = 0; i < 12; i++) + set_bit(gc_psx_btn[i], input_dev->keybit); + + break; + } + + return 0; +} + +static struct gc __init *gc_probe(int parport, int *pads, int n_pads) +{ + struct gc *gc; + struct parport *pp; + struct pardevice *pd; + int i; + int err; + + pp = parport_find_number(parport); + if (!pp) { + printk(KERN_ERR "gamecon.c: no such parport\n"); + err = -EINVAL; + goto err_out; + } + + pd = parport_register_device(pp, "gamecon", NULL, NULL, NULL, PARPORT_DEV_EXCL, NULL); + if (!pd) { + printk(KERN_ERR "gamecon.c: parport busy already - lp.o loaded?\n"); + err = -EBUSY; + goto err_put_pp; + } + + gc = kzalloc(sizeof(struct gc), GFP_KERNEL); + if (!gc) { + printk(KERN_ERR "gamecon.c: Not enough memory\n"); + err = -ENOMEM; + goto err_unreg_pardev; + } + + mutex_init(&gc->mutex); + gc->pd = pd; + init_timer(&gc->timer); + gc->timer.data = (long) gc; + gc->timer.function = gc_timer; + + for (i = 0; i < n_pads && i < GC_MAX_DEVICES; i++) { + if (!pads[i]) + continue; + + snprintf(gc->phys[i], sizeof(gc->phys[i]), + "%s/input%d", gc->pd->port->name, i); + err = gc_setup_pad(gc, i, pads[i]); + if (err) + goto err_unreg_devs; + + err = input_register_device(gc->dev[i]); + if (err) + goto err_free_dev; + } + + if (!gc->pads[0]) { + printk(KERN_ERR "gamecon.c: No valid devices specified\n"); + err = -EINVAL; + goto err_free_gc; + } + + parport_put_port(pp); + return gc; + + err_free_dev: + input_free_device(gc->dev[i]); + err_unreg_devs: + while (--i >= 0) + if (gc->dev[i]) + input_unregister_device(gc->dev[i]); + err_free_gc: + kfree(gc); + err_unreg_pardev: + parport_unregister_device(pd); + err_put_pp: + parport_put_port(pp); + err_out: + return ERR_PTR(err); +} + +static void gc_remove(struct gc *gc) +{ + int i; + + for (i = 0; i < GC_MAX_DEVICES; i++) + if (gc->dev[i]) + input_unregister_device(gc->dev[i]); + parport_unregister_device(gc->pd); + kfree(gc); +} + +static int __init gc_init(void) +{ + int i; + int have_dev = 0; + int err = 0; + + for (i = 0; i < GC_MAX_PORTS; i++) { + if (gc_cfg[i].nargs == 0 || gc_cfg[i].args[0] < 0) + continue; + + if (gc_cfg[i].nargs < 2) { + printk(KERN_ERR "gamecon.c: at least one device must be specified\n"); + err = -EINVAL; + break; + } + + gc_base[i] = gc_probe(gc_cfg[i].args[0], + gc_cfg[i].args + 1, gc_cfg[i].nargs - 1); + if (IS_ERR(gc_base[i])) { + err = PTR_ERR(gc_base[i]); + break; + } + + have_dev = 1; + } + + if (err) { + while (--i >= 0) + if (gc_base[i]) + gc_remove(gc_base[i]); + return err; + } + + return have_dev ? 0 : -ENODEV; +} + +static void __exit gc_exit(void) +{ + int i; + + for (i = 0; i < GC_MAX_PORTS; i++) + if (gc_base[i]) + gc_remove(gc_base[i]); +} + +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 0000000..67c207f --- /dev/null +++ b/drivers/input/joystick/gf2k.c @@ -0,0 +1,387 @@ +/* + * 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> +#include <linux/jiffies.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 = input_get_drvdata(dev); + + gameport_start_polling(gf2k->gameport); + return 0; +} + +static void gf2k_close(struct input_dev *dev) +{ + struct gf2k *gf2k = input_get_drvdata(dev); + + 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; + struct input_dev *input_dev; + unsigned char data[GF2K_LENGTH]; + int i, err; + + gf2k = kzalloc(sizeof(struct gf2k), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!gf2k || !input_dev) { + err = -ENOMEM; + goto fail1; + } + + gf2k->gameport = gameport; + gf2k->dev = input_dev; + + 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); + + snprintf(gf2k->phys, sizeof(gf2k->phys), "%s/input0", gameport->phys); + + gf2k->length = gf2k_lens[gf2k->id]; + + input_dev->name = gf2k_names[gf2k->id]; + input_dev->phys = gf2k->phys; + input_dev->id.bustype = BUS_GAMEPORT; + input_dev->id.vendor = GAMEPORT_ID_VENDOR_GENIUS; + input_dev->id.product = gf2k->id; + input_dev->id.version = 0x0100; + input_dev->dev.parent = &gameport->dev; + + input_set_drvdata(input_dev, gf2k); + + input_dev->open = gf2k_open; + input_dev->close = gf2k_close; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + + for (i = 0; i < gf2k_axes[gf2k->id]; i++) + set_bit(gf2k_abs[i], input_dev->absbit); + + for (i = 0; i < gf2k_hats[gf2k->id]; i++) { + set_bit(ABS_HAT0X + i, input_dev->absbit); + input_dev->absmin[ABS_HAT0X + i] = -1; + input_dev->absmax[ABS_HAT0X + i] = 1; + } + + for (i = 0; i < gf2k_joys[gf2k->id]; i++) + set_bit(gf2k_btn_joy[i], input_dev->keybit); + + for (i = 0; i < gf2k_pads[gf2k->id]; i++) + set_bit(gf2k_btn_pad[i], input_dev->keybit); + + gf2k_read_packet(gameport, gf2k->length, data); + gf2k_read(gf2k, data); + + for (i = 0; i < gf2k_axes[gf2k->id]; i++) { + input_dev->absmax[gf2k_abs[i]] = (i < 2) ? input_dev->abs[gf2k_abs[i]] * 2 - 32 : + input_dev->abs[gf2k_abs[0]] + input_dev->abs[gf2k_abs[1]] - 32; + input_dev->absmin[gf2k_abs[i]] = 32; + input_dev->absfuzz[gf2k_abs[i]] = 8; + input_dev->absflat[gf2k_abs[i]] = (i < 2) ? 24 : 0; + } + + err = input_register_device(gf2k->dev); + if (err) + goto fail2; + + return 0; + + fail2: gameport_close(gameport); + fail1: gameport_set_drvdata(gameport, NULL); + input_free_device(input_dev); + 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) +{ + return gameport_register_driver(&gf2k_drv); +} + +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 0000000..fc55899 --- /dev/null +++ b/drivers/input/joystick/grip.c @@ -0,0 +1,438 @@ +/* + * 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> +#include <linux/jiffies.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]; + if (!dev) + continue; + + 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 = input_get_drvdata(dev); + + gameport_start_polling(grip->gameport); + return 0; +} + +static void grip_close(struct input_dev *dev) +{ + struct grip *grip = input_get_drvdata(dev); + + gameport_stop_polling(grip->gameport); +} + +static int grip_connect(struct gameport *gameport, struct gameport_driver *drv) +{ + struct grip *grip; + struct input_dev *input_dev; + unsigned int data[GRIP_LENGTH_XT]; + int i, j, t; + int err; + + if (!(grip = kzalloc(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]) + continue; + + grip->dev[i] = input_dev = input_allocate_device(); + if (!input_dev) { + err = -ENOMEM; + goto fail3; + } + + snprintf(grip->phys[i], sizeof(grip->phys[i]), + "%s/input%d", gameport->phys, i); + + input_dev->name = grip_name[grip->mode[i]]; + input_dev->phys = grip->phys[i]; + input_dev->id.bustype = BUS_GAMEPORT; + input_dev->id.vendor = GAMEPORT_ID_VENDOR_GRAVIS; + input_dev->id.product = grip->mode[i]; + input_dev->id.version = 0x0100; + input_dev->dev.parent = &gameport->dev; + + input_set_drvdata(input_dev, grip); + + input_dev->open = grip_open; + input_dev->close = grip_close; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(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(input_dev, t, 14, 52, 1, 2); + else if (j < grip_anx[grip->mode[i]]) + input_set_abs_params(input_dev, t, 3, 57, 1, 0); + else + input_set_abs_params(input_dev, t, -1, 1, 0, 0); + } + + for (j = 0; (t = grip_btn[grip->mode[i]][j]) >= 0; j++) + if (t > 0) + set_bit(t, input_dev->keybit); + + err = input_register_device(grip->dev[i]); + if (err) + goto fail4; + } + + return 0; + + fail4: input_free_device(grip->dev[i]); + fail3: while (--i >= 0) + if (grip->dev[i]) + input_unregister_device(grip->dev[i]); + 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->dev[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", + .owner = THIS_MODULE, + }, + .description = DRIVER_DESC, + .connect = grip_connect, + .disconnect = grip_disconnect, +}; + +static int __init grip_init(void) +{ + return gameport_register_driver(&grip_drv); +} + +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 0000000..2d47baf --- /dev/null +++ b/drivers/input/joystick/grip_mp.c @@ -0,0 +1,701 @@ +/* + * 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> +#include <linux/jiffies.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 + +#define GRIP_MAX_PORTS 4 +/* + * Grip multiport state + */ + +struct grip_port { + struct input_dev *dev; + int mode; + int registered; + + /* individual gamepad states */ + int buttons; + int xaxes; + int yaxes; + int dirty; /* has the state been updated? */ +}; + +struct grip_mp { + struct gameport *gameport; + struct grip_port *port[GRIP_MAX_PORTS]; + int reads; + int bads; +}; + +/* + * 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 const int grip_btn_gp[] = { BTN_TR, BTN_TL, BTN_A, BTN_B, BTN_C, BTN_X, BTN_Y, BTN_Z, -1 }; +static const int grip_btn_c64[] = { BTN_JOYSTICK, -1 }; + +static const int grip_abs_gp[] = { ABS_X, ABS_Y, -1 }; +static const int grip_abs_c64[] = { ABS_X, ABS_Y, -1 }; + +static const int *grip_abs[] = { NULL, NULL, grip_abs_gp, grip_abs_c64 }; +static const int *grip_btn[] = { NULL, NULL, grip_btn_gp, grip_btn_c64 }; + +static const 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 const int axis_map[] = { 5, 9, 1, 5, 6, 10, 2, 6, 4, 8, 0, 4, 5, 9, 1, 5 }; + +static int 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; + int flags, tries = 0, bads = 0; + + for (i = 0; i < ARRAY_SIZE(init_seq); 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) +{ + struct grip_port *port; + u32 packet; + int joytype = 0; + int slot; + + /* 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; + + port = grip->port[slot]; + + /* + * 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 (port->registered) { + printk(KERN_INFO "grip_mp: removing %s, slot %d\n", + grip_name[port->mode], slot); + input_unregister_device(port->dev); + port->registered = 0; + } + dbg("Reset: grip multiport slot %d\n", slot); + port->mode = 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 */ + port->buttons = (~packet) & 0xff; + port->yaxes = ((axis_map[dir] >> 2) & 3) - 1; + port->xaxes = (axis_map[dir] & 3) - 1; + port->dirty = 1; + + if (port->mode == GRIP_MODE_RESET) + flags |= IO_SLOT_CHANGE; + + port->mode = GRIP_MODE_GP; + + if (!port->registered) { + dbg("New Grip pad in multiport slot %d.\n", slot); + if (register_slot(slot, grip)) { + port->mode = GRIP_MODE_RESET; + port->dirty = 0; + } + } + 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->port[slot]->mode == GRIP_MODE_RESET) + invalid = 1; + if (grip->port[slot]->mode != 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 grip_port *port = grip->port[slot]; + int i; + + /* Store button states with linux input driver */ + + for (i = 0; i < 8; i++) + input_report_key(port->dev, grip_btn_gp[i], (port->buttons >> i) & 1); + + /* Store axis states with linux driver */ + + input_report_abs(port->dev, ABS_X, port->xaxes); + input_report_abs(port->dev, ABS_Y, port->yaxes); + + /* Tell the receiver of the events to process them */ + + input_sync(port->dev); + + port->dirty = 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->port[i]->dirty) + report_slot(grip, i); +} + +/* + * Called when a joystick device file is opened + */ + +static int grip_open(struct input_dev *dev) +{ + struct grip_mp *grip = input_get_drvdata(dev); + + 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 = input_get_drvdata(dev); + + gameport_stop_polling(grip->gameport); +} + +/* + * Tell the linux input layer about a newly plugged-in gamepad. + */ + +static int register_slot(int slot, struct grip_mp *grip) +{ + struct grip_port *port = grip->port[slot]; + struct input_dev *input_dev; + int j, t; + int err; + + port->dev = input_dev = input_allocate_device(); + if (!input_dev) + return -ENOMEM; + + input_dev->name = grip_name[port->mode]; + input_dev->id.bustype = BUS_GAMEPORT; + input_dev->id.vendor = GAMEPORT_ID_VENDOR_GRAVIS; + input_dev->id.product = 0x0100 + port->mode; + input_dev->id.version = 0x0100; + input_dev->dev.parent = &grip->gameport->dev; + + input_set_drvdata(input_dev, grip); + + input_dev->open = grip_open; + input_dev->close = grip_close; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + + for (j = 0; (t = grip_abs[port->mode][j]) >= 0; j++) + input_set_abs_params(input_dev, t, -1, 1, 0, 0); + + for (j = 0; (t = grip_btn[port->mode][j]) >= 0; j++) + if (t > 0) + set_bit(t, input_dev->keybit); + + err = input_register_device(port->dev); + if (err) { + input_free_device(port->dev); + return err; + } + + port->registered = 1; + + if (port->dirty) /* report initial state, if any */ + report_slot(grip, slot); + + return 0; +} + +static int grip_connect(struct gameport *gameport, struct gameport_driver *drv) +{ + struct grip_mp *grip; + int err; + + if (!(grip = kzalloc(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->port[0]->mode && !grip->port[1]->mode && !grip->port[2]->mode && !grip->port[3]->mode) { + /* 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->port[i]->registered) + input_unregister_device(grip->port[i]->dev); + 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) +{ + return gameport_register_driver(&grip_drv); +} + +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 0000000..4058d4b --- /dev/null +++ b/drivers/input/joystick/guillemot.c @@ -0,0 +1,295 @@ +/* + * 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> +#include <linux/jiffies.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 = input_get_drvdata(dev); + + 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 = input_get_drvdata(dev); + + 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; + struct input_dev *input_dev; + u8 data[GUILLEMOT_MAX_LENGTH]; + int i, t; + int err; + + guillemot = kzalloc(sizeof(struct guillemot), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!guillemot || !input_dev) { + err = -ENOMEM; + goto fail1; + } + + guillemot->gameport = gameport; + guillemot->dev = input_dev; + + 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); + + snprintf(guillemot->phys, sizeof(guillemot->phys), "%s/input0", gameport->phys); + guillemot->type = guillemot_type + i; + + input_dev->name = guillemot_type[i].name; + input_dev->phys = guillemot->phys; + input_dev->id.bustype = BUS_GAMEPORT; + input_dev->id.vendor = GAMEPORT_ID_VENDOR_GUILLEMOT; + input_dev->id.product = guillemot_type[i].id; + input_dev->id.version = (int)data[14] << 8 | data[15]; + input_dev->dev.parent = &gameport->dev; + + input_set_drvdata(input_dev, guillemot); + + input_dev->open = guillemot_open; + input_dev->close = guillemot_close; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + + for (i = 0; (t = guillemot->type->abs[i]) >= 0; i++) + input_set_abs_params(input_dev, t, 0, 255, 0, 0); + + if (guillemot->type->hat) { + input_set_abs_params(input_dev, ABS_HAT0X, -1, 1, 0, 0); + input_set_abs_params(input_dev, ABS_HAT0Y, -1, 1, 0, 0); + } + + for (i = 0; (t = guillemot->type->btn[i]) >= 0; i++) + set_bit(t, input_dev->keybit); + + err = input_register_device(guillemot->dev); + if (err) + goto fail2; + + return 0; + +fail2: gameport_close(gameport); +fail1: gameport_set_drvdata(gameport, NULL); + input_free_device(input_dev); + 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) +{ + return gameport_register_driver(&guillemot_drv); +} + +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 0000000..8fde22a --- /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 0000000..74daff4 --- /dev/null +++ b/drivers/input/joystick/iforce/Makefile @@ -0,0 +1,20 @@ +# +# Makefile for the I-Force driver +# +# By Johann Deneux <johann.deneux@gmail.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 0000000..0de9a09 --- /dev/null +++ b/drivers/input/joystick/iforce/iforce-ff.c @@ -0,0 +1,543 @@ +/* + * Copyright (c) 2000-2002 Vojtech Pavlik <vojtech@ucw.cz> + * Copyright (c) 2001-2002, 2007 Johann Deneux <johann.deneux@gmail.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) { + mutex_lock(&iforce->mem_mutex); + if (allocate_resource(&(iforce->device_memory), mod_chunk, 2, + iforce->device_memory.start, iforce->device_memory.end, 2L, + NULL, NULL)) { + mutex_unlock(&iforce->mem_mutex); + return -ENOSPC; + } + mutex_unlock(&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) { + mutex_lock(&iforce->mem_mutex); + if (allocate_resource(&(iforce->device_memory), mod_chunk, 0x0c, + iforce->device_memory.start, iforce->device_memory.end, 2L, + NULL, NULL)) { + mutex_unlock(&iforce->mem_mutex); + return -ENOSPC; + } + mutex_unlock(&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) { + mutex_lock(&iforce->mem_mutex); + if (allocate_resource(&(iforce->device_memory), mod_chunk, 0x0e, + iforce->device_memory.start, iforce->device_memory.end, 2L, + NULL, NULL)) { + mutex_unlock(&iforce->mem_mutex); + return -ENOSPC; + } + mutex_unlock(&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) { + mutex_lock(&iforce->mem_mutex); + if (allocate_resource(&(iforce->device_memory), mod_chunk, 8, + iforce->device_memory.start, iforce->device_memory.end, 2L, + NULL, NULL)) { + mutex_unlock(&iforce->mem_mutex); + return -ENOSPC; + } + mutex_unlock(&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 *old, + struct ff_effect *new) +{ + int ret = 0; + int i; + + if (new->type != FF_SPRING && new->type != FF_FRICTION) { + dev_warn(&iforce->dev->dev, "bad effect type in %s\n", + __func__); + return 0; + } + + 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 *old, + struct ff_effect *effect) +{ + if (effect->type != FF_CONSTANT) { + dev_warn(&iforce->dev->dev, "bad effect type in %s\n", + __func__); + return 0; + } + + 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 *old, + struct ff_effect *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 1; + 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 1; + break; + + default: + dev_warn(&iforce->dev->dev, "bad effect type in %s\n", + __func__); + } + + return 0; +} + +/* + * 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 *old, + struct ff_effect *new) +{ + if (new->type != FF_PERIODIC) { + dev_warn(&iforce->dev->dev, "bad effect type in %s\n", + __func__); + return 0; + } + 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 ff_effect *old, struct ff_effect *new) +{ + 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 1; + + return 0; +} +/* + * 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, struct ff_effect *old) +{ + 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 (!old || need_period_modifier(iforce, old, effect)) { + param1_err = make_period_modifier(iforce, mod1_chunk, + old != NULL, + 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 (!old || need_envelope_modifier(iforce, old, effect)) { + param2_err = make_envelope_modifier(iforce, mod2_chunk, + old !=NULL, + 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 (!old || need_core(old, 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, struct ff_effect *old) +{ + 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 (!old || need_magnitude_modifier(iforce, old, effect)) { + param1_err = make_magnitude_modifier(iforce, mod1_chunk, + old != NULL, + effect->u.constant.level); + if (param1_err) + return param1_err; + set_bit(FF_MOD1_IS_USED, core_effect->flags); + } + + if (!old || need_envelope_modifier(iforce, old, effect)) { + param2_err = make_envelope_modifier(iforce, mod2_chunk, + old != NULL, + 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 (!old || need_core(old, 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, struct ff_effect *old) +{ + 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 (!old || need_condition_modifier(iforce, old, effect)) { + param_err = make_condition_modifier(iforce, mod1_chunk, + old != NULL, + 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, + old != NULL, + 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 (!old || need_core(old, 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 0000000..baabf83 --- /dev/null +++ b/drivers/input/joystick/iforce/iforce-main.c @@ -0,0 +1,496 @@ +/* + * Copyright (c) 2000-2002 Vojtech Pavlik <vojtech@ucw.cz> + * Copyright (c) 2001-2002, 2007 Johann Deneux <johann.deneux@gmail.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 <johann.deneux@gmail.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 }, //? + { 0x06f8, 0x0004, "Gullemot Jet Leader 3D", btn_joystick, abs_joystick, ff_iforce }, //? + { 0x06d6, 0x29bc, "Trust Force Feedback Race Master", btn_wheel, abs_wheel, ff_iforce }, + { 0x0000, 0x0000, "Unknown I-Force Device [%04x:%04x]", btn_joystick, abs_joystick, ff_iforce } +}; + +static int iforce_playback(struct input_dev *dev, int effect_id, int value) +{ + struct iforce *iforce = input_get_drvdata(dev); + struct iforce_core_effect *core_effect = &iforce->core_effects[effect_id]; + + if (value > 0) + set_bit(FF_CORE_SHOULD_PLAY, core_effect->flags); + else + clear_bit(FF_CORE_SHOULD_PLAY, core_effect->flags); + + iforce_control_playback(iforce, effect_id, value); + return 0; +} + +static void iforce_set_gain(struct input_dev *dev, u16 gain) +{ + struct iforce *iforce = input_get_drvdata(dev); + unsigned char data[3]; + + data[0] = gain >> 9; + iforce_send_packet(iforce, FF_CMD_GAIN, data); +} + +static void iforce_set_autocenter(struct input_dev *dev, u16 magnitude) +{ + struct iforce *iforce = input_get_drvdata(dev); + unsigned char data[3]; + + data[0] = 0x03; + data[1] = magnitude >> 9; + iforce_send_packet(iforce, FF_CMD_AUTOCENTER, data); + + data[0] = 0x04; + data[1] = 0x01; + iforce_send_packet(iforce, FF_CMD_AUTOCENTER, data); +} + +/* + * 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 ff_effect *old) +{ + struct iforce *iforce = input_get_drvdata(dev); + struct iforce_core_effect *core_effect = &iforce->core_effects[effect->id]; + int ret; + + if (__test_and_set_bit(FF_CORE_IS_USED, core_effect->flags)) { + /* Check the effect is not already being updated */ + if (test_bit(FF_CORE_UPDATE, core_effect->flags)) + return -EAGAIN; + } + +/* + * Upload the effect + */ + switch (effect->type) { + + case FF_PERIODIC: + ret = iforce_upload_periodic(iforce, effect, old); + break; + + case FF_CONSTANT: + ret = iforce_upload_constant(iforce, effect, old); + break; + + case FF_SPRING: + case FF_DAMPER: + ret = iforce_upload_condition(iforce, effect, old); + 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, core_effect->flags); + } + 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 = input_get_drvdata(dev); + struct iforce_core_effect *core_effect = &iforce->core_effects[effect_id]; + int err = 0; + + if (test_bit(FF_MOD1_IS_USED, core_effect->flags)) + err = release_resource(&core_effect->mod1_chunk); + + if (!err && test_bit(FF_MOD2_IS_USED, core_effect->flags)) + err = release_resource(&core_effect->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 = input_get_drvdata(dev); + + 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 + } + + if (test_bit(EV_FF, dev->evbit)) { + /* Enable force feedback */ + iforce_send_packet(iforce, FF_CMD_ENABLE, "\004"); + } + + return 0; +} + +static void iforce_release(struct input_dev *dev) +{ + struct iforce *iforce = input_get_drvdata(dev); + int i; + + if (test_bit(EV_FF, dev->evbit)) { + /* Check: no effects should be present in memory */ + for (i = 0; i < dev->ff->max_effects; i++) { + if (test_bit(FF_CORE_IS_USED, iforce->core_effects[i].flags)) { + dev_warn(&dev->dev, + "%s: Device still owns effects\n", + __func__); + break; + } + } + + /* Disable force feedback playback */ + iforce_send_packet(iforce, FF_CMD_ENABLE, "\001"); + } + + switch (iforce->bus) { +#ifdef CONFIG_JOYSTICK_IFORCE_USB + case IFORCE_USB: + usb_kill_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) +{ + struct input_dev *input_dev; + struct ff_device *ff; + unsigned char c[] = "CEOV"; + int i, error; + int ff_effects = 0; + + input_dev = input_allocate_device(); + if (!input_dev) + return -ENOMEM; + + init_waitqueue_head(&iforce->wait); + spin_lock_init(&iforce->xmit_lock); + mutex_init(&iforce->mem_mutex); + iforce->xmit.buf = iforce->xmit_data; + iforce->dev = input_dev; + +/* + * Input device fields. + */ + + switch (iforce->bus) { +#ifdef CONFIG_JOYSTICK_IFORCE_USB + case IFORCE_USB: + input_dev->id.bustype = BUS_USB; + input_dev->dev.parent = &iforce->usbdev->dev; + break; +#endif +#ifdef CONFIG_JOYSTICK_IFORCE_232 + case IFORCE_232: + input_dev->id.bustype = BUS_RS232; + input_dev->dev.parent = &iforce->serio->dev; + break; +#endif + } + + input_set_drvdata(input_dev, iforce); + + input_dev->name = "Unknown I-Force device"; + input_dev->open = iforce_open; + input_dev->close = iforce_release; + +/* + * 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 */ + err("Timeout waiting for response from device."); + error = -ENODEV; + goto fail; + } + +/* + * Get device info. + */ + + if (!iforce_get_id_packet(iforce, "M")) + input_dev->id.vendor = (iforce->edata[2] << 8) | iforce->edata[1]; + else + dev_warn(&iforce->dev->dev, "Device does not respond to id packet M\n"); + + if (!iforce_get_id_packet(iforce, "P")) + input_dev->id.product = (iforce->edata[2] << 8) | iforce->edata[1]; + else + dev_warn(&iforce->dev->dev, "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 + dev_warn(&iforce->dev->dev, "Device does not respond to id packet B\n"); + + if (!iforce_get_id_packet(iforce, "N")) + ff_effects = iforce->edata[1]; + else + dev_warn(&iforce->dev->dev, "Device does not respond to id packet N\n"); + + /* Check if the device can store more effects than the driver can really handle */ + if (ff_effects > IFORCE_EFFECTS_MAX) { + dev_warn(&iforce->dev->dev, "Limiting number of effects to %d (device reports %d)\n", + IFORCE_EFFECTS_MAX, ff_effects); + ff_effects = IFORCE_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. + */ + iforce_set_autocenter(input_dev, 0); + +/* + * Find appropriate device entry + */ + + for (i = 0; iforce_device[i].idvendor; i++) + if (iforce_device[i].idvendor == input_dev->id.vendor && + iforce_device[i].idproduct == input_dev->id.product) + break; + + iforce->type = iforce_device + i; + input_dev->name = iforce->type->name; + +/* + * Set input device bitfields and ranges. + */ + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS) | + BIT_MASK(EV_FF_STATUS); + + for (i = 0; iforce->type->btn[i] >= 0; i++) + set_bit(iforce->type->btn[i], input_dev->keybit); + set_bit(BTN_DEAD, input_dev->keybit); + + for (i = 0; iforce->type->abs[i] >= 0; i++) { + + signed short t = iforce->type->abs[i]; + + switch (t) { + + case ABS_X: + case ABS_Y: + case ABS_WHEEL: + + input_set_abs_params(input_dev, t, -1920, 1920, 16, 128); + set_bit(t, input_dev->ffbit); + break; + + case ABS_THROTTLE: + case ABS_GAS: + case ABS_BRAKE: + + input_set_abs_params(input_dev, t, 0, 255, 0, 0); + break; + + case ABS_RUDDER: + + input_set_abs_params(input_dev, t, -128, 127, 0, 0); + break; + + case ABS_HAT0X: + case ABS_HAT0Y: + case ABS_HAT1X: + case ABS_HAT1Y: + + input_set_abs_params(input_dev, t, -1, 1, 0, 0); + break; + } + } + + if (ff_effects) { + + for (i = 0; iforce->type->ff[i] >= 0; i++) + set_bit(iforce->type->ff[i], input_dev->ffbit); + + error = input_ff_create(input_dev, ff_effects); + if (error) + goto fail; + + ff = input_dev->ff; + ff->upload = iforce_upload_effect; + ff->erase = iforce_erase_effect; + ff->set_gain = iforce_set_gain; + ff->set_autocenter = iforce_set_autocenter; + ff->playback = iforce_playback; + } +/* + * Register input device. + */ + + error = input_register_device(iforce->dev); + if (error) + goto fail; + + return 0; + + fail: input_free_device(input_dev); + return error; +} + +static int __init iforce_init(void) +{ + int err = 0; + +#ifdef CONFIG_JOYSTICK_IFORCE_USB + err = usb_register(&iforce_usb_driver); + if (err) + return err; +#endif +#ifdef CONFIG_JOYSTICK_IFORCE_232 + err = serio_register_driver(&iforce_serio_drv); +#ifdef CONFIG_JOYSTICK_IFORCE_USB + if (err) + usb_deregister(&iforce_usb_driver); +#endif +#endif + return err; +} + +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 0000000..a17b500 --- /dev/null +++ b/drivers/input/joystick/iforce/iforce-packets.c @@ -0,0 +1,303 @@ +/* + * Copyright (c) 2000-2002 Vojtech Pavlik <vojtech@ucw.cz> + * Copyright (c) 2001-2002, 2007 Johann Deneux <johann.deneux@gmail.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 __FILE__ ": %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) { + dev_warn(&iforce->dev->dev, + "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]; + + 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; + + if (!iforce->dev->ff) + return 0; + + for (i = 0; i < iforce->dev->ff->max_effects; ++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; + } + } + dev_warn(&iforce->dev->dev, "unused effect %04x updated !!!\n", addr); + return -1; +} + +void iforce_process_packet(struct iforce *iforce, u16 cmd, unsigned char *data) +{ + struct input_dev *dev = iforce->dev; + int i; + static int being_used = 0; + + if (being_used) + dev_warn(&iforce->dev->dev, + "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); + } +#endif + wake_up(&iforce->wait); + + if (!iforce->type) { + being_used--; + return; + } + + switch (HI(cmd)) { + + case 0x01: /* joystick position data */ + case 0x03: /* wheel position data */ + 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_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) +{ + switch (iforce->bus) { + + case IFORCE_USB: { +#ifdef CONFIG_JOYSTICK_IFORCE_USB + int status; + + iforce->cr.bRequest = packet[0]; + iforce->ctrl->dev = iforce->usbdev; + + status = usb_submit_urb(iforce->ctrl, GFP_ATOMIC); + if (status) { + err("usb_submit_urb failed %d", status); + return -1; + } + + wait_event_interruptible_timeout(iforce->wait, + iforce->ctrl->status != -EINPROGRESS, HZ); + + if (iforce->ctrl->status) { + dbg("iforce->ctrl->status = %d", iforce->ctrl->status); + usb_unlink_urb(iforce->ctrl); + return -1; + } +#else + dbg("iforce_get_id_packet: iforce->bus = USB!"); +#endif + } + break; + + case IFORCE_232: + +#ifdef CONFIG_JOYSTICK_IFORCE_232 + iforce->expect_packet = FF_CMD_QUERY; + iforce_send_packet(iforce, FF_CMD_QUERY, packet); + + wait_event_interruptible_timeout(iforce->wait, + !iforce->expect_packet, HZ); + + if (iforce->expect_packet) { + iforce->expect_packet = 0; + return -1; + } +#else + err("iforce_get_id_packet: iforce->bus = SERIO!"); +#endif + break; + + default: + err("iforce_get_id_packet: iforce->bus = %d", 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 0000000..46d5041 --- /dev/null +++ b/drivers/input/joystick/iforce/iforce-serio.c @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2000-2001 Vojtech Pavlik <vojtech@ucw.cz> + * Copyright (c) 2001, 2007 Johann Deneux <johann.deneux@gmail.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 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); + 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; + + iforce = kzalloc(sizeof(struct iforce), GFP_KERNEL); + if (!iforce) + return -ENOMEM; + + iforce->bus = IFORCE_232; + iforce->serio = serio; + + serio_set_drvdata(serio, iforce); + + err = serio_open(serio, drv); + if (err) + goto fail1; + + err = iforce_init_device(iforce); + if (err) + goto fail2; + + return 0; + + fail2: serio_close(serio); + fail1: serio_set_drvdata(serio, NULL); + kfree(iforce); + return err; +} + +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 0000000..f83185a --- /dev/null +++ b/drivers/input/joystick/iforce/iforce-usb.c @@ -0,0 +1,239 @@ + /* + * Copyright (c) 2000-2002 Vojtech Pavlik <vojtech@ucw.cz> + * Copyright (c) 2001-2002, 2007 Johann Deneux <johann.deneux@gmail.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)) ) { + clear_bit(IFORCE_XMIT_RUNNING, iforce->xmit_flags); + dev_warn(&iforce->dev->dev, "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 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", + __func__, urb->status); + return; + default: + dbg("%s - urb has status of: %d", __func__, urb->status); + goto exit; + } + + iforce_process_packet(iforce, + (iforce->data[0] << 8) | (urb->actual_length - 1), iforce->data + 1); + +exit: + status = usb_submit_urb (urb, GFP_ATOMIC); + if (status) + err ("%s - usb_submit_urb failed with result %d", + __func__, status); +} + +static void iforce_usb_out(struct urb *urb) +{ + struct iforce *iforce = urb->context; + + if (urb->status) { + dbg("urb->status %d, exiting", urb->status); + return; + } + + iforce_usb_xmit(iforce); + + wake_up(&iforce->wait); +} + +static void iforce_usb_ctrl(struct urb *urb) +{ + 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; + int err = -ENOMEM; + + interface = intf->cur_altsetting; + + epirq = &interface->endpoint[0].desc; + epout = &interface->endpoint[1].desc; + + if (!(iforce = kzalloc(sizeof(struct iforce) + 32, GFP_KERNEL))) + goto fail; + + 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 = cpu_to_le16(16); + + usb_fill_int_urb(iforce->irq, dev, usb_rcvintpipe(dev, epirq->bEndpointAddress), + iforce->data, 16, iforce_usb_irq, iforce, epirq->bInterval); + + usb_fill_int_urb(iforce->out, dev, usb_sndintpipe(dev, epout->bEndpointAddress), + iforce + 1, 32, iforce_usb_out, iforce, epout->bInterval); + + usb_fill_control_urb(iforce->ctrl, dev, usb_rcvctrlpipe(dev, 0), + (void*) &iforce->cr, iforce->edata, 16, iforce_usb_ctrl, iforce); + + err = iforce_init_device(iforce); + if (err) + goto fail; + + usb_set_intfdata(intf, iforce); + return 0; + +fail: + if (iforce) { + usb_free_urb(iforce->irq); + usb_free_urb(iforce->out); + usb_free_urb(iforce->ctrl); + kfree(iforce); + } + + return err; +} + +/* Called by iforce_delete() */ +void iforce_usb_delete(struct iforce* iforce) +{ + usb_kill_urb(iforce->irq); + usb_kill_urb(iforce->out); + usb_kill_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 */ + { USB_DEVICE(0x06f8, 0xa302) }, /* Guillemot Jet Leader 3D */ + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE (usb, iforce_usb_ids); + +struct usb_driver iforce_usb_driver = { + .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 0000000..f2d91f4 --- /dev/null +++ b/drivers/input/joystick/iforce/iforce.h @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2000-2002 Vojtech Pavlik <vojtech@ucw.cz> + * Copyright (c) 2001-2002, 2007 Johann Deneux <johann.deneux@gmail.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/circ_buf.h> +#include <linux/mutex.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 IFORCE_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_CNT 6 + +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[BITS_TO_LONGS(FF_MODCORE_CNT)]; +}; + +#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]; + unsigned long xmit_flags[1]; + + /* Force Feedback */ + wait_queue_head_t wait; + struct resource device_memory; + struct iforce_core_effect core_effects[IFORCE_EFFECTS_MAX]; + struct mutex 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); +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 *, struct ff_effect *); +int iforce_upload_constant(struct iforce *, struct ff_effect *, struct ff_effect *); +int iforce_upload_condition(struct iforce *, struct ff_effect *, struct ff_effect *); + +/* 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 0000000..2478289 --- /dev/null +++ b/drivers/input/joystick/interact.c @@ -0,0 +1,329 @@ +/* + * 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> +#include <linux/jiffies.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 = input_get_drvdata(dev); + + 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 = input_get_drvdata(dev); + + 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; + struct input_dev *input_dev; + __u32 data[3]; + int i, t; + int err; + + interact = kzalloc(sizeof(struct interact), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!interact || !input_dev) { + err = -ENOMEM; + goto fail1; + } + + interact->gameport = gameport; + interact->dev = input_dev; + + 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); + + snprintf(interact->phys, sizeof(interact->phys), "%s/input0", gameport->phys); + + interact->type = i; + interact->length = interact_type[i].length; + + input_dev->name = interact_type[i].name; + input_dev->phys = interact->phys; + input_dev->id.bustype = BUS_GAMEPORT; + input_dev->id.vendor = GAMEPORT_ID_VENDOR_INTERACT; + input_dev->id.product = interact_type[i].id; + input_dev->id.version = 0x0100; + input_dev->dev.parent = &gameport->dev; + + input_set_drvdata(input_dev, interact); + + input_dev->open = interact_open; + input_dev->close = interact_close; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + + for (i = 0; (t = interact_type[interact->type].abs[i]) >= 0; i++) { + set_bit(t, input_dev->absbit); + if (i < interact_type[interact->type].b8) { + input_dev->absmin[t] = 0; + input_dev->absmax[t] = 255; + } else { + input_dev->absmin[t] = -1; + input_dev->absmax[t] = 1; + } + } + + for (i = 0; (t = interact_type[interact->type].btn[i]) >= 0; i++) + set_bit(t, input_dev->keybit); + + err = input_register_device(interact->dev); + if (err) + goto fail2; + + return 0; + +fail2: gameport_close(gameport); +fail1: gameport_set_drvdata(gameport, NULL); + input_free_device(input_dev); + 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) +{ + return gameport_register_driver(&interact_drv); +} + +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 0000000..cd894a0 --- /dev/null +++ b/drivers/input/joystick/joydump.c @@ -0,0 +1,173 @@ +/* + * 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> +#include <linux/slab.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) +{ + return gameport_register_driver(&joydump_drv); +} + +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 0000000..40e4078 --- /dev/null +++ b/drivers/input/joystick/magellan.c @@ -0,0 +1,240 @@ +/* + * 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 }; + +/* + * 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 input_dev *dev = magellan->dev; + unsigned char *data = magellan->data; + int i, t; + + if (!magellan->idx) return; + + 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 magellan* magellan = serio_get_drvdata(serio); + + if (data == '\r') { + magellan_process_packet(magellan); + 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); + + serio_close(serio); + serio_set_drvdata(serio, NULL); + input_unregister_device(magellan->dev); + 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; + struct input_dev *input_dev; + int err = -ENOMEM; + int i; + + magellan = kzalloc(sizeof(struct magellan), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!magellan || !input_dev) + goto fail1; + + magellan->dev = input_dev; + snprintf(magellan->phys, sizeof(magellan->phys), "%s/input0", serio->phys); + + input_dev->name = "LogiCad3D Magellan / SpaceMouse"; + input_dev->phys = magellan->phys; + input_dev->id.bustype = BUS_RS232; + input_dev->id.vendor = SERIO_MAGELLAN; + input_dev->id.product = 0x0001; + input_dev->id.version = 0x0100; + input_dev->dev.parent = &serio->dev; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + + for (i = 0; i < 9; i++) + set_bit(magellan_buttons[i], input_dev->keybit); + + for (i = 0; i < 6; i++) + input_set_abs_params(input_dev, magellan_axes[i], -360, 360, 0, 0); + + serio_set_drvdata(serio, magellan); + + err = serio_open(serio, drv); + if (err) + goto fail2; + + err = input_register_device(magellan->dev); + if (err) + goto fail3; + + return 0; + + fail3: serio_close(serio); + fail2: serio_set_drvdata(serio, NULL); + fail1: input_free_device(input_dev); + kfree(magellan); + return err; +} + +/* + * 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) +{ + return serio_register_driver(&magellan_drv); +} + +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 0000000..ca13a6b --- /dev/null +++ b/drivers/input/joystick/sidewinder.c @@ -0,0 +1,830 @@ +/* + * 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> +#include <linux/jiffies.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; + + switch (sw->type) { + + case SW_ID_3DP: + + if (sw_check(GB(0,64)) || (hat = (GB(6,1) << 3) | GB(60,3)) > 8) + return -1; + + dev = sw->dev[0]; + + 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(sw->dev[i], ABS_X, GB(i*15+3,1) - GB(i*15+2,1)); + input_report_abs(sw->dev[i], ABS_Y, GB(i*15+0,1) - GB(i*15+1,1)); + + for (j = 0; j < 10; j++) + input_report_key(sw->dev[i], sw_btn[SW_ID_GP][j], !GB(i*15+j+4,1)); + + input_sync(sw->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; + + dev = sw->dev[0]; + 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; + + dev = sw->dev[0]; + 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; + + dev = sw->dev[0]; + 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 = input_get_drvdata(dev); + + gameport_start_polling(sw->gameport); + return 0; +} + +static void sw_close(struct input_dev *dev) +{ + struct sw *sw = input_get_drvdata(dev); + + 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, size_t size) +{ + 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; + + snprintf(comment, size, " [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; + struct input_dev *input_dev; + int i, j, k, l; + int err = 0; + unsigned char *buf = NULL; /* [SW_LENGTH] */ + unsigned char *idbuf = NULL; /* [SW_LENGTH] */ + unsigned char m = 1; + char comment[40]; + + comment[0] = 0; + + sw = kzalloc(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, sizeof(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; + + snprintf(sw->name, sizeof(sw->name), + "Microsoft SideWinder %s", sw_name[sw->type]); + snprintf(sw->phys[i], sizeof(sw->phys[i]), + "%s/input%d", gameport->phys, i); + + sw->dev[i] = input_dev = input_allocate_device(); + if (!input_dev) { + err = -ENOMEM; + goto fail3; + } + + input_dev->name = sw->name; + input_dev->phys = sw->phys[i]; + input_dev->id.bustype = BUS_GAMEPORT; + input_dev->id.vendor = GAMEPORT_ID_VENDOR_MICROSOFT; + input_dev->id.product = sw->type; + input_dev->id.version = 0x0100; + input_dev->dev.parent = &gameport->dev; + + input_set_drvdata(input_dev, sw); + + input_dev->open = sw_open; + input_dev->close = sw_close; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + + for (j = 0; (bits = sw_bit[sw->type][j]); j++) { + code = sw_abs[sw->type][j]; + set_bit(code, input_dev->absbit); + input_dev->absmax[code] = (1 << bits) - 1; + input_dev->absmin[code] = (bits == 1) ? -1 : 0; + input_dev->absfuzz[code] = ((bits >> 1) >= 2) ? (1 << ((bits >> 1) - 2)) : 0; + if (code != ABS_THROTTLE) + input_dev->absflat[code] = (bits >= 5) ? (1 << (bits - 5)) : 0; + } + + for (j = 0; (code = sw_btn[sw->type][j]); j++) + set_bit(code, input_dev->keybit); + + dbg("%s%s [%d-bit id %d data %d]\n", sw->name, comment, m, l, k); + + err = input_register_device(sw->dev[i]); + if (err) + goto fail4; + } + + out: kfree(buf); + kfree(idbuf); + + return err; + + fail4: input_free_device(sw->dev[i]); + fail3: while (--i >= 0) + input_unregister_device(sw->dev[i]); + fail2: gameport_close(gameport); + fail1: gameport_set_drvdata(gameport, NULL); + kfree(sw); + goto out; +} + +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", + .owner = THIS_MODULE, + }, + .description = DRIVER_DESC, + .connect = sw_connect, + .disconnect = sw_disconnect, +}; + +static int __init sw_init(void) +{ + return gameport_register_driver(&sw_drv); +} + +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 0000000..0cd9b29 --- /dev/null +++ b/drivers/input/joystick/spaceball.c @@ -0,0 +1,314 @@ +/* + * 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 9 + +#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; + 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 input_dev *dev = spaceball->dev; + unsigned char *data = spaceball->data; + int i; + + if (spaceball->idx < 2) return; + + 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 spaceball *spaceball = serio_get_drvdata(serio); + + switch (data) { + case 0xd: + spaceball_process_packet(spaceball); + 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); + + serio_close(serio); + serio_set_drvdata(serio, NULL); + input_unregister_device(spaceball->dev); + 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; + struct input_dev *input_dev; + int err = -ENOMEM; + int i, id; + + if ((id = serio->id.id) > SPACEBALL_MAX_ID) + return -ENODEV; + + spaceball = kmalloc(sizeof(struct spaceball), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!spaceball || !input_dev) + goto fail1; + + spaceball->dev = input_dev; + snprintf(spaceball->phys, sizeof(spaceball->phys), "%s/input0", serio->phys); + + input_dev->name = spaceball_names[id]; + input_dev->phys = spaceball->phys; + input_dev->id.bustype = BUS_RS232; + input_dev->id.vendor = SERIO_SPACEBALL; + input_dev->id.product = id; + input_dev->id.version = 0x0100; + input_dev->dev.parent = &serio->dev; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + + switch (id) { + case SPACEBALL_4000FLX: + case SPACEBALL_4000FLX_L: + input_dev->keybit[BIT_WORD(BTN_0)] |= BIT_MASK(BTN_9); + input_dev->keybit[BIT_WORD(BTN_A)] |= BIT_MASK(BTN_A) | + BIT_MASK(BTN_B) | BIT_MASK(BTN_C) | + BIT_MASK(BTN_MODE); + default: + input_dev->keybit[BIT_WORD(BTN_0)] |= BIT_MASK(BTN_2) | + BIT_MASK(BTN_3) | BIT_MASK(BTN_4) | + BIT_MASK(BTN_5) | BIT_MASK(BTN_6) | + BIT_MASK(BTN_7) | BIT_MASK(BTN_8); + case SPACEBALL_3003C: + input_dev->keybit[BIT_WORD(BTN_0)] |= BIT_MASK(BTN_1) | + BIT_MASK(BTN_8); + } + + for (i = 0; i < 3; i++) { + input_set_abs_params(input_dev, ABS_X + i, -8000, 8000, 8, 40); + input_set_abs_params(input_dev, ABS_RX + i, -1600, 1600, 2, 8); + } + + serio_set_drvdata(serio, spaceball); + + err = serio_open(serio, drv); + if (err) + goto fail2; + + err = input_register_device(spaceball->dev); + if (err) + goto fail3; + + return 0; + + fail3: serio_close(serio); + fail2: serio_set_drvdata(serio, NULL); + fail1: input_free_device(input_dev); + kfree(spaceball); + return err; +} + +/* + * 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) +{ + return serio_register_driver(&spaceball_drv); +} + +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 0000000..a694bf8 --- /dev/null +++ b/drivers/input/joystick/spaceorb.c @@ -0,0 +1,255 @@ +/* + * 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 }; + +/* + * Per-Orb data. + */ + +struct spaceorb { + struct input_dev *dev; + 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 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; + + 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] is %s\n", + dev->name, spaceorb->data + i, spaceorb->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 < 6; 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 "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 spaceorb* spaceorb = serio_get_drvdata(serio); + + if (~data & 0x80) { + if (spaceorb->idx) spaceorb_process_packet(spaceorb); + 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); + + serio_close(serio); + serio_set_drvdata(serio, NULL); + input_unregister_device(spaceorb->dev); + 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; + struct input_dev *input_dev; + int err = -ENOMEM; + int i; + + spaceorb = kzalloc(sizeof(struct spaceorb), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!spaceorb || !input_dev) + goto fail1; + + spaceorb->dev = input_dev; + snprintf(spaceorb->phys, sizeof(spaceorb->phys), "%s/input0", serio->phys); + + input_dev->name = "SpaceTec SpaceOrb 360 / Avenger"; + input_dev->phys = spaceorb->phys; + input_dev->id.bustype = BUS_RS232; + input_dev->id.vendor = SERIO_SPACEORB; + input_dev->id.product = 0x0001; + input_dev->id.version = 0x0100; + input_dev->dev.parent = &serio->dev; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + + for (i = 0; i < 6; i++) + set_bit(spaceorb_buttons[i], input_dev->keybit); + + for (i = 0; i < 6; i++) + input_set_abs_params(input_dev, spaceorb_axes[i], -508, 508, 0, 0); + + serio_set_drvdata(serio, spaceorb); + + err = serio_open(serio, drv); + if (err) + goto fail2; + + err = input_register_device(spaceorb->dev); + if (err) + goto fail3; + + return 0; + + fail3: serio_close(serio); + fail2: serio_set_drvdata(serio, NULL); + fail1: input_free_device(input_dev); + kfree(spaceorb); + return err; +} + +/* + * 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) +{ + return serio_register_driver(&spaceorb_drv); +} + +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 0000000..e0db9f5 --- /dev/null +++ b/drivers/input/joystick/stinger.c @@ -0,0 +1,226 @@ +/* + * 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 + +/* + * 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 input_dev *dev = stinger->dev; + unsigned char *data = stinger->data; + + if (!stinger->idx) return; + + 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 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); + 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); + + serio_close(serio); + serio_set_drvdata(serio, NULL); + input_unregister_device(stinger->dev); + 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; + struct input_dev *input_dev; + int err = -ENOMEM; + + stinger = kmalloc(sizeof(struct stinger), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!stinger || !input_dev) + goto fail1; + + stinger->dev = input_dev; + snprintf(stinger->phys, sizeof(stinger->phys), "%s/serio0", serio->phys); + + input_dev->name = "Gravis Stinger"; + input_dev->phys = stinger->phys; + input_dev->id.bustype = BUS_RS232; + input_dev->id.vendor = SERIO_STINGER; + input_dev->id.product = 0x0001; + input_dev->id.version = 0x0100; + input_dev->dev.parent = &serio->dev; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + input_dev->keybit[BIT_WORD(BTN_A)] = BIT_MASK(BTN_A) | BIT_MASK(BTN_B) | + BIT_MASK(BTN_C) | BIT_MASK(BTN_X) | BIT_MASK(BTN_Y) | + BIT_MASK(BTN_Z) | BIT_MASK(BTN_TL) | BIT_MASK(BTN_TR) | + BIT_MASK(BTN_START) | BIT_MASK(BTN_SELECT); + input_set_abs_params(input_dev, ABS_X, -64, 64, 0, 4); + input_set_abs_params(input_dev, ABS_Y, -64, 64, 0, 4); + + serio_set_drvdata(serio, stinger); + + err = serio_open(serio, drv); + if (err) + goto fail2; + + err = input_register_device(stinger->dev); + if (err) + goto fail3; + + return 0; + + fail3: serio_close(serio); + fail2: serio_set_drvdata(serio, NULL); + fail1: input_free_device(input_dev); + kfree(stinger); + return err; +} + +/* + * 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) +{ + return serio_register_driver(&stinger_drv); +} + +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 0000000..d6c6098 --- /dev/null +++ b/drivers/input/joystick/tmdc.c @@ -0,0 +1,450 @@ +/* + * 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> +#include <linux/jiffies.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 const unsigned char tmdc_byte_a[16] = { 0, 1, 3, 4, 6, 7 }; +static const unsigned char tmdc_byte_d[16] = { 2, 5, 8, 9 }; + +static const signed char tmdc_abs[TMDC_ABS] = + { ABS_X, ABS_Y, ABS_RUDDER, ABS_THROTTLE, ABS_RX, ABS_RY, ABS_RZ }; +static const signed char tmdc_abs_hat[TMDC_ABS_HAT] = + { ABS_HAT0X, ABS_HAT0Y, ABS_HAT1X, ABS_HAT1Y }; +static const signed char tmdc_abs_at[TMDC_ABS] = + { ABS_X, ABS_Y, ABS_RUDDER, -1, ABS_THROTTLE }; +static const signed char tmdc_abs_fm[TMDC_ABS] = + { ABS_RX, ABS_RY, ABS_X, ABS_Y }; + +static const 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 const 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 const 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 const 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 const struct { + int x; + int y; +} tmdc_hat_to_axis[] = {{ 0, 0}, { 1, 0}, { 0,-1}, {-1, 0}, { 0, 1}}; + +static const struct tmdc_model { + unsigned char id; + const char *name; + char abs; + char hats; + char btnc[4]; + char btno[4]; + const signed char *axes; + const short *buttons; +} tmdc_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 } +}; + + +struct tmdc_port { + struct input_dev *dev; + char name[64]; + char phys[32]; + int mode; + const signed char *abs; + const short *btn; + unsigned char absc; + unsigned char btnc[4]; + unsigned char btno[4]; +}; + +struct tmdc { + struct gameport *gameport; + struct tmdc_port *port[2]; +#if 0 + 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]; +#endif + 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); +} + +static int tmdc_parse_packet(struct tmdc_port *port, unsigned char *data) +{ + int i, k, l; + + if (data[TMDC_BYTE_ID] != port->mode) + return -1; + + for (i = 0; i < port->absc; i++) { + if (port->abs[i] < 0) + return 0; + + input_report_abs(port->dev, port->abs[i], data[tmdc_byte_a[i]]); + } + + switch (port->mode) { + + case TMDC_MODE_M3DI: + + i = tmdc_byte_d[0]; + input_report_abs(port->dev, ABS_HAT0X, ((data[i] >> 3) & 1) - ((data[i] >> 1) & 1)); + input_report_abs(port->dev, ABS_HAT0Y, ((data[i] >> 2) & 1) - ( data[i] & 1)); + break; + + case TMDC_MODE_AT: + + i = tmdc_byte_a[3]; + input_report_abs(port->dev, ABS_HAT0X, tmdc_hat_to_axis[(data[i] - 141) / 25].x); + input_report_abs(port->dev, ABS_HAT0Y, tmdc_hat_to_axis[(data[i] - 141) / 25].y); + break; + + } + + for (k = l = 0; k < 4; k++) { + for (i = 0; i < port->btnc[k]; i++) + input_report_key(port->dev, port->btn[i + l], + ((data[tmdc_byte_d[k]] >> (i + port->btno[k])) & 1)); + l += port->btnc[k]; + } + + input_sync(port->dev); + + return 0; +} + +/* + * 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); + unsigned char r, bad = 0; + int i; + + tmdc->reads++; + + if ((r = tmdc_read_packet(tmdc->gameport, data)) != tmdc->exists) + bad = 1; + else { + for (i = 0; i < 2; i++) { + if (r & (1 << i) & tmdc->exists) { + + if (tmdc_parse_packet(tmdc->port[i], data[i])) + bad = 1; + } + } + } + + tmdc->bads += bad; +} + +static int tmdc_open(struct input_dev *dev) +{ + struct tmdc *tmdc = input_get_drvdata(dev); + + gameport_start_polling(tmdc->gameport); + return 0; +} + +static void tmdc_close(struct input_dev *dev) +{ + struct tmdc *tmdc = input_get_drvdata(dev); + + gameport_stop_polling(tmdc->gameport); +} + +static int tmdc_setup_port(struct tmdc *tmdc, int idx, unsigned char *data) +{ + const struct tmdc_model *model; + struct tmdc_port *port; + struct input_dev *input_dev; + int i, j, b = 0; + int err; + + tmdc->port[idx] = port = kzalloc(sizeof (struct tmdc_port), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!port || !input_dev) { + err = -ENOMEM; + goto fail; + } + + port->mode = data[TMDC_BYTE_ID]; + + for (model = tmdc_models; model->id && model->id != port->mode; model++) + /* empty */; + + port->abs = model->axes; + port->btn = model->buttons; + + if (!model->id) { + port->absc = data[TMDC_BYTE_DEF] >> 4; + for (i = 0; i < 4; i++) + port->btnc[i] = i < (data[TMDC_BYTE_DEF] & 0xf) ? 8 : 0; + } else { + port->absc = model->abs; + for (i = 0; i < 4; i++) + port->btnc[i] = model->btnc[i]; + } + + for (i = 0; i < 4; i++) + port->btno[i] = model->btno[i]; + + snprintf(port->name, sizeof(port->name), model->name, + port->absc, (data[TMDC_BYTE_DEF] & 0xf) << 3, port->mode); + snprintf(port->phys, sizeof(port->phys), "%s/input%d", tmdc->gameport->phys, i); + + port->dev = input_dev; + + input_dev->name = port->name; + input_dev->phys = port->phys; + input_dev->id.bustype = BUS_GAMEPORT; + input_dev->id.vendor = GAMEPORT_ID_VENDOR_THRUSTMASTER; + input_dev->id.product = model->id; + input_dev->id.version = 0x0100; + input_dev->dev.parent = &tmdc->gameport->dev; + + input_set_drvdata(input_dev, tmdc); + + input_dev->open = tmdc_open; + input_dev->close = tmdc_close; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + + for (i = 0; i < port->absc && i < TMDC_ABS; i++) + if (port->abs[i] >= 0) + input_set_abs_params(input_dev, port->abs[i], 8, 248, 2, 4); + + for (i = 0; i < model->hats && i < TMDC_ABS_HAT; i++) + input_set_abs_params(input_dev, tmdc_abs_hat[i], -1, 1, 0, 0); + + for (i = 0; i < 4; i++) { + for (j = 0; j < port->btnc[i] && j < TMDC_BTN; j++) + set_bit(port->btn[j + b], input_dev->keybit); + b += port->btnc[i]; + } + + err = input_register_device(port->dev); + if (err) + goto fail; + + return 0; + + fail: input_free_device(input_dev); + kfree(port); + return err; +} + +/* + * tmdc_probe() probes for ThrustMaster type joysticks. + */ + +static int tmdc_connect(struct gameport *gameport, struct gameport_driver *drv) +{ + unsigned char data[2][TMDC_MAX_LENGTH]; + struct tmdc *tmdc; + int i; + int err; + + if (!(tmdc = kzalloc(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 (i = 0; i < 2; i++) { + if (tmdc->exists & (1 << i)) { + + err = tmdc_setup_port(tmdc, i, data[i]); + if (err) + goto fail3; + } + } + + return 0; + + fail3: while (--i >= 0) { + if (tmdc->port[i]) { + input_unregister_device(tmdc->port[i]->dev); + kfree(tmdc->port[i]); + } + } + 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->port[i]) { + input_unregister_device(tmdc->port[i]->dev); + kfree(tmdc->port[i]); + } + } + gameport_close(gameport); + gameport_set_drvdata(gameport, NULL); + kfree(tmdc); +} + +static struct gameport_driver tmdc_drv = { + .driver = { + .name = "tmdc", + .owner = THIS_MODULE, + }, + .description = DRIVER_DESC, + .connect = tmdc_connect, + .disconnect = tmdc_disconnect, +}; + +static int __init tmdc_init(void) +{ + return gameport_register_driver(&tmdc_drv); +} + +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 0000000..b6f8598 --- /dev/null +++ b/drivers/input/joystick/turbografx.c @@ -0,0 +1,323 @@ +/* + * 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/init.h> +#include <linux/mutex.h> + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION("TurboGraFX parallel port interface driver"); +MODULE_LICENSE("GPL"); + +#define TGFX_MAX_PORTS 3 +#define TGFX_MAX_DEVICES 7 + +struct tgfx_config { + int args[TGFX_MAX_DEVICES + 1]; + unsigned int nargs; +}; + +static struct tgfx_config tgfx_cfg[TGFX_MAX_PORTS] __initdata; + +module_param_array_named(map, tgfx_cfg[0].args, int, &tgfx_cfg[0].nargs, 0); +MODULE_PARM_DESC(map, "Describes first set of devices (<parport#>,<js1>,<js2>,..<js7>"); +module_param_array_named(map2, tgfx_cfg[1].args, int, &tgfx_cfg[1].nargs, 0); +MODULE_PARM_DESC(map2, "Describes second set of devices"); +module_param_array_named(map3, tgfx_cfg[2].args, int, &tgfx_cfg[2].nargs, 0); +MODULE_PARM_DESC(map3, "Describes third set of devices"); + +#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 struct tgfx { + struct pardevice *pd; + struct timer_list timer; + struct input_dev *dev[TGFX_MAX_DEVICES]; + char name[TGFX_MAX_DEVICES][64]; + char phys[TGFX_MAX_DEVICES][32]; + int sticks; + int used; + struct mutex sem; +} *tgfx_base[TGFX_MAX_PORTS]; + +/* + * 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 = input_get_drvdata(dev); + int err; + + err = mutex_lock_interruptible(&tgfx->sem); + if (err) + return err; + + if (!tgfx->used++) { + parport_claim(tgfx->pd); + parport_write_control(tgfx->pd->port, 0x04); + mod_timer(&tgfx->timer, jiffies + TGFX_REFRESH_TIME); + } + + mutex_unlock(&tgfx->sem); + return 0; +} + +static void tgfx_close(struct input_dev *dev) +{ + struct tgfx *tgfx = input_get_drvdata(dev); + + mutex_lock(&tgfx->sem); + if (!--tgfx->used) { + del_timer_sync(&tgfx->timer); + parport_write_control(tgfx->pd->port, 0x00); + parport_release(tgfx->pd); + } + mutex_unlock(&tgfx->sem); +} + + + +/* + * tgfx_probe() probes for tg gamepads. + */ + +static struct tgfx __init *tgfx_probe(int parport, int *n_buttons, int n_devs) +{ + struct tgfx *tgfx; + struct input_dev *input_dev; + struct parport *pp; + struct pardevice *pd; + int i, j; + int err; + + pp = parport_find_number(parport); + if (!pp) { + printk(KERN_ERR "turbografx.c: no such parport\n"); + err = -EINVAL; + goto err_out; + } + + pd = parport_register_device(pp, "turbografx", NULL, NULL, NULL, PARPORT_DEV_EXCL, NULL); + if (!pd) { + printk(KERN_ERR "turbografx.c: parport busy already - lp.o loaded?\n"); + err = -EBUSY; + goto err_put_pp; + } + + tgfx = kzalloc(sizeof(struct tgfx), GFP_KERNEL); + if (!tgfx) { + printk(KERN_ERR "turbografx.c: Not enough memory\n"); + err = -ENOMEM; + goto err_unreg_pardev; + } + + mutex_init(&tgfx->sem); + tgfx->pd = pd; + init_timer(&tgfx->timer); + tgfx->timer.data = (long) tgfx; + tgfx->timer.function = tgfx_timer; + + for (i = 0; i < n_devs; i++) { + if (n_buttons[i] < 1) + continue; + + if (n_buttons[i] > 6) { + printk(KERN_ERR "turbografx.c: Invalid number of buttons %d\n", n_buttons[i]); + err = -EINVAL; + goto err_unreg_devs; + } + + tgfx->dev[i] = input_dev = input_allocate_device(); + if (!input_dev) { + printk(KERN_ERR "turbografx.c: Not enough memory for input device\n"); + err = -ENOMEM; + goto err_unreg_devs; + } + + tgfx->sticks |= (1 << i); + snprintf(tgfx->name[i], sizeof(tgfx->name[i]), + "TurboGraFX %d-button Multisystem joystick", n_buttons[i]); + snprintf(tgfx->phys[i], sizeof(tgfx->phys[i]), + "%s/input%d", tgfx->pd->port->name, i); + + input_dev->name = tgfx->name[i]; + input_dev->phys = tgfx->phys[i]; + input_dev->id.bustype = BUS_PARPORT; + input_dev->id.vendor = 0x0003; + input_dev->id.product = n_buttons[i]; + input_dev->id.version = 0x0100; + + input_set_drvdata(input_dev, tgfx); + + input_dev->open = tgfx_open; + input_dev->close = tgfx_close; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + input_set_abs_params(input_dev, ABS_X, -1, 1, 0, 0); + input_set_abs_params(input_dev, ABS_Y, -1, 1, 0, 0); + + for (j = 0; j < n_buttons[i]; j++) + set_bit(tgfx_buttons[j], input_dev->keybit); + + err = input_register_device(tgfx->dev[i]); + if (err) + goto err_free_dev; + } + + if (!tgfx->sticks) { + printk(KERN_ERR "turbografx.c: No valid devices specified\n"); + err = -EINVAL; + goto err_free_tgfx; + } + + return tgfx; + + err_free_dev: + input_free_device(tgfx->dev[i]); + err_unreg_devs: + while (--i >= 0) + if (tgfx->dev[i]) + input_unregister_device(tgfx->dev[i]); + err_free_tgfx: + kfree(tgfx); + err_unreg_pardev: + parport_unregister_device(pd); + err_put_pp: + parport_put_port(pp); + err_out: + return ERR_PTR(err); +} + +static void tgfx_remove(struct tgfx *tgfx) +{ + int i; + + for (i = 0; i < TGFX_MAX_DEVICES; i++) + if (tgfx->dev[i]) + input_unregister_device(tgfx->dev[i]); + parport_unregister_device(tgfx->pd); + kfree(tgfx); +} + +static int __init tgfx_init(void) +{ + int i; + int have_dev = 0; + int err = 0; + + for (i = 0; i < TGFX_MAX_PORTS; i++) { + if (tgfx_cfg[i].nargs == 0 || tgfx_cfg[i].args[0] < 0) + continue; + + if (tgfx_cfg[i].nargs < 2) { + printk(KERN_ERR "turbografx.c: at least one joystick must be specified\n"); + err = -EINVAL; + break; + } + + tgfx_base[i] = tgfx_probe(tgfx_cfg[i].args[0], + tgfx_cfg[i].args + 1, + tgfx_cfg[i].nargs - 1); + if (IS_ERR(tgfx_base[i])) { + err = PTR_ERR(tgfx_base[i]); + break; + } + + have_dev = 1; + } + + if (err) { + while (--i >= 0) + if (tgfx_base[i]) + tgfx_remove(tgfx_base[i]); + return err; + } + + return have_dev ? 0 : -ENODEV; +} + +static void __exit tgfx_exit(void) +{ + int i; + + for (i = 0; i < TGFX_MAX_PORTS; i++) + if (tgfx_base[i]) + tgfx_remove(tgfx_base[i]); +} + +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 0000000..3f4ec73 --- /dev/null +++ b/drivers/input/joystick/twidjoy.c @@ -0,0 +1,275 @@ +/* + * 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 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 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); + + 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); +} + +/* + * 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 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); + 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); + + serio_close(serio); + serio_set_drvdata(serio, NULL); + input_unregister_device(twidjoy->dev); + 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; + struct input_dev *input_dev; + int err = -ENOMEM; + int i; + + twidjoy = kzalloc(sizeof(struct twidjoy), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!twidjoy || !input_dev) + goto fail1; + + twidjoy->dev = input_dev; + snprintf(twidjoy->phys, sizeof(twidjoy->phys), "%s/input0", serio->phys); + + input_dev->name = "Handykey Twiddler"; + input_dev->phys = twidjoy->phys; + input_dev->id.bustype = BUS_RS232; + input_dev->id.vendor = SERIO_TWIDJOY; + input_dev->id.product = 0x0001; + input_dev->id.version = 0x0100; + input_dev->dev.parent = &serio->dev; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + input_set_abs_params(input_dev, ABS_X, -50, 50, 4, 4); + input_set_abs_params(input_dev, ABS_Y, -50, 50, 4, 4); + + for (bp = twidjoy_buttons; bp->bitmask; bp++) + for (i = 0; i < bp->bitmask; i++) + set_bit(bp->buttons[i], input_dev->keybit); + + serio_set_drvdata(serio, twidjoy); + + err = serio_open(serio, drv); + if (err) + goto fail2; + + err = input_register_device(twidjoy->dev); + if (err) + goto fail3; + + return 0; + + fail3: serio_close(serio); + fail2: serio_set_drvdata(serio, NULL); + fail1: input_free_device(input_dev); + kfree(twidjoy); + return err; +} + +/* + * 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. + */ + +static int __init twidjoy_init(void) +{ + return serio_register_driver(&twidjoy_drv); +} + +static 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 0000000..f72c83e --- /dev/null +++ b/drivers/input/joystick/warrior.c @@ -0,0 +1,235 @@ +/* + * 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 }; + +/* + * 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 input_dev *dev = warrior->dev; + unsigned char *data = warrior->data; + + if (!warrior->idx) return; + + 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 warrior *warrior = serio_get_drvdata(serio); + + if (data & 0x80) { + if (warrior->idx) warrior_process_packet(warrior); + 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); + 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); + + serio_close(serio); + serio_set_drvdata(serio, NULL); + input_unregister_device(warrior->dev); + 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; + struct input_dev *input_dev; + int err = -ENOMEM; + + warrior = kzalloc(sizeof(struct warrior), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!warrior || !input_dev) + goto fail1; + + warrior->dev = input_dev; + snprintf(warrior->phys, sizeof(warrior->phys), "%s/input0", serio->phys); + + input_dev->name = "Logitech WingMan Warrior"; + input_dev->phys = warrior->phys; + input_dev->id.bustype = BUS_RS232; + input_dev->id.vendor = SERIO_WARRIOR; + input_dev->id.product = 0x0001; + input_dev->id.version = 0x0100; + input_dev->dev.parent = &serio->dev; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REL) | + BIT_MASK(EV_ABS); + input_dev->keybit[BIT_WORD(BTN_TRIGGER)] = BIT_MASK(BTN_TRIGGER) | + BIT_MASK(BTN_THUMB) | BIT_MASK(BTN_TOP) | BIT_MASK(BTN_TOP2); + input_dev->relbit[0] = BIT_MASK(REL_DIAL); + input_set_abs_params(input_dev, ABS_X, -64, 64, 0, 8); + input_set_abs_params(input_dev, ABS_Y, -64, 64, 0, 8); + input_set_abs_params(input_dev, ABS_THROTTLE, -112, 112, 0, 0); + input_set_abs_params(input_dev, ABS_HAT0X, -1, 1, 0, 0); + input_set_abs_params(input_dev, ABS_HAT0Y, -1, 1, 0, 0); + + serio_set_drvdata(serio, warrior); + + err = serio_open(serio, drv); + if (err) + goto fail2; + + err = input_register_device(warrior->dev); + if (err) + goto fail3; + + return 0; + + fail3: serio_close(serio); + fail2: serio_set_drvdata(serio, NULL); + fail1: input_free_device(input_dev); + kfree(warrior); + return err; +} + +/* + * 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) +{ + return serio_register_driver(&warrior_drv); +} + +static void __exit warrior_exit(void) +{ + serio_unregister_driver(&warrior_drv); +} + +module_init(warrior_init); +module_exit(warrior_exit); diff --git a/drivers/input/joystick/xpad.c b/drivers/input/joystick/xpad.c new file mode 100644 index 0000000..b868b8d --- /dev/null +++ b/drivers/input/joystick/xpad.c @@ -0,0 +1,928 @@ +/* + * X-Box gamepad driver + * + * Copyright (c) 2002 Marko Friedemann <mfr@bmx-chemnitz.de> + * 2004 Oliver Schwartz <Oliver.Schwartz@gmx.de>, + * Steven Toth <steve@toth.demon.co.uk>, + * Franz Lehner <franz@caos.at>, + * Ivan Hawkes <blackhawk@ivanhawkes.com> + * 2005 Dominic Cerquetti <binary1230@yahoo.com> + * 2006 Adam Buchbinder <adam.buchbinder@gmail.com> + * 2007 Jan Kratochvil <honza@jikos.cz> + * + * 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 + * + * + * This driver is based on: + * - information from http://euc.jp/periphs/xbox-controller.ja.html + * - the iForce driver drivers/char/joystick/iforce.c + * - the skeleton-driver drivers/usb/usb-skeleton.c + * - Xbox 360 information http://www.free60.org/wiki/Gamepad + * + * Thanks to: + * - ITO Takayuki for providing essential xpad information on his website + * - Vojtech Pavlik - iforce driver / input subsystem + * - Greg Kroah-Hartman - usb-skeleton driver + * - XBOX Linux project - extra USB id's + * + * TODO: + * - fine tune axes (especially trigger axes) + * - fix "analog" buttons (reported as digital now) + * - get rumble working + * - need USB IDs for other dance pads + * + * History: + * + * 2002-06-27 - 0.0.1 : first version, just said "XBOX HID controller" + * + * 2002-07-02 - 0.0.2 : basic working version + * - all axes and 9 of the 10 buttons work (german InterAct device) + * - the black button does not work + * + * 2002-07-14 - 0.0.3 : rework by Vojtech Pavlik + * - indentation fixes + * - usb + input init sequence fixes + * + * 2002-07-16 - 0.0.4 : minor changes, merge with Vojtech's v0.0.3 + * - verified the lack of HID and report descriptors + * - verified that ALL buttons WORK + * - fixed d-pad to axes mapping + * + * 2002-07-17 - 0.0.5 : simplified d-pad handling + * + * 2004-10-02 - 0.0.6 : DDR pad support + * - borrowed from the XBOX linux kernel + * - USB id's for commonly used dance pads are present + * - dance pads will map D-PAD to buttons, not axes + * - pass the module paramater 'dpad_to_buttons' to force + * the D-PAD to map to buttons if your pad is not detected + * + * Later changes can be tracked in SCM. + */ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/stat.h> +#include <linux/module.h> +#include <linux/usb/input.h> + +#define DRIVER_AUTHOR "Marko Friedemann <mfr@bmx-chemnitz.de>" +#define DRIVER_DESC "X-Box pad driver" + +#define XPAD_PKT_LEN 32 + +/* xbox d-pads should map to buttons, as is required for DDR pads + but we map them to axes when possible to simplify things */ +#define MAP_DPAD_TO_BUTTONS 0 +#define MAP_DPAD_TO_AXES 1 +#define MAP_DPAD_UNKNOWN 2 + +#define XTYPE_XBOX 0 +#define XTYPE_XBOX360 1 +#define XTYPE_XBOX360W 2 +#define XTYPE_UNKNOWN 3 + +static int dpad_to_buttons; +module_param(dpad_to_buttons, bool, S_IRUGO); +MODULE_PARM_DESC(dpad_to_buttons, "Map D-PAD to buttons rather than axes for unknown pads"); + +static const struct xpad_device { + u16 idVendor; + u16 idProduct; + char *name; + u8 dpad_mapping; + u8 xtype; +} xpad_device[] = { + { 0x045e, 0x0202, "Microsoft X-Box pad v1 (US)", MAP_DPAD_TO_AXES, XTYPE_XBOX }, + { 0x045e, 0x0289, "Microsoft X-Box pad v2 (US)", MAP_DPAD_TO_AXES, XTYPE_XBOX }, + { 0x045e, 0x0285, "Microsoft X-Box pad (Japan)", MAP_DPAD_TO_AXES, XTYPE_XBOX }, + { 0x045e, 0x0287, "Microsoft Xbox Controller S", MAP_DPAD_TO_AXES, XTYPE_XBOX }, + { 0x045e, 0x0719, "Xbox 360 Wireless Receiver", MAP_DPAD_TO_BUTTONS, XTYPE_XBOX360W }, + { 0x0c12, 0x8809, "RedOctane Xbox Dance Pad", MAP_DPAD_TO_BUTTONS, XTYPE_XBOX }, + { 0x044f, 0x0f07, "Thrustmaster, Inc. Controller", MAP_DPAD_TO_AXES, XTYPE_XBOX }, + { 0x046d, 0xc242, "Logitech Chillstream Controller", MAP_DPAD_TO_AXES, XTYPE_XBOX360 }, + { 0x046d, 0xca84, "Logitech Xbox Cordless Controller", MAP_DPAD_TO_AXES, XTYPE_XBOX }, + { 0x046d, 0xca88, "Logitech Compact Controller for Xbox", MAP_DPAD_TO_AXES, XTYPE_XBOX }, + { 0x05fd, 0x1007, "Mad Catz Controller (unverified)", MAP_DPAD_TO_AXES, XTYPE_XBOX }, + { 0x05fd, 0x107a, "InterAct 'PowerPad Pro' X-Box pad (Germany)", MAP_DPAD_TO_AXES, XTYPE_XBOX }, + { 0x0738, 0x4516, "Mad Catz Control Pad", MAP_DPAD_TO_AXES, XTYPE_XBOX }, + { 0x0738, 0x4522, "Mad Catz LumiCON", MAP_DPAD_TO_AXES, XTYPE_XBOX }, + { 0x0738, 0x4526, "Mad Catz Control Pad Pro", MAP_DPAD_TO_AXES, XTYPE_XBOX }, + { 0x0738, 0x4536, "Mad Catz MicroCON", MAP_DPAD_TO_AXES, XTYPE_XBOX }, + { 0x0738, 0x4540, "Mad Catz Beat Pad", MAP_DPAD_TO_BUTTONS, XTYPE_XBOX }, + { 0x0738, 0x4556, "Mad Catz Lynx Wireless Controller", MAP_DPAD_TO_AXES, XTYPE_XBOX }, + { 0x0738, 0x4716, "Mad Catz Wired Xbox 360 Controller", MAP_DPAD_TO_AXES, XTYPE_XBOX360 }, + { 0x0738, 0x6040, "Mad Catz Beat Pad Pro", MAP_DPAD_TO_BUTTONS, XTYPE_XBOX }, + { 0x0c12, 0x8802, "Zeroplus Xbox Controller", MAP_DPAD_TO_AXES, XTYPE_XBOX }, + { 0x0c12, 0x880a, "Pelican Eclipse PL-2023", MAP_DPAD_TO_AXES, XTYPE_XBOX }, + { 0x0c12, 0x8810, "Zeroplus Xbox Controller", MAP_DPAD_TO_AXES, XTYPE_XBOX }, + { 0x0c12, 0x9902, "HAMA VibraX - *FAULTY HARDWARE*", MAP_DPAD_TO_AXES, XTYPE_XBOX }, + { 0x0e4c, 0x1097, "Radica Gamester Controller", MAP_DPAD_TO_AXES, XTYPE_XBOX }, + { 0x0e4c, 0x2390, "Radica Games Jtech Controller", MAP_DPAD_TO_AXES, XTYPE_XBOX }, + { 0x0e6f, 0x0003, "Logic3 Freebird wireless Controller", MAP_DPAD_TO_AXES, XTYPE_XBOX }, + { 0x0e6f, 0x0005, "Eclipse wireless Controller", MAP_DPAD_TO_AXES, XTYPE_XBOX }, + { 0x0e6f, 0x0006, "Edge wireless Controller", MAP_DPAD_TO_AXES, XTYPE_XBOX }, + { 0x0e6f, 0x0006, "Pelican 'TSZ' Wired Xbox 360 Controller", MAP_DPAD_TO_AXES, XTYPE_XBOX360 }, + { 0x0e8f, 0x0201, "SmartJoy Frag Xpad/PS2 adaptor", MAP_DPAD_TO_AXES, XTYPE_XBOX }, + { 0x0f30, 0x0202, "Joytech Advanced Controller", MAP_DPAD_TO_AXES, XTYPE_XBOX }, + { 0x0f30, 0x8888, "BigBen XBMiniPad Controller", MAP_DPAD_TO_AXES, XTYPE_XBOX }, + { 0x102c, 0xff0c, "Joytech Wireless Advanced Controller", MAP_DPAD_TO_AXES, XTYPE_XBOX }, + { 0x12ab, 0x8809, "Xbox DDR dancepad", MAP_DPAD_TO_BUTTONS, XTYPE_XBOX }, + { 0x1430, 0x4748, "RedOctane Guitar Hero X-plorer", MAP_DPAD_TO_AXES, XTYPE_XBOX360 }, + { 0x1430, 0x8888, "TX6500+ Dance Pad (first generation)", MAP_DPAD_TO_BUTTONS, XTYPE_XBOX }, + { 0x045e, 0x028e, "Microsoft X-Box 360 pad", MAP_DPAD_TO_AXES, XTYPE_XBOX360 }, + { 0xffff, 0xffff, "Chinese-made Xbox Controller", MAP_DPAD_TO_AXES, XTYPE_XBOX }, + { 0x0000, 0x0000, "Generic X-Box pad", MAP_DPAD_UNKNOWN, XTYPE_UNKNOWN } +}; + +/* buttons shared with xbox and xbox360 */ +static const signed short xpad_common_btn[] = { + BTN_A, BTN_B, BTN_X, BTN_Y, /* "analog" buttons */ + BTN_START, BTN_BACK, BTN_THUMBL, BTN_THUMBR, /* start/back/sticks */ + -1 /* terminating entry */ +}; + +/* original xbox controllers only */ +static const signed short xpad_btn[] = { + BTN_C, BTN_Z, /* "analog" buttons */ + -1 /* terminating entry */ +}; + +/* only used if MAP_DPAD_TO_BUTTONS */ +static const signed short xpad_btn_pad[] = { + BTN_LEFT, BTN_RIGHT, /* d-pad left, right */ + BTN_0, BTN_1, /* d-pad up, down (XXX names??) */ + -1 /* terminating entry */ +}; + +static const signed short xpad360_btn[] = { /* buttons for x360 controller */ + BTN_TL, BTN_TR, /* Button LB/RB */ + BTN_MODE, /* The big X button */ + -1 +}; + +static const signed short xpad_abs[] = { + ABS_X, ABS_Y, /* left stick */ + ABS_RX, ABS_RY, /* right stick */ + ABS_Z, ABS_RZ, /* triggers left/right */ + -1 /* terminating entry */ +}; + +/* only used if MAP_DPAD_TO_AXES */ +static const signed short xpad_abs_pad[] = { + ABS_HAT0X, ABS_HAT0Y, /* d-pad axes */ + -1 /* terminating entry */ +}; + +/* Xbox 360 has a vendor-specific class, so we cannot match it with only + * USB_INTERFACE_INFO (also specifically refused by USB subsystem), so we + * match against vendor id as well. Wired Xbox 360 devices have protocol 1, + * wireless controllers have protocol 129. */ +#define XPAD_XBOX360_VENDOR_PROTOCOL(vend,pr) \ + .match_flags = USB_DEVICE_ID_MATCH_VENDOR | USB_DEVICE_ID_MATCH_INT_INFO, \ + .idVendor = (vend), \ + .bInterfaceClass = USB_CLASS_VENDOR_SPEC, \ + .bInterfaceSubClass = 93, \ + .bInterfaceProtocol = (pr) +#define XPAD_XBOX360_VENDOR(vend) \ + { XPAD_XBOX360_VENDOR_PROTOCOL(vend,1) }, \ + { XPAD_XBOX360_VENDOR_PROTOCOL(vend,129) } + +static struct usb_device_id xpad_table [] = { + { USB_INTERFACE_INFO('X', 'B', 0) }, /* X-Box USB-IF not approved class */ + XPAD_XBOX360_VENDOR(0x045e), /* Microsoft X-Box 360 controllers */ + XPAD_XBOX360_VENDOR(0x046d), /* Logitech X-Box 360 style controllers */ + XPAD_XBOX360_VENDOR(0x0738), /* Mad Catz X-Box 360 controllers */ + XPAD_XBOX360_VENDOR(0x0e6f), /* 0x0e6f X-Box 360 controllers */ + XPAD_XBOX360_VENDOR(0x1430), /* RedOctane X-Box 360 controllers */ + { } +}; + +MODULE_DEVICE_TABLE (usb, xpad_table); + +struct usb_xpad { + struct input_dev *dev; /* input device interface */ + struct usb_device *udev; /* usb device */ + + int pad_present; + + struct urb *irq_in; /* urb for interrupt in report */ + unsigned char *idata; /* input data */ + dma_addr_t idata_dma; + + struct urb *bulk_out; + unsigned char *bdata; + +#if defined(CONFIG_JOYSTICK_XPAD_FF) || defined(CONFIG_JOYSTICK_XPAD_LEDS) + struct urb *irq_out; /* urb for interrupt out report */ + unsigned char *odata; /* output data */ + dma_addr_t odata_dma; + struct mutex odata_mutex; +#endif + +#if defined(CONFIG_JOYSTICK_XPAD_LEDS) + struct xpad_led *led; +#endif + + char phys[64]; /* physical device path */ + + int dpad_mapping; /* map d-pad to buttons or to axes */ + int xtype; /* type of xbox device */ +}; + +/* + * xpad_process_packet + * + * Completes a request by converting the data into events for the + * input subsystem. + * + * The used report descriptor was taken from ITO Takayukis website: + * http://euc.jp/periphs/xbox-controller.ja.html + */ + +static void xpad_process_packet(struct usb_xpad *xpad, u16 cmd, unsigned char *data) +{ + struct input_dev *dev = xpad->dev; + + /* left stick */ + input_report_abs(dev, ABS_X, + (__s16) le16_to_cpup((__le16 *)(data + 12))); + input_report_abs(dev, ABS_Y, + ~(__s16) le16_to_cpup((__le16 *)(data + 14))); + + /* right stick */ + input_report_abs(dev, ABS_RX, + (__s16) le16_to_cpup((__le16 *)(data + 16))); + input_report_abs(dev, ABS_RY, + ~(__s16) le16_to_cpup((__le16 *)(data + 18))); + + /* triggers left/right */ + input_report_abs(dev, ABS_Z, data[10]); + input_report_abs(dev, ABS_RZ, data[11]); + + /* digital pad */ + if (xpad->dpad_mapping == MAP_DPAD_TO_AXES) { + input_report_abs(dev, ABS_HAT0X, + !!(data[2] & 0x08) - !!(data[2] & 0x04)); + input_report_abs(dev, ABS_HAT0Y, + !!(data[2] & 0x02) - !!(data[2] & 0x01)); + } else /* xpad->dpad_mapping == MAP_DPAD_TO_BUTTONS */ { + input_report_key(dev, BTN_LEFT, data[2] & 0x04); + input_report_key(dev, BTN_RIGHT, data[2] & 0x08); + input_report_key(dev, BTN_0, data[2] & 0x01); /* up */ + input_report_key(dev, BTN_1, data[2] & 0x02); /* down */ + } + + /* start/back buttons and stick press left/right */ + input_report_key(dev, BTN_START, data[2] & 0x10); + input_report_key(dev, BTN_BACK, data[2] & 0x20); + input_report_key(dev, BTN_THUMBL, data[2] & 0x40); + input_report_key(dev, BTN_THUMBR, data[2] & 0x80); + + /* "analog" buttons A, B, X, Y */ + input_report_key(dev, BTN_A, data[4]); + input_report_key(dev, BTN_B, data[5]); + input_report_key(dev, BTN_X, data[6]); + input_report_key(dev, BTN_Y, data[7]); + + /* "analog" buttons black, white */ + input_report_key(dev, BTN_C, data[8]); + input_report_key(dev, BTN_Z, data[9]); + + input_sync(dev); +} + +/* + * xpad360_process_packet + * + * Completes a request by converting the data into events for the + * input subsystem. It is version for xbox 360 controller + * + * The used report descriptor was taken from: + * http://www.free60.org/wiki/Gamepad + */ + +static void xpad360_process_packet(struct usb_xpad *xpad, + u16 cmd, unsigned char *data) +{ + struct input_dev *dev = xpad->dev; + + /* digital pad */ + if (xpad->dpad_mapping == MAP_DPAD_TO_AXES) { + input_report_abs(dev, ABS_HAT0X, + !!(data[2] & 0x08) - !!(data[2] & 0x04)); + input_report_abs(dev, ABS_HAT0Y, + !!(data[2] & 0x02) - !!(data[2] & 0x01)); + } else if (xpad->dpad_mapping == MAP_DPAD_TO_BUTTONS) { + /* dpad as buttons (right, left, down, up) */ + input_report_key(dev, BTN_LEFT, data[2] & 0x04); + input_report_key(dev, BTN_RIGHT, data[2] & 0x08); + input_report_key(dev, BTN_0, data[2] & 0x01); /* up */ + input_report_key(dev, BTN_1, data[2] & 0x02); /* down */ + } + + /* start/back buttons */ + input_report_key(dev, BTN_START, data[2] & 0x10); + input_report_key(dev, BTN_BACK, data[2] & 0x20); + + /* stick press left/right */ + input_report_key(dev, BTN_THUMBL, data[2] & 0x40); + input_report_key(dev, BTN_THUMBR, data[2] & 0x80); + + /* buttons A,B,X,Y,TL,TR and MODE */ + input_report_key(dev, BTN_A, data[3] & 0x10); + input_report_key(dev, BTN_B, data[3] & 0x20); + input_report_key(dev, BTN_X, data[3] & 0x40); + input_report_key(dev, BTN_Y, data[3] & 0x80); + input_report_key(dev, BTN_TL, data[3] & 0x01); + input_report_key(dev, BTN_TR, data[3] & 0x02); + input_report_key(dev, BTN_MODE, data[3] & 0x04); + + /* left stick */ + input_report_abs(dev, ABS_X, + (__s16) le16_to_cpup((__le16 *)(data + 6))); + input_report_abs(dev, ABS_Y, + ~(__s16) le16_to_cpup((__le16 *)(data + 8))); + + /* right stick */ + input_report_abs(dev, ABS_RX, + (__s16) le16_to_cpup((__le16 *)(data + 10))); + input_report_abs(dev, ABS_RY, + ~(__s16) le16_to_cpup((__le16 *)(data + 12))); + + /* triggers left/right */ + input_report_abs(dev, ABS_Z, data[4]); + input_report_abs(dev, ABS_RZ, data[5]); + + input_sync(dev); +} + +/* + * xpad360w_process_packet + * + * Completes a request by converting the data into events for the + * input subsystem. It is version for xbox 360 wireless controller. + * + * Byte.Bit + * 00.1 - Status change: The controller or headset has connected/disconnected + * Bits 01.7 and 01.6 are valid + * 01.7 - Controller present + * 01.6 - Headset present + * 01.1 - Pad state (Bytes 4+) valid + * + */ + +static void xpad360w_process_packet(struct usb_xpad *xpad, u16 cmd, unsigned char *data) +{ + /* Presence change */ + if (data[0] & 0x08) { + if (data[1] & 0x80) { + xpad->pad_present = 1; + usb_submit_urb(xpad->bulk_out, GFP_ATOMIC); + } else + xpad->pad_present = 0; + } + + /* Valid pad data */ + if (!(data[1] & 0x1)) + return; + + xpad360_process_packet(xpad, cmd, &data[4]); +} + +static void xpad_irq_in(struct urb *urb) +{ + struct usb_xpad *xpad = urb->context; + int retval, status; + + status = urb->status; + + switch (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", + __func__, status); + return; + default: + dbg("%s - nonzero urb status received: %d", + __func__, status); + goto exit; + } + + switch (xpad->xtype) { + case XTYPE_XBOX360: + xpad360_process_packet(xpad, 0, xpad->idata); + break; + case XTYPE_XBOX360W: + xpad360w_process_packet(xpad, 0, xpad->idata); + break; + default: + xpad_process_packet(xpad, 0, xpad->idata); + } + +exit: + retval = usb_submit_urb (urb, GFP_ATOMIC); + if (retval) + err ("%s - usb_submit_urb failed with result %d", + __func__, retval); +} + +static void xpad_bulk_out(struct urb *urb) +{ + 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", __func__, urb->status); + break; + default: + dbg("%s - nonzero urb status received: %d", __func__, urb->status); + } +} + +#if defined(CONFIG_JOYSTICK_XPAD_FF) || defined(CONFIG_JOYSTICK_XPAD_LEDS) +static void xpad_irq_out(struct urb *urb) +{ + int retval, status; + + status = urb->status; + + switch (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", + __func__, status); + return; + default: + dbg("%s - nonzero urb status received: %d", + __func__, status); + goto exit; + } + +exit: + retval = usb_submit_urb(urb, GFP_ATOMIC); + if (retval) + err("%s - usb_submit_urb failed with result %d", + __func__, retval); +} + +static int xpad_init_output(struct usb_interface *intf, struct usb_xpad *xpad) +{ + struct usb_endpoint_descriptor *ep_irq_out; + int error = -ENOMEM; + + if (xpad->xtype != XTYPE_XBOX360) + return 0; + + xpad->odata = usb_buffer_alloc(xpad->udev, XPAD_PKT_LEN, + GFP_KERNEL, &xpad->odata_dma); + if (!xpad->odata) + goto fail1; + + mutex_init(&xpad->odata_mutex); + + xpad->irq_out = usb_alloc_urb(0, GFP_KERNEL); + if (!xpad->irq_out) + goto fail2; + + ep_irq_out = &intf->cur_altsetting->endpoint[1].desc; + usb_fill_int_urb(xpad->irq_out, xpad->udev, + usb_sndintpipe(xpad->udev, ep_irq_out->bEndpointAddress), + xpad->odata, XPAD_PKT_LEN, + xpad_irq_out, xpad, ep_irq_out->bInterval); + xpad->irq_out->transfer_dma = xpad->odata_dma; + xpad->irq_out->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + + return 0; + + fail2: usb_buffer_free(xpad->udev, XPAD_PKT_LEN, xpad->odata, xpad->odata_dma); + fail1: return error; +} + +static void xpad_stop_output(struct usb_xpad *xpad) +{ + if (xpad->xtype == XTYPE_XBOX360) + usb_kill_urb(xpad->irq_out); +} + +static void xpad_deinit_output(struct usb_xpad *xpad) +{ + if (xpad->xtype == XTYPE_XBOX360) { + usb_free_urb(xpad->irq_out); + usb_buffer_free(xpad->udev, XPAD_PKT_LEN, + xpad->odata, xpad->odata_dma); + } +} +#else +static int xpad_init_output(struct usb_interface *intf, struct usb_xpad *xpad) { return 0; } +static void xpad_deinit_output(struct usb_xpad *xpad) {} +static void xpad_stop_output(struct usb_xpad *xpad) {} +#endif + +#ifdef CONFIG_JOYSTICK_XPAD_FF +static int xpad_play_effect(struct input_dev *dev, void *data, + struct ff_effect *effect) +{ + struct usb_xpad *xpad = input_get_drvdata(dev); + + if (effect->type == FF_RUMBLE) { + __u16 strong = effect->u.rumble.strong_magnitude; + __u16 weak = effect->u.rumble.weak_magnitude; + xpad->odata[0] = 0x00; + xpad->odata[1] = 0x08; + xpad->odata[2] = 0x00; + xpad->odata[3] = strong / 256; + xpad->odata[4] = weak / 256; + xpad->odata[5] = 0x00; + xpad->odata[6] = 0x00; + xpad->odata[7] = 0x00; + xpad->irq_out->transfer_buffer_length = 8; + usb_submit_urb(xpad->irq_out, GFP_KERNEL); + } + + return 0; +} + +static int xpad_init_ff(struct usb_xpad *xpad) +{ + if (xpad->xtype != XTYPE_XBOX360) + return 0; + + input_set_capability(xpad->dev, EV_FF, FF_RUMBLE); + + return input_ff_create_memless(xpad->dev, NULL, xpad_play_effect); +} + +#else +static int xpad_init_ff(struct usb_xpad *xpad) { return 0; } +#endif + +#if defined(CONFIG_JOYSTICK_XPAD_LEDS) +#include <linux/leds.h> + +struct xpad_led { + char name[16]; + struct led_classdev led_cdev; + struct usb_xpad *xpad; +}; + +static void xpad_send_led_command(struct usb_xpad *xpad, int command) +{ + if (command >= 0 && command < 14) { + mutex_lock(&xpad->odata_mutex); + xpad->odata[0] = 0x01; + xpad->odata[1] = 0x03; + xpad->odata[2] = command; + xpad->irq_out->transfer_buffer_length = 3; + usb_submit_urb(xpad->irq_out, GFP_KERNEL); + mutex_unlock(&xpad->odata_mutex); + } +} + +static void xpad_led_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct xpad_led *xpad_led = container_of(led_cdev, + struct xpad_led, led_cdev); + + xpad_send_led_command(xpad_led->xpad, value); +} + +static int xpad_led_probe(struct usb_xpad *xpad) +{ + static atomic_t led_seq = ATOMIC_INIT(0); + long led_no; + struct xpad_led *led; + struct led_classdev *led_cdev; + int error; + + if (xpad->xtype != XTYPE_XBOX360) + return 0; + + xpad->led = led = kzalloc(sizeof(struct xpad_led), GFP_KERNEL); + if (!led) + return -ENOMEM; + + led_no = (long)atomic_inc_return(&led_seq) - 1; + + snprintf(led->name, sizeof(led->name), "xpad%ld", led_no); + led->xpad = xpad; + + led_cdev = &led->led_cdev; + led_cdev->name = led->name; + led_cdev->brightness_set = xpad_led_set; + + error = led_classdev_register(&xpad->udev->dev, led_cdev); + if (error) { + kfree(led); + xpad->led = NULL; + return error; + } + + /* + * Light up the segment corresponding to controller number + */ + xpad_send_led_command(xpad, (led_no % 4) + 2); + + return 0; +} + +static void xpad_led_disconnect(struct usb_xpad *xpad) +{ + struct xpad_led *xpad_led = xpad->led; + + if (xpad_led) { + led_classdev_unregister(&xpad_led->led_cdev); + kfree(xpad_led->name); + } +} +#else +static int xpad_led_probe(struct usb_xpad *xpad) { return 0; } +static void xpad_led_disconnect(struct usb_xpad *xpad) { } +#endif + + +static int xpad_open(struct input_dev *dev) +{ + struct usb_xpad *xpad = input_get_drvdata(dev); + + /* URB was submitted in probe */ + if(xpad->xtype == XTYPE_XBOX360W) + return 0; + + xpad->irq_in->dev = xpad->udev; + if (usb_submit_urb(xpad->irq_in, GFP_KERNEL)) + return -EIO; + + return 0; +} + +static void xpad_close(struct input_dev *dev) +{ + struct usb_xpad *xpad = input_get_drvdata(dev); + + if(xpad->xtype != XTYPE_XBOX360W) + usb_kill_urb(xpad->irq_in); + xpad_stop_output(xpad); +} + +static void xpad_set_up_abs(struct input_dev *input_dev, signed short abs) +{ + set_bit(abs, input_dev->absbit); + + switch (abs) { + case ABS_X: + case ABS_Y: + case ABS_RX: + case ABS_RY: /* the two sticks */ + input_set_abs_params(input_dev, abs, -32768, 32767, 16, 128); + break; + case ABS_Z: + case ABS_RZ: /* the triggers */ + input_set_abs_params(input_dev, abs, 0, 255, 0, 0); + break; + case ABS_HAT0X: + case ABS_HAT0Y: /* the d-pad (only if MAP_DPAD_TO_AXES) */ + input_set_abs_params(input_dev, abs, -1, 1, 0, 0); + break; + } +} + +static int xpad_probe(struct usb_interface *intf, const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(intf); + struct usb_xpad *xpad; + struct input_dev *input_dev; + struct usb_endpoint_descriptor *ep_irq_in; + int i; + int error = -ENOMEM; + + for (i = 0; xpad_device[i].idVendor; i++) { + if ((le16_to_cpu(udev->descriptor.idVendor) == xpad_device[i].idVendor) && + (le16_to_cpu(udev->descriptor.idProduct) == xpad_device[i].idProduct)) + break; + } + + xpad = kzalloc(sizeof(struct usb_xpad), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!xpad || !input_dev) + goto fail1; + + xpad->idata = usb_buffer_alloc(udev, XPAD_PKT_LEN, + GFP_KERNEL, &xpad->idata_dma); + if (!xpad->idata) + goto fail1; + + xpad->irq_in = usb_alloc_urb(0, GFP_KERNEL); + if (!xpad->irq_in) + goto fail2; + + xpad->udev = udev; + xpad->dpad_mapping = xpad_device[i].dpad_mapping; + xpad->xtype = xpad_device[i].xtype; + if (xpad->dpad_mapping == MAP_DPAD_UNKNOWN) + xpad->dpad_mapping = !dpad_to_buttons; + if (xpad->xtype == XTYPE_UNKNOWN) { + if (intf->cur_altsetting->desc.bInterfaceClass == USB_CLASS_VENDOR_SPEC) { + if (intf->cur_altsetting->desc.bInterfaceProtocol == 129) + xpad->xtype = XTYPE_XBOX360W; + else + xpad->xtype = XTYPE_XBOX360; + } else + xpad->xtype = XTYPE_XBOX; + } + xpad->dev = input_dev; + usb_make_path(udev, xpad->phys, sizeof(xpad->phys)); + strlcat(xpad->phys, "/input0", sizeof(xpad->phys)); + + input_dev->name = xpad_device[i].name; + input_dev->phys = xpad->phys; + usb_to_input_id(udev, &input_dev->id); + input_dev->dev.parent = &intf->dev; + + input_set_drvdata(input_dev, xpad); + + input_dev->open = xpad_open; + input_dev->close = xpad_close; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + + /* set up buttons */ + for (i = 0; xpad_common_btn[i] >= 0; i++) + set_bit(xpad_common_btn[i], input_dev->keybit); + if ((xpad->xtype == XTYPE_XBOX360) || (xpad->xtype == XTYPE_XBOX360W)) + for (i = 0; xpad360_btn[i] >= 0; i++) + set_bit(xpad360_btn[i], input_dev->keybit); + else + for (i = 0; xpad_btn[i] >= 0; i++) + set_bit(xpad_btn[i], input_dev->keybit); + if (xpad->dpad_mapping == MAP_DPAD_TO_BUTTONS) + for (i = 0; xpad_btn_pad[i] >= 0; i++) + set_bit(xpad_btn_pad[i], input_dev->keybit); + + /* set up axes */ + for (i = 0; xpad_abs[i] >= 0; i++) + xpad_set_up_abs(input_dev, xpad_abs[i]); + if (xpad->dpad_mapping == MAP_DPAD_TO_AXES) + for (i = 0; xpad_abs_pad[i] >= 0; i++) + xpad_set_up_abs(input_dev, xpad_abs_pad[i]); + + error = xpad_init_output(intf, xpad); + if (error) + goto fail2; + + error = xpad_init_ff(xpad); + if (error) + goto fail3; + + error = xpad_led_probe(xpad); + if (error) + goto fail3; + + ep_irq_in = &intf->cur_altsetting->endpoint[0].desc; + usb_fill_int_urb(xpad->irq_in, udev, + usb_rcvintpipe(udev, ep_irq_in->bEndpointAddress), + xpad->idata, XPAD_PKT_LEN, xpad_irq_in, + xpad, ep_irq_in->bInterval); + xpad->irq_in->transfer_dma = xpad->idata_dma; + xpad->irq_in->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + + error = input_register_device(xpad->dev); + if (error) + goto fail4; + + usb_set_intfdata(intf, xpad); + + /* + * Submit the int URB immediatly rather than waiting for open + * because we get status messages from the device whether + * or not any controllers are attached. In fact, it's + * exactly the message that a controller has arrived that + * we're waiting for. + */ + if (xpad->xtype == XTYPE_XBOX360W) { + xpad->irq_in->dev = xpad->udev; + error = usb_submit_urb(xpad->irq_in, GFP_KERNEL); + if (error) + goto fail4; + + /* + * Setup the message to set the LEDs on the + * controller when it shows up + */ + xpad->bulk_out = usb_alloc_urb(0, GFP_KERNEL); + if(!xpad->bulk_out) + goto fail5; + + xpad->bdata = kzalloc(XPAD_PKT_LEN, GFP_KERNEL); + if(!xpad->bdata) + goto fail6; + + xpad->bdata[2] = 0x08; + switch (intf->cur_altsetting->desc.bInterfaceNumber) { + case 0: + xpad->bdata[3] = 0x42; + break; + case 2: + xpad->bdata[3] = 0x43; + break; + case 4: + xpad->bdata[3] = 0x44; + break; + case 6: + xpad->bdata[3] = 0x45; + } + + ep_irq_in = &intf->cur_altsetting->endpoint[1].desc; + usb_fill_bulk_urb(xpad->bulk_out, udev, + usb_sndbulkpipe(udev, ep_irq_in->bEndpointAddress), + xpad->bdata, XPAD_PKT_LEN, xpad_bulk_out, xpad); + } + + return 0; + + fail6: usb_free_urb(xpad->bulk_out); + fail5: usb_kill_urb(xpad->irq_in); + fail4: usb_free_urb(xpad->irq_in); + fail3: xpad_deinit_output(xpad); + fail2: usb_buffer_free(udev, XPAD_PKT_LEN, xpad->idata, xpad->idata_dma); + fail1: input_free_device(input_dev); + kfree(xpad); + return error; + +} + +static void xpad_disconnect(struct usb_interface *intf) +{ + struct usb_xpad *xpad = usb_get_intfdata (intf); + + usb_set_intfdata(intf, NULL); + if (xpad) { + xpad_led_disconnect(xpad); + input_unregister_device(xpad->dev); + xpad_deinit_output(xpad); + if (xpad->xtype == XTYPE_XBOX360W) { + usb_kill_urb(xpad->bulk_out); + usb_free_urb(xpad->bulk_out); + usb_kill_urb(xpad->irq_in); + } + usb_free_urb(xpad->irq_in); + usb_buffer_free(xpad->udev, XPAD_PKT_LEN, + xpad->idata, xpad->idata_dma); + kfree(xpad); + } +} + +static struct usb_driver xpad_driver = { + .name = "xpad", + .probe = xpad_probe, + .disconnect = xpad_disconnect, + .id_table = xpad_table, +}; + +static int __init usb_xpad_init(void) +{ + int result = usb_register(&xpad_driver); + if (result == 0) + printk(KERN_INFO KBUILD_MODNAME ": " DRIVER_DESC "\n"); + return result; +} + +static void __exit usb_xpad_exit(void) +{ + usb_deregister(&xpad_driver); +} + +module_init(usb_xpad_init); +module_exit(usb_xpad_exit); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/joystick/zhenhua.c b/drivers/input/joystick/zhenhua.c new file mode 100644 index 0000000..b585312 --- /dev/null +++ b/drivers/input/joystick/zhenhua.c @@ -0,0 +1,243 @@ +/* + * derived from "twidjoy.c" + * + * Copyright (c) 2008 Martin Kebert + * Copyright (c) 2001 Arndt Schoenewald + * Copyright (c) 2000-2001 Vojtech Pavlik + * Copyright (c) 2000 Mark Fletcher + * + */ + +/* + * Driver to use 4CH RC transmitter using Zhen Hua 5-byte protocol (Walkera Lama, + * EasyCopter etc.) as a joystick under Linux. + * + * RC transmitters using Zhen Hua 5-byte protocol are cheap four channels + * transmitters for control a RC planes or RC helicopters with possibility to + * connect on a serial port. + * Data coming from transmitter is in this order: + * 1. byte = synchronisation byte + * 2. byte = X axis + * 3. byte = Y axis + * 4. byte = RZ axis + * 5. byte = Z axis + * (and this is repeated) + * + * For questions or feedback regarding this driver module please contact: + * Martin Kebert <gkmarty@gmail.com> - but I am not a C-programmer nor kernel + * coder :-( + */ + +/* + * 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 "RC transmitter with 5-byte Zhen Hua protocol joystick driver" + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +/* + * Constants. + */ + +#define ZHENHUA_MAX_LENGTH 5 + +/* + * Zhen Hua data. + */ + +struct zhenhua { + struct input_dev *dev; + int idx; + unsigned char data[ZHENHUA_MAX_LENGTH]; + char phys[32]; +}; + + +/* bits in all incoming bytes needs to be "reversed" */ +static int zhenhua_bitreverse(int x) +{ + x = ((x & 0xaa) >> 1) | ((x & 0x55) << 1); + x = ((x & 0xcc) >> 2) | ((x & 0x33) << 2); + x = ((x & 0xf0) >> 4) | ((x & 0x0f) << 4); + return x; +} + +/* + * zhenhua_process_packet() decodes packets the driver receives from the + * RC transmitter. It updates the data accordingly. + */ + +static void zhenhua_process_packet(struct zhenhua *zhenhua) +{ + struct input_dev *dev = zhenhua->dev; + unsigned char *data = zhenhua->data; + + input_report_abs(dev, ABS_Y, data[1]); + input_report_abs(dev, ABS_X, data[2]); + input_report_abs(dev, ABS_RZ, data[3]); + input_report_abs(dev, ABS_Z, data[4]); + + input_sync(dev); +} + +/* + * zhenhua_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 zhenhua_interrupt(struct serio *serio, unsigned char data, unsigned int flags) +{ + struct zhenhua *zhenhua = serio_get_drvdata(serio); + + /* All Zhen Hua packets are 5 bytes. The fact that the first byte + * is allways 0xf7 and all others are in range 0x32 - 0xc8 (50-200) + * can be used to check and regain sync. */ + + if (data == 0xef) + zhenhua->idx = 0; /* this byte starts a new packet */ + else if (zhenhua->idx == 0) + return IRQ_HANDLED; /* wrong MSB -- ignore this byte */ + + if (zhenhua->idx < ZHENHUA_MAX_LENGTH) + zhenhua->data[zhenhua->idx++] = zhenhua_bitreverse(data); + + if (zhenhua->idx == ZHENHUA_MAX_LENGTH) { + zhenhua_process_packet(zhenhua); + zhenhua->idx = 0; + } + + return IRQ_HANDLED; +} + +/* + * zhenhua_disconnect() is the opposite of zhenhua_connect() + */ + +static void zhenhua_disconnect(struct serio *serio) +{ + struct zhenhua *zhenhua = serio_get_drvdata(serio); + + serio_close(serio); + serio_set_drvdata(serio, NULL); + input_unregister_device(zhenhua->dev); + kfree(zhenhua); +} + +/* + * zhenhua_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 zhenhua_connect(struct serio *serio, struct serio_driver *drv) +{ + struct zhenhua *zhenhua; + struct input_dev *input_dev; + int err = -ENOMEM; + + zhenhua = kzalloc(sizeof(struct zhenhua), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!zhenhua || !input_dev) + goto fail1; + + zhenhua->dev = input_dev; + snprintf(zhenhua->phys, sizeof(zhenhua->phys), "%s/input0", serio->phys); + + input_dev->name = "Zhen Hua 5-byte device"; + input_dev->phys = zhenhua->phys; + input_dev->id.bustype = BUS_RS232; + input_dev->id.vendor = SERIO_ZHENHUA; + input_dev->id.product = 0x0001; + input_dev->id.version = 0x0100; + input_dev->dev.parent = &serio->dev; + + input_dev->evbit[0] = BIT(EV_ABS); + input_set_abs_params(input_dev, ABS_X, 50, 200, 0, 0); + input_set_abs_params(input_dev, ABS_Y, 50, 200, 0, 0); + input_set_abs_params(input_dev, ABS_Z, 50, 200, 0, 0); + input_set_abs_params(input_dev, ABS_RZ, 50, 200, 0, 0); + + serio_set_drvdata(serio, zhenhua); + + err = serio_open(serio, drv); + if (err) + goto fail2; + + err = input_register_device(zhenhua->dev); + if (err) + goto fail3; + + return 0; + + fail3: serio_close(serio); + fail2: serio_set_drvdata(serio, NULL); + fail1: input_free_device(input_dev); + kfree(zhenhua); + return err; +} + +/* + * The serio driver structure. + */ + +static struct serio_device_id zhenhua_serio_ids[] = { + { + .type = SERIO_RS232, + .proto = SERIO_ZHENHUA, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { 0 } +}; + +MODULE_DEVICE_TABLE(serio, zhenhua_serio_ids); + +static struct serio_driver zhenhua_drv = { + .driver = { + .name = "zhenhua", + }, + .description = DRIVER_DESC, + .id_table = zhenhua_serio_ids, + .interrupt = zhenhua_interrupt, + .connect = zhenhua_connect, + .disconnect = zhenhua_disconnect, +}; + +/* + * The functions for inserting/removing us as a module. + */ + +static int __init zhenhua_init(void) +{ + return serio_register_driver(&zhenhua_drv); +} + +static void __exit zhenhua_exit(void) +{ + serio_unregister_driver(&zhenhua_drv); +} + +module_init(zhenhua_init); +module_exit(zhenhua_exit); diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig new file mode 100644 index 0000000..efd70a9 --- /dev/null +++ b/drivers/input/keyboard/Kconfig @@ -0,0 +1,326 @@ +# +# 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 EMBEDDED || !X86_PC + default y + select SERIO + select SERIO_LIBPS2 + select SERIO_I8042 if X86_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 && INPUT_KEYBOARD + 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_STOWAWAY + tristate "Stowaway keyboard" + select SERIO + help + Say Y here if you have a Stowaway keyboard on a serial port. + Stowaway compatible keyboards like Dicota Input-PDA keyboard + are also supported by this driver. + + To compile this driver as a module, choose M here: the + module will be called stowaway. + +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_SPITZ + tristate "Spitz keyboard" + depends on PXA_SHARPSL + default y + help + Say Y here to enable the keyboard on the Sharp Zaurus SL-C1000, + SL-C3000 and Sl-C3100 series of PDAs. + + To compile this driver as a module, choose M here: the + module will be called spitzkbd. + +config KEYBOARD_TOSA + tristate "Tosa keyboard" + depends on MACH_TOSA + default y + help + Say Y here to enable the keyboard on the Sharp Zaurus SL-6000x (Tosa) + + To compile this driver as a module, choose M here: the + module will be called tosakbd. + +config KEYBOARD_TOSA_USE_EXT_KEYCODES + bool "Tosa keyboard: use extended keycodes" + depends on KEYBOARD_TOSA + default n + help + Say Y here to enable the tosa keyboard driver to generate extended + (>= 127) keycodes. Be aware, that they can't be correctly interpreted + by either console keyboard driver or by Kdrive keybd driver. + + Say Y only if you know, what you are doing! + +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 ATARI_KBD_CORE + bool + +config KEYBOARD_ATARI + tristate "Atari keyboard" + depends on ATARI + select ATARI_KBD_CORE + help + Say Y here if you are running Linux on any Atari and have a keyboard + attached. + + To compile this driver as a module, choose M here: the + module will be called atakbd. + +config KEYBOARD_HIL_OLD + tristate "HP HIL keyboard support (simple driver)" + depends on GSC || HP300 + 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, mice, 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 || HP300 + 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. + +config KEYBOARD_HP6XX + tristate "HP Jornada 6xx keyboard" + depends on SH_HP6XX + select INPUT_POLLDEV + help + Say Y here if you have a HP Jornada 620/660/680/690 and want to + support the built-in keyboard. + + To compile this driver as a module, choose M here: the + module will be called jornada680_kbd. + +config KEYBOARD_HP7XX + tristate "HP Jornada 7xx keyboard" + depends on SA1100_JORNADA720_SSP && SA1100_SSP + help + Say Y here if you have a HP Jornada 710/720/728 and want to + support the built-in keyboard. + + To compile this driver as a module, choose M here: the + module will be called jornada720_kbd. + +config KEYBOARD_OMAP + tristate "TI OMAP keypad support" + depends on (ARCH_OMAP1 || ARCH_OMAP2) + help + Say Y here if you want to use the OMAP keypad. + + To compile this driver as a module, choose M here: the + module will be called omap-keypad. + +config KEYBOARD_PXA27x + tristate "PXA27x/PXA3xx keypad support" + depends on PXA27x || PXA3xx + help + Enable support for PXA27x/PXA3xx keypad controller + + To compile this driver as a module, choose M here: the + module will be called pxa27x_keypad. + +config KEYBOARD_AAED2000 + tristate "AAED-2000 keyboard" + depends on MACH_AAED2000 + select INPUT_POLLDEV + default y + help + Say Y here to enable the keyboard on the Agilent AAED-2000 + development board. + + To compile this driver as a module, choose M here: the + module will be called aaed2000_kbd. + +config KEYBOARD_GPIO + tristate "GPIO Buttons" + depends on GENERIC_GPIO + help + This driver implements support for buttons connected + to GPIO pins of various CPUs (and some other chips). + + Say Y here if your device has buttons connected + directly to such GPIO pins. Your board-specific + setup logic must also provide a platform device, + with configuration data saying which GPIOs are used. + + To compile this driver as a module, choose M here: the + module will be called gpio-keys. + +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_BFIN + tristate "Blackfin BF54x keypad support" + depends on (BF54x && !BF544) + help + Say Y here if you want to use the BF54x keypad. + + To compile this driver as a module, choose M here: the + module will be called bf54x-keys. + +config KEYBOARD_SH_KEYSC + tristate "SuperH KEYSC keypad support" + depends on SUPERH + help + Say Y here if you want to use a keypad attached to the KEYSC block + on SuperH processors such as sh7722 and sh7343. + + To compile this driver as a module, choose M here: the + module will be called sh_keysc. +endif diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile new file mode 100644 index 0000000..0edc8f2 --- /dev/null +++ b/drivers/input/keyboard/Makefile @@ -0,0 +1,29 @@ +# +# Makefile for the input core drivers. +# + +# Each configuration option enables a list of files. + +obj-$(CONFIG_KEYBOARD_ATKBD) += atkbd.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_ATARI) += atakbd.o +obj-$(CONFIG_KEYBOARD_LOCOMO) += locomokbd.o +obj-$(CONFIG_KEYBOARD_NEWTON) += newtonkbd.o +obj-$(CONFIG_KEYBOARD_STOWAWAY) += stowaway.o +obj-$(CONFIG_KEYBOARD_CORGI) += corgikbd.o +obj-$(CONFIG_KEYBOARD_SPITZ) += spitzkbd.o +obj-$(CONFIG_KEYBOARD_TOSA) += tosakbd.o +obj-$(CONFIG_KEYBOARD_HIL) += hil_kbd.o +obj-$(CONFIG_KEYBOARD_HIL_OLD) += hilkbd.o +obj-$(CONFIG_KEYBOARD_OMAP) += omap-keypad.o +obj-$(CONFIG_KEYBOARD_PXA27x) += pxa27x_keypad.o +obj-$(CONFIG_KEYBOARD_AAED2000) += aaed2000_kbd.o +obj-$(CONFIG_KEYBOARD_GPIO) += gpio_keys.o +obj-$(CONFIG_KEYBOARD_HP6XX) += jornada680_kbd.o +obj-$(CONFIG_KEYBOARD_HP7XX) += jornada720_kbd.o +obj-$(CONFIG_KEYBOARD_MAPLE) += maple_keyb.o +obj-$(CONFIG_KEYBOARD_BFIN) += bf54x-keys.o +obj-$(CONFIG_KEYBOARD_SH_KEYSC) += sh_keysc.o diff --git a/drivers/input/keyboard/aaed2000_kbd.c b/drivers/input/keyboard/aaed2000_kbd.c new file mode 100644 index 0000000..18222a6 --- /dev/null +++ b/drivers/input/keyboard/aaed2000_kbd.c @@ -0,0 +1,186 @@ +/* + * Keyboard driver for the AAED-2000 dev board + * + * Copyright (c) 2006 Nicolas Bellido Y Ortega + * + * Based on corgikbd.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/platform_device.h> +#include <linux/init.h> +#include <linux/input-polldev.h> +#include <linux/interrupt.h> +#include <linux/jiffies.h> +#include <linux/module.h> +#include <linux/slab.h> + +#include <mach/hardware.h> +#include <mach/aaed2000.h> + +#define KB_ROWS 12 +#define KB_COLS 8 +#define KB_ROWMASK(r) (1 << (r)) +#define SCANCODE(r,c) (((c) * KB_ROWS) + (r)) +#define NR_SCANCODES (KB_COLS * KB_ROWS) + +#define SCAN_INTERVAL (50) /* ms */ +#define KB_ACTIVATE_DELAY (20) /* us */ + +static unsigned char aaedkbd_keycode[NR_SCANCODES] = { + KEY_9, KEY_0, KEY_MINUS, KEY_EQUAL, KEY_BACKSPACE, 0, KEY_SPACE, KEY_KP6, 0, KEY_KPDOT, 0, 0, + KEY_K, KEY_M, KEY_O, KEY_DOT, KEY_SLASH, 0, KEY_F, 0, 0, 0, KEY_LEFTSHIFT, 0, + KEY_I, KEY_P, KEY_LEFTBRACE, KEY_RIGHTBRACE, KEY_BACKSLASH, 0, 0, 0, 0, 0, KEY_RIGHTSHIFT, 0, + KEY_8, KEY_L, KEY_SEMICOLON, KEY_APOSTROPHE, KEY_ENTER, 0, 0, 0, 0, 0, 0, 0, + KEY_J, KEY_H, KEY_B, KEY_KP8, KEY_KP4, 0, KEY_C, KEY_D, KEY_S, KEY_A, 0, KEY_CAPSLOCK, + KEY_Y, KEY_U, KEY_N, KEY_T, 0, 0, KEY_R, KEY_E, KEY_W, KEY_Q, 0, KEY_TAB, + KEY_7, KEY_6, KEY_G, 0, KEY_5, 0, KEY_4, KEY_3, KEY_2, KEY_1, 0, KEY_GRAVE, + 0, 0, KEY_COMMA, 0, KEY_KP2, 0, KEY_V, KEY_LEFTALT, KEY_X, KEY_Z, 0, KEY_LEFTCTRL +}; + +struct aaedkbd { + unsigned char keycode[ARRAY_SIZE(aaedkbd_keycode)]; + struct input_polled_dev *poll_dev; + int kbdscan_state[KB_COLS]; + int kbdscan_count[KB_COLS]; +}; + +#define KBDSCAN_STABLE_COUNT 2 + +static void aaedkbd_report_col(struct aaedkbd *aaedkbd, + unsigned int col, unsigned int rowd) +{ + unsigned int scancode, pressed; + unsigned int row; + + for (row = 0; row < KB_ROWS; row++) { + scancode = SCANCODE(row, col); + pressed = rowd & KB_ROWMASK(row); + + input_report_key(aaedkbd->poll_dev->input, + aaedkbd->keycode[scancode], pressed); + } +} + +/* Scan the hardware keyboard and push any changes up through the input layer */ +static void aaedkbd_poll(struct input_polled_dev *dev) +{ + struct aaedkbd *aaedkbd = dev->private; + unsigned int col, rowd; + + col = 0; + do { + AAEC_GPIO_KSCAN = col + 8; + udelay(KB_ACTIVATE_DELAY); + rowd = AAED_EXT_GPIO & AAED_EGPIO_KBD_SCAN; + + if (rowd != aaedkbd->kbdscan_state[col]) { + aaedkbd->kbdscan_count[col] = 0; + aaedkbd->kbdscan_state[col] = rowd; + } else if (++aaedkbd->kbdscan_count[col] >= KBDSCAN_STABLE_COUNT) { + aaedkbd_report_col(aaedkbd, col, rowd); + col++; + } + } while (col < KB_COLS); + + AAEC_GPIO_KSCAN = 0x07; + input_sync(dev->input); +} + +static int __devinit aaedkbd_probe(struct platform_device *pdev) +{ + struct aaedkbd *aaedkbd; + struct input_polled_dev *poll_dev; + struct input_dev *input_dev; + int i; + int error; + + aaedkbd = kzalloc(sizeof(struct aaedkbd), GFP_KERNEL); + poll_dev = input_allocate_polled_device(); + if (!aaedkbd || !poll_dev) { + error = -ENOMEM; + goto fail; + } + + platform_set_drvdata(pdev, aaedkbd); + + aaedkbd->poll_dev = poll_dev; + memcpy(aaedkbd->keycode, aaedkbd_keycode, sizeof(aaedkbd->keycode)); + + poll_dev->private = aaedkbd; + poll_dev->poll = aaedkbd_poll; + poll_dev->poll_interval = SCAN_INTERVAL; + + input_dev = poll_dev->input; + input_dev->name = "AAED-2000 Keyboard"; + input_dev->phys = "aaedkbd/input0"; + input_dev->id.bustype = BUS_HOST; + input_dev->id.vendor = 0x0001; + input_dev->id.product = 0x0001; + input_dev->id.version = 0x0100; + input_dev->dev.parent = &pdev->dev; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP); + input_dev->keycode = aaedkbd->keycode; + input_dev->keycodesize = sizeof(unsigned char); + input_dev->keycodemax = ARRAY_SIZE(aaedkbd_keycode); + + for (i = 0; i < ARRAY_SIZE(aaedkbd_keycode); i++) + set_bit(aaedkbd->keycode[i], input_dev->keybit); + clear_bit(0, input_dev->keybit); + + error = input_register_polled_device(aaedkbd->poll_dev); + if (error) + goto fail; + + return 0; + + fail: kfree(aaedkbd); + input_free_polled_device(poll_dev); + return error; +} + +static int __devexit aaedkbd_remove(struct platform_device *pdev) +{ + struct aaedkbd *aaedkbd = platform_get_drvdata(pdev); + + input_unregister_polled_device(aaedkbd->poll_dev); + input_free_polled_device(aaedkbd->poll_dev); + kfree(aaedkbd); + + return 0; +} + +/* work with hotplug and coldplug */ +MODULE_ALIAS("platform:aaed2000-keyboard"); + +static struct platform_driver aaedkbd_driver = { + .probe = aaedkbd_probe, + .remove = __devexit_p(aaedkbd_remove), + .driver = { + .name = "aaed2000-keyboard", + .owner = THIS_MODULE, + }, +}; + +static int __init aaedkbd_init(void) +{ + return platform_driver_register(&aaedkbd_driver); +} + +static void __exit aaedkbd_exit(void) +{ + platform_driver_unregister(&aaedkbd_driver); +} + +module_init(aaedkbd_init); +module_exit(aaedkbd_exit); + +MODULE_AUTHOR("Nicolas Bellido Y Ortega"); +MODULE_DESCRIPTION("AAED-2000 Keyboard Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/input/keyboard/amikbd.c b/drivers/input/keyboard/amikbd.c new file mode 100644 index 0000000..35149ec --- /dev/null +++ b/drivers/input/keyboard/amikbd.c @@ -0,0 +1,258 @@ +/* + * 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 <linux/keyboard.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] __initdata = { + [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 irqreturn_t amikbd_interrupt(int irq, void *dummy) +{ + 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 */ + if (scancode == 98) { /* CapsLock is a toggle switch key on Amiga */ + input_report_key(amikbd_dev, scancode, 1); + input_report_key(amikbd_dev, scancode, 0); + } 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, j, err; + + if (!AMIGAHW_PRESENT(AMI_KEYBOARD)) + return -ENODEV; + + if (!request_mem_region(CIAA_PHYSADDR-1+0xb00, 0x100, "amikeyb")) + return -EBUSY; + + amikbd_dev = input_allocate_device(); + if (!amikbd_dev) { + printk(KERN_ERR "amikbd: not enough memory for input device\n"); + err = -ENOMEM; + goto fail1; + } + + amikbd_dev->name = "Amiga Keyboard"; + amikbd_dev->phys = "amikbd/input0"; + amikbd_dev->id.bustype = BUS_AMIGA; + amikbd_dev->id.vendor = 0x0001; + amikbd_dev->id.product = 0x0001; + amikbd_dev->id.version = 0x0100; + + amikbd_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP); + + for (i = 0; i < 0x78; i++) + set_bit(i, amikbd_dev->keybit); + + for (i = 0; i < MAX_NR_KEYMAPS; i++) { + static u_short temp_map[NR_KEYS] __initdata; + if (!key_maps[i]) + continue; + memset(temp_map, 0, sizeof(temp_map)); + for (j = 0; j < 0x78; j++) { + if (!amikbd_keycode[j]) + continue; + temp_map[j] = key_maps[i][amikbd_keycode[j]]; + } + for (j = 0; j < NR_KEYS; j++) { + if (!temp_map[j]) + temp_map[j] = 0xf200; + } + memcpy(key_maps[i], temp_map, sizeof(temp_map)); + } + ciaa.cra &= ~0x41; /* serial data in, turn off TA */ + if (request_irq(IRQ_AMIGA_CIAA_SP, amikbd_interrupt, 0, "amikbd", + amikbd_interrupt)) { + err = -EBUSY; + goto fail2; + } + + err = input_register_device(amikbd_dev); + if (err) + goto fail3; + + return 0; + + fail3: free_irq(IRQ_AMIGA_CIAA_SP, amikbd_interrupt); + fail2: input_free_device(amikbd_dev); + fail1: release_mem_region(CIAA_PHYSADDR - 1 + 0xb00, 0x100); + return err; +} + +static void __exit amikbd_exit(void) +{ + free_irq(IRQ_AMIGA_CIAA_SP, amikbd_interrupt); + input_unregister_device(amikbd_dev); + release_mem_region(CIAA_PHYSADDR - 1 + 0xb00, 0x100); +} + +module_init(amikbd_init); +module_exit(amikbd_exit); diff --git a/drivers/input/keyboard/atakbd.c b/drivers/input/keyboard/atakbd.c new file mode 100644 index 0000000..1839194 --- /dev/null +++ b/drivers/input/keyboard/atakbd.c @@ -0,0 +1,268 @@ +/* + * atakbd.c + * + * Copyright (c) 2005 Michael Schmitz + * + * Based on amikbd.c, which is + * + * Copyright (c) 2000-2001 Vojtech Pavlik + * + * Based on the work of: + * Hamish Macdonald + */ + +/* + * Atari keyboard driver for Linux/m68k + * + * The low level init and interrupt stuff is handled in arch/mm68k/atari/atakeyb.c + * (the keyboard ACIA also handles the mouse and joystick data, and the keyboard + * interrupt is shared with the MIDI ACIA so MIDI data also get handled there). + * This driver only deals with handing key events off to the input layer. + */ + +/* + * 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/atariints.h> +#include <asm/atarihw.h> +#include <asm/atarikb.h> +#include <asm/irq.h> + +MODULE_AUTHOR("Michael Schmitz <schmitz@biophys.uni-duesseldorf.de>"); +MODULE_DESCRIPTION("Atari keyboard driver"); +MODULE_LICENSE("GPL"); + +/* + 0x47: KP_7 71 + 0x48: KP_8 72 + 0x49: KP_9 73 + 0x62: KP_/ 98 + 0x4b: KP_4 75 + 0x4c: KP_5 76 + 0x4d: KP_6 77 + 0x37: KP_* 55 + 0x4f: KP_1 79 + 0x50: KP_2 80 + 0x51: KP_3 81 + 0x4a: KP_- 74 + 0x52: KP_0 82 + 0x53: KP_. 83 + 0x4e: KP_+ 78 + + 0x67: Up 103 + 0x6c: Down 108 + 0x69: Left 105 + 0x6a: Right 106 + */ + + +static unsigned char atakbd_keycode[0x72] = { /* American layout */ + [0] = KEY_GRAVE, + [1] = KEY_ESC, + [2] = KEY_1, + [3] = KEY_2, + [4] = KEY_3, + [5] = KEY_4, + [6] = KEY_5, + [7] = KEY_6, + [8] = KEY_7, + [9] = KEY_8, + [10] = KEY_9, + [11] = KEY_0, + [12] = KEY_MINUS, + [13] = KEY_EQUAL, + [14] = KEY_BACKSPACE, + [15] = KEY_TAB, + [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, + [28] = KEY_ENTER, + [29] = KEY_LEFTCTRL, + [30] = KEY_A, + [31] = KEY_S, + [32] = KEY_D, + [33] = KEY_F, + [34] = KEY_G, + [35] = KEY_H, + [36] = KEY_J, + [37] = KEY_K, + [38] = KEY_L, + [39] = KEY_SEMICOLON, + [40] = KEY_APOSTROPHE, + [41] = KEY_BACKSLASH, /* FIXME, '#' */ + [42] = KEY_LEFTSHIFT, + [43] = KEY_GRAVE, /* FIXME: '~' */ + [44] = KEY_Z, + [45] = KEY_X, + [46] = KEY_C, + [47] = KEY_V, + [48] = KEY_B, + [49] = KEY_N, + [50] = KEY_M, + [51] = KEY_COMMA, + [52] = KEY_DOT, + [53] = KEY_SLASH, + [54] = KEY_RIGHTSHIFT, + [55] = KEY_KPASTERISK, + [56] = KEY_LEFTALT, + [57] = KEY_SPACE, + [58] = KEY_CAPSLOCK, + [59] = KEY_F1, + [60] = KEY_F2, + [61] = KEY_F3, + [62] = KEY_F4, + [63] = KEY_F5, + [64] = KEY_F6, + [65] = KEY_F7, + [66] = KEY_F8, + [67] = KEY_F9, + [68] = KEY_F10, + [69] = KEY_ESC, + [70] = KEY_DELETE, + [71] = KEY_KP7, + [72] = KEY_KP8, + [73] = KEY_KP9, + [74] = KEY_KPMINUS, + [75] = KEY_KP4, + [76] = KEY_KP5, + [77] = KEY_KP6, + [78] = KEY_KPPLUS, + [79] = KEY_KP1, + [80] = KEY_KP2, + [81] = KEY_KP3, + [82] = KEY_KP0, + [83] = KEY_KPDOT, + [90] = KEY_KPLEFTPAREN, + [91] = KEY_KPRIGHTPAREN, + [92] = KEY_KPASTERISK, /* FIXME */ + [93] = KEY_KPASTERISK, + [94] = KEY_KPPLUS, + [95] = KEY_HELP, + [96] = KEY_BACKSLASH, /* FIXME: '<' */ + [97] = KEY_KPASTERISK, /* FIXME */ + [98] = KEY_KPSLASH, + [99] = KEY_KPLEFTPAREN, + [100] = KEY_KPRIGHTPAREN, + [101] = KEY_KPSLASH, + [102] = KEY_KPASTERISK, + [103] = KEY_UP, + [104] = KEY_KPASTERISK, /* FIXME */ + [105] = KEY_LEFT, + [106] = KEY_RIGHT, + [107] = KEY_KPASTERISK, /* FIXME */ + [108] = KEY_DOWN, + [109] = KEY_KPASTERISK, /* FIXME */ + [110] = KEY_KPASTERISK, /* FIXME */ + [111] = KEY_KPASTERISK, /* FIXME */ + [112] = KEY_KPASTERISK, /* FIXME */ + [113] = KEY_KPASTERISK /* FIXME */ +}; + +static struct input_dev *atakbd_dev; + +static void atakbd_interrupt(unsigned char scancode, char down) +{ + + if (scancode < 0x72) { /* scancodes < 0xf2 are keys */ + + // report raw events here? + + scancode = atakbd_keycode[scancode]; + + if (scancode == KEY_CAPSLOCK) { /* CapsLock is a toggle switch key on Amiga */ + input_report_key(atakbd_dev, scancode, 1); + input_report_key(atakbd_dev, scancode, 0); + input_sync(atakbd_dev); + } else { + input_report_key(atakbd_dev, scancode, down); + input_sync(atakbd_dev); + } + } else /* scancodes >= 0xf2 are mouse data, most likely */ + printk(KERN_INFO "atakbd: unhandled scancode %x\n", scancode); + + return; +} + +static int __init atakbd_init(void) +{ + int i, error; + + if (!MACH_IS_ATARI || !ATARIHW_PRESENT(ST_MFP)) + return -ENODEV; + + // need to init core driver if not already done so + if (atari_keyb_init()) + return -ENODEV; + + atakbd_dev = input_allocate_device(); + if (!atakbd_dev) + return -ENOMEM; + + atakbd_dev->name = "Atari Keyboard"; + atakbd_dev->phys = "atakbd/input0"; + atakbd_dev->id.bustype = BUS_HOST; + atakbd_dev->id.vendor = 0x0001; + atakbd_dev->id.product = 0x0001; + atakbd_dev->id.version = 0x0100; + + atakbd_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP); + atakbd_dev->keycode = atakbd_keycode; + atakbd_dev->keycodesize = sizeof(unsigned char); + atakbd_dev->keycodemax = ARRAY_SIZE(atakbd_keycode); + + for (i = 1; i < 0x72; i++) { + set_bit(atakbd_keycode[i], atakbd_dev->keybit); + } + + /* error check */ + error = input_register_device(atakbd_dev); + if (error) { + input_free_device(atakbd_dev); + return error; + } + + atari_input_keyboard_interrupt_hook = atakbd_interrupt; + + return 0; +} + +static void __exit atakbd_exit(void) +{ + atari_input_keyboard_interrupt_hook = NULL; + input_unregister_device(atakbd_dev); +} + +module_init(atakbd_init); +module_exit(atakbd_exit); diff --git a/drivers/input/keyboard/atkbd.c b/drivers/input/keyboard/atkbd.c new file mode 100644 index 0000000..159dbb6 --- /dev/null +++ b/drivers/input/keyboard/atkbd.c @@ -0,0 +1,1537 @@ +/* + * 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/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> +#include <linux/mutex.h> +#include <linux/dmi.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; +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"); + +/* + * Scancode to keycode tables. These are just the default setting, and + * are loadable via an userland utility. + */ + +static const unsigned short 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,172,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 const unsigned short 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,172,163,165,115,152,172,166,140,160,154,113,114,167,168, + 148,149,147,140 +}; + +static const unsigned short 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_HANJA 0xf1 +#define ATKBD_RET_HANGEUL 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 ATKBD_SCR_RIGHT + +#define ATKBD_LED_EVENT_BIT 0 +#define ATKBD_REP_EVENT_BIT 1 + +#define ATKBD_XL_ERR 0x01 +#define ATKBD_XL_BAT 0x02 +#define ATKBD_XL_ACK 0x04 +#define ATKBD_XL_NAK 0x08 +#define ATKBD_XL_HANGEUL 0x10 +#define ATKBD_XL_HANJA 0x20 + +static const struct { + unsigned char keycode; + unsigned char set2; +} atkbd_scroll_keys[] = { + { ATKBD_SCR_1, 0xc5 }, + { ATKBD_SCR_2, 0x9d }, + { ATKBD_SCR_4, 0xa4 }, + { ATKBD_SCR_8, 0x9b }, + { ATKBD_SCR_CLICK, 0xe0 }, + { ATKBD_SCR_LEFT, 0xcb }, + { ATKBD_SCR_RIGHT, 0xd2 }, +}; + +/* + * The atkbd control structure + */ + +struct atkbd { + + struct ps2dev ps2dev; + struct input_dev *dev; + + /* Written only during init */ + char name[64]; + char phys[32]; + + unsigned short id; + unsigned short keycode[512]; + DECLARE_BITMAP(force_release_mask, 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 long xl_bit; + unsigned int last; + unsigned long time; + unsigned long err_count; + + struct delayed_work event_work; + unsigned long event_jiffies; + struct mutex event_mutex; + unsigned long event_mask; +}; + +/* + * System-specific ketymap fixup routine + */ +static void (*atkbd_platform_fixup)(struct atkbd *); + +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, \ + struct device_attribute *attr, char *b) \ +{ \ + return atkbd_attr_show_helper(d, b, atkbd_show_##_name); \ +} \ +static ssize_t atkbd_do_set_##_name(struct device *d, \ + struct device_attribute *attr, 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); + +#define ATKBD_DEFINE_RO_ATTR(_name) \ +static ssize_t atkbd_show_##_name(struct atkbd *, char *); \ +static ssize_t atkbd_do_show_##_name(struct device *d, \ + struct device_attribute *attr, char *b) \ +{ \ + return atkbd_attr_show_helper(d, b, atkbd_show_##_name); \ +} \ +static struct device_attribute atkbd_attr_##_name = \ + __ATTR(_name, S_IRUGO, atkbd_do_show_##_name, NULL); + +ATKBD_DEFINE_RO_ATTR(err_count); + +static struct attribute *atkbd_attributes[] = { + &atkbd_attr_extra.attr, + &atkbd_attr_scroll.attr, + &atkbd_attr_set.attr, + &atkbd_attr_softrepeat.attr, + &atkbd_attr_softraw.attr, + &atkbd_attr_err_count.attr, + NULL +}; + +static struct attribute_group atkbd_attribute_group = { + .attrs = atkbd_attributes, +}; + +static const unsigned int xl_table[] = { + ATKBD_RET_BAT, ATKBD_RET_ERR, ATKBD_RET_ACK, + ATKBD_RET_NAK, ATKBD_RET_HANJA, ATKBD_RET_HANGEUL, +}; + +/* + * Checks if we should mangle the scancode to extract 'release' bit + * in translated mode. + */ +static int atkbd_need_xlate(unsigned long xl_bit, unsigned char code) +{ + int i; + + if (code == ATKBD_RET_EMUL0 || code == ATKBD_RET_EMUL1) + return 0; + + for (i = 0; i < ARRAY_SIZE(xl_table); i++) + if (code == xl_table[i]) + return test_bit(i, &xl_bit); + + return 1; +} + +/* + * Calculates new value of xl_bit so the driver can distinguish + * between make/break pair of scancodes for select keys and PS/2 + * protocol responses. + */ +static void atkbd_calculate_xl_bit(struct atkbd *atkbd, unsigned char code) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(xl_table); i++) { + if (!((code ^ xl_table[i]) & 0x7f)) { + if (code & 0x80) + __clear_bit(i, &atkbd->xl_bit); + else + __set_bit(i, &atkbd->xl_bit); + break; + } + } +} + +/* + * Encode the scancode, 0xe0 prefix, and high bit into a single integer, + * keeping kernel 2.4 compatibility for set 2 + */ +static unsigned int atkbd_compat_scancode(struct atkbd *atkbd, unsigned int code) +{ + if (atkbd->set == 3) { + if (atkbd->emul == 1) + code |= 0x100; + } else { + code = (code & 0x7f) | ((code & 0x80) << 1); + if (atkbd->emul == 1) + code |= 0x80; + } + + return code; +} + +/* + * 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 atkbd *atkbd = serio_get_drvdata(serio); + struct input_dev *dev = atkbd->dev; + unsigned int code = data; + int scroll = 0, hscroll = 0, click = -1; + int value; + unsigned short keycode; + +#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(dev, EV_MSC, MSC_RAW, code); + + if (atkbd->translated) { + + if (atkbd->emul || atkbd_need_xlate(atkbd->xl_bit, code)) { + atkbd->release = code >> 7; + code &= 0x7f; + } + + if (!atkbd->emul) + atkbd_calculate_xl_bit(atkbd, data); + } + + switch (code) { + case ATKBD_RET_BAT: + atkbd->enabled = 0; + serio_reconnect(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_ACK: + case ATKBD_RET_NAK: + if (printk_ratelimit()) + printk(KERN_WARNING "atkbd.c: Spurious %s on %s. " + "Some program might be trying access hardware directly.\n", + data == ATKBD_RET_ACK ? "ACK" : "NAK", serio->phys); + goto out; + case ATKBD_RET_ERR: + atkbd->err_count++; +#ifdef ATKBD_DEBUG + printk(KERN_DEBUG "atkbd.c: Keyboard on %s reports too many keys pressed.\n", serio->phys); +#endif + goto out; + } + + code = atkbd_compat_scancode(atkbd, code); + + if (atkbd->emul && --atkbd->emul) + goto out; + + keycode = atkbd->keycode[code]; + + if (keycode != ATKBD_KEY_NULL) + input_event(dev, EV_MSC, MSC_SCAN, code); + + switch (keycode) { + case ATKBD_KEY_NULL: + break; + case ATKBD_KEY_UNKNOWN: + 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(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: + if (atkbd->release) { + value = 0; + atkbd->last = 0; + } else if (!atkbd->softrepeat && test_bit(keycode, dev->key)) { + /* Workaround Toshiba laptop multiple keypress */ + value = time_before(jiffies, atkbd->time) && atkbd->last == code ? 1 : 2; + } else { + value = 1; + atkbd->last = code; + atkbd->time = jiffies + msecs_to_jiffies(dev->rep[REP_DELAY]) / 2; + } + + input_event(dev, EV_KEY, keycode, value); + input_sync(dev); + + if (value && test_bit(code, atkbd->force_release_mask)) { + input_report_key(dev, keycode, 0); + input_sync(dev); + } + } + + if (atkbd->scroll) { + if (click != -1) + input_report_key(dev, BTN_MIDDLE, click); + input_report_rel(dev, REL_WHEEL, scroll); + input_report_rel(dev, REL_HWHEEL, hscroll); + input_sync(dev); + } + + atkbd->release = 0; +out: + return IRQ_HANDLED; +} + +static int atkbd_set_repeat_rate(struct atkbd *atkbd) +{ + 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 }; + + struct input_dev *dev = atkbd->dev; + unsigned char param; + int i = 0, j = 0; + + while (i < ARRAY_SIZE(period) - 1 && period[i] < dev->rep[REP_PERIOD]) + i++; + dev->rep[REP_PERIOD] = period[i]; + + while (j < ARRAY_SIZE(delay) - 1 && delay[j] < dev->rep[REP_DELAY]) + j++; + dev->rep[REP_DELAY] = delay[j]; + + param = i | (j << 5); + return ps2_command(&atkbd->ps2dev, ¶m, ATKBD_CMD_SETREP); +} + +static int atkbd_set_leds(struct atkbd *atkbd) +{ + struct input_dev *dev = atkbd->dev; + unsigned char param[2]; + + 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); + if (ps2_command(&atkbd->ps2dev, param, ATKBD_CMD_SETLEDS)) + return -1; + + 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); + if (ps2_command(&atkbd->ps2dev, param, ATKBD_CMD_EX_SETLEDS)) + return -1; + } + + return 0; +} + +/* + * atkbd_event_work() is used to complete processing of events that + * can not be processed by input_event() which is often called from + * interrupt context. + */ + +static void atkbd_event_work(struct work_struct *work) +{ + struct atkbd *atkbd = container_of(work, struct atkbd, event_work.work); + + mutex_lock(&atkbd->event_mutex); + + if (test_and_clear_bit(ATKBD_LED_EVENT_BIT, &atkbd->event_mask)) + atkbd_set_leds(atkbd); + + if (test_and_clear_bit(ATKBD_REP_EVENT_BIT, &atkbd->event_mask)) + atkbd_set_repeat_rate(atkbd); + + mutex_unlock(&atkbd->event_mutex); +} + +/* + * Schedule switch for execution. We need to throttle requests, + * otherwise keyboard may become unresponsive. + */ +static void atkbd_schedule_event_work(struct atkbd *atkbd, int event_bit) +{ + unsigned long delay = msecs_to_jiffies(50); + + if (time_after(jiffies, atkbd->event_jiffies + delay)) + delay = 0; + + atkbd->event_jiffies = jiffies; + set_bit(event_bit, &atkbd->event_mask); + wmb(); + schedule_delayed_work(&atkbd->event_work, delay); +} + +/* + * Event callback from the input module. Events that change the state of + * the hardware are processed here. If action can not be performed in + * interrupt context it is offloaded to atkbd_event_work. + */ + +static int atkbd_event(struct input_dev *dev, + unsigned int type, unsigned int code, int value) +{ + struct atkbd *atkbd = input_get_drvdata(dev); + + if (!atkbd->write) + return -1; + + switch (type) { + + case EV_LED: + atkbd_schedule_event_work(atkbd, ATKBD_LED_EVENT_BIT); + return 0; + + case EV_REP: + if (!atkbd->softrepeat) + atkbd_schedule_event_work(atkbd, ATKBD_REP_EVENT_BIT); + 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 (!ps2_is_keyboard_id(param[0])) + 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); + + atkbd_disable(atkbd); + 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 */ + cancel_delayed_work_sync(&atkbd->event_work); + + sysfs_remove_group(&serio->dev.kobj, &atkbd_attribute_group); + input_unregister_device(atkbd->dev); + serio_close(serio); + serio_set_drvdata(serio, NULL); + kfree(atkbd); +} + +/* + * Most special keys (Fn+F?) on Dell laptops do not generate release + * events so we have to do it ourselves. + */ +static void atkbd_dell_laptop_keymap_fixup(struct atkbd *atkbd) +{ + const unsigned int forced_release_keys[] = { + 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8f, 0x93, + }; + int i; + + if (atkbd->set == 2) + for (i = 0; i < ARRAY_SIZE(forced_release_keys); i++) + __set_bit(forced_release_keys[i], + atkbd->force_release_mask); +} + +/* + * Perform fixup for HP system that doesn't generate release + * for its video switch + */ +static void atkbd_hp_keymap_fixup(struct atkbd *atkbd) +{ + const unsigned int forced_release_keys[] = { + 0x94, + }; + int i; + + if (atkbd->set == 2) + for (i = 0; i < ARRAY_SIZE(forced_release_keys); i++) + __set_bit(forced_release_keys[i], + atkbd->force_release_mask); +} + +/* + * Inventec system with broken key release on volume keys + */ +static void atkbd_inventec_keymap_fixup(struct atkbd *atkbd) +{ + const unsigned int forced_release_keys[] = { + 0xae, 0xb0, + }; + int i; + + if (atkbd->set == 2) + for (i = 0; i < ARRAY_SIZE(forced_release_keys); i++) + __set_bit(forced_release_keys[i], + atkbd->force_release_mask); +} + +/* + * Samsung NC10 with Fn+F? key release not working + */ +static void atkbd_samsung_keymap_fixup(struct atkbd *atkbd) +{ + const unsigned int forced_release_keys[] = { + 0x82, 0x83, 0x84, 0x86, 0x88, 0x89, 0xb3, 0xf7, 0xf9, + }; + int i; + + if (atkbd->set == 2) + for (i = 0; i < ARRAY_SIZE(forced_release_keys); i++) + __set_bit(forced_release_keys[i], + atkbd->force_release_mask); +} + +/* + * atkbd_set_keycode_table() initializes keyboard's keycode table + * according to the selected scancode set + */ + +static void atkbd_set_keycode_table(struct atkbd *atkbd) +{ + unsigned int scancode; + int i, j; + + memset(atkbd->keycode, 0, sizeof(atkbd->keycode)); + bitmap_zero(atkbd->force_release_mask, 512); + + if (atkbd->translated) { + for (i = 0; i < 128; i++) { + scancode = atkbd_unxlate_table[i]; + atkbd->keycode[i] = atkbd_set2_keycode[scancode]; + atkbd->keycode[i | 0x80] = atkbd_set2_keycode[scancode | 0x80]; + if (atkbd->scroll) + for (j = 0; j < ARRAY_SIZE(atkbd_scroll_keys); j++) + if ((scancode | 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++) { + scancode = atkbd_scroll_keys[i].set2; + atkbd->keycode[scancode] = atkbd_scroll_keys[i].keycode; + } + } + +/* + * HANGEUL and HANJA keys do not send release events so we need to + * generate such events ourselves + */ + scancode = atkbd_compat_scancode(atkbd, ATKBD_RET_HANGEUL); + atkbd->keycode[scancode] = KEY_HANGEUL; + __set_bit(scancode, atkbd->force_release_mask); + + scancode = atkbd_compat_scancode(atkbd, ATKBD_RET_HANJA); + atkbd->keycode[scancode] = KEY_HANJA; + __set_bit(scancode, atkbd->force_release_mask); + +/* + * Perform additional fixups + */ + if (atkbd_platform_fixup) + atkbd_platform_fixup(atkbd); +} + +/* + * atkbd_set_device_attrs() sets up keyboard's input device structure + */ + +static void atkbd_set_device_attrs(struct atkbd *atkbd) +{ + struct input_dev *input_dev = atkbd->dev; + int i; + + if (atkbd->extra) + snprintf(atkbd->name, sizeof(atkbd->name), + "AT Set 2 Extra keyboard"); + else + snprintf(atkbd->name, sizeof(atkbd->name), + "AT %s Set %d keyboard", + atkbd->translated ? "Translated" : "Raw", atkbd->set); + + snprintf(atkbd->phys, sizeof(atkbd->phys), + "%s/input0", atkbd->ps2dev.serio->phys); + + input_dev->name = atkbd->name; + input_dev->phys = atkbd->phys; + input_dev->id.bustype = BUS_I8042; + input_dev->id.vendor = 0x0001; + input_dev->id.product = atkbd->translated ? 1 : atkbd->set; + input_dev->id.version = atkbd->id; + input_dev->event = atkbd_event; + input_dev->dev.parent = &atkbd->ps2dev.serio->dev; + + input_set_drvdata(input_dev, atkbd); + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP) | + BIT_MASK(EV_MSC); + + if (atkbd->write) { + input_dev->evbit[0] |= BIT_MASK(EV_LED); + input_dev->ledbit[0] = BIT_MASK(LED_NUML) | + BIT_MASK(LED_CAPSL) | BIT_MASK(LED_SCROLLL); + } + + if (atkbd->extra) + input_dev->ledbit[0] |= BIT_MASK(LED_COMPOSE) | + BIT_MASK(LED_SUSPEND) | BIT_MASK(LED_SLEEP) | + BIT_MASK(LED_MUTE) | BIT_MASK(LED_MISC); + + if (!atkbd->softrepeat) { + input_dev->rep[REP_DELAY] = 250; + input_dev->rep[REP_PERIOD] = 33; + } + + input_dev->mscbit[0] = atkbd->softraw ? BIT_MASK(MSC_SCAN) : + BIT_MASK(MSC_RAW) | BIT_MASK(MSC_SCAN); + + if (atkbd->scroll) { + input_dev->evbit[0] |= BIT_MASK(EV_REL); + input_dev->relbit[0] = BIT_MASK(REL_WHEEL) | + BIT_MASK(REL_HWHEEL); + __set_bit(BTN_MIDDLE, input_dev->keybit); + } + + input_dev->keycode = atkbd->keycode; + input_dev->keycodesize = sizeof(unsigned short); + input_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], input_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; + struct input_dev *dev; + int err = -ENOMEM; + + atkbd = kzalloc(sizeof(struct atkbd), GFP_KERNEL); + dev = input_allocate_device(); + if (!atkbd || !dev) + goto fail1; + + atkbd->dev = dev; + ps2_init(&atkbd->ps2dev, serio); + INIT_DELAYED_WORK(&atkbd->event_work, atkbd_event_work); + mutex_init(&atkbd->event_mutex); + + 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->softrepeat) + atkbd->softraw = 1; + + serio_set_drvdata(serio, atkbd); + + err = serio_open(serio, drv); + if (err) + goto fail2; + + if (atkbd->write) { + + if (atkbd_probe(atkbd)) { + err = -ENODEV; + goto fail3; + } + + atkbd->set = atkbd_select_set(atkbd, atkbd_set, atkbd_extra); + atkbd_activate(atkbd); + + } else { + atkbd->set = 2; + atkbd->id = 0xab00; + } + + atkbd_set_keycode_table(atkbd); + atkbd_set_device_attrs(atkbd); + + err = sysfs_create_group(&serio->dev.kobj, &atkbd_attribute_group); + if (err) + goto fail3; + + atkbd_enable(atkbd); + + err = input_register_device(atkbd->dev); + if (err) + goto fail4; + + return 0; + + fail4: sysfs_remove_group(&serio->dev.kobj, &atkbd_attribute_group); + fail3: serio_close(serio); + fail2: serio_set_drvdata(serio, NULL); + fail1: input_free_device(dev); + kfree(atkbd); + return err; +} + +/* + * 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; + + if (!atkbd || !drv) { + printk(KERN_DEBUG "atkbd: reconnect request, but serio is disconnected, ignoring...\n"); + return -1; + } + + atkbd_disable(atkbd); + + if (atkbd->write) { + if (atkbd_probe(atkbd)) + return -1; + if (atkbd->set != atkbd_select_set(atkbd, atkbd->set, atkbd->extra)) + return -1; + + atkbd_activate(atkbd); + +/* + * Restore repeat rate and LEDs (that were reset by atkbd_activate) + * to pre-resume state + */ + if (!atkbd->softrepeat) + atkbd_set_repeat_rate(atkbd); + atkbd_set_leds(atkbd); + } + + 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) +{ + struct input_dev *old_dev, *new_dev; + unsigned long value; + int err; + unsigned char old_extra, old_set; + + if (!atkbd->write) + return -EIO; + + if (strict_strtoul(buf, 10, &value) || value > 1) + return -EINVAL; + + if (atkbd->extra != value) { + /* + * Since device's properties will change we need to + * unregister old device. But allocate and register + * new one first to make sure we have it. + */ + old_dev = atkbd->dev; + old_extra = atkbd->extra; + old_set = atkbd->set; + + new_dev = input_allocate_device(); + if (!new_dev) + return -ENOMEM; + + atkbd->dev = new_dev; + atkbd->set = atkbd_select_set(atkbd, atkbd->set, value); + atkbd_activate(atkbd); + atkbd_set_keycode_table(atkbd); + atkbd_set_device_attrs(atkbd); + + err = input_register_device(atkbd->dev); + if (err) { + input_free_device(new_dev); + + atkbd->dev = old_dev; + atkbd->set = atkbd_select_set(atkbd, old_set, old_extra); + atkbd_set_keycode_table(atkbd); + atkbd_set_device_attrs(atkbd); + + return err; + } + input_unregister_device(old_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) +{ + struct input_dev *old_dev, *new_dev; + unsigned long value; + int err; + unsigned char old_scroll; + + if (strict_strtoul(buf, 10, &value) || value > 1) + return -EINVAL; + + if (atkbd->scroll != value) { + old_dev = atkbd->dev; + old_scroll = atkbd->scroll; + + new_dev = input_allocate_device(); + if (!new_dev) + return -ENOMEM; + + atkbd->dev = new_dev; + atkbd->scroll = value; + atkbd_set_keycode_table(atkbd); + atkbd_set_device_attrs(atkbd); + + err = input_register_device(atkbd->dev); + if (err) { + input_free_device(new_dev); + + atkbd->scroll = old_scroll; + atkbd->dev = old_dev; + atkbd_set_keycode_table(atkbd); + atkbd_set_device_attrs(atkbd); + + return err; + } + input_unregister_device(old_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) +{ + struct input_dev *old_dev, *new_dev; + unsigned long value; + int err; + unsigned char old_set, old_extra; + + if (!atkbd->write) + return -EIO; + + if (strict_strtoul(buf, 10, &value) || (value != 2 && value != 3)) + return -EINVAL; + + if (atkbd->set != value) { + old_dev = atkbd->dev; + old_extra = atkbd->extra; + old_set = atkbd->set; + + new_dev = input_allocate_device(); + if (!new_dev) + return -ENOMEM; + + atkbd->dev = new_dev; + atkbd->set = atkbd_select_set(atkbd, value, atkbd->extra); + atkbd_activate(atkbd); + atkbd_set_keycode_table(atkbd); + atkbd_set_device_attrs(atkbd); + + err = input_register_device(atkbd->dev); + if (err) { + input_free_device(new_dev); + + atkbd->dev = old_dev; + atkbd->set = atkbd_select_set(atkbd, old_set, old_extra); + atkbd_set_keycode_table(atkbd); + atkbd_set_device_attrs(atkbd); + + return err; + } + input_unregister_device(old_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) +{ + struct input_dev *old_dev, *new_dev; + unsigned long value; + int err; + unsigned char old_softrepeat, old_softraw; + + if (!atkbd->write) + return -EIO; + + if (strict_strtoul(buf, 10, &value) || value > 1) + return -EINVAL; + + if (atkbd->softrepeat != value) { + old_dev = atkbd->dev; + old_softrepeat = atkbd->softrepeat; + old_softraw = atkbd->softraw; + + new_dev = input_allocate_device(); + if (!new_dev) + return -ENOMEM; + + atkbd->dev = new_dev; + atkbd->softrepeat = value; + if (atkbd->softrepeat) + atkbd->softraw = 1; + atkbd_set_device_attrs(atkbd); + + err = input_register_device(atkbd->dev); + if (err) { + input_free_device(new_dev); + + atkbd->dev = old_dev; + atkbd->softrepeat = old_softrepeat; + atkbd->softraw = old_softraw; + atkbd_set_device_attrs(atkbd); + + return err; + } + input_unregister_device(old_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) +{ + struct input_dev *old_dev, *new_dev; + unsigned long value; + int err; + unsigned char old_softraw; + + if (strict_strtoul(buf, 10, &value) || value > 1) + return -EINVAL; + + if (atkbd->softraw != value) { + old_dev = atkbd->dev; + old_softraw = atkbd->softraw; + + new_dev = input_allocate_device(); + if (!new_dev) + return -ENOMEM; + + atkbd->dev = new_dev; + atkbd->softraw = value; + atkbd_set_device_attrs(atkbd); + + err = input_register_device(atkbd->dev); + if (err) { + input_free_device(new_dev); + + atkbd->dev = old_dev; + atkbd->softraw = old_softraw; + atkbd_set_device_attrs(atkbd); + + return err; + } + input_unregister_device(old_dev); + } + return count; +} + +static ssize_t atkbd_show_err_count(struct atkbd *atkbd, char *buf) +{ + return sprintf(buf, "%lu\n", atkbd->err_count); +} + +static int __init atkbd_setup_fixup(const struct dmi_system_id *id) +{ + atkbd_platform_fixup = id->driver_data; + return 0; +} + +static struct dmi_system_id atkbd_dmi_quirk_table[] __initdata = { + { + .ident = "Dell Laptop", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_CHASSIS_TYPE, "8"), /* Portable */ + }, + .callback = atkbd_setup_fixup, + .driver_data = atkbd_dell_laptop_keymap_fixup, + }, + { + .ident = "HP 2133", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Hewlett-Packard"), + DMI_MATCH(DMI_PRODUCT_NAME, "HP 2133"), + }, + .callback = atkbd_setup_fixup, + .driver_data = atkbd_hp_keymap_fixup, + }, + { + .ident = "Inventec Symphony", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "INVENTEC"), + DMI_MATCH(DMI_PRODUCT_NAME, "SYMPHONY 6.0/7.0"), + }, + .callback = atkbd_setup_fixup, + .driver_data = atkbd_inventec_keymap_fixup, + }, + { + .ident = "Samsung NC10", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), + DMI_MATCH(DMI_PRODUCT_NAME, "NC10"), + }, + .callback = atkbd_setup_fixup, + .driver_data = atkbd_samsung_keymap_fixup, + }, + { } +}; + +static int __init atkbd_init(void) +{ + dmi_check_system(atkbd_dmi_quirk_table); + + return serio_register_driver(&atkbd_drv); +} + +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/bf54x-keys.c b/drivers/input/keyboard/bf54x-keys.c new file mode 100644 index 0000000..1928401 --- /dev/null +++ b/drivers/input/keyboard/bf54x-keys.c @@ -0,0 +1,421 @@ +/* + * File: drivers/input/keyboard/bf54x-keys.c + * Based on: + * Author: Michael Hennerich <hennerich@blackfin.uclinux.org> + * + * Created: + * Description: keypad driver for Analog Devices Blackfin BF54x Processors + * + * + * Modified: + * Copyright 2007-2008 Analog Devices Inc. + * + * Bugs: Enter bugs at http://blackfin.uclinux.org/ + * + * 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, see the file COPYING, or write + * to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <linux/module.h> + +#include <linux/init.h> +#include <linux/fs.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/sched.h> +#include <linux/pm.h> +#include <linux/sysctl.h> +#include <linux/proc_fs.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/input.h> + +#include <asm/portmux.h> +#include <mach/bf54x_keys.h> + +#define DRV_NAME "bf54x-keys" +#define TIME_SCALE 100 /* 100 ns */ +#define MAX_MULT (0xFF * TIME_SCALE) +#define MAX_RC 8 /* Max Row/Col */ + +static const u16 per_rows[] = { + P_KEY_ROW7, + P_KEY_ROW6, + P_KEY_ROW5, + P_KEY_ROW4, + P_KEY_ROW3, + P_KEY_ROW2, + P_KEY_ROW1, + P_KEY_ROW0, + 0 +}; + +static const u16 per_cols[] = { + P_KEY_COL7, + P_KEY_COL6, + P_KEY_COL5, + P_KEY_COL4, + P_KEY_COL3, + P_KEY_COL2, + P_KEY_COL1, + P_KEY_COL0, + 0 +}; + +struct bf54x_kpad { + struct input_dev *input; + int irq; + unsigned short lastkey; + unsigned short *keycode; + struct timer_list timer; + unsigned int keyup_test_jiffies; + unsigned short kpad_msel; + unsigned short kpad_prescale; + unsigned short kpad_ctl; +}; + +static inline int bfin_kpad_find_key(struct bf54x_kpad *bf54x_kpad, + struct input_dev *input, u16 keyident) +{ + u16 i; + + for (i = 0; i < input->keycodemax; i++) + if (bf54x_kpad->keycode[i + input->keycodemax] == keyident) + return bf54x_kpad->keycode[i]; + return -1; +} + +static inline void bfin_keycodecpy(unsigned short *keycode, + const unsigned int *pdata_kc, + unsigned short keymapsize) +{ + unsigned int i; + + for (i = 0; i < keymapsize; i++) { + keycode[i] = pdata_kc[i] & 0xffff; + keycode[i + keymapsize] = pdata_kc[i] >> 16; + } +} + +static inline u16 bfin_kpad_get_prescale(u32 timescale) +{ + u32 sclk = get_sclk(); + + return ((((sclk / 1000) * timescale) / 1024) - 1); +} + +static inline u16 bfin_kpad_get_keypressed(struct bf54x_kpad *bf54x_kpad) +{ + return (bfin_read_KPAD_STAT() & KPAD_PRESSED); +} + +static inline void bfin_kpad_clear_irq(void) +{ + bfin_write_KPAD_STAT(0xFFFF); + bfin_write_KPAD_ROWCOL(0xFFFF); +} + +static void bfin_kpad_timer(unsigned long data) +{ + struct platform_device *pdev = (struct platform_device *) data; + struct bf54x_kpad *bf54x_kpad = platform_get_drvdata(pdev); + + if (bfin_kpad_get_keypressed(bf54x_kpad)) { + /* Try again later */ + mod_timer(&bf54x_kpad->timer, + jiffies + bf54x_kpad->keyup_test_jiffies); + return; + } + + input_report_key(bf54x_kpad->input, bf54x_kpad->lastkey, 0); + input_sync(bf54x_kpad->input); + + /* Clear IRQ Status */ + + bfin_kpad_clear_irq(); + enable_irq(bf54x_kpad->irq); +} + +static irqreturn_t bfin_kpad_isr(int irq, void *dev_id) +{ + struct platform_device *pdev = dev_id; + struct bf54x_kpad *bf54x_kpad = platform_get_drvdata(pdev); + struct input_dev *input = bf54x_kpad->input; + int key; + u16 rowcol = bfin_read_KPAD_ROWCOL(); + + key = bfin_kpad_find_key(bf54x_kpad, input, rowcol); + + input_report_key(input, key, 1); + input_sync(input); + + if (bfin_kpad_get_keypressed(bf54x_kpad)) { + disable_irq(bf54x_kpad->irq); + bf54x_kpad->lastkey = key; + mod_timer(&bf54x_kpad->timer, + jiffies + bf54x_kpad->keyup_test_jiffies); + } else { + input_report_key(input, key, 0); + input_sync(input); + + bfin_kpad_clear_irq(); + } + + return IRQ_HANDLED; +} + +static int __devinit bfin_kpad_probe(struct platform_device *pdev) +{ + struct bf54x_kpad *bf54x_kpad; + struct bfin_kpad_platform_data *pdata = pdev->dev.platform_data; + struct input_dev *input; + int i, error; + + if (!pdata->rows || !pdata->cols || !pdata->keymap) { + printk(KERN_ERR DRV_NAME + ": No rows, cols or keymap from pdata\n"); + return -EINVAL; + } + + if (!pdata->keymapsize || + pdata->keymapsize > (pdata->rows * pdata->cols)) { + printk(KERN_ERR DRV_NAME ": Invalid keymapsize\n"); + return -EINVAL; + } + + bf54x_kpad = kzalloc(sizeof(struct bf54x_kpad), GFP_KERNEL); + if (!bf54x_kpad) + return -ENOMEM; + + platform_set_drvdata(pdev, bf54x_kpad); + + /* Allocate memory for keymap followed by private LUT */ + bf54x_kpad->keycode = kmalloc(pdata->keymapsize * + sizeof(unsigned short) * 2, GFP_KERNEL); + if (!bf54x_kpad->keycode) { + error = -ENOMEM; + goto out; + } + + if (!pdata->debounce_time || !pdata->debounce_time > MAX_MULT || + !pdata->coldrive_time || !pdata->coldrive_time > MAX_MULT) { + printk(KERN_ERR DRV_NAME + ": Invalid Debounce/Columdrive Time from pdata\n"); + bfin_write_KPAD_MSEL(0xFF0); /* Default MSEL */ + } else { + bfin_write_KPAD_MSEL( + ((pdata->debounce_time / TIME_SCALE) + & DBON_SCALE) | + (((pdata->coldrive_time / TIME_SCALE) << 8) + & COLDRV_SCALE)); + + } + + if (!pdata->keyup_test_interval) + bf54x_kpad->keyup_test_jiffies = msecs_to_jiffies(50); + else + bf54x_kpad->keyup_test_jiffies = + msecs_to_jiffies(pdata->keyup_test_interval); + + if (peripheral_request_list((u16 *)&per_rows[MAX_RC - pdata->rows], + DRV_NAME)) { + printk(KERN_ERR DRV_NAME + ": Requesting Peripherals failed\n"); + error = -EFAULT; + goto out0; + } + + if (peripheral_request_list((u16 *)&per_cols[MAX_RC - pdata->cols], + DRV_NAME)) { + printk(KERN_ERR DRV_NAME + ": Requesting Peripherals failed\n"); + error = -EFAULT; + goto out1; + } + + bf54x_kpad->irq = platform_get_irq(pdev, 0); + if (bf54x_kpad->irq < 0) { + error = -ENODEV; + goto out2; + } + + error = request_irq(bf54x_kpad->irq, bfin_kpad_isr, + IRQF_SAMPLE_RANDOM, DRV_NAME, pdev); + if (error) { + printk(KERN_ERR DRV_NAME + ": unable to claim irq %d; error %d\n", + bf54x_kpad->irq, error); + goto out2; + } + + input = input_allocate_device(); + if (!input) { + error = -ENOMEM; + goto out3; + } + + bf54x_kpad->input = input; + + input->name = pdev->name; + input->phys = "bf54x-keys/input0"; + input->dev.parent = &pdev->dev; + + input_set_drvdata(input, bf54x_kpad); + + input->id.bustype = BUS_HOST; + input->id.vendor = 0x0001; + input->id.product = 0x0001; + input->id.version = 0x0100; + + input->keycodesize = sizeof(unsigned short); + input->keycodemax = pdata->keymapsize; + input->keycode = bf54x_kpad->keycode; + + bfin_keycodecpy(bf54x_kpad->keycode, pdata->keymap, pdata->keymapsize); + + /* setup input device */ + __set_bit(EV_KEY, input->evbit); + + if (pdata->repeat) + __set_bit(EV_REP, input->evbit); + + for (i = 0; i < input->keycodemax; i++) + __set_bit(bf54x_kpad->keycode[i] & KEY_MAX, input->keybit); + __clear_bit(KEY_RESERVED, input->keybit); + + error = input_register_device(input); + if (error) { + printk(KERN_ERR DRV_NAME + ": Unable to register input device (%d)\n", error); + goto out4; + } + + /* Init Keypad Key Up/Release test timer */ + + setup_timer(&bf54x_kpad->timer, bfin_kpad_timer, (unsigned long) pdev); + + bfin_write_KPAD_PRESCALE(bfin_kpad_get_prescale(TIME_SCALE)); + + bfin_write_KPAD_CTL((((pdata->cols - 1) << 13) & KPAD_COLEN) | + (((pdata->rows - 1) << 10) & KPAD_ROWEN) | + (2 & KPAD_IRQMODE)); + + bfin_write_KPAD_CTL(bfin_read_KPAD_CTL() | KPAD_EN); + + device_init_wakeup(&pdev->dev, 1); + + printk(KERN_ERR DRV_NAME + ": Blackfin BF54x Keypad registered IRQ %d\n", bf54x_kpad->irq); + + return 0; + +out4: + input_free_device(input); +out3: + free_irq(bf54x_kpad->irq, pdev); +out2: + peripheral_free_list((u16 *)&per_cols[MAX_RC - pdata->cols]); +out1: + peripheral_free_list((u16 *)&per_rows[MAX_RC - pdata->rows]); +out0: + kfree(bf54x_kpad->keycode); +out: + kfree(bf54x_kpad); + platform_set_drvdata(pdev, NULL); + + return error; +} + +static int __devexit bfin_kpad_remove(struct platform_device *pdev) +{ + struct bfin_kpad_platform_data *pdata = pdev->dev.platform_data; + struct bf54x_kpad *bf54x_kpad = platform_get_drvdata(pdev); + + del_timer_sync(&bf54x_kpad->timer); + free_irq(bf54x_kpad->irq, pdev); + + input_unregister_device(bf54x_kpad->input); + + peripheral_free_list((u16 *)&per_rows[MAX_RC - pdata->rows]); + peripheral_free_list((u16 *)&per_cols[MAX_RC - pdata->cols]); + + kfree(bf54x_kpad->keycode); + kfree(bf54x_kpad); + platform_set_drvdata(pdev, NULL); + + return 0; +} + +#ifdef CONFIG_PM +static int bfin_kpad_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct bf54x_kpad *bf54x_kpad = platform_get_drvdata(pdev); + + bf54x_kpad->kpad_msel = bfin_read_KPAD_MSEL(); + bf54x_kpad->kpad_prescale = bfin_read_KPAD_PRESCALE(); + bf54x_kpad->kpad_ctl = bfin_read_KPAD_CTL(); + + if (device_may_wakeup(&pdev->dev)) + enable_irq_wake(bf54x_kpad->irq); + + return 0; +} + +static int bfin_kpad_resume(struct platform_device *pdev) +{ + struct bf54x_kpad *bf54x_kpad = platform_get_drvdata(pdev); + + bfin_write_KPAD_MSEL(bf54x_kpad->kpad_msel); + bfin_write_KPAD_PRESCALE(bf54x_kpad->kpad_prescale); + bfin_write_KPAD_CTL(bf54x_kpad->kpad_ctl); + + if (device_may_wakeup(&pdev->dev)) + disable_irq_wake(bf54x_kpad->irq); + + return 0; +} +#else +# define bfin_kpad_suspend NULL +# define bfin_kpad_resume NULL +#endif + +struct platform_driver bfin_kpad_device_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + }, + .probe = bfin_kpad_probe, + .remove = __devexit_p(bfin_kpad_remove), + .suspend = bfin_kpad_suspend, + .resume = bfin_kpad_resume, +}; + +static int __init bfin_kpad_init(void) +{ + return platform_driver_register(&bfin_kpad_device_driver); +} + +static void __exit bfin_kpad_exit(void) +{ + platform_driver_unregister(&bfin_kpad_device_driver); +} + +module_init(bfin_kpad_init); +module_exit(bfin_kpad_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>"); +MODULE_DESCRIPTION("Keypad driver for BF54x Processors"); +MODULE_ALIAS("platform:bf54x-keys"); diff --git a/drivers/input/keyboard/corgikbd.c b/drivers/input/keyboard/corgikbd.c new file mode 100644 index 0000000..c8ed065 --- /dev/null +++ b/drivers/input/keyboard/corgikbd.c @@ -0,0 +1,416 @@ +/* + * 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/platform_device.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/jiffies.h> +#include <linux/module.h> +#include <linux/slab.h> + +#include <mach/corgi.h> +#include <mach/hardware.h> +#include <mach/pxa-regs.h> +#include <mach/pxa2xx-gpio.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 */ +#define NR_SCANCODES ( SCANCODE(KB_ROWS-1,KB_COLS-1) +1 +1 ) + +#define SCAN_INTERVAL (50) /* ms */ +#define HINGE_SCAN_INTERVAL (250) /* ms */ + +#define CORGI_KEY_CALENDER KEY_F1 +#define CORGI_KEY_ADDRESS KEY_F2 +#define CORGI_KEY_FN KEY_F3 +#define CORGI_KEY_CANCEL KEY_F4 +#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_MAIL KEY_F10 +#define CORGI_KEY_OK KEY_F11 +#define CORGI_KEY_MENU KEY_F12 + +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 */ + CORGI_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, CORGI_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 */ +}; + + +struct corgikbd { + unsigned char keycode[ARRAY_SIZE(corgikbd_keycode)]; + struct input_dev *input; + + spinlock_t lock; + struct timer_list timer; + struct timer_list htimer; + + unsigned int suspended; + unsigned long suspend_jiffies; +}; + +#define KB_DISCHARGE_DELAY 10 +#define KB_ACTIVATE_DELAY 10 + +/* Helper functions for reading the keyboard matrix + * Note: We should really be using the generic gpio functions 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) +{ + unsigned int row, col, rowd; + unsigned long flags; + unsigned int num_pressed; + + if (corgikbd_data->suspended) + return; + + spin_lock_irqsave(&corgikbd_data->lock, flags); + + 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++) { + unsigned int scancode, pressed; + + scancode = SCANCODE(row, col); + pressed = rowd & KB_ROWMASK(row); + + input_report_key(corgikbd_data->input, corgikbd_data->keycode[scancode], pressed); + + if (pressed) + num_pressed++; + + if (pressed && (corgikbd_data->keycode[scancode] == CORGI_KEY_OFF) + && time_after(jiffies, corgikbd_data->suspend_jiffies + HZ)) { + input_event(corgikbd_data->input, EV_PWR, CORGI_KEY_OFF, 1); + corgikbd_data->suspend_jiffies=jiffies; + } + } + 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 + msecs_to_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 corgikbd *corgikbd_data = dev_id; + + if (!timer_pending(&corgikbd_data->timer)) { + /** wait chattering delay **/ + udelay(20); + corgikbd_scankeyboard(corgikbd_data); + } + + 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); +} + +/* + * The hinge switches generate no interrupt so they need to be + * monitored by a timer. + * + * We debounce the switches and pass them to the input system. + * + * gprr == 0x00 - Keyboard with Landscape Screen + * 0x08 - No Keyboard with Portrait Screen + * 0x0c - Keyboard and Screen Closed + */ + +#define READ_GPIO_BIT(x) (GPLR(x) & GPIO_bit(x)) +#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(&corgiscoop_device.dev, SCOOP_GPRR) & (CORGI_SCP_SWA | CORGI_SCP_SWB); + gprr |= (READ_GPIO_BIT(CORGI_GPIO_AK_INT) != 0); + 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); + + input_report_switch(corgikbd_data->input, SW_LID, ((sharpsl_hinge_state & CORGI_SCP_SWA) != 0)); + input_report_switch(corgikbd_data->input, SW_TABLET_MODE, ((sharpsl_hinge_state & CORGI_SCP_SWB) != 0)); + input_report_switch(corgikbd_data->input, SW_HEADPHONE_INSERT, (READ_GPIO_BIT(CORGI_GPIO_AK_INT) != 0)); + input_sync(corgikbd_data->input); + + spin_unlock_irqrestore(&corgikbd_data->lock, flags); + } + } + mod_timer(&corgikbd_data->htimer, jiffies + msecs_to_jiffies(HINGE_SCAN_INTERVAL)); +} + +#ifdef CONFIG_PM +static int corgikbd_suspend(struct platform_device *dev, pm_message_t state) +{ + int i; + struct corgikbd *corgikbd = platform_get_drvdata(dev); + + corgikbd->suspended = 1; + /* strobe 0 is the power key so this can't be made an input for + powersaving therefore i = 1 */ + for (i = 1; i < CORGI_KEY_STROBE_NUM; i++) + pxa_gpio_mode(CORGI_GPIO_KEY_STROBE(i) | GPIO_IN); + + return 0; +} + +static int corgikbd_resume(struct platform_device *dev) +{ + int i; + struct corgikbd *corgikbd = platform_get_drvdata(dev); + + for (i = 1; i < CORGI_KEY_STROBE_NUM; i++) + pxa_gpio_mode(CORGI_GPIO_KEY_STROBE(i) | GPIO_OUT | GPIO_DFLT_HIGH); + + /* Upon resume, ignore the suspend key for a short while */ + corgikbd->suspend_jiffies=jiffies; + corgikbd->suspended = 0; + + return 0; +} +#else +#define corgikbd_suspend NULL +#define corgikbd_resume NULL +#endif + +static int __init corgikbd_probe(struct platform_device *pdev) +{ + struct corgikbd *corgikbd; + struct input_dev *input_dev; + int i, err = -ENOMEM; + + corgikbd = kzalloc(sizeof(struct corgikbd), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!corgikbd || !input_dev) + goto fail; + + platform_set_drvdata(pdev, corgikbd); + + corgikbd->input = input_dev; + 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; + + corgikbd->suspend_jiffies=jiffies; + + memcpy(corgikbd->keycode, corgikbd_keycode, sizeof(corgikbd->keycode)); + + input_dev->name = "Corgi Keyboard"; + input_dev->phys = "corgikbd/input0"; + input_dev->id.bustype = BUS_HOST; + input_dev->id.vendor = 0x0001; + input_dev->id.product = 0x0001; + input_dev->id.version = 0x0100; + input_dev->dev.parent = &pdev->dev; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP) | + BIT_MASK(EV_PWR) | BIT_MASK(EV_SW); + input_dev->keycode = corgikbd->keycode; + input_dev->keycodesize = sizeof(unsigned char); + input_dev->keycodemax = ARRAY_SIZE(corgikbd_keycode); + + for (i = 0; i < ARRAY_SIZE(corgikbd_keycode); i++) + set_bit(corgikbd->keycode[i], input_dev->keybit); + clear_bit(0, input_dev->keybit); + set_bit(SW_LID, input_dev->swbit); + set_bit(SW_TABLET_MODE, input_dev->swbit); + set_bit(SW_HEADPHONE_INSERT, input_dev->swbit); + + err = input_register_device(corgikbd->input); + if (err) + goto fail; + + mod_timer(&corgikbd->htimer, jiffies + msecs_to_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, + IRQF_DISABLED | IRQF_TRIGGER_RISING, + "corgikbd", corgikbd)) + printk(KERN_WARNING "corgikbd: Can't get IRQ: %d!\n", i); + } + + /* 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); + + /* Setup the headphone jack as an input */ + pxa_gpio_mode(CORGI_GPIO_AK_INT | GPIO_IN); + + return 0; + + fail: input_free_device(input_dev); + kfree(corgikbd); + return err; +} + +static int corgikbd_remove(struct platform_device *pdev) +{ + int i; + struct corgikbd *corgikbd = platform_get_drvdata(pdev); + + 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 platform_driver corgikbd_driver = { + .probe = corgikbd_probe, + .remove = corgikbd_remove, + .suspend = corgikbd_suspend, + .resume = corgikbd_resume, + .driver = { + .name = "corgi-keyboard", + .owner = THIS_MODULE, + }, +}; + +static int __devinit corgikbd_init(void) +{ + return platform_driver_register(&corgikbd_driver); +} + +static void __exit corgikbd_exit(void) +{ + platform_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("GPL v2"); +MODULE_ALIAS("platform:corgi-keyboard"); diff --git a/drivers/input/keyboard/gpio_keys.c b/drivers/input/keyboard/gpio_keys.c new file mode 100644 index 0000000..05f3f43 --- /dev/null +++ b/drivers/input/keyboard/gpio_keys.c @@ -0,0 +1,276 @@ +/* + * Driver for keys on GPIO lines capable of generating interrupts. + * + * Copyright 2005 Phil Blundell + * + * 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/fs.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/sched.h> +#include <linux/pm.h> +#include <linux/sysctl.h> +#include <linux/proc_fs.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/input.h> +#include <linux/gpio_keys.h> + +#include <asm/gpio.h> + +struct gpio_button_data { + struct gpio_keys_button *button; + struct input_dev *input; + struct timer_list timer; +}; + +struct gpio_keys_drvdata { + struct input_dev *input; + struct gpio_button_data data[0]; +}; + +static void gpio_keys_report_event(struct gpio_button_data *bdata) +{ + struct gpio_keys_button *button = bdata->button; + struct input_dev *input = bdata->input; + unsigned int type = button->type ?: EV_KEY; + int state = (gpio_get_value(button->gpio) ? 1 : 0) ^ button->active_low; + + input_event(input, type, button->code, !!state); + input_sync(input); +} + +static void gpio_check_button(unsigned long _data) +{ + struct gpio_button_data *data = (struct gpio_button_data *)_data; + + gpio_keys_report_event(data); +} + +static irqreturn_t gpio_keys_isr(int irq, void *dev_id) +{ + struct gpio_button_data *bdata = dev_id; + struct gpio_keys_button *button = bdata->button; + + BUG_ON(irq != gpio_to_irq(button->gpio)); + + if (button->debounce_interval) + mod_timer(&bdata->timer, + jiffies + msecs_to_jiffies(button->debounce_interval)); + else + gpio_keys_report_event(bdata); + + return IRQ_HANDLED; +} + +static int __devinit gpio_keys_probe(struct platform_device *pdev) +{ + struct gpio_keys_platform_data *pdata = pdev->dev.platform_data; + struct gpio_keys_drvdata *ddata; + struct input_dev *input; + int i, error; + int wakeup = 0; + + ddata = kzalloc(sizeof(struct gpio_keys_drvdata) + + pdata->nbuttons * sizeof(struct gpio_button_data), + GFP_KERNEL); + input = input_allocate_device(); + if (!ddata || !input) { + error = -ENOMEM; + goto fail1; + } + + platform_set_drvdata(pdev, ddata); + + input->name = pdev->name; + input->phys = "gpio-keys/input0"; + input->dev.parent = &pdev->dev; + + input->id.bustype = BUS_HOST; + input->id.vendor = 0x0001; + input->id.product = 0x0001; + input->id.version = 0x0100; + + ddata->input = input; + + for (i = 0; i < pdata->nbuttons; i++) { + struct gpio_keys_button *button = &pdata->buttons[i]; + struct gpio_button_data *bdata = &ddata->data[i]; + int irq; + unsigned int type = button->type ?: EV_KEY; + + bdata->input = input; + bdata->button = button; + setup_timer(&bdata->timer, + gpio_check_button, (unsigned long)bdata); + + error = gpio_request(button->gpio, button->desc ?: "gpio_keys"); + if (error < 0) { + pr_err("gpio-keys: failed to request GPIO %d," + " error %d\n", button->gpio, error); + goto fail2; + } + + error = gpio_direction_input(button->gpio); + if (error < 0) { + pr_err("gpio-keys: failed to configure input" + " direction for GPIO %d, error %d\n", + button->gpio, error); + gpio_free(button->gpio); + goto fail2; + } + + irq = gpio_to_irq(button->gpio); + if (irq < 0) { + error = irq; + pr_err("gpio-keys: Unable to get irq number" + " for GPIO %d, error %d\n", + button->gpio, error); + gpio_free(button->gpio); + goto fail2; + } + + error = request_irq(irq, gpio_keys_isr, + IRQF_SAMPLE_RANDOM | IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING, + button->desc ? button->desc : "gpio_keys", + bdata); + if (error) { + pr_err("gpio-keys: Unable to claim irq %d; error %d\n", + irq, error); + gpio_free(button->gpio); + goto fail2; + } + + if (button->wakeup) + wakeup = 1; + + input_set_capability(input, type, button->code); + } + + error = input_register_device(input); + if (error) { + pr_err("gpio-keys: Unable to register input device, " + "error: %d\n", error); + goto fail2; + } + + device_init_wakeup(&pdev->dev, wakeup); + + return 0; + + fail2: + while (--i >= 0) { + free_irq(gpio_to_irq(pdata->buttons[i].gpio), &ddata->data[i]); + if (pdata->buttons[i].debounce_interval) + del_timer_sync(&ddata->data[i].timer); + gpio_free(pdata->buttons[i].gpio); + } + + platform_set_drvdata(pdev, NULL); + fail1: + input_free_device(input); + kfree(ddata); + + return error; +} + +static int __devexit gpio_keys_remove(struct platform_device *pdev) +{ + struct gpio_keys_platform_data *pdata = pdev->dev.platform_data; + struct gpio_keys_drvdata *ddata = platform_get_drvdata(pdev); + struct input_dev *input = ddata->input; + int i; + + device_init_wakeup(&pdev->dev, 0); + + for (i = 0; i < pdata->nbuttons; i++) { + int irq = gpio_to_irq(pdata->buttons[i].gpio); + free_irq(irq, &ddata->data[i]); + if (pdata->buttons[i].debounce_interval) + del_timer_sync(&ddata->data[i].timer); + gpio_free(pdata->buttons[i].gpio); + } + + input_unregister_device(input); + + return 0; +} + + +#ifdef CONFIG_PM +static int gpio_keys_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct gpio_keys_platform_data *pdata = pdev->dev.platform_data; + int i; + + if (device_may_wakeup(&pdev->dev)) { + for (i = 0; i < pdata->nbuttons; i++) { + struct gpio_keys_button *button = &pdata->buttons[i]; + if (button->wakeup) { + int irq = gpio_to_irq(button->gpio); + enable_irq_wake(irq); + } + } + } + + return 0; +} + +static int gpio_keys_resume(struct platform_device *pdev) +{ + struct gpio_keys_platform_data *pdata = pdev->dev.platform_data; + int i; + + if (device_may_wakeup(&pdev->dev)) { + for (i = 0; i < pdata->nbuttons; i++) { + struct gpio_keys_button *button = &pdata->buttons[i]; + if (button->wakeup) { + int irq = gpio_to_irq(button->gpio); + disable_irq_wake(irq); + } + } + } + + return 0; +} +#else +#define gpio_keys_suspend NULL +#define gpio_keys_resume NULL +#endif + +static struct platform_driver gpio_keys_device_driver = { + .probe = gpio_keys_probe, + .remove = __devexit_p(gpio_keys_remove), + .suspend = gpio_keys_suspend, + .resume = gpio_keys_resume, + .driver = { + .name = "gpio-keys", + .owner = THIS_MODULE, + } +}; + +static int __init gpio_keys_init(void) +{ + return platform_driver_register(&gpio_keys_device_driver); +} + +static void __exit gpio_keys_exit(void) +{ + platform_driver_unregister(&gpio_keys_device_driver); +} + +module_init(gpio_keys_init); +module_exit(gpio_keys_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Phil Blundell <pb@handhelds.org>"); +MODULE_DESCRIPTION("Keyboard driver for CPU GPIOs"); +MODULE_ALIAS("platform:gpio-keys"); diff --git a/drivers/input/keyboard/hil_kbd.c b/drivers/input/keyboard/hil_kbd.c new file mode 100644 index 0000000..71c1971 --- /dev/null +++ b/drivers/input/keyboard/hil_kbd.c @@ -0,0 +1,402 @@ +/* + * 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/semaphore.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] __read_mostly = + { 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] __read_mostly = + { HIL_KEYCODES_SET3 }; + +static const 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 hil_kbd *kbd; + hil_packet packet; + int idx; + + kbd = serio_get_drvdata(serio); + BUG_ON(kbd == NULL); + + 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 = serio_get_drvdata(serio); + BUG_ON(kbd == NULL); + + serio_close(serio); + input_unregister_device(kbd->dev); + kfree(kbd); +} + +static int hil_kbd_connect(struct serio *serio, struct serio_driver *drv) +{ + struct hil_kbd *kbd; + uint8_t did, *idd; + int i; + + kbd = kzalloc(sizeof(*kbd), GFP_KERNEL); + if (!kbd) + return -ENOMEM; + + kbd->dev = input_allocate_device(); + if (!kbd->dev) + goto bail0; + + if (serio_open(serio, drv)) + goto bail1; + + serio_set_drvdata(serio, kbd); + kbd->serio = serio; + + 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 bail2; + } + + 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 bail2; + } + + kbd->dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP); + kbd->dev->ledbit[0] = BIT_MASK(LED_NUML) | BIT_MASK(LED_CAPSL) | + BIT_MASK(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.parent = &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 0; + bail2: + serio_close(serio); + serio_set_drvdata(serio, NULL); + bail1: + input_free_device(kbd->dev); + bail0: + kfree(kbd); + return -EIO; +} + +static struct serio_device_id hil_kbd_ids[] = { + { + .type = SERIO_HIL_MLC, + .proto = SERIO_HIL, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { 0 } +}; + +static struct serio_driver hil_kbd_serio_drv = { + .driver = { + .name = "hil_kbd", + }, + .description = "HP HIL keyboard driver", + .id_table = hil_kbd_ids, + .connect = hil_kbd_connect, + .disconnect = hil_kbd_disconnect, + .interrupt = hil_kbd_interrupt +}; + +static int __init hil_kbd_init(void) +{ + return serio_register_driver(&hil_kbd_serio_drv); +} + +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 0000000..aacf71f --- /dev/null +++ b/drivers/input/keyboard/hilkbd.c @@ -0,0 +1,377 @@ +/* + * linux/drivers/hil/hilkbd.c + * + * Copyright (C) 1998 Philip Blundell <philb@gnu.org> + * Copyright (C) 1999 Matthew Wilcox <willy@bofh.ai> + * Copyright (C) 1999-2007 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/errno.h> +#include <linux/input.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/hil.h> +#include <linux/io.h> +#include <linux/spinlock.h> +#include <asm/irq.h> +#ifdef CONFIG_HP300 +#include <asm/hwtest.h> +#endif + + +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 0xf0428000UL /* HP300 (m68k) 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] __read_mostly = + { 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) +{ + 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; + int err; + + if (hil_dev.dev) { + return -ENODEV; /* already initialized */ + } + + spin_lock_init(&hil_dev.lock); + hil_dev.dev = input_allocate_device(); + if (!hil_dev.dev) + return -ENOMEM; + +#if defined(CONFIG_HP300) + if (!MACH_IS_HP300) { + err = -ENODEV; + goto err1; + } + if (!hwreg_present((void *)(HILBASE + HIL_DATA))) { + printk(KERN_ERR "HIL: hardware register was not found\n"); + err = -ENODEV; + goto err1; + } + if (!request_region(HILBASE + HIL_DATA, 2, "hil")) { + printk(KERN_ERR "HIL: IOPORT region already used\n"); + err = -EIO; + goto err1; + } +#endif + + err = request_irq(HIL_IRQ, hil_interrupt, 0, "hil", hil_dev.dev_id); + if (err) { + printk(KERN_ERR "HIL: Can't get IRQ\n"); + goto err2; + } + + /* 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); + + 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_MASK(EV_KEY) | BIT_MASK(EV_REP); + hil_dev.dev->ledbit[0] = BIT_MASK(LED_NUML) | BIT_MASK(LED_CAPSL) | + BIT_MASK(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; + + err = input_register_device(hil_dev.dev); + if (err) { + printk(KERN_ERR "HIL: Can't register device\n"); + goto err3; + } + 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; + +err3: + hil_do(HIL_INTOFF, NULL, 0); + disable_irq(HIL_IRQ); + free_irq(HIL_IRQ, hil_dev.dev_id); +err2: +#if defined(CONFIG_HP300) + release_region(HILBASE + HIL_DATA, 2); +err1: +#endif + input_free_device(hil_dev.dev); + hil_dev.dev = NULL; + return err; +} + + +#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.start); + return -ENODEV; + } + + hil_base = dev->hpa.start; + 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); + + hil_dev.dev = NULL; + +#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 0000000..dc33f69 --- /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/jornada680_kbd.c b/drivers/input/keyboard/jornada680_kbd.c new file mode 100644 index 0000000..781fc61 --- /dev/null +++ b/drivers/input/keyboard/jornada680_kbd.c @@ -0,0 +1,279 @@ +/* + * drivers/input/keyboard/jornada680_kbd.c + * + * HP Jornada 620/660/680/690 scan keyboard platform driver + * Copyright (C) 2007 Kristoffer Ericson <Kristoffer.Ericson@gmail.com> + * + * Based on hp680_keyb.c + * Copyright (C) 2006 Paul Mundt + * Copyright (C) 2005 Andriy Skulysh + * Split from drivers/input/keyboard/hp600_keyb.c + * Copyright (C) 2000 Yaegashi Takeshi (hp6xx kbd scan routine and translation table) + * Copyright (C) 2000 Niibe Yutaka (HP620 Keyb translation table) + * + * 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/input.h> +#include <linux/input-polldev.h> +#include <linux/interrupt.h> +#include <linux/jiffies.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> + +#include <asm/delay.h> +#include <asm/io.h> + +#define PCCR 0xa4000104 +#define PDCR 0xa4000106 +#define PECR 0xa4000108 +#define PFCR 0xa400010a +#define PCDR 0xa4000124 +#define PDDR 0xa4000126 +#define PEDR 0xa4000128 +#define PFDR 0xa400012a +#define PGDR 0xa400012c +#define PHDR 0xa400012e +#define PJDR 0xa4000130 +#define PKDR 0xa4000132 +#define PLDR 0xa4000134 + +static const unsigned short jornada_scancodes[] = { +/* PTD1 */ KEY_CAPSLOCK, KEY_MACRO, KEY_LEFTCTRL, 0, KEY_ESC, KEY_KP5, 0, 0, /* 1 -> 8 */ + KEY_F1, KEY_F2, KEY_F3, KEY_F8, KEY_F7, KEY_F6, KEY_F4, KEY_F5, /* 9 -> 16 */ +/* PTD5 */ KEY_SLASH, KEY_APOSTROPHE, KEY_ENTER, 0, KEY_Z, 0, 0, 0, /* 17 -> 24 */ + KEY_X, KEY_C, KEY_V, KEY_DOT, KEY_COMMA, KEY_M, KEY_B, KEY_N, /* 25 -> 32 */ +/* PTD7 */ KEY_KP2, KEY_KP6, KEY_KP3, 0, 0, 0, 0, 0, /* 33 -> 40 */ + KEY_F10, KEY_RO, KEY_F9, KEY_KP4, KEY_NUMLOCK, KEY_SCROLLLOCK, KEY_LEFTALT, KEY_HANJA, /* 41 -> 48 */ +/* PTE0 */ KEY_KATAKANA, KEY_KP0, KEY_GRAVE, 0, KEY_FINANCE, 0, 0, 0, /* 49 -> 56 */ + KEY_KPMINUS, KEY_HIRAGANA, KEY_SPACE, KEY_KPDOT, KEY_VOLUMEUP, 249, 0, 0, /* 57 -> 64 */ +/* PTE1 */ KEY_SEMICOLON, KEY_RIGHTBRACE, KEY_BACKSLASH, 0, KEY_A, 0, 0, 0, /* 65 -> 72 */ + KEY_S, KEY_D, KEY_F, KEY_L, KEY_K, KEY_J, KEY_G, KEY_H, /* 73 -> 80 */ +/* PTE3 */ KEY_KP8, KEY_LEFTMETA, KEY_RIGHTSHIFT, 0, KEY_TAB, 0, 0, 0, /* 81 -> 88 */ + 0, KEY_LEFTSHIFT, KEY_KP7, KEY_KP9, KEY_KP1, KEY_F11, KEY_KPPLUS, KEY_KPASTERISK, /* 89 -> 96 */ +/* PTE6 */ KEY_P, KEY_LEFTBRACE, KEY_BACKSPACE, 0, KEY_Q, 0, 0, 0, /* 97 -> 104 */ + KEY_W, KEY_E, KEY_R, KEY_O, KEY_I, KEY_U, KEY_T, KEY_Y, /* 105 -> 112 */ +/* PTE7 */ KEY_0, KEY_MINUS, KEY_EQUAL, 0, KEY_1, 0, 0, 0, /* 113 -> 120 */ + KEY_2, KEY_3, KEY_4, KEY_9, KEY_8, KEY_7, KEY_5, KEY_6, /* 121 -> 128 */ +/* **** */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0 +}; + +#define JORNADA_SCAN_SIZE 18 + +struct jornadakbd { + struct input_polled_dev *poll_dev; + unsigned short keymap[ARRAY_SIZE(jornada_scancodes)]; + unsigned char length; + unsigned char old_scan[JORNADA_SCAN_SIZE]; + unsigned char new_scan[JORNADA_SCAN_SIZE]; +}; + +static void jornada_parse_kbd(struct jornadakbd *jornadakbd) +{ + struct input_dev *input_dev = jornadakbd->poll_dev->input; + unsigned short *keymap = jornadakbd->keymap; + unsigned int sync_me = 0; + unsigned int i, j; + + for (i = 0; i < JORNADA_SCAN_SIZE; i++) { + unsigned char new = jornadakbd->new_scan[i]; + unsigned char old = jornadakbd->old_scan[i]; + unsigned int xor = new ^ old; + + if (xor == 0) + continue; + + for (j = 0; j < 8; j++) { + unsigned int bit = 1 << j; + if (xor & bit) { + unsigned int scancode = (i << 3) + j; + input_event(input_dev, + EV_MSC, MSC_SCAN, scancode); + input_report_key(input_dev, + keymap[scancode], + !(new & bit)); + sync_me = 1; + } + } + } + + if (sync_me) + input_sync(input_dev); +} + +static void jornada_scan_keyb(unsigned char *s) +{ + int i; + unsigned short ec_static, dc_static; /* = UINT16_t */ + unsigned char matrix_switch[] = { + 0xfd, 0xff, /* PTD1 PD(1) */ + 0xdf, 0xff, /* PTD5 PD(5) */ + 0x7f, 0xff, /* PTD7 PD(7) */ + 0xff, 0xfe, /* PTE0 PE(0) */ + 0xff, 0xfd, /* PTE1 PE(1) */ + 0xff, 0xf7, /* PTE3 PE(3) */ + 0xff, 0xbf, /* PTE6 PE(6) */ + 0xff, 0x7f, /* PTE7 PE(7) */ + }, *t = matrix_switch; + /* PD(x) : + 1. 0xcc0c & (1~(1 << (2*(x)+1))))) + 2. (0xf0cf & 0xfffff) */ + /* PE(x) : + 1. 0xcc0c & 0xffff + 2. 0xf0cf & (1~(1 << (2*(x)+1))))) */ + unsigned short matrix_PDE[] = { + 0xcc04, 0xf0cf, /* PD(1) */ + 0xc40c, 0xf0cf, /* PD(5) */ + 0x4c0c, 0xf0cf, /* PD(7) */ + 0xcc0c, 0xf0cd, /* PE(0) */ + 0xcc0c, 0xf0c7, /* PE(1) */ + 0xcc0c, 0xf04f, /* PE(3) */ + 0xcc0c, 0xd0cf, /* PE(6) */ + 0xcc0c, 0x70cf, /* PE(7) */ + }, *y = matrix_PDE; + + /* Save these control reg bits */ + dc_static = (ctrl_inw(PDCR) & (~0xcc0c)); + ec_static = (ctrl_inw(PECR) & (~0xf0cf)); + + for (i = 0; i < 8; i++) { + /* disable output for all but the one we want to scan */ + ctrl_outw((dc_static | *y++), PDCR); + ctrl_outw((ec_static | *y++), PECR); + udelay(5); + + /* Get scanline row */ + ctrl_outb(*t++, PDDR); + ctrl_outb(*t++, PEDR); + udelay(50); + + /* Read data */ + *s++ = ctrl_inb(PCDR); + *s++ = ctrl_inb(PFDR); + } + /* Scan no lines */ + ctrl_outb(0xff, PDDR); + ctrl_outb(0xff, PEDR); + + /* Enable all scanlines */ + ctrl_outw((dc_static | (0x5555 & 0xcc0c)),PDCR); + ctrl_outw((ec_static | (0x5555 & 0xf0cf)),PECR); + + /* Ignore extra keys and events */ + *s++ = ctrl_inb(PGDR); + *s++ = ctrl_inb(PHDR); +} + +static void jornadakbd680_poll(struct input_polled_dev *dev) +{ + struct jornadakbd *jornadakbd = dev->private; + + jornada_scan_keyb(jornadakbd->new_scan); + jornada_parse_kbd(jornadakbd); + memcpy(jornadakbd->old_scan, jornadakbd->new_scan, JORNADA_SCAN_SIZE); +} + +static int __devinit jornada680kbd_probe(struct platform_device *pdev) +{ + struct jornadakbd *jornadakbd; + struct input_polled_dev *poll_dev; + struct input_dev *input_dev; + int i, error; + + jornadakbd = kzalloc(sizeof(struct jornadakbd), GFP_KERNEL); + if (!jornadakbd) + return -ENOMEM; + + poll_dev = input_allocate_polled_device(); + if (!poll_dev) { + error = -ENOMEM; + goto failed; + } + + platform_set_drvdata(pdev, jornadakbd); + + jornadakbd->poll_dev = poll_dev; + + memcpy(jornadakbd->keymap, jornada_scancodes, + sizeof(jornadakbd->keymap)); + + poll_dev->private = jornadakbd; + poll_dev->poll = jornadakbd680_poll; + poll_dev->poll_interval = 50; /* msec */ + + input_dev = poll_dev->input; + input_dev->evbit[0] = BIT(EV_KEY) | BIT(EV_REP); + input_dev->name = "HP Jornada 680 keyboard"; + input_dev->phys = "jornadakbd/input0"; + input_dev->keycode = jornadakbd->keymap; + input_dev->keycodesize = sizeof(unsigned short); + input_dev->keycodemax = ARRAY_SIZE(jornada_scancodes); + input_dev->dev.parent = &pdev->dev; + input_dev->id.bustype = BUS_HOST; + + for (i = 0; i < 128; i++) + if (jornadakbd->keymap[i]) + __set_bit(jornadakbd->keymap[i], input_dev->keybit); + __clear_bit(KEY_RESERVED, input_dev->keybit); + + input_set_capability(input_dev, EV_MSC, MSC_SCAN); + + error = input_register_polled_device(jornadakbd->poll_dev); + if (error) + goto failed; + + return 0; + + failed: + printk(KERN_ERR "Jornadakbd: failed to register driver, error: %d\n", + error); + platform_set_drvdata(pdev, NULL); + input_free_polled_device(poll_dev); + kfree(jornadakbd); + return error; + +} + +static int __devexit jornada680kbd_remove(struct platform_device *pdev) +{ + struct jornadakbd *jornadakbd = platform_get_drvdata(pdev); + + platform_set_drvdata(pdev, NULL); + input_unregister_polled_device(jornadakbd->poll_dev); + input_free_polled_device(jornadakbd->poll_dev); + kfree(jornadakbd); + + return 0; +} + +static struct platform_driver jornada680kbd_driver = { + .driver = { + .name = "jornada680_kbd", + .owner = THIS_MODULE, + }, + .probe = jornada680kbd_probe, + .remove = __devexit_p(jornada680kbd_remove), +}; + +static int __init jornada680kbd_init(void) +{ + return platform_driver_register(&jornada680kbd_driver); +} + +static void __exit jornada680kbd_exit(void) +{ + platform_driver_unregister(&jornada680kbd_driver); +} + +module_init(jornada680kbd_init); +module_exit(jornada680kbd_exit); + +MODULE_AUTHOR("Kristoffer Ericson <kristoffer.ericson@gmail.com>"); +MODULE_DESCRIPTION("HP Jornada 620/660/680/690 Keyboard Driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:jornada680_kbd"); diff --git a/drivers/input/keyboard/jornada720_kbd.c b/drivers/input/keyboard/jornada720_kbd.c new file mode 100644 index 0000000..4e016d8 --- /dev/null +++ b/drivers/input/keyboard/jornada720_kbd.c @@ -0,0 +1,188 @@ +/* + * drivers/input/keyboard/jornada720_kbd.c + * + * HP Jornada 720 keyboard platform driver + * + * Copyright (C) 2006/2007 Kristoffer Ericson <Kristoffer.Ericson@Gmail.com> + * + * Copyright (C) 2006 jornada 720 kbd driver by + Filip Zyzniewsk <Filip.Zyzniewski@tefnet.plX + * based on (C) 2004 jornada 720 kbd driver by + Alex Lange <chicken@handhelds.org> + * + * 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/device.h> +#include <linux/errno.h> +#include <linux/interrupt.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> + +#include <mach/jornada720.h> +#include <mach/hardware.h> + +MODULE_AUTHOR("Kristoffer Ericson <Kristoffer.Ericson@gmail.com>"); +MODULE_DESCRIPTION("HP Jornada 710/720/728 keyboard driver"); +MODULE_LICENSE("GPL v2"); + +static unsigned short jornada_std_keymap[128] = { /* ROW */ + 0, KEY_ESC, KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5, KEY_F6, KEY_F7, /* #1 */ + KEY_F8, KEY_F9, KEY_F10, KEY_F11, KEY_VOLUMEUP, KEY_VOLUMEDOWN, KEY_MUTE, /* -> */ + 0, KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, KEY_7, KEY_8, KEY_9, /* #2 */ + KEY_0, KEY_MINUS, KEY_EQUAL,0, 0, 0, /* -> */ + 0, KEY_Q, KEY_W, KEY_E, KEY_R, KEY_T, KEY_Y, KEY_U, KEY_I, KEY_O, /* #3 */ + KEY_P, KEY_BACKSLASH, KEY_BACKSPACE, 0, 0, 0, /* -> */ + 0, KEY_A, KEY_S, KEY_D, KEY_F, KEY_G, KEY_H, KEY_J, KEY_K, KEY_L, /* #4 */ + KEY_SEMICOLON, KEY_LEFTBRACE, KEY_RIGHTBRACE, 0, 0, 0, /* -> */ + 0, KEY_Z, KEY_X, KEY_C, KEY_V, KEY_B, KEY_N, KEY_M, KEY_COMMA, /* #5 */ + KEY_DOT, KEY_KPMINUS, KEY_APOSTROPHE, KEY_ENTER, 0, 0,0, /* -> */ + 0, KEY_TAB, 0, KEY_LEFTSHIFT, 0, KEY_APOSTROPHE, 0, 0, 0, 0, /* #6 */ + KEY_UP, 0, KEY_RIGHTSHIFT, 0, 0, 0,0, 0, 0, 0, 0, KEY_LEFTALT, KEY_GRAVE, /* -> */ + 0, 0, KEY_LEFT, KEY_DOWN, KEY_RIGHT, 0, 0, 0, 0,0, KEY_KPASTERISK, /* -> */ + KEY_LEFTCTRL, 0, KEY_SPACE, 0, 0, 0, KEY_SLASH, KEY_DELETE, 0, 0, /* -> */ + 0, 0, 0, KEY_POWER, /* -> */ +}; + +struct jornadakbd { + unsigned short keymap[ARRAY_SIZE(jornada_std_keymap)]; + struct input_dev *input; +}; + +static irqreturn_t jornada720_kbd_interrupt(int irq, void *dev_id) +{ + struct platform_device *pdev = dev_id; + struct jornadakbd *jornadakbd = platform_get_drvdata(pdev); + struct input_dev *input = jornadakbd->input; + u8 count, kbd_data, scan_code; + + /* startup ssp with spinlock */ + jornada_ssp_start(); + + if (jornada_ssp_inout(GETSCANKEYCODE) != TXDUMMY) { + printk(KERN_DEBUG + "jornada720_kbd: " + "GetKeycode command failed with ETIMEDOUT, " + "flushed bus\n"); + } else { + /* How many keycodes are waiting for us? */ + count = jornada_ssp_byte(TXDUMMY); + + /* Lets drag them out one at a time */ + while (count--) { + /* Exchange TxDummy for location (keymap[kbddata]) */ + kbd_data = jornada_ssp_byte(TXDUMMY); + scan_code = kbd_data & 0x7f; + + input_event(input, EV_MSC, MSC_SCAN, scan_code); + input_report_key(input, jornadakbd->keymap[scan_code], + !(kbd_data & 0x80)); + input_sync(input); + } + } + + /* release spinlock and turn off ssp */ + jornada_ssp_end(); + + return IRQ_HANDLED; +}; + +static int __devinit jornada720_kbd_probe(struct platform_device *pdev) +{ + struct jornadakbd *jornadakbd; + struct input_dev *input_dev; + int i, err; + + jornadakbd = kzalloc(sizeof(struct jornadakbd), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!jornadakbd || !input_dev) { + err = -ENOMEM; + goto fail1; + } + + platform_set_drvdata(pdev, jornadakbd); + + memcpy(jornadakbd->keymap, jornada_std_keymap, + sizeof(jornada_std_keymap)); + jornadakbd->input = input_dev; + + input_dev->evbit[0] = BIT(EV_KEY) | BIT(EV_REP); + input_dev->name = "HP Jornada 720 keyboard"; + input_dev->phys = "jornadakbd/input0"; + input_dev->keycode = jornadakbd->keymap; + input_dev->keycodesize = sizeof(unsigned short); + input_dev->keycodemax = ARRAY_SIZE(jornada_std_keymap); + input_dev->id.bustype = BUS_HOST; + input_dev->dev.parent = &pdev->dev; + + for (i = 0; i < ARRAY_SIZE(jornadakbd->keymap); i++) + __set_bit(jornadakbd->keymap[i], input_dev->keybit); + __clear_bit(KEY_RESERVED, input_dev->keybit); + + input_set_capability(input_dev, EV_MSC, MSC_SCAN); + + err = request_irq(IRQ_GPIO0, + jornada720_kbd_interrupt, + IRQF_DISABLED | IRQF_TRIGGER_FALLING, + "jornadakbd", pdev); + if (err) { + printk(KERN_INFO "jornadakbd720_kbd: Unable to grab IRQ\n"); + goto fail1; + } + + err = input_register_device(jornadakbd->input); + if (err) + goto fail2; + + return 0; + + fail2: /* IRQ, DEVICE, MEMORY */ + free_irq(IRQ_GPIO0, pdev); + fail1: /* DEVICE, MEMORY */ + platform_set_drvdata(pdev, NULL); + input_free_device(input_dev); + kfree(jornadakbd); + return err; +}; + +static int __devexit jornada720_kbd_remove(struct platform_device *pdev) +{ + struct jornadakbd *jornadakbd = platform_get_drvdata(pdev); + + free_irq(IRQ_GPIO0, pdev); + platform_set_drvdata(pdev, NULL); + input_unregister_device(jornadakbd->input); + kfree(jornadakbd); + + return 0; +} + +/* work with hotplug and coldplug */ +MODULE_ALIAS("platform:jornada720_kbd"); + +static struct platform_driver jornada720_kbd_driver = { + .driver = { + .name = "jornada720_kbd", + .owner = THIS_MODULE, + }, + .probe = jornada720_kbd_probe, + .remove = __devexit_p(jornada720_kbd_remove), +}; + +static int __init jornada720_kbd_init(void) +{ + return platform_driver_register(&jornada720_kbd_driver); +} + +static void __exit jornada720_kbd_exit(void) +{ + platform_driver_unregister(&jornada720_kbd_driver); +} + +module_init(jornada720_kbd_init); +module_exit(jornada720_kbd_exit); diff --git a/drivers/input/keyboard/lkkbd.c b/drivers/input/keyboard/lkkbd.c new file mode 100644 index 0000000..4730ef3 --- /dev/null +++ b/drivers/input/keyboard/lkkbd.c @@ -0,0 +1,767 @@ +/* + * 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 + */ + +#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 "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; +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(LK, VAR_ON, VAR_OFF, LED, BITS) do { \ + if (test_bit (LED, (LK)->dev->led)) \ + VAR_ON |= BITS; \ + else \ + VAR_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; +}; + +#ifdef LKKBD_DEBUG +/* + * Responses from the keyboard and mapping back to their names. + */ +static struct { + unsigned char value; + unsigned char *name; +} lk_response[] = { +#define RESPONSE(x) { .value = (x), .name = #x, } + RESPONSE (LK_STUCK_KEY), + RESPONSE (LK_SELFTEST_FAILED), + RESPONSE (LK_ALL_KEYS_UP), + RESPONSE (LK_METRONOME), + RESPONSE (LK_OUTPUT_ERROR), + RESPONSE (LK_INPUT_ERROR), + RESPONSE (LK_KBD_LOCKED), + RESPONSE (LK_KBD_TEST_MODE_ACK), + RESPONSE (LK_PREFIX_KEY_DOWN), + RESPONSE (LK_MODE_CHANGE_ACK), + RESPONSE (LK_RESPONSE_RESERVED), +#undef RESPONSE +}; + +static unsigned char * +response_name (unsigned char value) +{ + int i; + + for (i = 0; i < ARRAY_SIZE (lk_response); i++) + if (lk_response[i].value == value) + return lk_response[i].name; + + return "<unknown>"; +} +#endif /* LKKBD_DEBUG */ + +/* + * 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: + strlcpy (lk->name, "DEC LK201 keyboard", + sizeof (lk->name)); + + if (lk201_compose_is_alt) + lk->keycode[0xb1] = KEY_LEFTALT; + break; + + case 2: + strlcpy (lk->name, "DEC LK401 keyboard", + sizeof (lk->name)); + break; + + default: + strlcpy (lk->name, "Unknown DEC keyboard", + sizeof (lk->name)); + 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 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: + 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 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; + + case LK_METRONOME: + case LK_OUTPUT_ERROR: + case LK_INPUT_ERROR: + case LK_KBD_LOCKED: + case LK_KBD_TEST_MODE_ACK: + case LK_PREFIX_KEY_DOWN: + case LK_MODE_CHANGE_ACK: + case LK_RESPONSE_RESERVED: + DBG (KERN_INFO "Got %s and don't know how to handle...\n", + response_name (data)); + break; + + default: + if (lk->keycode[data] != KEY_RESERVED) { + 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 = input_get_drvdata (dev); + unsigned char leds_on = 0; + unsigned char leds_off = 0; + + switch (type) { + case EV_LED: + CHECK_LED (lk, leds_on, leds_off, LED_CAPSL, LK_LED_SHIFTLOCK); + CHECK_LED (lk, leds_on, leds_off, LED_COMPOSE, LK_LED_COMPOSE); + CHECK_LED (lk, leds_on, leds_off, LED_SCROLLL, LK_LED_SCROLLLOCK); + CHECK_LED (lk, leds_on, leds_off, 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", __func__); + 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", __func__); + 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", + __func__, type, code, value); + } + + return -1; +} + +/* + * lkkbd_reinit() sets leds and beeps to a state the computer remembers they + * were in. + */ +static void +lkkbd_reinit (struct work_struct *work) +{ + struct lkkbd *lk = container_of(work, struct lkkbd, tq); + 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 (lk, leds_on, leds_off, LED_CAPSL, LK_LED_SHIFTLOCK); + CHECK_LED (lk, leds_on, leds_off, LED_COMPOSE, LK_LED_COMPOSE); + CHECK_LED (lk, leds_on, leds_off, LED_SCROLLL, LK_LED_SCROLLLOCK); + CHECK_LED (lk, leds_on, leds_off, 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; + struct input_dev *input_dev; + int i; + int err; + + lk = kzalloc (sizeof (struct lkkbd), GFP_KERNEL); + input_dev = input_allocate_device (); + if (!lk || !input_dev) { + err = -ENOMEM; + goto fail1; + } + + lk->serio = serio; + lk->dev = input_dev; + INIT_WORK (&lk->tq, lkkbd_reinit); + lk->bell_volume = bell_volume; + lk->keyclick_volume = keyclick_volume; + lk->ctrlclick_volume = ctrlclick_volume; + memcpy (lk->keycode, lkkbd_keycode, sizeof (lk_keycode_t) * LK_NUM_KEYCODES); + + strlcpy (lk->name, "DEC LK keyboard", sizeof(lk->name)); + snprintf (lk->phys, sizeof(lk->phys), "%s/input0", serio->phys); + + input_dev->name = lk->name; + input_dev->phys = lk->phys; + input_dev->id.bustype = BUS_RS232; + input_dev->id.vendor = SERIO_LKKBD; + input_dev->id.product = 0; + input_dev->id.version = 0x0100; + input_dev->dev.parent = &serio->dev; + input_dev->event = lkkbd_event; + + input_set_drvdata (input_dev, lk); + + set_bit (EV_KEY, input_dev->evbit); + set_bit (EV_LED, input_dev->evbit); + set_bit (EV_SND, input_dev->evbit); + set_bit (EV_REP, input_dev->evbit); + set_bit (LED_CAPSL, input_dev->ledbit); + set_bit (LED_SLEEP, input_dev->ledbit); + set_bit (LED_COMPOSE, input_dev->ledbit); + set_bit (LED_SCROLLL, input_dev->ledbit); + set_bit (SND_BELL, input_dev->sndbit); + set_bit (SND_CLICK, input_dev->sndbit); + + input_dev->keycode = lk->keycode; + input_dev->keycodesize = sizeof (lk_keycode_t); + input_dev->keycodemax = LK_NUM_KEYCODES; + for (i = 0; i < LK_NUM_KEYCODES; i++) + set_bit (lk->keycode[i], input_dev->keybit); + + serio_set_drvdata (serio, lk); + + err = serio_open (serio, drv); + if (err) + goto fail2; + + err = input_register_device (lk->dev); + if (err) + goto fail3; + + lk->serio->write (lk->serio, LK_CMD_POWERCYCLE_RESET); + + return 0; + + fail3: serio_close (serio); + fail2: serio_set_drvdata (serio, NULL); + fail1: input_free_device (input_dev); + kfree (lk); + return err; +} + +/* + * lkkbd_disconnect() unregisters and closes behind us. + */ +static void +lkkbd_disconnect (struct serio *serio) +{ + struct lkkbd *lk = serio_get_drvdata (serio); + + input_get_device (lk->dev); + input_unregister_device (lk->dev); + serio_close (serio); + serio_set_drvdata (serio, NULL); + input_put_device (lk->dev); + 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) +{ + return serio_register_driver(&lkkbd_drv); +} + +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 0000000..9caed30 --- /dev/null +++ b/drivers/input/keyboard/locomokbd.c @@ -0,0 +1,332 @@ +/* + * LoCoMo keyboard driver for Linux-based ARM PDAs: + * - SHARP Zaurus Collie (SL-5500) + * - SHARP Zaurus Poodle (SL-5600) + * + * Copyright (c) 2005 John Lenz + * Based on from xtkbd.c + * + * + * 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/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 const unsigned char +locomokbd_keycode[LOCOMOKBD_NUMKEYS] __devinitconst = { + 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 KB_DELAY 8 +#define SCAN_INTERVAL (HZ/10) + +struct locomokbd { + unsigned char keycode[LOCOMOKBD_NUMKEYS]; + struct input_dev *input; + char phys[32]; + + unsigned long base; + spinlock_t lock; + + struct timer_list timer; + unsigned long suspend_jiffies; + unsigned int count_cancel; +}; + +/* 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) +{ + unsigned int row, col, rowd; + unsigned long flags; + unsigned int num_pressed; + unsigned long membase = locomokbd->base; + + spin_lock_irqsave(&locomokbd->lock, flags); + + 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++) { + unsigned int scancode, pressed, key; + + scancode = SCANCODE(col, row); + pressed = rowd & KB_ROWMASK(row); + key = locomokbd->keycode[scancode]; + + input_report_key(locomokbd->input, key, pressed); + if (likely(!pressed)) + continue; + + num_pressed++; + + /* The "Cancel/ESC" key is labeled "On/Off" on + * Collie and Poodle and should suspend the device + * if it was pressed for more than a second. */ + if (unlikely(key == KEY_ESC)) { + if (!time_after(jiffies, + locomokbd->suspend_jiffies + HZ)) + continue; + if (locomokbd->count_cancel++ + != (HZ/SCAN_INTERVAL + 1)) + continue; + input_event(locomokbd->input, EV_PWR, + KEY_SUSPEND, 1); + locomokbd->suspend_jiffies = jiffies; + } else + locomokbd->count_cancel = 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); + else + locomokbd->count_cancel = 0; + + spin_unlock_irqrestore(&locomokbd->lock, flags); +} + +/* + * LoCoMo keyboard interrupt handler. + */ +static irqreturn_t locomokbd_interrupt(int irq, void *dev_id) +{ + struct locomokbd *locomokbd = dev_id; + /** wait chattering delay **/ + udelay(100); + + locomokbd_scankeyboard(locomokbd); + + 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); +} + +static int __devinit locomokbd_probe(struct locomo_dev *dev) +{ + struct locomokbd *locomokbd; + struct input_dev *input_dev; + int i, err; + + locomokbd = kzalloc(sizeof(struct locomokbd), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!locomokbd || !input_dev) { + err = -ENOMEM; + goto err_free_mem; + } + + /* try and claim memory region */ + if (!request_mem_region((unsigned long) dev->mapbase, + dev->length, + LOCOMO_DRIVER_NAME(dev))) { + err = -EBUSY; + printk(KERN_ERR "locomokbd: Can't acquire access to io memory for keyboard\n"); + goto err_free_mem; + } + + 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->suspend_jiffies = jiffies; + + locomokbd->input = input_dev; + strcpy(locomokbd->phys, "locomokbd/input0"); + + input_dev->name = "LoCoMo keyboard"; + input_dev->phys = locomokbd->phys; + input_dev->id.bustype = BUS_HOST; + input_dev->id.vendor = 0x0001; + input_dev->id.product = 0x0001; + input_dev->id.version = 0x0100; + input_dev->dev.parent = &dev->dev; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP) | + BIT_MASK(EV_PWR); + input_dev->keycode = locomokbd->keycode; + input_dev->keycodesize = sizeof(locomokbd_keycode[0]); + input_dev->keycodemax = ARRAY_SIZE(locomokbd_keycode); + + memcpy(locomokbd->keycode, locomokbd_keycode, sizeof(locomokbd->keycode)); + for (i = 0; i < LOCOMOKBD_NUMKEYS; i++) + set_bit(locomokbd->keycode[i], input_dev->keybit); + clear_bit(0, input_dev->keybit); + + /* attempt to get the interrupt */ + err = request_irq(dev->irq[0], locomokbd_interrupt, 0, "locomokbd", locomokbd); + if (err) { + printk(KERN_ERR "locomokbd: Can't get irq for keyboard\n"); + goto err_release_region; + } + + err = input_register_device(locomokbd->input); + if (err) + goto err_free_irq; + + return 0; + + err_free_irq: + free_irq(dev->irq[0], locomokbd); + err_release_region: + release_mem_region((unsigned long) dev->mapbase, dev->length); + locomo_set_drvdata(dev, NULL); + err_free_mem: + input_free_device(input_dev); + kfree(locomokbd); + + return err; +} + +static int __devexit 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 = __devexit_p(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 0000000..22f17a5 --- /dev/null +++ b/drivers/input/keyboard/maple_keyb.c @@ -0,0 +1,253 @@ +/* + * SEGA Dreamcast keyboard driver + * Based on drivers/usb/usbkbd.c + * Copyright YAEGASHI Takeshi, 2001 + * Porting to 2.6 Copyright Adrian McMenamin, 2007, 2008 + * + * 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, see the file COPYING, or write + * to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#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> + +/* Very simple mutex to ensure proper cleanup */ +static DEFINE_MUTEX(maple_keyb_mutex); + +#define NR_SCANCODES 256 + +MODULE_AUTHOR("YAEGASHI Takeshi, Adrian McMenamin"); +MODULE_DESCRIPTION("SEGA Dreamcast keyboard driver"); +MODULE_LICENSE("GPL"); + +struct dc_kbd { + struct input_dev *dev; + unsigned short keycode[NR_SCANCODES]; + unsigned char new[8]; + unsigned char old[8]; +}; + +static const unsigned short dc_kbd_keycode[NR_SCANCODES] = { + KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_A, KEY_B, + KEY_C, KEY_D, KEY_E, KEY_F, KEY_G, KEY_H, KEY_I, KEY_J, KEY_K, KEY_L, + KEY_M, KEY_N, KEY_O, KEY_P, KEY_Q, KEY_R, KEY_S, KEY_T, KEY_U, KEY_V, + KEY_W, KEY_X, KEY_Y, KEY_Z, KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, + KEY_7, KEY_8, KEY_9, KEY_0, KEY_ENTER, KEY_ESC, KEY_BACKSPACE, + KEY_TAB, KEY_SPACE, KEY_MINUS, KEY_EQUAL, KEY_LEFTBRACE, + KEY_RIGHTBRACE, KEY_BACKSLASH, KEY_BACKSLASH, KEY_SEMICOLON, + KEY_APOSTROPHE, KEY_GRAVE, KEY_COMMA, KEY_DOT, KEY_SLASH, + KEY_CAPSLOCK, KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5, KEY_F6, + KEY_F7, KEY_F8, KEY_F9, KEY_F10, KEY_F11, KEY_F12, KEY_SYSRQ, + KEY_SCROLLLOCK, KEY_PAUSE, KEY_INSERT, KEY_HOME, KEY_PAGEUP, + KEY_DELETE, KEY_END, KEY_PAGEDOWN, KEY_RIGHT, KEY_LEFT, KEY_DOWN, + KEY_UP, KEY_NUMLOCK, KEY_KPSLASH, KEY_KPASTERISK, KEY_KPMINUS, + KEY_KPPLUS, KEY_KPENTER, KEY_KP1, KEY_KP2, KEY_KP3, KEY_KP4, KEY_KP5, + KEY_KP6, KEY_KP7, KEY_KP8, KEY_KP9, KEY_KP0, KEY_KPDOT, KEY_102ND, + KEY_COMPOSE, KEY_POWER, KEY_KPEQUAL, KEY_F13, KEY_F14, KEY_F15, + KEY_F16, KEY_F17, KEY_F18, KEY_F19, KEY_F20, KEY_F21, KEY_F22, + KEY_F23, KEY_F24, KEY_OPEN, KEY_HELP, KEY_PROPS, KEY_FRONT, KEY_STOP, + KEY_AGAIN, KEY_UNDO, KEY_CUT, KEY_COPY, KEY_PASTE, KEY_FIND, KEY_MUTE, + KEY_VOLUMEUP, KEY_VOLUMEDOWN, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + KEY_KPCOMMA, KEY_RESERVED, KEY_RO, KEY_KATAKANAHIRAGANA , KEY_YEN, + KEY_HENKAN, KEY_MUHENKAN, KEY_KPJPCOMMA, KEY_RESERVED, KEY_RESERVED, + KEY_RESERVED, KEY_HANGEUL, KEY_HANJA, KEY_KATAKANA, KEY_HIRAGANA, + KEY_ZENKAKUHANKAKU, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + KEY_RESERVED, KEY_RESERVED, KEY_LEFTCTRL, KEY_LEFTSHIFT, KEY_LEFTALT, + KEY_LEFTMETA, KEY_RIGHTCTRL, KEY_RIGHTSHIFT, KEY_RIGHTALT, + KEY_RIGHTMETA, KEY_PLAYPAUSE, KEY_STOPCD, KEY_PREVIOUSSONG, + KEY_NEXTSONG, KEY_EJECTCD, KEY_VOLUMEUP, KEY_VOLUMEDOWN, KEY_MUTE, + KEY_WWW, KEY_BACK, KEY_FORWARD, KEY_STOP, KEY_FIND, KEY_SCROLLUP, + KEY_SCROLLDOWN, KEY_EDIT, KEY_SLEEP, KEY_SCREENLOCK, KEY_REFRESH, + KEY_CALC, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED +}; + +static void dc_scan_kbd(struct dc_kbd *kbd) +{ + struct input_dev *dev = kbd->dev; + void *ptr; + int code, keycode; + int i; + + for (i = 0; i < 8; i++) { + code = i + 224; + keycode = kbd->keycode[code]; + input_event(dev, EV_MSC, MSC_SCAN, code); + input_report_key(dev, keycode, (kbd->new[0] >> i) & 1); + } + + for (i = 2; i < 8; i++) { + ptr = memchr(kbd->new + 2, kbd->old[i], 6); + code = kbd->old[i]; + if (code > 3 && ptr == NULL) { + keycode = kbd->keycode[code]; + if (keycode) { + input_event(dev, EV_MSC, MSC_SCAN, code); + input_report_key(dev, keycode, 0); + } else + printk(KERN_DEBUG "maple_keyb: " + "Unknown key (scancode %#x) released.", + code); + } + ptr = memchr(kbd->old + 2, kbd->new[i], 6); + code = kbd->new[i]; + if (code > 3 && ptr) { + keycode = kbd->keycode[code]; + if (keycode) { + input_event(dev, EV_MSC, MSC_SCAN, code); + input_report_key(dev, keycode, 1); + } else + printk(KERN_DEBUG "maple_keyb: " + "Unknown key (scancode %#x) pressed.", + code); + } + } + 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 = maple_get_drvdata(mapledev); + unsigned long *buf = mq->recvbuf; + + /* + * We should always get the lock because the only + * time it may be locked is if the driver is in the cleanup phase. + */ + if (likely(mutex_trylock(&maple_keyb_mutex))) { + + if (buf[1] == mapledev->function) { + memcpy(kbd->new, buf + 2, 8); + dc_scan_kbd(kbd); + } + + mutex_unlock(&maple_keyb_mutex); + } +} + +static int probe_maple_kbd(struct device *dev) +{ + struct maple_device *mdev = to_maple_dev(dev); + struct maple_driver *mdrv = to_maple_driver(dev->driver); + int i, error; + struct dc_kbd *kbd; + struct input_dev *idev; + + if (!(mdev->function & MAPLE_FUNC_KEYBOARD)) + return -EINVAL; + + kbd = kzalloc(sizeof(struct dc_kbd), GFP_KERNEL); + idev = input_allocate_device(); + if (!kbd || !idev) { + error = -ENOMEM; + goto fail; + } + + kbd->dev = idev; + memcpy(kbd->keycode, dc_kbd_keycode, sizeof(kbd->keycode)); + + idev->name = mdev->product_name; + idev->evbit[0] = BIT(EV_KEY) | BIT(EV_REP); + idev->keycode = kbd->keycode; + idev->keycodesize = sizeof(unsigned short); + idev->keycodemax = ARRAY_SIZE(kbd->keycode); + idev->id.bustype = BUS_HOST; + idev->dev.parent = &mdev->dev; + + for (i = 0; i < NR_SCANCODES; i++) + __set_bit(dc_kbd_keycode[i], idev->keybit); + __clear_bit(KEY_RESERVED, idev->keybit); + + input_set_capability(idev, EV_MSC, MSC_SCAN); + input_set_drvdata(idev, kbd); + + error = input_register_device(idev); + if (error) + goto fail; + + /* Maple polling is locked to VBLANK - which may be just 50/s */ + maple_getcond_callback(mdev, dc_kbd_callback, HZ/50, + MAPLE_FUNC_KEYBOARD); + + mdev->driver = mdrv; + + maple_set_drvdata(mdev, kbd); + + return error; + +fail: + input_free_device(idev); + kfree(kbd); + maple_set_drvdata(mdev, NULL); + return error; +} + +static int remove_maple_kbd(struct device *dev) +{ + struct maple_device *mdev = to_maple_dev(dev); + struct dc_kbd *kbd = maple_get_drvdata(mdev); + + mutex_lock(&maple_keyb_mutex); + + input_unregister_device(kbd->dev); + kfree(kbd); + + maple_set_drvdata(mdev, NULL); + + mutex_unlock(&maple_keyb_mutex); + return 0; +} + +static struct maple_driver dc_kbd_driver = { + .function = MAPLE_FUNC_KEYBOARD, + .drv = { + .name = "Dreamcast_keyboard", + .probe = probe_maple_kbd, + .remove = remove_maple_kbd, + }, +}; + +static int __init dc_kbd_init(void) +{ + return maple_driver_register(&dc_kbd_driver); +} + +static void __exit dc_kbd_exit(void) +{ + maple_driver_unregister(&dc_kbd_driver); +} + +module_init(dc_kbd_init); +module_exit(dc_kbd_exit); diff --git a/drivers/input/keyboard/newtonkbd.c b/drivers/input/keyboard/newtonkbd.c new file mode 100644 index 0000000..48d1cab --- /dev/null +++ b/drivers/input/keyboard/newtonkbd.c @@ -0,0 +1,180 @@ +/* + * 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 +}; + +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 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_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->dev->name, serio->phys); + return IRQ_HANDLED; + +} + +static int nkbd_connect(struct serio *serio, struct serio_driver *drv) +{ + struct nkbd *nkbd; + struct input_dev *input_dev; + int err = -ENOMEM; + int i; + + nkbd = kzalloc(sizeof(struct nkbd), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!nkbd || !input_dev) + goto fail1; + + nkbd->serio = serio; + nkbd->dev = input_dev; + snprintf(nkbd->phys, sizeof(nkbd->phys), "%s/input0", serio->phys); + memcpy(nkbd->keycode, nkbd_keycode, sizeof(nkbd->keycode)); + + input_dev->name = "Newton Keyboard"; + input_dev->phys = nkbd->phys; + input_dev->id.bustype = BUS_RS232; + input_dev->id.vendor = SERIO_NEWTON; + input_dev->id.product = 0x0001; + input_dev->id.version = 0x0100; + input_dev->dev.parent = &serio->dev; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP); + input_dev->keycode = nkbd->keycode; + input_dev->keycodesize = sizeof(unsigned char); + input_dev->keycodemax = ARRAY_SIZE(nkbd_keycode); + for (i = 0; i < 128; i++) + set_bit(nkbd->keycode[i], input_dev->keybit); + clear_bit(0, input_dev->keybit); + + serio_set_drvdata(serio, nkbd); + + err = serio_open(serio, drv); + if (err) + goto fail2; + + err = input_register_device(nkbd->dev); + if (err) + goto fail3; + + return 0; + + fail3: serio_close(serio); + fail2: serio_set_drvdata(serio, NULL); + fail1: input_free_device(input_dev); + kfree(nkbd); + return err; +} + +static void nkbd_disconnect(struct serio *serio) +{ + struct nkbd *nkbd = serio_get_drvdata(serio); + + serio_close(serio); + serio_set_drvdata(serio, NULL); + input_unregister_device(nkbd->dev); + 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) +{ + return serio_register_driver(&nkbd_drv); +} + +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/omap-keypad.c b/drivers/input/keyboard/omap-keypad.c new file mode 100644 index 0000000..69e674e --- /dev/null +++ b/drivers/input/keyboard/omap-keypad.c @@ -0,0 +1,487 @@ +/* + * linux/drivers/input/keyboard/omap-keypad.c + * + * OMAP Keypad Driver + * + * Copyright (C) 2003 Nokia Corporation + * Written by Timo Teräs <ext-timo.teras@nokia.com> + * + * Added support for H2 & H3 Keypad + * Copyright (C) 2004 Texas Instruments + * + * 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/module.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/types.h> +#include <linux/input.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/mutex.h> +#include <linux/errno.h> +#include <mach/gpio.h> +#include <mach/keypad.h> +#include <mach/menelaus.h> +#include <asm/irq.h> +#include <mach/hardware.h> +#include <asm/io.h> +#include <mach/mux.h> + +#undef NEW_BOARD_LEARNING_MODE + +static void omap_kp_tasklet(unsigned long); +static void omap_kp_timer(unsigned long); + +static unsigned char keypad_state[8]; +static DEFINE_MUTEX(kp_enable_mutex); +static int kp_enable = 1; +static int kp_cur_group = -1; + +struct omap_kp { + struct input_dev *input; + struct timer_list timer; + int irq; + unsigned int rows; + unsigned int cols; + unsigned long delay; + unsigned int debounce; +}; + +static DECLARE_TASKLET_DISABLED(kp_tasklet, omap_kp_tasklet, 0); + +static int *keymap; +static unsigned int *row_gpios; +static unsigned int *col_gpios; + +#ifdef CONFIG_ARCH_OMAP2 +static void set_col_gpio_val(struct omap_kp *omap_kp, u8 value) +{ + int col; + + for (col = 0; col < omap_kp->cols; col++) + gpio_set_value(col_gpios[col], value & (1 << col)); +} + +static u8 get_row_gpio_val(struct omap_kp *omap_kp) +{ + int row; + u8 value = 0; + + for (row = 0; row < omap_kp->rows; row++) { + if (gpio_get_value(row_gpios[row])) + value |= (1 << row); + } + return value; +} +#else +#define set_col_gpio_val(x, y) do {} while (0) +#define get_row_gpio_val(x) 0 +#endif + +static irqreturn_t omap_kp_interrupt(int irq, void *dev_id) +{ + struct omap_kp *omap_kp = dev_id; + + /* disable keyboard interrupt and schedule for handling */ + if (cpu_is_omap24xx()) { + int i; + for (i = 0; i < omap_kp->rows; i++) + disable_irq(OMAP_GPIO_IRQ(row_gpios[i])); + } else + /* disable keyboard interrupt and schedule for handling */ + omap_writew(1, OMAP_MPUIO_BASE + OMAP_MPUIO_KBD_MASKIT); + + tasklet_schedule(&kp_tasklet); + + return IRQ_HANDLED; +} + +static void omap_kp_timer(unsigned long data) +{ + tasklet_schedule(&kp_tasklet); +} + +static void omap_kp_scan_keypad(struct omap_kp *omap_kp, unsigned char *state) +{ + int col = 0; + + /* read the keypad status */ + if (cpu_is_omap24xx()) { + int i; + for (i = 0; i < omap_kp->rows; i++) + disable_irq(OMAP_GPIO_IRQ(row_gpios[i])); + + /* read the keypad status */ + for (col = 0; col < omap_kp->cols; col++) { + set_col_gpio_val(omap_kp, ~(1 << col)); + state[col] = ~(get_row_gpio_val(omap_kp)) & 0x3f; + } + set_col_gpio_val(omap_kp, 0); + + } else { + /* disable keyboard interrupt and schedule for handling */ + omap_writew(1, OMAP_MPUIO_BASE + OMAP_MPUIO_KBD_MASKIT); + + /* read the keypad status */ + omap_writew(0xff, OMAP_MPUIO_BASE + OMAP_MPUIO_KBC); + for (col = 0; col < omap_kp->cols; col++) { + omap_writew(~(1 << col) & 0xff, + OMAP_MPUIO_BASE + OMAP_MPUIO_KBC); + + udelay(omap_kp->delay); + + state[col] = ~omap_readw(OMAP_MPUIO_BASE + + OMAP_MPUIO_KBR_LATCH) & 0xff; + } + omap_writew(0x00, OMAP_MPUIO_BASE + OMAP_MPUIO_KBC); + udelay(2); + } +} + +static inline int omap_kp_find_key(int col, int row) +{ + int i, key; + + key = KEY(col, row, 0); + for (i = 0; keymap[i] != 0; i++) + if ((keymap[i] & 0xff000000) == key) + return keymap[i] & 0x00ffffff; + return -1; +} + +static void omap_kp_tasklet(unsigned long data) +{ + struct omap_kp *omap_kp_data = (struct omap_kp *) data; + unsigned char new_state[8], changed, key_down = 0; + int col, row; + int spurious = 0; + + /* check for any changes */ + omap_kp_scan_keypad(omap_kp_data, new_state); + + /* check for changes and print those */ + for (col = 0; col < omap_kp_data->cols; col++) { + changed = new_state[col] ^ keypad_state[col]; + key_down |= new_state[col]; + if (changed == 0) + continue; + + for (row = 0; row < omap_kp_data->rows; row++) { + int key; + if (!(changed & (1 << row))) + continue; +#ifdef NEW_BOARD_LEARNING_MODE + printk(KERN_INFO "omap-keypad: key %d-%d %s\n", col, + row, (new_state[col] & (1 << row)) ? + "pressed" : "released"); +#else + key = omap_kp_find_key(col, row); + if (key < 0) { + printk(KERN_WARNING + "omap-keypad: Spurious key event %d-%d\n", + col, row); + /* We scan again after a couple of seconds */ + spurious = 1; + continue; + } + + if (!(kp_cur_group == (key & GROUP_MASK) || + kp_cur_group == -1)) + continue; + + kp_cur_group = key & GROUP_MASK; + input_report_key(omap_kp_data->input, key & ~GROUP_MASK, + new_state[col] & (1 << row)); +#endif + } + } + memcpy(keypad_state, new_state, sizeof(keypad_state)); + + if (key_down) { + int delay = HZ / 20; + /* some key is pressed - keep irq disabled and use timer + * to poll the keypad */ + if (spurious) + delay = 2 * HZ; + mod_timer(&omap_kp_data->timer, jiffies + delay); + } else { + /* enable interrupts */ + if (cpu_is_omap24xx()) { + int i; + for (i = 0; i < omap_kp_data->rows; i++) + enable_irq(OMAP_GPIO_IRQ(row_gpios[i])); + } else { + omap_writew(0, OMAP_MPUIO_BASE + OMAP_MPUIO_KBD_MASKIT); + kp_cur_group = -1; + } + } +} + +static ssize_t omap_kp_enable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%u\n", kp_enable); +} + +static ssize_t omap_kp_enable_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int state; + + if (sscanf(buf, "%u", &state) != 1) + return -EINVAL; + + if ((state != 1) && (state != 0)) + return -EINVAL; + + mutex_lock(&kp_enable_mutex); + if (state != kp_enable) { + if (state) + enable_irq(INT_KEYBOARD); + else + disable_irq(INT_KEYBOARD); + kp_enable = state; + } + mutex_unlock(&kp_enable_mutex); + + return strnlen(buf, count); +} + +static DEVICE_ATTR(enable, S_IRUGO | S_IWUSR, omap_kp_enable_show, omap_kp_enable_store); + +#ifdef CONFIG_PM +static int omap_kp_suspend(struct platform_device *dev, pm_message_t state) +{ + /* Nothing yet */ + + return 0; +} + +static int omap_kp_resume(struct platform_device *dev) +{ + /* Nothing yet */ + + return 0; +} +#else +#define omap_kp_suspend NULL +#define omap_kp_resume NULL +#endif + +static int __init omap_kp_probe(struct platform_device *pdev) +{ + struct omap_kp *omap_kp; + struct input_dev *input_dev; + struct omap_kp_platform_data *pdata = pdev->dev.platform_data; + int i, col_idx, row_idx, irq_idx, ret; + + if (!pdata->rows || !pdata->cols || !pdata->keymap) { + printk(KERN_ERR "No rows, cols or keymap from pdata\n"); + return -EINVAL; + } + + omap_kp = kzalloc(sizeof(struct omap_kp), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!omap_kp || !input_dev) { + kfree(omap_kp); + input_free_device(input_dev); + return -ENOMEM; + } + + platform_set_drvdata(pdev, omap_kp); + + omap_kp->input = input_dev; + + /* Disable the interrupt for the MPUIO keyboard */ + if (!cpu_is_omap24xx()) + omap_writew(1, OMAP_MPUIO_BASE + OMAP_MPUIO_KBD_MASKIT); + + keymap = pdata->keymap; + + if (pdata->rep) + __set_bit(EV_REP, input_dev->evbit); + + if (pdata->delay) + omap_kp->delay = pdata->delay; + + if (pdata->row_gpios && pdata->col_gpios) { + row_gpios = pdata->row_gpios; + col_gpios = pdata->col_gpios; + } + + omap_kp->rows = pdata->rows; + omap_kp->cols = pdata->cols; + + if (cpu_is_omap24xx()) { + /* Cols: outputs */ + for (col_idx = 0; col_idx < omap_kp->cols; col_idx++) { + if (gpio_request(col_gpios[col_idx], "omap_kp_col") < 0) { + printk(KERN_ERR "Failed to request" + "GPIO%d for keypad\n", + col_gpios[col_idx]); + goto err1; + } + gpio_direction_output(col_gpios[col_idx], 0); + } + /* Rows: inputs */ + for (row_idx = 0; row_idx < omap_kp->rows; row_idx++) { + if (gpio_request(row_gpios[row_idx], "omap_kp_row") < 0) { + printk(KERN_ERR "Failed to request" + "GPIO%d for keypad\n", + row_gpios[row_idx]); + goto err2; + } + gpio_direction_input(row_gpios[row_idx]); + } + } else { + col_idx = 0; + row_idx = 0; + } + + setup_timer(&omap_kp->timer, omap_kp_timer, (unsigned long)omap_kp); + + /* get the irq and init timer*/ + tasklet_enable(&kp_tasklet); + kp_tasklet.data = (unsigned long) omap_kp; + + ret = device_create_file(&pdev->dev, &dev_attr_enable); + if (ret < 0) + goto err2; + + /* setup input device */ + __set_bit(EV_KEY, input_dev->evbit); + for (i = 0; keymap[i] != 0; i++) + __set_bit(keymap[i] & KEY_MAX, input_dev->keybit); + input_dev->name = "omap-keypad"; + input_dev->phys = "omap-keypad/input0"; + input_dev->dev.parent = &pdev->dev; + + input_dev->id.bustype = BUS_HOST; + input_dev->id.vendor = 0x0001; + input_dev->id.product = 0x0001; + input_dev->id.version = 0x0100; + + ret = input_register_device(omap_kp->input); + if (ret < 0) { + printk(KERN_ERR "Unable to register omap-keypad input device\n"); + goto err3; + } + + if (pdata->dbounce) + omap_writew(0xff, OMAP_MPUIO_BASE + OMAP_MPUIO_GPIO_DEBOUNCING); + + /* scan current status and enable interrupt */ + omap_kp_scan_keypad(omap_kp, keypad_state); + if (!cpu_is_omap24xx()) { + omap_kp->irq = platform_get_irq(pdev, 0); + if (omap_kp->irq >= 0) { + if (request_irq(omap_kp->irq, omap_kp_interrupt, 0, + "omap-keypad", omap_kp) < 0) + goto err4; + } + omap_writew(0, OMAP_MPUIO_BASE + OMAP_MPUIO_KBD_MASKIT); + } else { + for (irq_idx = 0; irq_idx < omap_kp->rows; irq_idx++) { + if (request_irq(OMAP_GPIO_IRQ(row_gpios[irq_idx]), + omap_kp_interrupt, + IRQF_TRIGGER_FALLING, + "omap-keypad", omap_kp) < 0) + goto err5; + } + } + return 0; +err5: + for (i = irq_idx - 1; i >=0; i--) + free_irq(row_gpios[i], 0); +err4: + input_unregister_device(omap_kp->input); + input_dev = NULL; +err3: + device_remove_file(&pdev->dev, &dev_attr_enable); +err2: + for (i = row_idx - 1; i >=0; i--) + gpio_free(row_gpios[i]); +err1: + for (i = col_idx - 1; i >=0; i--) + gpio_free(col_gpios[i]); + + kfree(omap_kp); + input_free_device(input_dev); + + return -EINVAL; +} + +static int omap_kp_remove(struct platform_device *pdev) +{ + struct omap_kp *omap_kp = platform_get_drvdata(pdev); + + /* disable keypad interrupt handling */ + tasklet_disable(&kp_tasklet); + if (cpu_is_omap24xx()) { + int i; + for (i = 0; i < omap_kp->cols; i++) + gpio_free(col_gpios[i]); + for (i = 0; i < omap_kp->rows; i++) { + gpio_free(row_gpios[i]); + free_irq(OMAP_GPIO_IRQ(row_gpios[i]), 0); + } + } else { + omap_writew(1, OMAP_MPUIO_BASE + OMAP_MPUIO_KBD_MASKIT); + free_irq(omap_kp->irq, 0); + } + + del_timer_sync(&omap_kp->timer); + tasklet_kill(&kp_tasklet); + + /* unregister everything */ + input_unregister_device(omap_kp->input); + + kfree(omap_kp); + + return 0; +} + +static struct platform_driver omap_kp_driver = { + .probe = omap_kp_probe, + .remove = omap_kp_remove, + .suspend = omap_kp_suspend, + .resume = omap_kp_resume, + .driver = { + .name = "omap-keypad", + .owner = THIS_MODULE, + }, +}; + +static int __devinit omap_kp_init(void) +{ + printk(KERN_INFO "OMAP Keypad Driver\n"); + return platform_driver_register(&omap_kp_driver); +} + +static void __exit omap_kp_exit(void) +{ + platform_driver_unregister(&omap_kp_driver); +} + +module_init(omap_kp_init); +module_exit(omap_kp_exit); + +MODULE_AUTHOR("Timo Teräs"); +MODULE_DESCRIPTION("OMAP Keypad Driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:omap-keypad"); diff --git a/drivers/input/keyboard/pxa27x_keypad.c b/drivers/input/keyboard/pxa27x_keypad.c new file mode 100644 index 0000000..6d30c6d --- /dev/null +++ b/drivers/input/keyboard/pxa27x_keypad.c @@ -0,0 +1,600 @@ +/* + * linux/drivers/input/keyboard/pxa27x_keypad.c + * + * Driver for the pxa27x matrix keyboard controller. + * + * Created: Feb 22, 2007 + * Author: Rodolfo Giometti <giometti@linux.it> + * + * Based on a previous implementations by Kevin O'Connor + * <kevin_at_koconnor.net> and Alex Osborne <bobofdoom@gmail.com> and + * on some suggestions by Nicolas Pitre <nico@cam.org>. + * + * 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/interrupt.h> +#include <linux/input.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/err.h> + +#include <asm/mach/arch.h> +#include <asm/mach/map.h> + +#include <mach/hardware.h> +#include <mach/pxa27x_keypad.h> + +/* + * Keypad Controller registers + */ +#define KPC 0x0000 /* Keypad Control register */ +#define KPDK 0x0008 /* Keypad Direct Key register */ +#define KPREC 0x0010 /* Keypad Rotary Encoder register */ +#define KPMK 0x0018 /* Keypad Matrix Key register */ +#define KPAS 0x0020 /* Keypad Automatic Scan register */ + +/* Keypad Automatic Scan Multiple Key Presser register 0-3 */ +#define KPASMKP0 0x0028 +#define KPASMKP1 0x0030 +#define KPASMKP2 0x0038 +#define KPASMKP3 0x0040 +#define KPKDI 0x0048 + +/* bit definitions */ +#define KPC_MKRN(n) ((((n) - 1) & 0x7) << 26) /* matrix key row number */ +#define KPC_MKCN(n) ((((n) - 1) & 0x7) << 23) /* matrix key column number */ +#define KPC_DKN(n) ((((n) - 1) & 0x7) << 6) /* direct key number */ + +#define KPC_AS (0x1 << 30) /* Automatic Scan bit */ +#define KPC_ASACT (0x1 << 29) /* Automatic Scan on Activity */ +#define KPC_MI (0x1 << 22) /* Matrix interrupt bit */ +#define KPC_IMKP (0x1 << 21) /* Ignore Multiple Key Press */ + +#define KPC_MS(n) (0x1 << (13 + (n))) /* Matrix scan line 'n' */ +#define KPC_MS_ALL (0xff << 13) + +#define KPC_ME (0x1 << 12) /* Matrix Keypad Enable */ +#define KPC_MIE (0x1 << 11) /* Matrix Interrupt Enable */ +#define KPC_DK_DEB_SEL (0x1 << 9) /* Direct Keypad Debounce Select */ +#define KPC_DI (0x1 << 5) /* Direct key interrupt bit */ +#define KPC_RE_ZERO_DEB (0x1 << 4) /* Rotary Encoder Zero Debounce */ +#define KPC_REE1 (0x1 << 3) /* Rotary Encoder1 Enable */ +#define KPC_REE0 (0x1 << 2) /* Rotary Encoder0 Enable */ +#define KPC_DE (0x1 << 1) /* Direct Keypad Enable */ +#define KPC_DIE (0x1 << 0) /* Direct Keypad interrupt Enable */ + +#define KPDK_DKP (0x1 << 31) +#define KPDK_DK(n) ((n) & 0xff) + +#define KPREC_OF1 (0x1 << 31) +#define kPREC_UF1 (0x1 << 30) +#define KPREC_OF0 (0x1 << 15) +#define KPREC_UF0 (0x1 << 14) + +#define KPREC_RECOUNT0(n) ((n) & 0xff) +#define KPREC_RECOUNT1(n) (((n) >> 16) & 0xff) + +#define KPMK_MKP (0x1 << 31) +#define KPAS_SO (0x1 << 31) +#define KPASMKPx_SO (0x1 << 31) + +#define KPAS_MUKP(n) (((n) >> 26) & 0x1f) +#define KPAS_RP(n) (((n) >> 4) & 0xf) +#define KPAS_CP(n) ((n) & 0xf) + +#define KPASMKP_MKC_MASK (0xff) + +#define keypad_readl(off) __raw_readl(keypad->mmio_base + (off)) +#define keypad_writel(off, v) __raw_writel((v), keypad->mmio_base + (off)) + +#define MAX_MATRIX_KEY_NUM (8 * 8) + +struct pxa27x_keypad { + struct pxa27x_keypad_platform_data *pdata; + + struct clk *clk; + struct input_dev *input_dev; + void __iomem *mmio_base; + + int irq; + + /* matrix key code map */ + unsigned int matrix_keycodes[MAX_MATRIX_KEY_NUM]; + + /* state row bits of each column scan */ + uint32_t matrix_key_state[MAX_MATRIX_KEY_COLS]; + uint32_t direct_key_state; + + unsigned int direct_key_mask; + + int rotary_rel_code[2]; + int rotary_up_key[2]; + int rotary_down_key[2]; +}; + +static void pxa27x_keypad_build_keycode(struct pxa27x_keypad *keypad) +{ + struct pxa27x_keypad_platform_data *pdata = keypad->pdata; + struct input_dev *input_dev = keypad->input_dev; + unsigned int *key; + int i; + + key = &pdata->matrix_key_map[0]; + for (i = 0; i < pdata->matrix_key_map_size; i++, key++) { + int row = ((*key) >> 28) & 0xf; + int col = ((*key) >> 24) & 0xf; + int code = (*key) & 0xffffff; + + keypad->matrix_keycodes[(row << 3) + col] = code; + set_bit(code, input_dev->keybit); + } + + for (i = 0; i < pdata->direct_key_num; i++) + set_bit(pdata->direct_key_map[i], input_dev->keybit); + + keypad->rotary_up_key[0] = pdata->rotary0_up_key; + keypad->rotary_up_key[1] = pdata->rotary1_up_key; + keypad->rotary_down_key[0] = pdata->rotary0_down_key; + keypad->rotary_down_key[1] = pdata->rotary1_down_key; + keypad->rotary_rel_code[0] = pdata->rotary0_rel_code; + keypad->rotary_rel_code[1] = pdata->rotary1_rel_code; + + if (pdata->enable_rotary0) { + if (pdata->rotary0_up_key && pdata->rotary0_down_key) { + set_bit(pdata->rotary0_up_key, input_dev->keybit); + set_bit(pdata->rotary0_down_key, input_dev->keybit); + } else + set_bit(pdata->rotary0_rel_code, input_dev->relbit); + } + + if (pdata->enable_rotary1) { + if (pdata->rotary1_up_key && pdata->rotary1_down_key) { + set_bit(pdata->rotary1_up_key, input_dev->keybit); + set_bit(pdata->rotary1_down_key, input_dev->keybit); + } else + set_bit(pdata->rotary1_rel_code, input_dev->relbit); + } +} + +static inline unsigned int lookup_matrix_keycode( + struct pxa27x_keypad *keypad, int row, int col) +{ + return keypad->matrix_keycodes[(row << 3) + col]; +} + +static void pxa27x_keypad_scan_matrix(struct pxa27x_keypad *keypad) +{ + struct pxa27x_keypad_platform_data *pdata = keypad->pdata; + int row, col, num_keys_pressed = 0; + uint32_t new_state[MAX_MATRIX_KEY_COLS]; + uint32_t kpas = keypad_readl(KPAS); + + num_keys_pressed = KPAS_MUKP(kpas); + + memset(new_state, 0, sizeof(new_state)); + + if (num_keys_pressed == 0) + goto scan; + + if (num_keys_pressed == 1) { + col = KPAS_CP(kpas); + row = KPAS_RP(kpas); + + /* if invalid row/col, treat as no key pressed */ + if (col >= pdata->matrix_key_cols || + row >= pdata->matrix_key_rows) + goto scan; + + new_state[col] = (1 << row); + goto scan; + } + + if (num_keys_pressed > 1) { + uint32_t kpasmkp0 = keypad_readl(KPASMKP0); + uint32_t kpasmkp1 = keypad_readl(KPASMKP1); + uint32_t kpasmkp2 = keypad_readl(KPASMKP2); + uint32_t kpasmkp3 = keypad_readl(KPASMKP3); + + new_state[0] = kpasmkp0 & KPASMKP_MKC_MASK; + new_state[1] = (kpasmkp0 >> 16) & KPASMKP_MKC_MASK; + new_state[2] = kpasmkp1 & KPASMKP_MKC_MASK; + new_state[3] = (kpasmkp1 >> 16) & KPASMKP_MKC_MASK; + new_state[4] = kpasmkp2 & KPASMKP_MKC_MASK; + new_state[5] = (kpasmkp2 >> 16) & KPASMKP_MKC_MASK; + new_state[6] = kpasmkp3 & KPASMKP_MKC_MASK; + new_state[7] = (kpasmkp3 >> 16) & KPASMKP_MKC_MASK; + } +scan: + for (col = 0; col < pdata->matrix_key_cols; col++) { + uint32_t bits_changed; + + bits_changed = keypad->matrix_key_state[col] ^ new_state[col]; + if (bits_changed == 0) + continue; + + for (row = 0; row < pdata->matrix_key_rows; row++) { + if ((bits_changed & (1 << row)) == 0) + continue; + + input_report_key(keypad->input_dev, + lookup_matrix_keycode(keypad, row, col), + new_state[col] & (1 << row)); + } + } + input_sync(keypad->input_dev); + memcpy(keypad->matrix_key_state, new_state, sizeof(new_state)); +} + +#define DEFAULT_KPREC (0x007f007f) + +static inline int rotary_delta(uint32_t kprec) +{ + if (kprec & KPREC_OF0) + return (kprec & 0xff) + 0x7f; + else if (kprec & KPREC_UF0) + return (kprec & 0xff) - 0x7f - 0xff; + else + return (kprec & 0xff) - 0x7f; +} + +static void report_rotary_event(struct pxa27x_keypad *keypad, int r, int delta) +{ + struct input_dev *dev = keypad->input_dev; + + if (delta == 0) + return; + + if (keypad->rotary_up_key[r] && keypad->rotary_down_key[r]) { + int keycode = (delta > 0) ? keypad->rotary_up_key[r] : + keypad->rotary_down_key[r]; + + /* simulate a press-n-release */ + input_report_key(dev, keycode, 1); + input_sync(dev); + input_report_key(dev, keycode, 0); + input_sync(dev); + } else { + input_report_rel(dev, keypad->rotary_rel_code[r], delta); + input_sync(dev); + } +} + +static void pxa27x_keypad_scan_rotary(struct pxa27x_keypad *keypad) +{ + struct pxa27x_keypad_platform_data *pdata = keypad->pdata; + uint32_t kprec; + + /* read and reset to default count value */ + kprec = keypad_readl(KPREC); + keypad_writel(KPREC, DEFAULT_KPREC); + + if (pdata->enable_rotary0) + report_rotary_event(keypad, 0, rotary_delta(kprec)); + + if (pdata->enable_rotary1) + report_rotary_event(keypad, 1, rotary_delta(kprec >> 16)); +} + +static void pxa27x_keypad_scan_direct(struct pxa27x_keypad *keypad) +{ + struct pxa27x_keypad_platform_data *pdata = keypad->pdata; + unsigned int new_state; + uint32_t kpdk, bits_changed; + int i; + + kpdk = keypad_readl(KPDK); + + if (pdata->enable_rotary0 || pdata->enable_rotary1) + pxa27x_keypad_scan_rotary(keypad); + + if (pdata->direct_key_map == NULL) + return; + + new_state = KPDK_DK(kpdk) & keypad->direct_key_mask; + bits_changed = keypad->direct_key_state ^ new_state; + + if (bits_changed == 0) + return; + + for (i = 0; i < pdata->direct_key_num; i++) { + if (bits_changed & (1 << i)) + input_report_key(keypad->input_dev, + pdata->direct_key_map[i], + (new_state & (1 << i))); + } + input_sync(keypad->input_dev); + keypad->direct_key_state = new_state; +} + +static irqreturn_t pxa27x_keypad_irq_handler(int irq, void *dev_id) +{ + struct pxa27x_keypad *keypad = dev_id; + unsigned long kpc = keypad_readl(KPC); + + if (kpc & KPC_DI) + pxa27x_keypad_scan_direct(keypad); + + if (kpc & KPC_MI) + pxa27x_keypad_scan_matrix(keypad); + + return IRQ_HANDLED; +} + +static void pxa27x_keypad_config(struct pxa27x_keypad *keypad) +{ + struct pxa27x_keypad_platform_data *pdata = keypad->pdata; + unsigned int mask = 0, direct_key_num = 0; + unsigned long kpc = 0; + + /* enable matrix keys with automatic scan */ + if (pdata->matrix_key_rows && pdata->matrix_key_cols) { + kpc |= KPC_ASACT | KPC_MIE | KPC_ME | KPC_MS_ALL; + kpc |= KPC_MKRN(pdata->matrix_key_rows) | + KPC_MKCN(pdata->matrix_key_cols); + } + + /* enable rotary key, debounce interval same as direct keys */ + if (pdata->enable_rotary0) { + mask |= 0x03; + direct_key_num = 2; + kpc |= KPC_REE0; + } + + if (pdata->enable_rotary1) { + mask |= 0x0c; + direct_key_num = 4; + kpc |= KPC_REE1; + } + + if (pdata->direct_key_num > direct_key_num) + direct_key_num = pdata->direct_key_num; + + keypad->direct_key_mask = ((2 << direct_key_num) - 1) & ~mask; + + /* enable direct key */ + if (direct_key_num) + kpc |= KPC_DE | KPC_DIE | KPC_DKN(direct_key_num); + + keypad_writel(KPC, kpc | KPC_RE_ZERO_DEB); + keypad_writel(KPREC, DEFAULT_KPREC); + keypad_writel(KPKDI, pdata->debounce_interval); +} + +static int pxa27x_keypad_open(struct input_dev *dev) +{ + struct pxa27x_keypad *keypad = input_get_drvdata(dev); + + /* Enable unit clock */ + clk_enable(keypad->clk); + pxa27x_keypad_config(keypad); + + return 0; +} + +static void pxa27x_keypad_close(struct input_dev *dev) +{ + struct pxa27x_keypad *keypad = input_get_drvdata(dev); + + /* Disable clock unit */ + clk_disable(keypad->clk); +} + +#ifdef CONFIG_PM +static int pxa27x_keypad_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct pxa27x_keypad *keypad = platform_get_drvdata(pdev); + + clk_disable(keypad->clk); + + if (device_may_wakeup(&pdev->dev)) + enable_irq_wake(keypad->irq); + + return 0; +} + +static int pxa27x_keypad_resume(struct platform_device *pdev) +{ + struct pxa27x_keypad *keypad = platform_get_drvdata(pdev); + struct input_dev *input_dev = keypad->input_dev; + + if (device_may_wakeup(&pdev->dev)) + disable_irq_wake(keypad->irq); + + mutex_lock(&input_dev->mutex); + + if (input_dev->users) { + /* Enable unit clock */ + clk_enable(keypad->clk); + pxa27x_keypad_config(keypad); + } + + mutex_unlock(&input_dev->mutex); + + return 0; +} +#else +#define pxa27x_keypad_suspend NULL +#define pxa27x_keypad_resume NULL +#endif + +#define res_size(res) ((res)->end - (res)->start + 1) + +static int __devinit pxa27x_keypad_probe(struct platform_device *pdev) +{ + struct pxa27x_keypad *keypad; + struct input_dev *input_dev; + struct resource *res; + int irq, error; + + keypad = kzalloc(sizeof(struct pxa27x_keypad), GFP_KERNEL); + if (keypad == NULL) { + dev_err(&pdev->dev, "failed to allocate driver data\n"); + return -ENOMEM; + } + + keypad->pdata = pdev->dev.platform_data; + if (keypad->pdata == NULL) { + dev_err(&pdev->dev, "no platform data defined\n"); + error = -EINVAL; + goto failed_free; + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "failed to get keypad irq\n"); + error = -ENXIO; + goto failed_free; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + dev_err(&pdev->dev, "failed to get I/O memory\n"); + error = -ENXIO; + goto failed_free; + } + + res = request_mem_region(res->start, res_size(res), pdev->name); + if (res == NULL) { + dev_err(&pdev->dev, "failed to request I/O memory\n"); + error = -EBUSY; + goto failed_free; + } + + keypad->mmio_base = ioremap(res->start, res_size(res)); + if (keypad->mmio_base == NULL) { + dev_err(&pdev->dev, "failed to remap I/O memory\n"); + error = -ENXIO; + goto failed_free_mem; + } + + keypad->clk = clk_get(&pdev->dev, "KBDCLK"); + if (IS_ERR(keypad->clk)) { + dev_err(&pdev->dev, "failed to get keypad clock\n"); + error = PTR_ERR(keypad->clk); + goto failed_free_io; + } + + /* Create and register the input driver. */ + input_dev = input_allocate_device(); + if (!input_dev) { + dev_err(&pdev->dev, "failed to allocate input device\n"); + error = -ENOMEM; + goto failed_put_clk; + } + + input_dev->name = pdev->name; + input_dev->id.bustype = BUS_HOST; + input_dev->open = pxa27x_keypad_open; + input_dev->close = pxa27x_keypad_close; + input_dev->dev.parent = &pdev->dev; + + keypad->input_dev = input_dev; + input_set_drvdata(input_dev, keypad); + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP); + if ((keypad->pdata->enable_rotary0 && + keypad->pdata->rotary0_rel_code) || + (keypad->pdata->enable_rotary1 && + keypad->pdata->rotary1_rel_code)) { + input_dev->evbit[0] |= BIT_MASK(EV_REL); + } + + pxa27x_keypad_build_keycode(keypad); + platform_set_drvdata(pdev, keypad); + + error = request_irq(irq, pxa27x_keypad_irq_handler, IRQF_DISABLED, + pdev->name, keypad); + if (error) { + dev_err(&pdev->dev, "failed to request IRQ\n"); + goto failed_free_dev; + } + + keypad->irq = irq; + + /* Register the input device */ + error = input_register_device(input_dev); + if (error) { + dev_err(&pdev->dev, "failed to register input device\n"); + goto failed_free_irq; + } + + device_init_wakeup(&pdev->dev, 1); + + return 0; + +failed_free_irq: + free_irq(irq, pdev); + platform_set_drvdata(pdev, NULL); +failed_free_dev: + input_free_device(input_dev); +failed_put_clk: + clk_put(keypad->clk); +failed_free_io: + iounmap(keypad->mmio_base); +failed_free_mem: + release_mem_region(res->start, res_size(res)); +failed_free: + kfree(keypad); + return error; +} + +static int __devexit pxa27x_keypad_remove(struct platform_device *pdev) +{ + struct pxa27x_keypad *keypad = platform_get_drvdata(pdev); + struct resource *res; + + free_irq(keypad->irq, pdev); + + clk_disable(keypad->clk); + clk_put(keypad->clk); + + input_unregister_device(keypad->input_dev); + input_free_device(keypad->input_dev); + + iounmap(keypad->mmio_base); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + release_mem_region(res->start, res_size(res)); + + platform_set_drvdata(pdev, NULL); + kfree(keypad); + return 0; +} + +/* work with hotplug and coldplug */ +MODULE_ALIAS("platform:pxa27x-keypad"); + +static struct platform_driver pxa27x_keypad_driver = { + .probe = pxa27x_keypad_probe, + .remove = __devexit_p(pxa27x_keypad_remove), + .suspend = pxa27x_keypad_suspend, + .resume = pxa27x_keypad_resume, + .driver = { + .name = "pxa27x-keypad", + .owner = THIS_MODULE, + }, +}; + +static int __init pxa27x_keypad_init(void) +{ + return platform_driver_register(&pxa27x_keypad_driver); +} + +static void __exit pxa27x_keypad_exit(void) +{ + platform_driver_unregister(&pxa27x_keypad_driver); +} + +module_init(pxa27x_keypad_init); +module_exit(pxa27x_keypad_exit); + +MODULE_DESCRIPTION("PXA27x Keypad Controller Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/keyboard/sh_keysc.c b/drivers/input/keyboard/sh_keysc.c new file mode 100644 index 0000000..c600ab7 --- /dev/null +++ b/drivers/input/keyboard/sh_keysc.c @@ -0,0 +1,267 @@ +/* + * SuperH KEYSC Keypad Driver + * + * Copyright (C) 2008 Magnus Damm + * + * Based on gpio_keys.c, Copyright 2005 Phil Blundell + * + * 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/interrupt.h> +#include <linux/irq.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/input.h> +#include <linux/io.h> +#include <asm/sh_keysc.h> + +#define KYCR1_OFFS 0x00 +#define KYCR2_OFFS 0x04 +#define KYINDR_OFFS 0x08 +#define KYOUTDR_OFFS 0x0c + +#define KYCR2_IRQ_LEVEL 0x10 +#define KYCR2_IRQ_DISABLED 0x00 + +static const struct { + unsigned char kymd, keyout, keyin; +} sh_keysc_mode[] = { + [SH_KEYSC_MODE_1] = { 0, 6, 5 }, + [SH_KEYSC_MODE_2] = { 1, 5, 6 }, + [SH_KEYSC_MODE_3] = { 2, 4, 7 }, +}; + +struct sh_keysc_priv { + void __iomem *iomem_base; + unsigned long last_keys; + struct input_dev *input; + struct sh_keysc_info pdata; +}; + +static irqreturn_t sh_keysc_isr(int irq, void *dev_id) +{ + struct platform_device *pdev = dev_id; + struct sh_keysc_priv *priv = platform_get_drvdata(pdev); + struct sh_keysc_info *pdata = &priv->pdata; + unsigned long keys, keys1, keys0, mask; + unsigned char keyin_set, tmp; + int i, k; + + dev_dbg(&pdev->dev, "isr!\n"); + + keys1 = ~0; + keys0 = 0; + + do { + keys = 0; + keyin_set = 0; + + iowrite16(KYCR2_IRQ_DISABLED, priv->iomem_base + KYCR2_OFFS); + + for (i = 0; i < sh_keysc_mode[pdata->mode].keyout; i++) { + iowrite16(0xfff ^ (3 << (i * 2)), + priv->iomem_base + KYOUTDR_OFFS); + udelay(pdata->delay); + tmp = ioread16(priv->iomem_base + KYINDR_OFFS); + keys |= tmp << (sh_keysc_mode[pdata->mode].keyin * i); + tmp ^= (1 << sh_keysc_mode[pdata->mode].keyin) - 1; + keyin_set |= tmp; + } + + iowrite16(0, priv->iomem_base + KYOUTDR_OFFS); + iowrite16(KYCR2_IRQ_LEVEL | (keyin_set << 8), + priv->iomem_base + KYCR2_OFFS); + + keys ^= ~0; + keys &= (1 << (sh_keysc_mode[pdata->mode].keyin * + sh_keysc_mode[pdata->mode].keyout)) - 1; + keys1 &= keys; + keys0 |= keys; + + dev_dbg(&pdev->dev, "keys 0x%08lx\n", keys); + + } while (ioread16(priv->iomem_base + KYCR2_OFFS) & 0x01); + + dev_dbg(&pdev->dev, "last_keys 0x%08lx keys0 0x%08lx keys1 0x%08lx\n", + priv->last_keys, keys0, keys1); + + for (i = 0; i < SH_KEYSC_MAXKEYS; i++) { + k = pdata->keycodes[i]; + if (!k) + continue; + + mask = 1 << i; + + if (!((priv->last_keys ^ keys0) & mask)) + continue; + + if ((keys1 | keys0) & mask) { + input_event(priv->input, EV_KEY, k, 1); + priv->last_keys |= mask; + } + + if (!(keys1 & mask)) { + input_event(priv->input, EV_KEY, k, 0); + priv->last_keys &= ~mask; + } + + } + input_sync(priv->input); + + return IRQ_HANDLED; +} + +#define res_size(res) ((res)->end - (res)->start + 1) + +static int __devinit sh_keysc_probe(struct platform_device *pdev) +{ + struct sh_keysc_priv *priv; + struct sh_keysc_info *pdata; + struct resource *res; + struct input_dev *input; + int i, k; + int irq, error; + + if (!pdev->dev.platform_data) { + dev_err(&pdev->dev, "no platform data defined\n"); + error = -EINVAL; + goto err0; + } + + error = -ENXIO; + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + dev_err(&pdev->dev, "failed to get I/O memory\n"); + goto err0; + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "failed to get irq\n"); + goto err0; + } + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (priv == NULL) { + dev_err(&pdev->dev, "failed to allocate driver data\n"); + error = -ENOMEM; + goto err0; + } + + platform_set_drvdata(pdev, priv); + memcpy(&priv->pdata, pdev->dev.platform_data, sizeof(priv->pdata)); + pdata = &priv->pdata; + + priv->iomem_base = ioremap_nocache(res->start, res_size(res)); + if (priv->iomem_base == NULL) { + dev_err(&pdev->dev, "failed to remap I/O memory\n"); + error = -ENXIO; + goto err1; + } + + priv->input = input_allocate_device(); + if (!priv->input) { + dev_err(&pdev->dev, "failed to allocate input device\n"); + error = -ENOMEM; + goto err2; + } + + input = priv->input; + input->evbit[0] = BIT_MASK(EV_KEY); + + input->name = pdev->name; + input->phys = "sh-keysc-keys/input0"; + input->dev.parent = &pdev->dev; + + input->id.bustype = BUS_HOST; + input->id.vendor = 0x0001; + input->id.product = 0x0001; + input->id.version = 0x0100; + + error = request_irq(irq, sh_keysc_isr, 0, pdev->name, pdev); + if (error) { + dev_err(&pdev->dev, "failed to request IRQ\n"); + goto err3; + } + + for (i = 0; i < SH_KEYSC_MAXKEYS; i++) { + k = pdata->keycodes[i]; + if (k) + input_set_capability(input, EV_KEY, k); + } + + error = input_register_device(input); + if (error) { + dev_err(&pdev->dev, "failed to register input device\n"); + goto err4; + } + + iowrite16((sh_keysc_mode[pdata->mode].kymd << 8) | + pdata->scan_timing, priv->iomem_base + KYCR1_OFFS); + iowrite16(0, priv->iomem_base + KYOUTDR_OFFS); + iowrite16(KYCR2_IRQ_LEVEL, priv->iomem_base + KYCR2_OFFS); + return 0; + err4: + free_irq(irq, pdev); + err3: + input_free_device(input); + err2: + iounmap(priv->iomem_base); + err1: + platform_set_drvdata(pdev, NULL); + kfree(priv); + err0: + return error; +} + +static int __devexit sh_keysc_remove(struct platform_device *pdev) +{ + struct sh_keysc_priv *priv = platform_get_drvdata(pdev); + + iowrite16(KYCR2_IRQ_DISABLED, priv->iomem_base + KYCR2_OFFS); + + input_unregister_device(priv->input); + free_irq(platform_get_irq(pdev, 0), pdev); + iounmap(priv->iomem_base); + + platform_set_drvdata(pdev, NULL); + kfree(priv); + return 0; +} + + +#define sh_keysc_suspend NULL +#define sh_keysc_resume NULL + +struct platform_driver sh_keysc_device_driver = { + .probe = sh_keysc_probe, + .remove = __devexit_p(sh_keysc_remove), + .suspend = sh_keysc_suspend, + .resume = sh_keysc_resume, + .driver = { + .name = "sh_keysc", + } +}; + +static int __init sh_keysc_init(void) +{ + return platform_driver_register(&sh_keysc_device_driver); +} + +static void __exit sh_keysc_exit(void) +{ + platform_driver_unregister(&sh_keysc_device_driver); +} + +module_init(sh_keysc_init); +module_exit(sh_keysc_exit); + +MODULE_AUTHOR("Magnus Damm"); +MODULE_DESCRIPTION("SuperH KEYSC Keypad Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/keyboard/spitzkbd.c b/drivers/input/keyboard/spitzkbd.c new file mode 100644 index 0000000..c48b76a --- /dev/null +++ b/drivers/input/keyboard/spitzkbd.c @@ -0,0 +1,498 @@ +/* + * Keyboard driver for Sharp Spitz, Borzoi and Akita (SL-Cxx00 series) + * + * Copyright (c) 2005 Richard Purdie + * + * Based on corgikbd.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/platform_device.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/jiffies.h> +#include <linux/module.h> +#include <linux/slab.h> + +#include <mach/spitz.h> +#include <mach/hardware.h> +#include <mach/pxa-regs.h> +#include <mach/pxa2xx-gpio.h> + +#define KB_ROWS 7 +#define KB_COLS 11 +#define KB_ROWMASK(r) (1 << (r)) +#define SCANCODE(r,c) (((r)<<4) + (c) + 1) +#define NR_SCANCODES ((KB_ROWS<<4) + 1) + +#define SCAN_INTERVAL (50) /* ms */ +#define HINGE_SCAN_INTERVAL (150) /* ms */ + +#define SPITZ_KEY_CALENDER KEY_F1 +#define SPITZ_KEY_ADDRESS KEY_F2 +#define SPITZ_KEY_FN KEY_F3 +#define SPITZ_KEY_CANCEL KEY_F4 +#define SPITZ_KEY_EXOK KEY_F5 +#define SPITZ_KEY_EXCANCEL KEY_F6 +#define SPITZ_KEY_EXJOGDOWN KEY_F7 +#define SPITZ_KEY_EXJOGUP KEY_F8 +#define SPITZ_KEY_JAP1 KEY_LEFTALT +#define SPITZ_KEY_JAP2 KEY_RIGHTCTRL +#define SPITZ_KEY_SYNC KEY_F9 +#define SPITZ_KEY_MAIL KEY_F10 +#define SPITZ_KEY_OK KEY_F11 +#define SPITZ_KEY_MENU KEY_F12 + +static unsigned char spitzkbd_keycode[NR_SCANCODES] = { + 0, /* 0 */ + KEY_LEFTCTRL, KEY_1, KEY_3, KEY_5, KEY_6, KEY_7, KEY_9, KEY_0, KEY_BACKSPACE, SPITZ_KEY_EXOK, SPITZ_KEY_EXCANCEL, 0, 0, 0, 0, 0, /* 1-16 */ + 0, KEY_2, KEY_4, KEY_R, KEY_Y, KEY_8, KEY_I, KEY_O, KEY_P, SPITZ_KEY_EXJOGDOWN, SPITZ_KEY_EXJOGUP, 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 */ + SPITZ_KEY_ADDRESS, 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 */ + SPITZ_KEY_CALENDER, KEY_A, KEY_D, KEY_C, KEY_B, KEY_N, KEY_DOT, 0, KEY_ENTER, KEY_LEFTSHIFT, 0, 0, 0, 0, 0, 0, /* 65-80 */ + SPITZ_KEY_MAIL, KEY_Z, KEY_X, KEY_MINUS, KEY_SPACE, KEY_COMMA, 0, KEY_UP, 0, 0, SPITZ_KEY_FN, 0, 0, 0, 0, 0, /* 81-96 */ + KEY_SYSRQ, SPITZ_KEY_JAP1, SPITZ_KEY_JAP2, SPITZ_KEY_CANCEL, SPITZ_KEY_OK, SPITZ_KEY_MENU, KEY_LEFT, KEY_DOWN, KEY_RIGHT, 0, 0, 0, 0, 0, 0, 0 /* 97-112 */ +}; + +static int spitz_strobes[] = { + SPITZ_GPIO_KEY_STROBE0, + SPITZ_GPIO_KEY_STROBE1, + SPITZ_GPIO_KEY_STROBE2, + SPITZ_GPIO_KEY_STROBE3, + SPITZ_GPIO_KEY_STROBE4, + SPITZ_GPIO_KEY_STROBE5, + SPITZ_GPIO_KEY_STROBE6, + SPITZ_GPIO_KEY_STROBE7, + SPITZ_GPIO_KEY_STROBE8, + SPITZ_GPIO_KEY_STROBE9, + SPITZ_GPIO_KEY_STROBE10, +}; + +static int spitz_senses[] = { + SPITZ_GPIO_KEY_SENSE0, + SPITZ_GPIO_KEY_SENSE1, + SPITZ_GPIO_KEY_SENSE2, + SPITZ_GPIO_KEY_SENSE3, + SPITZ_GPIO_KEY_SENSE4, + SPITZ_GPIO_KEY_SENSE5, + SPITZ_GPIO_KEY_SENSE6, +}; + +struct spitzkbd { + unsigned char keycode[ARRAY_SIZE(spitzkbd_keycode)]; + struct input_dev *input; + char phys[32]; + + spinlock_t lock; + struct timer_list timer; + struct timer_list htimer; + + unsigned int suspended; + unsigned long suspend_jiffies; +}; + +#define KB_DISCHARGE_DELAY 10 +#define KB_ACTIVATE_DELAY 10 + +/* Helper functions for reading the keyboard matrix + * Note: We should really be using the generic gpio functions to alter + * GPDR but it requires a function call per GPIO bit which is + * excessive when we need to access 11 bits at once, multiple times. + * These functions must be called within local_irq_save()/local_irq_restore() + * or similar. + */ +static inline void spitzkbd_discharge_all(void) +{ + /* STROBE All HiZ */ + GPCR0 = SPITZ_GPIO_G0_STROBE_BIT; + GPDR0 &= ~SPITZ_GPIO_G0_STROBE_BIT; + GPCR1 = SPITZ_GPIO_G1_STROBE_BIT; + GPDR1 &= ~SPITZ_GPIO_G1_STROBE_BIT; + GPCR2 = SPITZ_GPIO_G2_STROBE_BIT; + GPDR2 &= ~SPITZ_GPIO_G2_STROBE_BIT; + GPCR3 = SPITZ_GPIO_G3_STROBE_BIT; + GPDR3 &= ~SPITZ_GPIO_G3_STROBE_BIT; +} + +static inline void spitzkbd_activate_all(void) +{ + /* STROBE ALL -> High */ + GPSR0 = SPITZ_GPIO_G0_STROBE_BIT; + GPDR0 |= SPITZ_GPIO_G0_STROBE_BIT; + GPSR1 = SPITZ_GPIO_G1_STROBE_BIT; + GPDR1 |= SPITZ_GPIO_G1_STROBE_BIT; + GPSR2 = SPITZ_GPIO_G2_STROBE_BIT; + GPDR2 |= SPITZ_GPIO_G2_STROBE_BIT; + GPSR3 = SPITZ_GPIO_G3_STROBE_BIT; + GPDR3 |= SPITZ_GPIO_G3_STROBE_BIT; + + udelay(KB_DISCHARGE_DELAY); + + /* Clear any interrupts we may have triggered when altering the GPIO lines */ + GEDR0 = SPITZ_GPIO_G0_SENSE_BIT; + GEDR1 = SPITZ_GPIO_G1_SENSE_BIT; + GEDR2 = SPITZ_GPIO_G2_SENSE_BIT; + GEDR3 = SPITZ_GPIO_G3_SENSE_BIT; +} + +static inline void spitzkbd_activate_col(int col) +{ + int gpio = spitz_strobes[col]; + GPDR0 &= ~SPITZ_GPIO_G0_STROBE_BIT; + GPDR1 &= ~SPITZ_GPIO_G1_STROBE_BIT; + GPDR2 &= ~SPITZ_GPIO_G2_STROBE_BIT; + GPDR3 &= ~SPITZ_GPIO_G3_STROBE_BIT; + GPSR(gpio) = GPIO_bit(gpio); + GPDR(gpio) |= GPIO_bit(gpio); +} + +static inline void spitzkbd_reset_col(int col) +{ + int gpio = spitz_strobes[col]; + GPDR0 &= ~SPITZ_GPIO_G0_STROBE_BIT; + GPDR1 &= ~SPITZ_GPIO_G1_STROBE_BIT; + GPDR2 &= ~SPITZ_GPIO_G2_STROBE_BIT; + GPDR3 &= ~SPITZ_GPIO_G3_STROBE_BIT; + GPCR(gpio) = GPIO_bit(gpio); + GPDR(gpio) |= GPIO_bit(gpio); +} + +static inline int spitzkbd_get_row_status(int col) +{ + return ((GPLR0 >> 12) & 0x01) | ((GPLR0 >> 16) & 0x02) + | ((GPLR2 >> 25) & 0x04) | ((GPLR1 << 1) & 0x08) + | ((GPLR1 >> 0) & 0x10) | ((GPLR1 >> 1) & 0x60); +} + +/* + * The spitz 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 spitzkbd_scankeyboard(struct spitzkbd *spitzkbd_data) +{ + unsigned int row, col, rowd; + unsigned long flags; + unsigned int num_pressed, pwrkey = ((GPLR(SPITZ_GPIO_ON_KEY) & GPIO_bit(SPITZ_GPIO_ON_KEY)) != 0); + + if (spitzkbd_data->suspended) + return; + + spin_lock_irqsave(&spitzkbd_data->lock, flags); + + num_pressed = 0; + for (col = 0; col < KB_COLS; col++) { + /* + * Discharge the output driver capacitatance + * in the keyboard matrix. (Yes it is significant..) + */ + + spitzkbd_discharge_all(); + udelay(KB_DISCHARGE_DELAY); + + spitzkbd_activate_col(col); + udelay(KB_ACTIVATE_DELAY); + + rowd = spitzkbd_get_row_status(col); + for (row = 0; row < KB_ROWS; row++) { + unsigned int scancode, pressed; + + scancode = SCANCODE(row, col); + pressed = rowd & KB_ROWMASK(row); + + input_report_key(spitzkbd_data->input, spitzkbd_data->keycode[scancode], pressed); + + if (pressed) + num_pressed++; + } + spitzkbd_reset_col(col); + } + + spitzkbd_activate_all(); + + input_report_key(spitzkbd_data->input, SPITZ_KEY_SYNC, (GPLR(SPITZ_GPIO_SYNC) & GPIO_bit(SPITZ_GPIO_SYNC)) != 0 ); + input_report_key(spitzkbd_data->input, KEY_SUSPEND, pwrkey); + + if (pwrkey && time_after(jiffies, spitzkbd_data->suspend_jiffies + msecs_to_jiffies(1000))) { + input_event(spitzkbd_data->input, EV_PWR, KEY_SUSPEND, 1); + spitzkbd_data->suspend_jiffies = jiffies; + } + + input_sync(spitzkbd_data->input); + + /* if any keys are pressed, enable the timer */ + if (num_pressed) + mod_timer(&spitzkbd_data->timer, jiffies + msecs_to_jiffies(SCAN_INTERVAL)); + + spin_unlock_irqrestore(&spitzkbd_data->lock, flags); +} + +/* + * spitz keyboard interrupt handler. + */ +static irqreturn_t spitzkbd_interrupt(int irq, void *dev_id) +{ + struct spitzkbd *spitzkbd_data = dev_id; + + if (!timer_pending(&spitzkbd_data->timer)) { + /** wait chattering delay **/ + udelay(20); + spitzkbd_scankeyboard(spitzkbd_data); + } + + return IRQ_HANDLED; +} + +/* + * spitz timer checking for released keys + */ +static void spitzkbd_timer_callback(unsigned long data) +{ + struct spitzkbd *spitzkbd_data = (struct spitzkbd *) data; + + spitzkbd_scankeyboard(spitzkbd_data); +} + +/* + * The hinge switches generate an interrupt. + * We debounce the switches and pass them to the input system. + */ + +static irqreturn_t spitzkbd_hinge_isr(int irq, void *dev_id) +{ + struct spitzkbd *spitzkbd_data = dev_id; + + if (!timer_pending(&spitzkbd_data->htimer)) + mod_timer(&spitzkbd_data->htimer, jiffies + msecs_to_jiffies(HINGE_SCAN_INTERVAL)); + + return IRQ_HANDLED; +} + +#define HINGE_STABLE_COUNT 2 +static int sharpsl_hinge_state; +static int hinge_count; + +static void spitzkbd_hinge_timer(unsigned long data) +{ + struct spitzkbd *spitzkbd_data = (struct spitzkbd *) data; + unsigned long state; + unsigned long flags; + + state = GPLR(SPITZ_GPIO_SWA) & (GPIO_bit(SPITZ_GPIO_SWA)|GPIO_bit(SPITZ_GPIO_SWB)); + state |= (GPLR(SPITZ_GPIO_AK_INT) & GPIO_bit(SPITZ_GPIO_AK_INT)); + if (state != sharpsl_hinge_state) { + hinge_count = 0; + sharpsl_hinge_state = state; + } else if (hinge_count < HINGE_STABLE_COUNT) { + hinge_count++; + } + + if (hinge_count >= HINGE_STABLE_COUNT) { + spin_lock_irqsave(&spitzkbd_data->lock, flags); + + input_report_switch(spitzkbd_data->input, SW_LID, ((GPLR(SPITZ_GPIO_SWA) & GPIO_bit(SPITZ_GPIO_SWA)) != 0)); + input_report_switch(spitzkbd_data->input, SW_TABLET_MODE, ((GPLR(SPITZ_GPIO_SWB) & GPIO_bit(SPITZ_GPIO_SWB)) != 0)); + input_report_switch(spitzkbd_data->input, SW_HEADPHONE_INSERT, ((GPLR(SPITZ_GPIO_AK_INT) & GPIO_bit(SPITZ_GPIO_AK_INT)) != 0)); + input_sync(spitzkbd_data->input); + + spin_unlock_irqrestore(&spitzkbd_data->lock, flags); + } else { + mod_timer(&spitzkbd_data->htimer, jiffies + msecs_to_jiffies(HINGE_SCAN_INTERVAL)); + } +} + +#ifdef CONFIG_PM +static int spitzkbd_suspend(struct platform_device *dev, pm_message_t state) +{ + int i; + struct spitzkbd *spitzkbd = platform_get_drvdata(dev); + spitzkbd->suspended = 1; + + /* Set Strobe lines as inputs - *except* strobe line 0 leave this + enabled so we can detect a power button press for resume */ + for (i = 1; i < SPITZ_KEY_STROBE_NUM; i++) + pxa_gpio_mode(spitz_strobes[i] | GPIO_IN); + + return 0; +} + +static int spitzkbd_resume(struct platform_device *dev) +{ + int i; + struct spitzkbd *spitzkbd = platform_get_drvdata(dev); + + for (i = 0; i < SPITZ_KEY_STROBE_NUM; i++) + pxa_gpio_mode(spitz_strobes[i] | GPIO_OUT | GPIO_DFLT_HIGH); + + /* Upon resume, ignore the suspend key for a short while */ + spitzkbd->suspend_jiffies = jiffies; + spitzkbd->suspended = 0; + + return 0; +} +#else +#define spitzkbd_suspend NULL +#define spitzkbd_resume NULL +#endif + +static int __init spitzkbd_probe(struct platform_device *dev) +{ + struct spitzkbd *spitzkbd; + struct input_dev *input_dev; + int i, err = -ENOMEM; + + spitzkbd = kzalloc(sizeof(struct spitzkbd), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!spitzkbd || !input_dev) + goto fail; + + platform_set_drvdata(dev, spitzkbd); + strcpy(spitzkbd->phys, "spitzkbd/input0"); + + spin_lock_init(&spitzkbd->lock); + + /* Init Keyboard rescan timer */ + init_timer(&spitzkbd->timer); + spitzkbd->timer.function = spitzkbd_timer_callback; + spitzkbd->timer.data = (unsigned long) spitzkbd; + + /* Init Hinge Timer */ + init_timer(&spitzkbd->htimer); + spitzkbd->htimer.function = spitzkbd_hinge_timer; + spitzkbd->htimer.data = (unsigned long) spitzkbd; + + spitzkbd->suspend_jiffies = jiffies; + + spitzkbd->input = input_dev; + + input_dev->name = "Spitz Keyboard"; + input_dev->phys = spitzkbd->phys; + input_dev->dev.parent = &dev->dev; + + input_dev->id.bustype = BUS_HOST; + input_dev->id.vendor = 0x0001; + input_dev->id.product = 0x0001; + input_dev->id.version = 0x0100; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP) | + BIT_MASK(EV_PWR) | BIT_MASK(EV_SW); + input_dev->keycode = spitzkbd->keycode; + input_dev->keycodesize = sizeof(unsigned char); + input_dev->keycodemax = ARRAY_SIZE(spitzkbd_keycode); + + memcpy(spitzkbd->keycode, spitzkbd_keycode, sizeof(spitzkbd->keycode)); + for (i = 0; i < ARRAY_SIZE(spitzkbd_keycode); i++) + set_bit(spitzkbd->keycode[i], input_dev->keybit); + clear_bit(0, input_dev->keybit); + set_bit(KEY_SUSPEND, input_dev->keybit); + set_bit(SW_LID, input_dev->swbit); + set_bit(SW_TABLET_MODE, input_dev->swbit); + set_bit(SW_HEADPHONE_INSERT, input_dev->swbit); + + err = input_register_device(input_dev); + if (err) + goto fail; + + mod_timer(&spitzkbd->htimer, jiffies + msecs_to_jiffies(HINGE_SCAN_INTERVAL)); + + /* Setup sense interrupts - RisingEdge Detect, sense lines as inputs */ + for (i = 0; i < SPITZ_KEY_SENSE_NUM; i++) { + pxa_gpio_mode(spitz_senses[i] | GPIO_IN); + if (request_irq(IRQ_GPIO(spitz_senses[i]), spitzkbd_interrupt, + IRQF_DISABLED|IRQF_TRIGGER_RISING, + "Spitzkbd Sense", spitzkbd)) + printk(KERN_WARNING "spitzkbd: Can't get Sense IRQ: %d!\n", i); + } + + /* Set Strobe lines as outputs - set high */ + for (i = 0; i < SPITZ_KEY_STROBE_NUM; i++) + pxa_gpio_mode(spitz_strobes[i] | GPIO_OUT | GPIO_DFLT_HIGH); + + pxa_gpio_mode(SPITZ_GPIO_SYNC | GPIO_IN); + pxa_gpio_mode(SPITZ_GPIO_ON_KEY | GPIO_IN); + pxa_gpio_mode(SPITZ_GPIO_SWA | GPIO_IN); + pxa_gpio_mode(SPITZ_GPIO_SWB | GPIO_IN); + + request_irq(SPITZ_IRQ_GPIO_SYNC, spitzkbd_interrupt, + IRQF_DISABLED | IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + "Spitzkbd Sync", spitzkbd); + request_irq(SPITZ_IRQ_GPIO_ON_KEY, spitzkbd_interrupt, + IRQF_DISABLED | IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + "Spitzkbd PwrOn", spitzkbd); + request_irq(SPITZ_IRQ_GPIO_SWA, spitzkbd_hinge_isr, + IRQF_DISABLED | IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + "Spitzkbd SWA", spitzkbd); + request_irq(SPITZ_IRQ_GPIO_SWB, spitzkbd_hinge_isr, + IRQF_DISABLED | IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + "Spitzkbd SWB", spitzkbd); + request_irq(SPITZ_IRQ_GPIO_AK_INT, spitzkbd_hinge_isr, + IRQF_DISABLED | IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + "Spitzkbd HP", spitzkbd); + + return 0; + + fail: input_free_device(input_dev); + kfree(spitzkbd); + return err; +} + +static int spitzkbd_remove(struct platform_device *dev) +{ + int i; + struct spitzkbd *spitzkbd = platform_get_drvdata(dev); + + for (i = 0; i < SPITZ_KEY_SENSE_NUM; i++) + free_irq(IRQ_GPIO(spitz_senses[i]), spitzkbd); + + free_irq(SPITZ_IRQ_GPIO_SYNC, spitzkbd); + free_irq(SPITZ_IRQ_GPIO_ON_KEY, spitzkbd); + free_irq(SPITZ_IRQ_GPIO_SWA, spitzkbd); + free_irq(SPITZ_IRQ_GPIO_SWB, spitzkbd); + free_irq(SPITZ_IRQ_GPIO_AK_INT, spitzkbd); + + del_timer_sync(&spitzkbd->htimer); + del_timer_sync(&spitzkbd->timer); + + input_unregister_device(spitzkbd->input); + + kfree(spitzkbd); + + return 0; +} + +static struct platform_driver spitzkbd_driver = { + .probe = spitzkbd_probe, + .remove = spitzkbd_remove, + .suspend = spitzkbd_suspend, + .resume = spitzkbd_resume, + .driver = { + .name = "spitz-keyboard", + .owner = THIS_MODULE, + }, +}; + +static int __devinit spitzkbd_init(void) +{ + return platform_driver_register(&spitzkbd_driver); +} + +static void __exit spitzkbd_exit(void) +{ + platform_driver_unregister(&spitzkbd_driver); +} + +module_init(spitzkbd_init); +module_exit(spitzkbd_exit); + +MODULE_AUTHOR("Richard Purdie <rpurdie@rpsys.net>"); +MODULE_DESCRIPTION("Spitz Keyboard Driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:spitz-keyboard"); diff --git a/drivers/input/keyboard/stowaway.c b/drivers/input/keyboard/stowaway.c new file mode 100644 index 0000000..7437219 --- /dev/null +++ b/drivers/input/keyboard/stowaway.c @@ -0,0 +1,184 @@ +/* + * Stowaway keyboard driver for Linux + */ + +/* + * Copyright (c) 2006 Marek Vasut + * + * Based on Newton keyboard driver for Linux + * by Justin Cormack + */ + +/* + * 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 <marek.vasut@gmail.com>, or by paper mail: + * Marek Vasut, Liskovecka 559, Frydek-Mistek, 738 01 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 "Stowaway keyboard driver" + +MODULE_AUTHOR("Marek Vasut <marek.vasut@gmail.com>"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +#define SKBD_KEY_MASK 0x7f +#define SKBD_RELEASE 0x80 + +static unsigned char skbd_keycode[128] = { + KEY_1, KEY_2, KEY_3, KEY_Z, KEY_4, KEY_5, KEY_6, KEY_7, + 0, KEY_Q, KEY_W, KEY_E, KEY_R, KEY_T, KEY_Y, KEY_GRAVE, + KEY_X, KEY_A, KEY_S, KEY_D, KEY_F, KEY_G, KEY_H, KEY_SPACE, + KEY_CAPSLOCK, KEY_TAB, KEY_LEFTCTRL, 0, 0, 0, 0, 0, + 0, 0, 0, KEY_LEFTALT, 0, 0, 0, 0, + 0, 0, 0, 0, KEY_C, KEY_V, KEY_B, KEY_N, + KEY_MINUS, KEY_EQUAL, KEY_BACKSPACE, KEY_HOME, KEY_8, KEY_9, KEY_0, KEY_ESC, + KEY_LEFTBRACE, KEY_RIGHTBRACE, KEY_BACKSLASH, KEY_END, KEY_U, KEY_I, KEY_O, KEY_P, + KEY_APOSTROPHE, KEY_ENTER, KEY_PAGEUP,0, KEY_J, KEY_K, KEY_L, KEY_SEMICOLON, + KEY_SLASH, KEY_UP, KEY_PAGEDOWN, 0,KEY_M, KEY_COMMA, KEY_DOT, KEY_INSERT, + KEY_DELETE, KEY_LEFT, KEY_DOWN, KEY_RIGHT, 0, 0, 0, + KEY_LEFTSHIFT, KEY_RIGHTSHIFT, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5, KEY_F6, KEY_F7, + KEY_F8, KEY_F9, KEY_F10, KEY_F11, KEY_F12, 0, 0, 0 +}; + +struct skbd { + unsigned char keycode[128]; + struct input_dev *dev; + struct serio *serio; + char phys[32]; +}; + +static irqreturn_t skbd_interrupt(struct serio *serio, unsigned char data, + unsigned int flags) +{ + struct skbd *skbd = serio_get_drvdata(serio); + struct input_dev *dev = skbd->dev; + + if (skbd->keycode[data & SKBD_KEY_MASK]) { + input_report_key(dev, skbd->keycode[data & SKBD_KEY_MASK], + !(data & SKBD_RELEASE)); + input_sync(dev); + } + + return IRQ_HANDLED; +} + +static int skbd_connect(struct serio *serio, struct serio_driver *drv) +{ + struct skbd *skbd; + struct input_dev *input_dev; + int err = -ENOMEM; + int i; + + skbd = kzalloc(sizeof(struct skbd), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!skbd || !input_dev) + goto fail1; + + skbd->serio = serio; + skbd->dev = input_dev; + snprintf(skbd->phys, sizeof(skbd->phys), "%s/input0", serio->phys); + memcpy(skbd->keycode, skbd_keycode, sizeof(skbd->keycode)); + + input_dev->name = "Stowaway Keyboard"; + input_dev->phys = skbd->phys; + input_dev->id.bustype = BUS_RS232; + input_dev->id.vendor = SERIO_STOWAWAY; + input_dev->id.product = 0x0001; + input_dev->id.version = 0x0100; + input_dev->dev.parent = &serio->dev; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP); + input_dev->keycode = skbd->keycode; + input_dev->keycodesize = sizeof(unsigned char); + input_dev->keycodemax = ARRAY_SIZE(skbd_keycode); + for (i = 0; i < ARRAY_SIZE(skbd_keycode); i++) + set_bit(skbd_keycode[i], input_dev->keybit); + clear_bit(0, input_dev->keybit); + + serio_set_drvdata(serio, skbd); + + err = serio_open(serio, drv); + if (err) + goto fail2; + + err = input_register_device(skbd->dev); + if (err) + goto fail3; + + return 0; + + fail3: serio_close(serio); + fail2: serio_set_drvdata(serio, NULL); + fail1: input_free_device(input_dev); + kfree(skbd); + return err; +} + +static void skbd_disconnect(struct serio *serio) +{ + struct skbd *skbd = serio_get_drvdata(serio); + + serio_close(serio); + serio_set_drvdata(serio, NULL); + input_unregister_device(skbd->dev); + kfree(skbd); +} + +static struct serio_device_id skbd_serio_ids[] = { + { + .type = SERIO_RS232, + .proto = SERIO_STOWAWAY, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { 0 } +}; + +MODULE_DEVICE_TABLE(serio, skbd_serio_ids); + +static struct serio_driver skbd_drv = { + .driver = { + .name = "stowaway", + }, + .description = DRIVER_DESC, + .id_table = skbd_serio_ids, + .interrupt = skbd_interrupt, + .connect = skbd_connect, + .disconnect = skbd_disconnect, +}; + +static int __init skbd_init(void) +{ + return serio_register_driver(&skbd_drv); +} + +static void __exit skbd_exit(void) +{ + serio_unregister_driver(&skbd_drv); +} + +module_init(skbd_init); +module_exit(skbd_exit); diff --git a/drivers/input/keyboard/sunkbd.c b/drivers/input/keyboard/sunkbd.c new file mode 100644 index 0000000..9fce6d1 --- /dev/null +++ b/drivers/input/keyboard/sunkbd.c @@ -0,0 +1,366 @@ +/* + * 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,112, + 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; + unsigned char enabled; + 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 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->enabled) + break; + + if (sunkbd->keycode[data & SUNKBD_KEY]) { + 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 = input_get_drvdata(dev); + + 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(struct work_struct *work) +{ + struct sunkbd *sunkbd = container_of(work, struct sunkbd, tq); + + 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)); +} + +static void sunkbd_enable(struct sunkbd *sunkbd, int enable) +{ + serio_pause_rx(sunkbd->serio); + sunkbd->enabled = enable; + serio_continue_rx(sunkbd->serio); +} + +/* + * 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; + struct input_dev *input_dev; + int err = -ENOMEM; + int i; + + sunkbd = kzalloc(sizeof(struct sunkbd), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!sunkbd || !input_dev) + goto fail1; + + sunkbd->serio = serio; + sunkbd->dev = input_dev; + init_waitqueue_head(&sunkbd->wait); + INIT_WORK(&sunkbd->tq, sunkbd_reinit); + snprintf(sunkbd->phys, sizeof(sunkbd->phys), "%s/input0", serio->phys); + + serio_set_drvdata(serio, sunkbd); + + err = serio_open(serio, drv); + if (err) + goto fail2; + + if (sunkbd_initialize(sunkbd) < 0) { + err = -ENODEV; + goto fail3; + } + + snprintf(sunkbd->name, sizeof(sunkbd->name), "Sun Type %d keyboard", sunkbd->type); + memcpy(sunkbd->keycode, sunkbd_keycode, sizeof(sunkbd->keycode)); + + input_dev->name = sunkbd->name; + input_dev->phys = sunkbd->phys; + input_dev->id.bustype = BUS_RS232; + input_dev->id.vendor = SERIO_SUNKBD; + input_dev->id.product = sunkbd->type; + input_dev->id.version = 0x0100; + input_dev->dev.parent = &serio->dev; + + input_set_drvdata(input_dev, sunkbd); + + input_dev->event = sunkbd_event; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_LED) | + BIT_MASK(EV_SND) | BIT_MASK(EV_REP); + input_dev->ledbit[0] = BIT_MASK(LED_CAPSL) | BIT_MASK(LED_COMPOSE) | + BIT_MASK(LED_SCROLLL) | BIT_MASK(LED_NUML); + input_dev->sndbit[0] = BIT_MASK(SND_CLICK) | BIT_MASK(SND_BELL); + + input_dev->keycode = sunkbd->keycode; + input_dev->keycodesize = sizeof(unsigned char); + input_dev->keycodemax = ARRAY_SIZE(sunkbd_keycode); + for (i = 0; i < 128; i++) + set_bit(sunkbd->keycode[i], input_dev->keybit); + clear_bit(0, input_dev->keybit); + + sunkbd_enable(sunkbd, 1); + + err = input_register_device(sunkbd->dev); + if (err) + goto fail4; + + return 0; + + fail4: sunkbd_enable(sunkbd, 0); + fail3: serio_close(serio); + fail2: serio_set_drvdata(serio, NULL); + fail1: input_free_device(input_dev); + kfree(sunkbd); + return err; +} + +/* + * sunkbd_disconnect() unregisters and closes behind us. + */ + +static void sunkbd_disconnect(struct serio *serio) +{ + struct sunkbd *sunkbd = serio_get_drvdata(serio); + + sunkbd_enable(sunkbd, 0); + 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) +{ + return serio_register_driver(&sunkbd_drv); +} + +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/tosakbd.c b/drivers/input/keyboard/tosakbd.c new file mode 100644 index 0000000..677276b --- /dev/null +++ b/drivers/input/keyboard/tosakbd.c @@ -0,0 +1,430 @@ +/* + * Keyboard driver for Sharp Tosa models (SL-6000x) + * + * Copyright (c) 2005 Dirk Opfer + * Copyright (c) 2007 Dmitry Baryshkov + * + * Based on xtkbd.c/locomkbd.c/corgikbd.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/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/input.h> +#include <linux/delay.h> +#include <linux/interrupt.h> + +#include <mach/gpio.h> +#include <mach/tosa.h> + +#define KB_ROWMASK(r) (1 << (r)) +#define SCANCODE(r, c) (((r)<<4) + (c) + 1) +#define NR_SCANCODES SCANCODE(TOSA_KEY_SENSE_NUM - 1, TOSA_KEY_STROBE_NUM - 1) + 1 + +#define SCAN_INTERVAL (HZ/10) + +#define KB_DISCHARGE_DELAY 10 +#define KB_ACTIVATE_DELAY 10 + +static unsigned int tosakbd_keycode[NR_SCANCODES] = { +0, +0, KEY_W, 0, 0, 0, KEY_K, KEY_BACKSPACE, KEY_P, +0, 0, 0, 0, 0, 0, 0, 0, +KEY_Q, KEY_E, KEY_T, KEY_Y, 0, KEY_O, KEY_I, KEY_COMMA, +0, 0, 0, 0, 0, 0, 0, 0, +KEY_A, KEY_D, KEY_G, KEY_U, 0, KEY_L, KEY_ENTER, KEY_DOT, +0, 0, 0, 0, 0, 0, 0, 0, +KEY_Z, KEY_C, KEY_V, KEY_J, TOSA_KEY_ADDRESSBOOK, TOSA_KEY_CANCEL, TOSA_KEY_CENTER, TOSA_KEY_OK, +KEY_LEFTSHIFT, 0, 0, 0, 0, 0, 0, 0, +KEY_S, KEY_R, KEY_B, KEY_N, TOSA_KEY_CALENDAR, TOSA_KEY_HOMEPAGE, KEY_LEFTCTRL, TOSA_KEY_LIGHT, +0, KEY_RIGHTSHIFT, 0, 0, 0, 0, 0, 0, +KEY_TAB, KEY_SLASH, KEY_H, KEY_M, TOSA_KEY_MENU, 0, KEY_UP, 0, +0, 0, TOSA_KEY_FN, 0, 0, 0, 0, 0, +KEY_X, KEY_F, KEY_SPACE, KEY_APOSTROPHE, TOSA_KEY_MAIL, KEY_LEFT, KEY_DOWN, KEY_RIGHT, +0, 0, 0, +}; + +struct tosakbd { + unsigned int keycode[ARRAY_SIZE(tosakbd_keycode)]; + struct input_dev *input; + int suspended; + spinlock_t lock; /* protect kbd scanning */ + struct timer_list timer; +}; + + +/* Helper functions for reading the keyboard matrix + * Note: We should really be using the generic gpio functions 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. + */ +#define GET_ROWS_STATUS(c) ((GPLR2 & TOSA_GPIO_ALL_SENSE_BIT) >> TOSA_GPIO_ALL_SENSE_RSHIFT) + +static inline void tosakbd_discharge_all(void) +{ + /* STROBE All HiZ */ + GPCR1 = TOSA_GPIO_HIGH_STROBE_BIT; + GPDR1 &= ~TOSA_GPIO_HIGH_STROBE_BIT; + GPCR2 = TOSA_GPIO_LOW_STROBE_BIT; + GPDR2 &= ~TOSA_GPIO_LOW_STROBE_BIT; +} + +static inline void tosakbd_activate_all(void) +{ + /* STROBE ALL -> High */ + GPSR1 = TOSA_GPIO_HIGH_STROBE_BIT; + GPDR1 |= TOSA_GPIO_HIGH_STROBE_BIT; + GPSR2 = TOSA_GPIO_LOW_STROBE_BIT; + GPDR2 |= TOSA_GPIO_LOW_STROBE_BIT; + + udelay(KB_DISCHARGE_DELAY); + + /* STATE CLEAR */ + GEDR2 |= TOSA_GPIO_ALL_SENSE_BIT; +} + +static inline void tosakbd_activate_col(int col) +{ + if (col <= 5) { + /* STROBE col -> High, not col -> HiZ */ + GPSR1 = TOSA_GPIO_STROBE_BIT(col); + GPDR1 = (GPDR1 & ~TOSA_GPIO_HIGH_STROBE_BIT) | TOSA_GPIO_STROBE_BIT(col); + } else { + /* STROBE col -> High, not col -> HiZ */ + GPSR2 = TOSA_GPIO_STROBE_BIT(col); + GPDR2 = (GPDR2 & ~TOSA_GPIO_LOW_STROBE_BIT) | TOSA_GPIO_STROBE_BIT(col); + } +} + +static inline void tosakbd_reset_col(int col) +{ + if (col <= 5) { + /* STROBE col -> Low */ + GPCR1 = TOSA_GPIO_STROBE_BIT(col); + /* STROBE col -> out, not col -> HiZ */ + GPDR1 = (GPDR1 & ~TOSA_GPIO_HIGH_STROBE_BIT) | TOSA_GPIO_STROBE_BIT(col); + } else { + /* STROBE col -> Low */ + GPCR2 = TOSA_GPIO_STROBE_BIT(col); + /* STROBE col -> out, not col -> HiZ */ + GPDR2 = (GPDR2 & ~TOSA_GPIO_LOW_STROBE_BIT) | TOSA_GPIO_STROBE_BIT(col); + } +} +/* + * The tosa 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 tosakbd_scankeyboard(struct platform_device *dev) +{ + struct tosakbd *tosakbd = platform_get_drvdata(dev); + unsigned int row, col, rowd; + unsigned long flags; + unsigned int num_pressed = 0; + + spin_lock_irqsave(&tosakbd->lock, flags); + + if (tosakbd->suspended) + goto out; + + for (col = 0; col < TOSA_KEY_STROBE_NUM; col++) { + /* + * Discharge the output driver capacitatance + * in the keyboard matrix. (Yes it is significant..) + */ + tosakbd_discharge_all(); + udelay(KB_DISCHARGE_DELAY); + + tosakbd_activate_col(col); + udelay(KB_ACTIVATE_DELAY); + + rowd = GET_ROWS_STATUS(col); + + for (row = 0; row < TOSA_KEY_SENSE_NUM; row++) { + unsigned int scancode, pressed; + scancode = SCANCODE(row, col); + pressed = rowd & KB_ROWMASK(row); + + if (pressed && !tosakbd->keycode[scancode]) + dev_warn(&dev->dev, + "unhandled scancode: 0x%02x\n", + scancode); + + input_report_key(tosakbd->input, + tosakbd->keycode[scancode], + pressed); + if (pressed) + num_pressed++; + } + + tosakbd_reset_col(col); + } + + tosakbd_activate_all(); + + input_sync(tosakbd->input); + + /* if any keys are pressed, enable the timer */ + if (num_pressed) + mod_timer(&tosakbd->timer, jiffies + SCAN_INTERVAL); + + out: + spin_unlock_irqrestore(&tosakbd->lock, flags); +} + +/* + * tosa keyboard interrupt handler. + */ +static irqreturn_t tosakbd_interrupt(int irq, void *__dev) +{ + struct platform_device *dev = __dev; + struct tosakbd *tosakbd = platform_get_drvdata(dev); + + if (!timer_pending(&tosakbd->timer)) { + /** wait chattering delay **/ + udelay(20); + tosakbd_scankeyboard(dev); + } + + return IRQ_HANDLED; +} + +/* + * tosa timer checking for released keys + */ +static void tosakbd_timer_callback(unsigned long __dev) +{ + struct platform_device *dev = (struct platform_device *)__dev; + + tosakbd_scankeyboard(dev); +} + +#ifdef CONFIG_PM +static int tosakbd_suspend(struct platform_device *dev, pm_message_t state) +{ + struct tosakbd *tosakbd = platform_get_drvdata(dev); + unsigned long flags; + + spin_lock_irqsave(&tosakbd->lock, flags); + tosakbd->suspended = 1; + spin_unlock_irqrestore(&tosakbd->lock, flags); + + del_timer_sync(&tosakbd->timer); + + return 0; +} + +static int tosakbd_resume(struct platform_device *dev) +{ + struct tosakbd *tosakbd = platform_get_drvdata(dev); + + tosakbd->suspended = 0; + tosakbd_scankeyboard(dev); + + return 0; +} +#else +#define tosakbd_suspend NULL +#define tosakbd_resume NULL +#endif + +static int __devinit tosakbd_probe(struct platform_device *pdev) { + + int i; + struct tosakbd *tosakbd; + struct input_dev *input_dev; + int error; + + tosakbd = kzalloc(sizeof(struct tosakbd), GFP_KERNEL); + if (!tosakbd) + return -ENOMEM; + + input_dev = input_allocate_device(); + if (!input_dev) { + kfree(tosakbd); + return -ENOMEM; + } + + platform_set_drvdata(pdev, tosakbd); + + spin_lock_init(&tosakbd->lock); + + /* Init Keyboard rescan timer */ + init_timer(&tosakbd->timer); + tosakbd->timer.function = tosakbd_timer_callback; + tosakbd->timer.data = (unsigned long) pdev; + + tosakbd->input = input_dev; + + input_set_drvdata(input_dev, tosakbd); + input_dev->name = "Tosa Keyboard"; + input_dev->phys = "tosakbd/input0"; + input_dev->dev.parent = &pdev->dev; + + input_dev->id.bustype = BUS_HOST; + input_dev->id.vendor = 0x0001; + input_dev->id.product = 0x0001; + input_dev->id.version = 0x0100; + + input_dev->evbit[0] = BIT(EV_KEY) | BIT(EV_REP); + input_dev->keycode = tosakbd->keycode; + input_dev->keycodesize = sizeof(unsigned int); + input_dev->keycodemax = ARRAY_SIZE(tosakbd_keycode); + + memcpy(tosakbd->keycode, tosakbd_keycode, sizeof(tosakbd_keycode)); + + for (i = 0; i < ARRAY_SIZE(tosakbd_keycode); i++) + __set_bit(tosakbd->keycode[i], input_dev->keybit); + clear_bit(0, input_dev->keybit); + + /* Setup sense interrupts - RisingEdge Detect, sense lines as inputs */ + for (i = 0; i < TOSA_KEY_SENSE_NUM; i++) { + int gpio = TOSA_GPIO_KEY_SENSE(i); + int irq; + error = gpio_request(gpio, "tosakbd"); + if (error < 0) { + printk(KERN_ERR "tosakbd: failed to request GPIO %d, " + " error %d\n", gpio, error); + goto fail; + } + + error = gpio_direction_input(TOSA_GPIO_KEY_SENSE(i)); + if (error < 0) { + printk(KERN_ERR "tosakbd: failed to configure input" + " direction for GPIO %d, error %d\n", + gpio, error); + gpio_free(gpio); + goto fail; + } + + irq = gpio_to_irq(gpio); + if (irq < 0) { + error = irq; + printk(KERN_ERR "gpio-keys: Unable to get irq number" + " for GPIO %d, error %d\n", + gpio, error); + gpio_free(gpio); + goto fail; + } + + error = request_irq(irq, tosakbd_interrupt, + IRQF_DISABLED | IRQF_TRIGGER_RISING, + "tosakbd", pdev); + + if (error) { + printk("tosakbd: Can't get IRQ: %d: error %d!\n", + irq, error); + gpio_free(gpio); + goto fail; + } + } + + /* Set Strobe lines as outputs - set high */ + for (i = 0; i < TOSA_KEY_STROBE_NUM; i++) { + int gpio = TOSA_GPIO_KEY_STROBE(i); + error = gpio_request(gpio, "tosakbd"); + if (error < 0) { + printk(KERN_ERR "tosakbd: failed to request GPIO %d, " + " error %d\n", gpio, error); + goto fail2; + } + + error = gpio_direction_output(gpio, 1); + if (error < 0) { + printk(KERN_ERR "tosakbd: failed to configure input" + " direction for GPIO %d, error %d\n", + gpio, error); + gpio_free(gpio); + goto fail; + } + + } + + error = input_register_device(input_dev); + if (error) { + printk(KERN_ERR "tosakbd: Unable to register input device, " + "error: %d\n", error); + goto fail; + } + + printk(KERN_INFO "input: Tosa Keyboard Registered\n"); + + return 0; + +fail2: + while (--i >= 0) + gpio_free(TOSA_GPIO_KEY_STROBE(i)); + + i = TOSA_KEY_SENSE_NUM; +fail: + while (--i >= 0) { + free_irq(gpio_to_irq(TOSA_GPIO_KEY_SENSE(i)), pdev); + gpio_free(TOSA_GPIO_KEY_SENSE(i)); + } + + platform_set_drvdata(pdev, NULL); + input_free_device(input_dev); + kfree(tosakbd); + + return error; +} + +static int __devexit tosakbd_remove(struct platform_device *dev) +{ + int i; + struct tosakbd *tosakbd = platform_get_drvdata(dev); + + for (i = 0; i < TOSA_KEY_STROBE_NUM; i++) + gpio_free(TOSA_GPIO_KEY_STROBE(i)); + + for (i = 0; i < TOSA_KEY_SENSE_NUM; i++) { + free_irq(gpio_to_irq(TOSA_GPIO_KEY_SENSE(i)), dev); + gpio_free(TOSA_GPIO_KEY_SENSE(i)); + } + + del_timer_sync(&tosakbd->timer); + + input_unregister_device(tosakbd->input); + + kfree(tosakbd); + + return 0; +} + +static struct platform_driver tosakbd_driver = { + .probe = tosakbd_probe, + .remove = __devexit_p(tosakbd_remove), + .suspend = tosakbd_suspend, + .resume = tosakbd_resume, + .driver = { + .name = "tosa-keyboard", + .owner = THIS_MODULE, + }, +}; + +static int __devinit tosakbd_init(void) +{ + return platform_driver_register(&tosakbd_driver); +} + +static void __exit tosakbd_exit(void) +{ + platform_driver_unregister(&tosakbd_driver); +} + +module_init(tosakbd_init); +module_exit(tosakbd_exit); + +MODULE_AUTHOR("Dirk Opfer <Dirk@Opfer-Online.de>"); +MODULE_DESCRIPTION("Tosa Keyboard Driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:tosa-keyboard"); diff --git a/drivers/input/keyboard/xtkbd.c b/drivers/input/keyboard/xtkbd.c new file mode 100644 index 0000000..37b01d7 --- /dev/null +++ b/drivers/input/keyboard/xtkbd.c @@ -0,0 +1,183 @@ +/* + * 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 +}; + +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 xtkbd *xtkbd = serio_get_drvdata(serio); + + switch (data) { + case XTKBD_EMUL0: + case XTKBD_EMUL1: + break; + default: + + if (xtkbd->keycode[data & XTKBD_KEY]) { + 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; + struct input_dev *input_dev; + int err = -ENOMEM; + int i; + + xtkbd = kmalloc(sizeof(struct xtkbd), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!xtkbd || !input_dev) + goto fail1; + + xtkbd->serio = serio; + xtkbd->dev = input_dev; + snprintf(xtkbd->phys, sizeof(xtkbd->phys), "%s/input0", serio->phys); + memcpy(xtkbd->keycode, xtkbd_keycode, sizeof(xtkbd->keycode)); + + input_dev->name = "XT Keyboard"; + input_dev->phys = xtkbd->phys; + input_dev->id.bustype = BUS_XTKBD; + input_dev->id.vendor = 0x0001; + input_dev->id.product = 0x0001; + input_dev->id.version = 0x0100; + input_dev->dev.parent = &serio->dev; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP); + input_dev->keycode = xtkbd->keycode; + input_dev->keycodesize = sizeof(unsigned char); + input_dev->keycodemax = ARRAY_SIZE(xtkbd_keycode); + + for (i = 0; i < 255; i++) + set_bit(xtkbd->keycode[i], input_dev->keybit); + clear_bit(0, input_dev->keybit); + + serio_set_drvdata(serio, xtkbd); + + err = serio_open(serio, drv); + if (err) + goto fail2; + + err = input_register_device(xtkbd->dev); + if (err) + goto fail3; + + return 0; + + fail3: serio_close(serio); + fail2: serio_set_drvdata(serio, NULL); + fail1: input_free_device(input_dev); + kfree(xtkbd); + return err; +} + +static void xtkbd_disconnect(struct serio *serio) +{ + struct xtkbd *xtkbd = serio_get_drvdata(serio); + + serio_close(serio); + serio_set_drvdata(serio, NULL); + input_unregister_device(xtkbd->dev); + 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) +{ + return serio_register_driver(&xtkbd_drv); +} + +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 0000000..199055d --- /dev/null +++ b/drivers/input/misc/Kconfig @@ -0,0 +1,223 @@ +# +# 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 PCSPKR_PLATFORM + 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 && 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_APANEL + tristate "Fujitsu Lifebook Application Panel buttons" + depends on X86 && I2C && LEDS_CLASS + select INPUT_POLLDEV + select CHECK_SIGNATURE + help + Say Y here for support of the Application Panel buttons, used on + Fujitsu Lifebook. These are attached to the mainboard through + an SMBus interface managed by the I2C Intel ICH (i801) driver, + which you should also build for this kernel. + + To compile this driver as a module, choose M here: the module will + be called apanel. + +config INPUT_IXP4XX_BEEPER + tristate "IXP4XX Beeper support" + depends on ARCH_IXP4XX + help + If you say yes here, you can connect a beeper to the + ixp4xx gpio pins. This is used by the LinkSys NSLU2. + + If unsure, say Y. + + To compile this driver as a module, choose M here: the + module will be called ixp4xx-beeper. + +config INPUT_COBALT_BTNS + tristate "Cobalt button interface" + depends on MIPS_COBALT + select INPUT_POLLDEV + help + Say Y here if you want to support MIPS Cobalt button interface. + + To compile this driver as a module, choose M here: the + module will be called cobalt_btns. + +config INPUT_WISTRON_BTNS + tristate "x86 Wistron laptop button interface" + depends on X86 && !X86_64 + select INPUT_POLLDEV + select NEW_LEDS + select LEDS_CLASS + select CHECK_SIGNATURE + help + Say Y here for support of Wistron laptop button interfaces, used on + laptops of various brands, including Acer and Fujitsu-Siemens. If + available, mail and wifi LEDs will be controllable via /sys/class/leds. + + To compile this driver as a module, choose M here: the module will + be called wistron_btns. + +config INPUT_ATLAS_BTNS + tristate "x86 Atlas button interface" + depends on X86 && ACPI + help + Say Y here for support of Atlas wallmount touchscreen buttons. + The events will show up as scancodes F1 through F9 via evdev. + + To compile this driver as a module, choose M here: the module will + be called atlas_btns. + +config INPUT_ATI_REMOTE + tristate "ATI / X10 USB RF remote control" + depends on USB_ARCH_HAS_HCD + select USB + help + Say Y here if you want to use an ATI or X10 "Lola" USB remote control. + These are RF remotes with USB receivers. + The ATI remote comes with many of ATI's All-In-Wonder video cards. + The X10 "Lola" remote is available at: + <http://www.x10.com/products/lola_sg1.htm> + This driver provides mouse pointer, left and right mouse buttons, + and maps all the other remote buttons to keypress events. + + To compile this driver as a module, choose M here: the module will be + called ati_remote. + +config INPUT_ATI_REMOTE2 + tristate "ATI / Philips USB RF remote control" + depends on USB_ARCH_HAS_HCD + select USB + help + Say Y here if you want to use an ATI or Philips USB RF remote control. + These are RF remotes with USB receivers. + ATI Remote Wonder II comes with some ATI's All-In-Wonder video cards + and is also available as a separate product. + This driver provides mouse pointer, left and right mouse buttons, + and maps all the other remote buttons to keypress events. + + To compile this driver as a module, choose M here: the module will be + called ati_remote2. + +config INPUT_KEYSPAN_REMOTE + tristate "Keyspan DMR USB remote control (EXPERIMENTAL)" + depends on EXPERIMENTAL + depends on USB_ARCH_HAS_HCD + select USB + help + Say Y here if you want to use a Keyspan DMR USB remote control. + Currently only the UIA-11 type of receiver has been tested. The tag + on the receiver that connects to the USB port should have a P/N that + will tell you what type of DMR you have. The UIA-10 type is not + supported at this time. This driver maps all buttons to keypress + events. + + To compile this driver as a module, choose M here: the module will + be called keyspan_remote. + +config INPUT_POWERMATE + tristate "Griffin PowerMate and Contour Jog support" + depends on USB_ARCH_HAS_HCD + select USB + help + Say Y here if you want to use Griffin PowerMate or Contour Jog devices. + These are aluminum dials which can measure clockwise and anticlockwise + rotation. The dial also acts as a pushbutton. The base contains an LED + which can be instructed to pulse or to switch to a particular intensity. + + You can download userspace tools from + <http://sowerbutts.com/powermate/>. + + To compile this driver as a module, choose M here: the + module will be called powermate. + +config INPUT_YEALINK + tristate "Yealink usb-p1k voip phone" + depends on EXPERIMENTAL + depends on USB_ARCH_HAS_HCD + select USB + help + Say Y here if you want to enable keyboard and LCD functions of the + Yealink usb-p1k usb phones. The audio part is enabled by the generic + usb sound driver, so you might want to enable that as well. + + For information about how to use these additional functions, see + <file:Documentation/input/yealink.txt>. + + To compile this driver as a module, choose M here: the module will be + called yealink. + +config INPUT_CM109 + tristate "C-Media CM109 USB I/O Controller" + depends on EXPERIMENTAL + depends on USB_ARCH_HAS_HCD + select USB + help + Say Y here if you want to enable keyboard and buzzer functions of the + C-Media CM109 usb phones. The audio part is enabled by the generic + usb sound driver, so you might want to enable that as well. + + To compile this driver as a module, choose M here: the module will be + called cm109. + +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 INPUT_SGI_BTNS + tristate "SGI Indy/O2 volume button interface" + depends on SGI_IP22 || SGI_IP32 + select INPUT_POLLDEV + help + Say Y here if you want to support SGI Indy/O2 volume button interface. + + To compile this driver as a module, choose M here: the + module will be called sgi_btns. + +config HP_SDC_RTC + tristate "HP SDC Real Time Clock" + depends on GSC || HP300 + 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 0000000..d7db2ae --- /dev/null +++ b/drivers/input/misc/Makefile @@ -0,0 +1,23 @@ +# +# 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_IXP4XX_BEEPER) += ixp4xx-beeper.o +obj-$(CONFIG_INPUT_COBALT_BTNS) += cobalt_btns.o +obj-$(CONFIG_INPUT_WISTRON_BTNS) += wistron_btns.o +obj-$(CONFIG_INPUT_ATLAS_BTNS) += atlas_btns.o +obj-$(CONFIG_INPUT_ATI_REMOTE) += ati_remote.o +obj-$(CONFIG_INPUT_ATI_REMOTE2) += ati_remote2.o +obj-$(CONFIG_INPUT_KEYSPAN_REMOTE) += keyspan_remote.o +obj-$(CONFIG_INPUT_POWERMATE) += powermate.o +obj-$(CONFIG_INPUT_YEALINK) += yealink.o +obj-$(CONFIG_INPUT_CM109) += cm109.o +obj-$(CONFIG_HP_SDC_RTC) += hp_sdc_rtc.o +obj-$(CONFIG_INPUT_UINPUT) += uinput.o +obj-$(CONFIG_INPUT_APANEL) += apanel.o +obj-$(CONFIG_INPUT_SGI_BTNS) += sgi_btns.o diff --git a/drivers/input/misc/apanel.c b/drivers/input/misc/apanel.c new file mode 100644 index 0000000..d82f7f7 --- /dev/null +++ b/drivers/input/misc/apanel.c @@ -0,0 +1,377 @@ +/* + * Fujitsu Lifebook Application Panel button drive + * + * Copyright (C) 2007 Stephen Hemminger <shemminger@linux-foundation.org> + * Copyright (C) 2001-2003 Jochen Eisinger <jochen@penguin-breeder.org> + * + * 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. + * + * Many Fujitsu Lifebook laptops have a small panel of buttons that are + * accessible via the i2c/smbus interface. This driver polls those + * buttons and generates input events. + * + * For more details see: + * http://apanel.sourceforge.net/tech.php + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/ioport.h> +#include <linux/io.h> +#include <linux/input-polldev.h> +#include <linux/i2c.h> +#include <linux/workqueue.h> +#include <linux/leds.h> + +#define APANEL_NAME "Fujitsu Application Panel" +#define APANEL_VERSION "1.3.1" +#define APANEL "apanel" + +/* How often we poll keys - msecs */ +#define POLL_INTERVAL_DEFAULT 1000 + +/* Magic constants in BIOS that tell about buttons */ +enum apanel_devid { + APANEL_DEV_NONE = 0, + APANEL_DEV_APPBTN = 1, + APANEL_DEV_CDBTN = 2, + APANEL_DEV_LCD = 3, + APANEL_DEV_LED = 4, + + APANEL_DEV_MAX, +}; + +enum apanel_chip { + CHIP_NONE = 0, + CHIP_OZ992C = 1, + CHIP_OZ163T = 2, + CHIP_OZ711M3 = 4, +}; + +/* Result of BIOS snooping/probing -- what features are supported */ +static enum apanel_chip device_chip[APANEL_DEV_MAX]; + +#define MAX_PANEL_KEYS 12 + +struct apanel { + struct input_polled_dev *ipdev; + struct i2c_client client; + unsigned short keymap[MAX_PANEL_KEYS]; + u16 nkeys; + u16 led_bits; + struct work_struct led_work; + struct led_classdev mail_led; +}; + + +static int apanel_probe(struct i2c_adapter *, int, int); + +/* for now, we only support one address */ +static unsigned short normal_i2c[] = {0, I2C_CLIENT_END}; +static unsigned short ignore = I2C_CLIENT_END; +static struct i2c_client_address_data addr_data = { + .normal_i2c = normal_i2c, + .probe = &ignore, + .ignore = &ignore, +}; + +static void report_key(struct input_dev *input, unsigned keycode) +{ + pr_debug(APANEL ": report key %#x\n", keycode); + input_report_key(input, keycode, 1); + input_sync(input); + + input_report_key(input, keycode, 0); + input_sync(input); +} + +/* Poll for key changes + * + * Read Application keys via SMI + * A (0x4), B (0x8), Internet (0x2), Email (0x1). + * + * CD keys: + * Forward (0x100), Rewind (0x200), Stop (0x400), Pause (0x800) + */ +static void apanel_poll(struct input_polled_dev *ipdev) +{ + struct apanel *ap = ipdev->private; + struct input_dev *idev = ipdev->input; + u8 cmd = device_chip[APANEL_DEV_APPBTN] == CHIP_OZ992C ? 0 : 8; + s32 data; + int i; + + data = i2c_smbus_read_word_data(&ap->client, cmd); + if (data < 0) + return; /* ignore errors (due to ACPI??) */ + + /* write back to clear latch */ + i2c_smbus_write_word_data(&ap->client, cmd, 0); + + if (!data) + return; + + dev_dbg(&idev->dev, APANEL ": data %#x\n", data); + for (i = 0; i < idev->keycodemax; i++) + if ((1u << i) & data) + report_key(idev, ap->keymap[i]); +} + +/* Track state changes of LED */ +static void led_update(struct work_struct *work) +{ + struct apanel *ap = container_of(work, struct apanel, led_work); + + i2c_smbus_write_word_data(&ap->client, 0x10, ap->led_bits); +} + +static void mail_led_set(struct led_classdev *led, + enum led_brightness value) +{ + struct apanel *ap = container_of(led, struct apanel, mail_led); + + if (value != LED_OFF) + ap->led_bits |= 0x8000; + else + ap->led_bits &= ~0x8000; + + schedule_work(&ap->led_work); +} + +static int apanel_detach_client(struct i2c_client *client) +{ + struct apanel *ap = i2c_get_clientdata(client); + + if (device_chip[APANEL_DEV_LED] != CHIP_NONE) + led_classdev_unregister(&ap->mail_led); + + input_unregister_polled_device(ap->ipdev); + i2c_detach_client(&ap->client); + input_free_polled_device(ap->ipdev); + + return 0; +} + +/* Function is invoked for every i2c adapter. */ +static int apanel_attach_adapter(struct i2c_adapter *adap) +{ + dev_dbg(&adap->dev, APANEL ": attach adapter id=%d\n", adap->id); + + /* Our device is connected only to i801 on laptop */ + if (adap->id != I2C_HW_SMBUS_I801) + return -ENODEV; + + return i2c_probe(adap, &addr_data, apanel_probe); +} + +static void apanel_shutdown(struct i2c_client *client) +{ + apanel_detach_client(client); +} + +static struct i2c_driver apanel_driver = { + .driver = { + .name = APANEL, + }, + .attach_adapter = &apanel_attach_adapter, + .detach_client = &apanel_detach_client, + .shutdown = &apanel_shutdown, +}; + +static struct apanel apanel = { + .client = { + .driver = &apanel_driver, + .name = APANEL, + }, + .keymap = { + [0] = KEY_MAIL, + [1] = KEY_WWW, + [2] = KEY_PROG2, + [3] = KEY_PROG1, + + [8] = KEY_FORWARD, + [9] = KEY_REWIND, + [10] = KEY_STOPCD, + [11] = KEY_PLAYPAUSE, + + }, + .mail_led = { + .name = "mail:blue", + .brightness_set = mail_led_set, + }, +}; + +/* NB: Only one panel on the i2c. */ +static int apanel_probe(struct i2c_adapter *bus, int address, int kind) +{ + struct apanel *ap; + struct input_polled_dev *ipdev; + struct input_dev *idev; + u8 cmd = device_chip[APANEL_DEV_APPBTN] == CHIP_OZ992C ? 0 : 8; + int i, err = -ENOMEM; + + dev_dbg(&bus->dev, APANEL ": probe adapter %p addr %d kind %d\n", + bus, address, kind); + + ap = &apanel; + + ipdev = input_allocate_polled_device(); + if (!ipdev) + goto out1; + + ap->ipdev = ipdev; + ap->client.adapter = bus; + ap->client.addr = address; + + i2c_set_clientdata(&ap->client, ap); + + err = i2c_attach_client(&ap->client); + if (err) + goto out2; + + err = i2c_smbus_write_word_data(&ap->client, cmd, 0); + if (err) { + dev_warn(&ap->client.dev, APANEL ": smbus write error %d\n", + err); + goto out3; + } + + ipdev->poll = apanel_poll; + ipdev->poll_interval = POLL_INTERVAL_DEFAULT; + ipdev->private = ap; + + idev = ipdev->input; + idev->name = APANEL_NAME " buttons"; + idev->phys = "apanel/input0"; + idev->id.bustype = BUS_HOST; + idev->dev.parent = &ap->client.dev; + + set_bit(EV_KEY, idev->evbit); + + idev->keycode = ap->keymap; + idev->keycodesize = sizeof(ap->keymap[0]); + idev->keycodemax = (device_chip[APANEL_DEV_CDBTN] != CHIP_NONE) ? 12 : 4; + + for (i = 0; i < idev->keycodemax; i++) + if (ap->keymap[i]) + set_bit(ap->keymap[i], idev->keybit); + + err = input_register_polled_device(ipdev); + if (err) + goto out3; + + INIT_WORK(&ap->led_work, led_update); + if (device_chip[APANEL_DEV_LED] != CHIP_NONE) { + err = led_classdev_register(&ap->client.dev, &ap->mail_led); + if (err) + goto out4; + } + + return 0; +out4: + input_unregister_polled_device(ipdev); +out3: + i2c_detach_client(&ap->client); +out2: + input_free_polled_device(ipdev); +out1: + return err; +} + +/* Scan the system ROM for the signature "FJKEYINF" */ +static __init const void __iomem *bios_signature(const void __iomem *bios) +{ + ssize_t offset; + const unsigned char signature[] = "FJKEYINF"; + + for (offset = 0; offset < 0x10000; offset += 0x10) { + if (check_signature(bios + offset, signature, + sizeof(signature)-1)) + return bios + offset; + } + pr_notice(APANEL ": Fujitsu BIOS signature '%s' not found...\n", + signature); + return NULL; +} + +static int __init apanel_init(void) +{ + void __iomem *bios; + const void __iomem *p; + u8 devno; + int found = 0; + + bios = ioremap(0xF0000, 0x10000); /* Can't fail */ + + p = bios_signature(bios); + if (!p) { + iounmap(bios); + return -ENODEV; + } + + /* just use the first address */ + p += 8; + normal_i2c[0] = readb(p+3) >> 1; + + for ( ; (devno = readb(p)) & 0x7f; p += 4) { + unsigned char method, slave, chip; + + method = readb(p + 1); + chip = readb(p + 2); + slave = readb(p + 3) >> 1; + + if (slave != normal_i2c[0]) { + pr_notice(APANEL ": only one SMBus slave " + "address supported, skiping device...\n"); + continue; + } + + /* translate alternative device numbers */ + switch (devno) { + case 6: + devno = APANEL_DEV_APPBTN; + break; + case 7: + devno = APANEL_DEV_LED; + break; + } + + if (devno >= APANEL_DEV_MAX) + pr_notice(APANEL ": unknown device %u found\n", devno); + else if (device_chip[devno] != CHIP_NONE) + pr_warning(APANEL ": duplicate entry for devno %u\n", devno); + + else if (method != 1 && method != 2 && method != 4) { + pr_notice(APANEL ": unknown method %u for devno %u\n", + method, devno); + } else { + device_chip[devno] = (enum apanel_chip) chip; + ++found; + } + } + iounmap(bios); + + if (found == 0) { + pr_info(APANEL ": no input devices reported by BIOS\n"); + return -EIO; + } + + return i2c_add_driver(&apanel_driver); +} +module_init(apanel_init); + +static void __exit apanel_cleanup(void) +{ + i2c_del_driver(&apanel_driver); +} +module_exit(apanel_cleanup); + +MODULE_AUTHOR("Stephen Hemminger <shemminger@linux-foundation.org>"); +MODULE_DESCRIPTION(APANEL_NAME " driver"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(APANEL_VERSION); + +MODULE_ALIAS("dmi:*:svnFUJITSU:pnLifeBook*:pvr*:rvnFUJITSU:*"); +MODULE_ALIAS("dmi:*:svnFUJITSU:pnLifebook*:pvr*:rvnFUJITSU:*"); diff --git a/drivers/input/misc/ati_remote.c b/drivers/input/misc/ati_remote.c new file mode 100644 index 0000000..e290fde --- /dev/null +++ b/drivers/input/misc/ati_remote.c @@ -0,0 +1,863 @@ +/* + * USB ATI Remote support + * + * Version 2.2.0 Copyright (c) 2004 Torrey Hoffman <thoffman@arnor.net> + * Version 2.1.1 Copyright (c) 2002 Vladimir Dergachev + * + * This 2.2.0 version is a rewrite / cleanup of the 2.1.1 driver, including + * porting to the 2.6 kernel interfaces, along with other modification + * to better match the style of the existing usb/input drivers. However, the + * protocol and hardware handling is essentially unchanged from 2.1.1. + * + * The 2.1.1 driver was derived from the usbati_remote and usbkbd drivers by + * Vojtech Pavlik. + * + * Changes: + * + * Feb 2004: Torrey Hoffman <thoffman@arnor.net> + * Version 2.2.0 + * Jun 2004: Torrey Hoffman <thoffman@arnor.net> + * Version 2.2.1 + * Added key repeat support contributed by: + * Vincent Vanackere <vanackere@lif.univ-mrs.fr> + * Added support for the "Lola" remote contributed by: + * Seth Cohn <sethcohn@yahoo.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 + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * + * Hardware & software notes + * + * These remote controls are distributed by ATI as part of their + * "All-In-Wonder" video card packages. The receiver self-identifies as a + * "USB Receiver" with manufacturer "X10 Wireless Technology Inc". + * + * The "Lola" remote is available from X10. See: + * http://www.x10.com/products/lola_sg1.htm + * The Lola is similar to the ATI remote but has no mouse support, and slightly + * different keys. + * + * It is possible to use multiple receivers and remotes on multiple computers + * simultaneously by configuring them to use specific channels. + * + * The RF protocol used by the remote supports 16 distinct channels, 1 to 16. + * Actually, it may even support more, at least in some revisions of the + * hardware. + * + * Each remote can be configured to transmit on one channel as follows: + * - Press and hold the "hand icon" button. + * - When the red LED starts to blink, let go of the "hand icon" button. + * - When it stops blinking, input the channel code as two digits, from 01 + * to 16, and press the hand icon again. + * + * The timing can be a little tricky. Try loading the module with debug=1 + * to have the kernel print out messages about the remote control number + * and mask. Note: debugging prints remote numbers as zero-based hexadecimal. + * + * The driver has a "channel_mask" parameter. This bitmask specifies which + * channels will be ignored by the module. To mask out channels, just add + * all the 2^channel_number values together. + * + * For instance, set channel_mask = 2^4 = 16 (binary 10000) to make ati_remote + * ignore signals coming from remote controls transmitting on channel 4, but + * accept all other channels. + * + * Or, set channel_mask = 65533, (0xFFFD), and all channels except 1 will be + * ignored. + * + * The default is 0 (respond to all channels). Bit 0 and bits 17-32 of this + * parameter are unused. + * + */ + +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/usb/input.h> +#include <linux/wait.h> +#include <linux/jiffies.h> + +/* + * Module and Version Information, Module Parameters + */ + +#define ATI_REMOTE_VENDOR_ID 0x0bc7 +#define ATI_REMOTE_PRODUCT_ID 0x004 +#define LOLA_REMOTE_PRODUCT_ID 0x002 +#define MEDION_REMOTE_PRODUCT_ID 0x006 + +#define DRIVER_VERSION "2.2.1" +#define DRIVER_AUTHOR "Torrey Hoffman <thoffman@arnor.net>" +#define DRIVER_DESC "ATI/X10 RF USB Remote Control" + +#define NAME_BUFSIZE 80 /* size of product name, path buffers */ +#define DATA_BUFSIZE 63 /* size of URB data buffers */ + +/* + * Duplicate event filtering time. + * Sequential, identical KIND_FILTERED inputs with less than + * FILTER_TIME milliseconds between them are considered as repeat + * events. The hardware generates 5 events for the first keypress + * and we have to take this into account for an accurate repeat + * behaviour. + */ +#define FILTER_TIME 60 /* msec */ +#define REPEAT_DELAY 500 /* msec */ + +static unsigned long channel_mask; +module_param(channel_mask, ulong, 0644); +MODULE_PARM_DESC(channel_mask, "Bitmask of remote control channels to ignore"); + +static int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Enable extra debug messages and information"); + +static int repeat_filter = FILTER_TIME; +module_param(repeat_filter, int, 0644); +MODULE_PARM_DESC(repeat_filter, "Repeat filter time, default = 60 msec"); + +static int repeat_delay = REPEAT_DELAY; +module_param(repeat_delay, int, 0644); +MODULE_PARM_DESC(repeat_delay, "Delay before sending repeats, default = 500 msec"); + +#define dbginfo(dev, format, arg...) do { if (debug) dev_info(dev , format , ## arg); } while (0) +#undef err +#define err(format, arg...) printk(KERN_ERR format , ## arg) + +static struct usb_device_id ati_remote_table[] = { + { USB_DEVICE(ATI_REMOTE_VENDOR_ID, ATI_REMOTE_PRODUCT_ID) }, + { USB_DEVICE(ATI_REMOTE_VENDOR_ID, LOLA_REMOTE_PRODUCT_ID) }, + { USB_DEVICE(ATI_REMOTE_VENDOR_ID, MEDION_REMOTE_PRODUCT_ID) }, + {} /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, ati_remote_table); + +/* Get hi and low bytes of a 16-bits int */ +#define HI(a) ((unsigned char)((a) >> 8)) +#define LO(a) ((unsigned char)((a) & 0xff)) + +#define SEND_FLAG_IN_PROGRESS 1 +#define SEND_FLAG_COMPLETE 2 + +/* Device initialization strings */ +static char init1[] = { 0x01, 0x00, 0x20, 0x14 }; +static char init2[] = { 0x01, 0x00, 0x20, 0x14, 0x20, 0x20, 0x20 }; + +struct ati_remote { + struct input_dev *idev; + struct usb_device *udev; + struct usb_interface *interface; + + struct urb *irq_urb; + struct urb *out_urb; + struct usb_endpoint_descriptor *endpoint_in; + struct usb_endpoint_descriptor *endpoint_out; + unsigned char *inbuf; + unsigned char *outbuf; + dma_addr_t inbuf_dma; + dma_addr_t outbuf_dma; + + unsigned char old_data[2]; /* Detect duplicate events */ + unsigned long old_jiffies; + unsigned long acc_jiffies; /* handle acceleration */ + unsigned long first_jiffies; + + unsigned int repeat_count; + + char name[NAME_BUFSIZE]; + char phys[NAME_BUFSIZE]; + + wait_queue_head_t wait; + int send_flags; +}; + +/* "Kinds" of messages sent from the hardware to the driver. */ +#define KIND_END 0 +#define KIND_LITERAL 1 /* Simply pass to input system */ +#define KIND_FILTERED 2 /* Add artificial key-up events, drop keyrepeats */ +#define KIND_LU 3 /* Directional keypad diagonals - left up, */ +#define KIND_RU 4 /* right up, */ +#define KIND_LD 5 /* left down, */ +#define KIND_RD 6 /* right down */ +#define KIND_ACCEL 7 /* Directional keypad - left, right, up, down.*/ + +/* Translation table from hardware messages to input events. */ +static const struct { + short kind; + unsigned char data1, data2; + int type; + unsigned int code; + int value; +} ati_remote_tbl[] = { + /* Directional control pad axes */ + {KIND_ACCEL, 0x35, 0x70, EV_REL, REL_X, -1}, /* left */ + {KIND_ACCEL, 0x36, 0x71, EV_REL, REL_X, 1}, /* right */ + {KIND_ACCEL, 0x37, 0x72, EV_REL, REL_Y, -1}, /* up */ + {KIND_ACCEL, 0x38, 0x73, EV_REL, REL_Y, 1}, /* down */ + /* Directional control pad diagonals */ + {KIND_LU, 0x39, 0x74, EV_REL, 0, 0}, /* left up */ + {KIND_RU, 0x3a, 0x75, EV_REL, 0, 0}, /* right up */ + {KIND_LD, 0x3c, 0x77, EV_REL, 0, 0}, /* left down */ + {KIND_RD, 0x3b, 0x76, EV_REL, 0, 0}, /* right down */ + + /* "Mouse button" buttons */ + {KIND_LITERAL, 0x3d, 0x78, EV_KEY, BTN_LEFT, 1}, /* left btn down */ + {KIND_LITERAL, 0x3e, 0x79, EV_KEY, BTN_LEFT, 0}, /* left btn up */ + {KIND_LITERAL, 0x41, 0x7c, EV_KEY, BTN_RIGHT, 1},/* right btn down */ + {KIND_LITERAL, 0x42, 0x7d, EV_KEY, BTN_RIGHT, 0},/* right btn up */ + + /* Artificial "doubleclick" events are generated by the hardware. + * They are mapped to the "side" and "extra" mouse buttons here. */ + {KIND_FILTERED, 0x3f, 0x7a, EV_KEY, BTN_SIDE, 1}, /* left dblclick */ + {KIND_FILTERED, 0x43, 0x7e, EV_KEY, BTN_EXTRA, 1},/* right dblclick */ + + /* keyboard. */ + {KIND_FILTERED, 0xd2, 0x0d, EV_KEY, KEY_1, 1}, + {KIND_FILTERED, 0xd3, 0x0e, EV_KEY, KEY_2, 1}, + {KIND_FILTERED, 0xd4, 0x0f, EV_KEY, KEY_3, 1}, + {KIND_FILTERED, 0xd5, 0x10, EV_KEY, KEY_4, 1}, + {KIND_FILTERED, 0xd6, 0x11, EV_KEY, KEY_5, 1}, + {KIND_FILTERED, 0xd7, 0x12, EV_KEY, KEY_6, 1}, + {KIND_FILTERED, 0xd8, 0x13, EV_KEY, KEY_7, 1}, + {KIND_FILTERED, 0xd9, 0x14, EV_KEY, KEY_8, 1}, + {KIND_FILTERED, 0xda, 0x15, EV_KEY, KEY_9, 1}, + {KIND_FILTERED, 0xdc, 0x17, EV_KEY, KEY_0, 1}, + {KIND_FILTERED, 0xc5, 0x00, EV_KEY, KEY_A, 1}, + {KIND_FILTERED, 0xc6, 0x01, EV_KEY, KEY_B, 1}, + {KIND_FILTERED, 0xde, 0x19, EV_KEY, KEY_C, 1}, + {KIND_FILTERED, 0xe0, 0x1b, EV_KEY, KEY_D, 1}, + {KIND_FILTERED, 0xe6, 0x21, EV_KEY, KEY_E, 1}, + {KIND_FILTERED, 0xe8, 0x23, EV_KEY, KEY_F, 1}, + + /* "special" keys */ + {KIND_FILTERED, 0xdd, 0x18, EV_KEY, KEY_KPENTER, 1}, /* "check" */ + {KIND_FILTERED, 0xdb, 0x16, EV_KEY, KEY_MENU, 1}, /* "menu" */ + {KIND_FILTERED, 0xc7, 0x02, EV_KEY, KEY_POWER, 1}, /* Power */ + {KIND_FILTERED, 0xc8, 0x03, EV_KEY, KEY_TV, 1}, /* TV */ + {KIND_FILTERED, 0xc9, 0x04, EV_KEY, KEY_DVD, 1}, /* DVD */ + {KIND_FILTERED, 0xca, 0x05, EV_KEY, KEY_WWW, 1}, /* WEB */ + {KIND_FILTERED, 0xcb, 0x06, EV_KEY, KEY_BOOKMARKS, 1}, /* "book" */ + {KIND_FILTERED, 0xcc, 0x07, EV_KEY, KEY_EDIT, 1}, /* "hand" */ + {KIND_FILTERED, 0xe1, 0x1c, EV_KEY, KEY_COFFEE, 1}, /* "timer" */ + {KIND_FILTERED, 0xe5, 0x20, EV_KEY, KEY_FRONT, 1}, /* "max" */ + {KIND_FILTERED, 0xe2, 0x1d, EV_KEY, KEY_LEFT, 1}, /* left */ + {KIND_FILTERED, 0xe4, 0x1f, EV_KEY, KEY_RIGHT, 1}, /* right */ + {KIND_FILTERED, 0xe7, 0x22, EV_KEY, KEY_DOWN, 1}, /* down */ + {KIND_FILTERED, 0xdf, 0x1a, EV_KEY, KEY_UP, 1}, /* up */ + {KIND_FILTERED, 0xe3, 0x1e, EV_KEY, KEY_OK, 1}, /* "OK" */ + {KIND_FILTERED, 0xce, 0x09, EV_KEY, KEY_VOLUMEDOWN, 1}, /* VOL + */ + {KIND_FILTERED, 0xcd, 0x08, EV_KEY, KEY_VOLUMEUP, 1}, /* VOL - */ + {KIND_FILTERED, 0xcf, 0x0a, EV_KEY, KEY_MUTE, 1}, /* MUTE */ + {KIND_FILTERED, 0xd0, 0x0b, EV_KEY, KEY_CHANNELUP, 1}, /* CH + */ + {KIND_FILTERED, 0xd1, 0x0c, EV_KEY, KEY_CHANNELDOWN, 1},/* CH - */ + {KIND_FILTERED, 0xec, 0x27, EV_KEY, KEY_RECORD, 1}, /* ( o) red */ + {KIND_FILTERED, 0xea, 0x25, EV_KEY, KEY_PLAY, 1}, /* ( >) */ + {KIND_FILTERED, 0xe9, 0x24, EV_KEY, KEY_REWIND, 1}, /* (<<) */ + {KIND_FILTERED, 0xeb, 0x26, EV_KEY, KEY_FORWARD, 1}, /* (>>) */ + {KIND_FILTERED, 0xed, 0x28, EV_KEY, KEY_STOP, 1}, /* ([]) */ + {KIND_FILTERED, 0xee, 0x29, EV_KEY, KEY_PAUSE, 1}, /* ('') */ + {KIND_FILTERED, 0xf0, 0x2b, EV_KEY, KEY_PREVIOUS, 1}, /* (<-) */ + {KIND_FILTERED, 0xef, 0x2a, EV_KEY, KEY_NEXT, 1}, /* (>+) */ + {KIND_FILTERED, 0xf2, 0x2D, EV_KEY, KEY_INFO, 1}, /* PLAYING */ + {KIND_FILTERED, 0xf3, 0x2E, EV_KEY, KEY_HOME, 1}, /* TOP */ + {KIND_FILTERED, 0xf4, 0x2F, EV_KEY, KEY_END, 1}, /* END */ + {KIND_FILTERED, 0xf5, 0x30, EV_KEY, KEY_SELECT, 1}, /* SELECT */ + + {KIND_END, 0x00, 0x00, EV_MAX + 1, 0, 0} +}; + +/* Local function prototypes */ +static int ati_remote_open (struct input_dev *inputdev); +static void ati_remote_close (struct input_dev *inputdev); +static int ati_remote_sendpacket (struct ati_remote *ati_remote, u16 cmd, unsigned char *data); +static void ati_remote_irq_out (struct urb *urb); +static void ati_remote_irq_in (struct urb *urb); +static void ati_remote_input_report (struct urb *urb); +static int ati_remote_initialize (struct ati_remote *ati_remote); +static int ati_remote_probe (struct usb_interface *interface, const struct usb_device_id *id); +static void ati_remote_disconnect (struct usb_interface *interface); + +/* usb specific object to register with the usb subsystem */ +static struct usb_driver ati_remote_driver = { + .name = "ati_remote", + .probe = ati_remote_probe, + .disconnect = ati_remote_disconnect, + .id_table = ati_remote_table, +}; + +/* + * ati_remote_dump_input + */ +static void ati_remote_dump(struct device *dev, unsigned char *data, + unsigned int len) +{ + if ((len == 1) && (data[0] != (unsigned char)0xff) && (data[0] != 0x00)) + dev_warn(dev, "Weird byte 0x%02x\n", data[0]); + else if (len == 4) + dev_warn(dev, "Weird key %02x %02x %02x %02x\n", + data[0], data[1], data[2], data[3]); + else + dev_warn(dev, "Weird data, len=%d %02x %02x %02x %02x %02x %02x ...\n", + len, data[0], data[1], data[2], data[3], data[4], data[5]); +} + +/* + * ati_remote_open + */ +static int ati_remote_open(struct input_dev *inputdev) +{ + struct ati_remote *ati_remote = input_get_drvdata(inputdev); + + /* On first open, submit the read urb which was set up previously. */ + ati_remote->irq_urb->dev = ati_remote->udev; + if (usb_submit_urb(ati_remote->irq_urb, GFP_KERNEL)) { + dev_err(&ati_remote->interface->dev, + "%s: usb_submit_urb failed!\n", __func__); + return -EIO; + } + + return 0; +} + +/* + * ati_remote_close + */ +static void ati_remote_close(struct input_dev *inputdev) +{ + struct ati_remote *ati_remote = input_get_drvdata(inputdev); + + usb_kill_urb(ati_remote->irq_urb); +} + +/* + * ati_remote_irq_out + */ +static void ati_remote_irq_out(struct urb *urb) +{ + struct ati_remote *ati_remote = urb->context; + + if (urb->status) { + dev_dbg(&ati_remote->interface->dev, "%s: status %d\n", + __func__, urb->status); + return; + } + + ati_remote->send_flags |= SEND_FLAG_COMPLETE; + wmb(); + wake_up(&ati_remote->wait); +} + +/* + * ati_remote_sendpacket + * + * Used to send device initialization strings + */ +static int ati_remote_sendpacket(struct ati_remote *ati_remote, u16 cmd, unsigned char *data) +{ + int retval = 0; + + /* Set up out_urb */ + memcpy(ati_remote->out_urb->transfer_buffer + 1, data, LO(cmd)); + ((char *) ati_remote->out_urb->transfer_buffer)[0] = HI(cmd); + + ati_remote->out_urb->transfer_buffer_length = LO(cmd) + 1; + ati_remote->out_urb->dev = ati_remote->udev; + ati_remote->send_flags = SEND_FLAG_IN_PROGRESS; + + retval = usb_submit_urb(ati_remote->out_urb, GFP_ATOMIC); + if (retval) { + dev_dbg(&ati_remote->interface->dev, + "sendpacket: usb_submit_urb failed: %d\n", retval); + return retval; + } + + wait_event_timeout(ati_remote->wait, + ((ati_remote->out_urb->status != -EINPROGRESS) || + (ati_remote->send_flags & SEND_FLAG_COMPLETE)), + HZ); + usb_kill_urb(ati_remote->out_urb); + + return retval; +} + +/* + * ati_remote_event_lookup + */ +static int ati_remote_event_lookup(int rem, unsigned char d1, unsigned char d2) +{ + int i; + + for (i = 0; ati_remote_tbl[i].kind != KIND_END; i++) { + /* + * Decide if the table entry matches the remote input. + */ + if ((((ati_remote_tbl[i].data1 & 0x0f) == (d1 & 0x0f))) && + ((((ati_remote_tbl[i].data1 >> 4) - + (d1 >> 4) + rem) & 0x0f) == 0x0f) && + (ati_remote_tbl[i].data2 == d2)) + return i; + + } + return -1; +} + +/* + * ati_remote_compute_accel + * + * Implements acceleration curve for directional control pad + * If elapsed time since last event is > 1/4 second, user "stopped", + * so reset acceleration. Otherwise, user is probably holding the control + * pad down, so we increase acceleration, ramping up over two seconds to + * a maximum speed. + */ +static int ati_remote_compute_accel(struct ati_remote *ati_remote) +{ + static const char accel[] = { 1, 2, 4, 6, 9, 13, 20 }; + unsigned long now = jiffies; + int acc; + + if (time_after(now, ati_remote->old_jiffies + msecs_to_jiffies(250))) { + acc = 1; + ati_remote->acc_jiffies = now; + } + else if (time_before(now, ati_remote->acc_jiffies + msecs_to_jiffies(125))) + acc = accel[0]; + else if (time_before(now, ati_remote->acc_jiffies + msecs_to_jiffies(250))) + acc = accel[1]; + else if (time_before(now, ati_remote->acc_jiffies + msecs_to_jiffies(500))) + acc = accel[2]; + else if (time_before(now, ati_remote->acc_jiffies + msecs_to_jiffies(1000))) + acc = accel[3]; + else if (time_before(now, ati_remote->acc_jiffies + msecs_to_jiffies(1500))) + acc = accel[4]; + else if (time_before(now, ati_remote->acc_jiffies + msecs_to_jiffies(2000))) + acc = accel[5]; + else + acc = accel[6]; + + return acc; +} + +/* + * ati_remote_report_input + */ +static void ati_remote_input_report(struct urb *urb) +{ + struct ati_remote *ati_remote = urb->context; + unsigned char *data= ati_remote->inbuf; + struct input_dev *dev = ati_remote->idev; + int index, acc; + int remote_num; + + /* Deal with strange looking inputs */ + if ( (urb->actual_length != 4) || (data[0] != 0x14) || + ((data[3] & 0x0f) != 0x00) ) { + ati_remote_dump(&urb->dev->dev, data, urb->actual_length); + return; + } + + /* Mask unwanted remote channels. */ + /* note: remote_num is 0-based, channel 1 on remote == 0 here */ + remote_num = (data[3] >> 4) & 0x0f; + if (channel_mask & (1 << (remote_num + 1))) { + dbginfo(&ati_remote->interface->dev, + "Masked input from channel 0x%02x: data %02x,%02x, mask= 0x%02lx\n", + remote_num, data[1], data[2], channel_mask); + return; + } + + /* Look up event code index in translation table */ + index = ati_remote_event_lookup(remote_num, data[1], data[2]); + if (index < 0) { + dev_warn(&ati_remote->interface->dev, + "Unknown input from channel 0x%02x: data %02x,%02x\n", + remote_num, data[1], data[2]); + return; + } + dbginfo(&ati_remote->interface->dev, + "channel 0x%02x; data %02x,%02x; index %d; keycode %d\n", + remote_num, data[1], data[2], index, ati_remote_tbl[index].code); + + if (ati_remote_tbl[index].kind == KIND_LITERAL) { + input_event(dev, ati_remote_tbl[index].type, + ati_remote_tbl[index].code, + ati_remote_tbl[index].value); + input_sync(dev); + + ati_remote->old_jiffies = jiffies; + return; + } + + if (ati_remote_tbl[index].kind == KIND_FILTERED) { + unsigned long now = jiffies; + + /* Filter duplicate events which happen "too close" together. */ + if (ati_remote->old_data[0] == data[1] && + ati_remote->old_data[1] == data[2] && + time_before(now, ati_remote->old_jiffies + + msecs_to_jiffies(repeat_filter))) { + ati_remote->repeat_count++; + } else { + ati_remote->repeat_count = 0; + ati_remote->first_jiffies = now; + } + + ati_remote->old_data[0] = data[1]; + ati_remote->old_data[1] = data[2]; + ati_remote->old_jiffies = now; + + /* Ensure we skip at least the 4 first duplicate events (generated + * by a single keypress), and continue skipping until repeat_delay + * msecs have passed + */ + if (ati_remote->repeat_count > 0 && + (ati_remote->repeat_count < 5 || + time_before(now, ati_remote->first_jiffies + + msecs_to_jiffies(repeat_delay)))) + return; + + + input_event(dev, ati_remote_tbl[index].type, + ati_remote_tbl[index].code, 1); + input_sync(dev); + input_event(dev, ati_remote_tbl[index].type, + ati_remote_tbl[index].code, 0); + input_sync(dev); + + } else { + + /* + * Other event kinds are from the directional control pad, and have an + * acceleration factor applied to them. Without this acceleration, the + * control pad is mostly unusable. + */ + acc = ati_remote_compute_accel(ati_remote); + + switch (ati_remote_tbl[index].kind) { + case KIND_ACCEL: + input_event(dev, ati_remote_tbl[index].type, + ati_remote_tbl[index].code, + ati_remote_tbl[index].value * acc); + break; + case KIND_LU: + input_report_rel(dev, REL_X, -acc); + input_report_rel(dev, REL_Y, -acc); + break; + case KIND_RU: + input_report_rel(dev, REL_X, acc); + input_report_rel(dev, REL_Y, -acc); + break; + case KIND_LD: + input_report_rel(dev, REL_X, -acc); + input_report_rel(dev, REL_Y, acc); + break; + case KIND_RD: + input_report_rel(dev, REL_X, acc); + input_report_rel(dev, REL_Y, acc); + break; + default: + dev_dbg(&ati_remote->interface->dev, "ati_remote kind=%d\n", + ati_remote_tbl[index].kind); + } + input_sync(dev); + + ati_remote->old_jiffies = jiffies; + ati_remote->old_data[0] = data[1]; + ati_remote->old_data[1] = data[2]; + } +} + +/* + * ati_remote_irq_in + */ +static void ati_remote_irq_in(struct urb *urb) +{ + struct ati_remote *ati_remote = urb->context; + int retval; + + switch (urb->status) { + case 0: /* success */ + ati_remote_input_report(urb); + break; + case -ECONNRESET: /* unlink */ + case -ENOENT: + case -ESHUTDOWN: + dev_dbg(&ati_remote->interface->dev, "%s: urb error status, unlink? \n", + __func__); + return; + default: /* error */ + dev_dbg(&ati_remote->interface->dev, "%s: Nonzero urb status %d\n", + __func__, urb->status); + } + + retval = usb_submit_urb(urb, GFP_ATOMIC); + if (retval) + dev_err(&ati_remote->interface->dev, "%s: usb_submit_urb()=%d\n", + __func__, retval); +} + +/* + * ati_remote_alloc_buffers + */ +static int ati_remote_alloc_buffers(struct usb_device *udev, + struct ati_remote *ati_remote) +{ + ati_remote->inbuf = usb_buffer_alloc(udev, DATA_BUFSIZE, GFP_ATOMIC, + &ati_remote->inbuf_dma); + if (!ati_remote->inbuf) + return -1; + + ati_remote->outbuf = usb_buffer_alloc(udev, DATA_BUFSIZE, GFP_ATOMIC, + &ati_remote->outbuf_dma); + if (!ati_remote->outbuf) + return -1; + + ati_remote->irq_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!ati_remote->irq_urb) + return -1; + + ati_remote->out_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!ati_remote->out_urb) + return -1; + + return 0; +} + +/* + * ati_remote_free_buffers + */ +static void ati_remote_free_buffers(struct ati_remote *ati_remote) +{ + usb_free_urb(ati_remote->irq_urb); + usb_free_urb(ati_remote->out_urb); + + usb_buffer_free(ati_remote->udev, DATA_BUFSIZE, + ati_remote->inbuf, ati_remote->inbuf_dma); + + usb_buffer_free(ati_remote->udev, DATA_BUFSIZE, + ati_remote->outbuf, ati_remote->outbuf_dma); +} + +static void ati_remote_input_init(struct ati_remote *ati_remote) +{ + struct input_dev *idev = ati_remote->idev; + int i; + + idev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REL); + idev->keybit[BIT_WORD(BTN_MOUSE)] = BIT_MASK(BTN_LEFT) | + BIT_MASK(BTN_RIGHT) | BIT_MASK(BTN_SIDE) | BIT_MASK(BTN_EXTRA); + idev->relbit[0] = BIT_MASK(REL_X) | BIT_MASK(REL_Y); + for (i = 0; ati_remote_tbl[i].kind != KIND_END; i++) + if (ati_remote_tbl[i].type == EV_KEY) + set_bit(ati_remote_tbl[i].code, idev->keybit); + + input_set_drvdata(idev, ati_remote); + + idev->open = ati_remote_open; + idev->close = ati_remote_close; + + idev->name = ati_remote->name; + idev->phys = ati_remote->phys; + + usb_to_input_id(ati_remote->udev, &idev->id); + idev->dev.parent = &ati_remote->udev->dev; +} + +static int ati_remote_initialize(struct ati_remote *ati_remote) +{ + struct usb_device *udev = ati_remote->udev; + int pipe, maxp; + + init_waitqueue_head(&ati_remote->wait); + + /* Set up irq_urb */ + pipe = usb_rcvintpipe(udev, ati_remote->endpoint_in->bEndpointAddress); + maxp = usb_maxpacket(udev, pipe, usb_pipeout(pipe)); + maxp = (maxp > DATA_BUFSIZE) ? DATA_BUFSIZE : maxp; + + usb_fill_int_urb(ati_remote->irq_urb, udev, pipe, ati_remote->inbuf, + maxp, ati_remote_irq_in, ati_remote, + ati_remote->endpoint_in->bInterval); + ati_remote->irq_urb->transfer_dma = ati_remote->inbuf_dma; + ati_remote->irq_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + + /* Set up out_urb */ + pipe = usb_sndintpipe(udev, ati_remote->endpoint_out->bEndpointAddress); + maxp = usb_maxpacket(udev, pipe, usb_pipeout(pipe)); + maxp = (maxp > DATA_BUFSIZE) ? DATA_BUFSIZE : maxp; + + usb_fill_int_urb(ati_remote->out_urb, udev, pipe, ati_remote->outbuf, + maxp, ati_remote_irq_out, ati_remote, + ati_remote->endpoint_out->bInterval); + ati_remote->out_urb->transfer_dma = ati_remote->outbuf_dma; + ati_remote->out_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + + /* send initialization strings */ + if ((ati_remote_sendpacket(ati_remote, 0x8004, init1)) || + (ati_remote_sendpacket(ati_remote, 0x8007, init2))) { + dev_err(&ati_remote->interface->dev, + "Initializing ati_remote hardware failed.\n"); + return -EIO; + } + + return 0; +} + +/* + * ati_remote_probe + */ +static int ati_remote_probe(struct usb_interface *interface, const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(interface); + struct usb_host_interface *iface_host = interface->cur_altsetting; + struct usb_endpoint_descriptor *endpoint_in, *endpoint_out; + struct ati_remote *ati_remote; + struct input_dev *input_dev; + int err = -ENOMEM; + + if (iface_host->desc.bNumEndpoints != 2) { + err("%s: Unexpected desc.bNumEndpoints\n", __func__); + return -ENODEV; + } + + endpoint_in = &iface_host->endpoint[0].desc; + endpoint_out = &iface_host->endpoint[1].desc; + + if (!usb_endpoint_is_int_in(endpoint_in)) { + err("%s: Unexpected endpoint_in\n", __func__); + return -ENODEV; + } + if (le16_to_cpu(endpoint_in->wMaxPacketSize) == 0) { + err("%s: endpoint_in message size==0? \n", __func__); + return -ENODEV; + } + + ati_remote = kzalloc(sizeof (struct ati_remote), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!ati_remote || !input_dev) + goto fail1; + + /* Allocate URB buffers, URBs */ + if (ati_remote_alloc_buffers(udev, ati_remote)) + goto fail2; + + ati_remote->endpoint_in = endpoint_in; + ati_remote->endpoint_out = endpoint_out; + ati_remote->udev = udev; + ati_remote->idev = input_dev; + ati_remote->interface = interface; + + usb_make_path(udev, ati_remote->phys, sizeof(ati_remote->phys)); + strlcpy(ati_remote->phys, "/input0", sizeof(ati_remote->phys)); + + if (udev->manufacturer) + strlcpy(ati_remote->name, udev->manufacturer, sizeof(ati_remote->name)); + + if (udev->product) + snprintf(ati_remote->name, sizeof(ati_remote->name), + "%s %s", ati_remote->name, udev->product); + + if (!strlen(ati_remote->name)) + snprintf(ati_remote->name, sizeof(ati_remote->name), + DRIVER_DESC "(%04x,%04x)", + le16_to_cpu(ati_remote->udev->descriptor.idVendor), + le16_to_cpu(ati_remote->udev->descriptor.idProduct)); + + ati_remote_input_init(ati_remote); + + /* Device Hardware Initialization - fills in ati_remote->idev from udev. */ + err = ati_remote_initialize(ati_remote); + if (err) + goto fail3; + + /* Set up and register input device */ + err = input_register_device(ati_remote->idev); + if (err) + goto fail3; + + usb_set_intfdata(interface, ati_remote); + return 0; + + fail3: usb_kill_urb(ati_remote->irq_urb); + usb_kill_urb(ati_remote->out_urb); + fail2: ati_remote_free_buffers(ati_remote); + fail1: input_free_device(input_dev); + kfree(ati_remote); + return err; +} + +/* + * ati_remote_disconnect + */ +static void ati_remote_disconnect(struct usb_interface *interface) +{ + struct ati_remote *ati_remote; + + ati_remote = usb_get_intfdata(interface); + usb_set_intfdata(interface, NULL); + if (!ati_remote) { + dev_warn(&interface->dev, "%s - null device?\n", __func__); + return; + } + + usb_kill_urb(ati_remote->irq_urb); + usb_kill_urb(ati_remote->out_urb); + input_unregister_device(ati_remote->idev); + ati_remote_free_buffers(ati_remote); + kfree(ati_remote); +} + +/* + * ati_remote_init + */ +static int __init ati_remote_init(void) +{ + int result; + + result = usb_register(&ati_remote_driver); + if (result) + printk(KERN_ERR KBUILD_MODNAME + ": usb_register error #%d\n", result); + else + printk(KERN_INFO KBUILD_MODNAME ": " DRIVER_VERSION ":" + DRIVER_DESC "\n"); + + return result; +} + +/* + * ati_remote_exit + */ +static void __exit ati_remote_exit(void) +{ + usb_deregister(&ati_remote_driver); +} + +/* + * module specification + */ + +module_init(ati_remote_init); +module_exit(ati_remote_exit); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/misc/ati_remote2.c b/drivers/input/misc/ati_remote2.c new file mode 100644 index 0000000..3c9988d --- /dev/null +++ b/drivers/input/misc/ati_remote2.c @@ -0,0 +1,737 @@ +/* + * ati_remote2 - ATI/Philips USB RF remote driver + * + * Copyright (C) 2005-2008 Ville Syrjala <syrjala@sci.fi> + * Copyright (C) 2007-2008 Peter Stokes <linux@dadeos.co.uk> + * + * 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/usb/input.h> + +#define DRIVER_DESC "ATI/Philips USB RF remote driver" +#define DRIVER_VERSION "0.3" + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_VERSION(DRIVER_VERSION); +MODULE_AUTHOR("Ville Syrjala <syrjala@sci.fi>"); +MODULE_LICENSE("GPL"); + +/* + * ATI Remote Wonder II Channel Configuration + * + * The remote control can by assigned one of sixteen "channels" in order to facilitate + * the use of multiple remote controls within range of each other. + * A remote's "channel" may be altered by pressing and holding the "PC" button for + * approximately 3 seconds, after which the button will slowly flash the count of the + * currently configured "channel", using the numeric keypad enter a number between 1 and + * 16 and then press the "PC" button again, the button will slowly flash the count of the + * newly configured "channel". + */ + +static unsigned int channel_mask = 0xFFFF; +module_param(channel_mask, uint, 0644); +MODULE_PARM_DESC(channel_mask, "Bitmask of channels to accept <15:Channel16>...<1:Channel2><0:Channel1>"); + +static unsigned int mode_mask = 0x1F; +module_param(mode_mask, uint, 0644); +MODULE_PARM_DESC(mode_mask, "Bitmask of modes to accept <4:PC><3:AUX4><2:AUX3><1:AUX2><0:AUX1>"); + +static struct usb_device_id ati_remote2_id_table[] = { + { USB_DEVICE(0x0471, 0x0602) }, /* ATI Remote Wonder II */ + { } +}; +MODULE_DEVICE_TABLE(usb, ati_remote2_id_table); + +static DEFINE_MUTEX(ati_remote2_mutex); + +enum { + ATI_REMOTE2_OPENED = 0x1, + ATI_REMOTE2_SUSPENDED = 0x2, +}; + +enum { + ATI_REMOTE2_AUX1, + ATI_REMOTE2_AUX2, + ATI_REMOTE2_AUX3, + ATI_REMOTE2_AUX4, + ATI_REMOTE2_PC, + ATI_REMOTE2_MODES, +}; + +static const struct { + u8 hw_code; + u16 keycode; +} ati_remote2_key_table[] = { + { 0x00, KEY_0 }, + { 0x01, KEY_1 }, + { 0x02, KEY_2 }, + { 0x03, KEY_3 }, + { 0x04, KEY_4 }, + { 0x05, KEY_5 }, + { 0x06, KEY_6 }, + { 0x07, KEY_7 }, + { 0x08, KEY_8 }, + { 0x09, KEY_9 }, + { 0x0c, KEY_POWER }, + { 0x0d, KEY_MUTE }, + { 0x10, KEY_VOLUMEUP }, + { 0x11, KEY_VOLUMEDOWN }, + { 0x20, KEY_CHANNELUP }, + { 0x21, KEY_CHANNELDOWN }, + { 0x28, KEY_FORWARD }, + { 0x29, KEY_REWIND }, + { 0x2c, KEY_PLAY }, + { 0x30, KEY_PAUSE }, + { 0x31, KEY_STOP }, + { 0x37, KEY_RECORD }, + { 0x38, KEY_DVD }, + { 0x39, KEY_TV }, + { 0x3f, KEY_PROG1 }, /* AUX1-AUX4 and PC */ + { 0x54, KEY_MENU }, + { 0x58, KEY_UP }, + { 0x59, KEY_DOWN }, + { 0x5a, KEY_LEFT }, + { 0x5b, KEY_RIGHT }, + { 0x5c, KEY_OK }, + { 0x78, KEY_A }, + { 0x79, KEY_B }, + { 0x7a, KEY_C }, + { 0x7b, KEY_D }, + { 0x7c, KEY_E }, + { 0x7d, KEY_F }, + { 0x82, KEY_ENTER }, + { 0x8e, KEY_VENDOR }, + { 0x96, KEY_COFFEE }, + { 0xa9, BTN_LEFT }, + { 0xaa, BTN_RIGHT }, + { 0xbe, KEY_QUESTION }, + { 0xd0, KEY_EDIT }, + { 0xd5, KEY_FRONT }, + { 0xf9, KEY_INFO }, +}; + +struct ati_remote2 { + struct input_dev *idev; + struct usb_device *udev; + + struct usb_interface *intf[2]; + struct usb_endpoint_descriptor *ep[2]; + struct urb *urb[2]; + void *buf[2]; + dma_addr_t buf_dma[2]; + + unsigned long jiffies; + int mode; + + char name[64]; + char phys[64]; + + /* Each mode (AUX1-AUX4 and PC) can have an independent keymap. */ + u16 keycode[ATI_REMOTE2_MODES][ARRAY_SIZE(ati_remote2_key_table)]; + + unsigned int flags; +}; + +static int ati_remote2_probe(struct usb_interface *interface, const struct usb_device_id *id); +static void ati_remote2_disconnect(struct usb_interface *interface); +static int ati_remote2_suspend(struct usb_interface *interface, pm_message_t message); +static int ati_remote2_resume(struct usb_interface *interface); + +static struct usb_driver ati_remote2_driver = { + .name = "ati_remote2", + .probe = ati_remote2_probe, + .disconnect = ati_remote2_disconnect, + .id_table = ati_remote2_id_table, + .suspend = ati_remote2_suspend, + .resume = ati_remote2_resume, + .supports_autosuspend = 1, +}; + +static int ati_remote2_submit_urbs(struct ati_remote2 *ar2) +{ + int r; + + r = usb_submit_urb(ar2->urb[0], GFP_KERNEL); + if (r) { + dev_err(&ar2->intf[0]->dev, + "%s(): usb_submit_urb() = %d\n", __func__, r); + return r; + } + r = usb_submit_urb(ar2->urb[1], GFP_KERNEL); + if (r) { + usb_kill_urb(ar2->urb[0]); + dev_err(&ar2->intf[1]->dev, + "%s(): usb_submit_urb() = %d\n", __func__, r); + return r; + } + + return 0; +} + +static void ati_remote2_kill_urbs(struct ati_remote2 *ar2) +{ + usb_kill_urb(ar2->urb[1]); + usb_kill_urb(ar2->urb[0]); +} + +static int ati_remote2_open(struct input_dev *idev) +{ + struct ati_remote2 *ar2 = input_get_drvdata(idev); + int r; + + dev_dbg(&ar2->intf[0]->dev, "%s()\n", __func__); + + r = usb_autopm_get_interface(ar2->intf[0]); + if (r) { + dev_err(&ar2->intf[0]->dev, + "%s(): usb_autopm_get_interface() = %d\n", __func__, r); + goto fail1; + } + + mutex_lock(&ati_remote2_mutex); + + if (!(ar2->flags & ATI_REMOTE2_SUSPENDED)) { + r = ati_remote2_submit_urbs(ar2); + if (r) + goto fail2; + } + + ar2->flags |= ATI_REMOTE2_OPENED; + + mutex_unlock(&ati_remote2_mutex); + + usb_autopm_put_interface(ar2->intf[0]); + + return 0; + + fail2: + mutex_unlock(&ati_remote2_mutex); + usb_autopm_put_interface(ar2->intf[0]); + fail1: + return r; +} + +static void ati_remote2_close(struct input_dev *idev) +{ + struct ati_remote2 *ar2 = input_get_drvdata(idev); + + dev_dbg(&ar2->intf[0]->dev, "%s()\n", __func__); + + mutex_lock(&ati_remote2_mutex); + + if (!(ar2->flags & ATI_REMOTE2_SUSPENDED)) + ati_remote2_kill_urbs(ar2); + + ar2->flags &= ~ATI_REMOTE2_OPENED; + + mutex_unlock(&ati_remote2_mutex); +} + +static void ati_remote2_input_mouse(struct ati_remote2 *ar2) +{ + struct input_dev *idev = ar2->idev; + u8 *data = ar2->buf[0]; + int channel, mode; + + channel = data[0] >> 4; + + if (!((1 << channel) & channel_mask)) + return; + + mode = data[0] & 0x0F; + + if (mode > ATI_REMOTE2_PC) { + dev_err(&ar2->intf[0]->dev, + "Unknown mode byte (%02x %02x %02x %02x)\n", + data[3], data[2], data[1], data[0]); + return; + } + + if (!((1 << mode) & mode_mask)) + return; + + input_event(idev, EV_REL, REL_X, (s8) data[1]); + input_event(idev, EV_REL, REL_Y, (s8) data[2]); + input_sync(idev); +} + +static int ati_remote2_lookup(unsigned int hw_code) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(ati_remote2_key_table); i++) + if (ati_remote2_key_table[i].hw_code == hw_code) + return i; + + return -1; +} + +static void ati_remote2_input_key(struct ati_remote2 *ar2) +{ + struct input_dev *idev = ar2->idev; + u8 *data = ar2->buf[1]; + int channel, mode, hw_code, index; + + channel = data[0] >> 4; + + if (!((1 << channel) & channel_mask)) + return; + + mode = data[0] & 0x0F; + + if (mode > ATI_REMOTE2_PC) { + dev_err(&ar2->intf[1]->dev, + "Unknown mode byte (%02x %02x %02x %02x)\n", + data[3], data[2], data[1], data[0]); + return; + } + + hw_code = data[2]; + if (hw_code == 0x3f) { + /* + * For some incomprehensible reason the mouse pad generates + * events which look identical to the events from the last + * pressed mode key. Naturally we don't want to generate key + * events for the mouse pad so we filter out any subsequent + * events from the same mode key. + */ + if (ar2->mode == mode) + return; + + if (data[1] == 0) + ar2->mode = mode; + } + + if (!((1 << mode) & mode_mask)) + return; + + index = ati_remote2_lookup(hw_code); + if (index < 0) { + dev_err(&ar2->intf[1]->dev, + "Unknown code byte (%02x %02x %02x %02x)\n", + data[3], data[2], data[1], data[0]); + return; + } + + switch (data[1]) { + case 0: /* release */ + break; + case 1: /* press */ + ar2->jiffies = jiffies + msecs_to_jiffies(idev->rep[REP_DELAY]); + break; + case 2: /* repeat */ + + /* No repeat for mouse buttons. */ + if (ar2->keycode[mode][index] == BTN_LEFT || + ar2->keycode[mode][index] == BTN_RIGHT) + return; + + if (!time_after_eq(jiffies, ar2->jiffies)) + return; + + ar2->jiffies = jiffies + msecs_to_jiffies(idev->rep[REP_PERIOD]); + break; + default: + dev_err(&ar2->intf[1]->dev, + "Unknown state byte (%02x %02x %02x %02x)\n", + data[3], data[2], data[1], data[0]); + return; + } + + input_event(idev, EV_KEY, ar2->keycode[mode][index], data[1]); + input_sync(idev); +} + +static void ati_remote2_complete_mouse(struct urb *urb) +{ + struct ati_remote2 *ar2 = urb->context; + int r; + + switch (urb->status) { + case 0: + usb_mark_last_busy(ar2->udev); + ati_remote2_input_mouse(ar2); + break; + case -ENOENT: + case -EILSEQ: + case -ECONNRESET: + case -ESHUTDOWN: + dev_dbg(&ar2->intf[0]->dev, + "%s(): urb status = %d\n", __func__, urb->status); + return; + default: + usb_mark_last_busy(ar2->udev); + dev_err(&ar2->intf[0]->dev, + "%s(): urb status = %d\n", __func__, urb->status); + } + + r = usb_submit_urb(urb, GFP_ATOMIC); + if (r) + dev_err(&ar2->intf[0]->dev, + "%s(): usb_submit_urb() = %d\n", __func__, r); +} + +static void ati_remote2_complete_key(struct urb *urb) +{ + struct ati_remote2 *ar2 = urb->context; + int r; + + switch (urb->status) { + case 0: + usb_mark_last_busy(ar2->udev); + ati_remote2_input_key(ar2); + break; + case -ENOENT: + case -EILSEQ: + case -ECONNRESET: + case -ESHUTDOWN: + dev_dbg(&ar2->intf[1]->dev, + "%s(): urb status = %d\n", __func__, urb->status); + return; + default: + usb_mark_last_busy(ar2->udev); + dev_err(&ar2->intf[1]->dev, + "%s(): urb status = %d\n", __func__, urb->status); + } + + r = usb_submit_urb(urb, GFP_ATOMIC); + if (r) + dev_err(&ar2->intf[1]->dev, + "%s(): usb_submit_urb() = %d\n", __func__, r); +} + +static int ati_remote2_getkeycode(struct input_dev *idev, + int scancode, int *keycode) +{ + struct ati_remote2 *ar2 = input_get_drvdata(idev); + int index, mode; + + mode = scancode >> 8; + if (mode > ATI_REMOTE2_PC || !((1 << mode) & mode_mask)) + return -EINVAL; + + index = ati_remote2_lookup(scancode & 0xFF); + if (index < 0) + return -EINVAL; + + *keycode = ar2->keycode[mode][index]; + return 0; +} + +static int ati_remote2_setkeycode(struct input_dev *idev, int scancode, int keycode) +{ + struct ati_remote2 *ar2 = input_get_drvdata(idev); + int index, mode, old_keycode; + + mode = scancode >> 8; + if (mode > ATI_REMOTE2_PC || !((1 << mode) & mode_mask)) + return -EINVAL; + + index = ati_remote2_lookup(scancode & 0xFF); + if (index < 0) + return -EINVAL; + + if (keycode < KEY_RESERVED || keycode > KEY_MAX) + return -EINVAL; + + old_keycode = ar2->keycode[mode][index]; + ar2->keycode[mode][index] = keycode; + set_bit(keycode, idev->keybit); + + for (mode = 0; mode < ATI_REMOTE2_MODES; mode++) { + for (index = 0; index < ARRAY_SIZE(ati_remote2_key_table); index++) { + if (ar2->keycode[mode][index] == old_keycode) + return 0; + } + } + + clear_bit(old_keycode, idev->keybit); + + return 0; +} + +static int ati_remote2_input_init(struct ati_remote2 *ar2) +{ + struct input_dev *idev; + int index, mode, retval; + + idev = input_allocate_device(); + if (!idev) + return -ENOMEM; + + ar2->idev = idev; + input_set_drvdata(idev, ar2); + + idev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP) | BIT_MASK(EV_REL); + idev->keybit[BIT_WORD(BTN_MOUSE)] = BIT_MASK(BTN_LEFT) | + BIT_MASK(BTN_RIGHT); + idev->relbit[0] = BIT_MASK(REL_X) | BIT_MASK(REL_Y); + + for (mode = 0; mode < ATI_REMOTE2_MODES; mode++) { + for (index = 0; index < ARRAY_SIZE(ati_remote2_key_table); index++) { + ar2->keycode[mode][index] = ati_remote2_key_table[index].keycode; + set_bit(ar2->keycode[mode][index], idev->keybit); + } + } + + /* AUX1-AUX4 and PC generate the same scancode. */ + index = ati_remote2_lookup(0x3f); + ar2->keycode[ATI_REMOTE2_AUX1][index] = KEY_PROG1; + ar2->keycode[ATI_REMOTE2_AUX2][index] = KEY_PROG2; + ar2->keycode[ATI_REMOTE2_AUX3][index] = KEY_PROG3; + ar2->keycode[ATI_REMOTE2_AUX4][index] = KEY_PROG4; + ar2->keycode[ATI_REMOTE2_PC][index] = KEY_PC; + set_bit(KEY_PROG1, idev->keybit); + set_bit(KEY_PROG2, idev->keybit); + set_bit(KEY_PROG3, idev->keybit); + set_bit(KEY_PROG4, idev->keybit); + set_bit(KEY_PC, idev->keybit); + + idev->rep[REP_DELAY] = 250; + idev->rep[REP_PERIOD] = 33; + + idev->open = ati_remote2_open; + idev->close = ati_remote2_close; + + idev->getkeycode = ati_remote2_getkeycode; + idev->setkeycode = ati_remote2_setkeycode; + + idev->name = ar2->name; + idev->phys = ar2->phys; + + usb_to_input_id(ar2->udev, &idev->id); + idev->dev.parent = &ar2->udev->dev; + + retval = input_register_device(idev); + if (retval) + input_free_device(idev); + + return retval; +} + +static int ati_remote2_urb_init(struct ati_remote2 *ar2) +{ + struct usb_device *udev = ar2->udev; + int i, pipe, maxp; + + for (i = 0; i < 2; i++) { + ar2->buf[i] = usb_buffer_alloc(udev, 4, GFP_KERNEL, &ar2->buf_dma[i]); + if (!ar2->buf[i]) + return -ENOMEM; + + ar2->urb[i] = usb_alloc_urb(0, GFP_KERNEL); + if (!ar2->urb[i]) + return -ENOMEM; + + pipe = usb_rcvintpipe(udev, ar2->ep[i]->bEndpointAddress); + maxp = usb_maxpacket(udev, pipe, usb_pipeout(pipe)); + maxp = maxp > 4 ? 4 : maxp; + + usb_fill_int_urb(ar2->urb[i], udev, pipe, ar2->buf[i], maxp, + i ? ati_remote2_complete_key : ati_remote2_complete_mouse, + ar2, ar2->ep[i]->bInterval); + ar2->urb[i]->transfer_dma = ar2->buf_dma[i]; + ar2->urb[i]->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + } + + return 0; +} + +static void ati_remote2_urb_cleanup(struct ati_remote2 *ar2) +{ + int i; + + for (i = 0; i < 2; i++) { + usb_free_urb(ar2->urb[i]); + usb_buffer_free(ar2->udev, 4, ar2->buf[i], ar2->buf_dma[i]); + } +} + +static int ati_remote2_setup(struct ati_remote2 *ar2) +{ + int r, i, channel; + + /* + * Configure receiver to only accept input from remote "channel" + * channel == 0 -> Accept input from any remote channel + * channel == 1 -> Only accept input from remote channel 1 + * channel == 2 -> Only accept input from remote channel 2 + * ... + * channel == 16 -> Only accept input from remote channel 16 + */ + + channel = 0; + for (i = 0; i < 16; i++) { + if ((1 << i) & channel_mask) { + if (!(~(1 << i) & 0xFFFF & channel_mask)) + channel = i + 1; + break; + } + } + + r = usb_control_msg(ar2->udev, usb_sndctrlpipe(ar2->udev, 0), + 0x20, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_INTERFACE, + channel, 0x0, NULL, 0, USB_CTRL_SET_TIMEOUT); + if (r) { + dev_err(&ar2->udev->dev, "%s - failed to set channel due to error: %d\n", + __func__, r); + return r; + } + + return 0; +} + +static int ati_remote2_probe(struct usb_interface *interface, const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(interface); + struct usb_host_interface *alt = interface->cur_altsetting; + struct ati_remote2 *ar2; + int r; + + if (alt->desc.bInterfaceNumber) + return -ENODEV; + + ar2 = kzalloc(sizeof (struct ati_remote2), GFP_KERNEL); + if (!ar2) + return -ENOMEM; + + ar2->udev = udev; + + ar2->intf[0] = interface; + ar2->ep[0] = &alt->endpoint[0].desc; + + ar2->intf[1] = usb_ifnum_to_if(udev, 1); + r = usb_driver_claim_interface(&ati_remote2_driver, ar2->intf[1], ar2); + if (r) + goto fail1; + alt = ar2->intf[1]->cur_altsetting; + ar2->ep[1] = &alt->endpoint[0].desc; + + r = ati_remote2_urb_init(ar2); + if (r) + goto fail2; + + r = ati_remote2_setup(ar2); + if (r) + goto fail2; + + usb_make_path(udev, ar2->phys, sizeof(ar2->phys)); + strlcat(ar2->phys, "/input0", sizeof(ar2->phys)); + + strlcat(ar2->name, "ATI Remote Wonder II", sizeof(ar2->name)); + + r = ati_remote2_input_init(ar2); + if (r) + goto fail2; + + usb_set_intfdata(interface, ar2); + + interface->needs_remote_wakeup = 1; + + return 0; + + fail2: + ati_remote2_urb_cleanup(ar2); + + usb_driver_release_interface(&ati_remote2_driver, ar2->intf[1]); + fail1: + kfree(ar2); + + return r; +} + +static void ati_remote2_disconnect(struct usb_interface *interface) +{ + struct ati_remote2 *ar2; + struct usb_host_interface *alt = interface->cur_altsetting; + + if (alt->desc.bInterfaceNumber) + return; + + ar2 = usb_get_intfdata(interface); + usb_set_intfdata(interface, NULL); + + input_unregister_device(ar2->idev); + + ati_remote2_urb_cleanup(ar2); + + usb_driver_release_interface(&ati_remote2_driver, ar2->intf[1]); + + kfree(ar2); +} + +static int ati_remote2_suspend(struct usb_interface *interface, + pm_message_t message) +{ + struct ati_remote2 *ar2; + struct usb_host_interface *alt = interface->cur_altsetting; + + if (alt->desc.bInterfaceNumber) + return 0; + + ar2 = usb_get_intfdata(interface); + + dev_dbg(&ar2->intf[0]->dev, "%s()\n", __func__); + + mutex_lock(&ati_remote2_mutex); + + if (ar2->flags & ATI_REMOTE2_OPENED) + ati_remote2_kill_urbs(ar2); + + ar2->flags |= ATI_REMOTE2_SUSPENDED; + + mutex_unlock(&ati_remote2_mutex); + + return 0; +} + +static int ati_remote2_resume(struct usb_interface *interface) +{ + struct ati_remote2 *ar2; + struct usb_host_interface *alt = interface->cur_altsetting; + int r = 0; + + if (alt->desc.bInterfaceNumber) + return 0; + + ar2 = usb_get_intfdata(interface); + + dev_dbg(&ar2->intf[0]->dev, "%s()\n", __func__); + + mutex_lock(&ati_remote2_mutex); + + if (ar2->flags & ATI_REMOTE2_OPENED) + r = ati_remote2_submit_urbs(ar2); + + if (!r) + ar2->flags &= ~ATI_REMOTE2_SUSPENDED; + + mutex_unlock(&ati_remote2_mutex); + + return r; +} + +static int __init ati_remote2_init(void) +{ + int r; + + r = usb_register(&ati_remote2_driver); + if (r) + printk(KERN_ERR "ati_remote2: usb_register() = %d\n", r); + else + printk(KERN_INFO "ati_remote2: " DRIVER_DESC " " DRIVER_VERSION "\n"); + + return r; +} + +static void __exit ati_remote2_exit(void) +{ + usb_deregister(&ati_remote2_driver); +} + +module_init(ati_remote2_init); +module_exit(ati_remote2_exit); diff --git a/drivers/input/misc/atlas_btns.c b/drivers/input/misc/atlas_btns.c new file mode 100644 index 0000000..1b87191 --- /dev/null +++ b/drivers/input/misc/atlas_btns.c @@ -0,0 +1,182 @@ +/* + * atlas_btns.c - Atlas Wallmount Touchscreen ACPI Extras + * + * Copyright (C) 2006 Jaya Kumar + * Based on Toshiba ACPI by John Belmonte and ASUS ACPI + * This work was sponsored by CIS(M) Sdn Bhd. + * + * 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/init.h> +#include <linux/input.h> +#include <linux/types.h> +#include <asm/uaccess.h> +#include <acpi/acpi_drivers.h> + +#define ACPI_ATLAS_NAME "Atlas ACPI" +#define ACPI_ATLAS_CLASS "Atlas" + +static unsigned short atlas_keymap[16]; +static struct input_dev *input_dev; + +/* button handling code */ +static acpi_status acpi_atlas_button_setup(acpi_handle region_handle, + u32 function, void *handler_context, void **return_context) +{ + *return_context = + (function != ACPI_REGION_DEACTIVATE) ? handler_context : NULL; + + return AE_OK; +} + +static acpi_status acpi_atlas_button_handler(u32 function, + acpi_physical_address address, + u32 bit_width, acpi_integer *value, + void *handler_context, void *region_context) +{ + acpi_status status; + + if (function == ACPI_WRITE) { + int code = address & 0x0f; + int key_down = !(address & 0x10); + + input_event(input_dev, EV_MSC, MSC_SCAN, code); + input_report_key(input_dev, atlas_keymap[code], key_down); + input_sync(input_dev); + + status = 0; + } else { + printk(KERN_WARNING "atlas: shrugged on unexpected function" + ":function=%x,address=%lx,value=%x\n", + function, (unsigned long)address, (u32)*value); + status = -EINVAL; + } + + return status; +} + +static int atlas_acpi_button_add(struct acpi_device *device) +{ + acpi_status status; + int i; + int err; + + input_dev = input_allocate_device(); + if (!input_dev) { + printk(KERN_ERR "atlas: unable to allocate input device\n"); + return -ENOMEM; + } + + input_dev->name = "Atlas ACPI button driver"; + input_dev->phys = "ASIM0000/atlas/input0"; + input_dev->id.bustype = BUS_HOST; + input_dev->keycode = atlas_keymap; + input_dev->keycodesize = sizeof(unsigned short); + input_dev->keycodemax = ARRAY_SIZE(atlas_keymap); + + input_set_capability(input_dev, EV_MSC, MSC_SCAN); + __set_bit(EV_KEY, input_dev->evbit); + for (i = 0; i < ARRAY_SIZE(atlas_keymap); i++) { + if (i < 9) { + atlas_keymap[i] = KEY_F1 + i; + __set_bit(KEY_F1 + i, input_dev->keybit); + } else + atlas_keymap[i] = KEY_RESERVED; + } + + err = input_register_device(input_dev); + if (err) { + printk(KERN_ERR "atlas: couldn't register input device\n"); + input_free_device(input_dev); + return err; + } + + /* hookup button handler */ + status = acpi_install_address_space_handler(device->handle, + 0x81, &acpi_atlas_button_handler, + &acpi_atlas_button_setup, device); + if (ACPI_FAILURE(status)) { + printk(KERN_ERR "Atlas: Error installing addr spc handler\n"); + input_unregister_device(input_dev); + status = -EINVAL; + } + + return status; +} + +static int atlas_acpi_button_remove(struct acpi_device *device, int type) +{ + acpi_status status; + + status = acpi_remove_address_space_handler(device->handle, + 0x81, &acpi_atlas_button_handler); + if (ACPI_FAILURE(status)) { + printk(KERN_ERR "Atlas: Error removing addr spc handler\n"); + status = -EINVAL; + } + + input_unregister_device(input_dev); + + return status; +} + +static const struct acpi_device_id atlas_device_ids[] = { + {"ASIM0000", 0}, + {"", 0}, +}; +MODULE_DEVICE_TABLE(acpi, atlas_device_ids); + +static struct acpi_driver atlas_acpi_driver = { + .name = ACPI_ATLAS_NAME, + .class = ACPI_ATLAS_CLASS, + .ids = atlas_device_ids, + .ops = { + .add = atlas_acpi_button_add, + .remove = atlas_acpi_button_remove, + }, +}; + +static int __init atlas_acpi_init(void) +{ + int result; + + if (acpi_disabled) + return -ENODEV; + + result = acpi_bus_register_driver(&atlas_acpi_driver); + if (result < 0) { + printk(KERN_ERR "Atlas ACPI: Unable to register driver\n"); + return -ENODEV; + } + + return 0; +} + +static void __exit atlas_acpi_exit(void) +{ + acpi_bus_unregister_driver(&atlas_acpi_driver); +} + +module_init(atlas_acpi_init); +module_exit(atlas_acpi_exit); + +MODULE_AUTHOR("Jaya Kumar"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Atlas button driver"); + diff --git a/drivers/input/misc/cm109.c b/drivers/input/misc/cm109.c new file mode 100644 index 0000000..86457fe --- /dev/null +++ b/drivers/input/misc/cm109.c @@ -0,0 +1,917 @@ +/* + * Driver for the VoIP USB phones with CM109 chipsets. + * + * Copyright (C) 2007 - 2008 Alfred E. Heggestad <aeh@db.org> + * + * 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, version 2. + */ + +/* + * Tested devices: + * - Komunikate KIP1000 + * - Genius G-talk + * - Allied-Telesis Corega USBPH01 + * - ... + * + * This driver is based on the yealink.c driver + * + * Thanks to: + * - Authors of yealink.c + * - Thomas Reitmayr + * - Oliver Neukum for good review comments and code + * - Shaun Jackman <sjackman@gmail.com> for Genius G-talk keymap + * - Dmitry Torokhov for valuable input and review + * + * Todo: + * - Read/write EEPROM + */ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/rwsem.h> +#include <linux/usb/input.h> + +#define DRIVER_VERSION "20080805" +#define DRIVER_AUTHOR "Alfred E. Heggestad" +#define DRIVER_DESC "CM109 phone driver" + +static char *phone = "kip1000"; +module_param(phone, charp, S_IRUSR); +MODULE_PARM_DESC(phone, "Phone name {kip1000, gtalk, usbph01, atcom}"); + +enum { + /* HID Registers */ + HID_IR0 = 0x00, /* Record/Playback-mute button, Volume up/down */ + HID_IR1 = 0x01, /* GPI, generic registers or EEPROM_DATA0 */ + HID_IR2 = 0x02, /* Generic registers or EEPROM_DATA1 */ + HID_IR3 = 0x03, /* Generic registers or EEPROM_CTRL */ + HID_OR0 = 0x00, /* Mapping control, buzzer, SPDIF (offset 0x04) */ + HID_OR1 = 0x01, /* GPO - General Purpose Output */ + HID_OR2 = 0x02, /* Set GPIO to input/output mode */ + HID_OR3 = 0x03, /* SPDIF status channel or EEPROM_CTRL */ + + /* HID_IR0 */ + RECORD_MUTE = 1 << 3, + PLAYBACK_MUTE = 1 << 2, + VOLUME_DOWN = 1 << 1, + VOLUME_UP = 1 << 0, + + /* HID_OR0 */ + /* bits 7-6 + 0: HID_OR1-2 are used for GPO; HID_OR0, 3 are used for buzzer + and SPDIF + 1: HID_OR0-3 are used as generic HID registers + 2: Values written to HID_OR0-3 are also mapped to MCU_CTRL, + EEPROM_DATA0-1, EEPROM_CTRL (see Note) + 3: Reserved + */ + HID_OR_GPO_BUZ_SPDIF = 0 << 6, + HID_OR_GENERIC_HID_REG = 1 << 6, + HID_OR_MAP_MCU_EEPROM = 2 << 6, + + BUZZER_ON = 1 << 5, + + /* up to 256 normal keys, up to 16 special keys */ + KEYMAP_SIZE = 256 + 16, +}; + +/* CM109 protocol packet */ +struct cm109_ctl_packet { + u8 byte[4]; +} __attribute__ ((packed)); + +enum { USB_PKT_LEN = sizeof(struct cm109_ctl_packet) }; + +/* CM109 device structure */ +struct cm109_dev { + struct input_dev *idev; /* input device */ + struct usb_device *udev; /* usb device */ + struct usb_interface *intf; + + /* irq input channel */ + struct cm109_ctl_packet *irq_data; + dma_addr_t irq_dma; + struct urb *urb_irq; + + /* control output channel */ + struct cm109_ctl_packet *ctl_data; + dma_addr_t ctl_dma; + struct usb_ctrlrequest *ctl_req; + dma_addr_t ctl_req_dma; + struct urb *urb_ctl; + /* + * The 3 bitfields below are protected by ctl_submit_lock. + * They have to be separate since they are accessed from IRQ + * context. + */ + unsigned irq_urb_pending:1; /* irq_urb is in flight */ + unsigned ctl_urb_pending:1; /* ctl_urb is in flight */ + unsigned buzzer_pending:1; /* need to issue buzz command */ + spinlock_t ctl_submit_lock; + + unsigned char buzzer_state; /* on/off */ + + /* flags */ + unsigned open:1; + unsigned resetting:1; + unsigned shutdown:1; + + /* This mutex protects writes to the above flags */ + struct mutex pm_mutex; + + unsigned short keymap[KEYMAP_SIZE]; + + char phys[64]; /* physical device path */ + int key_code; /* last reported key */ + int keybit; /* 0=new scan 1,2,4,8=scan columns */ + u8 gpi; /* Cached value of GPI (high nibble) */ +}; + +/****************************************************************************** + * CM109 key interface + *****************************************************************************/ + +static unsigned short special_keymap(int code) +{ + if (code > 0xff) { + switch (code - 0xff) { + case RECORD_MUTE: return KEY_MUTE; + case PLAYBACK_MUTE: return KEY_MUTE; + case VOLUME_DOWN: return KEY_VOLUMEDOWN; + case VOLUME_UP: return KEY_VOLUMEUP; + } + } + return KEY_RESERVED; +} + +/* Map device buttons to internal key events. + * + * The "up" and "down" keys, are symbolised by arrows on the button. + * The "pickup" and "hangup" keys are symbolised by a green and red phone + * on the button. + + Komunikate KIP1000 Keyboard Matrix + + -> -- 1 -- 2 -- 3 --> GPI pin 4 (0x10) + | | | | + <- -- 4 -- 5 -- 6 --> GPI pin 5 (0x20) + | | | | + END - 7 -- 8 -- 9 --> GPI pin 6 (0x40) + | | | | + OK -- * -- 0 -- # --> GPI pin 7 (0x80) + | | | | + + /|\ /|\ /|\ /|\ + | | | | +GPO +pin: 3 2 1 0 + 0x8 0x4 0x2 0x1 + + */ +static unsigned short keymap_kip1000(int scancode) +{ + switch (scancode) { /* phone key: */ + case 0x82: return KEY_NUMERIC_0; /* 0 */ + case 0x14: return KEY_NUMERIC_1; /* 1 */ + case 0x12: return KEY_NUMERIC_2; /* 2 */ + case 0x11: return KEY_NUMERIC_3; /* 3 */ + case 0x24: return KEY_NUMERIC_4; /* 4 */ + case 0x22: return KEY_NUMERIC_5; /* 5 */ + case 0x21: return KEY_NUMERIC_6; /* 6 */ + case 0x44: return KEY_NUMERIC_7; /* 7 */ + case 0x42: return KEY_NUMERIC_8; /* 8 */ + case 0x41: return KEY_NUMERIC_9; /* 9 */ + case 0x81: return KEY_NUMERIC_POUND; /* # */ + case 0x84: return KEY_NUMERIC_STAR; /* * */ + case 0x88: return KEY_ENTER; /* pickup */ + case 0x48: return KEY_ESC; /* hangup */ + case 0x28: return KEY_LEFT; /* IN */ + case 0x18: return KEY_RIGHT; /* OUT */ + default: return special_keymap(scancode); + } +} + +/* + Contributed by Shaun Jackman <sjackman@gmail.com> + + Genius G-Talk keyboard matrix + 0 1 2 3 + 4: 0 4 8 Talk + 5: 1 5 9 End + 6: 2 6 # Up + 7: 3 7 * Down +*/ +static unsigned short keymap_gtalk(int scancode) +{ + switch (scancode) { + case 0x11: return KEY_NUMERIC_0; + case 0x21: return KEY_NUMERIC_1; + case 0x41: return KEY_NUMERIC_2; + case 0x81: return KEY_NUMERIC_3; + case 0x12: return KEY_NUMERIC_4; + case 0x22: return KEY_NUMERIC_5; + case 0x42: return KEY_NUMERIC_6; + case 0x82: return KEY_NUMERIC_7; + case 0x14: return KEY_NUMERIC_8; + case 0x24: return KEY_NUMERIC_9; + case 0x44: return KEY_NUMERIC_POUND; /* # */ + case 0x84: return KEY_NUMERIC_STAR; /* * */ + case 0x18: return KEY_ENTER; /* Talk (green handset) */ + case 0x28: return KEY_ESC; /* End (red handset) */ + case 0x48: return KEY_UP; /* Menu up (rocker switch) */ + case 0x88: return KEY_DOWN; /* Menu down (rocker switch) */ + default: return special_keymap(scancode); + } +} + +/* + * Keymap for Allied-Telesis Corega USBPH01 + * http://www.alliedtelesis-corega.com/2/1344/1437/1360/chprd.html + * + * Contributed by july@nat.bg + */ +static unsigned short keymap_usbph01(int scancode) +{ + switch (scancode) { + case 0x11: return KEY_NUMERIC_0; /* 0 */ + case 0x21: return KEY_NUMERIC_1; /* 1 */ + case 0x41: return KEY_NUMERIC_2; /* 2 */ + case 0x81: return KEY_NUMERIC_3; /* 3 */ + case 0x12: return KEY_NUMERIC_4; /* 4 */ + case 0x22: return KEY_NUMERIC_5; /* 5 */ + case 0x42: return KEY_NUMERIC_6; /* 6 */ + case 0x82: return KEY_NUMERIC_7; /* 7 */ + case 0x14: return KEY_NUMERIC_8; /* 8 */ + case 0x24: return KEY_NUMERIC_9; /* 9 */ + case 0x44: return KEY_NUMERIC_POUND; /* # */ + case 0x84: return KEY_NUMERIC_STAR; /* * */ + case 0x18: return KEY_ENTER; /* pickup */ + case 0x28: return KEY_ESC; /* hangup */ + case 0x48: return KEY_LEFT; /* IN */ + case 0x88: return KEY_RIGHT; /* OUT */ + default: return special_keymap(scancode); + } +} + +/* + * Keymap for ATCom AU-100 + * http://www.atcom.cn/En_products_AU100.html + * http://www.packetizer.com/products/au100/ + * http://www.voip-info.org/wiki/view/AU-100 + * + * Contributed by daniel@gimpelevich.san-francisco.ca.us + */ +static unsigned short keymap_atcom(int scancode) +{ + switch (scancode) { /* phone key: */ + case 0x82: return KEY_NUMERIC_0; /* 0 */ + case 0x11: return KEY_NUMERIC_1; /* 1 */ + case 0x12: return KEY_NUMERIC_2; /* 2 */ + case 0x14: return KEY_NUMERIC_3; /* 3 */ + case 0x21: return KEY_NUMERIC_4; /* 4 */ + case 0x22: return KEY_NUMERIC_5; /* 5 */ + case 0x24: return KEY_NUMERIC_6; /* 6 */ + case 0x41: return KEY_NUMERIC_7; /* 7 */ + case 0x42: return KEY_NUMERIC_8; /* 8 */ + case 0x44: return KEY_NUMERIC_9; /* 9 */ + case 0x84: return KEY_NUMERIC_POUND; /* # */ + case 0x81: return KEY_NUMERIC_STAR; /* * */ + case 0x18: return KEY_ENTER; /* pickup */ + case 0x28: return KEY_ESC; /* hangup */ + case 0x48: return KEY_LEFT; /* left arrow */ + case 0x88: return KEY_RIGHT; /* right arrow */ + default: return special_keymap(scancode); + } +} + +static unsigned short (*keymap)(int) = keymap_kip1000; + +/* + * Completes a request by converting the data into events for the + * input subsystem. + */ +static void report_key(struct cm109_dev *dev, int key) +{ + struct input_dev *idev = dev->idev; + + if (dev->key_code >= 0) { + /* old key up */ + input_report_key(idev, dev->key_code, 0); + } + + dev->key_code = key; + if (key >= 0) { + /* new valid key */ + input_report_key(idev, key, 1); + } + + input_sync(idev); +} + +/****************************************************************************** + * CM109 usb communication interface + *****************************************************************************/ + +static void cm109_submit_buzz_toggle(struct cm109_dev *dev) +{ + int error; + + if (dev->buzzer_state) + dev->ctl_data->byte[HID_OR0] |= BUZZER_ON; + else + dev->ctl_data->byte[HID_OR0] &= ~BUZZER_ON; + + error = usb_submit_urb(dev->urb_ctl, GFP_ATOMIC); + if (error) + err("%s: usb_submit_urb (urb_ctl) failed %d", __func__, error); +} + +/* + * IRQ handler + */ +static void cm109_urb_irq_callback(struct urb *urb) +{ + struct cm109_dev *dev = urb->context; + const int status = urb->status; + int error; + + dev_dbg(&urb->dev->dev, "### URB IRQ: [0x%02x 0x%02x 0x%02x 0x%02x] keybit=0x%02x\n", + dev->irq_data->byte[0], + dev->irq_data->byte[1], + dev->irq_data->byte[2], + dev->irq_data->byte[3], + dev->keybit); + + if (status) { + if (status == -ESHUTDOWN) + return; + err("%s: urb status %d", __func__, status); + } + + /* Special keys */ + if (dev->irq_data->byte[HID_IR0] & 0x0f) { + const int code = (dev->irq_data->byte[HID_IR0] & 0x0f); + report_key(dev, dev->keymap[0xff + code]); + } + + /* Scan key column */ + if (dev->keybit == 0xf) { + + /* Any changes ? */ + if ((dev->gpi & 0xf0) == (dev->irq_data->byte[HID_IR1] & 0xf0)) + goto out; + + dev->gpi = dev->irq_data->byte[HID_IR1] & 0xf0; + dev->keybit = 0x1; + } else { + report_key(dev, dev->keymap[dev->irq_data->byte[HID_IR1]]); + + dev->keybit <<= 1; + if (dev->keybit > 0x8) + dev->keybit = 0xf; + } + + out: + + spin_lock(&dev->ctl_submit_lock); + + dev->irq_urb_pending = 0; + + if (likely(!dev->shutdown)) { + + if (dev->buzzer_state) + dev->ctl_data->byte[HID_OR0] |= BUZZER_ON; + else + dev->ctl_data->byte[HID_OR0] &= ~BUZZER_ON; + + dev->ctl_data->byte[HID_OR1] = dev->keybit; + dev->ctl_data->byte[HID_OR2] = dev->keybit; + + dev->buzzer_pending = 0; + dev->ctl_urb_pending = 1; + + error = usb_submit_urb(dev->urb_ctl, GFP_ATOMIC); + if (error) + err("%s: usb_submit_urb (urb_ctl) failed %d", + __func__, error); + } + + spin_unlock(&dev->ctl_submit_lock); +} + +static void cm109_urb_ctl_callback(struct urb *urb) +{ + struct cm109_dev *dev = urb->context; + const int status = urb->status; + int error; + + dev_dbg(&urb->dev->dev, "### URB CTL: [0x%02x 0x%02x 0x%02x 0x%02x]\n", + dev->ctl_data->byte[0], + dev->ctl_data->byte[1], + dev->ctl_data->byte[2], + dev->ctl_data->byte[3]); + + if (status) + err("%s: urb status %d", __func__, status); + + spin_lock(&dev->ctl_submit_lock); + + dev->ctl_urb_pending = 0; + + if (likely(!dev->shutdown)) { + + if (dev->buzzer_pending) { + dev->buzzer_pending = 0; + dev->ctl_urb_pending = 1; + cm109_submit_buzz_toggle(dev); + } else if (likely(!dev->irq_urb_pending)) { + /* ask for key data */ + dev->irq_urb_pending = 1; + error = usb_submit_urb(dev->urb_irq, GFP_ATOMIC); + if (error) + err("%s: usb_submit_urb (urb_irq) failed %d", + __func__, error); + } + } + + spin_unlock(&dev->ctl_submit_lock); +} + +static void cm109_toggle_buzzer_async(struct cm109_dev *dev) +{ + unsigned long flags; + + spin_lock_irqsave(&dev->ctl_submit_lock, flags); + + if (dev->ctl_urb_pending) { + /* URB completion will resubmit */ + dev->buzzer_pending = 1; + } else { + dev->ctl_urb_pending = 1; + cm109_submit_buzz_toggle(dev); + } + + spin_unlock_irqrestore(&dev->ctl_submit_lock, flags); +} + +static void cm109_toggle_buzzer_sync(struct cm109_dev *dev, int on) +{ + int error; + + if (on) + dev->ctl_data->byte[HID_OR0] |= BUZZER_ON; + else + dev->ctl_data->byte[HID_OR0] &= ~BUZZER_ON; + + error = usb_control_msg(dev->udev, + usb_sndctrlpipe(dev->udev, 0), + dev->ctl_req->bRequest, + dev->ctl_req->bRequestType, + le16_to_cpu(dev->ctl_req->wValue), + le16_to_cpu(dev->ctl_req->wIndex), + dev->ctl_data, + USB_PKT_LEN, USB_CTRL_SET_TIMEOUT); + if (error && error != EINTR) + err("%s: usb_control_msg() failed %d", __func__, error); +} + +static void cm109_stop_traffic(struct cm109_dev *dev) +{ + dev->shutdown = 1; + /* + * Make sure other CPUs see this + */ + smp_wmb(); + + usb_kill_urb(dev->urb_ctl); + usb_kill_urb(dev->urb_irq); + + cm109_toggle_buzzer_sync(dev, 0); + + dev->shutdown = 0; + smp_wmb(); +} + +static void cm109_restore_state(struct cm109_dev *dev) +{ + if (dev->open) { + /* + * Restore buzzer state. + * This will also kick regular URB submission + */ + cm109_toggle_buzzer_async(dev); + } +} + +/****************************************************************************** + * input event interface + *****************************************************************************/ + +static int cm109_input_open(struct input_dev *idev) +{ + struct cm109_dev *dev = input_get_drvdata(idev); + int error; + + error = usb_autopm_get_interface(dev->intf); + if (error < 0) { + err("%s - cannot autoresume, result %d", + __func__, error); + return error; + } + + mutex_lock(&dev->pm_mutex); + + dev->buzzer_state = 0; + dev->key_code = -1; /* no keys pressed */ + dev->keybit = 0xf; + + /* issue INIT */ + dev->ctl_data->byte[HID_OR0] = HID_OR_GPO_BUZ_SPDIF; + dev->ctl_data->byte[HID_OR1] = dev->keybit; + dev->ctl_data->byte[HID_OR2] = dev->keybit; + dev->ctl_data->byte[HID_OR3] = 0x00; + + error = usb_submit_urb(dev->urb_ctl, GFP_KERNEL); + if (error) + err("%s: usb_submit_urb (urb_ctl) failed %d", __func__, error); + else + dev->open = 1; + + mutex_unlock(&dev->pm_mutex); + + if (error) + usb_autopm_put_interface(dev->intf); + + return error; +} + +static void cm109_input_close(struct input_dev *idev) +{ + struct cm109_dev *dev = input_get_drvdata(idev); + + mutex_lock(&dev->pm_mutex); + + /* + * Once we are here event delivery is stopped so we + * don't need to worry about someone starting buzzer + * again + */ + cm109_stop_traffic(dev); + dev->open = 0; + + mutex_unlock(&dev->pm_mutex); + + usb_autopm_put_interface(dev->intf); +} + +static int cm109_input_ev(struct input_dev *idev, unsigned int type, + unsigned int code, int value) +{ + struct cm109_dev *dev = input_get_drvdata(idev); + + dev_dbg(&dev->udev->dev, + "input_ev: type=%u code=%u value=%d\n", type, code, value); + + if (type != EV_SND) + return -EINVAL; + + switch (code) { + case SND_TONE: + case SND_BELL: + dev->buzzer_state = !!value; + if (!dev->resetting) + cm109_toggle_buzzer_async(dev); + return 0; + + default: + return -EINVAL; + } +} + + +/****************************************************************************** + * Linux interface and usb initialisation + *****************************************************************************/ + +struct driver_info { + char *name; +}; + +static const struct driver_info info_cm109 = { + .name = "CM109 USB driver", +}; + +enum { + VENDOR_ID = 0x0d8c, /* C-Media Electronics */ + PRODUCT_ID_CM109 = 0x000e, /* CM109 defines range 0x0008 - 0x000f */ +}; + +/* table of devices that work with this driver */ +static const struct usb_device_id cm109_usb_table[] = { + { + .match_flags = USB_DEVICE_ID_MATCH_DEVICE | + USB_DEVICE_ID_MATCH_INT_INFO, + .idVendor = VENDOR_ID, + .idProduct = PRODUCT_ID_CM109, + .bInterfaceClass = USB_CLASS_HID, + .bInterfaceSubClass = 0, + .bInterfaceProtocol = 0, + .driver_info = (kernel_ulong_t) &info_cm109 + }, + /* you can add more devices here with product ID 0x0008 - 0x000f */ + { } +}; + +static void cm109_usb_cleanup(struct cm109_dev *dev) +{ + if (dev->ctl_req) + usb_buffer_free(dev->udev, sizeof(*(dev->ctl_req)), + dev->ctl_req, dev->ctl_req_dma); + if (dev->ctl_data) + usb_buffer_free(dev->udev, USB_PKT_LEN, + dev->ctl_data, dev->ctl_dma); + if (dev->irq_data) + usb_buffer_free(dev->udev, USB_PKT_LEN, + dev->irq_data, dev->irq_dma); + + usb_free_urb(dev->urb_irq); /* parameter validation in core/urb */ + usb_free_urb(dev->urb_ctl); /* parameter validation in core/urb */ + kfree(dev); +} + +static void cm109_usb_disconnect(struct usb_interface *interface) +{ + struct cm109_dev *dev = usb_get_intfdata(interface); + + usb_set_intfdata(interface, NULL); + input_unregister_device(dev->idev); + cm109_usb_cleanup(dev); +} + +static int cm109_usb_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(intf); + struct driver_info *nfo = (struct driver_info *)id->driver_info; + struct usb_host_interface *interface; + struct usb_endpoint_descriptor *endpoint; + struct cm109_dev *dev; + struct input_dev *input_dev = NULL; + int ret, pipe, i; + int error = -ENOMEM; + + interface = intf->cur_altsetting; + endpoint = &interface->endpoint[0].desc; + + if (!usb_endpoint_is_int_in(endpoint)) + return -ENODEV; + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + spin_lock_init(&dev->ctl_submit_lock); + mutex_init(&dev->pm_mutex); + + dev->udev = udev; + dev->intf = intf; + + dev->idev = input_dev = input_allocate_device(); + if (!input_dev) + goto err_out; + + /* allocate usb buffers */ + dev->irq_data = usb_buffer_alloc(udev, USB_PKT_LEN, + GFP_KERNEL, &dev->irq_dma); + if (!dev->irq_data) + goto err_out; + + dev->ctl_data = usb_buffer_alloc(udev, USB_PKT_LEN, + GFP_KERNEL, &dev->ctl_dma); + if (!dev->ctl_data) + goto err_out; + + dev->ctl_req = usb_buffer_alloc(udev, sizeof(*(dev->ctl_req)), + GFP_KERNEL, &dev->ctl_req_dma); + if (!dev->ctl_req) + goto err_out; + + /* allocate urb structures */ + dev->urb_irq = usb_alloc_urb(0, GFP_KERNEL); + if (!dev->urb_irq) + goto err_out; + + dev->urb_ctl = usb_alloc_urb(0, GFP_KERNEL); + if (!dev->urb_ctl) + goto err_out; + + /* get a handle to the interrupt data pipe */ + pipe = usb_rcvintpipe(udev, endpoint->bEndpointAddress); + ret = usb_maxpacket(udev, pipe, usb_pipeout(pipe)); + if (ret != USB_PKT_LEN) + err("invalid payload size %d, expected %d", ret, USB_PKT_LEN); + + /* initialise irq urb */ + usb_fill_int_urb(dev->urb_irq, udev, pipe, dev->irq_data, + USB_PKT_LEN, + cm109_urb_irq_callback, dev, endpoint->bInterval); + dev->urb_irq->transfer_dma = dev->irq_dma; + dev->urb_irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + dev->urb_irq->dev = udev; + + /* initialise ctl urb */ + dev->ctl_req->bRequestType = USB_TYPE_CLASS | USB_RECIP_INTERFACE | + USB_DIR_OUT; + dev->ctl_req->bRequest = USB_REQ_SET_CONFIGURATION; + dev->ctl_req->wValue = cpu_to_le16(0x200); + dev->ctl_req->wIndex = cpu_to_le16(interface->desc.bInterfaceNumber); + dev->ctl_req->wLength = cpu_to_le16(USB_PKT_LEN); + + usb_fill_control_urb(dev->urb_ctl, udev, usb_sndctrlpipe(udev, 0), + (void *)dev->ctl_req, dev->ctl_data, USB_PKT_LEN, + cm109_urb_ctl_callback, dev); + dev->urb_ctl->setup_dma = dev->ctl_req_dma; + dev->urb_ctl->transfer_dma = dev->ctl_dma; + dev->urb_ctl->transfer_flags |= URB_NO_SETUP_DMA_MAP | + URB_NO_TRANSFER_DMA_MAP; + dev->urb_ctl->dev = udev; + + /* find out the physical bus location */ + usb_make_path(udev, dev->phys, sizeof(dev->phys)); + strlcat(dev->phys, "/input0", sizeof(dev->phys)); + + /* register settings for the input device */ + input_dev->name = nfo->name; + input_dev->phys = dev->phys; + usb_to_input_id(udev, &input_dev->id); + input_dev->dev.parent = &intf->dev; + + input_set_drvdata(input_dev, dev); + input_dev->open = cm109_input_open; + input_dev->close = cm109_input_close; + input_dev->event = cm109_input_ev; + + input_dev->keycode = dev->keymap; + input_dev->keycodesize = sizeof(unsigned char); + input_dev->keycodemax = ARRAY_SIZE(dev->keymap); + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_SND); + input_dev->sndbit[0] = BIT_MASK(SND_BELL) | BIT_MASK(SND_TONE); + + /* register available key events */ + for (i = 0; i < KEYMAP_SIZE; i++) { + unsigned short k = keymap(i); + dev->keymap[i] = k; + __set_bit(k, input_dev->keybit); + } + __clear_bit(KEY_RESERVED, input_dev->keybit); + + error = input_register_device(dev->idev); + if (error) + goto err_out; + + usb_set_intfdata(intf, dev); + + return 0; + + err_out: + input_free_device(input_dev); + cm109_usb_cleanup(dev); + return error; +} + +static int cm109_usb_suspend(struct usb_interface *intf, pm_message_t message) +{ + struct cm109_dev *dev = usb_get_intfdata(intf); + + dev_info(&intf->dev, "cm109: usb_suspend (event=%d)\n", message.event); + + mutex_lock(&dev->pm_mutex); + cm109_stop_traffic(dev); + mutex_unlock(&dev->pm_mutex); + + return 0; +} + +static int cm109_usb_resume(struct usb_interface *intf) +{ + struct cm109_dev *dev = usb_get_intfdata(intf); + + dev_info(&intf->dev, "cm109: usb_resume\n"); + + mutex_lock(&dev->pm_mutex); + cm109_restore_state(dev); + mutex_unlock(&dev->pm_mutex); + + return 0; +} + +static int cm109_usb_pre_reset(struct usb_interface *intf) +{ + struct cm109_dev *dev = usb_get_intfdata(intf); + + mutex_lock(&dev->pm_mutex); + + /* + * Make sure input events don't try to toggle buzzer + * while we are resetting + */ + dev->resetting = 1; + smp_wmb(); + + cm109_stop_traffic(dev); + + return 0; +} + +static int cm109_usb_post_reset(struct usb_interface *intf) +{ + struct cm109_dev *dev = usb_get_intfdata(intf); + + dev->resetting = 0; + smp_wmb(); + + cm109_restore_state(dev); + + mutex_unlock(&dev->pm_mutex); + + return 0; +} + +static struct usb_driver cm109_driver = { + .name = "cm109", + .probe = cm109_usb_probe, + .disconnect = cm109_usb_disconnect, + .suspend = cm109_usb_suspend, + .resume = cm109_usb_resume, + .reset_resume = cm109_usb_resume, + .pre_reset = cm109_usb_pre_reset, + .post_reset = cm109_usb_post_reset, + .id_table = cm109_usb_table, + .supports_autosuspend = 1, +}; + +static int __init cm109_select_keymap(void) +{ + /* Load the phone keymap */ + if (!strcasecmp(phone, "kip1000")) { + keymap = keymap_kip1000; + printk(KERN_INFO KBUILD_MODNAME ": " + "Keymap for Komunikate KIP1000 phone loaded\n"); + } else if (!strcasecmp(phone, "gtalk")) { + keymap = keymap_gtalk; + printk(KERN_INFO KBUILD_MODNAME ": " + "Keymap for Genius G-talk phone loaded\n"); + } else if (!strcasecmp(phone, "usbph01")) { + keymap = keymap_usbph01; + printk(KERN_INFO KBUILD_MODNAME ": " + "Keymap for Allied-Telesis Corega USBPH01 phone loaded\n"); + } else if (!strcasecmp(phone, "atcom")) { + keymap = keymap_atcom; + printk(KERN_INFO KBUILD_MODNAME ": " + "Keymap for ATCom AU-100 phone loaded\n"); + } else { + printk(KERN_ERR KBUILD_MODNAME ": " + "Unsupported phone: %s\n", phone); + return -EINVAL; + } + + return 0; +} + +static int __init cm109_init(void) +{ + int err; + + err = cm109_select_keymap(); + if (err) + return err; + + err = usb_register(&cm109_driver); + if (err) + return err; + + printk(KERN_INFO KBUILD_MODNAME ": " + DRIVER_DESC ": " DRIVER_VERSION " (C) " DRIVER_AUTHOR "\n"); + + return 0; +} + +static void __exit cm109_exit(void) +{ + usb_deregister(&cm109_driver); +} + +module_init(cm109_init); +module_exit(cm109_exit); + +MODULE_DEVICE_TABLE(usb, cm109_usb_table); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/misc/cobalt_btns.c b/drivers/input/misc/cobalt_btns.c new file mode 100644 index 0000000..2adf9cb --- /dev/null +++ b/drivers/input/misc/cobalt_btns.c @@ -0,0 +1,177 @@ +/* + * Cobalt button interface driver. + * + * Copyright (C) 2007-2008 Yoichi Yuasa <yoichi_yuasa@tripeaks.co.jp> + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +#include <linux/init.h> +#include <linux/input-polldev.h> +#include <linux/ioport.h> +#include <linux/module.h> +#include <linux/platform_device.h> + +#define BUTTONS_POLL_INTERVAL 30 /* msec */ +#define BUTTONS_COUNT_THRESHOLD 3 +#define BUTTONS_STATUS_MASK 0xfe000000 + +static const unsigned short cobalt_map[] = { + KEY_RESERVED, + KEY_RESTART, + KEY_LEFT, + KEY_UP, + KEY_DOWN, + KEY_RIGHT, + KEY_ENTER, + KEY_SELECT +}; + +struct buttons_dev { + struct input_polled_dev *poll_dev; + unsigned short keymap[ARRAY_SIZE(cobalt_map)]; + int count[ARRAY_SIZE(cobalt_map)]; + void __iomem *reg; +}; + +static void handle_buttons(struct input_polled_dev *dev) +{ + struct buttons_dev *bdev = dev->private; + struct input_dev *input = dev->input; + uint32_t status; + int i; + + status = ~readl(bdev->reg) >> 24; + + for (i = 0; i < ARRAY_SIZE(bdev->keymap); i++) { + if (status & (1U << i)) { + if (++bdev->count[i] == BUTTONS_COUNT_THRESHOLD) { + input_event(input, EV_MSC, MSC_SCAN, i); + input_report_key(input, bdev->keymap[i], 1); + input_sync(input); + } + } else { + if (bdev->count[i] >= BUTTONS_COUNT_THRESHOLD) { + input_event(input, EV_MSC, MSC_SCAN, i); + input_report_key(input, bdev->keymap[i], 0); + input_sync(input); + } + bdev->count[i] = 0; + } + } +} + +static int __devinit cobalt_buttons_probe(struct platform_device *pdev) +{ + struct buttons_dev *bdev; + struct input_polled_dev *poll_dev; + struct input_dev *input; + struct resource *res; + int error, i; + + bdev = kzalloc(sizeof(struct buttons_dev), GFP_KERNEL); + poll_dev = input_allocate_polled_device(); + if (!bdev || !poll_dev) { + error = -ENOMEM; + goto err_free_mem; + } + + memcpy(bdev->keymap, cobalt_map, sizeof(bdev->keymap)); + + poll_dev->private = bdev; + poll_dev->poll = handle_buttons; + poll_dev->poll_interval = BUTTONS_POLL_INTERVAL; + + input = poll_dev->input; + input->name = "Cobalt buttons"; + input->phys = "cobalt/input0"; + input->id.bustype = BUS_HOST; + input->dev.parent = &pdev->dev; + + input->keycode = bdev->keymap; + input->keycodemax = ARRAY_SIZE(bdev->keymap); + input->keycodesize = sizeof(unsigned short); + + input_set_capability(input, EV_MSC, MSC_SCAN); + __set_bit(EV_KEY, input->evbit); + for (i = 0; i < ARRAY_SIZE(cobalt_map); i++) + __set_bit(bdev->keymap[i], input->keybit); + __clear_bit(KEY_RESERVED, input->keybit); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + error = -EBUSY; + goto err_free_mem; + } + + bdev->poll_dev = poll_dev; + bdev->reg = ioremap(res->start, res->end - res->start + 1); + dev_set_drvdata(&pdev->dev, bdev); + + error = input_register_polled_device(poll_dev); + if (error) + goto err_iounmap; + + return 0; + + err_iounmap: + iounmap(bdev->reg); + err_free_mem: + input_free_polled_device(poll_dev); + kfree(bdev); + dev_set_drvdata(&pdev->dev, NULL); + return error; +} + +static int __devexit cobalt_buttons_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct buttons_dev *bdev = dev_get_drvdata(dev); + + input_unregister_polled_device(bdev->poll_dev); + input_free_polled_device(bdev->poll_dev); + iounmap(bdev->reg); + kfree(bdev); + dev_set_drvdata(dev, NULL); + + return 0; +} + +MODULE_AUTHOR("Yoichi Yuasa <yoichi_yuasa@tripeaks.co.jp>"); +MODULE_DESCRIPTION("Cobalt button interface driver"); +MODULE_LICENSE("GPL"); +/* work with hotplug and coldplug */ +MODULE_ALIAS("platform:Cobalt buttons"); + +static struct platform_driver cobalt_buttons_driver = { + .probe = cobalt_buttons_probe, + .remove = __devexit_p(cobalt_buttons_remove), + .driver = { + .name = "Cobalt buttons", + .owner = THIS_MODULE, + }, +}; + +static int __init cobalt_buttons_init(void) +{ + return platform_driver_register(&cobalt_buttons_driver); +} + +static void __exit cobalt_buttons_exit(void) +{ + platform_driver_unregister(&cobalt_buttons_driver); +} + +module_init(cobalt_buttons_init); +module_exit(cobalt_buttons_exit); diff --git a/drivers/input/misc/hp_sdc_rtc.c b/drivers/input/misc/hp_sdc_rtc.c new file mode 100644 index 0000000..216a559 --- /dev/null +++ b/drivers/input/misc/hp_sdc_rtc.c @@ -0,0 +1,714 @@ +/* + * 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/smp_lock.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> +#include <linux/semaphore.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 ssize_t hp_sdc_rtc_read(struct file *file, char __user *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_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 ssize_t hp_sdc_rtc_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) { + ssize_t retval; + + if (count < sizeof(unsigned long)) + return -EINVAL; + + retval = put_user(68, (unsigned long __user *)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) +{ + cycle_kernel_lock(); + 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, (int)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, (int)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, (int)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, (int)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, (int)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 const struct file_operations hp_sdc_rtc_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .read = hp_sdc_rtc_read, + .poll = hp_sdc_rtc_poll, + .ioctl = hp_sdc_rtc_ioctl, + .open = hp_sdc_rtc_open, + .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; + +#ifdef __mc68000__ + if (!MACH_IS_HP300) + return -ENODEV; +#endif + + init_MUTEX(&i8042tregs); + + if ((ret = hp_sdc_request_timer_irq(&hp_sdc_rtc_isr))) + return ret; + if (misc_register(&hp_sdc_rtc_dev) != 0) + printk(KERN_INFO "Could not register misc. dev for i8042 rtc\n"); + + create_proc_read_entry ("driver/rtc", 0, NULL, + 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/ixp4xx-beeper.c b/drivers/input/misc/ixp4xx-beeper.c new file mode 100644 index 0000000..9946d73 --- /dev/null +++ b/drivers/input/misc/ixp4xx-beeper.c @@ -0,0 +1,186 @@ +/* + * Generic IXP4xx beeper driver + * + * Copyright (C) 2005 Tower Technologies + * + * based on nslu2-io.c + * Copyright (C) 2004 Karen Spearel + * + * Author: Alessandro Zummo <a.zummo@towertech.it> + * Maintainers: http://www.nslu2-linux.org/ + * + * 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/input.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <mach/hardware.h> + +MODULE_AUTHOR("Alessandro Zummo <a.zummo@towertech.it>"); +MODULE_DESCRIPTION("ixp4xx beeper driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:ixp4xx-beeper"); + +static DEFINE_SPINLOCK(beep_lock); + +static void ixp4xx_spkr_control(unsigned int pin, unsigned int count) +{ + unsigned long flags; + + spin_lock_irqsave(&beep_lock, flags); + + if (count) { + gpio_line_config(pin, IXP4XX_GPIO_OUT); + gpio_line_set(pin, IXP4XX_GPIO_LOW); + + *IXP4XX_OSRT2 = (count & ~IXP4XX_OST_RELOAD_MASK) | IXP4XX_OST_ENABLE; + } else { + gpio_line_config(pin, IXP4XX_GPIO_IN); + gpio_line_set(pin, IXP4XX_GPIO_HIGH); + + *IXP4XX_OSRT2 = 0; + } + + spin_unlock_irqrestore(&beep_lock, flags); +} + +static int ixp4xx_spkr_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) +{ + unsigned int pin = (unsigned int) input_get_drvdata(dev); + 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) +#ifndef FREQ + count = (ixp4xx_get_board_tick_rate() / (value * 4)) - 1; +#else + count = (FREQ / (value * 4)) - 1; +#endif + + ixp4xx_spkr_control(pin, count); + + return 0; +} + +static irqreturn_t ixp4xx_spkr_interrupt(int irq, void *dev_id) +{ + /* clear interrupt */ + *IXP4XX_OSST = IXP4XX_OSST_TIMER_2_PEND; + + /* flip the beeper output */ + *IXP4XX_GPIO_GPOUTR ^= (1 << (unsigned int) dev_id); + + return IRQ_HANDLED; +} + +static int __devinit ixp4xx_spkr_probe(struct platform_device *dev) +{ + struct input_dev *input_dev; + int err; + + input_dev = input_allocate_device(); + if (!input_dev) + return -ENOMEM; + + input_set_drvdata(input_dev, (void *) dev->id); + + input_dev->name = "ixp4xx beeper", + input_dev->phys = "ixp4xx/gpio"; + input_dev->id.bustype = BUS_HOST; + input_dev->id.vendor = 0x001f; + input_dev->id.product = 0x0001; + input_dev->id.version = 0x0100; + input_dev->dev.parent = &dev->dev; + + input_dev->evbit[0] = BIT_MASK(EV_SND); + input_dev->sndbit[0] = BIT_MASK(SND_BELL) | BIT_MASK(SND_TONE); + input_dev->event = ixp4xx_spkr_event; + + err = request_irq(IRQ_IXP4XX_TIMER2, &ixp4xx_spkr_interrupt, + IRQF_DISABLED | IRQF_TIMER, "ixp4xx-beeper", (void *) dev->id); + if (err) + goto err_free_device; + + err = input_register_device(input_dev); + if (err) + goto err_free_irq; + + platform_set_drvdata(dev, input_dev); + + return 0; + + err_free_irq: + free_irq(IRQ_IXP4XX_TIMER2, dev); + err_free_device: + input_free_device(input_dev); + + return err; +} + +static int __devexit ixp4xx_spkr_remove(struct platform_device *dev) +{ + struct input_dev *input_dev = platform_get_drvdata(dev); + unsigned int pin = (unsigned int) input_get_drvdata(input_dev); + + input_unregister_device(input_dev); + platform_set_drvdata(dev, NULL); + + /* turn the speaker off */ + disable_irq(IRQ_IXP4XX_TIMER2); + ixp4xx_spkr_control(pin, 0); + + free_irq(IRQ_IXP4XX_TIMER2, dev); + + return 0; +} + +static void ixp4xx_spkr_shutdown(struct platform_device *dev) +{ + struct input_dev *input_dev = platform_get_drvdata(dev); + unsigned int pin = (unsigned int) input_get_drvdata(input_dev); + + /* turn off the speaker */ + disable_irq(IRQ_IXP4XX_TIMER2); + ixp4xx_spkr_control(pin, 0); +} + +static struct platform_driver ixp4xx_spkr_platform_driver = { + .driver = { + .name = "ixp4xx-beeper", + .owner = THIS_MODULE, + }, + .probe = ixp4xx_spkr_probe, + .remove = __devexit_p(ixp4xx_spkr_remove), + .shutdown = ixp4xx_spkr_shutdown, +}; + +static int __init ixp4xx_spkr_init(void) +{ + return platform_driver_register(&ixp4xx_spkr_platform_driver); +} + +static void __exit ixp4xx_spkr_exit(void) +{ + platform_driver_unregister(&ixp4xx_spkr_platform_driver); +} + +module_init(ixp4xx_spkr_init); +module_exit(ixp4xx_spkr_exit); diff --git a/drivers/input/misc/keyspan_remote.c b/drivers/input/misc/keyspan_remote.c new file mode 100644 index 0000000..86afdd1 --- /dev/null +++ b/drivers/input/misc/keyspan_remote.c @@ -0,0 +1,607 @@ +/* + * keyspan_remote: USB driver for the Keyspan DMR + * + * Copyright (C) 2005 Zymeta Corporation - Michael Downey (downey@zymeta.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, version 2. + * + * This driver has been put together with the support of Innosys, Inc. + * and Keyspan, Inc the manufacturers of the Keyspan USB DMR product. + */ + +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/usb/input.h> + +#define DRIVER_VERSION "v0.1" +#define DRIVER_AUTHOR "Michael Downey <downey@zymeta.com>" +#define DRIVER_DESC "Driver for the USB Keyspan remote control." +#define DRIVER_LICENSE "GPL" + +/* Parameters that can be passed to the driver. */ +static int debug; +module_param(debug, int, 0444); +MODULE_PARM_DESC(debug, "Enable extra debug messages and information"); + +/* Vendor and product ids */ +#define USB_KEYSPAN_VENDOR_ID 0x06CD +#define USB_KEYSPAN_PRODUCT_UIA11 0x0202 + +/* Defines for converting the data from the remote. */ +#define ZERO 0x18 +#define ZERO_MASK 0x1F /* 5 bits for a 0 */ +#define ONE 0x3C +#define ONE_MASK 0x3F /* 6 bits for a 1 */ +#define SYNC 0x3F80 +#define SYNC_MASK 0x3FFF /* 14 bits for a SYNC sequence */ +#define STOP 0x00 +#define STOP_MASK 0x1F /* 5 bits for the STOP sequence */ +#define GAP 0xFF + +#define RECV_SIZE 8 /* The UIA-11 type have a 8 byte limit. */ + +/* + * Table that maps the 31 possible keycodes to input keys. + * Currently there are 15 and 17 button models so RESERVED codes + * are blank areas in the mapping. + */ +static const unsigned short keyspan_key_table[] = { + KEY_RESERVED, /* 0 is just a place holder. */ + KEY_RESERVED, + KEY_STOP, + KEY_PLAYCD, + KEY_RESERVED, + KEY_PREVIOUSSONG, + KEY_REWIND, + KEY_FORWARD, + KEY_NEXTSONG, + KEY_RESERVED, + KEY_RESERVED, + KEY_RESERVED, + KEY_PAUSE, + KEY_VOLUMEUP, + KEY_RESERVED, + KEY_RESERVED, + KEY_RESERVED, + KEY_VOLUMEDOWN, + KEY_RESERVED, + KEY_UP, + KEY_RESERVED, + KEY_MUTE, + KEY_LEFT, + KEY_ENTER, + KEY_RIGHT, + KEY_RESERVED, + KEY_RESERVED, + KEY_DOWN, + KEY_RESERVED, + KEY_KPASTERISK, + KEY_RESERVED, + KEY_MENU +}; + +/* table of devices that work with this driver */ +static struct usb_device_id keyspan_table[] = { + { USB_DEVICE(USB_KEYSPAN_VENDOR_ID, USB_KEYSPAN_PRODUCT_UIA11) }, + { } /* Terminating entry */ +}; + +/* Structure to store all the real stuff that a remote sends to us. */ +struct keyspan_message { + u16 system; + u8 button; + u8 toggle; +}; + +/* Structure used for all the bit testing magic needed to be done. */ +struct bit_tester { + u32 tester; + int len; + int pos; + int bits_left; + u8 buffer[32]; +}; + +/* Structure to hold all of our driver specific stuff */ +struct usb_keyspan { + char name[128]; + char phys[64]; + unsigned short keymap[ARRAY_SIZE(keyspan_key_table)]; + struct usb_device *udev; + struct input_dev *input; + struct usb_interface *interface; + struct usb_endpoint_descriptor *in_endpoint; + struct urb* irq_urb; + int open; + dma_addr_t in_dma; + unsigned char *in_buffer; + + /* variables used to parse messages from remote. */ + struct bit_tester data; + int stage; + int toggle; +}; + +static struct usb_driver keyspan_driver; + +/* + * Debug routine that prints out what we've received from the remote. + */ +static void keyspan_print(struct usb_keyspan* dev) /*unsigned char* data)*/ +{ + char codes[4 * RECV_SIZE]; + int i; + + for (i = 0; i < RECV_SIZE; i++) + snprintf(codes + i * 3, 4, "%02x ", dev->in_buffer[i]); + + dev_info(&dev->udev->dev, "%s\n", codes); +} + +/* + * Routine that manages the bit_tester structure. It makes sure that there are + * at least bits_needed bits loaded into the tester. + */ +static int keyspan_load_tester(struct usb_keyspan* dev, int bits_needed) +{ + if (dev->data.bits_left >= bits_needed) + return 0; + + /* + * Somehow we've missed the last message. The message will be repeated + * though so it's not too big a deal + */ + if (dev->data.pos >= dev->data.len) { + dev_dbg(&dev->udev->dev, + "%s - Error ran out of data. pos: %d, len: %d\n", + __func__, dev->data.pos, dev->data.len); + return -1; + } + + /* Load as much as we can into the tester. */ + while ((dev->data.bits_left + 7 < (sizeof(dev->data.tester) * 8)) && + (dev->data.pos < dev->data.len)) { + dev->data.tester += (dev->data.buffer[dev->data.pos++] << dev->data.bits_left); + dev->data.bits_left += 8; + } + + return 0; +} + +static void keyspan_report_button(struct usb_keyspan *remote, int button, int press) +{ + struct input_dev *input = remote->input; + + input_event(input, EV_MSC, MSC_SCAN, button); + input_report_key(input, remote->keymap[button], press); + input_sync(input); +} + +/* + * Routine that handles all the logic needed to parse out the message from the remote. + */ +static void keyspan_check_data(struct usb_keyspan *remote) +{ + int i; + int found = 0; + struct keyspan_message message; + + switch(remote->stage) { + case 0: + /* + * In stage 0 we want to find the start of a message. The remote sends a 0xFF as filler. + * So the first byte that isn't a FF should be the start of a new message. + */ + for (i = 0; i < RECV_SIZE && remote->in_buffer[i] == GAP; ++i); + + if (i < RECV_SIZE) { + memcpy(remote->data.buffer, remote->in_buffer, RECV_SIZE); + remote->data.len = RECV_SIZE; + remote->data.pos = 0; + remote->data.tester = 0; + remote->data.bits_left = 0; + remote->stage = 1; + } + break; + + case 1: + /* + * Stage 1 we should have 16 bytes and should be able to detect a + * SYNC. The SYNC is 14 bits, 7 0's and then 7 1's. + */ + memcpy(remote->data.buffer + remote->data.len, remote->in_buffer, RECV_SIZE); + remote->data.len += RECV_SIZE; + + found = 0; + while ((remote->data.bits_left >= 14 || remote->data.pos < remote->data.len) && !found) { + for (i = 0; i < 8; ++i) { + if (keyspan_load_tester(remote, 14) != 0) { + remote->stage = 0; + return; + } + + if ((remote->data.tester & SYNC_MASK) == SYNC) { + remote->data.tester = remote->data.tester >> 14; + remote->data.bits_left -= 14; + found = 1; + break; + } else { + remote->data.tester = remote->data.tester >> 1; + --remote->data.bits_left; + } + } + } + + if (!found) { + remote->stage = 0; + remote->data.len = 0; + } else { + remote->stage = 2; + } + break; + + case 2: + /* + * Stage 2 we should have 24 bytes which will be enough for a full + * message. We need to parse out the system code, button code, + * toggle code, and stop. + */ + memcpy(remote->data.buffer + remote->data.len, remote->in_buffer, RECV_SIZE); + remote->data.len += RECV_SIZE; + + message.system = 0; + for (i = 0; i < 9; i++) { + keyspan_load_tester(remote, 6); + + if ((remote->data.tester & ZERO_MASK) == ZERO) { + message.system = message.system << 1; + remote->data.tester = remote->data.tester >> 5; + remote->data.bits_left -= 5; + } else if ((remote->data.tester & ONE_MASK) == ONE) { + message.system = (message.system << 1) + 1; + remote->data.tester = remote->data.tester >> 6; + remote->data.bits_left -= 6; + } else { + err("%s - Unknown sequence found in system data.\n", __func__); + remote->stage = 0; + return; + } + } + + message.button = 0; + for (i = 0; i < 5; i++) { + keyspan_load_tester(remote, 6); + + if ((remote->data.tester & ZERO_MASK) == ZERO) { + message.button = message.button << 1; + remote->data.tester = remote->data.tester >> 5; + remote->data.bits_left -= 5; + } else if ((remote->data.tester & ONE_MASK) == ONE) { + message.button = (message.button << 1) + 1; + remote->data.tester = remote->data.tester >> 6; + remote->data.bits_left -= 6; + } else { + err("%s - Unknown sequence found in button data.\n", __func__); + remote->stage = 0; + return; + } + } + + keyspan_load_tester(remote, 6); + if ((remote->data.tester & ZERO_MASK) == ZERO) { + message.toggle = 0; + remote->data.tester = remote->data.tester >> 5; + remote->data.bits_left -= 5; + } else if ((remote->data.tester & ONE_MASK) == ONE) { + message.toggle = 1; + remote->data.tester = remote->data.tester >> 6; + remote->data.bits_left -= 6; + } else { + err("%s - Error in message, invalid toggle.\n", __func__); + remote->stage = 0; + return; + } + + keyspan_load_tester(remote, 5); + if ((remote->data.tester & STOP_MASK) == STOP) { + remote->data.tester = remote->data.tester >> 5; + remote->data.bits_left -= 5; + } else { + err("Bad message recieved, no stop bit found.\n"); + } + + dev_dbg(&remote->udev->dev, + "%s found valid message: system: %d, button: %d, toggle: %d\n", + __func__, message.system, message.button, message.toggle); + + if (message.toggle != remote->toggle) { + keyspan_report_button(remote, message.button, 1); + keyspan_report_button(remote, message.button, 0); + remote->toggle = message.toggle; + } + + remote->stage = 0; + break; + } +} + +/* + * Routine for sending all the initialization messages to the remote. + */ +static int keyspan_setup(struct usb_device* dev) +{ + int retval = 0; + + retval = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + 0x11, 0x40, 0x5601, 0x0, NULL, 0, 0); + if (retval) { + dev_dbg(&dev->dev, "%s - failed to set bit rate due to error: %d\n", + __func__, retval); + return(retval); + } + + retval = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + 0x44, 0x40, 0x0, 0x0, NULL, 0, 0); + if (retval) { + dev_dbg(&dev->dev, "%s - failed to set resume sensitivity due to error: %d\n", + __func__, retval); + return(retval); + } + + retval = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + 0x22, 0x40, 0x0, 0x0, NULL, 0, 0); + if (retval) { + dev_dbg(&dev->dev, "%s - failed to turn receive on due to error: %d\n", + __func__, retval); + return(retval); + } + + dev_dbg(&dev->dev, "%s - Setup complete.\n", __func__); + return(retval); +} + +/* + * Routine used to handle a new message that has come in. + */ +static void keyspan_irq_recv(struct urb *urb) +{ + struct usb_keyspan *dev = urb->context; + int retval; + + /* Check our status in case we need to bail out early. */ + switch (urb->status) { + case 0: + break; + + /* Device went away so don't keep trying to read from it. */ + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + return; + + default: + goto resubmit; + break; + } + + if (debug) + keyspan_print(dev); + + keyspan_check_data(dev); + +resubmit: + retval = usb_submit_urb(urb, GFP_ATOMIC); + if (retval) + err ("%s - usb_submit_urb failed with result: %d", __func__, retval); +} + +static int keyspan_open(struct input_dev *dev) +{ + struct usb_keyspan *remote = input_get_drvdata(dev); + + remote->irq_urb->dev = remote->udev; + if (usb_submit_urb(remote->irq_urb, GFP_KERNEL)) + return -EIO; + + return 0; +} + +static void keyspan_close(struct input_dev *dev) +{ + struct usb_keyspan *remote = input_get_drvdata(dev); + + usb_kill_urb(remote->irq_urb); +} + +static struct usb_endpoint_descriptor *keyspan_get_in_endpoint(struct usb_host_interface *iface) +{ + + struct usb_endpoint_descriptor *endpoint; + int i; + + for (i = 0; i < iface->desc.bNumEndpoints; ++i) { + endpoint = &iface->endpoint[i].desc; + + if (usb_endpoint_is_int_in(endpoint)) { + /* we found our interrupt in endpoint */ + return endpoint; + } + } + + return NULL; +} + +/* + * Routine that sets up the driver to handle a specific USB device detected on the bus. + */ +static int keyspan_probe(struct usb_interface *interface, const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(interface); + struct usb_endpoint_descriptor *endpoint; + struct usb_keyspan *remote; + struct input_dev *input_dev; + int i, error; + + endpoint = keyspan_get_in_endpoint(interface->cur_altsetting); + if (!endpoint) + return -ENODEV; + + remote = kzalloc(sizeof(*remote), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!remote || !input_dev) { + error = -ENOMEM; + goto fail1; + } + + remote->udev = udev; + remote->input = input_dev; + remote->interface = interface; + remote->in_endpoint = endpoint; + remote->toggle = -1; /* Set to -1 so we will always not match the toggle from the first remote message. */ + + remote->in_buffer = usb_buffer_alloc(udev, RECV_SIZE, GFP_ATOMIC, &remote->in_dma); + if (!remote->in_buffer) { + error = -ENOMEM; + goto fail1; + } + + remote->irq_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!remote->irq_urb) { + error = -ENOMEM; + goto fail2; + } + + error = keyspan_setup(udev); + if (error) { + error = -ENODEV; + goto fail3; + } + + if (udev->manufacturer) + strlcpy(remote->name, udev->manufacturer, sizeof(remote->name)); + + if (udev->product) { + if (udev->manufacturer) + strlcat(remote->name, " ", sizeof(remote->name)); + strlcat(remote->name, udev->product, sizeof(remote->name)); + } + + if (!strlen(remote->name)) + snprintf(remote->name, sizeof(remote->name), + "USB Keyspan Remote %04x:%04x", + le16_to_cpu(udev->descriptor.idVendor), + le16_to_cpu(udev->descriptor.idProduct)); + + usb_make_path(udev, remote->phys, sizeof(remote->phys)); + strlcat(remote->phys, "/input0", sizeof(remote->phys)); + memcpy(remote->keymap, keyspan_key_table, sizeof(remote->keymap)); + + input_dev->name = remote->name; + input_dev->phys = remote->phys; + usb_to_input_id(udev, &input_dev->id); + input_dev->dev.parent = &interface->dev; + input_dev->keycode = remote->keymap; + input_dev->keycodesize = sizeof(unsigned short); + input_dev->keycodemax = ARRAY_SIZE(remote->keymap); + + input_set_capability(input_dev, EV_MSC, MSC_SCAN); + __set_bit(EV_KEY, input_dev->evbit); + for (i = 0; i < ARRAY_SIZE(keyspan_key_table); i++) + __set_bit(keyspan_key_table[i], input_dev->keybit); + __clear_bit(KEY_RESERVED, input_dev->keybit); + + input_set_drvdata(input_dev, remote); + + input_dev->open = keyspan_open; + input_dev->close = keyspan_close; + + /* + * Initialize the URB to access the device. + * The urb gets sent to the device in keyspan_open() + */ + usb_fill_int_urb(remote->irq_urb, + remote->udev, + usb_rcvintpipe(remote->udev, endpoint->bEndpointAddress), + remote->in_buffer, RECV_SIZE, keyspan_irq_recv, remote, + endpoint->bInterval); + remote->irq_urb->transfer_dma = remote->in_dma; + remote->irq_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + + /* we can register the device now, as it is ready */ + error = input_register_device(remote->input); + if (error) + goto fail3; + + /* save our data pointer in this interface device */ + usb_set_intfdata(interface, remote); + + return 0; + + fail3: usb_free_urb(remote->irq_urb); + fail2: usb_buffer_free(udev, RECV_SIZE, remote->in_buffer, remote->in_dma); + fail1: kfree(remote); + input_free_device(input_dev); + + return error; +} + +/* + * Routine called when a device is disconnected from the USB. + */ +static void keyspan_disconnect(struct usb_interface *interface) +{ + struct usb_keyspan *remote; + + remote = usb_get_intfdata(interface); + usb_set_intfdata(interface, NULL); + + if (remote) { /* We have a valid driver structure so clean up everything we allocated. */ + input_unregister_device(remote->input); + usb_kill_urb(remote->irq_urb); + usb_free_urb(remote->irq_urb); + usb_buffer_free(remote->udev, RECV_SIZE, remote->in_buffer, remote->in_dma); + kfree(remote); + } +} + +/* + * Standard driver set up sections + */ +static struct usb_driver keyspan_driver = +{ + .name = "keyspan_remote", + .probe = keyspan_probe, + .disconnect = keyspan_disconnect, + .id_table = keyspan_table +}; + +static int __init usb_keyspan_init(void) +{ + int result; + + /* register this driver with the USB subsystem */ + result = usb_register(&keyspan_driver); + if (result) + err("usb_register failed. Error number %d\n", result); + + return result; +} + +static void __exit usb_keyspan_exit(void) +{ + /* deregister this driver with the USB subsystem */ + usb_deregister(&keyspan_driver); +} + +module_init(usb_keyspan_init); +module_exit(usb_keyspan_exit); + +MODULE_DEVICE_TABLE(usb, keyspan_table); +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE(DRIVER_LICENSE); diff --git a/drivers/input/misc/m68kspkr.c b/drivers/input/misc/m68kspkr.c new file mode 100644 index 0000000..0c64d9b --- /dev/null +++ b/drivers/input/misc/m68kspkr.c @@ -0,0 +1,151 @@ +/* + * 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 <linux/platform_device.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 struct platform_device *m68kspkr_platform_device; + +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 __devinit m68kspkr_probe(struct platform_device *dev) +{ + struct input_dev *input_dev; + int err; + + input_dev = input_allocate_device(); + if (!input_dev) + return -ENOMEM; + + input_dev->name = "m68k beeper"; + input_dev->phys = "m68k/generic"; + input_dev->id.bustype = BUS_HOST; + input_dev->id.vendor = 0x001f; + input_dev->id.product = 0x0001; + input_dev->id.version = 0x0100; + input_dev->dev.parent = &dev->dev; + + input_dev->evbit[0] = BIT_MASK(EV_SND); + input_dev->sndbit[0] = BIT_MASK(SND_BELL) | BIT_MASK(SND_TONE); + input_dev->event = m68kspkr_event; + + err = input_register_device(input_dev); + if (err) { + input_free_device(input_dev); + return err; + } + + platform_set_drvdata(dev, input_dev); + + return 0; +} + +static int __devexit m68kspkr_remove(struct platform_device *dev) +{ + struct input_dev *input_dev = platform_get_drvdata(dev); + + input_unregister_device(input_dev); + platform_set_drvdata(dev, NULL); + /* turn off the speaker */ + m68kspkr_event(NULL, EV_SND, SND_BELL, 0); + + return 0; +} + +static void m68kspkr_shutdown(struct platform_device *dev) +{ + /* turn off the speaker */ + m68kspkr_event(NULL, EV_SND, SND_BELL, 0); +} + +static struct platform_driver m68kspkr_platform_driver = { + .driver = { + .name = "m68kspkr", + .owner = THIS_MODULE, + }, + .probe = m68kspkr_probe, + .remove = __devexit_p(m68kspkr_remove), + .shutdown = m68kspkr_shutdown, +}; + +static int __init m68kspkr_init(void) +{ + int err; + + if (!mach_beep) { + printk(KERN_INFO "m68kspkr: no lowlevel beep support\n"); + return -ENODEV; + } + + err = platform_driver_register(&m68kspkr_platform_driver); + if (err) + return err; + + m68kspkr_platform_device = platform_device_alloc("m68kspkr", -1); + if (!m68kspkr_platform_device) { + err = -ENOMEM; + goto err_unregister_driver; + } + + err = platform_device_add(m68kspkr_platform_device); + if (err) + goto err_free_device; + + return 0; + + err_free_device: + platform_device_put(m68kspkr_platform_device); + err_unregister_driver: + platform_driver_unregister(&m68kspkr_platform_driver); + + return err; +} + +static void __exit m68kspkr_exit(void) +{ + platform_device_unregister(m68kspkr_platform_device); + platform_driver_unregister(&m68kspkr_platform_driver); +} + +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 0000000..43aaa5c --- /dev/null +++ b/drivers/input/misc/pcspkr.c @@ -0,0 +1,152 @@ +/* + * 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 <linux/platform_device.h> +#include <asm/io.h> + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION("PC Speaker beeper driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:pcspkr"); + +#if defined(CONFIG_MIPS) || defined(CONFIG_X86) +/* Use the global PIT lock ! */ +#include <asm/i8253.h> +#else +#include <asm/8253pit.h> +static DEFINE_SPINLOCK(i8253_lock); +#endif + +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_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_lock, flags); + + return 0; +} + +static int __devinit pcspkr_probe(struct platform_device *dev) +{ + struct input_dev *pcspkr_dev; + int err; + + pcspkr_dev = input_allocate_device(); + if (!pcspkr_dev) + return -ENOMEM; + + pcspkr_dev->name = "PC Speaker"; + pcspkr_dev->phys = "isa0061/input0"; + pcspkr_dev->id.bustype = BUS_ISA; + pcspkr_dev->id.vendor = 0x001f; + pcspkr_dev->id.product = 0x0001; + pcspkr_dev->id.version = 0x0100; + pcspkr_dev->dev.parent = &dev->dev; + + pcspkr_dev->evbit[0] = BIT_MASK(EV_SND); + pcspkr_dev->sndbit[0] = BIT_MASK(SND_BELL) | BIT_MASK(SND_TONE); + pcspkr_dev->event = pcspkr_event; + + err = input_register_device(pcspkr_dev); + if (err) { + input_free_device(pcspkr_dev); + return err; + } + + platform_set_drvdata(dev, pcspkr_dev); + + return 0; +} + +static int __devexit pcspkr_remove(struct platform_device *dev) +{ + struct input_dev *pcspkr_dev = platform_get_drvdata(dev); + + input_unregister_device(pcspkr_dev); + platform_set_drvdata(dev, NULL); + /* turn off the speaker */ + pcspkr_event(NULL, EV_SND, SND_BELL, 0); + + return 0; +} + +static int pcspkr_suspend(struct platform_device *dev, pm_message_t state) +{ + pcspkr_event(NULL, EV_SND, SND_BELL, 0); + + return 0; +} + +static void pcspkr_shutdown(struct platform_device *dev) +{ + /* turn off the speaker */ + pcspkr_event(NULL, EV_SND, SND_BELL, 0); +} + +static struct platform_driver pcspkr_platform_driver = { + .driver = { + .name = "pcspkr", + .owner = THIS_MODULE, + }, + .probe = pcspkr_probe, + .remove = __devexit_p(pcspkr_remove), + .suspend = pcspkr_suspend, + .shutdown = pcspkr_shutdown, +}; + + +static int __init pcspkr_init(void) +{ + return platform_driver_register(&pcspkr_platform_driver); +} + +static void __exit pcspkr_exit(void) +{ + platform_driver_unregister(&pcspkr_platform_driver); +} + +module_init(pcspkr_init); +module_exit(pcspkr_exit); diff --git a/drivers/input/misc/powermate.c b/drivers/input/misc/powermate.c new file mode 100644 index 0000000..a53c488 --- /dev/null +++ b/drivers/input/misc/powermate.c @@ -0,0 +1,464 @@ +/* + * A driver for the Griffin Technology, Inc. "PowerMate" USB controller dial. + * + * v1.1, (c)2002 William R Sowerbutts <will@sowerbutts.com> + * + * This device is a anodised aluminium knob which connects over USB. It can measure + * clockwise and anticlockwise rotation. The dial also acts as a pushbutton with + * a spring for automatic release. The base contains a pair of LEDs which illuminate + * the translucent base. It rotates without limit and reports its relative rotation + * back to the host when polled by the USB controller. + * + * Testing with the knob I have has shown that it measures approximately 94 "clicks" + * for one full rotation. Testing with my High Speed Rotation Actuator (ok, it was + * a variable speed cordless electric drill) has shown that the device can measure + * speeds of up to 7 clicks either clockwise or anticlockwise between pollings from + * the host. If it counts more than 7 clicks before it is polled, it will wrap back + * to zero and start counting again. This was at quite high speed, however, almost + * certainly faster than the human hand could turn it. Griffin say that it loses a + * pulse or two on a direction change; the granularity is so fine that I never + * noticed this in practice. + * + * The device's microcontroller can be programmed to set the LED to either a constant + * intensity, or to a rhythmic pulsing. Several patterns and speeds are available. + * + * Griffin were very happy to provide documentation and free hardware for development. + * + * Some userspace tools are available on the web: http://sowerbutts.com/powermate/ + * + */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/spinlock.h> +#include <linux/usb/input.h> + +#define POWERMATE_VENDOR 0x077d /* Griffin Technology, Inc. */ +#define POWERMATE_PRODUCT_NEW 0x0410 /* Griffin PowerMate */ +#define POWERMATE_PRODUCT_OLD 0x04AA /* Griffin soundKnob */ + +#define CONTOUR_VENDOR 0x05f3 /* Contour Design, Inc. */ +#define CONTOUR_JOG 0x0240 /* Jog and Shuttle */ + +/* these are the command codes we send to the device */ +#define SET_STATIC_BRIGHTNESS 0x01 +#define SET_PULSE_ASLEEP 0x02 +#define SET_PULSE_AWAKE 0x03 +#define SET_PULSE_MODE 0x04 + +/* these refer to bits in the powermate_device's requires_update field. */ +#define UPDATE_STATIC_BRIGHTNESS (1<<0) +#define UPDATE_PULSE_ASLEEP (1<<1) +#define UPDATE_PULSE_AWAKE (1<<2) +#define UPDATE_PULSE_MODE (1<<3) + +/* at least two versions of the hardware exist, with differing payload + sizes. the first three bytes always contain the "interesting" data in + the relevant format. */ +#define POWERMATE_PAYLOAD_SIZE_MAX 6 +#define POWERMATE_PAYLOAD_SIZE_MIN 3 +struct powermate_device { + signed char *data; + dma_addr_t data_dma; + struct urb *irq, *config; + struct usb_ctrlrequest *configcr; + dma_addr_t configcr_dma; + struct usb_device *udev; + struct input_dev *input; + spinlock_t lock; + int static_brightness; + int pulse_speed; + int pulse_table; + int pulse_asleep; + int pulse_awake; + int requires_update; // physical settings which are out of sync + char phys[64]; +}; + +static char pm_name_powermate[] = "Griffin PowerMate"; +static char pm_name_soundknob[] = "Griffin SoundKnob"; + +static void powermate_config_complete(struct urb *urb); + +/* Callback for data arriving from the PowerMate over the USB interrupt pipe */ +static void powermate_irq(struct urb *urb) +{ + struct powermate_device *pm = urb->context; + int retval; + + 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", __func__, urb->status); + return; + default: + dbg("%s - nonzero urb status received: %d", __func__, urb->status); + goto exit; + } + + /* handle updates to device state */ + input_report_key(pm->input, BTN_0, pm->data[0] & 0x01); + input_report_rel(pm->input, REL_DIAL, pm->data[1]); + input_sync(pm->input); + +exit: + retval = usb_submit_urb (urb, GFP_ATOMIC); + if (retval) + err ("%s - usb_submit_urb failed with result %d", + __func__, retval); +} + +/* Decide if we need to issue a control message and do so. Must be called with pm->lock taken */ +static void powermate_sync_state(struct powermate_device *pm) +{ + if (pm->requires_update == 0) + return; /* no updates are required */ + if (pm->config->status == -EINPROGRESS) + return; /* an update is already in progress; it'll issue this update when it completes */ + + if (pm->requires_update & UPDATE_PULSE_ASLEEP){ + pm->configcr->wValue = cpu_to_le16( SET_PULSE_ASLEEP ); + pm->configcr->wIndex = cpu_to_le16( pm->pulse_asleep ? 1 : 0 ); + pm->requires_update &= ~UPDATE_PULSE_ASLEEP; + }else if (pm->requires_update & UPDATE_PULSE_AWAKE){ + pm->configcr->wValue = cpu_to_le16( SET_PULSE_AWAKE ); + pm->configcr->wIndex = cpu_to_le16( pm->pulse_awake ? 1 : 0 ); + pm->requires_update &= ~UPDATE_PULSE_AWAKE; + }else if (pm->requires_update & UPDATE_PULSE_MODE){ + int op, arg; + /* the powermate takes an operation and an argument for its pulse algorithm. + the operation can be: + 0: divide the speed + 1: pulse at normal speed + 2: multiply the speed + the argument only has an effect for operations 0 and 2, and ranges between + 1 (least effect) to 255 (maximum effect). + + thus, several states are equivalent and are coalesced into one state. + + we map this onto a range from 0 to 510, with: + 0 -- 254 -- use divide (0 = slowest) + 255 -- use normal speed + 256 -- 510 -- use multiple (510 = fastest). + + Only values of 'arg' quite close to 255 are particularly useful/spectacular. + */ + if (pm->pulse_speed < 255) { + op = 0; // divide + arg = 255 - pm->pulse_speed; + } else if (pm->pulse_speed > 255) { + op = 2; // multiply + arg = pm->pulse_speed - 255; + } else { + op = 1; // normal speed + arg = 0; // can be any value + } + pm->configcr->wValue = cpu_to_le16( (pm->pulse_table << 8) | SET_PULSE_MODE ); + pm->configcr->wIndex = cpu_to_le16( (arg << 8) | op ); + pm->requires_update &= ~UPDATE_PULSE_MODE; + } else if (pm->requires_update & UPDATE_STATIC_BRIGHTNESS) { + pm->configcr->wValue = cpu_to_le16( SET_STATIC_BRIGHTNESS ); + pm->configcr->wIndex = cpu_to_le16( pm->static_brightness ); + pm->requires_update &= ~UPDATE_STATIC_BRIGHTNESS; + } else { + printk(KERN_ERR "powermate: unknown update required"); + pm->requires_update = 0; /* fudge the bug */ + return; + } + +/* printk("powermate: %04x %04x\n", pm->configcr->wValue, pm->configcr->wIndex); */ + + pm->configcr->bRequestType = 0x41; /* vendor request */ + pm->configcr->bRequest = 0x01; + pm->configcr->wLength = 0; + + usb_fill_control_urb(pm->config, pm->udev, usb_sndctrlpipe(pm->udev, 0), + (void *) pm->configcr, NULL, 0, + powermate_config_complete, pm); + pm->config->setup_dma = pm->configcr_dma; + pm->config->transfer_flags |= URB_NO_SETUP_DMA_MAP; + + if (usb_submit_urb(pm->config, GFP_ATOMIC)) + printk(KERN_ERR "powermate: usb_submit_urb(config) failed"); +} + +/* Called when our asynchronous control message completes. We may need to issue another immediately */ +static void powermate_config_complete(struct urb *urb) +{ + struct powermate_device *pm = urb->context; + unsigned long flags; + + if (urb->status) + printk(KERN_ERR "powermate: config urb returned %d\n", urb->status); + + spin_lock_irqsave(&pm->lock, flags); + powermate_sync_state(pm); + spin_unlock_irqrestore(&pm->lock, flags); +} + +/* Set the LED up as described and begin the sync with the hardware if required */ +static void powermate_pulse_led(struct powermate_device *pm, int static_brightness, int pulse_speed, + int pulse_table, int pulse_asleep, int pulse_awake) +{ + unsigned long flags; + + if (pulse_speed < 0) + pulse_speed = 0; + if (pulse_table < 0) + pulse_table = 0; + if (pulse_speed > 510) + pulse_speed = 510; + if (pulse_table > 2) + pulse_table = 2; + + pulse_asleep = !!pulse_asleep; + pulse_awake = !!pulse_awake; + + + spin_lock_irqsave(&pm->lock, flags); + + /* mark state updates which are required */ + if (static_brightness != pm->static_brightness) { + pm->static_brightness = static_brightness; + pm->requires_update |= UPDATE_STATIC_BRIGHTNESS; + } + if (pulse_asleep != pm->pulse_asleep) { + pm->pulse_asleep = pulse_asleep; + pm->requires_update |= (UPDATE_PULSE_ASLEEP | UPDATE_STATIC_BRIGHTNESS); + } + if (pulse_awake != pm->pulse_awake) { + pm->pulse_awake = pulse_awake; + pm->requires_update |= (UPDATE_PULSE_AWAKE | UPDATE_STATIC_BRIGHTNESS); + } + if (pulse_speed != pm->pulse_speed || pulse_table != pm->pulse_table) { + pm->pulse_speed = pulse_speed; + pm->pulse_table = pulse_table; + pm->requires_update |= UPDATE_PULSE_MODE; + } + + powermate_sync_state(pm); + + spin_unlock_irqrestore(&pm->lock, flags); +} + +/* Callback from the Input layer when an event arrives from userspace to configure the LED */ +static int powermate_input_event(struct input_dev *dev, unsigned int type, unsigned int code, int _value) +{ + unsigned int command = (unsigned int)_value; + struct powermate_device *pm = input_get_drvdata(dev); + + if (type == EV_MSC && code == MSC_PULSELED){ + /* + bits 0- 7: 8 bits: LED brightness + bits 8-16: 9 bits: pulsing speed modifier (0 ... 510); 0-254 = slower, 255 = standard, 256-510 = faster. + bits 17-18: 2 bits: pulse table (0, 1, 2 valid) + bit 19: 1 bit : pulse whilst asleep? + bit 20: 1 bit : pulse constantly? + */ + int static_brightness = command & 0xFF; // bits 0-7 + int pulse_speed = (command >> 8) & 0x1FF; // bits 8-16 + int pulse_table = (command >> 17) & 0x3; // bits 17-18 + int pulse_asleep = (command >> 19) & 0x1; // bit 19 + int pulse_awake = (command >> 20) & 0x1; // bit 20 + + powermate_pulse_led(pm, static_brightness, pulse_speed, pulse_table, pulse_asleep, pulse_awake); + } + + return 0; +} + +static int powermate_alloc_buffers(struct usb_device *udev, struct powermate_device *pm) +{ + pm->data = usb_buffer_alloc(udev, POWERMATE_PAYLOAD_SIZE_MAX, + GFP_ATOMIC, &pm->data_dma); + if (!pm->data) + return -1; + + pm->configcr = usb_buffer_alloc(udev, sizeof(*(pm->configcr)), + GFP_ATOMIC, &pm->configcr_dma); + if (!pm->configcr) + return -1; + + return 0; +} + +static void powermate_free_buffers(struct usb_device *udev, struct powermate_device *pm) +{ + usb_buffer_free(udev, POWERMATE_PAYLOAD_SIZE_MAX, + pm->data, pm->data_dma); + usb_buffer_free(udev, sizeof(*(pm->configcr)), + pm->configcr, pm->configcr_dma); +} + +/* Called whenever a USB device matching one in our supported devices table is connected */ +static int powermate_probe(struct usb_interface *intf, const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev (intf); + struct usb_host_interface *interface; + struct usb_endpoint_descriptor *endpoint; + struct powermate_device *pm; + struct input_dev *input_dev; + int pipe, maxp; + int error = -ENOMEM; + + interface = intf->cur_altsetting; + endpoint = &interface->endpoint[0].desc; + if (!usb_endpoint_is_int_in(endpoint)) + return -EIO; + + usb_control_msg(udev, usb_sndctrlpipe(udev, 0), + 0x0a, USB_TYPE_CLASS | USB_RECIP_INTERFACE, + 0, interface->desc.bInterfaceNumber, NULL, 0, + USB_CTRL_SET_TIMEOUT); + + pm = kzalloc(sizeof(struct powermate_device), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!pm || !input_dev) + goto fail1; + + if (powermate_alloc_buffers(udev, pm)) + goto fail2; + + pm->irq = usb_alloc_urb(0, GFP_KERNEL); + if (!pm->irq) + goto fail2; + + pm->config = usb_alloc_urb(0, GFP_KERNEL); + if (!pm->config) + goto fail3; + + pm->udev = udev; + pm->input = input_dev; + + usb_make_path(udev, pm->phys, sizeof(pm->phys)); + strlcpy(pm->phys, "/input0", sizeof(pm->phys)); + + spin_lock_init(&pm->lock); + + switch (le16_to_cpu(udev->descriptor.idProduct)) { + case POWERMATE_PRODUCT_NEW: + input_dev->name = pm_name_powermate; + break; + case POWERMATE_PRODUCT_OLD: + input_dev->name = pm_name_soundknob; + break; + default: + input_dev->name = pm_name_soundknob; + printk(KERN_WARNING "powermate: unknown product id %04x\n", + le16_to_cpu(udev->descriptor.idProduct)); + } + + input_dev->phys = pm->phys; + usb_to_input_id(udev, &input_dev->id); + input_dev->dev.parent = &intf->dev; + + input_set_drvdata(input_dev, pm); + + input_dev->event = powermate_input_event; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REL) | + BIT_MASK(EV_MSC); + input_dev->keybit[BIT_WORD(BTN_0)] = BIT_MASK(BTN_0); + input_dev->relbit[BIT_WORD(REL_DIAL)] = BIT_MASK(REL_DIAL); + input_dev->mscbit[BIT_WORD(MSC_PULSELED)] = BIT_MASK(MSC_PULSELED); + + /* get a handle to the interrupt data pipe */ + pipe = usb_rcvintpipe(udev, endpoint->bEndpointAddress); + maxp = usb_maxpacket(udev, pipe, usb_pipeout(pipe)); + + if (maxp < POWERMATE_PAYLOAD_SIZE_MIN || maxp > POWERMATE_PAYLOAD_SIZE_MAX) { + printk(KERN_WARNING "powermate: Expected payload of %d--%d bytes, found %d bytes!\n", + POWERMATE_PAYLOAD_SIZE_MIN, POWERMATE_PAYLOAD_SIZE_MAX, maxp); + maxp = POWERMATE_PAYLOAD_SIZE_MAX; + } + + usb_fill_int_urb(pm->irq, udev, pipe, pm->data, + maxp, powermate_irq, + pm, endpoint->bInterval); + pm->irq->transfer_dma = pm->data_dma; + pm->irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + + /* register our interrupt URB with the USB system */ + if (usb_submit_urb(pm->irq, GFP_KERNEL)) { + error = -EIO; + goto fail4; + } + + error = input_register_device(pm->input); + if (error) + goto fail5; + + + /* force an update of everything */ + pm->requires_update = UPDATE_PULSE_ASLEEP | UPDATE_PULSE_AWAKE | UPDATE_PULSE_MODE | UPDATE_STATIC_BRIGHTNESS; + powermate_pulse_led(pm, 0x80, 255, 0, 1, 0); // set default pulse parameters + + usb_set_intfdata(intf, pm); + return 0; + + fail5: usb_kill_urb(pm->irq); + fail4: usb_free_urb(pm->config); + fail3: usb_free_urb(pm->irq); + fail2: powermate_free_buffers(udev, pm); + fail1: input_free_device(input_dev); + kfree(pm); + return error; +} + +/* Called when a USB device we've accepted ownership of is removed */ +static void powermate_disconnect(struct usb_interface *intf) +{ + struct powermate_device *pm = usb_get_intfdata (intf); + + usb_set_intfdata(intf, NULL); + if (pm) { + pm->requires_update = 0; + usb_kill_urb(pm->irq); + input_unregister_device(pm->input); + usb_free_urb(pm->irq); + usb_free_urb(pm->config); + powermate_free_buffers(interface_to_usbdev(intf), pm); + + kfree(pm); + } +} + +static struct usb_device_id powermate_devices [] = { + { USB_DEVICE(POWERMATE_VENDOR, POWERMATE_PRODUCT_NEW) }, + { USB_DEVICE(POWERMATE_VENDOR, POWERMATE_PRODUCT_OLD) }, + { USB_DEVICE(CONTOUR_VENDOR, CONTOUR_JOG) }, + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE (usb, powermate_devices); + +static struct usb_driver powermate_driver = { + .name = "powermate", + .probe = powermate_probe, + .disconnect = powermate_disconnect, + .id_table = powermate_devices, +}; + +static int __init powermate_init(void) +{ + return usb_register(&powermate_driver); +} + +static void __exit powermate_cleanup(void) +{ + usb_deregister(&powermate_driver); +} + +module_init(powermate_init); +module_exit(powermate_cleanup); + +MODULE_AUTHOR( "William R Sowerbutts" ); +MODULE_DESCRIPTION( "Griffin Technology, Inc PowerMate driver" ); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/misc/sgi_btns.c b/drivers/input/misc/sgi_btns.c new file mode 100644 index 0000000..be3a15f --- /dev/null +++ b/drivers/input/misc/sgi_btns.c @@ -0,0 +1,179 @@ +/* + * SGI Volume Button interface driver + * + * Copyright (C) 2008 Thomas Bogendoerfer <tsbogend@alpha.franken.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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +#include <linux/init.h> +#include <linux/input-polldev.h> +#include <linux/ioport.h> +#include <linux/module.h> +#include <linux/platform_device.h> + +#ifdef CONFIG_SGI_IP22 +#include <asm/sgi/ioc.h> + +static inline u8 button_status(void) +{ + u8 status; + + status = readb(&sgioc->panel) ^ 0xa0; + return ((status & 0x80) >> 6) | ((status & 0x20) >> 5); +} +#endif + +#ifdef CONFIG_SGI_IP32 +#include <asm/ip32/mace.h> + +static inline u8 button_status(void) +{ + u64 status; + + status = readq(&mace->perif.audio.control); + writeq(status & ~(3U << 23), &mace->perif.audio.control); + + return (status >> 23) & 3; +} +#endif + +#define BUTTONS_POLL_INTERVAL 30 /* msec */ +#define BUTTONS_COUNT_THRESHOLD 3 + +static const unsigned short sgi_map[] = { + KEY_VOLUMEDOWN, + KEY_VOLUMEUP +}; + +struct buttons_dev { + struct input_polled_dev *poll_dev; + unsigned short keymap[ARRAY_SIZE(sgi_map)]; + int count[ARRAY_SIZE(sgi_map)]; +}; + +static void handle_buttons(struct input_polled_dev *dev) +{ + struct buttons_dev *bdev = dev->private; + struct input_dev *input = dev->input; + u8 status; + int i; + + status = button_status(); + + for (i = 0; i < ARRAY_SIZE(bdev->keymap); i++) { + if (status & (1U << i)) { + if (++bdev->count[i] == BUTTONS_COUNT_THRESHOLD) { + input_event(input, EV_MSC, MSC_SCAN, i); + input_report_key(input, bdev->keymap[i], 1); + input_sync(input); + } + } else { + if (bdev->count[i] >= BUTTONS_COUNT_THRESHOLD) { + input_event(input, EV_MSC, MSC_SCAN, i); + input_report_key(input, bdev->keymap[i], 0); + input_sync(input); + } + bdev->count[i] = 0; + } + } +} + +static int __devinit sgi_buttons_probe(struct platform_device *pdev) +{ + struct buttons_dev *bdev; + struct input_polled_dev *poll_dev; + struct input_dev *input; + int error, i; + + bdev = kzalloc(sizeof(struct buttons_dev), GFP_KERNEL); + poll_dev = input_allocate_polled_device(); + if (!bdev || !poll_dev) { + error = -ENOMEM; + goto err_free_mem; + } + + memcpy(bdev->keymap, sgi_map, sizeof(bdev->keymap)); + + poll_dev->private = bdev; + poll_dev->poll = handle_buttons; + poll_dev->poll_interval = BUTTONS_POLL_INTERVAL; + + input = poll_dev->input; + input->name = "SGI buttons"; + input->phys = "sgi/input0"; + input->id.bustype = BUS_HOST; + input->dev.parent = &pdev->dev; + + input->keycode = bdev->keymap; + input->keycodemax = ARRAY_SIZE(bdev->keymap); + input->keycodesize = sizeof(unsigned short); + + input_set_capability(input, EV_MSC, MSC_SCAN); + __set_bit(EV_KEY, input->evbit); + for (i = 0; i < ARRAY_SIZE(sgi_map); i++) + __set_bit(bdev->keymap[i], input->keybit); + __clear_bit(KEY_RESERVED, input->keybit); + + bdev->poll_dev = poll_dev; + dev_set_drvdata(&pdev->dev, bdev); + + error = input_register_polled_device(poll_dev); + if (error) + goto err_free_mem; + + return 0; + + err_free_mem: + input_free_polled_device(poll_dev); + kfree(bdev); + dev_set_drvdata(&pdev->dev, NULL); + return error; +} + +static int __devexit sgi_buttons_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct buttons_dev *bdev = dev_get_drvdata(dev); + + input_unregister_polled_device(bdev->poll_dev); + input_free_polled_device(bdev->poll_dev); + kfree(bdev); + dev_set_drvdata(dev, NULL); + + return 0; +} + +static struct platform_driver sgi_buttons_driver = { + .probe = sgi_buttons_probe, + .remove = __devexit_p(sgi_buttons_remove), + .driver = { + .name = "sgibtns", + .owner = THIS_MODULE, + }, +}; + +static int __init sgi_buttons_init(void) +{ + return platform_driver_register(&sgi_buttons_driver); +} + +static void __exit sgi_buttons_exit(void) +{ + platform_driver_unregister(&sgi_buttons_driver); +} + +MODULE_LICENSE("GPL"); +module_init(sgi_buttons_init); +module_exit(sgi_buttons_exit); diff --git a/drivers/input/misc/sparcspkr.c b/drivers/input/misc/sparcspkr.c new file mode 100644 index 0000000..c4f4231 --- /dev/null +++ b/drivers/input/misc/sparcspkr.c @@ -0,0 +1,369 @@ +/* + * Driver for PC-speaker like devices found on various Sparc systems. + * + * Copyright (c) 2002 Vojtech Pavlik + * Copyright (c) 2002, 2006, 2008 David S. Miller (davem@davemloft.net) + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/of_device.h> + +#include <asm/io.h> + +MODULE_AUTHOR("David S. Miller <davem@davemloft.net>"); +MODULE_DESCRIPTION("Sparc Speaker beeper driver"); +MODULE_LICENSE("GPL"); + +struct grover_beep_info { + void __iomem *freq_regs; + void __iomem *enable_reg; +}; + +struct bbc_beep_info { + u32 clock_freq; + void __iomem *regs; +}; + +struct sparcspkr_state { + const char *name; + int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value); + spinlock_t lock; + struct input_dev *input_dev; + union { + struct grover_beep_info grover; + struct bbc_beep_info bbc; + } u; +}; + +static u32 bbc_count_to_reg(struct bbc_beep_info *info, unsigned int count) +{ + u32 val, clock_freq = info->clock_freq; + int i; + + if (!count) + return 0; + + if (count <= clock_freq >> 20) + return 1 << 18; + + if (count >= clock_freq >> 12) + return 1 << 10; + + val = 1 << 18; + for (i = 19; i >= 11; i--) { + val >>= 1; + if (count <= clock_freq >> i) + break; + } + + return val; +} + +static int bbc_spkr_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) +{ + struct sparcspkr_state *state = dev_get_drvdata(dev->dev.parent); + struct bbc_beep_info *info = &state->u.bbc; + 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; + + count = bbc_count_to_reg(info, count); + + spin_lock_irqsave(&state->lock, flags); + + if (count) { + outb(0x01, info->regs + 0); + outb(0x00, info->regs + 2); + outb((count >> 16) & 0xff, info->regs + 3); + outb((count >> 8) & 0xff, info->regs + 4); + outb(0x00, info->regs + 5); + } else { + outb(0x00, info->regs + 0); + } + + spin_unlock_irqrestore(&state->lock, flags); + + return 0; +} + +static int grover_spkr_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) +{ + struct sparcspkr_state *state = dev_get_drvdata(dev->dev.parent); + struct grover_beep_info *info = &state->u.grover; + 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(&state->lock, flags); + + if (count) { + /* enable counter 2 */ + outb(inb(info->enable_reg) | 3, info->enable_reg); + /* set command for counter 2, 2 byte write */ + outb(0xB6, info->freq_regs + 1); + /* select desired HZ */ + outb(count & 0xff, info->freq_regs + 0); + outb((count >> 8) & 0xff, info->freq_regs + 0); + } else { + /* disable counter 2 */ + outb(inb_p(info->enable_reg) & 0xFC, info->enable_reg); + } + + spin_unlock_irqrestore(&state->lock, flags); + + return 0; +} + +static int __devinit sparcspkr_probe(struct device *dev) +{ + struct sparcspkr_state *state = dev_get_drvdata(dev); + struct input_dev *input_dev; + int error; + + input_dev = input_allocate_device(); + if (!input_dev) + return -ENOMEM; + + input_dev->name = state->name; + input_dev->phys = "sparc/input0"; + input_dev->id.bustype = BUS_ISA; + input_dev->id.vendor = 0x001f; + input_dev->id.product = 0x0001; + input_dev->id.version = 0x0100; + input_dev->dev.parent = dev; + + input_dev->evbit[0] = BIT_MASK(EV_SND); + input_dev->sndbit[0] = BIT_MASK(SND_BELL) | BIT_MASK(SND_TONE); + + input_dev->event = state->event; + + error = input_register_device(input_dev); + if (error) { + input_free_device(input_dev); + return error; + } + + state->input_dev = input_dev; + + return 0; +} + +static int sparcspkr_shutdown(struct of_device *dev) +{ + struct sparcspkr_state *state = dev_get_drvdata(&dev->dev); + struct input_dev *input_dev = state->input_dev; + + /* turn off the speaker */ + state->event(input_dev, EV_SND, SND_BELL, 0); + + return 0; +} + +static int __devinit bbc_beep_probe(struct of_device *op, const struct of_device_id *match) +{ + struct sparcspkr_state *state; + struct bbc_beep_info *info; + struct device_node *dp; + int err = -ENOMEM; + + state = kzalloc(sizeof(*state), GFP_KERNEL); + if (!state) + goto out_err; + + state->name = "Sparc BBC Speaker"; + state->event = bbc_spkr_event; + spin_lock_init(&state->lock); + + dp = of_find_node_by_path("/"); + err = -ENODEV; + if (!dp) + goto out_free; + + info = &state->u.bbc; + info->clock_freq = of_getintprop_default(dp, "clock-frequency", 0); + if (!info->clock_freq) + goto out_free; + + info->regs = of_ioremap(&op->resource[0], 0, 6, "bbc beep"); + if (!info->regs) + goto out_free; + + dev_set_drvdata(&op->dev, state); + + err = sparcspkr_probe(&op->dev); + if (err) + goto out_clear_drvdata; + + return 0; + +out_clear_drvdata: + dev_set_drvdata(&op->dev, NULL); + of_iounmap(&op->resource[0], info->regs, 6); + +out_free: + kfree(state); +out_err: + return err; +} + +static int bbc_remove(struct of_device *op) +{ + struct sparcspkr_state *state = dev_get_drvdata(&op->dev); + struct input_dev *input_dev = state->input_dev; + struct bbc_beep_info *info = &state->u.bbc; + + /* turn off the speaker */ + state->event(input_dev, EV_SND, SND_BELL, 0); + + input_unregister_device(input_dev); + + of_iounmap(&op->resource[0], info->regs, 6); + + dev_set_drvdata(&op->dev, NULL); + kfree(state); + + return 0; +} + +static const struct of_device_id bbc_beep_match[] = { + { + .name = "beep", + .compatible = "SUNW,bbc-beep", + }, + {}, +}; + +static struct of_platform_driver bbc_beep_driver = { + .name = "bbcbeep", + .match_table = bbc_beep_match, + .probe = bbc_beep_probe, + .remove = __devexit_p(bbc_remove), + .shutdown = sparcspkr_shutdown, +}; + +static int __devinit grover_beep_probe(struct of_device *op, const struct of_device_id *match) +{ + struct sparcspkr_state *state; + struct grover_beep_info *info; + int err = -ENOMEM; + + state = kzalloc(sizeof(*state), GFP_KERNEL); + if (!state) + goto out_err; + + state->name = "Sparc Grover Speaker"; + state->event = grover_spkr_event; + spin_lock_init(&state->lock); + + info = &state->u.grover; + info->freq_regs = of_ioremap(&op->resource[2], 0, 2, "grover beep freq"); + if (!info->freq_regs) + goto out_free; + + info->enable_reg = of_ioremap(&op->resource[3], 0, 1, "grover beep enable"); + if (!info->enable_reg) + goto out_unmap_freq_regs; + + dev_set_drvdata(&op->dev, state); + + err = sparcspkr_probe(&op->dev); + if (err) + goto out_clear_drvdata; + + return 0; + +out_clear_drvdata: + dev_set_drvdata(&op->dev, NULL); + of_iounmap(&op->resource[3], info->enable_reg, 1); + +out_unmap_freq_regs: + of_iounmap(&op->resource[2], info->freq_regs, 2); +out_free: + kfree(state); +out_err: + return err; +} + +static int grover_remove(struct of_device *op) +{ + struct sparcspkr_state *state = dev_get_drvdata(&op->dev); + struct grover_beep_info *info = &state->u.grover; + struct input_dev *input_dev = state->input_dev; + + /* turn off the speaker */ + state->event(input_dev, EV_SND, SND_BELL, 0); + + input_unregister_device(input_dev); + + of_iounmap(&op->resource[3], info->enable_reg, 1); + of_iounmap(&op->resource[2], info->freq_regs, 2); + + dev_set_drvdata(&op->dev, NULL); + kfree(state); + + return 0; +} + +static const struct of_device_id grover_beep_match[] = { + { + .name = "beep", + .compatible = "SUNW,smbus-beep", + }, + {}, +}; + +static struct of_platform_driver grover_beep_driver = { + .name = "groverbeep", + .match_table = grover_beep_match, + .probe = grover_beep_probe, + .remove = __devexit_p(grover_remove), + .shutdown = sparcspkr_shutdown, +}; + +static int __init sparcspkr_init(void) +{ + int err = of_register_driver(&bbc_beep_driver, + &of_platform_bus_type); + + if (!err) { + err = of_register_driver(&grover_beep_driver, + &of_platform_bus_type); + if (err) + of_unregister_driver(&bbc_beep_driver); + } + + return err; +} + +static void __exit sparcspkr_exit(void) +{ + of_unregister_driver(&bbc_beep_driver); + of_unregister_driver(&grover_beep_driver); +} + +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 0000000..223d56d --- /dev/null +++ b/drivers/input/misc/uinput.c @@ -0,0 +1,662 @@ +/* + * 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.3 09/04/2006 (Anssi Hannula <anssi.hannula@gmail.com>) + * - updated ff support for the changes in kernel interface + * - added MODULE_VERSION + * 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/smp_lock.h> +#include <linux/fs.h> +#include <linux/miscdevice.h> +#include <linux/uinput.h> + +static int uinput_dev_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) +{ + struct uinput_device *udev = input_get_drvdata(dev); + + 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 uinput_device *udev, struct uinput_request *request) +{ + /* Atomically allocate an ID for the given request. Returns 0 on success. */ + int id; + int err = -1; + + spin_lock(&udev->requests_lock); + + for (id = 0; id < UINPUT_NUM_REQUESTS; id++) + if (!udev->requests[id]) { + request->id = id; + udev->requests[id] = request; + err = 0; + break; + } + + spin_unlock(&udev->requests_lock); + return err; +} + +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; + return udev->requests[id]; +} + +static inline int uinput_request_reserve_slot(struct uinput_device *udev, struct uinput_request *request) +{ + /* Allocate slot. If none are available right away, wait. */ + return wait_event_interruptible(udev->requests_waitq, + !uinput_request_alloc_id(udev, request)); +} + +static void uinput_request_done(struct uinput_device *udev, struct uinput_request *request) +{ + /* Mark slot as available */ + udev->requests[request->id] = NULL; + wake_up(&udev->requests_waitq); + + complete(&request->done); +} + +static int uinput_request_submit(struct input_dev *dev, struct uinput_request *request) +{ + /* 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 */ + wait_for_completion(&request->done); + return request->retval; +} + +static void uinput_dev_set_gain(struct input_dev *dev, u16 gain) +{ + uinput_dev_event(dev, EV_FF, FF_GAIN, gain); +} + +static void uinput_dev_set_autocenter(struct input_dev *dev, u16 magnitude) +{ + uinput_dev_event(dev, EV_FF, FF_AUTOCENTER, magnitude); +} + +static int uinput_dev_playback(struct input_dev *dev, int effect_id, int value) +{ + return uinput_dev_event(dev, EV_FF, effect_id, value); +} + +static int uinput_dev_upload_effect(struct input_dev *dev, struct ff_effect *effect, struct ff_effect *old) +{ + struct uinput_request request; + int retval; + + request.id = -1; + init_completion(&request.done); + request.code = UI_FF_UPLOAD; + request.u.upload.effect = effect; + request.u.upload.old = old; + + retval = uinput_request_reserve_slot(input_get_drvdata(dev), &request); + if (!retval) + retval = uinput_request_submit(dev, &request); + + return retval; +} + +static int uinput_dev_erase_effect(struct input_dev *dev, int effect_id) +{ + struct uinput_request request; + int retval; + + if (!test_bit(EV_FF, dev->evbit)) + return -ENOSYS; + + request.id = -1; + init_completion(&request.done); + request.code = UI_FF_ERASE; + request.u.effect_id = effect_id; + + retval = uinput_request_reserve_slot(input_get_drvdata(dev), &request); + if (!retval) + retval = uinput_request_submit(dev, &request); + + return retval; +} + +static void uinput_destroy_device(struct uinput_device *udev) +{ + const char *name, *phys; + + if (udev->dev) { + name = udev->dev->name; + phys = udev->dev->phys; + if (udev->state == UIST_CREATED) + input_unregister_device(udev->dev); + else + input_free_device(udev->dev); + kfree(name); + kfree(phys); + udev->dev = NULL; + } + + udev->state = UIST_NEW_DEVICE; +} + +static int uinput_create_device(struct uinput_device *udev) +{ + struct input_dev *dev = udev->dev; + int error; + + if (udev->state != UIST_SETUP_COMPLETE) { + printk(KERN_DEBUG "%s: write device info first\n", UINPUT_NAME); + return -EINVAL; + } + + if (udev->ff_effects_max) { + error = input_ff_create(dev, udev->ff_effects_max); + if (error) + goto fail1; + + dev->ff->upload = uinput_dev_upload_effect; + dev->ff->erase = uinput_dev_erase_effect; + dev->ff->playback = uinput_dev_playback; + dev->ff->set_gain = uinput_dev_set_gain; + dev->ff->set_autocenter = uinput_dev_set_autocenter; + } + + error = input_register_device(udev->dev); + if (error) + goto fail2; + + udev->state = UIST_CREATED; + + return 0; + + fail2: input_ff_destroy(dev); + fail1: uinput_destroy_device(udev); + return error; +} + +static int uinput_open(struct inode *inode, struct file *file) +{ + struct uinput_device *newdev; + + newdev = kzalloc(sizeof(struct uinput_device), GFP_KERNEL); + if (!newdev) + return -ENOMEM; + + lock_kernel(); + mutex_init(&newdev->mutex); + spin_lock_init(&newdev->requests_lock); + init_waitqueue_head(&newdev->requests_waitq); + init_waitqueue_head(&newdev->waitq); + newdev->state = UIST_NEW_DEVICE; + + file->private_data = newdev; + unlock_kernel(); + + return 0; +} + +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_allocate_device(struct uinput_device *udev) +{ + udev->dev = input_allocate_device(); + if (!udev->dev) + return -ENOMEM; + + udev->dev->event = uinput_dev_event; + input_set_drvdata(udev->dev, udev); + + return 0; +} + +static int uinput_setup_device(struct uinput_device *udev, const char __user *buffer, size_t count) +{ + struct uinput_user_dev *user_dev; + struct input_dev *dev; + char *name; + int size; + int retval; + + if (count != sizeof(struct uinput_user_dev)) + return -EINVAL; + + if (!udev->dev) { + retval = uinput_allocate_device(udev); + if (retval) + return retval; + } + + dev = udev->dev; + + user_dev = kmalloc(sizeof(struct uinput_user_dev), GFP_KERNEL); + if (!user_dev) + return -ENOMEM; + + if (copy_from_user(user_dev, buffer, sizeof(struct uinput_user_dev))) { + retval = -EFAULT; + goto exit; + } + + udev->ff_effects_max = user_dev->ff_effects_max; + + size = strnlen(user_dev->name, UINPUT_MAX_NAME_SIZE) + 1; + if (!size) { + retval = -EINVAL; + goto exit; + } + + kfree(dev->name); + dev->name = name = kmalloc(size, GFP_KERNEL); + if (!name) { + retval = -ENOMEM; + goto exit; + } + strlcpy(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; + + 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) + goto exit; + } + + udev->state = UIST_SETUP_COMPLETE; + retval = count; + + exit: + kfree(user_dev); + return retval; +} + +static inline ssize_t uinput_inject_event(struct uinput_device *udev, const char __user *buffer, size_t count) +{ + struct input_event ev; + + if (count != sizeof(struct input_event)) + return -EINVAL; + + if (copy_from_user(&ev, buffer, sizeof(struct input_event))) + return -EFAULT; + + input_event(udev->dev, ev.type, ev.code, ev.value); + + return sizeof(struct input_event); +} + +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; + int retval; + + retval = mutex_lock_interruptible(&udev->mutex); + if (retval) + return retval; + + retval = udev->state == UIST_CREATED ? + uinput_inject_event(udev, buffer, count) : + uinput_setup_device(udev, buffer, count); + + mutex_unlock(&udev->mutex); + + return retval; +} + +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 (udev->state != UIST_CREATED) + return -ENODEV; + + if (udev->head == udev->tail && (file->f_flags & O_NONBLOCK)) + return -EAGAIN; + + retval = wait_event_interruptible(udev->waitq, + udev->head != udev->tail || udev->state != UIST_CREATED); + if (retval) + return retval; + + retval = mutex_lock_interruptible(&udev->mutex); + if (retval) + return retval; + + if (udev->state != UIST_CREATED) { + retval = -ENODEV; + goto out; + } + + 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))) { + retval = -EFAULT; + goto out; + } + udev->tail = (udev->tail + 1) % UINPUT_BUFFER_SIZE; + retval += sizeof(struct input_event); + } + + out: + mutex_unlock(&udev->mutex); + + 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_release(struct inode *inode, struct file *file) +{ + struct uinput_device *udev = file->private_data; + + uinput_destroy_device(udev); + kfree(udev); + + return 0; +} + +#define uinput_set_bit(_arg, _bit, _max) \ +({ \ + int __ret = 0; \ + if (udev->state == UIST_CREATED) \ + __ret = -EINVAL; \ + else if ((_arg) > (_max)) \ + __ret = -EINVAL; \ + else set_bit((_arg), udev->dev->_bit); \ + __ret; \ +}) + +static long uinput_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + int retval; + 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; + char *phys; + + udev = file->private_data; + + retval = mutex_lock_interruptible(&udev->mutex); + if (retval) + return retval; + + if (!udev->dev) { + retval = uinput_allocate_device(udev); + if (retval) + goto out; + } + + switch (cmd) { + case UI_DEV_CREATE: + retval = uinput_create_device(udev); + break; + + case UI_DEV_DESTROY: + uinput_destroy_device(udev); + break; + + case UI_SET_EVBIT: + retval = uinput_set_bit(arg, evbit, EV_MAX); + break; + + case UI_SET_KEYBIT: + retval = uinput_set_bit(arg, keybit, KEY_MAX); + break; + + case UI_SET_RELBIT: + retval = uinput_set_bit(arg, relbit, REL_MAX); + break; + + case UI_SET_ABSBIT: + retval = uinput_set_bit(arg, absbit, ABS_MAX); + break; + + case UI_SET_MSCBIT: + retval = uinput_set_bit(arg, mscbit, MSC_MAX); + break; + + case UI_SET_LEDBIT: + retval = uinput_set_bit(arg, ledbit, LED_MAX); + break; + + case UI_SET_SNDBIT: + retval = uinput_set_bit(arg, sndbit, SND_MAX); + break; + + case UI_SET_FFBIT: + retval = uinput_set_bit(arg, ffbit, FF_MAX); + break; + + case UI_SET_SWBIT: + retval = uinput_set_bit(arg, swbit, SW_MAX); + break; + + case UI_SET_PHYS: + if (udev->state == UIST_CREATED) { + retval = -EINVAL; + goto out; + } + length = strnlen_user(p, 1024); + if (length <= 0) { + retval = -EFAULT; + break; + } + kfree(udev->dev->phys); + udev->dev->phys = phys = kmalloc(length, GFP_KERNEL); + if (!phys) { + retval = -ENOMEM; + break; + } + if (copy_from_user(phys, p, length)) { + udev->dev->phys = NULL; + kfree(phys); + retval = -EFAULT; + break; + } + 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.upload.effect)) { + retval = -EINVAL; + break; + } + ff_up.retval = 0; + memcpy(&ff_up.effect, req->u.upload.effect, sizeof(struct ff_effect)); + if (req->u.upload.old) + memcpy(&ff_up.old, req->u.upload.old, sizeof(struct ff_effect)); + else + memset(&ff_up.old, 0, 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.upload.effect)) { + retval = -EINVAL; + break; + } + req->retval = ff_up.retval; + uinput_request_done(udev, req); + 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; + uinput_request_done(udev, req); + break; + + default: + retval = -EINVAL; + } + + out: + mutex_unlock(&udev->mutex); + return retval; +} + +static const struct file_operations uinput_fops = { + .owner = THIS_MODULE, + .open = uinput_open, + .release = uinput_release, + .read = uinput_read, + .write = uinput_write, + .poll = uinput_poll, + .unlocked_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_VERSION("0.3"); + +module_init(uinput_init); +module_exit(uinput_exit); + diff --git a/drivers/input/misc/wistron_btns.c b/drivers/input/misc/wistron_btns.c new file mode 100644 index 0000000..7c8957d --- /dev/null +++ b/drivers/input/misc/wistron_btns.c @@ -0,0 +1,1418 @@ +/* + * Wistron laptop button driver + * Copyright (C) 2005 Miloslav Trmac <mitr@volny.cz> + * Copyright (C) 2005 Bernhard Rosenkraenzer <bero@arklinux.org> + * Copyright (C) 2005 Dmitry Torokhov <dtor@mail.ru> + * + * You can redistribute and/or modify this program under the terms of the + * GNU General Public License version 2 as published by the Free Software + * Foundation. + * + * 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/io.h> +#include <linux/dmi.h> +#include <linux/init.h> +#include <linux/input-polldev.h> +#include <linux/interrupt.h> +#include <linux/jiffies.h> +#include <linux/kernel.h> +#include <linux/mc146818rtc.h> +#include <linux/module.h> +#include <linux/preempt.h> +#include <linux/string.h> +#include <linux/types.h> +#include <linux/platform_device.h> +#include <linux/leds.h> + +/* How often we poll keys - msecs */ +#define POLL_INTERVAL_DEFAULT 500 /* when idle */ +#define POLL_INTERVAL_BURST 100 /* when a key was recently pressed */ + +/* BIOS subsystem IDs */ +#define WIFI 0x35 +#define BLUETOOTH 0x34 +#define MAIL_LED 0x31 + +MODULE_AUTHOR("Miloslav Trmac <mitr@volny.cz>"); +MODULE_DESCRIPTION("Wistron laptop button driver"); +MODULE_LICENSE("GPL v2"); +MODULE_VERSION("0.3"); + +static int force; /* = 0; */ +module_param(force, bool, 0); +MODULE_PARM_DESC(force, "Load even if computer is not in database"); + +static char *keymap_name; /* = NULL; */ +module_param_named(keymap, keymap_name, charp, 0); +MODULE_PARM_DESC(keymap, "Keymap name, if it can't be autodetected [generic, 1557/MS2141]"); + +static struct platform_device *wistron_device; + + /* BIOS interface implementation */ + +static void __iomem *bios_entry_point; /* BIOS routine entry point */ +static void __iomem *bios_code_map_base; +static void __iomem *bios_data_map_base; + +static u8 cmos_address; + +struct regs { + u32 eax, ebx, ecx; +}; + +static void call_bios(struct regs *regs) +{ + unsigned long flags; + + preempt_disable(); + local_irq_save(flags); + asm volatile ("pushl %%ebp;" + "movl %7, %%ebp;" + "call *%6;" + "popl %%ebp" + : "=a" (regs->eax), "=b" (regs->ebx), "=c" (regs->ecx) + : "0" (regs->eax), "1" (regs->ebx), "2" (regs->ecx), + "m" (bios_entry_point), "m" (bios_data_map_base) + : "edx", "edi", "esi", "memory"); + local_irq_restore(flags); + preempt_enable(); +} + +static ssize_t __init locate_wistron_bios(void __iomem *base) +{ + static unsigned char __initdata signature[] = + { 0x42, 0x21, 0x55, 0x30 }; + ssize_t offset; + + for (offset = 0; offset < 0x10000; offset += 0x10) { + if (check_signature(base + offset, signature, + sizeof(signature)) != 0) + return offset; + } + return -1; +} + +static int __init map_bios(void) +{ + void __iomem *base; + ssize_t offset; + u32 entry_point; + + base = ioremap(0xF0000, 0x10000); /* Can't fail */ + offset = locate_wistron_bios(base); + if (offset < 0) { + printk(KERN_ERR "wistron_btns: BIOS entry point not found\n"); + iounmap(base); + return -ENODEV; + } + + entry_point = readl(base + offset + 5); + printk(KERN_DEBUG + "wistron_btns: BIOS signature found at %p, entry point %08X\n", + base + offset, entry_point); + + if (entry_point >= 0xF0000) { + bios_code_map_base = base; + bios_entry_point = bios_code_map_base + (entry_point & 0xFFFF); + } else { + iounmap(base); + bios_code_map_base = ioremap(entry_point & ~0x3FFF, 0x4000); + if (bios_code_map_base == NULL) { + printk(KERN_ERR + "wistron_btns: Can't map BIOS code at %08X\n", + entry_point & ~0x3FFF); + goto err; + } + bios_entry_point = bios_code_map_base + (entry_point & 0x3FFF); + } + /* The Windows driver maps 0x10000 bytes, we keep only one page... */ + bios_data_map_base = ioremap(0x400, 0xc00); + if (bios_data_map_base == NULL) { + printk(KERN_ERR "wistron_btns: Can't map BIOS data\n"); + goto err_code; + } + return 0; + +err_code: + iounmap(bios_code_map_base); +err: + return -ENOMEM; +} + +static inline void unmap_bios(void) +{ + iounmap(bios_code_map_base); + iounmap(bios_data_map_base); +} + + /* BIOS calls */ + +static u16 bios_pop_queue(void) +{ + struct regs regs; + + memset(®s, 0, sizeof (regs)); + regs.eax = 0x9610; + regs.ebx = 0x061C; + regs.ecx = 0x0000; + call_bios(®s); + + return regs.eax; +} + +static void __devinit bios_attach(void) +{ + struct regs regs; + + memset(®s, 0, sizeof (regs)); + regs.eax = 0x9610; + regs.ebx = 0x012E; + call_bios(®s); +} + +static void bios_detach(void) +{ + struct regs regs; + + memset(®s, 0, sizeof (regs)); + regs.eax = 0x9610; + regs.ebx = 0x002E; + call_bios(®s); +} + +static u8 __devinit bios_get_cmos_address(void) +{ + struct regs regs; + + memset(®s, 0, sizeof (regs)); + regs.eax = 0x9610; + regs.ebx = 0x051C; + call_bios(®s); + + return regs.ecx; +} + +static u16 __devinit bios_get_default_setting(u8 subsys) +{ + struct regs regs; + + memset(®s, 0, sizeof (regs)); + regs.eax = 0x9610; + regs.ebx = 0x0200 | subsys; + call_bios(®s); + + return regs.eax; +} + +static void bios_set_state(u8 subsys, int enable) +{ + struct regs regs; + + memset(®s, 0, sizeof (regs)); + regs.eax = 0x9610; + regs.ebx = (enable ? 0x0100 : 0x0000) | subsys; + call_bios(®s); +} + +/* Hardware database */ + +struct key_entry { + char type; /* See KE_* below */ + u8 code; + union { + u16 keycode; /* For KE_KEY */ + struct { /* For KE_SW */ + u8 code; + u8 value; + } sw; + }; +}; + +enum { KE_END, KE_KEY, KE_SW, KE_WIFI, KE_BLUETOOTH }; + +#define FE_MAIL_LED 0x01 +#define FE_WIFI_LED 0x02 +#define FE_UNTESTED 0x80 + +static struct key_entry *keymap; /* = NULL; Current key map */ +static int have_wifi; +static int have_bluetooth; +static int have_leds; + +static int __init dmi_matched(const struct dmi_system_id *dmi) +{ + const struct key_entry *key; + + keymap = dmi->driver_data; + for (key = keymap; key->type != KE_END; key++) { + if (key->type == KE_WIFI) + have_wifi = 1; + else if (key->type == KE_BLUETOOTH) + have_bluetooth = 1; + } + have_leds = key->code & (FE_MAIL_LED | FE_WIFI_LED); + + return 1; +} + +static struct key_entry keymap_empty[] __initdata = { + { KE_END, 0 } +}; + +static struct key_entry keymap_fs_amilo_pro_v2000[] __initdata = { + { KE_KEY, 0x01, {KEY_HELP} }, + { KE_KEY, 0x11, {KEY_PROG1} }, + { KE_KEY, 0x12, {KEY_PROG2} }, + { KE_WIFI, 0x30 }, + { KE_KEY, 0x31, {KEY_MAIL} }, + { KE_KEY, 0x36, {KEY_WWW} }, + { KE_END, 0 } +}; + +static struct key_entry keymap_fs_amilo_pro_v3505[] __initdata = { + { KE_KEY, 0x01, {KEY_HELP} }, /* Fn+F1 */ + { KE_KEY, 0x06, {KEY_DISPLAYTOGGLE} }, /* Fn+F4 */ + { KE_BLUETOOTH, 0x30 }, /* Fn+F10 */ + { KE_KEY, 0x31, {KEY_MAIL} }, /* mail button */ + { KE_KEY, 0x36, {KEY_WWW} }, /* www button */ + { KE_WIFI, 0x78 }, /* satelite dish button */ + { KE_END, 0 } +}; + +static struct key_entry keymap_fujitsu_n3510[] __initdata = { + { KE_KEY, 0x11, {KEY_PROG1} }, + { KE_KEY, 0x12, {KEY_PROG2} }, + { KE_KEY, 0x36, {KEY_WWW} }, + { KE_KEY, 0x31, {KEY_MAIL} }, + { KE_KEY, 0x71, {KEY_STOPCD} }, + { KE_KEY, 0x72, {KEY_PLAYPAUSE} }, + { KE_KEY, 0x74, {KEY_REWIND} }, + { KE_KEY, 0x78, {KEY_FORWARD} }, + { KE_END, 0 } +}; + +static struct key_entry keymap_wistron_ms2111[] __initdata = { + { KE_KEY, 0x11, {KEY_PROG1} }, + { KE_KEY, 0x12, {KEY_PROG2} }, + { KE_KEY, 0x13, {KEY_PROG3} }, + { KE_KEY, 0x31, {KEY_MAIL} }, + { KE_KEY, 0x36, {KEY_WWW} }, + { KE_END, FE_MAIL_LED } +}; + +static struct key_entry keymap_wistron_md40100[] __initdata = { + { KE_KEY, 0x01, {KEY_HELP} }, + { KE_KEY, 0x02, {KEY_CONFIG} }, + { KE_KEY, 0x31, {KEY_MAIL} }, + { KE_KEY, 0x36, {KEY_WWW} }, + { KE_KEY, 0x37, {KEY_DISPLAYTOGGLE} }, /* Display on/off */ + { KE_END, FE_MAIL_LED | FE_WIFI_LED | FE_UNTESTED } +}; + +static struct key_entry keymap_wistron_ms2141[] __initdata = { + { KE_KEY, 0x11, {KEY_PROG1} }, + { KE_KEY, 0x12, {KEY_PROG2} }, + { KE_WIFI, 0x30 }, + { KE_KEY, 0x22, {KEY_REWIND} }, + { KE_KEY, 0x23, {KEY_FORWARD} }, + { KE_KEY, 0x24, {KEY_PLAYPAUSE} }, + { KE_KEY, 0x25, {KEY_STOPCD} }, + { KE_KEY, 0x31, {KEY_MAIL} }, + { KE_KEY, 0x36, {KEY_WWW} }, + { KE_END, 0 } +}; + +static struct key_entry keymap_acer_aspire_1500[] __initdata = { + { KE_KEY, 0x01, {KEY_HELP} }, + { KE_KEY, 0x03, {KEY_POWER} }, + { KE_KEY, 0x11, {KEY_PROG1} }, + { KE_KEY, 0x12, {KEY_PROG2} }, + { KE_WIFI, 0x30 }, + { KE_KEY, 0x31, {KEY_MAIL} }, + { KE_KEY, 0x36, {KEY_WWW} }, + { KE_KEY, 0x49, {KEY_CONFIG} }, + { KE_BLUETOOTH, 0x44 }, + { KE_END, FE_UNTESTED } +}; + +static struct key_entry keymap_acer_aspire_1600[] __initdata = { + { KE_KEY, 0x01, {KEY_HELP} }, + { KE_KEY, 0x03, {KEY_POWER} }, + { KE_KEY, 0x08, {KEY_MUTE} }, + { KE_KEY, 0x11, {KEY_PROG1} }, + { KE_KEY, 0x12, {KEY_PROG2} }, + { KE_KEY, 0x13, {KEY_PROG3} }, + { KE_KEY, 0x31, {KEY_MAIL} }, + { KE_KEY, 0x36, {KEY_WWW} }, + { KE_KEY, 0x49, {KEY_CONFIG} }, + { KE_WIFI, 0x30 }, + { KE_BLUETOOTH, 0x44 }, + { KE_END, FE_MAIL_LED | FE_UNTESTED } +}; + +/* 3020 has been tested */ +static struct key_entry keymap_acer_aspire_5020[] __initdata = { + { KE_KEY, 0x01, {KEY_HELP} }, + { KE_KEY, 0x03, {KEY_POWER} }, + { KE_KEY, 0x05, {KEY_SWITCHVIDEOMODE} }, /* Display selection */ + { KE_KEY, 0x11, {KEY_PROG1} }, + { KE_KEY, 0x12, {KEY_PROG2} }, + { KE_KEY, 0x31, {KEY_MAIL} }, + { KE_KEY, 0x36, {KEY_WWW} }, + { KE_KEY, 0x6a, {KEY_CONFIG} }, + { KE_WIFI, 0x30 }, + { KE_BLUETOOTH, 0x44 }, + { KE_END, FE_MAIL_LED | FE_UNTESTED } +}; + +static struct key_entry keymap_acer_travelmate_2410[] __initdata = { + { KE_KEY, 0x01, {KEY_HELP} }, + { KE_KEY, 0x6d, {KEY_POWER} }, + { KE_KEY, 0x11, {KEY_PROG1} }, + { KE_KEY, 0x12, {KEY_PROG2} }, + { KE_KEY, 0x31, {KEY_MAIL} }, + { KE_KEY, 0x36, {KEY_WWW} }, + { KE_KEY, 0x6a, {KEY_CONFIG} }, + { KE_WIFI, 0x30 }, + { KE_BLUETOOTH, 0x44 }, + { KE_END, FE_MAIL_LED | FE_UNTESTED } +}; + +static struct key_entry keymap_acer_travelmate_110[] __initdata = { + { KE_KEY, 0x01, {KEY_HELP} }, + { KE_KEY, 0x02, {KEY_CONFIG} }, + { KE_KEY, 0x03, {KEY_POWER} }, + { KE_KEY, 0x08, {KEY_MUTE} }, + { KE_KEY, 0x11, {KEY_PROG1} }, + { KE_KEY, 0x12, {KEY_PROG2} }, + { KE_KEY, 0x20, {KEY_VOLUMEUP} }, + { KE_KEY, 0x21, {KEY_VOLUMEDOWN} }, + { KE_KEY, 0x31, {KEY_MAIL} }, + { KE_KEY, 0x36, {KEY_WWW} }, + { KE_SW, 0x4a, {.sw = {SW_LID, 1}} }, /* lid close */ + { KE_SW, 0x4b, {.sw = {SW_LID, 0}} }, /* lid open */ + { KE_WIFI, 0x30 }, + { KE_END, FE_MAIL_LED | FE_UNTESTED } +}; + +static struct key_entry keymap_acer_travelmate_300[] __initdata = { + { KE_KEY, 0x01, {KEY_HELP} }, + { KE_KEY, 0x02, {KEY_CONFIG} }, + { KE_KEY, 0x03, {KEY_POWER} }, + { KE_KEY, 0x08, {KEY_MUTE} }, + { KE_KEY, 0x11, {KEY_PROG1} }, + { KE_KEY, 0x12, {KEY_PROG2} }, + { KE_KEY, 0x20, {KEY_VOLUMEUP} }, + { KE_KEY, 0x21, {KEY_VOLUMEDOWN} }, + { KE_KEY, 0x31, {KEY_MAIL} }, + { KE_KEY, 0x36, {KEY_WWW} }, + { KE_WIFI, 0x30 }, + { KE_BLUETOOTH, 0x44 }, + { KE_END, FE_MAIL_LED | FE_UNTESTED } +}; + +static struct key_entry keymap_acer_travelmate_380[] __initdata = { + { KE_KEY, 0x01, {KEY_HELP} }, + { KE_KEY, 0x02, {KEY_CONFIG} }, + { KE_KEY, 0x03, {KEY_POWER} }, /* not 370 */ + { KE_KEY, 0x11, {KEY_PROG1} }, + { KE_KEY, 0x12, {KEY_PROG2} }, + { KE_KEY, 0x13, {KEY_PROG3} }, + { KE_KEY, 0x31, {KEY_MAIL} }, + { KE_KEY, 0x36, {KEY_WWW} }, + { KE_WIFI, 0x30 }, + { KE_END, FE_MAIL_LED | FE_UNTESTED } +}; + +/* unusual map */ +static struct key_entry keymap_acer_travelmate_220[] __initdata = { + { KE_KEY, 0x01, {KEY_HELP} }, + { KE_KEY, 0x02, {KEY_CONFIG} }, + { KE_KEY, 0x11, {KEY_MAIL} }, + { KE_KEY, 0x12, {KEY_WWW} }, + { KE_KEY, 0x13, {KEY_PROG2} }, + { KE_KEY, 0x31, {KEY_PROG1} }, + { KE_END, FE_WIFI_LED | FE_UNTESTED } +}; + +static struct key_entry keymap_acer_travelmate_230[] __initdata = { + { KE_KEY, 0x01, {KEY_HELP} }, + { KE_KEY, 0x02, {KEY_CONFIG} }, + { KE_KEY, 0x11, {KEY_PROG1} }, + { KE_KEY, 0x12, {KEY_PROG2} }, + { KE_KEY, 0x31, {KEY_MAIL} }, + { KE_KEY, 0x36, {KEY_WWW} }, + { KE_END, FE_WIFI_LED | FE_UNTESTED } +}; + +static struct key_entry keymap_acer_travelmate_240[] __initdata = { + { KE_KEY, 0x01, {KEY_HELP} }, + { KE_KEY, 0x02, {KEY_CONFIG} }, + { KE_KEY, 0x03, {KEY_POWER} }, + { KE_KEY, 0x08, {KEY_MUTE} }, + { KE_KEY, 0x31, {KEY_MAIL} }, + { KE_KEY, 0x36, {KEY_WWW} }, + { KE_KEY, 0x11, {KEY_PROG1} }, + { KE_KEY, 0x12, {KEY_PROG2} }, + { KE_BLUETOOTH, 0x44 }, + { KE_WIFI, 0x30 }, + { KE_END, FE_UNTESTED } +}; + +static struct key_entry keymap_acer_travelmate_350[] __initdata = { + { KE_KEY, 0x01, {KEY_HELP} }, + { KE_KEY, 0x02, {KEY_CONFIG} }, + { KE_KEY, 0x11, {KEY_PROG1} }, + { KE_KEY, 0x12, {KEY_PROG2} }, + { KE_KEY, 0x13, {KEY_MAIL} }, + { KE_KEY, 0x14, {KEY_PROG3} }, + { KE_KEY, 0x15, {KEY_WWW} }, + { KE_END, FE_MAIL_LED | FE_WIFI_LED | FE_UNTESTED } +}; + +static struct key_entry keymap_acer_travelmate_360[] __initdata = { + { KE_KEY, 0x01, {KEY_HELP} }, + { KE_KEY, 0x02, {KEY_CONFIG} }, + { KE_KEY, 0x11, {KEY_PROG1} }, + { KE_KEY, 0x12, {KEY_PROG2} }, + { KE_KEY, 0x13, {KEY_MAIL} }, + { KE_KEY, 0x14, {KEY_PROG3} }, + { KE_KEY, 0x15, {KEY_WWW} }, + { KE_KEY, 0x40, {KEY_WLAN} }, + { KE_END, FE_WIFI_LED | FE_UNTESTED } /* no mail led */ +}; + +/* Wifi subsystem only activates the led. Therefore we need to pass + * wifi event as a normal key, then userspace can really change the wifi state. + * TODO we need to export led state to userspace (wifi and mail) */ +static struct key_entry keymap_acer_travelmate_610[] __initdata = { + { KE_KEY, 0x01, {KEY_HELP} }, + { KE_KEY, 0x02, {KEY_CONFIG} }, + { KE_KEY, 0x11, {KEY_PROG1} }, + { KE_KEY, 0x12, {KEY_PROG2} }, + { KE_KEY, 0x13, {KEY_PROG3} }, + { KE_KEY, 0x14, {KEY_MAIL} }, + { KE_KEY, 0x15, {KEY_WWW} }, + { KE_KEY, 0x40, {KEY_WLAN} }, + { KE_END, FE_MAIL_LED | FE_WIFI_LED } +}; + +static struct key_entry keymap_acer_travelmate_630[] __initdata = { + { KE_KEY, 0x01, {KEY_HELP} }, + { KE_KEY, 0x02, {KEY_CONFIG} }, + { KE_KEY, 0x03, {KEY_POWER} }, + { KE_KEY, 0x08, {KEY_MUTE} }, /* not 620 */ + { KE_KEY, 0x11, {KEY_PROG1} }, + { KE_KEY, 0x12, {KEY_PROG2} }, + { KE_KEY, 0x13, {KEY_PROG3} }, + { KE_KEY, 0x20, {KEY_VOLUMEUP} }, + { KE_KEY, 0x21, {KEY_VOLUMEDOWN} }, + { KE_KEY, 0x31, {KEY_MAIL} }, + { KE_KEY, 0x36, {KEY_WWW} }, + { KE_WIFI, 0x30 }, + { KE_END, FE_MAIL_LED | FE_UNTESTED } +}; + +static struct key_entry keymap_aopen_1559as[] __initdata = { + { KE_KEY, 0x01, {KEY_HELP} }, + { KE_KEY, 0x06, {KEY_PROG3} }, + { KE_KEY, 0x11, {KEY_PROG1} }, + { KE_KEY, 0x12, {KEY_PROG2} }, + { KE_WIFI, 0x30 }, + { KE_KEY, 0x31, {KEY_MAIL} }, + { KE_KEY, 0x36, {KEY_WWW} }, + { KE_END, 0 }, +}; + +static struct key_entry keymap_fs_amilo_d88x0[] __initdata = { + { KE_KEY, 0x01, {KEY_HELP} }, + { KE_KEY, 0x08, {KEY_MUTE} }, + { KE_KEY, 0x31, {KEY_MAIL} }, + { KE_KEY, 0x36, {KEY_WWW} }, + { KE_KEY, 0x11, {KEY_PROG1} }, + { KE_KEY, 0x12, {KEY_PROG2} }, + { KE_KEY, 0x13, {KEY_PROG3} }, + { KE_END, FE_MAIL_LED | FE_WIFI_LED | FE_UNTESTED } +}; + +static struct key_entry keymap_wistron_md2900[] __initdata = { + { KE_KEY, 0x01, {KEY_HELP} }, + { KE_KEY, 0x02, {KEY_CONFIG} }, + { KE_KEY, 0x11, {KEY_PROG1} }, + { KE_KEY, 0x12, {KEY_PROG2} }, + { KE_KEY, 0x31, {KEY_MAIL} }, + { KE_KEY, 0x36, {KEY_WWW} }, + { KE_WIFI, 0x30 }, + { KE_END, FE_MAIL_LED | FE_UNTESTED } +}; + +static struct key_entry keymap_wistron_md96500[] __initdata = { + { KE_KEY, 0x01, {KEY_HELP} }, + { KE_KEY, 0x02, {KEY_CONFIG} }, + { KE_KEY, 0x05, {KEY_SWITCHVIDEOMODE} }, /* Display selection */ + { KE_KEY, 0x06, {KEY_DISPLAYTOGGLE} }, /* Display on/off */ + { KE_KEY, 0x08, {KEY_MUTE} }, + { KE_KEY, 0x11, {KEY_PROG1} }, + { KE_KEY, 0x12, {KEY_PROG2} }, + { KE_KEY, 0x20, {KEY_VOLUMEUP} }, + { KE_KEY, 0x21, {KEY_VOLUMEDOWN} }, + { KE_KEY, 0x22, {KEY_REWIND} }, + { KE_KEY, 0x23, {KEY_FORWARD} }, + { KE_KEY, 0x24, {KEY_PLAYPAUSE} }, + { KE_KEY, 0x25, {KEY_STOPCD} }, + { KE_KEY, 0x31, {KEY_MAIL} }, + { KE_KEY, 0x36, {KEY_WWW} }, + { KE_WIFI, 0x30 }, + { KE_BLUETOOTH, 0x44 }, + { KE_END, FE_UNTESTED } +}; + +static struct key_entry keymap_wistron_generic[] __initdata = { + { KE_KEY, 0x01, {KEY_HELP} }, + { KE_KEY, 0x02, {KEY_CONFIG} }, + { KE_KEY, 0x03, {KEY_POWER} }, + { KE_KEY, 0x05, {KEY_SWITCHVIDEOMODE} }, /* Display selection */ + { KE_KEY, 0x06, {KEY_DISPLAYTOGGLE} }, /* Display on/off */ + { KE_KEY, 0x08, {KEY_MUTE} }, + { KE_KEY, 0x11, {KEY_PROG1} }, + { KE_KEY, 0x12, {KEY_PROG2} }, + { KE_KEY, 0x13, {KEY_PROG3} }, + { KE_KEY, 0x14, {KEY_MAIL} }, + { KE_KEY, 0x15, {KEY_WWW} }, + { KE_KEY, 0x20, {KEY_VOLUMEUP} }, + { KE_KEY, 0x21, {KEY_VOLUMEDOWN} }, + { KE_KEY, 0x22, {KEY_REWIND} }, + { KE_KEY, 0x23, {KEY_FORWARD} }, + { KE_KEY, 0x24, {KEY_PLAYPAUSE} }, + { KE_KEY, 0x25, {KEY_STOPCD} }, + { KE_KEY, 0x31, {KEY_MAIL} }, + { KE_KEY, 0x36, {KEY_WWW} }, + { KE_KEY, 0x37, {KEY_DISPLAYTOGGLE} }, /* Display on/off */ + { KE_KEY, 0x40, {KEY_WLAN} }, + { KE_KEY, 0x49, {KEY_CONFIG} }, + { KE_SW, 0x4a, {.sw = {SW_LID, 1}} }, /* lid close */ + { KE_SW, 0x4b, {.sw = {SW_LID, 0}} }, /* lid open */ + { KE_KEY, 0x6a, {KEY_CONFIG} }, + { KE_KEY, 0x6d, {KEY_POWER} }, + { KE_KEY, 0x71, {KEY_STOPCD} }, + { KE_KEY, 0x72, {KEY_PLAYPAUSE} }, + { KE_KEY, 0x74, {KEY_REWIND} }, + { KE_KEY, 0x78, {KEY_FORWARD} }, + { KE_WIFI, 0x30 }, + { KE_BLUETOOTH, 0x44 }, + { KE_END, 0 } +}; + +/* + * If your machine is not here (which is currently rather likely), please send + * a list of buttons and their key codes (reported when loading this module + * with force=1) and the output of dmidecode to $MODULE_AUTHOR. + */ +static struct dmi_system_id dmi_ids[] __initdata = { + { + .callback = dmi_matched, + .ident = "Fujitsu-Siemens Amilo Pro V2000", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"), + DMI_MATCH(DMI_PRODUCT_NAME, "AMILO Pro V2000"), + }, + .driver_data = keymap_fs_amilo_pro_v2000 + }, + { + .callback = dmi_matched, + .ident = "Fujitsu-Siemens Amilo Pro Edition V3505", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"), + DMI_MATCH(DMI_PRODUCT_NAME, "AMILO Pro Edition V3505"), + }, + .driver_data = keymap_fs_amilo_pro_v3505 + }, + { + .callback = dmi_matched, + .ident = "Fujitsu-Siemens Amilo M7400", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"), + DMI_MATCH(DMI_PRODUCT_NAME, "AMILO M "), + }, + .driver_data = keymap_fs_amilo_pro_v2000 + }, + { + .callback = dmi_matched, + .ident = "Fujitsu N3510", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"), + DMI_MATCH(DMI_PRODUCT_NAME, "N3510"), + }, + .driver_data = keymap_fujitsu_n3510 + }, + { + .callback = dmi_matched, + .ident = "Acer Aspire 1500", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 1500"), + }, + .driver_data = keymap_acer_aspire_1500 + }, + { + .callback = dmi_matched, + .ident = "Acer Aspire 1600", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 1600"), + }, + .driver_data = keymap_acer_aspire_1600 + }, + { + .callback = dmi_matched, + .ident = "Acer Aspire 3020", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 3020"), + }, + .driver_data = keymap_acer_aspire_5020 + }, + { + .callback = dmi_matched, + .ident = "Acer Aspire 5020", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 5020"), + }, + .driver_data = keymap_acer_aspire_5020 + }, + { + .callback = dmi_matched, + .ident = "Acer TravelMate 2100", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate 2100"), + }, + .driver_data = keymap_acer_aspire_5020 + }, + { + .callback = dmi_matched, + .ident = "Acer TravelMate 2410", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate 2410"), + }, + .driver_data = keymap_acer_travelmate_2410 + }, + { + .callback = dmi_matched, + .ident = "Acer TravelMate C300", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate C300"), + }, + .driver_data = keymap_acer_travelmate_300 + }, + { + .callback = dmi_matched, + .ident = "Acer TravelMate C100", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate C100"), + }, + .driver_data = keymap_acer_travelmate_300 + }, + { + .callback = dmi_matched, + .ident = "Acer TravelMate C110", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate C110"), + }, + .driver_data = keymap_acer_travelmate_110 + }, + { + .callback = dmi_matched, + .ident = "Acer TravelMate 380", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate 380"), + }, + .driver_data = keymap_acer_travelmate_380 + }, + { + .callback = dmi_matched, + .ident = "Acer TravelMate 370", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate 370"), + }, + .driver_data = keymap_acer_travelmate_380 /* keyboard minus 1 key */ + }, + { + .callback = dmi_matched, + .ident = "Acer TravelMate 220", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate 220"), + }, + .driver_data = keymap_acer_travelmate_220 + }, + { + .callback = dmi_matched, + .ident = "Acer TravelMate 260", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate 260"), + }, + .driver_data = keymap_acer_travelmate_220 + }, + { + .callback = dmi_matched, + .ident = "Acer TravelMate 230", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate 230"), + /* acerhk looks for "TravelMate F4..." ?! */ + }, + .driver_data = keymap_acer_travelmate_230 + }, + { + .callback = dmi_matched, + .ident = "Acer TravelMate 280", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate 280"), + }, + .driver_data = keymap_acer_travelmate_230 + }, + { + .callback = dmi_matched, + .ident = "Acer TravelMate 240", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate 240"), + }, + .driver_data = keymap_acer_travelmate_240 + }, + { + .callback = dmi_matched, + .ident = "Acer TravelMate 250", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate 250"), + }, + .driver_data = keymap_acer_travelmate_240 + }, + { + .callback = dmi_matched, + .ident = "Acer TravelMate 2424NWXCi", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate 2420"), + }, + .driver_data = keymap_acer_travelmate_240 + }, + { + .callback = dmi_matched, + .ident = "Acer TravelMate 350", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate 350"), + }, + .driver_data = keymap_acer_travelmate_350 + }, + { + .callback = dmi_matched, + .ident = "Acer TravelMate 360", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate 360"), + }, + .driver_data = keymap_acer_travelmate_360 + }, + { + .callback = dmi_matched, + .ident = "Acer TravelMate 610", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "ACER"), + DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate 610"), + }, + .driver_data = keymap_acer_travelmate_610 + }, + { + .callback = dmi_matched, + .ident = "Acer TravelMate 620", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate 620"), + }, + .driver_data = keymap_acer_travelmate_630 + }, + { + .callback = dmi_matched, + .ident = "Acer TravelMate 630", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate 630"), + }, + .driver_data = keymap_acer_travelmate_630 + }, + { + .callback = dmi_matched, + .ident = "AOpen 1559AS", + .matches = { + DMI_MATCH(DMI_PRODUCT_NAME, "E2U"), + DMI_MATCH(DMI_BOARD_NAME, "E2U"), + }, + .driver_data = keymap_aopen_1559as + }, + { + .callback = dmi_matched, + .ident = "Medion MD 9783", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "MEDIONNB"), + DMI_MATCH(DMI_PRODUCT_NAME, "MD 9783"), + }, + .driver_data = keymap_wistron_ms2111 + }, + { + .callback = dmi_matched, + .ident = "Medion MD 40100", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "MEDIONNB"), + DMI_MATCH(DMI_PRODUCT_NAME, "WID2000"), + }, + .driver_data = keymap_wistron_md40100 + }, + { + .callback = dmi_matched, + .ident = "Medion MD 2900", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "MEDIONNB"), + DMI_MATCH(DMI_PRODUCT_NAME, "WIM 2000"), + }, + .driver_data = keymap_wistron_md2900 + }, + { + .callback = dmi_matched, + .ident = "Medion MD 96500", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "MEDIONPC"), + DMI_MATCH(DMI_PRODUCT_NAME, "WIM 2040"), + }, + .driver_data = keymap_wistron_md96500 + }, + { + .callback = dmi_matched, + .ident = "Medion MD 95400", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "MEDIONPC"), + DMI_MATCH(DMI_PRODUCT_NAME, "WIM 2050"), + }, + .driver_data = keymap_wistron_md96500 + }, + { + .callback = dmi_matched, + .ident = "Fujitsu Siemens Amilo D7820", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"), /* not sure */ + DMI_MATCH(DMI_PRODUCT_NAME, "Amilo D"), + }, + .driver_data = keymap_fs_amilo_d88x0 + }, + { + .callback = dmi_matched, + .ident = "Fujitsu Siemens Amilo D88x0", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"), + DMI_MATCH(DMI_PRODUCT_NAME, "AMILO D"), + }, + .driver_data = keymap_fs_amilo_d88x0 + }, + { NULL, } +}; + +/* Copy the good keymap, as the original ones are free'd */ +static int __init copy_keymap(void) +{ + const struct key_entry *key; + struct key_entry *new_keymap; + unsigned int length = 1; + + for (key = keymap; key->type != KE_END; key++) + length++; + + new_keymap = kmalloc(length * sizeof(struct key_entry), GFP_KERNEL); + if (!new_keymap) + return -ENOMEM; + + memcpy(new_keymap, keymap, length * sizeof(struct key_entry)); + keymap = new_keymap; + + return 0; +} + +static int __init select_keymap(void) +{ + dmi_check_system(dmi_ids); + if (keymap_name != NULL) { + if (strcmp (keymap_name, "1557/MS2141") == 0) + keymap = keymap_wistron_ms2141; + else if (strcmp (keymap_name, "generic") == 0) + keymap = keymap_wistron_generic; + else { + printk(KERN_ERR "wistron_btns: Keymap unknown\n"); + return -EINVAL; + } + } + if (keymap == NULL) { + if (!force) { + printk(KERN_ERR "wistron_btns: System unknown\n"); + return -ENODEV; + } + keymap = keymap_empty; + } + + return copy_keymap(); +} + + /* Input layer interface */ + +static struct input_polled_dev *wistron_idev; +static unsigned long jiffies_last_press; +static int wifi_enabled; +static int bluetooth_enabled; + +static void report_key(struct input_dev *dev, unsigned int keycode) +{ + input_report_key(dev, keycode, 1); + input_sync(dev); + input_report_key(dev, keycode, 0); + input_sync(dev); +} + +static void report_switch(struct input_dev *dev, unsigned int code, int value) +{ + input_report_switch(dev, code, value); + input_sync(dev); +} + + + /* led management */ +static void wistron_mail_led_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + bios_set_state(MAIL_LED, (value != LED_OFF) ? 1 : 0); +} + +/* same as setting up wifi card, but for laptops on which the led is managed */ +static void wistron_wifi_led_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + bios_set_state(WIFI, (value != LED_OFF) ? 1 : 0); +} + +static struct led_classdev wistron_mail_led = { + .name = "wistron:green:mail", + .brightness_set = wistron_mail_led_set, +}; + +static struct led_classdev wistron_wifi_led = { + .name = "wistron:red:wifi", + .brightness_set = wistron_wifi_led_set, +}; + +static void __devinit wistron_led_init(struct device *parent) +{ + if (have_leds & FE_WIFI_LED) { + u16 wifi = bios_get_default_setting(WIFI); + if (wifi & 1) { + wistron_wifi_led.brightness = (wifi & 2) ? LED_FULL : LED_OFF; + if (led_classdev_register(parent, &wistron_wifi_led)) + have_leds &= ~FE_WIFI_LED; + else + bios_set_state(WIFI, wistron_wifi_led.brightness); + + } else + have_leds &= ~FE_WIFI_LED; + } + + if (have_leds & FE_MAIL_LED) { + /* bios_get_default_setting(MAIL) always retuns 0, so just turn the led off */ + wistron_mail_led.brightness = LED_OFF; + if (led_classdev_register(parent, &wistron_mail_led)) + have_leds &= ~FE_MAIL_LED; + else + bios_set_state(MAIL_LED, wistron_mail_led.brightness); + } +} + +static void __devexit wistron_led_remove(void) +{ + if (have_leds & FE_MAIL_LED) + led_classdev_unregister(&wistron_mail_led); + + if (have_leds & FE_WIFI_LED) + led_classdev_unregister(&wistron_wifi_led); +} + +static inline void wistron_led_suspend(void) +{ + if (have_leds & FE_MAIL_LED) + led_classdev_suspend(&wistron_mail_led); + + if (have_leds & FE_WIFI_LED) + led_classdev_suspend(&wistron_wifi_led); +} + +static inline void wistron_led_resume(void) +{ + if (have_leds & FE_MAIL_LED) + led_classdev_resume(&wistron_mail_led); + + if (have_leds & FE_WIFI_LED) + led_classdev_resume(&wistron_wifi_led); +} + +static struct key_entry *wistron_get_entry_by_scancode(int code) +{ + struct key_entry *key; + + for (key = keymap; key->type != KE_END; key++) + if (code == key->code) + return key; + + return NULL; +} + +static struct key_entry *wistron_get_entry_by_keycode(int keycode) +{ + struct key_entry *key; + + for (key = keymap; key->type != KE_END; key++) + if (key->type == KE_KEY && keycode == key->keycode) + return key; + + return NULL; +} + +static void handle_key(u8 code) +{ + const struct key_entry *key = wistron_get_entry_by_scancode(code); + + if (key) { + switch (key->type) { + case KE_KEY: + report_key(wistron_idev->input, key->keycode); + break; + + case KE_SW: + report_switch(wistron_idev->input, + key->sw.code, key->sw.value); + break; + + case KE_WIFI: + if (have_wifi) { + wifi_enabled = !wifi_enabled; + bios_set_state(WIFI, wifi_enabled); + } + break; + + case KE_BLUETOOTH: + if (have_bluetooth) { + bluetooth_enabled = !bluetooth_enabled; + bios_set_state(BLUETOOTH, bluetooth_enabled); + } + break; + + default: + BUG(); + } + jiffies_last_press = jiffies; + } else + printk(KERN_NOTICE + "wistron_btns: Unknown key code %02X\n", code); +} + +static void poll_bios(bool discard) +{ + u8 qlen; + u16 val; + + for (;;) { + qlen = CMOS_READ(cmos_address); + if (qlen == 0) + break; + val = bios_pop_queue(); + if (val != 0 && !discard) + handle_key((u8)val); + } +} + +static void wistron_flush(struct input_polled_dev *dev) +{ + /* Flush stale event queue */ + poll_bios(true); +} + +static void wistron_poll(struct input_polled_dev *dev) +{ + poll_bios(false); + + /* Increase poll frequency if user is currently pressing keys (< 2s ago) */ + if (time_before(jiffies, jiffies_last_press + 2 * HZ)) + dev->poll_interval = POLL_INTERVAL_BURST; + else + dev->poll_interval = POLL_INTERVAL_DEFAULT; +} + +static int wistron_getkeycode(struct input_dev *dev, int scancode, int *keycode) +{ + const struct key_entry *key = wistron_get_entry_by_scancode(scancode); + + if (key && key->type == KE_KEY) { + *keycode = key->keycode; + return 0; + } + + return -EINVAL; +} + +static int wistron_setkeycode(struct input_dev *dev, int scancode, int keycode) +{ + struct key_entry *key; + int old_keycode; + + if (keycode < 0 || keycode > KEY_MAX) + return -EINVAL; + + key = wistron_get_entry_by_scancode(scancode); + if (key && key->type == KE_KEY) { + old_keycode = key->keycode; + key->keycode = keycode; + set_bit(keycode, dev->keybit); + if (!wistron_get_entry_by_keycode(old_keycode)) + clear_bit(old_keycode, dev->keybit); + return 0; + } + + return -EINVAL; +} + +static int __devinit setup_input_dev(void) +{ + struct key_entry *key; + struct input_dev *input_dev; + int error; + + wistron_idev = input_allocate_polled_device(); + if (!wistron_idev) + return -ENOMEM; + + wistron_idev->flush = wistron_flush; + wistron_idev->poll = wistron_poll; + wistron_idev->poll_interval = POLL_INTERVAL_DEFAULT; + + input_dev = wistron_idev->input; + input_dev->name = "Wistron laptop buttons"; + input_dev->phys = "wistron/input0"; + input_dev->id.bustype = BUS_HOST; + input_dev->dev.parent = &wistron_device->dev; + + input_dev->getkeycode = wistron_getkeycode; + input_dev->setkeycode = wistron_setkeycode; + + for (key = keymap; key->type != KE_END; key++) { + switch (key->type) { + case KE_KEY: + set_bit(EV_KEY, input_dev->evbit); + set_bit(key->keycode, input_dev->keybit); + break; + + case KE_SW: + set_bit(EV_SW, input_dev->evbit); + set_bit(key->sw.code, input_dev->swbit); + break; + + /* if wifi or bluetooth are not available, create normal keys */ + case KE_WIFI: + if (!have_wifi) { + key->type = KE_KEY; + key->keycode = KEY_WLAN; + key--; + } + break; + + case KE_BLUETOOTH: + if (!have_bluetooth) { + key->type = KE_KEY; + key->keycode = KEY_BLUETOOTH; + key--; + } + break; + + default: + break; + } + } + + /* reads information flags on KE_END */ + if (key->code & FE_UNTESTED) + printk(KERN_WARNING "Untested laptop multimedia keys, " + "please report success or failure to eric.piel" + "@tremplin-utc.net\n"); + + error = input_register_polled_device(wistron_idev); + if (error) { + input_free_polled_device(wistron_idev); + return error; + } + + return 0; +} + +/* Driver core */ + +static int __devinit wistron_probe(struct platform_device *dev) +{ + int err; + + bios_attach(); + cmos_address = bios_get_cmos_address(); + + if (have_wifi) { + u16 wifi = bios_get_default_setting(WIFI); + if (wifi & 1) + wifi_enabled = (wifi & 2) ? 1 : 0; + else + have_wifi = 0; + + if (have_wifi) + bios_set_state(WIFI, wifi_enabled); + } + + if (have_bluetooth) { + u16 bt = bios_get_default_setting(BLUETOOTH); + if (bt & 1) + bluetooth_enabled = (bt & 2) ? 1 : 0; + else + have_bluetooth = 0; + + if (have_bluetooth) + bios_set_state(BLUETOOTH, bluetooth_enabled); + } + + wistron_led_init(&dev->dev); + err = setup_input_dev(); + if (err) { + bios_detach(); + return err; + } + + return 0; +} + +static int __devexit wistron_remove(struct platform_device *dev) +{ + wistron_led_remove(); + input_unregister_polled_device(wistron_idev); + input_free_polled_device(wistron_idev); + bios_detach(); + + return 0; +} + +#ifdef CONFIG_PM +static int wistron_suspend(struct platform_device *dev, pm_message_t state) +{ + if (have_wifi) + bios_set_state(WIFI, 0); + + if (have_bluetooth) + bios_set_state(BLUETOOTH, 0); + + wistron_led_suspend(); + return 0; +} + +static int wistron_resume(struct platform_device *dev) +{ + if (have_wifi) + bios_set_state(WIFI, wifi_enabled); + + if (have_bluetooth) + bios_set_state(BLUETOOTH, bluetooth_enabled); + + wistron_led_resume(); + poll_bios(true); + + return 0; +} +#else +#define wistron_suspend NULL +#define wistron_resume NULL +#endif + +static struct platform_driver wistron_driver = { + .driver = { + .name = "wistron-bios", + .owner = THIS_MODULE, + }, + .probe = wistron_probe, + .remove = __devexit_p(wistron_remove), + .suspend = wistron_suspend, + .resume = wistron_resume, +}; + +static int __init wb_module_init(void) +{ + int err; + + err = select_keymap(); + if (err) + return err; + + err = map_bios(); + if (err) + return err; + + err = platform_driver_register(&wistron_driver); + if (err) + goto err_unmap_bios; + + wistron_device = platform_device_alloc("wistron-bios", -1); + if (!wistron_device) { + err = -ENOMEM; + goto err_unregister_driver; + } + + err = platform_device_add(wistron_device); + if (err) + goto err_free_device; + + return 0; + + err_free_device: + platform_device_put(wistron_device); + err_unregister_driver: + platform_driver_unregister(&wistron_driver); + err_unmap_bios: + unmap_bios(); + + return err; +} + +static void __exit wb_module_exit(void) +{ + platform_device_unregister(wistron_device); + platform_driver_unregister(&wistron_driver); + unmap_bios(); + kfree(keymap); +} + +module_init(wb_module_init); +module_exit(wb_module_exit); diff --git a/drivers/input/misc/yealink.c b/drivers/input/misc/yealink.c new file mode 100644 index 0000000..93a22ac --- /dev/null +++ b/drivers/input/misc/yealink.c @@ -0,0 +1,1019 @@ +/* + * drivers/usb/input/yealink.c + * + * Copyright (c) 2005 Henk Vergonet <Henk.Vergonet@gmail.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 + */ +/* + * Description: + * Driver for the USB-P1K voip usb phone. + * This device is produced by Yealink Network Technology Co Ltd + * but may be branded under several names: + * - Yealink usb-p1k + * - Tiptel 115 + * - ... + * + * This driver is based on: + * - the usbb2k-api http://savannah.nongnu.org/projects/usbb2k-api/ + * - information from http://memeteau.free.fr/usbb2k + * - the xpad-driver drivers/input/joystick/xpad.c + * + * Thanks to: + * - Olivier Vandorpe, for providing the usbb2k-api. + * - Martin Diehl, for spotting my memory allocation bug. + * + * History: + * 20050527 henk First version, functional keyboard. Keyboard events + * will pop-up on the ../input/eventX bus. + * 20050531 henk Added led, LCD, dialtone and sysfs interface. + * 20050610 henk Cleanups, make it ready for public consumption. + * 20050630 henk Cleanups, fixes in response to comments. + * 20050701 henk sysfs write serialisation, fix potential unload races + * 20050801 henk Added ringtone, restructure USB + * 20050816 henk Merge 2.6.13-rc6 + */ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/rwsem.h> +#include <linux/usb/input.h> +#include <linux/map_to_7segment.h> + +#include "yealink.h" + +#define DRIVER_VERSION "yld-20051230" +#define DRIVER_AUTHOR "Henk Vergonet" +#define DRIVER_DESC "Yealink phone driver" + +#define YEALINK_POLLING_FREQUENCY 10 /* in [Hz] */ + +struct yld_status { + u8 lcd[24]; + u8 led; + u8 dialtone; + u8 ringtone; + u8 keynum; +} __attribute__ ((packed)); + +/* + * Register the LCD segment and icon map + */ +#define _LOC(k,l) { .a = (k), .m = (l) } +#define _SEG(t, a, am, b, bm, c, cm, d, dm, e, em, f, fm, g, gm) \ + { .type = (t), \ + .u = { .s = { _LOC(a, am), _LOC(b, bm), _LOC(c, cm), \ + _LOC(d, dm), _LOC(e, em), _LOC(g, gm), \ + _LOC(f, fm) } } } +#define _PIC(t, h, hm, n) \ + { .type = (t), \ + .u = { .p = { .name = (n), .a = (h), .m = (hm) } } } + +static const struct lcd_segment_map { + char type; + union { + struct pictogram_map { + u8 a,m; + char name[10]; + } p; + struct segment_map { + u8 a,m; + } s[7]; + } u; +} lcdMap[] = { +#include "yealink.h" +}; + +struct yealink_dev { + struct input_dev *idev; /* input device */ + struct usb_device *udev; /* usb device */ + + /* irq input channel */ + struct yld_ctl_packet *irq_data; + dma_addr_t irq_dma; + struct urb *urb_irq; + + /* control output channel */ + struct yld_ctl_packet *ctl_data; + dma_addr_t ctl_dma; + struct usb_ctrlrequest *ctl_req; + dma_addr_t ctl_req_dma; + struct urb *urb_ctl; + + char phys[64]; /* physical device path */ + + u8 lcdMap[ARRAY_SIZE(lcdMap)]; /* state of LCD, LED ... */ + int key_code; /* last reported key */ + + unsigned int shutdown:1; + + int stat_ix; + union { + struct yld_status s; + u8 b[sizeof(struct yld_status)]; + } master, copy; +}; + + +/******************************************************************************* + * Yealink lcd interface + ******************************************************************************/ + +/* + * Register a default 7 segment character set + */ +static SEG7_DEFAULT_MAP(map_seg7); + + /* Display a char, + * char '\9' and '\n' are placeholders and do not overwrite the original text. + * A space will always hide an icon. + */ +static int setChar(struct yealink_dev *yld, int el, int chr) +{ + int i, a, m, val; + + if (el >= ARRAY_SIZE(lcdMap)) + return -EINVAL; + + if (chr == '\t' || chr == '\n') + return 0; + + yld->lcdMap[el] = chr; + + if (lcdMap[el].type == '.') { + a = lcdMap[el].u.p.a; + m = lcdMap[el].u.p.m; + if (chr != ' ') + yld->master.b[a] |= m; + else + yld->master.b[a] &= ~m; + return 0; + } + + val = map_to_seg7(&map_seg7, chr); + for (i = 0; i < ARRAY_SIZE(lcdMap[0].u.s); i++) { + m = lcdMap[el].u.s[i].m; + + if (m == 0) + continue; + + a = lcdMap[el].u.s[i].a; + if (val & 1) + yld->master.b[a] |= m; + else + yld->master.b[a] &= ~m; + val = val >> 1; + } + return 0; +}; + +/******************************************************************************* + * Yealink key interface + ******************************************************************************/ + +/* Map device buttons to internal key events. + * + * USB-P1K button layout: + * + * up + * IN OUT + * down + * + * pickup C hangup + * 1 2 3 + * 4 5 6 + * 7 8 9 + * * 0 # + * + * The "up" and "down" keys, are symbolised by arrows on the button. + * The "pickup" and "hangup" keys are symbolised by a green and red phone + * on the button. + */ +static int map_p1k_to_key(int scancode) +{ + switch(scancode) { /* phone key: */ + case 0x23: return KEY_LEFT; /* IN */ + case 0x33: return KEY_UP; /* up */ + case 0x04: return KEY_RIGHT; /* OUT */ + case 0x24: return KEY_DOWN; /* down */ + case 0x03: return KEY_ENTER; /* pickup */ + case 0x14: return KEY_BACKSPACE; /* C */ + case 0x13: return KEY_ESC; /* hangup */ + case 0x00: return KEY_1; /* 1 */ + case 0x01: return KEY_2; /* 2 */ + case 0x02: return KEY_3; /* 3 */ + case 0x10: return KEY_4; /* 4 */ + case 0x11: return KEY_5; /* 5 */ + case 0x12: return KEY_6; /* 6 */ + case 0x20: return KEY_7; /* 7 */ + case 0x21: return KEY_8; /* 8 */ + case 0x22: return KEY_9; /* 9 */ + case 0x30: return KEY_KPASTERISK; /* * */ + case 0x31: return KEY_0; /* 0 */ + case 0x32: return KEY_LEFTSHIFT | + KEY_3 << 8; /* # */ + } + return -EINVAL; +} + +/* Completes a request by converting the data into events for the + * input subsystem. + * + * The key parameter can be cascaded: key2 << 8 | key1 + */ +static void report_key(struct yealink_dev *yld, int key) +{ + struct input_dev *idev = yld->idev; + + if (yld->key_code >= 0) { + /* old key up */ + input_report_key(idev, yld->key_code & 0xff, 0); + if (yld->key_code >> 8) + input_report_key(idev, yld->key_code >> 8, 0); + } + + yld->key_code = key; + if (key >= 0) { + /* new valid key */ + input_report_key(idev, key & 0xff, 1); + if (key >> 8) + input_report_key(idev, key >> 8, 1); + } + input_sync(idev); +} + +/******************************************************************************* + * Yealink usb communication interface + ******************************************************************************/ + +static int yealink_cmd(struct yealink_dev *yld, struct yld_ctl_packet *p) +{ + u8 *buf = (u8 *)p; + int i; + u8 sum = 0; + + for(i=0; i<USB_PKT_LEN-1; i++) + sum -= buf[i]; + p->sum = sum; + return usb_control_msg(yld->udev, + usb_sndctrlpipe(yld->udev, 0), + USB_REQ_SET_CONFIGURATION, + USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_OUT, + 0x200, 3, + p, sizeof(*p), + USB_CTRL_SET_TIMEOUT); +} + +static u8 default_ringtone[] = { + 0xEF, /* volume [0-255] */ + 0xFB, 0x1E, 0x00, 0x0C, /* 1250 [hz], 12/100 [s] */ + 0xFC, 0x18, 0x00, 0x0C, /* 1000 [hz], 12/100 [s] */ + 0xFB, 0x1E, 0x00, 0x0C, + 0xFC, 0x18, 0x00, 0x0C, + 0xFB, 0x1E, 0x00, 0x0C, + 0xFC, 0x18, 0x00, 0x0C, + 0xFB, 0x1E, 0x00, 0x0C, + 0xFC, 0x18, 0x00, 0x0C, + 0xFF, 0xFF, 0x01, 0x90, /* silent, 400/100 [s] */ + 0x00, 0x00 /* end of sequence */ +}; + +static int yealink_set_ringtone(struct yealink_dev *yld, u8 *buf, size_t size) +{ + struct yld_ctl_packet *p = yld->ctl_data; + int ix, len; + + if (size <= 0) + return -EINVAL; + + /* Set the ringtone volume */ + memset(yld->ctl_data, 0, sizeof(*(yld->ctl_data))); + yld->ctl_data->cmd = CMD_RING_VOLUME; + yld->ctl_data->size = 1; + yld->ctl_data->data[0] = buf[0]; + yealink_cmd(yld, p); + + buf++; + size--; + + p->cmd = CMD_RING_NOTE; + ix = 0; + while (size != ix) { + len = size - ix; + if (len > sizeof(p->data)) + len = sizeof(p->data); + p->size = len; + p->offset = cpu_to_be16(ix); + memcpy(p->data, &buf[ix], len); + yealink_cmd(yld, p); + ix += len; + } + return 0; +} + +/* keep stat_master & stat_copy in sync. + */ +static int yealink_do_idle_tasks(struct yealink_dev *yld) +{ + u8 val; + int i, ix, len; + + ix = yld->stat_ix; + + memset(yld->ctl_data, 0, sizeof(*(yld->ctl_data))); + yld->ctl_data->cmd = CMD_KEYPRESS; + yld->ctl_data->size = 1; + yld->ctl_data->sum = 0xff - CMD_KEYPRESS; + + /* If state update pointer wraps do a KEYPRESS first. */ + if (ix >= sizeof(yld->master)) { + yld->stat_ix = 0; + return 0; + } + + /* find update candidates: copy != master */ + do { + val = yld->master.b[ix]; + if (val != yld->copy.b[ix]) + goto send_update; + } while (++ix < sizeof(yld->master)); + + /* nothing todo, wait a bit and poll for a KEYPRESS */ + yld->stat_ix = 0; + /* TODO how can we wait abit. ?? + * msleep_interruptible(1000 / YEALINK_POLLING_FREQUENCY); + */ + return 0; + +send_update: + + /* Setup an appropriate update request */ + yld->copy.b[ix] = val; + yld->ctl_data->data[0] = val; + + switch(ix) { + case offsetof(struct yld_status, led): + yld->ctl_data->cmd = CMD_LED; + yld->ctl_data->sum = -1 - CMD_LED - val; + break; + case offsetof(struct yld_status, dialtone): + yld->ctl_data->cmd = CMD_DIALTONE; + yld->ctl_data->sum = -1 - CMD_DIALTONE - val; + break; + case offsetof(struct yld_status, ringtone): + yld->ctl_data->cmd = CMD_RINGTONE; + yld->ctl_data->sum = -1 - CMD_RINGTONE - val; + break; + case offsetof(struct yld_status, keynum): + val--; + val &= 0x1f; + yld->ctl_data->cmd = CMD_SCANCODE; + yld->ctl_data->offset = cpu_to_be16(val); + yld->ctl_data->data[0] = 0; + yld->ctl_data->sum = -1 - CMD_SCANCODE - val; + break; + default: + len = sizeof(yld->master.s.lcd) - ix; + if (len > sizeof(yld->ctl_data->data)) + len = sizeof(yld->ctl_data->data); + + /* Combine up to <len> consecutive LCD bytes in a singe request + */ + yld->ctl_data->cmd = CMD_LCD; + yld->ctl_data->offset = cpu_to_be16(ix); + yld->ctl_data->size = len; + yld->ctl_data->sum = -CMD_LCD - ix - val - len; + for(i=1; i<len; i++) { + ix++; + val = yld->master.b[ix]; + yld->copy.b[ix] = val; + yld->ctl_data->data[i] = val; + yld->ctl_data->sum -= val; + } + } + yld->stat_ix = ix + 1; + return 1; +} + +/* Decide on how to handle responses + * + * The state transition diagram is somethhing like: + * + * syncState<--+ + * | | + * | idle + * \|/ | + * init --ok--> waitForKey --ok--> getKey + * ^ ^ | + * | +-------ok-------+ + * error,start + * + */ +static void urb_irq_callback(struct urb *urb) +{ + struct yealink_dev *yld = urb->context; + int ret, status = urb->status; + + if (status) + err("%s - urb status %d", __func__, status); + + switch (yld->irq_data->cmd) { + case CMD_KEYPRESS: + + yld->master.s.keynum = yld->irq_data->data[0]; + break; + + case CMD_SCANCODE: + dbg("get scancode %x", yld->irq_data->data[0]); + + report_key(yld, map_p1k_to_key(yld->irq_data->data[0])); + break; + + default: + err("unexpected response %x", yld->irq_data->cmd); + } + + yealink_do_idle_tasks(yld); + + if (!yld->shutdown) { + ret = usb_submit_urb(yld->urb_ctl, GFP_ATOMIC); + if (ret && ret != -EPERM) + err("%s - usb_submit_urb failed %d", __func__, ret); + } +} + +static void urb_ctl_callback(struct urb *urb) +{ + struct yealink_dev *yld = urb->context; + int ret = 0, status = urb->status; + + if (status) + err("%s - urb status %d", __func__, status); + + switch (yld->ctl_data->cmd) { + case CMD_KEYPRESS: + case CMD_SCANCODE: + /* ask for a response */ + if (!yld->shutdown) + ret = usb_submit_urb(yld->urb_irq, GFP_ATOMIC); + break; + default: + /* send new command */ + yealink_do_idle_tasks(yld); + if (!yld->shutdown) + ret = usb_submit_urb(yld->urb_ctl, GFP_ATOMIC); + break; + } + + if (ret && ret != -EPERM) + err("%s - usb_submit_urb failed %d", __func__, ret); +} + +/******************************************************************************* + * input event interface + ******************************************************************************/ + +/* TODO should we issue a ringtone on a SND_BELL event? +static int input_ev(struct input_dev *dev, unsigned int type, + unsigned int code, int value) +{ + + if (type != EV_SND) + return -EINVAL; + + switch (code) { + case SND_BELL: + case SND_TONE: + break; + default: + return -EINVAL; + } + + return 0; +} +*/ + +static int input_open(struct input_dev *dev) +{ + struct yealink_dev *yld = input_get_drvdata(dev); + int i, ret; + + dbg("%s", __func__); + + /* force updates to device */ + for (i = 0; i<sizeof(yld->master); i++) + yld->copy.b[i] = ~yld->master.b[i]; + yld->key_code = -1; /* no keys pressed */ + + yealink_set_ringtone(yld, default_ringtone, sizeof(default_ringtone)); + + /* issue INIT */ + memset(yld->ctl_data, 0, sizeof(*(yld->ctl_data))); + yld->ctl_data->cmd = CMD_INIT; + yld->ctl_data->size = 10; + yld->ctl_data->sum = 0x100-CMD_INIT-10; + if ((ret = usb_submit_urb(yld->urb_ctl, GFP_KERNEL)) != 0) { + dbg("%s - usb_submit_urb failed with result %d", + __func__, ret); + return ret; + } + return 0; +} + +static void input_close(struct input_dev *dev) +{ + struct yealink_dev *yld = input_get_drvdata(dev); + + yld->shutdown = 1; + /* + * Make sure the flag is seen by other CPUs before we start + * killing URBs so new URBs won't be submitted + */ + smp_wmb(); + + usb_kill_urb(yld->urb_ctl); + usb_kill_urb(yld->urb_irq); + + yld->shutdown = 0; + smp_wmb(); +} + +/******************************************************************************* + * sysfs interface + ******************************************************************************/ + +static DECLARE_RWSEM(sysfs_rwsema); + +/* Interface to the 7-segments translation table aka. char set. + */ +static ssize_t show_map(struct device *dev, struct device_attribute *attr, + char *buf) +{ + memcpy(buf, &map_seg7, sizeof(map_seg7)); + return sizeof(map_seg7); +} + +static ssize_t store_map(struct device *dev, struct device_attribute *attr, + const char *buf, size_t cnt) +{ + if (cnt != sizeof(map_seg7)) + return -EINVAL; + memcpy(&map_seg7, buf, sizeof(map_seg7)); + return sizeof(map_seg7); +} + +/* Interface to the LCD. + */ + +/* Reading /sys/../lineX will return the format string with its settings: + * + * Example: + * cat ./line3 + * 888888888888 + * Linux Rocks! + */ +static ssize_t show_line(struct device *dev, char *buf, int a, int b) +{ + struct yealink_dev *yld; + int i; + + down_read(&sysfs_rwsema); + yld = dev_get_drvdata(dev); + if (yld == NULL) { + up_read(&sysfs_rwsema); + return -ENODEV; + } + + for (i = a; i < b; i++) + *buf++ = lcdMap[i].type; + *buf++ = '\n'; + for (i = a; i < b; i++) + *buf++ = yld->lcdMap[i]; + *buf++ = '\n'; + *buf = 0; + + up_read(&sysfs_rwsema); + return 3 + ((b - a) << 1); +} + +static ssize_t show_line1(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return show_line(dev, buf, LCD_LINE1_OFFSET, LCD_LINE2_OFFSET); +} + +static ssize_t show_line2(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return show_line(dev, buf, LCD_LINE2_OFFSET, LCD_LINE3_OFFSET); +} + +static ssize_t show_line3(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return show_line(dev, buf, LCD_LINE3_OFFSET, LCD_LINE4_OFFSET); +} + +/* Writing to /sys/../lineX will set the coresponding LCD line. + * - Excess characters are ignored. + * - If less characters are written than allowed, the remaining digits are + * unchanged. + * - The '\n' or '\t' char is a placeholder, it does not overwrite the + * original content. + */ +static ssize_t store_line(struct device *dev, const char *buf, size_t count, + int el, size_t len) +{ + struct yealink_dev *yld; + int i; + + down_write(&sysfs_rwsema); + yld = dev_get_drvdata(dev); + if (yld == NULL) { + up_write(&sysfs_rwsema); + return -ENODEV; + } + + if (len > count) + len = count; + for (i = 0; i < len; i++) + setChar(yld, el++, buf[i]); + + up_write(&sysfs_rwsema); + return count; +} + +static ssize_t store_line1(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + return store_line(dev, buf, count, LCD_LINE1_OFFSET, LCD_LINE1_SIZE); +} + +static ssize_t store_line2(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + return store_line(dev, buf, count, LCD_LINE2_OFFSET, LCD_LINE2_SIZE); +} + +static ssize_t store_line3(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + return store_line(dev, buf, count, LCD_LINE3_OFFSET, LCD_LINE3_SIZE); +} + +/* Interface to visible and audible "icons", these include: + * pictures on the LCD, the LED, and the dialtone signal. + */ + +/* Get a list of "switchable elements" with their current state. */ +static ssize_t get_icons(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct yealink_dev *yld; + int i, ret = 1; + + down_read(&sysfs_rwsema); + yld = dev_get_drvdata(dev); + if (yld == NULL) { + up_read(&sysfs_rwsema); + return -ENODEV; + } + + for (i = 0; i < ARRAY_SIZE(lcdMap); i++) { + if (lcdMap[i].type != '.') + continue; + ret += sprintf(&buf[ret], "%s %s\n", + yld->lcdMap[i] == ' ' ? " " : "on", + lcdMap[i].u.p.name); + } + up_read(&sysfs_rwsema); + return ret; +} + +/* Change the visibility of a particular element. */ +static ssize_t set_icon(struct device *dev, const char *buf, size_t count, + int chr) +{ + struct yealink_dev *yld; + int i; + + down_write(&sysfs_rwsema); + yld = dev_get_drvdata(dev); + if (yld == NULL) { + up_write(&sysfs_rwsema); + return -ENODEV; + } + + for (i = 0; i < ARRAY_SIZE(lcdMap); i++) { + if (lcdMap[i].type != '.') + continue; + if (strncmp(buf, lcdMap[i].u.p.name, count) == 0) { + setChar(yld, i, chr); + break; + } + } + + up_write(&sysfs_rwsema); + return count; +} + +static ssize_t show_icon(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + return set_icon(dev, buf, count, buf[0]); +} + +static ssize_t hide_icon(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + return set_icon(dev, buf, count, ' '); +} + +/* Upload a ringtone to the device. + */ + +/* Stores raw ringtone data in the phone */ +static ssize_t store_ringtone(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct yealink_dev *yld; + + down_write(&sysfs_rwsema); + yld = dev_get_drvdata(dev); + if (yld == NULL) { + up_write(&sysfs_rwsema); + return -ENODEV; + } + + /* TODO locking with async usb control interface??? */ + yealink_set_ringtone(yld, (char *)buf, count); + up_write(&sysfs_rwsema); + return count; +} + +#define _M444 S_IRUGO +#define _M664 S_IRUGO|S_IWUSR|S_IWGRP +#define _M220 S_IWUSR|S_IWGRP + +static DEVICE_ATTR(map_seg7 , _M664, show_map , store_map ); +static DEVICE_ATTR(line1 , _M664, show_line1 , store_line1 ); +static DEVICE_ATTR(line2 , _M664, show_line2 , store_line2 ); +static DEVICE_ATTR(line3 , _M664, show_line3 , store_line3 ); +static DEVICE_ATTR(get_icons , _M444, get_icons , NULL ); +static DEVICE_ATTR(show_icon , _M220, NULL , show_icon ); +static DEVICE_ATTR(hide_icon , _M220, NULL , hide_icon ); +static DEVICE_ATTR(ringtone , _M220, NULL , store_ringtone); + +static struct attribute *yld_attributes[] = { + &dev_attr_line1.attr, + &dev_attr_line2.attr, + &dev_attr_line3.attr, + &dev_attr_get_icons.attr, + &dev_attr_show_icon.attr, + &dev_attr_hide_icon.attr, + &dev_attr_map_seg7.attr, + &dev_attr_ringtone.attr, + NULL +}; + +static struct attribute_group yld_attr_group = { + .attrs = yld_attributes +}; + +/******************************************************************************* + * Linux interface and usb initialisation + ******************************************************************************/ + +struct driver_info { + char *name; +}; + +static const struct driver_info info_P1K = { + .name = "Yealink usb-p1k", +}; + +static const struct usb_device_id usb_table [] = { + { + .match_flags = USB_DEVICE_ID_MATCH_DEVICE | + USB_DEVICE_ID_MATCH_INT_INFO, + .idVendor = 0x6993, + .idProduct = 0xb001, + .bInterfaceClass = USB_CLASS_HID, + .bInterfaceSubClass = 0, + .bInterfaceProtocol = 0, + .driver_info = (kernel_ulong_t)&info_P1K + }, + { } +}; + +static int usb_cleanup(struct yealink_dev *yld, int err) +{ + if (yld == NULL) + return err; + + if (yld->idev) { + if (err) + input_free_device(yld->idev); + else + input_unregister_device(yld->idev); + } + + usb_free_urb(yld->urb_irq); + usb_free_urb(yld->urb_ctl); + + usb_buffer_free(yld->udev, sizeof(*(yld->ctl_req)), + yld->ctl_req, yld->ctl_req_dma); + usb_buffer_free(yld->udev, USB_PKT_LEN, + yld->ctl_data, yld->ctl_dma); + usb_buffer_free(yld->udev, USB_PKT_LEN, + yld->irq_data, yld->irq_dma); + + kfree(yld); + return err; +} + +static void usb_disconnect(struct usb_interface *intf) +{ + struct yealink_dev *yld; + + down_write(&sysfs_rwsema); + yld = usb_get_intfdata(intf); + sysfs_remove_group(&intf->dev.kobj, &yld_attr_group); + usb_set_intfdata(intf, NULL); + up_write(&sysfs_rwsema); + + usb_cleanup(yld, 0); +} + +static int usb_probe(struct usb_interface *intf, const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev (intf); + struct driver_info *nfo = (struct driver_info *)id->driver_info; + struct usb_host_interface *interface; + struct usb_endpoint_descriptor *endpoint; + struct yealink_dev *yld; + struct input_dev *input_dev; + int ret, pipe, i; + + interface = intf->cur_altsetting; + endpoint = &interface->endpoint[0].desc; + if (!usb_endpoint_is_int_in(endpoint)) + return -ENODEV; + + yld = kzalloc(sizeof(struct yealink_dev), GFP_KERNEL); + if (!yld) + return -ENOMEM; + + yld->udev = udev; + + yld->idev = input_dev = input_allocate_device(); + if (!input_dev) + return usb_cleanup(yld, -ENOMEM); + + /* allocate usb buffers */ + yld->irq_data = usb_buffer_alloc(udev, USB_PKT_LEN, + GFP_ATOMIC, &yld->irq_dma); + if (yld->irq_data == NULL) + return usb_cleanup(yld, -ENOMEM); + + yld->ctl_data = usb_buffer_alloc(udev, USB_PKT_LEN, + GFP_ATOMIC, &yld->ctl_dma); + if (!yld->ctl_data) + return usb_cleanup(yld, -ENOMEM); + + yld->ctl_req = usb_buffer_alloc(udev, sizeof(*(yld->ctl_req)), + GFP_ATOMIC, &yld->ctl_req_dma); + if (yld->ctl_req == NULL) + return usb_cleanup(yld, -ENOMEM); + + /* allocate urb structures */ + yld->urb_irq = usb_alloc_urb(0, GFP_KERNEL); + if (yld->urb_irq == NULL) + return usb_cleanup(yld, -ENOMEM); + + yld->urb_ctl = usb_alloc_urb(0, GFP_KERNEL); + if (yld->urb_ctl == NULL) + return usb_cleanup(yld, -ENOMEM); + + /* get a handle to the interrupt data pipe */ + pipe = usb_rcvintpipe(udev, endpoint->bEndpointAddress); + ret = usb_maxpacket(udev, pipe, usb_pipeout(pipe)); + if (ret != USB_PKT_LEN) + err("invalid payload size %d, expected %zd", ret, USB_PKT_LEN); + + /* initialise irq urb */ + usb_fill_int_urb(yld->urb_irq, udev, pipe, yld->irq_data, + USB_PKT_LEN, + urb_irq_callback, + yld, endpoint->bInterval); + yld->urb_irq->transfer_dma = yld->irq_dma; + yld->urb_irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + yld->urb_irq->dev = udev; + + /* initialise ctl urb */ + yld->ctl_req->bRequestType = USB_TYPE_CLASS | USB_RECIP_INTERFACE | + USB_DIR_OUT; + yld->ctl_req->bRequest = USB_REQ_SET_CONFIGURATION; + yld->ctl_req->wValue = cpu_to_le16(0x200); + yld->ctl_req->wIndex = cpu_to_le16(interface->desc.bInterfaceNumber); + yld->ctl_req->wLength = cpu_to_le16(USB_PKT_LEN); + + usb_fill_control_urb(yld->urb_ctl, udev, usb_sndctrlpipe(udev, 0), + (void *)yld->ctl_req, yld->ctl_data, USB_PKT_LEN, + urb_ctl_callback, yld); + yld->urb_ctl->setup_dma = yld->ctl_req_dma; + yld->urb_ctl->transfer_dma = yld->ctl_dma; + yld->urb_ctl->transfer_flags |= URB_NO_SETUP_DMA_MAP | + URB_NO_TRANSFER_DMA_MAP; + yld->urb_ctl->dev = udev; + + /* find out the physical bus location */ + usb_make_path(udev, yld->phys, sizeof(yld->phys)); + strlcat(yld->phys, "/input0", sizeof(yld->phys)); + + /* register settings for the input device */ + input_dev->name = nfo->name; + input_dev->phys = yld->phys; + usb_to_input_id(udev, &input_dev->id); + input_dev->dev.parent = &intf->dev; + + input_set_drvdata(input_dev, yld); + + input_dev->open = input_open; + input_dev->close = input_close; + /* input_dev->event = input_ev; TODO */ + + /* register available key events */ + input_dev->evbit[0] = BIT_MASK(EV_KEY); + for (i = 0; i < 256; i++) { + int k = map_p1k_to_key(i); + if (k >= 0) { + set_bit(k & 0xff, input_dev->keybit); + if (k >> 8) + set_bit(k >> 8, input_dev->keybit); + } + } + + ret = input_register_device(yld->idev); + if (ret) + return usb_cleanup(yld, ret); + + usb_set_intfdata(intf, yld); + + /* clear visible elements */ + for (i = 0; i < ARRAY_SIZE(lcdMap); i++) + setChar(yld, i, ' '); + + /* display driver version on LCD line 3 */ + store_line3(&intf->dev, NULL, + DRIVER_VERSION, sizeof(DRIVER_VERSION)); + + /* Register sysfs hooks (don't care about failure) */ + ret = sysfs_create_group(&intf->dev.kobj, &yld_attr_group); + return 0; +} + +static struct usb_driver yealink_driver = { + .name = "yealink", + .probe = usb_probe, + .disconnect = usb_disconnect, + .id_table = usb_table, +}; + +static int __init yealink_dev_init(void) +{ + int ret = usb_register(&yealink_driver); + if (ret == 0) + printk(KERN_INFO KBUILD_MODNAME ": " DRIVER_VERSION ":" + DRIVER_DESC "\n"); + return ret; +} + +static void __exit yealink_dev_exit(void) +{ + usb_deregister(&yealink_driver); +} + +module_init(yealink_dev_init); +module_exit(yealink_dev_exit); + +MODULE_DEVICE_TABLE (usb, usb_table); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/misc/yealink.h b/drivers/input/misc/yealink.h new file mode 100644 index 0000000..48af0be --- /dev/null +++ b/drivers/input/misc/yealink.h @@ -0,0 +1,220 @@ +/* + * drivers/usb/input/yealink.h + * + * Copyright (c) 2005 Henk Vergonet <Henk.Vergonet@gmail.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 + */ +#ifndef INPUT_YEALINK_H +#define INPUT_YEALINK_H + +/* Using the control channel on interface 3 various aspects of the phone + * can be controlled like LCD, LED, dialtone and the ringtone. + */ + +struct yld_ctl_packet { + u8 cmd; /* command code, see below */ + u8 size; /* 1-11, size of used data bytes. */ + u16 offset; /* internal packet offset */ + u8 data[11]; + s8 sum; /* negative sum of 15 preceding bytes */ +} __attribute__ ((packed)); + +#define USB_PKT_LEN sizeof(struct yld_ctl_packet) + +/* The following yld_ctl_packet's are available: */ + +/* Init registers + * + * cmd 0x8e + * size 10 + * offset 0 + * data 0,0,0,0.... + */ +#define CMD_INIT 0x8e + +/* Request key scan + * + * cmd 0x80 + * size 1 + * offset 0 + * data[0] on return returns the key number, if it changes there's a new + * key pressed. + */ +#define CMD_KEYPRESS 0x80 + +/* Request scancode + * + * cmd 0x81 + * size 1 + * offset key number [0-1f] + * data[0] on return returns the scancode + */ +#define CMD_SCANCODE 0x81 + +/* Set LCD + * + * cmd 0x04 + * size 1-11 + * offset 0-23 + * data segment bits + */ +#define CMD_LCD 0x04 + +/* Set led + * + * cmd 0x05 + * size 1 + * offset 0 + * data[0] 0 OFF / 1 ON + */ +#define CMD_LED 0x05 + +/* Set ringtone volume + * + * cmd 0x11 + * size 1 + * offset 0 + * data[0] 0-0xff volume + */ +#define CMD_RING_VOLUME 0x11 + +/* Set ringtone notes + * + * cmd 0x02 + * size 1-11 + * offset 0-> + * data binary representation LE16(-freq), LE16(duration) .... + */ +#define CMD_RING_NOTE 0x02 + +/* Sound ringtone via the speaker on the back + * + * cmd 0x03 + * size 1 + * offset 0 + * data[0] 0 OFF / 0x24 ON + */ +#define CMD_RINGTONE 0x03 + +/* Sound dial tone via the ear speaker + * + * cmd 0x09 + * size 1 + * offset 0 + * data[0] 0 OFF / 1 ON + */ +#define CMD_DIALTONE 0x09 + +#endif /* INPUT_YEALINK_H */ + + +#if defined(_SEG) && defined(_PIC) +/* This table maps the LCD segments onto individual bit positions in the + * yld_status struct. + */ + +/* LCD, each segment must be driven seperately. + * + * Layout: + * + * |[] [][] [][] [][] in |[][] + * |[] M [][] D [][] : [][] out |[][] + * store + * + * NEW REP SU MO TU WE TH FR SA + * + * [] [] [] [] [] [] [] [] [] [] [] [] + * [] [] [] [] [] [] [] [] [] [] [] [] + */ + +/* Line 1 + * Format : 18.e8.M8.88...188 + * Icon names : M D : IN OUT STORE + */ +#define LCD_LINE1_OFFSET 0 +#define LCD_LINE1_SIZE 17 + +/* Note: first g then f => ! ! */ +/* _SEG( type a b c d e g f ) */ + _SEG('1', 0,0 , 22,2 , 22,2 , 0,0 , 0,0 , 0,0 , 0,0 ), + _SEG('8', 20,1 , 20,2 , 20,4 , 20,8 , 21,4 , 21,2 , 21,1 ), + _PIC('.', 22,1 , "M" ), + _SEG('e', 18,1 , 18,2 , 18,4 , 18,1 , 19,2 , 18,1 , 19,1 ), + _SEG('8', 16,1 , 16,2 , 16,4 , 16,8 , 17,4 , 17,2 , 17,1 ), + _PIC('.', 15,8 , "D" ), + _SEG('M', 14,1 , 14,2 , 14,4 , 14,1 , 15,4 , 15,2 , 15,1 ), + _SEG('8', 12,1 , 12,2 , 12,4 , 12,8 , 13,4 , 13,2 , 13,1 ), + _PIC('.', 11,8 , ":" ), + _SEG('8', 10,1 , 10,2 , 10,4 , 10,8 , 11,4 , 11,2 , 11,1 ), + _SEG('8', 8,1 , 8,2 , 8,4 , 8,8 , 9,4 , 9,2 , 9,1 ), + _PIC('.', 7,1 , "IN" ), + _PIC('.', 7,2 , "OUT" ), + _PIC('.', 7,4 , "STORE" ), + _SEG('1', 0,0 , 5,1 , 5,1 , 0,0 , 0,0 , 0,0 , 0,0 ), + _SEG('8', 4,1 , 4,2 , 4,4 , 4,8 , 5,8 , 5,4 , 5,2 ), + _SEG('8', 2,1 , 2,2 , 2,4 , 2,8 , 3,4 , 3,2 , 3,1 ), + +/* Line 2 + * Format : ......... + * Pict. name : NEW REP SU MO TU WE TH FR SA + */ +#define LCD_LINE2_OFFSET LCD_LINE1_OFFSET + LCD_LINE1_SIZE +#define LCD_LINE2_SIZE 9 + + _PIC('.', 23,2 , "NEW" ), + _PIC('.', 23,4 , "REP" ), + _PIC('.', 1,8 , "SU" ), + _PIC('.', 1,4 , "MO" ), + _PIC('.', 1,2 , "TU" ), + _PIC('.', 1,1 , "WE" ), + _PIC('.', 0,1 , "TH" ), + _PIC('.', 0,2 , "FR" ), + _PIC('.', 0,4 , "SA" ), + +/* Line 3 + * Format : 888888888888 + */ +#define LCD_LINE3_OFFSET LCD_LINE2_OFFSET + LCD_LINE2_SIZE +#define LCD_LINE3_SIZE 12 + + _SEG('8', 22,16, 22,32, 22,64, 22,128, 23,128, 23,64, 23,32 ), + _SEG('8', 20,16, 20,32, 20,64, 20,128, 21,128, 21,64, 21,32 ), + _SEG('8', 18,16, 18,32, 18,64, 18,128, 19,128, 19,64, 19,32 ), + _SEG('8', 16,16, 16,32, 16,64, 16,128, 17,128, 17,64, 17,32 ), + _SEG('8', 14,16, 14,32, 14,64, 14,128, 15,128, 15,64, 15,32 ), + _SEG('8', 12,16, 12,32, 12,64, 12,128, 13,128, 13,64, 13,32 ), + _SEG('8', 10,16, 10,32, 10,64, 10,128, 11,128, 11,64, 11,32 ), + _SEG('8', 8,16, 8,32, 8,64, 8,128, 9,128, 9,64, 9,32 ), + _SEG('8', 6,16, 6,32, 6,64, 6,128, 7,128, 7,64, 7,32 ), + _SEG('8', 4,16, 4,32, 4,64, 4,128, 5,128, 5,64, 5,32 ), + _SEG('8', 2,16, 2,32, 2,64, 2,128, 3,128, 3,64, 3,32 ), + _SEG('8', 0,16, 0,32, 0,64, 0,128, 1,128, 1,64, 1,32 ), + +/* Line 4 + * + * The LED, DIALTONE and RINGTONE are implemented as icons and use the same + * sysfs interface. + */ +#define LCD_LINE4_OFFSET LCD_LINE3_OFFSET + LCD_LINE3_SIZE + + _PIC('.', offsetof(struct yld_status, led) , 0x01, "LED" ), + _PIC('.', offsetof(struct yld_status, dialtone) , 0x01, "DIALTONE" ), + _PIC('.', offsetof(struct yld_status, ringtone) , 0x24, "RINGTONE" ), + +#undef _SEG +#undef _PIC +#endif /* _SEG && _PIC */ diff --git a/drivers/input/mouse/Kconfig b/drivers/input/mouse/Kconfig new file mode 100644 index 0000000..4e99342 --- /dev/null +++ b/drivers/input/mouse/Kconfig @@ -0,0 +1,289 @@ +# +# Mouse driver configuration +# +menuconfig INPUT_MOUSE + bool "Mice" + 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 X86_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, ALPS or Elantech TouchPad users might be interested + in a specialized Xorg/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_PS2_ALPS + bool "ALPS PS/2 mouse protocol extension" if EMBEDDED + default y + depends on MOUSE_PS2 + help + Say Y here if you have an ALPS PS/2 touchpad connected to + your system. + + If unsure, say Y. + +config MOUSE_PS2_LOGIPS2PP + bool "Logitech PS/2++ mouse protocol extension" if EMBEDDED + default y + depends on MOUSE_PS2 + help + Say Y here if you have a Logictech PS/2++ mouse connected to + your system. + + If unsure, say Y. + +config MOUSE_PS2_SYNAPTICS + bool "Synaptics PS/2 mouse protocol extension" if EMBEDDED + default y + depends on MOUSE_PS2 + help + Say Y here if you have a Synaptics PS/2 TouchPad connected to + your system. + + If unsure, say Y. + +config MOUSE_PS2_LIFEBOOK + bool "Fujitsu Lifebook PS/2 mouse protocol extension" if EMBEDDED + default y + depends on MOUSE_PS2 + help + Say Y here if you have a Fujitsu B-series Lifebook PS/2 + TouchScreen connected to your system. + + If unsure, say Y. + +config MOUSE_PS2_TRACKPOINT + bool "IBM Trackpoint PS/2 mouse protocol extension" if EMBEDDED + default y + depends on MOUSE_PS2 + help + Say Y here if you have an IBM Trackpoint PS/2 mouse connected + to your system. + + If unsure, say Y. + +config MOUSE_PS2_ELANTECH + bool "Elantech PS/2 protocol extension" + depends on MOUSE_PS2 + help + Say Y here if you have an Elantech PS/2 touchpad connected + to your system. + + Note that if you enable this driver you will need an updated + X.org Synaptics driver that does not require ABS_PRESSURE + reports from the touchpad (i.e. post 1.5.0 version). You can + grab a patch for the driver here: + + http://userweb.kernel.org/~dtor/synaptics-no-abspressure.patch + + If unsure, say N. + + This driver exposes some configuration registers via sysfs + entries. For further information, + see <file:Documentation/input/elantech.txt>. + + +config MOUSE_PS2_TOUCHKIT + bool "eGalax TouchKit PS/2 protocol extension" + depends on MOUSE_PS2 + help + Say Y here if you have an eGalax TouchKit PS/2 touchscreen + connected to your system. + + If unsure, say N. + +config MOUSE_PS2_OLPC + bool "OLPC PS/2 mouse protocol extension" + depends on MOUSE_PS2 && OLPC + help + Say Y here if you have an OLPC XO-1 laptop (with built-in + PS/2 touchpad/tablet device). The manufacturer calls the + touchpad an HGPK. + + If unsure, say N. + +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_APPLETOUCH + tristate "Apple USB Touchpad support" + depends on USB_ARCH_HAS_HCD + select USB + help + Say Y here if you want to use an Apple USB Touchpad. + + These are the touchpads that can be found on post-February 2005 + Apple Powerbooks (prior models have a Synaptics touchpad connected + to the ADB bus). + + This driver provides a basic mouse driver but can be interfaced + with the synaptics X11 driver to provide acceleration and + scrolling in X11. + + For further information, see + <file:Documentation/input/appletouch.txt>. + + To compile this driver as a module, choose M here: the + module will be called appletouch. + +config MOUSE_BCM5974 + tristate "Apple USB BCM5974 Multitouch trackpad support" + depends on USB_ARCH_HAS_HCD + select USB + help + Say Y here if you have an Apple USB BCM5974 Multitouch + trackpad. + + The BCM5974 is the multitouch trackpad found in the Macbook + Air (JAN2008) and Macbook Pro Penryn (FEB2008) laptops. + + It is also found in the IPhone (2007) and Ipod Touch (2008). + + This driver provides multitouch functionality together with + the synaptics X11 driver. + + The interface is currently identical to the appletouch interface, + for further information, see + <file:Documentation/input/appletouch.txt>. + + To compile this driver as a module, choose M here: the + module will be called bcm5974. + +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_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_ATARI + tristate "Atari mouse" + depends on ATARI + select ATARI_KBD_CORE + help + Say Y here if you have an Atari and want its native mouse + supported by the kernel. + + To compile this driver as a module, choose M here: the + module will be called atarimouse. + +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 || HP300 + select HP_SDC + select HIL_MLC + help + Say Y here to support HIL pointers. + +config MOUSE_GPIO + tristate "GPIO mouse" + depends on GENERIC_GPIO + select INPUT_POLLDEV + help + This driver simulates a mouse on GPIO lines of various CPUs (and some + other chips). + + Say Y here if your device has buttons or a simple joystick connected + directly to GPIO lines. Your board-specific setup logic must also + provide a platform device and platform data saying which GPIOs are + used. + + To compile this driver as a module, choose M here: the + module will be called gpio_mouse. + +endif diff --git a/drivers/input/mouse/Makefile b/drivers/input/mouse/Makefile new file mode 100644 index 0000000..96f1dd8 --- /dev/null +++ b/drivers/input/mouse/Makefile @@ -0,0 +1,29 @@ +# +# Makefile for the mouse drivers. +# + +# Each configuration option enables a list of files. + +obj-$(CONFIG_MOUSE_AMIGA) += amimouse.o +obj-$(CONFIG_MOUSE_APPLETOUCH) += appletouch.o +obj-$(CONFIG_MOUSE_BCM5974) += bcm5974.o +obj-$(CONFIG_MOUSE_ATARI) += atarimouse.o +obj-$(CONFIG_MOUSE_RISCPC) += rpcmouse.o +obj-$(CONFIG_MOUSE_INPORT) += inport.o +obj-$(CONFIG_MOUSE_LOGIBM) += logibm.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 +obj-$(CONFIG_MOUSE_GPIO) += gpio_mouse.o + +psmouse-objs := psmouse-base.o synaptics.o + +psmouse-$(CONFIG_MOUSE_PS2_ALPS) += alps.o +psmouse-$(CONFIG_MOUSE_PS2_ELANTECH) += elantech.o +psmouse-$(CONFIG_MOUSE_PS2_OLPC) += hgpk.o +psmouse-$(CONFIG_MOUSE_PS2_LOGIPS2PP) += logips2pp.o +psmouse-$(CONFIG_MOUSE_PS2_LIFEBOOK) += lifebook.o +psmouse-$(CONFIG_MOUSE_PS2_TRACKPOINT) += trackpoint.o +psmouse-$(CONFIG_MOUSE_PS2_TOUCHKIT) += touchkit_ps2.o diff --git a/drivers/input/mouse/alps.c b/drivers/input/mouse/alps.c new file mode 100644 index 0000000..cbedf95 --- /dev/null +++ b/drivers/input/mouse/alps.c @@ -0,0 +1,533 @@ +/* + * ALPS touchpad PS/2 mouse driver + * + * Copyright (c) 2003 Neil Brown <neilb@cse.unsw.edu.au> + * Copyright (c) 2003-2005 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_1 0x04 +#define ALPS_4BTN 0x08 +#define ALPS_OLDPROTO 0x10 +#define ALPS_PASS 0x20 +#define ALPS_FW_BK_2 0x40 + +static const 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 }, + { { 0x60, 0x03, 0xc8 }, 0xf8, 0xf8, 0 }, /* HP ze1115 */ + { { 0x63, 0x02, 0x0a }, 0xf8, 0xf8, 0 }, + { { 0x63, 0x02, 0x14 }, 0xf8, 0xf8, 0 }, + { { 0x63, 0x02, 0x28 }, 0xf8, 0xf8, ALPS_FW_BK_2 }, /* Fujitsu Siemens S6010 */ + { { 0x63, 0x02, 0x3c }, 0x8f, 0x8f, ALPS_WHEEL }, /* Toshiba Satellite S2400-103 */ + { { 0x63, 0x02, 0x50 }, 0xef, 0xef, ALPS_FW_BK_1 }, /* NEC Versa L320 */ + { { 0x63, 0x02, 0x64 }, 0xf8, 0xf8, 0 }, + { { 0x63, 0x03, 0xc8 }, 0xf8, 0xf8, ALPS_PASS }, /* Dell Latitude D800 */ + { { 0x73, 0x00, 0x0a }, 0xf8, 0xf8, ALPS_DUALPOINT }, /* ThinkPad R61 8918-5QG */ + { { 0x73, 0x02, 0x0a }, 0xf8, 0xf8, 0 }, + { { 0x73, 0x02, 0x14 }, 0xf8, 0xf8, ALPS_FW_BK_2 }, /* Ahtec Laptop */ + { { 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 */ + { { 0x62, 0x02, 0x14 }, 0xcf, 0xcf, ALPS_PASS | ALPS_DUALPOINT }, /* Dell Latitude E6500 */ + { { 0x73, 0x02, 0x50 }, 0xcf, 0xcf, ALPS_FW_BK_1 } /* Dell Vostro 1400 */ +}; + +/* + * 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 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; + int back = 0, forward = 0; + + 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] & 0x10; + right = packet[2] & 0x08; + 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]; + } + + if (priv->i->flags & ALPS_FW_BK_1) { + back = packet[0] & 0x10; + forward = packet[2] & 4; + } + + if (priv->i->flags & ALPS_FW_BK_2) { + back = packet[3] & 4; + forward = packet[2] & 4; + if ((middle = forward && back)) + forward = back = 0; + } + + 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[2] << 1) & 0x08) - ((packet[0] >> 4) & 0x07)); + + if (priv->i->flags & (ALPS_FW_BK_1 | ALPS_FW_BK_2)) { + input_report_key(dev, BTN_FORWARD, forward); + input_report_key(dev, BTN_BACK, back); + } + + input_sync(dev); +} + +static psmouse_ret_t alps_process_byte(struct psmouse *psmouse) +{ + struct alps_data *priv = psmouse->private; + + if ((psmouse->packet[0] & 0xc8) == 0x08) { /* PS/2 packet */ + if (psmouse->pktcnt == 3) { + alps_process_packet(psmouse); + 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); + return PSMOUSE_FULL_PACKET; + } + + return PSMOUSE_GOOD_DATA; +} + +static const struct alps_model_info *alps_get_model(struct psmouse *psmouse, int *version) +{ + struct ps2dev *ps2dev = &psmouse->ps2dev; + static const 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]); + + if (version) { + for (i = 0; i < ARRAY_SIZE(rates) && param[2] != rates[i]; i++) + /* empty */; + *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; + 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_drain(ps2dev, 3, 100); + + 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; +} + +/* + * alps_poll() - poll the touchpad for current motion packet. + * Used in resync. + */ +static int alps_poll(struct psmouse *psmouse) +{ + struct alps_data *priv = psmouse->private; + unsigned char buf[6]; + int poll_failed; + + if (priv->i->flags & ALPS_PASS) + alps_passthrough_mode(psmouse, 1); + + poll_failed = ps2_command(&psmouse->ps2dev, buf, + PSMOUSE_CMD_POLL | (psmouse->pktsize << 8)) < 0; + + if (priv->i->flags & ALPS_PASS) + alps_passthrough_mode(psmouse, 0); + + if (poll_failed || (buf[0] & priv->i->mask0) != priv->i->byte0) + return -1; + + if ((psmouse->badbyte & 0xc8) == 0x08) { +/* + * Poll the track stick ... + */ + if (ps2_command(&psmouse->ps2dev, buf, PSMOUSE_CMD_POLL | (3 << 8))) + return -1; + } + + memcpy(psmouse->packet, buf, sizeof(buf)); + return 0; +} + +static int alps_hw_init(struct psmouse *psmouse, int *version) +{ + struct alps_data *priv = psmouse->private; + + priv->i = alps_get_model(psmouse, version); + if (!priv->i) + return -1; + + if ((priv->i->flags & ALPS_PASS) && alps_passthrough_mode(psmouse, 1)) + return -1; + + if (alps_tap_mode(psmouse, 1)) { + printk(KERN_WARNING "alps.c: Failed to enable hardware tapping\n"); + return -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; + + /* ALPS needs stream mode, otherwise it won't report any data */ + if (ps2_command(&psmouse->ps2dev, NULL, PSMOUSE_CMD_SETSTREAM)) { + printk(KERN_ERR "alps.c: Failed to enable stream mode\n"); + return -1; + } + + return 0; +} + +static int alps_reconnect(struct psmouse *psmouse) +{ + psmouse_reset(psmouse); + + if (alps_hw_init(psmouse, NULL)) + 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; + struct input_dev *dev1 = psmouse->dev, *dev2; + int version; + + priv = kzalloc(sizeof(struct alps_data), GFP_KERNEL); + dev2 = input_allocate_device(); + if (!priv || !dev2) + goto init_fail; + + priv->dev2 = dev2; + psmouse->private = priv; + + if (alps_hw_init(psmouse, &version)) + goto init_fail; + + dev1->evbit[BIT_WORD(EV_KEY)] |= BIT_MASK(EV_KEY); + dev1->keybit[BIT_WORD(BTN_TOUCH)] |= BIT_MASK(BTN_TOUCH); + dev1->keybit[BIT_WORD(BTN_TOOL_FINGER)] |= BIT_MASK(BTN_TOOL_FINGER); + dev1->keybit[BIT_WORD(BTN_LEFT)] |= BIT_MASK(BTN_LEFT) | + BIT_MASK(BTN_MIDDLE) | BIT_MASK(BTN_RIGHT); + + dev1->evbit[BIT_WORD(EV_ABS)] |= BIT_MASK(EV_ABS); + input_set_abs_params(dev1, ABS_X, 0, 1023, 0, 0); + input_set_abs_params(dev1, ABS_Y, 0, 767, 0, 0); + input_set_abs_params(dev1, ABS_PRESSURE, 0, 127, 0, 0); + + if (priv->i->flags & ALPS_WHEEL) { + dev1->evbit[BIT_WORD(EV_REL)] |= BIT_MASK(EV_REL); + dev1->relbit[BIT_WORD(REL_WHEEL)] |= BIT_MASK(REL_WHEEL); + } + + if (priv->i->flags & (ALPS_FW_BK_1 | ALPS_FW_BK_2)) { + dev1->keybit[BIT_WORD(BTN_FORWARD)] |= BIT_MASK(BTN_FORWARD); + dev1->keybit[BIT_WORD(BTN_BACK)] |= BIT_MASK(BTN_BACK); + } + + snprintf(priv->phys, sizeof(priv->phys), "%s/input1", psmouse->ps2dev.serio->phys); + dev2->phys = priv->phys; + dev2->name = (priv->i->flags & ALPS_DUALPOINT) ? "DualPoint Stick" : "PS/2 Mouse"; + dev2->id.bustype = BUS_I8042; + dev2->id.vendor = 0x0002; + dev2->id.product = PSMOUSE_ALPS; + dev2->id.version = 0x0000; + dev2->dev.parent = &psmouse->ps2dev.serio->dev; + + dev2->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REL); + dev2->relbit[BIT_WORD(REL_X)] |= BIT_MASK(REL_X) | BIT_MASK(REL_Y); + dev2->keybit[BIT_WORD(BTN_LEFT)] |= BIT_MASK(BTN_LEFT) | + BIT_MASK(BTN_MIDDLE) | BIT_MASK(BTN_RIGHT); + + if (input_register_device(priv->dev2)) + goto init_fail; + + psmouse->protocol_handler = alps_process_byte; + psmouse->poll = alps_poll; + psmouse->disconnect = alps_disconnect; + psmouse->reconnect = alps_reconnect; + psmouse->pktsize = 6; + + /* We are having trouble resyncing ALPS touchpads so disable it for now */ + psmouse->resync_time = 0; + + return 0; + +init_fail: + psmouse_reset(psmouse); + input_free_device(dev2); + kfree(priv); + psmouse->private = NULL; + return -1; +} + +int alps_detect(struct psmouse *psmouse, int set_properties) +{ + int version; + const struct alps_model_info *model; + + model = alps_get_model(psmouse, &version); + if (!model) + return -1; + + if (set_properties) { + psmouse->vendor = "ALPS"; + psmouse->name = model->flags & ALPS_DUALPOINT ? + "DualPoint TouchPad" : "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 0000000..4bbddc9 --- /dev/null +++ b/drivers/input/mouse/alps.h @@ -0,0 +1,42 @@ +/* + * 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 + +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 phys[32]; /* Phys */ + const struct alps_model_info *i;/* Info */ + int prev_fin; /* Finger bit from previous packet */ +}; + +#ifdef CONFIG_MOUSE_PS2_ALPS +int alps_detect(struct psmouse *psmouse, int set_properties); +int alps_init(struct psmouse *psmouse); +#else +inline int alps_detect(struct psmouse *psmouse, int set_properties) +{ + return -ENOSYS; +} +inline int alps_init(struct psmouse *psmouse) +{ + return -ENOSYS; +} +#endif /* CONFIG_MOUSE_PS2_ALPS */ + +#endif diff --git a/drivers/input/mouse/amimouse.c b/drivers/input/mouse/amimouse.c new file mode 100644 index 0000000..a185ac7 --- /dev/null +++ b/drivers/input/mouse/amimouse.c @@ -0,0 +1,136 @@ +/* + * 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_lastx, amimouse_lasty; +static struct input_dev *amimouse_dev; + +static irqreturn_t amimouse_interrupt(int irq, void *dummy) +{ + unsigned short joy0dat, potgor; + int nx, ny, dx, dy; + + joy0dat = amiga_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 = amiga_custom.potgor; + + 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; + + joy0dat = amiga_custom.joy0dat; + + amimouse_lastx = joy0dat & 0xff; + amimouse_lasty = joy0dat >> 8; + + if (request_irq(IRQ_AMIGA_VERTB, amimouse_interrupt, 0, "amimouse", amimouse_interrupt)) { + 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) +{ + free_irq(IRQ_AMIGA_VERTB, amimouse_interrupt); +} + +static int __init amimouse_init(void) +{ + int err; + + if (!MACH_IS_AMIGA || !AMIGAHW_PRESENT(AMI_MOUSE)) + return -ENODEV; + + amimouse_dev = input_allocate_device(); + if (!amimouse_dev) + return -ENOMEM; + + amimouse_dev->name = "Amiga mouse"; + amimouse_dev->phys = "amimouse/input0"; + amimouse_dev->id.bustype = BUS_AMIGA; + amimouse_dev->id.vendor = 0x0001; + amimouse_dev->id.product = 0x0002; + amimouse_dev->id.version = 0x0100; + + amimouse_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REL); + amimouse_dev->relbit[0] = BIT_MASK(REL_X) | BIT_MASK(REL_Y); + amimouse_dev->keybit[BIT_WORD(BTN_LEFT)] = BIT_MASK(BTN_LEFT) | + BIT_MASK(BTN_MIDDLE) | BIT_MASK(BTN_RIGHT); + amimouse_dev->open = amimouse_open; + amimouse_dev->close = amimouse_close; + + err = input_register_device(amimouse_dev); + if (err) { + input_free_device(amimouse_dev); + return err; + } + + 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/appletouch.c b/drivers/input/mouse/appletouch.c new file mode 100644 index 0000000..079816e --- /dev/null +++ b/drivers/input/mouse/appletouch.c @@ -0,0 +1,942 @@ +/* + * Apple USB Touchpad (for post-February 2005 PowerBooks and MacBooks) driver + * + * Copyright (C) 2001-2004 Greg Kroah-Hartman (greg@kroah.com) + * Copyright (C) 2005-2008 Johannes Berg (johannes@sipsolutions.net) + * Copyright (C) 2005 Stelian Pop (stelian@popies.net) + * Copyright (C) 2005 Frank Arnold (frank@scirocco-5v-turbo.de) + * Copyright (C) 2005 Peter Osterlund (petero2@telia.com) + * Copyright (C) 2005 Michael Hanselmann (linux-kernel@hansmi.ch) + * Copyright (C) 2006 Nicolas Boichat (nicolas@boichat.ch) + * Copyright (C) 2007-2008 Sven Anders (anders@anduras.de) + * + * Thanks to Alex Harper <basilisk@foobox.net> for his inputs. + * + * 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 <linux/kernel.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/usb/input.h> + +/* Type of touchpad */ +enum atp_touchpad_type { + ATP_FOUNTAIN, + ATP_GEYSER1, + ATP_GEYSER2, + ATP_GEYSER3, + ATP_GEYSER4 +}; + +#define ATP_DEVICE(prod, type) \ +{ \ + .match_flags = USB_DEVICE_ID_MATCH_DEVICE | \ + USB_DEVICE_ID_MATCH_INT_CLASS | \ + USB_DEVICE_ID_MATCH_INT_PROTOCOL, \ + .idVendor = 0x05ac, /* Apple */ \ + .idProduct = (prod), \ + .bInterfaceClass = 0x03, \ + .bInterfaceProtocol = 0x02, \ + .driver_info = ATP_ ## type, \ +} + +/* + * Table of devices (Product IDs) that work with this driver. + * (The names come from Info.plist in AppleUSBTrackpad.kext, + * According to Info.plist Geyser IV is the same as Geyser III.) + */ + +static struct usb_device_id atp_table [] = { + /* PowerBooks Feb 2005, iBooks G4 */ + ATP_DEVICE(0x020e, FOUNTAIN), /* FOUNTAIN ANSI */ + ATP_DEVICE(0x020f, FOUNTAIN), /* FOUNTAIN ISO */ + ATP_DEVICE(0x030a, FOUNTAIN), /* FOUNTAIN TP ONLY */ + ATP_DEVICE(0x030b, GEYSER1), /* GEYSER 1 TP ONLY */ + + /* PowerBooks Oct 2005 */ + ATP_DEVICE(0x0214, GEYSER2), /* GEYSER 2 ANSI */ + ATP_DEVICE(0x0215, GEYSER2), /* GEYSER 2 ISO */ + ATP_DEVICE(0x0216, GEYSER2), /* GEYSER 2 JIS */ + + /* Core Duo MacBook & MacBook Pro */ + ATP_DEVICE(0x0217, GEYSER3), /* GEYSER 3 ANSI */ + ATP_DEVICE(0x0218, GEYSER3), /* GEYSER 3 ISO */ + ATP_DEVICE(0x0219, GEYSER3), /* GEYSER 3 JIS */ + + /* Core2 Duo MacBook & MacBook Pro */ + ATP_DEVICE(0x021a, GEYSER4), /* GEYSER 4 ANSI */ + ATP_DEVICE(0x021b, GEYSER4), /* GEYSER 4 ISO */ + ATP_DEVICE(0x021c, GEYSER4), /* GEYSER 4 JIS */ + + /* Core2 Duo MacBook3,1 */ + ATP_DEVICE(0x0229, GEYSER4), /* GEYSER 4 HF ANSI */ + ATP_DEVICE(0x022a, GEYSER4), /* GEYSER 4 HF ISO */ + ATP_DEVICE(0x022b, GEYSER4), /* GEYSER 4 HF JIS */ + + /* Terminating entry */ + { } +}; +MODULE_DEVICE_TABLE(usb, atp_table); + +/* + * number of sensors. Note that only 16 instead of 26 X (horizontal) + * sensors exist on 12" and 15" PowerBooks. All models have 16 Y + * (vertical) sensors. + */ +#define ATP_XSENSORS 26 +#define ATP_YSENSORS 16 + +/* amount of fuzz this touchpad generates */ +#define ATP_FUZZ 16 + +/* maximum pressure this driver will report */ +#define ATP_PRESSURE 300 +/* + * multiplication factor for the X and Y coordinates. + * We try to keep the touchpad aspect ratio while still doing only simple + * arithmetics. + * The factors below give coordinates like: + * + * 0 <= x < 960 on 12" and 15" Powerbooks + * 0 <= x < 1600 on 17" Powerbooks and 17" MacBook Pro + * 0 <= x < 1216 on MacBooks and 15" MacBook Pro + * + * 0 <= y < 646 on all Powerbooks + * 0 <= y < 774 on all MacBooks + */ +#define ATP_XFACT 64 +#define ATP_YFACT 43 + +/* + * Threshold for the touchpad sensors. Any change less than ATP_THRESHOLD is + * ignored. + */ +#define ATP_THRESHOLD 5 + +/* Geyser initialization constants */ +#define ATP_GEYSER_MODE_READ_REQUEST_ID 1 +#define ATP_GEYSER_MODE_WRITE_REQUEST_ID 9 +#define ATP_GEYSER_MODE_REQUEST_VALUE 0x300 +#define ATP_GEYSER_MODE_REQUEST_INDEX 0 +#define ATP_GEYSER_MODE_VENDOR_VALUE 0x04 + +/** + * enum atp_status_bits - status bit meanings + * + * These constants represent the meaning of the status bits. + * (only Geyser 3/4) + * + * @ATP_STATUS_BUTTON: The button was pressed + * @ATP_STATUS_BASE_UPDATE: Update of the base values (untouched pad) + * @ATP_STATUS_FROM_RESET: Reset previously performed + */ +enum atp_status_bits { + ATP_STATUS_BUTTON = BIT(0), + ATP_STATUS_BASE_UPDATE = BIT(2), + ATP_STATUS_FROM_RESET = BIT(4), +}; + +/* Structure to hold all of our device specific stuff */ +struct atp { + char phys[64]; + struct usb_device *udev; /* usb device */ + struct urb *urb; /* usb request block */ + u8 *data; /* transferred data */ + struct input_dev *input; /* input dev */ + enum atp_touchpad_type type; /* type of touchpad */ + bool open; + bool valid; /* are the samples valid? */ + bool size_detect_done; + bool overflow_warned; + int x_old; /* last reported x/y, */ + int y_old; /* used for smoothing */ + signed char xy_cur[ATP_XSENSORS + ATP_YSENSORS]; + signed char xy_old[ATP_XSENSORS + ATP_YSENSORS]; + int xy_acc[ATP_XSENSORS + ATP_YSENSORS]; + int datalen; /* size of USB transfer */ + int idlecount; /* number of empty packets */ + struct work_struct work; +}; + +#define dbg_dump(msg, tab) \ + if (debug > 1) { \ + int __i; \ + printk(KERN_DEBUG "appletouch: %s", msg); \ + for (__i = 0; __i < ATP_XSENSORS + ATP_YSENSORS; __i++) \ + printk(" %02x", tab[__i]); \ + printk("\n"); \ + } + +#define dprintk(format, a...) \ + do { \ + if (debug) \ + printk(KERN_DEBUG format, ##a); \ + } while (0) + +MODULE_AUTHOR("Johannes Berg"); +MODULE_AUTHOR("Stelian Pop"); +MODULE_AUTHOR("Frank Arnold"); +MODULE_AUTHOR("Michael Hanselmann"); +MODULE_AUTHOR("Sven Anders"); +MODULE_DESCRIPTION("Apple PowerBook and MacBook USB touchpad driver"); +MODULE_LICENSE("GPL"); + +/* + * Make the threshold a module parameter + */ +static int threshold = ATP_THRESHOLD; +module_param(threshold, int, 0644); +MODULE_PARM_DESC(threshold, "Discard any change in data from a sensor" + " (the trackpad has many of these sensors)" + " less than this value."); + +static int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Activate debugging output"); + +/* + * By default newer Geyser devices send standard USB HID mouse + * packets (Report ID 2). This code changes device mode, so it + * sends raw sensor reports (Report ID 5). + */ +static int atp_geyser_init(struct usb_device *udev) +{ + char data[8]; + int size; + int i; + + size = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), + ATP_GEYSER_MODE_READ_REQUEST_ID, + USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + ATP_GEYSER_MODE_REQUEST_VALUE, + ATP_GEYSER_MODE_REQUEST_INDEX, &data, 8, 5000); + + if (size != 8) { + dprintk("atp_geyser_init: read error\n"); + for (i = 0; i < 8; i++) + dprintk("appletouch[%d]: %d\n", i, data[i]); + + err("Failed to read mode from device."); + return -EIO; + } + + /* Apply the mode switch */ + data[0] = ATP_GEYSER_MODE_VENDOR_VALUE; + + size = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), + ATP_GEYSER_MODE_WRITE_REQUEST_ID, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + ATP_GEYSER_MODE_REQUEST_VALUE, + ATP_GEYSER_MODE_REQUEST_INDEX, &data, 8, 5000); + + if (size != 8) { + dprintk("atp_geyser_init: write error\n"); + for (i = 0; i < 8; i++) + dprintk("appletouch[%d]: %d\n", i, data[i]); + + err("Failed to request geyser raw mode"); + return -EIO; + } + return 0; +} + +/* + * Reinitialise the device. This usually stops stream of empty packets + * coming from it. + */ +static void atp_reinit(struct work_struct *work) +{ + struct atp *dev = container_of(work, struct atp, work); + struct usb_device *udev = dev->udev; + int retval; + + dprintk("appletouch: putting appletouch to sleep (reinit)\n"); + atp_geyser_init(udev); + + retval = usb_submit_urb(dev->urb, GFP_ATOMIC); + if (retval) + err("atp_reinit: usb_submit_urb failed with error %d", + retval); +} + +static int atp_calculate_abs(int *xy_sensors, int nb_sensors, int fact, + int *z, int *fingers) +{ + int i; + /* values to calculate mean */ + int pcum = 0, psum = 0; + int is_increasing = 0; + + *fingers = 0; + + for (i = 0; i < nb_sensors; i++) { + if (xy_sensors[i] < threshold) { + if (is_increasing) + is_increasing = 0; + + continue; + } + + /* + * Makes the finger detection more versatile. For example, + * two fingers with no gap will be detected. Also, my + * tests show it less likely to have intermittent loss + * of multiple finger readings while moving around (scrolling). + * + * Changes the multiple finger detection to counting humps on + * sensors (transitions from nonincreasing to increasing) + * instead of counting transitions from low sensors (no + * finger reading) to high sensors (finger above + * sensor) + * + * - Jason Parekh <jasonparekh@gmail.com> + */ + if (i < 1 || + (!is_increasing && xy_sensors[i - 1] < xy_sensors[i])) { + (*fingers)++; + is_increasing = 1; + } else if (i > 0 && xy_sensors[i - 1] >= xy_sensors[i]) { + is_increasing = 0; + } + + /* + * Subtracts threshold so a high sensor that just passes the + * threshold won't skew the calculated absolute coordinate. + * Fixes an issue where slowly moving the mouse would + * occasionally jump a number of pixels (slowly moving the + * finger makes this issue most apparent.) + */ + pcum += (xy_sensors[i] - threshold) * i; + psum += (xy_sensors[i] - threshold); + } + + if (psum > 0) { + *z = psum; + return pcum * fact / psum; + } + + return 0; +} + +static inline void atp_report_fingers(struct input_dev *input, int fingers) +{ + input_report_key(input, BTN_TOOL_FINGER, fingers == 1); + input_report_key(input, BTN_TOOL_DOUBLETAP, fingers == 2); + input_report_key(input, BTN_TOOL_TRIPLETAP, fingers > 2); +} + +/* Check URB status and for correct length of data package */ + +#define ATP_URB_STATUS_SUCCESS 0 +#define ATP_URB_STATUS_ERROR 1 +#define ATP_URB_STATUS_ERROR_FATAL 2 + +static int atp_status_check(struct urb *urb) +{ + struct atp *dev = urb->context; + + switch (urb->status) { + case 0: + /* success */ + break; + case -EOVERFLOW: + if (!dev->overflow_warned) { + printk(KERN_WARNING "appletouch: OVERFLOW with data " + "length %d, actual length is %d\n", + dev->datalen, dev->urb->actual_length); + dev->overflow_warned = true; + } + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + /* This urb is terminated, clean up */ + dbg("atp_complete: urb shutting down with status: %d", + urb->status); + return ATP_URB_STATUS_ERROR_FATAL; + + default: + dbg("atp_complete: nonzero urb status received: %d", + urb->status); + return ATP_URB_STATUS_ERROR; + } + + /* drop incomplete datasets */ + if (dev->urb->actual_length != dev->datalen) { + dprintk("appletouch: incomplete data package" + " (first byte: %d, length: %d).\n", + dev->data[0], dev->urb->actual_length); + return ATP_URB_STATUS_ERROR; + } + + return ATP_URB_STATUS_SUCCESS; +} + +/* + * USB interrupt callback functions + */ + +/* Interrupt function for older touchpads: FOUNTAIN/GEYSER1/GEYSER2 */ + +static void atp_complete_geyser_1_2(struct urb *urb) +{ + int x, y, x_z, y_z, x_f, y_f; + int retval, i, j; + int key; + struct atp *dev = urb->context; + int status = atp_status_check(urb); + + if (status == ATP_URB_STATUS_ERROR_FATAL) + return; + else if (status == ATP_URB_STATUS_ERROR) + goto exit; + + /* reorder the sensors values */ + if (dev->type == ATP_GEYSER2) { + memset(dev->xy_cur, 0, sizeof(dev->xy_cur)); + + /* + * The values are laid out like this: + * Y1, Y2, -, Y3, Y4, -, ..., X1, X2, -, X3, X4, -, ... + * '-' is an unused value. + */ + + /* read X values */ + for (i = 0, j = 19; i < 20; i += 2, j += 3) { + dev->xy_cur[i] = dev->data[j]; + dev->xy_cur[i + 1] = dev->data[j + 1]; + } + + /* read Y values */ + for (i = 0, j = 1; i < 9; i += 2, j += 3) { + dev->xy_cur[ATP_XSENSORS + i] = dev->data[j]; + dev->xy_cur[ATP_XSENSORS + i + 1] = dev->data[j + 1]; + } + } else { + for (i = 0; i < 8; i++) { + /* X values */ + dev->xy_cur[i + 0] = dev->data[5 * i + 2]; + dev->xy_cur[i + 8] = dev->data[5 * i + 4]; + dev->xy_cur[i + 16] = dev->data[5 * i + 42]; + if (i < 2) + dev->xy_cur[i + 24] = dev->data[5 * i + 44]; + + /* Y values */ + dev->xy_cur[i + 26] = dev->data[5 * i + 1]; + dev->xy_cur[i + 34] = dev->data[5 * i + 3]; + } + } + + dbg_dump("sample", dev->xy_cur); + + if (!dev->valid) { + /* first sample */ + dev->valid = true; + dev->x_old = dev->y_old = -1; + + /* Store first sample */ + memcpy(dev->xy_old, dev->xy_cur, sizeof(dev->xy_old)); + + /* Perform size detection, if not done already */ + if (!dev->size_detect_done) { + + /* 17" Powerbooks have extra X sensors */ + for (i = (dev->type == ATP_GEYSER2 ? 15 : 16); + i < ATP_XSENSORS; i++) { + if (!dev->xy_cur[i]) + continue; + + printk(KERN_INFO + "appletouch: 17\" model detected.\n"); + + if (dev->type == ATP_GEYSER2) + input_set_abs_params(dev->input, ABS_X, + 0, + (20 - 1) * + ATP_XFACT - 1, + ATP_FUZZ, 0); + else + input_set_abs_params(dev->input, ABS_X, + 0, + (26 - 1) * + ATP_XFACT - 1, + ATP_FUZZ, 0); + break; + } + + dev->size_detect_done = 1; + goto exit; + } + } + + for (i = 0; i < ATP_XSENSORS + ATP_YSENSORS; i++) { + /* accumulate the change */ + signed char change = dev->xy_old[i] - dev->xy_cur[i]; + dev->xy_acc[i] -= change; + + /* prevent down drifting */ + if (dev->xy_acc[i] < 0) + dev->xy_acc[i] = 0; + } + + memcpy(dev->xy_old, dev->xy_cur, sizeof(dev->xy_old)); + + dbg_dump("accumulator", dev->xy_acc); + + x = atp_calculate_abs(dev->xy_acc, ATP_XSENSORS, + ATP_XFACT, &x_z, &x_f); + y = atp_calculate_abs(dev->xy_acc + ATP_XSENSORS, ATP_YSENSORS, + ATP_YFACT, &y_z, &y_f); + key = dev->data[dev->datalen - 1] & ATP_STATUS_BUTTON; + + if (x && y) { + if (dev->x_old != -1) { + x = (dev->x_old * 3 + x) >> 2; + y = (dev->y_old * 3 + y) >> 2; + dev->x_old = x; + dev->y_old = y; + + if (debug > 1) + printk(KERN_DEBUG "appletouch: " + "X: %3d Y: %3d Xz: %3d Yz: %3d\n", + x, y, x_z, y_z); + + input_report_key(dev->input, BTN_TOUCH, 1); + input_report_abs(dev->input, ABS_X, x); + input_report_abs(dev->input, ABS_Y, y); + input_report_abs(dev->input, ABS_PRESSURE, + min(ATP_PRESSURE, x_z + y_z)); + atp_report_fingers(dev->input, max(x_f, y_f)); + } + dev->x_old = x; + dev->y_old = y; + + } else if (!x && !y) { + + dev->x_old = dev->y_old = -1; + input_report_key(dev->input, BTN_TOUCH, 0); + input_report_abs(dev->input, ABS_PRESSURE, 0); + atp_report_fingers(dev->input, 0); + + /* reset the accumulator on release */ + memset(dev->xy_acc, 0, sizeof(dev->xy_acc)); + } + + input_report_key(dev->input, BTN_LEFT, key); + input_sync(dev->input); + + exit: + retval = usb_submit_urb(dev->urb, GFP_ATOMIC); + if (retval) + err("atp_complete: usb_submit_urb failed with result %d", + retval); +} + +/* Interrupt function for older touchpads: GEYSER3/GEYSER4 */ + +static void atp_complete_geyser_3_4(struct urb *urb) +{ + int x, y, x_z, y_z, x_f, y_f; + int retval, i, j; + int key; + struct atp *dev = urb->context; + int status = atp_status_check(urb); + + if (status == ATP_URB_STATUS_ERROR_FATAL) + return; + else if (status == ATP_URB_STATUS_ERROR) + goto exit; + + /* Reorder the sensors values: + * + * The values are laid out like this: + * -, Y1, Y2, -, Y3, Y4, -, ..., -, X1, X2, -, X3, X4, ... + * '-' is an unused value. + */ + + /* read X values */ + for (i = 0, j = 19; i < 20; i += 2, j += 3) { + dev->xy_cur[i] = dev->data[j + 1]; + dev->xy_cur[i + 1] = dev->data[j + 2]; + } + /* read Y values */ + for (i = 0, j = 1; i < 9; i += 2, j += 3) { + dev->xy_cur[ATP_XSENSORS + i] = dev->data[j + 1]; + dev->xy_cur[ATP_XSENSORS + i + 1] = dev->data[j + 2]; + } + + dbg_dump("sample", dev->xy_cur); + + /* Just update the base values (i.e. touchpad in untouched state) */ + if (dev->data[dev->datalen - 1] & ATP_STATUS_BASE_UPDATE) { + + dprintk(KERN_DEBUG "appletouch: updated base values\n"); + + memcpy(dev->xy_old, dev->xy_cur, sizeof(dev->xy_old)); + goto exit; + } + + for (i = 0; i < ATP_XSENSORS + ATP_YSENSORS; i++) { + /* calculate the change */ + dev->xy_acc[i] = dev->xy_cur[i] - dev->xy_old[i]; + + /* this is a round-robin value, so couple with that */ + if (dev->xy_acc[i] > 127) + dev->xy_acc[i] -= 256; + + if (dev->xy_acc[i] < -127) + dev->xy_acc[i] += 256; + + /* prevent down drifting */ + if (dev->xy_acc[i] < 0) + dev->xy_acc[i] = 0; + } + + dbg_dump("accumulator", dev->xy_acc); + + x = atp_calculate_abs(dev->xy_acc, ATP_XSENSORS, + ATP_XFACT, &x_z, &x_f); + y = atp_calculate_abs(dev->xy_acc + ATP_XSENSORS, ATP_YSENSORS, + ATP_YFACT, &y_z, &y_f); + key = dev->data[dev->datalen - 1] & ATP_STATUS_BUTTON; + + if (x && y) { + if (dev->x_old != -1) { + x = (dev->x_old * 3 + x) >> 2; + y = (dev->y_old * 3 + y) >> 2; + dev->x_old = x; + dev->y_old = y; + + if (debug > 1) + printk(KERN_DEBUG "appletouch: X: %3d Y: %3d " + "Xz: %3d Yz: %3d\n", + x, y, x_z, y_z); + + input_report_key(dev->input, BTN_TOUCH, 1); + input_report_abs(dev->input, ABS_X, x); + input_report_abs(dev->input, ABS_Y, y); + input_report_abs(dev->input, ABS_PRESSURE, + min(ATP_PRESSURE, x_z + y_z)); + atp_report_fingers(dev->input, max(x_f, y_f)); + } + dev->x_old = x; + dev->y_old = y; + + } else if (!x && !y) { + + dev->x_old = dev->y_old = -1; + input_report_key(dev->input, BTN_TOUCH, 0); + input_report_abs(dev->input, ABS_PRESSURE, 0); + atp_report_fingers(dev->input, 0); + + /* reset the accumulator on release */ + memset(dev->xy_acc, 0, sizeof(dev->xy_acc)); + } + + input_report_key(dev->input, BTN_LEFT, key); + input_sync(dev->input); + + /* + * Geysers 3/4 will continue to send packets continually after + * the first touch unless reinitialised. Do so if it's been + * idle for a while in order to avoid waking the kernel up + * several hundred times a second. + */ + + /* + * Button must not be pressed when entering suspend, + * otherwise we will never release the button. + */ + if (!x && !y && !key) { + dev->idlecount++; + if (dev->idlecount == 10) { + dev->x_old = dev->y_old = -1; + dev->idlecount = 0; + schedule_work(&dev->work); + /* Don't resubmit urb here, wait for reinit */ + return; + } + } else + dev->idlecount = 0; + + exit: + retval = usb_submit_urb(dev->urb, GFP_ATOMIC); + if (retval) + err("atp_complete: usb_submit_urb failed with result %d", + retval); +} + +static int atp_open(struct input_dev *input) +{ + struct atp *dev = input_get_drvdata(input); + + if (usb_submit_urb(dev->urb, GFP_ATOMIC)) + return -EIO; + + dev->open = 1; + return 0; +} + +static void atp_close(struct input_dev *input) +{ + struct atp *dev = input_get_drvdata(input); + + usb_kill_urb(dev->urb); + cancel_work_sync(&dev->work); + dev->open = 0; +} + +static int atp_handle_geyser(struct atp *dev) +{ + struct usb_device *udev = dev->udev; + + if (dev->type != ATP_FOUNTAIN) { + /* switch to raw sensor mode */ + if (atp_geyser_init(udev)) + return -EIO; + + printk(KERN_INFO "appletouch: Geyser mode initialized.\n"); + } + + return 0; +} + +static int atp_probe(struct usb_interface *iface, + const struct usb_device_id *id) +{ + struct atp *dev; + struct input_dev *input_dev; + struct usb_device *udev = interface_to_usbdev(iface); + struct usb_host_interface *iface_desc; + struct usb_endpoint_descriptor *endpoint; + int int_in_endpointAddr = 0; + int i, error = -ENOMEM; + + /* set up the endpoint information */ + /* use only the first interrupt-in endpoint */ + iface_desc = iface->cur_altsetting; + for (i = 0; i < iface_desc->desc.bNumEndpoints; i++) { + endpoint = &iface_desc->endpoint[i].desc; + if (!int_in_endpointAddr && usb_endpoint_is_int_in(endpoint)) { + /* we found an interrupt in endpoint */ + int_in_endpointAddr = endpoint->bEndpointAddress; + break; + } + } + if (!int_in_endpointAddr) { + err("Could not find int-in endpoint"); + return -EIO; + } + + /* allocate memory for our device state and initialize it */ + dev = kzalloc(sizeof(struct atp), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!dev || !input_dev) { + err("Out of memory"); + goto err_free_devs; + } + + dev->udev = udev; + dev->input = input_dev; + dev->type = id->driver_info; + dev->overflow_warned = false; + if (dev->type == ATP_FOUNTAIN || dev->type == ATP_GEYSER1) + dev->datalen = 81; + else + dev->datalen = 64; + + dev->urb = usb_alloc_urb(0, GFP_KERNEL); + if (!dev->urb) + goto err_free_devs; + + dev->data = usb_buffer_alloc(dev->udev, dev->datalen, GFP_KERNEL, + &dev->urb->transfer_dma); + if (!dev->data) + goto err_free_urb; + + /* Select the USB complete (callback) function */ + if (dev->type == ATP_FOUNTAIN || + dev->type == ATP_GEYSER1 || + dev->type == ATP_GEYSER2) + usb_fill_int_urb(dev->urb, udev, + usb_rcvintpipe(udev, int_in_endpointAddr), + dev->data, dev->datalen, + atp_complete_geyser_1_2, dev, 1); + else + usb_fill_int_urb(dev->urb, udev, + usb_rcvintpipe(udev, int_in_endpointAddr), + dev->data, dev->datalen, + atp_complete_geyser_3_4, dev, 1); + + error = atp_handle_geyser(dev); + if (error) + goto err_free_buffer; + + usb_make_path(udev, dev->phys, sizeof(dev->phys)); + strlcat(dev->phys, "/input0", sizeof(dev->phys)); + + input_dev->name = "appletouch"; + input_dev->phys = dev->phys; + usb_to_input_id(dev->udev, &input_dev->id); + input_dev->dev.parent = &iface->dev; + + input_set_drvdata(input_dev, dev); + + input_dev->open = atp_open; + input_dev->close = atp_close; + + set_bit(EV_ABS, input_dev->evbit); + + if (dev->type == ATP_GEYSER3 || dev->type == ATP_GEYSER4) { + /* + * MacBook have 20 X sensors, 10 Y sensors + */ + input_set_abs_params(input_dev, ABS_X, 0, + ((20 - 1) * ATP_XFACT) - 1, ATP_FUZZ, 0); + input_set_abs_params(input_dev, ABS_Y, 0, + ((10 - 1) * ATP_YFACT) - 1, ATP_FUZZ, 0); + } else if (dev->type == ATP_GEYSER2) { + /* + * Oct 2005 15" PowerBooks have 15 X sensors, 17" are detected + * later. + */ + input_set_abs_params(input_dev, ABS_X, 0, + ((15 - 1) * ATP_XFACT) - 1, ATP_FUZZ, 0); + input_set_abs_params(input_dev, ABS_Y, 0, + ((9 - 1) * ATP_YFACT) - 1, ATP_FUZZ, 0); + } else { + /* + * 12" and 15" Powerbooks only have 16 x sensors, + * 17" models are detected later. + */ + input_set_abs_params(input_dev, ABS_X, 0, + (16 - 1) * ATP_XFACT - 1, + ATP_FUZZ, 0); + input_set_abs_params(input_dev, ABS_Y, 0, + (ATP_YSENSORS - 1) * ATP_YFACT - 1, + ATP_FUZZ, 0); + } + input_set_abs_params(input_dev, ABS_PRESSURE, 0, ATP_PRESSURE, 0, 0); + + set_bit(EV_KEY, input_dev->evbit); + set_bit(BTN_TOUCH, input_dev->keybit); + set_bit(BTN_TOOL_FINGER, input_dev->keybit); + set_bit(BTN_TOOL_DOUBLETAP, input_dev->keybit); + set_bit(BTN_TOOL_TRIPLETAP, input_dev->keybit); + set_bit(BTN_LEFT, input_dev->keybit); + + error = input_register_device(dev->input); + if (error) + goto err_free_buffer; + + /* save our data pointer in this interface device */ + usb_set_intfdata(iface, dev); + + INIT_WORK(&dev->work, atp_reinit); + + return 0; + + err_free_buffer: + usb_buffer_free(dev->udev, dev->datalen, + dev->data, dev->urb->transfer_dma); + err_free_urb: + usb_free_urb(dev->urb); + err_free_devs: + usb_set_intfdata(iface, NULL); + kfree(dev); + input_free_device(input_dev); + return error; +} + +static void atp_disconnect(struct usb_interface *iface) +{ + struct atp *dev = usb_get_intfdata(iface); + + usb_set_intfdata(iface, NULL); + if (dev) { + usb_kill_urb(dev->urb); + input_unregister_device(dev->input); + usb_buffer_free(dev->udev, dev->datalen, + dev->data, dev->urb->transfer_dma); + usb_free_urb(dev->urb); + kfree(dev); + } + printk(KERN_INFO "input: appletouch disconnected\n"); +} + +static int atp_recover(struct atp *dev) +{ + int error; + + error = atp_handle_geyser(dev); + if (error) + return error; + + if (dev->open && usb_submit_urb(dev->urb, GFP_ATOMIC)) + return -EIO; + + return 0; +} + +static int atp_suspend(struct usb_interface *iface, pm_message_t message) +{ + struct atp *dev = usb_get_intfdata(iface); + + usb_kill_urb(dev->urb); + return 0; +} + +static int atp_resume(struct usb_interface *iface) +{ + struct atp *dev = usb_get_intfdata(iface); + + if (dev->open && usb_submit_urb(dev->urb, GFP_ATOMIC)) + return -EIO; + + return 0; +} + +static int atp_reset_resume(struct usb_interface *iface) +{ + struct atp *dev = usb_get_intfdata(iface); + + return atp_recover(dev); +} + +static struct usb_driver atp_driver = { + .name = "appletouch", + .probe = atp_probe, + .disconnect = atp_disconnect, + .suspend = atp_suspend, + .resume = atp_resume, + .reset_resume = atp_reset_resume, + .id_table = atp_table, +}; + +static int __init atp_init(void) +{ + return usb_register(&atp_driver); +} + +static void __exit atp_exit(void) +{ + usb_deregister(&atp_driver); +} + +module_init(atp_init); +module_exit(atp_exit); diff --git a/drivers/input/mouse/atarimouse.c b/drivers/input/mouse/atarimouse.c new file mode 100644 index 0000000..adf45b3 --- /dev/null +++ b/drivers/input/mouse/atarimouse.c @@ -0,0 +1,158 @@ +/* + * Atari mouse driver for Linux/m68k + * + * Copyright (c) 2005 Michael Schmitz + * + * Based on: + * Amiga mouse driver for Linux/m68k + * + * Copyright (c) 2000-2002 Vojtech Pavlik + * + */ +/* + * The low level init and interrupt stuff is handled in arch/mm68k/atari/atakeyb.c + * (the keyboard ACIA also handles the mouse and joystick data, and the keyboard + * interrupt is shared with the MIDI ACIA so MIDI data also get handled there). + * This driver only deals with handing key events off to the input layer. + * + * Largely based on the old: + * + * Atari Mouse Driver for Linux + * by Robert de Vries (robert@and.nl) 19Jul93 + * + * 16 Nov 1994 Andreas Schwab + * Compatibility with busmouse + * Support for three button mouse (shamelessly stolen from MiNT) + * third button wired to one of the joystick directions on joystick 1 + * + * 1996/02/11 Andreas Schwab + * Module support + * Allow multiple open's + * + * Converted to use new generic busmouse code. 5 Apr 1998 + * Russell King <rmk@arm.uk.linux.org> + */ + + +/* + * 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/atarihw.h> +#include <asm/atarikb.h> +#include <asm/atariints.h> + +MODULE_AUTHOR("Michael Schmitz <schmitz@biophys.uni-duesseldorf.de>"); +MODULE_DESCRIPTION("Atari mouse driver"); +MODULE_LICENSE("GPL"); + +static int mouse_threshold[2] = {2, 2}; +module_param_array(mouse_threshold, int, NULL, 0); + +#ifdef FIXED_ATARI_JOYSTICK +extern int atari_mouse_buttons; +#endif + +static struct input_dev *atamouse_dev; + +static void atamouse_interrupt(char *buf) +{ + int buttons, dx, dy; + + buttons = (buf[0] & 1) | ((buf[0] & 2) << 1); +#ifdef FIXED_ATARI_JOYSTICK + buttons |= atari_mouse_buttons & 2; + atari_mouse_buttons = buttons; +#endif + + /* only relative events get here */ + dx = buf[1]; + dy = -buf[2]; + + input_report_rel(atamouse_dev, REL_X, dx); + input_report_rel(atamouse_dev, REL_Y, dy); + + input_report_key(atamouse_dev, BTN_LEFT, buttons & 0x1); + input_report_key(atamouse_dev, BTN_MIDDLE, buttons & 0x2); + input_report_key(atamouse_dev, BTN_RIGHT, buttons & 0x4); + + input_sync(atamouse_dev); + + return; +} + +static int atamouse_open(struct input_dev *dev) +{ +#ifdef FIXED_ATARI_JOYSTICK + atari_mouse_buttons = 0; +#endif + ikbd_mouse_y0_top(); + ikbd_mouse_thresh(mouse_threshold[0], mouse_threshold[1]); + ikbd_mouse_rel_pos(); + atari_input_mouse_interrupt_hook = atamouse_interrupt; + + return 0; +} + +static void atamouse_close(struct input_dev *dev) +{ + ikbd_mouse_disable(); + atari_mouse_interrupt_hook = NULL; +} + +static int __init atamouse_init(void) +{ + int error; + + if (!MACH_IS_ATARI || !ATARIHW_PRESENT(ST_MFP)) + return -ENODEV; + + if (!atari_keyb_init()) + return -ENODEV; + + atamouse_dev = input_allocate_device(); + if (!atamouse_dev) + return -ENOMEM; + + atamouse_dev->name = "Atari mouse"; + atamouse_dev->phys = "atamouse/input0"; + atamouse_dev->id.bustype = BUS_HOST; + atamouse_dev->id.vendor = 0x0001; + atamouse_dev->id.product = 0x0002; + atamouse_dev->id.version = 0x0100; + + atamouse_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REL); + atamouse_dev->relbit[0] = BIT_MASK(REL_X) | BIT_MASK(REL_Y); + atamouse_dev->keybit[BIT_WORD(BTN_LEFT)] = BIT_MASK(BTN_LEFT) | + BIT_MASK(BTN_MIDDLE) | BIT_MASK(BTN_RIGHT); + + atamouse_dev->open = atamouse_open; + atamouse_dev->close = atamouse_close; + + error = input_register_device(atamouse_dev); + if (error) { + input_free_device(atamouse_dev); + return error; + } + + return 0; +} + +static void __exit atamouse_exit(void) +{ + input_unregister_device(atamouse_dev); +} + +module_init(atamouse_init); +module_exit(atamouse_exit); diff --git a/drivers/input/mouse/bcm5974.c b/drivers/input/mouse/bcm5974.c new file mode 100644 index 0000000..2998a6a --- /dev/null +++ b/drivers/input/mouse/bcm5974.c @@ -0,0 +1,726 @@ +/* + * Apple USB BCM5974 (Macbook Air and Penryn Macbook Pro) multitouch driver + * + * Copyright (C) 2008 Henrik Rydberg (rydberg@euromail.se) + * + * The USB initialization and package decoding was made by + * Scott Shawcroft as part of the touchd user-space driver project: + * Copyright (C) 2008 Scott Shawcroft (scott.shawcroft@gmail.com) + * + * The BCM5974 driver is based on the appletouch driver: + * Copyright (C) 2001-2004 Greg Kroah-Hartman (greg@kroah.com) + * Copyright (C) 2005 Johannes Berg (johannes@sipsolutions.net) + * Copyright (C) 2005 Stelian Pop (stelian@popies.net) + * Copyright (C) 2005 Frank Arnold (frank@scirocco-5v-turbo.de) + * Copyright (C) 2005 Peter Osterlund (petero2@telia.com) + * Copyright (C) 2005 Michael Hanselmann (linux-kernel@hansmi.ch) + * Copyright (C) 2006 Nicolas Boichat (nicolas@boichat.ch) + * + * 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 <linux/kernel.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/usb/input.h> +#include <linux/hid.h> +#include <linux/mutex.h> + +#define USB_VENDOR_ID_APPLE 0x05ac + +/* MacbookAir, aka wellspring */ +#define USB_DEVICE_ID_APPLE_WELLSPRING_ANSI 0x0223 +#define USB_DEVICE_ID_APPLE_WELLSPRING_ISO 0x0224 +#define USB_DEVICE_ID_APPLE_WELLSPRING_JIS 0x0225 +/* MacbookProPenryn, aka wellspring2 */ +#define USB_DEVICE_ID_APPLE_WELLSPRING2_ANSI 0x0230 +#define USB_DEVICE_ID_APPLE_WELLSPRING2_ISO 0x0231 +#define USB_DEVICE_ID_APPLE_WELLSPRING2_JIS 0x0232 + +#define BCM5974_DEVICE(prod) { \ + .match_flags = (USB_DEVICE_ID_MATCH_DEVICE | \ + USB_DEVICE_ID_MATCH_INT_CLASS | \ + USB_DEVICE_ID_MATCH_INT_PROTOCOL), \ + .idVendor = USB_VENDOR_ID_APPLE, \ + .idProduct = (prod), \ + .bInterfaceClass = USB_INTERFACE_CLASS_HID, \ + .bInterfaceProtocol = USB_INTERFACE_PROTOCOL_MOUSE \ +} + +/* table of devices that work with this driver */ +static const struct usb_device_id bcm5974_table[] = { + /* MacbookAir1.1 */ + BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING_ANSI), + BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING_ISO), + BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING_JIS), + /* MacbookProPenryn */ + BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING2_ANSI), + BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING2_ISO), + BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING2_JIS), + /* Terminating entry */ + {} +}; +MODULE_DEVICE_TABLE(usb, bcm5974_table); + +MODULE_AUTHOR("Henrik Rydberg"); +MODULE_DESCRIPTION("Apple USB BCM5974 multitouch driver"); +MODULE_LICENSE("GPL"); + +#define dprintk(level, format, a...)\ + { if (debug >= level) printk(KERN_DEBUG format, ##a); } + +static int debug = 1; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Activate debugging output"); + +/* button data structure */ +struct bt_data { + u8 unknown1; /* constant */ + u8 button; /* left button */ + u8 rel_x; /* relative x coordinate */ + u8 rel_y; /* relative y coordinate */ +}; + +/* trackpad header structure */ +struct tp_header { + u8 unknown1[16]; /* constants, timers, etc */ + u8 fingers; /* number of fingers on trackpad */ + u8 unknown2[9]; /* constants, timers, etc */ +}; + +/* trackpad finger structure */ +struct tp_finger { + __le16 origin; /* zero when switching track finger */ + __le16 abs_x; /* absolute x coodinate */ + __le16 abs_y; /* absolute y coodinate */ + __le16 rel_x; /* relative x coodinate */ + __le16 rel_y; /* relative y coodinate */ + __le16 size_major; /* finger size, major axis? */ + __le16 size_minor; /* finger size, minor axis? */ + __le16 orientation; /* 16384 when point, else 15 bit angle */ + __le16 force_major; /* trackpad force, major axis? */ + __le16 force_minor; /* trackpad force, minor axis? */ + __le16 unused[3]; /* zeros */ + __le16 multi; /* one finger: varies, more fingers: constant */ +}; + +/* trackpad data structure, empirically at least ten fingers */ +struct tp_data { + struct tp_header header; + struct tp_finger finger[16]; +}; + +/* device-specific parameters */ +struct bcm5974_param { + int dim; /* logical dimension */ + int fuzz; /* logical noise value */ + int devmin; /* device minimum reading */ + int devmax; /* device maximum reading */ +}; + +/* device-specific configuration */ +struct bcm5974_config { + int ansi, iso, jis; /* the product id of this device */ + int bt_ep; /* the endpoint of the button interface */ + int bt_datalen; /* data length of the button interface */ + int tp_ep; /* the endpoint of the trackpad interface */ + int tp_datalen; /* data length of the trackpad interface */ + struct bcm5974_param p; /* finger pressure limits */ + struct bcm5974_param w; /* finger width limits */ + struct bcm5974_param x; /* horizontal limits */ + struct bcm5974_param y; /* vertical limits */ +}; + +/* logical device structure */ +struct bcm5974 { + char phys[64]; + struct usb_device *udev; /* usb device */ + struct usb_interface *intf; /* our interface */ + struct input_dev *input; /* input dev */ + struct bcm5974_config cfg; /* device configuration */ + struct mutex pm_mutex; /* serialize access to open/suspend */ + int opened; /* 1: opened, 0: closed */ + struct urb *bt_urb; /* button usb request block */ + struct bt_data *bt_data; /* button transferred data */ + struct urb *tp_urb; /* trackpad usb request block */ + struct tp_data *tp_data; /* trackpad transferred data */ + int fingers; /* number of fingers on trackpad */ +}; + +/* logical dimensions */ +#define DIM_PRESSURE 256 /* maximum finger pressure */ +#define DIM_WIDTH 16 /* maximum finger width */ +#define DIM_X 1280 /* maximum trackpad x value */ +#define DIM_Y 800 /* maximum trackpad y value */ + +/* logical signal quality */ +#define SN_PRESSURE 45 /* pressure signal-to-noise ratio */ +#define SN_WIDTH 100 /* width signal-to-noise ratio */ +#define SN_COORD 250 /* coordinate signal-to-noise ratio */ + +/* pressure thresholds */ +#define PRESSURE_LOW (2 * DIM_PRESSURE / SN_PRESSURE) +#define PRESSURE_HIGH (3 * PRESSURE_LOW) + +/* device constants */ +static const struct bcm5974_config bcm5974_config_table[] = { + { + USB_DEVICE_ID_APPLE_WELLSPRING_ANSI, + USB_DEVICE_ID_APPLE_WELLSPRING_ISO, + USB_DEVICE_ID_APPLE_WELLSPRING_JIS, + 0x84, sizeof(struct bt_data), + 0x81, sizeof(struct tp_data), + { DIM_PRESSURE, DIM_PRESSURE / SN_PRESSURE, 0, 256 }, + { DIM_WIDTH, DIM_WIDTH / SN_WIDTH, 0, 2048 }, + { DIM_X, DIM_X / SN_COORD, -4824, 5342 }, + { DIM_Y, DIM_Y / SN_COORD, -172, 5820 } + }, + { + USB_DEVICE_ID_APPLE_WELLSPRING2_ANSI, + USB_DEVICE_ID_APPLE_WELLSPRING2_ISO, + USB_DEVICE_ID_APPLE_WELLSPRING2_JIS, + 0x84, sizeof(struct bt_data), + 0x81, sizeof(struct tp_data), + { DIM_PRESSURE, DIM_PRESSURE / SN_PRESSURE, 0, 256 }, + { DIM_WIDTH, DIM_WIDTH / SN_WIDTH, 0, 2048 }, + { DIM_X, DIM_X / SN_COORD, -4824, 4824 }, + { DIM_Y, DIM_Y / SN_COORD, -172, 4290 } + }, + {} +}; + +/* return the device-specific configuration by device */ +static const struct bcm5974_config *bcm5974_get_config(struct usb_device *udev) +{ + u16 id = le16_to_cpu(udev->descriptor.idProduct); + const struct bcm5974_config *cfg; + + for (cfg = bcm5974_config_table; cfg->ansi; ++cfg) + if (cfg->ansi == id || cfg->iso == id || cfg->jis == id) + return cfg; + + return bcm5974_config_table; +} + +/* convert 16-bit little endian to signed integer */ +static inline int raw2int(__le16 x) +{ + return (signed short)le16_to_cpu(x); +} + +/* scale device data to logical dimensions (asserts devmin < devmax) */ +static inline int int2scale(const struct bcm5974_param *p, int x) +{ + return x * p->dim / (p->devmax - p->devmin); +} + +/* all logical value ranges are [0,dim). */ +static inline int int2bound(const struct bcm5974_param *p, int x) +{ + int s = int2scale(p, x); + + return clamp_val(s, 0, p->dim - 1); +} + +/* setup which logical events to report */ +static void setup_events_to_report(struct input_dev *input_dev, + const struct bcm5974_config *cfg) +{ + __set_bit(EV_ABS, input_dev->evbit); + + input_set_abs_params(input_dev, ABS_PRESSURE, + 0, cfg->p.dim, cfg->p.fuzz, 0); + input_set_abs_params(input_dev, ABS_TOOL_WIDTH, + 0, cfg->w.dim, cfg->w.fuzz, 0); + input_set_abs_params(input_dev, ABS_X, + 0, cfg->x.dim, cfg->x.fuzz, 0); + input_set_abs_params(input_dev, ABS_Y, + 0, cfg->y.dim, cfg->y.fuzz, 0); + + __set_bit(EV_KEY, input_dev->evbit); + __set_bit(BTN_TOUCH, input_dev->keybit); + __set_bit(BTN_TOOL_FINGER, input_dev->keybit); + __set_bit(BTN_TOOL_DOUBLETAP, input_dev->keybit); + __set_bit(BTN_TOOL_TRIPLETAP, input_dev->keybit); + __set_bit(BTN_LEFT, input_dev->keybit); +} + +/* report button data as logical button state */ +static int report_bt_state(struct bcm5974 *dev, int size) +{ + if (size != sizeof(struct bt_data)) + return -EIO; + + input_report_key(dev->input, BTN_LEFT, dev->bt_data->button); + input_sync(dev->input); + + return 0; +} + +/* report trackpad data as logical trackpad state */ +static int report_tp_state(struct bcm5974 *dev, int size) +{ + const struct bcm5974_config *c = &dev->cfg; + const struct tp_finger *f = dev->tp_data->finger; + struct input_dev *input = dev->input; + const int fingers = (size - 26) / 28; + int raw_p, raw_w, raw_x, raw_y; + int ptest = 0, origin = 0, nmin = 0, nmax = 0; + int abs_p = 0, abs_w = 0, abs_x = 0, abs_y = 0; + + if (size < 26 || (size - 26) % 28 != 0) + return -EIO; + + /* always track the first finger; when detached, start over */ + if (fingers) { + raw_p = raw2int(f->force_major); + raw_w = raw2int(f->size_major); + raw_x = raw2int(f->abs_x); + raw_y = raw2int(f->abs_y); + + dprintk(9, + "bcm5974: raw: p: %+05d w: %+05d x: %+05d y: %+05d\n", + raw_p, raw_w, raw_x, raw_y); + + ptest = int2bound(&c->p, raw_p); + origin = raw2int(f->origin); + } + + /* while tracking finger still valid, count all fingers */ + if (ptest > PRESSURE_LOW && origin) { + abs_p = ptest; + abs_w = int2bound(&c->w, raw_w); + abs_x = int2bound(&c->x, raw_x - c->x.devmin); + abs_y = int2bound(&c->y, c->y.devmax - raw_y); + for (; f != dev->tp_data->finger + fingers; f++) { + ptest = int2bound(&c->p, raw2int(f->force_major)); + if (ptest > PRESSURE_LOW) + nmax++; + if (ptest > PRESSURE_HIGH) + nmin++; + } + } + + if (dev->fingers < nmin) + dev->fingers = nmin; + if (dev->fingers > nmax) + dev->fingers = nmax; + + input_report_key(input, BTN_TOUCH, dev->fingers > 0); + input_report_key(input, BTN_TOOL_FINGER, dev->fingers == 1); + input_report_key(input, BTN_TOOL_DOUBLETAP, dev->fingers == 2); + input_report_key(input, BTN_TOOL_TRIPLETAP, dev->fingers > 2); + + input_report_abs(input, ABS_PRESSURE, abs_p); + input_report_abs(input, ABS_TOOL_WIDTH, abs_w); + + if (abs_p) { + input_report_abs(input, ABS_X, abs_x); + input_report_abs(input, ABS_Y, abs_y); + + dprintk(8, + "bcm5974: abs: p: %+05d w: %+05d x: %+05d y: %+05d " + "nmin: %d nmax: %d n: %d\n", + abs_p, abs_w, abs_x, abs_y, nmin, nmax, dev->fingers); + + } + + input_sync(input); + + return 0; +} + +/* Wellspring initialization constants */ +#define BCM5974_WELLSPRING_MODE_READ_REQUEST_ID 1 +#define BCM5974_WELLSPRING_MODE_WRITE_REQUEST_ID 9 +#define BCM5974_WELLSPRING_MODE_REQUEST_VALUE 0x300 +#define BCM5974_WELLSPRING_MODE_REQUEST_INDEX 0 +#define BCM5974_WELLSPRING_MODE_VENDOR_VALUE 0x01 +#define BCM5974_WELLSPRING_MODE_NORMAL_VALUE 0x08 + +static int bcm5974_wellspring_mode(struct bcm5974 *dev, bool on) +{ + char *data = kmalloc(8, GFP_KERNEL); + int retval = 0, size; + + if (!data) { + err("bcm5974: out of memory"); + retval = -ENOMEM; + goto out; + } + + /* read configuration */ + size = usb_control_msg(dev->udev, usb_rcvctrlpipe(dev->udev, 0), + BCM5974_WELLSPRING_MODE_READ_REQUEST_ID, + USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + BCM5974_WELLSPRING_MODE_REQUEST_VALUE, + BCM5974_WELLSPRING_MODE_REQUEST_INDEX, data, 8, 5000); + + if (size != 8) { + err("bcm5974: could not read from device"); + retval = -EIO; + goto out; + } + + /* apply the mode switch */ + data[0] = on ? + BCM5974_WELLSPRING_MODE_VENDOR_VALUE : + BCM5974_WELLSPRING_MODE_NORMAL_VALUE; + + /* write configuration */ + size = usb_control_msg(dev->udev, usb_sndctrlpipe(dev->udev, 0), + BCM5974_WELLSPRING_MODE_WRITE_REQUEST_ID, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + BCM5974_WELLSPRING_MODE_REQUEST_VALUE, + BCM5974_WELLSPRING_MODE_REQUEST_INDEX, data, 8, 5000); + + if (size != 8) { + err("bcm5974: could not write to device"); + retval = -EIO; + goto out; + } + + dprintk(2, "bcm5974: switched to %s mode.\n", + on ? "wellspring" : "normal"); + + out: + kfree(data); + return retval; +} + +static void bcm5974_irq_button(struct urb *urb) +{ + struct bcm5974 *dev = urb->context; + int error; + + switch (urb->status) { + case 0: + break; + case -EOVERFLOW: + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + dbg("bcm5974: button urb shutting down: %d", urb->status); + return; + default: + dbg("bcm5974: button urb status: %d", urb->status); + goto exit; + } + + if (report_bt_state(dev, dev->bt_urb->actual_length)) + dprintk(1, "bcm5974: bad button package, length: %d\n", + dev->bt_urb->actual_length); + +exit: + error = usb_submit_urb(dev->bt_urb, GFP_ATOMIC); + if (error) + err("bcm5974: button urb failed: %d", error); +} + +static void bcm5974_irq_trackpad(struct urb *urb) +{ + struct bcm5974 *dev = urb->context; + int error; + + switch (urb->status) { + case 0: + break; + case -EOVERFLOW: + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + dbg("bcm5974: trackpad urb shutting down: %d", urb->status); + return; + default: + dbg("bcm5974: trackpad urb status: %d", urb->status); + goto exit; + } + + /* control response ignored */ + if (dev->tp_urb->actual_length == 2) + goto exit; + + if (report_tp_state(dev, dev->tp_urb->actual_length)) + dprintk(1, "bcm5974: bad trackpad package, length: %d\n", + dev->tp_urb->actual_length); + +exit: + error = usb_submit_urb(dev->tp_urb, GFP_ATOMIC); + if (error) + err("bcm5974: trackpad urb failed: %d", error); +} + +/* + * The Wellspring trackpad, like many recent Apple trackpads, share + * the usb device with the keyboard. Since keyboards are usually + * handled by the HID system, the device ends up being handled by two + * modules. Setting up the device therefore becomes slightly + * complicated. To enable multitouch features, a mode switch is + * required, which is usually applied via the control interface of the + * device. It can be argued where this switch should take place. In + * some drivers, like appletouch, the switch is made during + * probe. However, the hid module may also alter the state of the + * device, resulting in trackpad malfunction under certain + * circumstances. To get around this problem, there is at least one + * example that utilizes the USB_QUIRK_RESET_RESUME quirk in order to + * recieve a reset_resume request rather than the normal resume. + * Since the implementation of reset_resume is equal to mode switch + * plus start_traffic, it seems easier to always do the switch when + * starting traffic on the device. + */ +static int bcm5974_start_traffic(struct bcm5974 *dev) +{ + if (bcm5974_wellspring_mode(dev, true)) { + dprintk(1, "bcm5974: mode switch failed\n"); + goto error; + } + + if (usb_submit_urb(dev->bt_urb, GFP_KERNEL)) + goto error; + + if (usb_submit_urb(dev->tp_urb, GFP_KERNEL)) + goto err_kill_bt; + + return 0; + +err_kill_bt: + usb_kill_urb(dev->bt_urb); +error: + return -EIO; +} + +static void bcm5974_pause_traffic(struct bcm5974 *dev) +{ + usb_kill_urb(dev->tp_urb); + usb_kill_urb(dev->bt_urb); + bcm5974_wellspring_mode(dev, false); +} + +/* + * The code below implements open/close and manual suspend/resume. + * All functions may be called in random order. + * + * Opening a suspended device fails with EACCES - permission denied. + * + * Failing a resume leaves the device resumed but closed. + */ +static int bcm5974_open(struct input_dev *input) +{ + struct bcm5974 *dev = input_get_drvdata(input); + int error; + + error = usb_autopm_get_interface(dev->intf); + if (error) + return error; + + mutex_lock(&dev->pm_mutex); + + error = bcm5974_start_traffic(dev); + if (!error) + dev->opened = 1; + + mutex_unlock(&dev->pm_mutex); + + if (error) + usb_autopm_put_interface(dev->intf); + + return error; +} + +static void bcm5974_close(struct input_dev *input) +{ + struct bcm5974 *dev = input_get_drvdata(input); + + mutex_lock(&dev->pm_mutex); + + bcm5974_pause_traffic(dev); + dev->opened = 0; + + mutex_unlock(&dev->pm_mutex); + + usb_autopm_put_interface(dev->intf); +} + +static int bcm5974_suspend(struct usb_interface *iface, pm_message_t message) +{ + struct bcm5974 *dev = usb_get_intfdata(iface); + + mutex_lock(&dev->pm_mutex); + + if (dev->opened) + bcm5974_pause_traffic(dev); + + mutex_unlock(&dev->pm_mutex); + + return 0; +} + +static int bcm5974_resume(struct usb_interface *iface) +{ + struct bcm5974 *dev = usb_get_intfdata(iface); + int error = 0; + + mutex_lock(&dev->pm_mutex); + + if (dev->opened) + error = bcm5974_start_traffic(dev); + + mutex_unlock(&dev->pm_mutex); + + return error; +} + +static int bcm5974_probe(struct usb_interface *iface, + const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(iface); + const struct bcm5974_config *cfg; + struct bcm5974 *dev; + struct input_dev *input_dev; + int error = -ENOMEM; + + /* find the product index */ + cfg = bcm5974_get_config(udev); + + /* allocate memory for our device state and initialize it */ + dev = kzalloc(sizeof(struct bcm5974), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!dev || !input_dev) { + err("bcm5974: out of memory"); + goto err_free_devs; + } + + dev->udev = udev; + dev->intf = iface; + dev->input = input_dev; + dev->cfg = *cfg; + mutex_init(&dev->pm_mutex); + + /* setup urbs */ + dev->bt_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!dev->bt_urb) + goto err_free_devs; + + dev->tp_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!dev->tp_urb) + goto err_free_bt_urb; + + dev->bt_data = usb_buffer_alloc(dev->udev, + dev->cfg.bt_datalen, GFP_KERNEL, + &dev->bt_urb->transfer_dma); + if (!dev->bt_data) + goto err_free_urb; + + dev->tp_data = usb_buffer_alloc(dev->udev, + dev->cfg.tp_datalen, GFP_KERNEL, + &dev->tp_urb->transfer_dma); + if (!dev->tp_data) + goto err_free_bt_buffer; + + usb_fill_int_urb(dev->bt_urb, udev, + usb_rcvintpipe(udev, cfg->bt_ep), + dev->bt_data, dev->cfg.bt_datalen, + bcm5974_irq_button, dev, 1); + + usb_fill_int_urb(dev->tp_urb, udev, + usb_rcvintpipe(udev, cfg->tp_ep), + dev->tp_data, dev->cfg.tp_datalen, + bcm5974_irq_trackpad, dev, 1); + + /* create bcm5974 device */ + usb_make_path(udev, dev->phys, sizeof(dev->phys)); + strlcat(dev->phys, "/input0", sizeof(dev->phys)); + + input_dev->name = "bcm5974"; + input_dev->phys = dev->phys; + usb_to_input_id(dev->udev, &input_dev->id); + input_dev->dev.parent = &iface->dev; + + input_set_drvdata(input_dev, dev); + + input_dev->open = bcm5974_open; + input_dev->close = bcm5974_close; + + setup_events_to_report(input_dev, cfg); + + error = input_register_device(dev->input); + if (error) + goto err_free_buffer; + + /* save our data pointer in this interface device */ + usb_set_intfdata(iface, dev); + + return 0; + +err_free_buffer: + usb_buffer_free(dev->udev, dev->cfg.tp_datalen, + dev->tp_data, dev->tp_urb->transfer_dma); +err_free_bt_buffer: + usb_buffer_free(dev->udev, dev->cfg.bt_datalen, + dev->bt_data, dev->bt_urb->transfer_dma); +err_free_urb: + usb_free_urb(dev->tp_urb); +err_free_bt_urb: + usb_free_urb(dev->bt_urb); +err_free_devs: + usb_set_intfdata(iface, NULL); + input_free_device(input_dev); + kfree(dev); + return error; +} + +static void bcm5974_disconnect(struct usb_interface *iface) +{ + struct bcm5974 *dev = usb_get_intfdata(iface); + + usb_set_intfdata(iface, NULL); + + input_unregister_device(dev->input); + usb_buffer_free(dev->udev, dev->cfg.tp_datalen, + dev->tp_data, dev->tp_urb->transfer_dma); + usb_buffer_free(dev->udev, dev->cfg.bt_datalen, + dev->bt_data, dev->bt_urb->transfer_dma); + usb_free_urb(dev->tp_urb); + usb_free_urb(dev->bt_urb); + kfree(dev); +} + +static struct usb_driver bcm5974_driver = { + .name = "bcm5974", + .probe = bcm5974_probe, + .disconnect = bcm5974_disconnect, + .suspend = bcm5974_suspend, + .resume = bcm5974_resume, + .reset_resume = bcm5974_resume, + .id_table = bcm5974_table, + .supports_autosuspend = 1, +}; + +static int __init bcm5974_init(void) +{ + return usb_register(&bcm5974_driver); +} + +static void __exit bcm5974_exit(void) +{ + usb_deregister(&bcm5974_driver); +} + +module_init(bcm5974_init); +module_exit(bcm5974_exit); + diff --git a/drivers/input/mouse/elantech.c b/drivers/input/mouse/elantech.c new file mode 100644 index 0000000..b9a25d5 --- /dev/null +++ b/drivers/input/mouse/elantech.c @@ -0,0 +1,674 @@ +/* + * Elantech Touchpad driver (v5) + * + * Copyright (C) 2007-2008 Arjan Opmeer <arjan@opmeer.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. + * + * Trademarks are the property of their respective owners. + */ + +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/input.h> +#include <linux/serio.h> +#include <linux/libps2.h> +#include "psmouse.h" +#include "elantech.h" + +#define elantech_debug(format, arg...) \ + do { \ + if (etd->debug) \ + printk(KERN_DEBUG format, ##arg); \ + } while (0) + +/* + * Send a Synaptics style sliced query command + */ +static int synaptics_send_cmd(struct psmouse *psmouse, unsigned char c, + unsigned char *param) +{ + if (psmouse_sliced_command(psmouse, c) || + ps2_command(&psmouse->ps2dev, param, PSMOUSE_CMD_GETINFO)) { + pr_err("elantech.c: synaptics_send_cmd query 0x%02x failed.\n", c); + return -1; + } + + return 0; +} + +/* + * A retrying version of ps2_command + */ +static int elantech_ps2_command(struct psmouse *psmouse, + unsigned char *param, int command) +{ + struct ps2dev *ps2dev = &psmouse->ps2dev; + struct elantech_data *etd = psmouse->private; + int rc; + int tries = ETP_PS2_COMMAND_TRIES; + + do { + rc = ps2_command(ps2dev, param, command); + if (rc == 0) + break; + tries--; + elantech_debug("elantech.c: retrying ps2 command 0x%02x (%d).\n", + command, tries); + msleep(ETP_PS2_COMMAND_DELAY); + } while (tries > 0); + + if (rc) + pr_err("elantech.c: ps2 command 0x%02x failed.\n", command); + + return rc; +} + +/* + * Send an Elantech style special command to read a value from a register + */ +static int elantech_read_reg(struct psmouse *psmouse, unsigned char reg, + unsigned char *val) +{ + struct elantech_data *etd = psmouse->private; + unsigned char param[3]; + int rc = 0; + + if (reg < 0x10 || reg > 0x26) + return -1; + + if (reg > 0x11 && reg < 0x20) + return -1; + + switch (etd->hw_version) { + case 1: + if (psmouse_sliced_command(psmouse, ETP_REGISTER_READ) || + psmouse_sliced_command(psmouse, reg) || + ps2_command(&psmouse->ps2dev, param, PSMOUSE_CMD_GETINFO)) { + rc = -1; + } + break; + + case 2: + if (elantech_ps2_command(psmouse, NULL, ETP_PS2_CUSTOM_COMMAND) || + elantech_ps2_command(psmouse, NULL, ETP_REGISTER_READ) || + elantech_ps2_command(psmouse, NULL, ETP_PS2_CUSTOM_COMMAND) || + elantech_ps2_command(psmouse, NULL, reg) || + elantech_ps2_command(psmouse, param, PSMOUSE_CMD_GETINFO)) { + rc = -1; + } + break; + } + + if (rc) + pr_err("elantech.c: failed to read register 0x%02x.\n", reg); + else + *val = param[0]; + + return rc; +} + +/* + * Send an Elantech style special command to write a register with a value + */ +static int elantech_write_reg(struct psmouse *psmouse, unsigned char reg, + unsigned char val) +{ + struct elantech_data *etd = psmouse->private; + int rc = 0; + + if (reg < 0x10 || reg > 0x26) + return -1; + + if (reg > 0x11 && reg < 0x20) + return -1; + + switch (etd->hw_version) { + case 1: + if (psmouse_sliced_command(psmouse, ETP_REGISTER_WRITE) || + psmouse_sliced_command(psmouse, reg) || + psmouse_sliced_command(psmouse, val) || + ps2_command(&psmouse->ps2dev, NULL, PSMOUSE_CMD_SETSCALE11)) { + rc = -1; + } + break; + + case 2: + if (elantech_ps2_command(psmouse, NULL, ETP_PS2_CUSTOM_COMMAND) || + elantech_ps2_command(psmouse, NULL, ETP_REGISTER_WRITE) || + elantech_ps2_command(psmouse, NULL, ETP_PS2_CUSTOM_COMMAND) || + elantech_ps2_command(psmouse, NULL, reg) || + elantech_ps2_command(psmouse, NULL, ETP_PS2_CUSTOM_COMMAND) || + elantech_ps2_command(psmouse, NULL, val) || + elantech_ps2_command(psmouse, NULL, PSMOUSE_CMD_SETSCALE11)) { + rc = -1; + } + break; + } + + if (rc) + pr_err("elantech.c: failed to write register 0x%02x with value 0x%02x.\n", + reg, val); + + return rc; +} + +/* + * Dump a complete mouse movement packet to the syslog + */ +static void elantech_packet_dump(unsigned char *packet, int size) +{ + int i; + + printk(KERN_DEBUG "elantech.c: PS/2 packet ["); + for (i = 0; i < size; i++) + printk("%s0x%02x ", (i) ? ", " : " ", packet[i]); + printk("]\n"); +} + +/* + * Interpret complete data packets and report absolute mode input events for + * hardware version 1. (4 byte packets) + */ +static void elantech_report_absolute_v1(struct psmouse *psmouse) +{ + struct input_dev *dev = psmouse->dev; + struct elantech_data *etd = psmouse->private; + unsigned char *packet = psmouse->packet; + int fingers; + + if (etd->fw_version_maj == 0x01) { + /* byte 0: D U p1 p2 1 p3 R L + byte 1: f 0 th tw x9 x8 y9 y8 */ + fingers = ((packet[1] & 0x80) >> 7) + + ((packet[1] & 0x30) >> 4); + } else { + /* byte 0: n1 n0 p2 p1 1 p3 R L + byte 1: 0 0 0 0 x9 x8 y9 y8 */ + fingers = (packet[0] & 0xc0) >> 6; + } + + input_report_key(dev, BTN_TOUCH, fingers != 0); + + /* byte 2: x7 x6 x5 x4 x3 x2 x1 x0 + byte 3: y7 y6 y5 y4 y3 y2 y1 y0 */ + if (fingers) { + input_report_abs(dev, ABS_X, + ((packet[1] & 0x0c) << 6) | packet[2]); + input_report_abs(dev, ABS_Y, ETP_YMAX_V1 - + (((packet[1] & 0x03) << 8) | packet[3])); + } + + input_report_key(dev, BTN_TOOL_FINGER, fingers == 1); + input_report_key(dev, BTN_TOOL_DOUBLETAP, fingers == 2); + input_report_key(dev, BTN_TOOL_TRIPLETAP, fingers == 3); + input_report_key(dev, BTN_LEFT, packet[0] & 0x01); + input_report_key(dev, BTN_RIGHT, packet[0] & 0x02); + + if ((etd->fw_version_maj == 0x01) && + (etd->capabilities & ETP_CAP_HAS_ROCKER)) { + /* rocker up */ + input_report_key(dev, BTN_FORWARD, packet[0] & 0x40); + /* rocker down */ + input_report_key(dev, BTN_BACK, packet[0] & 0x80); + } + + input_sync(dev); +} + +/* + * Interpret complete data packets and report absolute mode input events for + * hardware version 2. (6 byte packets) + */ +static void elantech_report_absolute_v2(struct psmouse *psmouse) +{ + struct input_dev *dev = psmouse->dev; + unsigned char *packet = psmouse->packet; + int fingers, x1, y1, x2, y2; + + /* byte 0: n1 n0 . . . . R L */ + fingers = (packet[0] & 0xc0) >> 6; + input_report_key(dev, BTN_TOUCH, fingers != 0); + + switch (fingers) { + case 1: + /* byte 1: x15 x14 x13 x12 x11 x10 x9 x8 + byte 2: x7 x6 x5 x4 x4 x2 x1 x0 */ + input_report_abs(dev, ABS_X, (packet[1] << 8) | packet[2]); + /* byte 4: y15 y14 y13 y12 y11 y10 y8 y8 + byte 5: y7 y6 y5 y4 y3 y2 y1 y0 */ + input_report_abs(dev, ABS_Y, ETP_YMAX_V2 - + ((packet[4] << 8) | packet[5])); + break; + + case 2: + /* The coordinate of each finger is reported separately with + a lower resolution for two finger touches */ + /* byte 0: . . ay8 ax8 . . . . + byte 1: ax7 ax6 ax5 ax4 ax3 ax2 ax1 ax0 */ + x1 = ((packet[0] & 0x10) << 4) | packet[1]; + /* byte 2: ay7 ay6 ay5 ay4 ay3 ay2 ay1 ay0 */ + y1 = ETP_2FT_YMAX - (((packet[0] & 0x20) << 3) | packet[2]); + /* byte 3: . . by8 bx8 . . . . + byte 4: bx7 bx6 bx5 bx4 bx3 bx2 bx1 bx0 */ + x2 = ((packet[3] & 0x10) << 4) | packet[4]; + /* byte 5: by7 by8 by5 by4 by3 by2 by1 by0 */ + y2 = ETP_2FT_YMAX - (((packet[3] & 0x20) << 3) | packet[5]); + /* For compatibility with the X Synaptics driver scale up one + coordinate and report as ordinary mouse movent */ + input_report_abs(dev, ABS_X, x1 << 2); + input_report_abs(dev, ABS_Y, y1 << 2); + /* For compatibility with the proprietary X Elantech driver + report both coordinates as hat coordinates */ + input_report_abs(dev, ABS_HAT0X, x1); + input_report_abs(dev, ABS_HAT0Y, y1); + input_report_abs(dev, ABS_HAT1X, x2); + input_report_abs(dev, ABS_HAT1Y, y2); + break; + } + + input_report_key(dev, BTN_TOOL_FINGER, fingers == 1); + input_report_key(dev, BTN_TOOL_DOUBLETAP, fingers == 2); + input_report_key(dev, BTN_TOOL_TRIPLETAP, fingers == 3); + input_report_key(dev, BTN_LEFT, packet[0] & 0x01); + input_report_key(dev, BTN_RIGHT, packet[0] & 0x02); + + input_sync(dev); +} + +static int elantech_check_parity_v1(struct psmouse *psmouse) +{ + struct elantech_data *etd = psmouse->private; + unsigned char *packet = psmouse->packet; + unsigned char p1, p2, p3; + + /* Parity bits are placed differently */ + if (etd->fw_version_maj == 0x01) { + /* byte 0: D U p1 p2 1 p3 R L */ + p1 = (packet[0] & 0x20) >> 5; + p2 = (packet[0] & 0x10) >> 4; + } else { + /* byte 0: n1 n0 p2 p1 1 p3 R L */ + p1 = (packet[0] & 0x10) >> 4; + p2 = (packet[0] & 0x20) >> 5; + } + + p3 = (packet[0] & 0x04) >> 2; + + return etd->parity[packet[1]] == p1 && + etd->parity[packet[2]] == p2 && + etd->parity[packet[3]] == p3; +} + +/* + * Process byte stream from mouse and handle complete packets + */ +static psmouse_ret_t elantech_process_byte(struct psmouse *psmouse) +{ + struct elantech_data *etd = psmouse->private; + + if (psmouse->pktcnt < psmouse->pktsize) + return PSMOUSE_GOOD_DATA; + + if (etd->debug > 1) + elantech_packet_dump(psmouse->packet, psmouse->pktsize); + + switch (etd->hw_version) { + case 1: + if (etd->paritycheck && !elantech_check_parity_v1(psmouse)) + return PSMOUSE_BAD_DATA; + + elantech_report_absolute_v1(psmouse); + break; + + case 2: + /* We don't know how to check parity in protocol v2 */ + elantech_report_absolute_v2(psmouse); + break; + } + + return PSMOUSE_FULL_PACKET; +} + +/* + * Put the touchpad into absolute mode + */ +static int elantech_set_absolute_mode(struct psmouse *psmouse) +{ + struct elantech_data *etd = psmouse->private; + unsigned char val; + int tries = ETP_READ_BACK_TRIES; + int rc = 0; + + switch (etd->hw_version) { + case 1: + etd->reg_10 = 0x16; + etd->reg_11 = 0x8f; + if (elantech_write_reg(psmouse, 0x10, etd->reg_10) || + elantech_write_reg(psmouse, 0x11, etd->reg_11)) { + rc = -1; + } + break; + + case 2: + /* Windows driver values */ + etd->reg_10 = 0x54; + etd->reg_11 = 0x88; /* 0x8a */ + etd->reg_21 = 0x60; /* 0x00 */ + if (elantech_write_reg(psmouse, 0x10, etd->reg_10) || + elantech_write_reg(psmouse, 0x11, etd->reg_11) || + elantech_write_reg(psmouse, 0x21, etd->reg_21)) { + rc = -1; + break; + } + /* + * Read back reg 0x10. The touchpad is probably initalising + * and not ready until we read back the value we just wrote. + */ + do { + rc = elantech_read_reg(psmouse, 0x10, &val); + if (rc == 0) + break; + tries--; + elantech_debug("elantech.c: retrying read (%d).\n", + tries); + msleep(ETP_READ_BACK_DELAY); + } while (tries > 0); + if (rc) + pr_err("elantech.c: failed to read back register 0x10.\n"); + break; + } + + if (rc) + pr_err("elantech.c: failed to initialise registers.\n"); + + return rc; +} + +/* + * Set the appropriate event bits for the input subsystem + */ +static void elantech_set_input_params(struct psmouse *psmouse) +{ + struct input_dev *dev = psmouse->dev; + struct elantech_data *etd = psmouse->private; + + __set_bit(EV_KEY, dev->evbit); + __set_bit(EV_ABS, dev->evbit); + + __set_bit(BTN_LEFT, dev->keybit); + __set_bit(BTN_RIGHT, dev->keybit); + + __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); + + switch (etd->hw_version) { + case 1: + /* Rocker button */ + if ((etd->fw_version_maj == 0x01) && + (etd->capabilities & ETP_CAP_HAS_ROCKER)) { + __set_bit(BTN_FORWARD, dev->keybit); + __set_bit(BTN_BACK, dev->keybit); + } + input_set_abs_params(dev, ABS_X, ETP_XMIN_V1, ETP_XMAX_V1, 0, 0); + input_set_abs_params(dev, ABS_Y, ETP_YMIN_V1, ETP_YMAX_V1, 0, 0); + break; + + case 2: + input_set_abs_params(dev, ABS_X, ETP_XMIN_V2, ETP_XMAX_V2, 0, 0); + input_set_abs_params(dev, ABS_Y, ETP_YMIN_V2, ETP_YMAX_V2, 0, 0); + input_set_abs_params(dev, ABS_HAT0X, ETP_2FT_XMIN, ETP_2FT_XMAX, 0, 0); + input_set_abs_params(dev, ABS_HAT0Y, ETP_2FT_YMIN, ETP_2FT_YMAX, 0, 0); + input_set_abs_params(dev, ABS_HAT1X, ETP_2FT_XMIN, ETP_2FT_XMAX, 0, 0); + input_set_abs_params(dev, ABS_HAT1Y, ETP_2FT_YMIN, ETP_2FT_YMAX, 0, 0); + break; + } +} + +struct elantech_attr_data { + size_t field_offset; + unsigned char reg; +}; + +/* + * Display a register value by reading a sysfs entry + */ +static ssize_t elantech_show_int_attr(struct psmouse *psmouse, void *data, + char *buf) +{ + struct elantech_data *etd = psmouse->private; + struct elantech_attr_data *attr = data; + unsigned char *reg = (unsigned char *) etd + attr->field_offset; + int rc = 0; + + if (attr->reg) + rc = elantech_read_reg(psmouse, attr->reg, reg); + + return sprintf(buf, "0x%02x\n", (attr->reg && rc) ? -1 : *reg); +} + +/* + * Write a register value by writing a sysfs entry + */ +static ssize_t elantech_set_int_attr(struct psmouse *psmouse, + void *data, const char *buf, size_t count) +{ + struct elantech_data *etd = psmouse->private; + struct elantech_attr_data *attr = data; + unsigned char *reg = (unsigned char *) etd + attr->field_offset; + unsigned long value; + int err; + + err = strict_strtoul(buf, 16, &value); + if (err) + return err; + + if (value > 0xff) + return -EINVAL; + + /* Do we need to preserve some bits for version 2 hardware too? */ + if (etd->hw_version == 1) { + if (attr->reg == 0x10) + /* Force absolute mode always on */ + value |= ETP_R10_ABSOLUTE_MODE; + else if (attr->reg == 0x11) + /* Force 4 byte mode always on */ + value |= ETP_R11_4_BYTE_MODE; + } + + if (!attr->reg || elantech_write_reg(psmouse, attr->reg, value) == 0) + *reg = value; + + return count; +} + +#define ELANTECH_INT_ATTR(_name, _register) \ + static struct elantech_attr_data elantech_attr_##_name = { \ + .field_offset = offsetof(struct elantech_data, _name), \ + .reg = _register, \ + }; \ + PSMOUSE_DEFINE_ATTR(_name, S_IWUSR | S_IRUGO, \ + &elantech_attr_##_name, \ + elantech_show_int_attr, \ + elantech_set_int_attr) + +ELANTECH_INT_ATTR(reg_10, 0x10); +ELANTECH_INT_ATTR(reg_11, 0x11); +ELANTECH_INT_ATTR(reg_20, 0x20); +ELANTECH_INT_ATTR(reg_21, 0x21); +ELANTECH_INT_ATTR(reg_22, 0x22); +ELANTECH_INT_ATTR(reg_23, 0x23); +ELANTECH_INT_ATTR(reg_24, 0x24); +ELANTECH_INT_ATTR(reg_25, 0x25); +ELANTECH_INT_ATTR(reg_26, 0x26); +ELANTECH_INT_ATTR(debug, 0); +ELANTECH_INT_ATTR(paritycheck, 0); + +static struct attribute *elantech_attrs[] = { + &psmouse_attr_reg_10.dattr.attr, + &psmouse_attr_reg_11.dattr.attr, + &psmouse_attr_reg_20.dattr.attr, + &psmouse_attr_reg_21.dattr.attr, + &psmouse_attr_reg_22.dattr.attr, + &psmouse_attr_reg_23.dattr.attr, + &psmouse_attr_reg_24.dattr.attr, + &psmouse_attr_reg_25.dattr.attr, + &psmouse_attr_reg_26.dattr.attr, + &psmouse_attr_debug.dattr.attr, + &psmouse_attr_paritycheck.dattr.attr, + NULL +}; + +static struct attribute_group elantech_attr_group = { + .attrs = elantech_attrs, +}; + +/* + * Use magic knock to detect Elantech touchpad + */ +int elantech_detect(struct psmouse *psmouse, int set_properties) +{ + struct ps2dev *ps2dev = &psmouse->ps2dev; + unsigned char param[3]; + + ps2_command(&psmouse->ps2dev, NULL, PSMOUSE_CMD_RESET_DIS); + + if (ps2_command(ps2dev, NULL, PSMOUSE_CMD_DISABLE) || + 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)) { + pr_err("elantech.c: sending Elantech magic knock failed.\n"); + return -1; + } + + /* + * Report this in case there are Elantech models that use a different + * set of magic numbers + */ + if (param[0] != 0x3c || param[1] != 0x03 || param[2] != 0xc8) { + pr_info("elantech.c: unexpected magic knock result 0x%02x, 0x%02x, 0x%02x.\n", + param[0], param[1], param[2]); + return -1; + } + + if (set_properties) { + psmouse->vendor = "Elantech"; + psmouse->name = "Touchpad"; + } + + return 0; +} + +/* + * Clean up sysfs entries when disconnecting + */ +static void elantech_disconnect(struct psmouse *psmouse) +{ + sysfs_remove_group(&psmouse->ps2dev.serio->dev.kobj, + &elantech_attr_group); + kfree(psmouse->private); + psmouse->private = NULL; +} + +/* + * Put the touchpad back into absolute mode when reconnecting + */ +static int elantech_reconnect(struct psmouse *psmouse) +{ + if (elantech_detect(psmouse, 0)) + return -1; + + if (elantech_set_absolute_mode(psmouse)) { + pr_err("elantech.c: failed to put touchpad back into absolute mode.\n"); + return -1; + } + + return 0; +} + +/* + * Initialize the touchpad and create sysfs entries + */ +int elantech_init(struct psmouse *psmouse) +{ + struct elantech_data *etd; + int i, error; + unsigned char param[3]; + + etd = kzalloc(sizeof(struct elantech_data), GFP_KERNEL); + psmouse->private = etd; + if (!etd) + return -1; + + etd->parity[0] = 1; + for (i = 1; i < 256; i++) + etd->parity[i] = etd->parity[i & (i - 1)] ^ 1; + + /* + * Find out what version hardware this is + */ + if (synaptics_send_cmd(psmouse, ETP_FW_VERSION_QUERY, param)) { + pr_err("elantech.c: failed to query firmware version.\n"); + goto init_fail; + } + pr_info("elantech.c: Elantech version query result 0x%02x, 0x%02x, 0x%02x.\n", + param[0], param[1], param[2]); + etd->fw_version_maj = param[0]; + etd->fw_version_min = param[2]; + + /* + * Assume every version greater than this is new EeePC style + * hardware with 6 byte packets + */ + if (etd->fw_version_maj >= 0x02 && etd->fw_version_min >= 0x30) { + etd->hw_version = 2; + /* For now show extra debug information */ + etd->debug = 1; + /* Don't know how to do parity checking for version 2 */ + etd->paritycheck = 0; + } else { + etd->hw_version = 1; + etd->paritycheck = 1; + } + pr_info("elantech.c: assuming hardware version %d, firmware version %d.%d\n", + etd->hw_version, etd->fw_version_maj, etd->fw_version_min); + + if (synaptics_send_cmd(psmouse, ETP_CAPABILITIES_QUERY, param)) { + pr_err("elantech.c: failed to query capabilities.\n"); + goto init_fail; + } + pr_info("elantech.c: Synaptics capabilities query result 0x%02x, 0x%02x, 0x%02x.\n", + param[0], param[1], param[2]); + etd->capabilities = param[0]; + + if (elantech_set_absolute_mode(psmouse)) { + pr_err("elantech.c: failed to put touchpad into absolute mode.\n"); + goto init_fail; + } + + elantech_set_input_params(psmouse); + + error = sysfs_create_group(&psmouse->ps2dev.serio->dev.kobj, + &elantech_attr_group); + if (error) { + pr_err("elantech.c: failed to create sysfs attributes, error: %d.\n", + error); + goto init_fail; + } + + psmouse->protocol_handler = elantech_process_byte; + psmouse->disconnect = elantech_disconnect; + psmouse->reconnect = elantech_reconnect; + psmouse->pktsize = etd->hw_version == 2 ? 6 : 4; + + return 0; + + init_fail: + kfree(etd); + return -1; +} diff --git a/drivers/input/mouse/elantech.h b/drivers/input/mouse/elantech.h new file mode 100644 index 0000000..bee282b --- /dev/null +++ b/drivers/input/mouse/elantech.h @@ -0,0 +1,124 @@ +/* + * Elantech Touchpad driver (v5) + * + * Copyright (C) 2007-2008 Arjan Opmeer <arjan@opmeer.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. + * + * Trademarks are the property of their respective owners. + */ + +#ifndef _ELANTECH_H +#define _ELANTECH_H + +/* + * Command values for Synaptics style queries + */ +#define ETP_FW_VERSION_QUERY 0x01 +#define ETP_CAPABILITIES_QUERY 0x02 + +/* + * Command values for register reading or writing + */ +#define ETP_REGISTER_READ 0x10 +#define ETP_REGISTER_WRITE 0x11 + +/* + * Hardware version 2 custom PS/2 command value + */ +#define ETP_PS2_CUSTOM_COMMAND 0xf8 + +/* + * Times to retry a ps2_command and millisecond delay between tries + */ +#define ETP_PS2_COMMAND_TRIES 3 +#define ETP_PS2_COMMAND_DELAY 500 + +/* + * Times to try to read back a register and millisecond delay between tries + */ +#define ETP_READ_BACK_TRIES 5 +#define ETP_READ_BACK_DELAY 2000 + +/* + * Register bitmasks for hardware version 1 + */ +#define ETP_R10_ABSOLUTE_MODE 0x04 +#define ETP_R11_4_BYTE_MODE 0x02 + +/* + * Capability bitmasks + */ +#define ETP_CAP_HAS_ROCKER 0x04 + +/* + * One hard to find application note states that X axis range is 0 to 576 + * and Y axis range is 0 to 384 for harware version 1. + * Edge fuzz might be necessary because of bezel around the touchpad + */ +#define ETP_EDGE_FUZZ_V1 32 + +#define ETP_XMIN_V1 ( 0 + ETP_EDGE_FUZZ_V1) +#define ETP_XMAX_V1 (576 - ETP_EDGE_FUZZ_V1) +#define ETP_YMIN_V1 ( 0 + ETP_EDGE_FUZZ_V1) +#define ETP_YMAX_V1 (384 - ETP_EDGE_FUZZ_V1) + +/* + * It seems the resolution for hardware version 2 doubled. + * Hence the X and Y ranges are doubled too. + * The bezel around the pad also appears to be smaller + */ +#define ETP_EDGE_FUZZ_V2 8 + +#define ETP_XMIN_V2 ( 0 + ETP_EDGE_FUZZ_V2) +#define ETP_XMAX_V2 (1152 - ETP_EDGE_FUZZ_V2) +#define ETP_YMIN_V2 ( 0 + ETP_EDGE_FUZZ_V2) +#define ETP_YMAX_V2 ( 768 - ETP_EDGE_FUZZ_V2) + +/* + * For two finger touches the coordinate of each finger gets reported + * separately but with reduced resolution. + */ +#define ETP_2FT_FUZZ 4 + +#define ETP_2FT_XMIN ( 0 + ETP_2FT_FUZZ) +#define ETP_2FT_XMAX (288 - ETP_2FT_FUZZ) +#define ETP_2FT_YMIN ( 0 + ETP_2FT_FUZZ) +#define ETP_2FT_YMAX (192 - ETP_2FT_FUZZ) + +struct elantech_data { + unsigned char reg_10; + unsigned char reg_11; + unsigned char reg_20; + unsigned char reg_21; + unsigned char reg_22; + unsigned char reg_23; + unsigned char reg_24; + unsigned char reg_25; + unsigned char reg_26; + unsigned char debug; + unsigned char capabilities; + unsigned char fw_version_maj; + unsigned char fw_version_min; + unsigned char hw_version; + unsigned char paritycheck; + unsigned char parity[256]; +}; + +#ifdef CONFIG_MOUSE_PS2_ELANTECH +int elantech_detect(struct psmouse *psmouse, int set_properties); +int elantech_init(struct psmouse *psmouse); +#else +static inline int elantech_detect(struct psmouse *psmouse, int set_properties) +{ + return -ENOSYS; +} +static inline int elantech_init(struct psmouse *psmouse) +{ + return -ENOSYS; +} +#endif /* CONFIG_MOUSE_PS2_ELANTECH */ + +#endif diff --git a/drivers/input/mouse/gpio_mouse.c b/drivers/input/mouse/gpio_mouse.c new file mode 100644 index 0000000..72cf5e3 --- /dev/null +++ b/drivers/input/mouse/gpio_mouse.c @@ -0,0 +1,199 @@ +/* + * Driver for simulating a mouse on GPIO lines. + * + * Copyright (C) 2007 Atmel Corporation + * + * 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/module.h> +#include <linux/platform_device.h> +#include <linux/input-polldev.h> +#include <linux/gpio_mouse.h> + +#include <asm/gpio.h> + +/* + * Timer function which is run every scan_ms ms when the device is opened. + * The dev input varaible is set to the the input_dev pointer. + */ +static void gpio_mouse_scan(struct input_polled_dev *dev) +{ + struct gpio_mouse_platform_data *gpio = dev->private; + struct input_dev *input = dev->input; + int x, y; + + if (gpio->bleft >= 0) + input_report_key(input, BTN_LEFT, + gpio_get_value(gpio->bleft) ^ gpio->polarity); + if (gpio->bmiddle >= 0) + input_report_key(input, BTN_MIDDLE, + gpio_get_value(gpio->bmiddle) ^ gpio->polarity); + if (gpio->bright >= 0) + input_report_key(input, BTN_RIGHT, + gpio_get_value(gpio->bright) ^ gpio->polarity); + + x = (gpio_get_value(gpio->right) ^ gpio->polarity) + - (gpio_get_value(gpio->left) ^ gpio->polarity); + y = (gpio_get_value(gpio->down) ^ gpio->polarity) + - (gpio_get_value(gpio->up) ^ gpio->polarity); + + input_report_rel(input, REL_X, x); + input_report_rel(input, REL_Y, y); + input_sync(input); +} + +static int __init gpio_mouse_probe(struct platform_device *pdev) +{ + struct gpio_mouse_platform_data *pdata = pdev->dev.platform_data; + struct input_polled_dev *input_poll; + struct input_dev *input; + int pin, i; + int error; + + if (!pdata) { + dev_err(&pdev->dev, "no platform data\n"); + error = -ENXIO; + goto out; + } + + if (pdata->scan_ms < 0) { + dev_err(&pdev->dev, "invalid scan time\n"); + error = -EINVAL; + goto out; + } + + for (i = 0; i < GPIO_MOUSE_PIN_MAX; i++) { + pin = pdata->pins[i]; + + if (pin < 0) { + + if (i <= GPIO_MOUSE_PIN_RIGHT) { + /* Mouse direction is required. */ + dev_err(&pdev->dev, + "missing GPIO for directions\n"); + error = -EINVAL; + goto out_free_gpios; + } + + if (i == GPIO_MOUSE_PIN_BLEFT) + dev_dbg(&pdev->dev, "no left button defined\n"); + + } else { + error = gpio_request(pin, "gpio_mouse"); + if (error) { + dev_err(&pdev->dev, "fail %d pin (%d idx)\n", + pin, i); + goto out_free_gpios; + } + + gpio_direction_input(pin); + } + } + + input_poll = input_allocate_polled_device(); + if (!input_poll) { + dev_err(&pdev->dev, "not enough memory for input device\n"); + error = -ENOMEM; + goto out_free_gpios; + } + + platform_set_drvdata(pdev, input_poll); + + /* set input-polldev handlers */ + input_poll->private = pdata; + input_poll->poll = gpio_mouse_scan; + input_poll->poll_interval = pdata->scan_ms; + + input = input_poll->input; + input->name = pdev->name; + input->id.bustype = BUS_HOST; + input->dev.parent = &pdev->dev; + + input_set_capability(input, EV_REL, REL_X); + input_set_capability(input, EV_REL, REL_Y); + if (pdata->bleft >= 0) + input_set_capability(input, EV_KEY, BTN_LEFT); + if (pdata->bmiddle >= 0) + input_set_capability(input, EV_KEY, BTN_MIDDLE); + if (pdata->bright >= 0) + input_set_capability(input, EV_KEY, BTN_RIGHT); + + error = input_register_polled_device(input_poll); + if (error) { + dev_err(&pdev->dev, "could not register input device\n"); + goto out_free_polldev; + } + + dev_dbg(&pdev->dev, "%d ms scan time, buttons: %s%s%s\n", + pdata->scan_ms, + pdata->bleft < 0 ? "" : "left ", + pdata->bmiddle < 0 ? "" : "middle ", + pdata->bright < 0 ? "" : "right"); + + return 0; + + out_free_polldev: + input_free_polled_device(input_poll); + platform_set_drvdata(pdev, NULL); + + out_free_gpios: + while (--i >= 0) { + pin = pdata->pins[i]; + if (pin) + gpio_free(pin); + } + out: + return error; +} + +static int __devexit gpio_mouse_remove(struct platform_device *pdev) +{ + struct input_polled_dev *input = platform_get_drvdata(pdev); + struct gpio_mouse_platform_data *pdata = input->private; + int pin, i; + + input_unregister_polled_device(input); + input_free_polled_device(input); + + for (i = 0; i < GPIO_MOUSE_PIN_MAX; i++) { + pin = pdata->pins[i]; + if (pin >= 0) + gpio_free(pin); + } + + platform_set_drvdata(pdev, NULL); + + return 0; +} + +/* work with hotplug and coldplug */ +MODULE_ALIAS("platform:gpio_mouse"); + +struct platform_driver gpio_mouse_device_driver = { + .remove = __devexit_p(gpio_mouse_remove), + .driver = { + .name = "gpio_mouse", + .owner = THIS_MODULE, + } +}; + +static int __init gpio_mouse_init(void) +{ + return platform_driver_probe(&gpio_mouse_device_driver, + gpio_mouse_probe); +} +module_init(gpio_mouse_init); + +static void __exit gpio_mouse_exit(void) +{ + platform_driver_unregister(&gpio_mouse_device_driver); +} +module_exit(gpio_mouse_exit); + +MODULE_AUTHOR("Hans-Christian Egtvedt <hcegtvedt@atmel.com>"); +MODULE_DESCRIPTION("GPIO mouse driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/mouse/hgpk.c b/drivers/input/mouse/hgpk.c new file mode 100644 index 0000000..88f04bf --- /dev/null +++ b/drivers/input/mouse/hgpk.c @@ -0,0 +1,477 @@ +/* + * OLPC HGPK (XO-1) touchpad PS/2 mouse driver + * + * Copyright (c) 2006-2008 One Laptop Per Child + * Authors: + * Zephaniah E. Hull + * Andres Salomon <dilinger@debian.org> + * + * This driver is partly based on the ALPS driver, which is: + * + * Copyright (c) 2003 Neil Brown <neilb@cse.unsw.edu.au> + * Copyright (c) 2003-2005 Peter Osterlund <petero2@telia.com> + * Copyright (c) 2004 Dmitry Torokhov <dtor@mail.ru> + * 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. + */ + +/* + * The spec from ALPS is available from + * <http://wiki.laptop.org/go/Touch_Pad/Tablet>. It refers to this + * device as HGPK (Hybrid GS, PT, and Keymatrix). + * + * The earliest versions of the device had simultaneous reporting; that + * was removed. After that, the device used the Advanced Mode GS/PT streaming + * stuff. That turned out to be too buggy to support, so we've finally + * switched to Mouse Mode (which utilizes only the center 1/3 of the touchpad). + */ + +#define DEBUG +#include <linux/input.h> +#include <linux/serio.h> +#include <linux/libps2.h> +#include <linux/delay.h> +#include <asm/olpc.h> + +#include "psmouse.h" +#include "hgpk.h" + +static int tpdebug; +module_param(tpdebug, int, 0644); +MODULE_PARM_DESC(tpdebug, "enable debugging, dumping packets to KERN_DEBUG."); + +static int recalib_delta = 100; +module_param(recalib_delta, int, 0644); +MODULE_PARM_DESC(recalib_delta, + "packets containing a delta this large will cause a recalibration."); + +/* + * When the touchpad gets ultra-sensitive, one can keep their finger 1/2" + * above the pad and still have it send packets. This causes a jump cursor + * when one places their finger on the pad. We can probably detect the + * jump as we see a large deltas (>= 100px). In mouse mode, I've been + * unable to even come close to 100px deltas during normal usage, so I think + * this threshold is safe. If a large delta occurs, trigger a recalibration. + */ +static void hgpk_jumpy_hack(struct psmouse *psmouse, int x, int y) +{ + struct hgpk_data *priv = psmouse->private; + + if (abs(x) > recalib_delta || abs(y) > recalib_delta) { + hgpk_err(psmouse, ">%dpx jump detected (%d,%d)\n", + recalib_delta, x, y); + /* My car gets forty rods to the hogshead and that's the + * way I likes it! */ + psmouse_queue_work(psmouse, &priv->recalib_wq, + msecs_to_jiffies(1000)); + } +} + +/* + * We have no idea why this particular hardware bug occurs. The touchpad + * will randomly start spewing packets without anything touching the + * pad. This wouldn't necessarily be bad, but it's indicative of a + * severely miscalibrated pad; attempting to use the touchpad while it's + * spewing means the cursor will jump all over the place, and act "drunk". + * + * The packets that are spewed tend to all have deltas between -2 and 2, and + * the cursor will move around without really going very far. It will + * tend to end up in the same location; if we tally up the changes over + * 100 packets, we end up w/ a final delta of close to 0. This happens + * pretty regularly when the touchpad is spewing, and is pretty hard to + * manually trigger (at least for *my* fingers). So, it makes a perfect + * scheme for detecting spews. + */ +static void hgpk_spewing_hack(struct psmouse *psmouse, + int l, int r, int x, int y) +{ + struct hgpk_data *priv = psmouse->private; + + /* ignore button press packets; many in a row could trigger + * a false-positive! */ + if (l || r) + return; + + priv->x_tally += x; + priv->y_tally += y; + + if (++priv->count > 100) { + if (abs(priv->x_tally) < 3 && abs(priv->y_tally) < 3) { + hgpk_dbg(psmouse, "packet spew detected (%d,%d)\n", + priv->x_tally, priv->y_tally); + psmouse_queue_work(psmouse, &priv->recalib_wq, + msecs_to_jiffies(1000)); + } + /* reset every 100 packets */ + priv->count = 0; + priv->x_tally = 0; + priv->y_tally = 0; + } +} + +/* + * HGPK Mouse Mode format (standard mouse format, sans middle button) + * + * byte 0: y-over x-over y-neg x-neg 1 0 swr swl + * byte 1: x7 x6 x5 x4 x3 x2 x1 x0 + * byte 2: y7 y6 y5 y4 y3 y2 y1 y0 + * + * swr/swl are the left/right buttons. + * x-neg/y-neg are the x and y delta negative bits + * x-over/y-over are the x and y overflow bits + */ +static int hgpk_validate_byte(unsigned char *packet) +{ + return (packet[0] & 0x0C) != 0x08; +} + +static void hgpk_process_packet(struct psmouse *psmouse) +{ + struct input_dev *dev = psmouse->dev; + unsigned char *packet = psmouse->packet; + int x, y, left, right; + + left = packet[0] & 1; + right = (packet[0] >> 1) & 1; + + x = packet[1] - ((packet[0] << 4) & 0x100); + y = ((packet[0] << 3) & 0x100) - packet[2]; + + hgpk_jumpy_hack(psmouse, x, y); + hgpk_spewing_hack(psmouse, left, right, x, y); + + if (tpdebug) + hgpk_dbg(psmouse, "l=%d r=%d x=%d y=%d\n", left, right, x, y); + + input_report_key(dev, BTN_LEFT, left); + input_report_key(dev, BTN_RIGHT, right); + + input_report_rel(dev, REL_X, x); + input_report_rel(dev, REL_Y, y); + + input_sync(dev); +} + +static psmouse_ret_t hgpk_process_byte(struct psmouse *psmouse) +{ + struct hgpk_data *priv = psmouse->private; + + if (hgpk_validate_byte(psmouse->packet)) { + hgpk_dbg(psmouse, "%s: (%d) %02x %02x %02x\n", + __func__, psmouse->pktcnt, psmouse->packet[0], + psmouse->packet[1], psmouse->packet[2]); + return PSMOUSE_BAD_DATA; + } + + if (psmouse->pktcnt >= psmouse->pktsize) { + hgpk_process_packet(psmouse); + return PSMOUSE_FULL_PACKET; + } + + if (priv->recalib_window) { + if (time_before(jiffies, priv->recalib_window)) { + /* + * ugh, got a packet inside our recalibration + * window, schedule another recalibration. + */ + hgpk_dbg(psmouse, + "packet inside calibration window, " + "queueing another recalibration\n"); + psmouse_queue_work(psmouse, &priv->recalib_wq, + msecs_to_jiffies(1000)); + } + priv->recalib_window = 0; + } + + return PSMOUSE_GOOD_DATA; +} + +static int hgpk_force_recalibrate(struct psmouse *psmouse) +{ + struct ps2dev *ps2dev = &psmouse->ps2dev; + struct hgpk_data *priv = psmouse->private; + + /* C-series touchpads added the recalibrate command */ + if (psmouse->model < HGPK_MODEL_C) + return 0; + + /* we don't want to race with the irq handler, nor with resyncs */ + psmouse_set_state(psmouse, PSMOUSE_INITIALIZING); + + /* start by resetting the device */ + psmouse_reset(psmouse); + + /* send the recalibrate request */ + if (ps2_command(ps2dev, NULL, 0xf5) || + ps2_command(ps2dev, NULL, 0xf5) || + ps2_command(ps2dev, NULL, 0xe6) || + ps2_command(ps2dev, NULL, 0xf5)) { + return -1; + } + + /* according to ALPS, 150mS is required for recalibration */ + msleep(150); + + /* XXX: If a finger is down during this delay, recalibration will + * detect capacitance incorrectly. This is a hardware bug, and + * we don't have a good way to deal with it. The 2s window stuff + * (below) is our best option for now. + */ + + if (ps2_command(ps2dev, NULL, PSMOUSE_CMD_ENABLE)) + return -1; + + psmouse_set_state(psmouse, PSMOUSE_ACTIVATED); + + /* After we recalibrate, we shouldn't get any packets for 2s. If + * we do, it's likely that someone's finger was on the touchpad. + * If someone's finger *was* on the touchpad, it's probably + * miscalibrated. So, we should schedule another recalibration + */ + priv->recalib_window = jiffies + msecs_to_jiffies(2000); + + return 0; +} + +/* + * This kills power to the touchpad; according to ALPS, current consumption + * goes down to 50uA after running this. To turn power back on, we drive + * MS-DAT low. + */ +static int hgpk_toggle_power(struct psmouse *psmouse, int enable) +{ + struct ps2dev *ps2dev = &psmouse->ps2dev; + int timeo; + + /* Added on D-series touchpads */ + if (psmouse->model < HGPK_MODEL_D) + return 0; + + if (enable) { + psmouse_set_state(psmouse, PSMOUSE_INITIALIZING); + + /* + * Sending a byte will drive MS-DAT low; this will wake up + * the controller. Once we get an ACK back from it, it + * means we can continue with the touchpad re-init. ALPS + * tells us that 1s should be long enough, so set that as + * the upper bound. + */ + for (timeo = 20; timeo > 0; timeo--) { + if (!ps2_sendbyte(&psmouse->ps2dev, + PSMOUSE_CMD_DISABLE, 20)) + break; + msleep(50); + } + + psmouse_reset(psmouse); + + /* should be all set, enable the touchpad */ + ps2_command(&psmouse->ps2dev, NULL, PSMOUSE_CMD_ENABLE); + psmouse_set_state(psmouse, PSMOUSE_ACTIVATED); + + } else { + hgpk_dbg(psmouse, "Powering off touchpad.\n"); + psmouse_set_state(psmouse, PSMOUSE_IGNORE); + + if (ps2_command(ps2dev, NULL, 0xec) || + ps2_command(ps2dev, NULL, 0xec) || + ps2_command(ps2dev, NULL, 0xea)) { + return -1; + } + + /* probably won't see an ACK, the touchpad will be off */ + ps2_sendbyte(&psmouse->ps2dev, 0xec, 20); + } + + return 0; +} + +static int hgpk_poll(struct psmouse *psmouse) +{ + /* We can't poll, so always return failure. */ + return -1; +} + +static int hgpk_reconnect(struct psmouse *psmouse) +{ + /* During suspend/resume the ps2 rails remain powered. We don't want + * to do a reset because it's flush data out of buffers; however, + * earlier prototypes (B1) had some brokenness that required a reset. */ + if (olpc_board_at_least(olpc_board(0xb2))) + if (psmouse->ps2dev.serio->dev.power.power_state.event != + PM_EVENT_ON) + return 0; + + psmouse_reset(psmouse); + + return 0; +} + +static ssize_t hgpk_show_powered(struct psmouse *psmouse, void *data, char *buf) +{ + struct hgpk_data *priv = psmouse->private; + + return sprintf(buf, "%d\n", priv->powered); +} + +static ssize_t hgpk_set_powered(struct psmouse *psmouse, void *data, + const char *buf, size_t count) +{ + struct hgpk_data *priv = psmouse->private; + unsigned long value; + int err; + + err = strict_strtoul(buf, 10, &value); + if (err || value > 1) + return -EINVAL; + + if (value != priv->powered) { + /* + * hgpk_toggle_power will deal w/ state so + * we're not racing w/ irq + */ + err = hgpk_toggle_power(psmouse, value); + if (!err) + priv->powered = value; + } + + return err ? err : count; +} + +__PSMOUSE_DEFINE_ATTR(powered, S_IWUSR | S_IRUGO, NULL, + hgpk_show_powered, hgpk_set_powered, 0); + +static void hgpk_disconnect(struct psmouse *psmouse) +{ + struct hgpk_data *priv = psmouse->private; + + device_remove_file(&psmouse->ps2dev.serio->dev, + &psmouse_attr_powered.dattr); + psmouse_reset(psmouse); + kfree(priv); +} + +static void hgpk_recalib_work(struct work_struct *work) +{ + struct delayed_work *w = container_of(work, struct delayed_work, work); + struct hgpk_data *priv = container_of(w, struct hgpk_data, recalib_wq); + struct psmouse *psmouse = priv->psmouse; + + hgpk_dbg(psmouse, "recalibrating touchpad..\n"); + + if (hgpk_force_recalibrate(psmouse)) + hgpk_err(psmouse, "recalibration failed!\n"); +} + +static int hgpk_register(struct psmouse *psmouse) +{ + struct input_dev *dev = psmouse->dev; + int err; + + /* unset the things that psmouse-base sets which we don't have */ + __clear_bit(BTN_MIDDLE, dev->keybit); + + /* set the things we do have */ + __set_bit(EV_KEY, dev->evbit); + __set_bit(EV_REL, dev->evbit); + + __set_bit(REL_X, dev->relbit); + __set_bit(REL_Y, dev->relbit); + + __set_bit(BTN_LEFT, dev->keybit); + __set_bit(BTN_RIGHT, dev->keybit); + + /* register handlers */ + psmouse->protocol_handler = hgpk_process_byte; + psmouse->poll = hgpk_poll; + psmouse->disconnect = hgpk_disconnect; + psmouse->reconnect = hgpk_reconnect; + psmouse->pktsize = 3; + + /* Disable the idle resync. */ + psmouse->resync_time = 0; + /* Reset after a lot of bad bytes. */ + psmouse->resetafter = 1024; + + err = device_create_file(&psmouse->ps2dev.serio->dev, + &psmouse_attr_powered.dattr); + if (err) + hgpk_err(psmouse, "Failed to create sysfs attribute\n"); + + return err; +} + +int hgpk_init(struct psmouse *psmouse) +{ + struct hgpk_data *priv; + int err = -ENOMEM; + + priv = kzalloc(sizeof(struct hgpk_data), GFP_KERNEL); + if (!priv) + goto alloc_fail; + + psmouse->private = priv; + priv->psmouse = psmouse; + priv->powered = 1; + INIT_DELAYED_WORK(&priv->recalib_wq, hgpk_recalib_work); + + err = psmouse_reset(psmouse); + if (err) + goto init_fail; + + err = hgpk_register(psmouse); + if (err) + goto init_fail; + + return 0; + +init_fail: + kfree(priv); +alloc_fail: + return err; +} + +static enum hgpk_model_t hgpk_get_model(struct psmouse *psmouse) +{ + struct ps2dev *ps2dev = &psmouse->ps2dev; + unsigned char param[3]; + + /* E7, E7, E7, E9 gets us a 3 byte identifier */ + if (ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSCALE21) || + ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSCALE21) || + ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSCALE21) || + ps2_command(ps2dev, param, PSMOUSE_CMD_GETINFO)) { + return -EIO; + } + + hgpk_dbg(psmouse, "ID: %02x %02x %02x", param[0], param[1], param[2]); + + /* HGPK signature: 0x67, 0x00, 0x<model> */ + if (param[0] != 0x67 || param[1] != 0x00) + return -ENODEV; + + hgpk_info(psmouse, "OLPC touchpad revision 0x%x\n", param[2]); + + return param[2]; +} + +int hgpk_detect(struct psmouse *psmouse, int set_properties) +{ + int version; + + version = hgpk_get_model(psmouse); + if (version < 0) + return version; + + if (set_properties) { + psmouse->vendor = "ALPS"; + psmouse->name = "HGPK"; + psmouse->model = version; + } + + return 0; +} diff --git a/drivers/input/mouse/hgpk.h b/drivers/input/mouse/hgpk.h new file mode 100644 index 0000000..a4b2a96 --- /dev/null +++ b/drivers/input/mouse/hgpk.h @@ -0,0 +1,49 @@ +/* + * OLPC HGPK (XO-1) touchpad PS/2 mouse driver + */ + +#ifndef _HGPK_H +#define _HGPK_H + +enum hgpk_model_t { + HGPK_MODEL_PREA = 0x0a, /* pre-B1s */ + HGPK_MODEL_A = 0x14, /* found on B1s, PT disabled in hardware */ + HGPK_MODEL_B = 0x28, /* B2s, has capacitance issues */ + HGPK_MODEL_C = 0x3c, + HGPK_MODEL_D = 0x50, /* C1, mass production */ +}; + +struct hgpk_data { + struct psmouse *psmouse; + int powered; + int count, x_tally, y_tally; /* hardware workaround stuff */ + unsigned long recalib_window; + struct delayed_work recalib_wq; +}; + +#define hgpk_dbg(psmouse, format, arg...) \ + dev_dbg(&(psmouse)->ps2dev.serio->dev, format, ## arg) +#define hgpk_err(psmouse, format, arg...) \ + dev_err(&(psmouse)->ps2dev.serio->dev, format, ## arg) +#define hgpk_info(psmouse, format, arg...) \ + dev_info(&(psmouse)->ps2dev.serio->dev, format, ## arg) +#define hgpk_warn(psmouse, format, arg...) \ + dev_warn(&(psmouse)->ps2dev.serio->dev, format, ## arg) +#define hgpk_notice(psmouse, format, arg...) \ + dev_notice(&(psmouse)->ps2dev.serio->dev, format, ## arg) + +#ifdef CONFIG_MOUSE_PS2_OLPC +int hgpk_detect(struct psmouse *psmouse, int set_properties); +int hgpk_init(struct psmouse *psmouse); +#else +static inline int hgpk_detect(struct psmouse *psmouse, int set_properties) +{ + return -ENODEV; +} +static inline int hgpk_init(struct psmouse *psmouse) +{ + return -ENODEV; +} +#endif + +#endif diff --git a/drivers/input/mouse/hil_ptr.c b/drivers/input/mouse/hil_ptr.c new file mode 100644 index 0000000..e532c48 --- /dev/null +++ b/drivers/input/mouse/hil_ptr.c @@ -0,0 +1,447 @@ +/* + * 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 < dev->absmin[ABS_X + i]) + dev->absmin[ABS_X + i] = val; + if (val > dev->absmax[ABS_X + i]) + dev->absmax[ABS_X + i] = val; +#endif + if (i%3) val = 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); +} + +static irqreturn_t hil_ptr_interrupt(struct serio *serio, + unsigned char data, unsigned int flags) +{ + struct hil_ptr *ptr; + hil_packet packet; + int idx; + + ptr = serio_get_drvdata(serio); + BUG_ON(ptr == NULL); + + 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 = serio_get_drvdata(serio); + BUG_ON(ptr == NULL); + + serio_close(serio); + input_unregister_device(ptr->dev); + kfree(ptr); +} + +static int hil_ptr_connect(struct serio *serio, struct serio_driver *driver) +{ + struct hil_ptr *ptr; + const char *txt; + unsigned int i, naxsets, btntype; + uint8_t did, *idd; + int error; + + ptr = kzalloc(sizeof(struct hil_ptr), GFP_KERNEL); + if (!ptr) + return -ENOMEM; + + ptr->dev = input_allocate_device(); + if (!ptr->dev) { + error = -ENOMEM; + goto bail0; + } + + error = serio_open(serio, driver); + if (error) + goto bail1; + + serio_set_drvdata(serio, ptr); + ptr->serio = serio; + + 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); + + 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_MASK(EV_REL); + txt = "relative"; + } + + if ((did & HIL_IDD_DID_TYPE_MASK) == HIL_IDD_DID_TYPE_ABS) { + ptr->dev->evbit[0] = BIT_MASK(EV_ABS); + txt = "absolute"; + } + + if (!ptr->dev->evbit[0]) { + error = -ENODEV; + goto bail2; + } + + ptr->nbtn = HIL_IDD_NUM_BUTTONS(idd); + if (ptr->nbtn) + ptr->dev->evbit[0] |= BIT_MASK(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.parent = &serio->dev; + + error = input_register_device(ptr->dev); + if (error) { + printk(KERN_INFO PREFIX "Unable to register input device\n"); + goto bail2; + } + + printk(KERN_INFO "input: %s (%s), ID: %d\n", + ptr->dev->name, + (btntype == BTN_MOUSE) ? "HIL mouse":"HIL tablet or touchpad", + did); + + return 0; + + bail2: + serio_close(serio); + bail1: + input_free_device(ptr->dev); + bail0: + kfree(ptr); + serio_set_drvdata(serio, NULL); + return error; +} + +static struct serio_device_id hil_ptr_ids[] = { + { + .type = SERIO_HIL_MLC, + .proto = SERIO_HIL, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { 0 } +}; + +static struct serio_driver hil_ptr_serio_driver = { + .driver = { + .name = "hil_ptr", + }, + .description = "HP HIL mouse/tablet driver", + .id_table = hil_ptr_ids, + .connect = hil_ptr_connect, + .disconnect = hil_ptr_disconnect, + .interrupt = hil_ptr_interrupt +}; + +static int __init hil_ptr_init(void) +{ + return serio_register_driver(&hil_ptr_serio_driver); +} + +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 0000000..3827a22 --- /dev/null +++ b/drivers/input/mouse/inport.c @@ -0,0 +1,196 @@ +/* + * 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/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_MOUSE_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)"); + +static struct input_dev *inport_dev; + +static irqreturn_t inport_interrupt(int irq, void *dev_id) +{ + unsigned char buttons; + + outb(INPORT_REG_MODE, INPORT_CONTROL_PORT); + outb(INPORT_MODE_HOLD | INPORT_MODE_IRQ | INPORT_MODE_BASE, INPORT_DATA_PORT); + + 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 inport_open(struct input_dev *dev) +{ + 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) +{ + outb(INPORT_REG_MODE, INPORT_CONTROL_PORT); + outb(INPORT_MODE_BASE, INPORT_DATA_PORT); + free_irq(inport_irq, NULL); +} + +static int __init inport_init(void) +{ + unsigned char a, b, c; + int err; + + 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) { + printk(KERN_INFO "inport.c: Didn't find InPort mouse at %#x\n", INPORT_BASE); + err = -ENODEV; + goto err_release_region; + } + + inport_dev = input_allocate_device(); + if (!inport_dev) { + printk(KERN_ERR "inport.c: Not enough memory for input device\n"); + err = -ENOMEM; + goto err_release_region; + } + + inport_dev->name = INPORT_NAME; + inport_dev->phys = "isa023c/input0"; + inport_dev->id.bustype = BUS_ISA; + inport_dev->id.vendor = INPORT_VENDOR; + inport_dev->id.product = 0x0001; + inport_dev->id.version = 0x0100; + + inport_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REL); + inport_dev->keybit[BIT_WORD(BTN_LEFT)] = BIT_MASK(BTN_LEFT) | + BIT_MASK(BTN_MIDDLE) | BIT_MASK(BTN_RIGHT); + inport_dev->relbit[0] = BIT_MASK(REL_X) | BIT_MASK(REL_Y); + + inport_dev->open = inport_open; + inport_dev->close = inport_close; + + outb(INPORT_RESET, INPORT_CONTROL_PORT); + outb(INPORT_REG_MODE, INPORT_CONTROL_PORT); + outb(INPORT_MODE_BASE, INPORT_DATA_PORT); + + err = input_register_device(inport_dev); + if (err) + goto err_free_dev; + + return 0; + + err_free_dev: + input_free_device(inport_dev); + err_release_region: + release_region(INPORT_BASE, INPORT_EXTENT); + + return err; +} + +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/lifebook.c b/drivers/input/mouse/lifebook.c new file mode 100644 index 0000000..df81b0a --- /dev/null +++ b/drivers/input/mouse/lifebook.c @@ -0,0 +1,332 @@ +/* + * Fujitsu B-series Lifebook PS/2 TouchScreen driver + * + * Copyright (c) 2005 Vojtech Pavlik <vojtech@suse.cz> + * Copyright (c) 2005 Kenan Esau <kenan.esau@conan.de> + * + * TouchScreen detection, absolute mode setting and packet layout is taken from + * Harald Hoyer's description of the device. + * + * 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 <linux/dmi.h> + +#include "psmouse.h" +#include "lifebook.h" + +struct lifebook_data { + struct input_dev *dev2; /* Relative device */ + char phys[32]; +}; + +static const char *desired_serio_phys; + +static int lifebook_set_serio_phys(const struct dmi_system_id *d) +{ + desired_serio_phys = d->driver_data; + return 0; +} + +static unsigned char lifebook_use_6byte_proto; + +static int lifebook_set_6byte_proto(const struct dmi_system_id *d) +{ + lifebook_use_6byte_proto = 1; + return 0; +} + +static const struct dmi_system_id lifebook_dmi_table[] = { + { + .ident = "FLORA-ie 55mi", + .matches = { + DMI_MATCH(DMI_PRODUCT_NAME, "FLORA-ie 55mi"), + }, + }, + { + .ident = "LifeBook B", + .matches = { + DMI_MATCH(DMI_PRODUCT_NAME, "LifeBook B Series"), + }, + }, + { + .ident = "Lifebook B", + .matches = { + DMI_MATCH(DMI_PRODUCT_NAME, "LIFEBOOK B Series"), + }, + }, + { + .ident = "Lifebook B213x/B2150", + .matches = { + DMI_MATCH(DMI_PRODUCT_NAME, "LifeBook B2131/B2133/B2150"), + }, + }, + { + .ident = "Zephyr", + .matches = { + DMI_MATCH(DMI_PRODUCT_NAME, "ZEPHYR"), + }, + }, + { + .ident = "CF-18", + .matches = { + DMI_MATCH(DMI_PRODUCT_NAME, "CF-18"), + }, + .callback = lifebook_set_serio_phys, + .driver_data = "isa0060/serio3", + }, + { + .ident = "Panasonic CF-28", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Matsushita"), + DMI_MATCH(DMI_PRODUCT_NAME, "CF-28"), + }, + .callback = lifebook_set_6byte_proto, + }, + { + .ident = "Panasonic CF-29", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Matsushita"), + DMI_MATCH(DMI_PRODUCT_NAME, "CF-29"), + }, + .callback = lifebook_set_6byte_proto, + }, + { + .ident = "CF-72", + .matches = { + DMI_MATCH(DMI_PRODUCT_NAME, "CF-72"), + }, + .callback = lifebook_set_serio_phys, + .driver_data = "isa0060/serio3", + }, + { + .ident = "Lifebook B142", + .matches = { + DMI_MATCH(DMI_PRODUCT_NAME, "LifeBook B142"), + }, + }, + { } +}; + +static psmouse_ret_t lifebook_process_byte(struct psmouse *psmouse) +{ + struct lifebook_data *priv = psmouse->private; + struct input_dev *dev1 = psmouse->dev; + struct input_dev *dev2 = priv ? priv->dev2 : NULL; + unsigned char *packet = psmouse->packet; + int relative_packet = packet[0] & 0x08; + + if (relative_packet || !lifebook_use_6byte_proto) { + if (psmouse->pktcnt != 3) + return PSMOUSE_GOOD_DATA; + } else { + switch (psmouse->pktcnt) { + case 1: + return (packet[0] & 0xf8) == 0x00 ? + PSMOUSE_GOOD_DATA : PSMOUSE_BAD_DATA; + case 2: + return PSMOUSE_GOOD_DATA; + case 3: + return ((packet[2] & 0x30) << 2) == (packet[2] & 0xc0) ? + PSMOUSE_GOOD_DATA : PSMOUSE_BAD_DATA; + case 4: + return (packet[3] & 0xf8) == 0xc0 ? + PSMOUSE_GOOD_DATA : PSMOUSE_BAD_DATA; + case 5: + return (packet[4] & 0xc0) == (packet[2] & 0xc0) ? + PSMOUSE_GOOD_DATA : PSMOUSE_BAD_DATA; + case 6: + if (((packet[5] & 0x30) << 2) != (packet[5] & 0xc0)) + return PSMOUSE_BAD_DATA; + if ((packet[5] & 0xc0) != (packet[1] & 0xc0)) + return PSMOUSE_BAD_DATA; + break; /* report data */ + } + } + + if (relative_packet) { + if (!dev2) + printk(KERN_WARNING "lifebook.c: got relative packet " + "but no relative device set up\n"); + } else if (lifebook_use_6byte_proto) { + input_report_abs(dev1, ABS_X, + ((packet[1] & 0x3f) << 6) | (packet[2] & 0x3f)); + input_report_abs(dev1, ABS_Y, + 4096 - (((packet[4] & 0x3f) << 6) | (packet[5] & 0x3f))); + } else { + input_report_abs(dev1, ABS_X, + (packet[1] | ((packet[0] & 0x30) << 4))); + input_report_abs(dev1, ABS_Y, + 1024 - (packet[2] | ((packet[0] & 0xC0) << 2))); + } + + input_report_key(dev1, BTN_TOUCH, packet[0] & 0x04); + input_sync(dev1); + + if (dev2) { + if (relative_packet) { + input_report_rel(dev2, REL_X, + ((packet[0] & 0x10) ? packet[1] - 256 : packet[1])); + input_report_rel(dev2, REL_Y, + -(int)((packet[0] & 0x20) ? packet[2] - 256 : packet[2])); + } + input_report_key(dev2, BTN_LEFT, packet[0] & 0x01); + input_report_key(dev2, BTN_RIGHT, packet[0] & 0x02); + input_sync(dev2); + } + + return PSMOUSE_FULL_PACKET; +} + +static int lifebook_absolute_mode(struct psmouse *psmouse) +{ + struct ps2dev *ps2dev = &psmouse->ps2dev; + unsigned char param; + + if (psmouse_reset(psmouse)) + return -1; + + /* + Enable absolute output -- ps2_command fails always but if + you leave this call out the touchsreen will never send + absolute coordinates + */ + param = lifebook_use_6byte_proto ? 0x08 : 0x07; + ps2_command(ps2dev, ¶m, PSMOUSE_CMD_SETRES); + + return 0; +} + +static void lifebook_relative_mode(struct psmouse *psmouse) +{ + struct ps2dev *ps2dev = &psmouse->ps2dev; + unsigned char param = 0x06; + + ps2_command(ps2dev, ¶m, PSMOUSE_CMD_SETRES); +} + +static void lifebook_set_resolution(struct psmouse *psmouse, unsigned int resolution) +{ + static const unsigned char params[] = { 0, 1, 2, 2, 3 }; + unsigned char p; + + if (resolution == 0 || resolution > 400) + resolution = 400; + + p = params[resolution / 100]; + ps2_command(&psmouse->ps2dev, &p, PSMOUSE_CMD_SETRES); + psmouse->resolution = 50 << p; +} + +static void lifebook_disconnect(struct psmouse *psmouse) +{ + struct lifebook_data *priv = psmouse->private; + + psmouse_reset(psmouse); + if (priv) { + input_unregister_device(priv->dev2); + kfree(priv); + } + psmouse->private = NULL; +} + +int lifebook_detect(struct psmouse *psmouse, int set_properties) +{ + if (!dmi_check_system(lifebook_dmi_table)) + return -1; + + if (desired_serio_phys && + strcmp(psmouse->ps2dev.serio->phys, desired_serio_phys)) + return -1; + + if (set_properties) { + psmouse->vendor = "Fujitsu"; + psmouse->name = "Lifebook TouchScreen"; + } + + return 0; +} + +static int lifebook_create_relative_device(struct psmouse *psmouse) +{ + struct input_dev *dev2; + struct lifebook_data *priv; + int error = -ENOMEM; + + priv = kzalloc(sizeof(struct lifebook_data), GFP_KERNEL); + dev2 = input_allocate_device(); + if (!priv || !dev2) + goto err_out; + + priv->dev2 = dev2; + snprintf(priv->phys, sizeof(priv->phys), + "%s/input1", psmouse->ps2dev.serio->phys); + + dev2->phys = priv->phys; + dev2->name = "PS/2 Touchpad"; + dev2->id.bustype = BUS_I8042; + dev2->id.vendor = 0x0002; + dev2->id.product = PSMOUSE_LIFEBOOK; + dev2->id.version = 0x0000; + dev2->dev.parent = &psmouse->ps2dev.serio->dev; + + dev2->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REL); + dev2->relbit[BIT_WORD(REL_X)] = BIT_MASK(REL_X) | BIT_MASK(REL_Y); + dev2->keybit[BIT_WORD(BTN_LEFT)] = BIT_MASK(BTN_LEFT) | + BIT_MASK(BTN_RIGHT); + + error = input_register_device(priv->dev2); + if (error) + goto err_out; + + psmouse->private = priv; + return 0; + + err_out: + input_free_device(dev2); + kfree(priv); + return error; +} + +int lifebook_init(struct psmouse *psmouse) +{ + struct input_dev *dev1 = psmouse->dev; + int max_coord = lifebook_use_6byte_proto ? 4096 : 1024; + + if (lifebook_absolute_mode(psmouse)) + return -1; + + dev1->evbit[0] = BIT_MASK(EV_ABS) | BIT_MASK(EV_KEY); + dev1->relbit[0] = 0; + dev1->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); + input_set_abs_params(dev1, ABS_X, 0, max_coord, 0, 0); + input_set_abs_params(dev1, ABS_Y, 0, max_coord, 0, 0); + + if (!desired_serio_phys) { + if (lifebook_create_relative_device(psmouse)) { + lifebook_relative_mode(psmouse); + return -1; + } + } + + psmouse->protocol_handler = lifebook_process_byte; + psmouse->set_resolution = lifebook_set_resolution; + psmouse->disconnect = lifebook_disconnect; + psmouse->reconnect = lifebook_absolute_mode; + + psmouse->model = lifebook_use_6byte_proto ? 6 : 3; + + /* + * Use packet size = 3 even when using 6-byte protocol because + * that's what POLL will return on Lifebooks (according to spec). + */ + psmouse->pktsize = 3; + + return 0; +} + diff --git a/drivers/input/mouse/lifebook.h b/drivers/input/mouse/lifebook.h new file mode 100644 index 0000000..c1647cf --- /dev/null +++ b/drivers/input/mouse/lifebook.h @@ -0,0 +1,28 @@ +/* + * Fujitsu B-series Lifebook PS/2 TouchScreen driver + * + * Copyright (c) 2005 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. + */ + +#ifndef _LIFEBOOK_H +#define _LIFEBOOK_H + +#ifdef CONFIG_MOUSE_PS2_LIFEBOOK +int lifebook_detect(struct psmouse *psmouse, int set_properties); +int lifebook_init(struct psmouse *psmouse); +#else +inline int lifebook_detect(struct psmouse *psmouse, int set_properties) +{ + return -ENOSYS; +} +inline int lifebook_init(struct psmouse *psmouse) +{ + return -ENOSYS; +} +#endif + +#endif diff --git a/drivers/input/mouse/logibm.c b/drivers/input/mouse/logibm.c new file mode 100644 index 0000000..e241311 --- /dev/null +++ b/drivers/input/mouse/logibm.c @@ -0,0 +1,185 @@ +/* + * 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/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)"); + +static struct input_dev *logibm_dev; + +static irqreturn_t logibm_interrupt(int irq, void *dev_id) +{ + 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_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 logibm_open(struct input_dev *dev) +{ + if (request_irq(logibm_irq, logibm_interrupt, 0, "logibm", NULL)) { + 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) +{ + outb(LOGIBM_DISABLE_IRQ, LOGIBM_CONTROL_PORT); + free_irq(logibm_irq, NULL); +} + +static int __init logibm_init(void) +{ + int err; + + 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) { + printk(KERN_INFO "logibm.c: Didn't find Logitech busmouse at %#x\n", LOGIBM_BASE); + err = -ENODEV; + goto err_release_region; + } + + outb(LOGIBM_DEFAULT_MODE, LOGIBM_CONFIG_PORT); + outb(LOGIBM_DISABLE_IRQ, LOGIBM_CONTROL_PORT); + + logibm_dev = input_allocate_device(); + if (!logibm_dev) { + printk(KERN_ERR "logibm.c: Not enough memory for input device\n"); + err = -ENOMEM; + goto err_release_region; + } + + logibm_dev->name = "Logitech bus mouse"; + logibm_dev->phys = "isa023c/input0"; + logibm_dev->id.bustype = BUS_ISA; + logibm_dev->id.vendor = 0x0003; + logibm_dev->id.product = 0x0001; + logibm_dev->id.version = 0x0100; + + logibm_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REL); + logibm_dev->keybit[BIT_WORD(BTN_LEFT)] = BIT_MASK(BTN_LEFT) | + BIT_MASK(BTN_MIDDLE) | BIT_MASK(BTN_RIGHT); + logibm_dev->relbit[0] = BIT_MASK(REL_X) | BIT_MASK(REL_Y); + + logibm_dev->open = logibm_open; + logibm_dev->close = logibm_close; + + err = input_register_device(logibm_dev); + if (err) + goto err_free_dev; + + return 0; + + err_free_dev: + input_free_device(logibm_dev); + err_release_region: + release_region(LOGIBM_BASE, LOGIBM_EXTENT); + + return err; +} + +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 0000000..390f1db --- /dev/null +++ b/drivers/input/mouse/logips2pp.c @@ -0,0 +1,417 @@ +/* + * 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 +#define PS2PP_KIND_TRACKMAN 4 + +/* 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 { + u8 model; + u8 kind; + u16 features; +}; + +/* + * Process a PS2++ or PS2T++ packet. + */ + +static psmouse_ret_t ps2pp_process_byte(struct psmouse *psmouse) +{ + struct input_dev *dev = psmouse->dev; + unsigned char *packet = psmouse->packet; + + if (psmouse->pktcnt < 3) + return PSMOUSE_GOOD_DATA; + +/* + * Full packet accumulated, process it + */ + + 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 | 0x0300)) + 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 ps2pp_attr_show_smartscroll(struct psmouse *psmouse, void *data, char *buf) +{ + return sprintf(buf, "%d\n", psmouse->smartscroll ? 1 : 0); +} + +static ssize_t ps2pp_attr_set_smartscroll(struct psmouse *psmouse, void *data, const char *buf, size_t count) +{ + unsigned long value; + + if (strict_strtoul(buf, 10, &value) || value > 1) + return -EINVAL; + + ps2pp_set_smartscroll(psmouse, value); + psmouse->smartscroll = value; + return count; +} + +PSMOUSE_DEFINE_ATTR(smartscroll, S_IWUSR | S_IRUGO, NULL, + ps2pp_attr_show_smartscroll, ps2pp_attr_set_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.dattr); +} + +static const struct ps2pp_info *get_model_info(unsigned char model) +{ + static const struct ps2pp_info ps2pp_list[] = { + { 1, 0, 0 }, /* Simple 2-button mouse */ + { 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 }, + { 56, PS2PP_KIND_WHEEL, PS2PP_SIDE_BTN | PS2PP_WHEEL }, /* Cordless MouseMan Wheel */ + { 61, PS2PP_KIND_MX, /* MX700 */ + PS2PP_WHEEL | PS2PP_SIDE_BTN | PS2PP_TASK_BTN | + PS2PP_EXTRA_BTN | PS2PP_NAV_BTN }, + { 66, PS2PP_KIND_MX, /* MX3100 reciver */ + PS2PP_WHEEL | PS2PP_SIDE_BTN | PS2PP_TASK_BTN | + PS2PP_EXTRA_BTN | PS2PP_NAV_BTN | PS2PP_HWHEEL }, + { 72, PS2PP_KIND_TRACKMAN, 0 }, /* T-CH11: TrackMan Marble */ + { 73, 0, PS2PP_SIDE_BTN }, + { 75, PS2PP_KIND_WHEEL, PS2PP_WHEEL }, + { 76, PS2PP_KIND_WHEEL, PS2PP_WHEEL }, + { 79, PS2PP_KIND_TRACKMAN, PS2PP_WHEEL }, /* TrackMan with wheel */ + { 80, PS2PP_KIND_WHEEL, PS2PP_SIDE_BTN | PS2PP_WHEEL }, + { 81, PS2PP_KIND_WHEEL, PS2PP_WHEEL }, + { 83, PS2PP_KIND_WHEEL, PS2PP_WHEEL }, + { 85, PS2PP_KIND_WHEEL, PS2PP_WHEEL }, + { 86, PS2PP_KIND_WHEEL, PS2PP_WHEEL }, + { 87, PS2PP_KIND_WHEEL, PS2PP_WHEEL }, + { 88, PS2PP_KIND_WHEEL, PS2PP_WHEEL }, + { 96, 0, 0 }, + { 97, PS2PP_KIND_TP3, PS2PP_WHEEL | PS2PP_HWHEEL }, + { 99, PS2PP_KIND_WHEEL, PS2PP_WHEEL }, + { 100, PS2PP_KIND_MX, /* MX510 */ + PS2PP_WHEEL | PS2PP_SIDE_BTN | PS2PP_TASK_BTN | + PS2PP_EXTRA_BTN | PS2PP_NAV_BTN }, + { 111, PS2PP_KIND_MX, PS2PP_WHEEL | PS2PP_SIDE_BTN }, /* MX300 reports task button as side */ + { 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; i < ARRAY_SIZE(ps2pp_list); 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, + const struct ps2pp_info *model_info, + int using_ps2pp) +{ + struct input_dev *input_dev = psmouse->dev; + + if (model_info->features & PS2PP_SIDE_BTN) + set_bit(BTN_SIDE, input_dev->keybit); + + if (model_info->features & PS2PP_EXTRA_BTN) + set_bit(BTN_EXTRA, input_dev->keybit); + + if (model_info->features & PS2PP_TASK_BTN) + set_bit(BTN_TASK, input_dev->keybit); + + if (model_info->features & PS2PP_NAV_BTN) { + set_bit(BTN_FORWARD, input_dev->keybit); + set_bit(BTN_BACK, input_dev->keybit); + } + + if (model_info->features & PS2PP_WHEEL) + set_bit(REL_WHEEL, input_dev->relbit); + + if (model_info->features & PS2PP_HWHEEL) + set_bit(REL_HWHEEL, input_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; + + case PS2PP_KIND_TRACKMAN: + psmouse->name = "TrackMan"; + 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; + const struct ps2pp_info *model_info; + int use_ps2pp = 0; + int error; + + 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); + + model = ((param[0] >> 4) & 0x07) | ((param[0] << 3) & 0x78); + buttons = param[1]; + + if (!model || !buttons) + return -1; + + if ((model_info = get_model_info(model)) != NULL) { + +/* + * Do Logitech PS2++ / PS2T++ magic init. + */ + if (model_info->kind == PS2PP_KIND_TP3) { /* 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; + + error = device_create_file(&psmouse->ps2dev.serio->dev, + &psmouse_attr_smartscroll.dattr); + if (error) { + printk(KERN_ERR + "logips2pp.c: failed to create smartscroll " + "sysfs attribute, error: %d\n", error); + return -1; + } + } + } + + if (buttons < 3) + clear_bit(BTN_MIDDLE, 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 0000000..6e57125 --- /dev/null +++ b/drivers/input/mouse/logips2pp.h @@ -0,0 +1,23 @@ +/* + * 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 + +#ifdef CONFIG_MOUSE_PS2_LOGIPS2PP +int ps2pp_init(struct psmouse *psmouse, int set_properties); +#else +inline int ps2pp_init(struct psmouse *psmouse, int set_properties) +{ + return -ENOSYS; +} +#endif /* CONFIG_MOUSE_PS2_LOGIPS2PP */ + +#endif diff --git a/drivers/input/mouse/pc110pad.c b/drivers/input/mouse/pc110pad.c new file mode 100644 index 0000000..fd09c8d --- /dev/null +++ b/drivers/input/mouse/pc110pad.c @@ -0,0 +1,183 @@ +/* + * 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 <linux/delay.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 irqreturn_t pc110pad_interrupt(int irq, void *ptr) +{ + int value = inb_p(pc110pad_io); + int handshake = inb_p(pc110pad_io + 2); + + outb(handshake | 1, pc110pad_io + 2); + udelay(2); + outb(handshake & ~1, pc110pad_io + 2); + udelay(2); + inb_p(0x64); + + pc110pad_data[pc110pad_count++] = value; + + if (pc110pad_count < 3) + return IRQ_HANDLED; + + 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) +{ + outb(PC110PAD_OFF, pc110pad_io + 2); +} + +static int pc110pad_open(struct input_dev *dev) +{ + pc110pad_interrupt(0, NULL); + pc110pad_interrupt(0, NULL); + pc110pad_interrupt(0, 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; + int err; + + dev = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, NULL); + if (dev) { + pci_dev_put(dev); + return -ENODEV; + } + + 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)) { + printk(KERN_ERR "pc110pad: Unable to get irq %d.\n", pc110pad_irq); + err = -EBUSY; + goto err_release_region; + } + + pc110pad_dev = input_allocate_device(); + if (!pc110pad_dev) { + printk(KERN_ERR "pc110pad: Not enough memory.\n"); + err = -ENOMEM; + goto err_free_irq; + } + + pc110pad_dev->name = "IBM PC110 TouchPad"; + pc110pad_dev->phys = "isa15e0/input0"; + pc110pad_dev->id.bustype = BUS_ISA; + pc110pad_dev->id.vendor = 0x0003; + pc110pad_dev->id.product = 0x0001; + pc110pad_dev->id.version = 0x0100; + + pc110pad_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + pc110pad_dev->absbit[0] = BIT_MASK(ABS_X) | BIT_MASK(ABS_Y); + pc110pad_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); + + pc110pad_dev->absmax[ABS_X] = 0x1ff; + pc110pad_dev->absmax[ABS_Y] = 0x0ff; + + pc110pad_dev->open = pc110pad_open; + pc110pad_dev->close = pc110pad_close; + + err = input_register_device(pc110pad_dev); + if (err) + goto err_free_dev; + + return 0; + + err_free_dev: + input_free_device(pc110pad_dev); + err_free_irq: + free_irq(pc110pad_irq, NULL); + err_release_region: + release_region(pc110pad_io, 4); + + return err; +} + +static void __exit pc110pad_exit(void) +{ + outb(PC110PAD_OFF, pc110pad_io + 2); + free_irq(pc110pad_irq, NULL); + input_unregister_device(pc110pad_dev); + 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 0000000..f8f86de --- /dev/null +++ b/drivers/input/mouse/psmouse-base.c @@ -0,0 +1,1672 @@ +/* + * 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/slab.h> +#include <linux/interrupt.h> +#include <linux/input.h> +#include <linux/serio.h> +#include <linux/init.h> +#include <linux/libps2.h> +#include <linux/mutex.h> + +#include "psmouse.h" +#include "synaptics.h" +#include "logips2pp.h" +#include "alps.h" +#include "hgpk.h" +#include "lifebook.h" +#include "trackpoint.h" +#include "touchkit_ps2.h" +#include "elantech.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 = PSMOUSE_AUTO; +static int psmouse_set_maxproto(const char *val, struct kernel_param *kp); +static int psmouse_get_maxproto(char *buffer, struct kernel_param *kp); +#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 = 5; +module_param_named(resetafter, psmouse_resetafter, uint, 0644); +MODULE_PARM_DESC(resetafter, "Reset device after so many bad packets (0 = never)."); + +static unsigned int psmouse_resync_time; +module_param_named(resync_time, psmouse_resync_time, uint, 0644); +MODULE_PARM_DESC(resync_time, "How long can mouse stay idle before forcing resync (in seconds, 0 = never)."); + +PSMOUSE_DEFINE_ATTR(protocol, S_IWUSR | S_IRUGO, + NULL, + psmouse_attr_show_protocol, psmouse_attr_set_protocol); +PSMOUSE_DEFINE_ATTR(rate, S_IWUSR | S_IRUGO, + (void *) offsetof(struct psmouse, rate), + psmouse_show_int_attr, psmouse_attr_set_rate); +PSMOUSE_DEFINE_ATTR(resolution, S_IWUSR | S_IRUGO, + (void *) offsetof(struct psmouse, resolution), + psmouse_show_int_attr, psmouse_attr_set_resolution); +PSMOUSE_DEFINE_ATTR(resetafter, S_IWUSR | S_IRUGO, + (void *) offsetof(struct psmouse, resetafter), + psmouse_show_int_attr, psmouse_set_int_attr); +PSMOUSE_DEFINE_ATTR(resync_time, S_IWUSR | S_IRUGO, + (void *) offsetof(struct psmouse, resync_time), + psmouse_show_int_attr, psmouse_set_int_attr); + +static struct attribute *psmouse_attributes[] = { + &psmouse_attr_protocol.dattr.attr, + &psmouse_attr_rate.dattr.attr, + &psmouse_attr_resolution.dattr.attr, + &psmouse_attr_resetafter.dattr.attr, + &psmouse_attr_resync_time.dattr.attr, + NULL +}; + +static struct attribute_group psmouse_attribute_group = { + .attrs = psmouse_attributes, +}; + +/* + * psmouse_mutex protects all operations changing state of mouse + * (connecting, disconnecting, changing rate or resolution via + * sysfs). We could use a per-device semaphore but since there + * rarely more than one PS/2 mouse connected and since semaphore + * is taken in "slow" paths it is not worth it. + */ +static DEFINE_MUTEX(psmouse_mutex); + +static struct workqueue_struct *kpsmoused_wq; + +struct psmouse_protocol { + enum psmouse_type type; + const char *name; + const char *alias; + int maxproto; + int (*detect)(struct psmouse *, int); + int (*init)(struct psmouse *); +}; + +/* + * 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 input_dev *dev = psmouse->dev; + unsigned char *packet = psmouse->packet; + + if (psmouse->pktcnt < psmouse->pktsize) + return PSMOUSE_GOOD_DATA; + +/* + * Full packet accumulated, process it + */ + +/* + * 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) { + switch (packet[3] & 0xC0) { + case 0x80: /* vertical scroll on IntelliMouse Explorer 4.0 */ + input_report_rel(dev, REL_WHEEL, (int) (packet[3] & 32) - (int) (packet[3] & 31)); + break; + case 0x40: /* horizontal scroll on IntelliMouse Explorer 4.0 */ + input_report_rel(dev, REL_HWHEEL, (int) (packet[3] & 32) - (int) (packet[3] & 31)); + break; + case 0x00: + case 0xC0: + 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); + break; + } + } + +/* + * 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; + } + +/* + * Cortron PS2 Trackball reports SIDE button on the 4th bit of the first + * byte. + */ + if (psmouse->type == PSMOUSE_CORTRON) { + input_report_key(dev, BTN_SIDE, (packet[0] >> 3) & 1); + packet[0] |= 0x08; + } + +/* + * 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; +} + +void psmouse_queue_work(struct psmouse *psmouse, struct delayed_work *work, + unsigned long delay) +{ + queue_delayed_work(kpsmoused_wq, work, delay); +} + +/* + * __psmouse_set_state() sets new psmouse state and resets all flags. + */ + +static inline void __psmouse_set_state(struct psmouse *psmouse, enum psmouse_state new_state) +{ + psmouse->state = new_state; + psmouse->pktcnt = psmouse->out_of_sync = 0; + psmouse->ps2dev.flags = 0; + psmouse->last = jiffies; +} + + +/* + * 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. + */ + +void psmouse_set_state(struct psmouse *psmouse, enum psmouse_state new_state) +{ + serio_pause_rx(psmouse->ps2dev.serio); + __psmouse_set_state(psmouse, new_state); + serio_continue_rx(psmouse->ps2dev.serio); +} + +/* + * psmouse_handle_byte() processes one byte of the input data stream + * by calling corresponding protocol handler. + */ + +static int psmouse_handle_byte(struct psmouse *psmouse) +{ + psmouse_ret_t rc = psmouse->protocol_handler(psmouse); + + switch (rc) { + case PSMOUSE_BAD_DATA: + if (psmouse->state == PSMOUSE_ACTIVATED) { + printk(KERN_WARNING "psmouse.c: %s at %s lost sync at byte %d\n", + psmouse->name, psmouse->phys, psmouse->pktcnt); + if (++psmouse->out_of_sync == psmouse->resetafter) { + __psmouse_set_state(psmouse, PSMOUSE_IGNORE); + printk(KERN_NOTICE "psmouse.c: issuing reconnect request\n"); + serio_reconnect(psmouse->ps2dev.serio); + return -1; + } + } + psmouse->pktcnt = 0; + 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; + } + return 0; +} + +/* + * psmouse_interrupt() handles incoming characters, either passing them + * for normal processing or gathering them as command response. + */ + +static irqreturn_t psmouse_interrupt(struct serio *serio, + unsigned char data, unsigned int flags) +{ + struct psmouse *psmouse = serio_get_drvdata(serio); + + 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_RESYNCING) + goto out; + + if (psmouse->state == PSMOUSE_ACTIVATED && + psmouse->pktcnt && time_after(jiffies, psmouse->last + HZ/2)) { + printk(KERN_INFO "psmouse.c: %s at %s lost synchronization, throwing %d bytes away.\n", + psmouse->name, psmouse->phys, psmouse->pktcnt); + psmouse->badbyte = psmouse->packet[0]; + __psmouse_set_state(psmouse, PSMOUSE_RESYNCING); + psmouse_queue_work(psmouse, &psmouse->resync_work, 0); + goto out; + } + + psmouse->packet[psmouse->pktcnt++] = data; +/* + * Check if this is a new device announcement (0xAA 0x00) + */ + if (unlikely(psmouse->packet[0] == PSMOUSE_RET_BAT && psmouse->pktcnt <= 2)) { + if (psmouse->pktcnt == 1) { + psmouse->last = jiffies; + goto out; + } + + if (psmouse->packet[1] == PSMOUSE_RET_ID) { + __psmouse_set_state(psmouse, PSMOUSE_IGNORE); + serio_reconnect(serio); + goto out; + } +/* + * Not a new device, try processing first byte normally + */ + psmouse->pktcnt = 1; + if (psmouse_handle_byte(psmouse)) + goto out; + + psmouse->packet[psmouse->pktcnt++] = data; + } + +/* + * See if we need to force resync because mouse was idle for too long + */ + if (psmouse->state == PSMOUSE_ACTIVATED && + psmouse->pktcnt == 1 && psmouse->resync_time && + time_after(jiffies, psmouse->last + psmouse->resync_time * HZ)) { + psmouse->badbyte = psmouse->packet[0]; + __psmouse_set_state(psmouse, PSMOUSE_RESYNCING); + psmouse_queue_work(psmouse, &psmouse->resync_work, 0); + goto out; + } + + psmouse->last = jiffies; + psmouse_handle_byte(psmouse); + + 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 = "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(BTN_MIDDLE, psmouse->dev->keybit); + 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; + +/* Magic to enable horizontal scrolling on IntelliMouse 4.0 */ + param[0] = 200; + ps2_command(ps2dev, param, PSMOUSE_CMD_SETRATE); + param[0] = 80; + ps2_command(ps2dev, param, PSMOUSE_CMD_SETRATE); + param[0] = 40; + ps2_command(ps2dev, param, PSMOUSE_CMD_SETRATE); + + if (set_properties) { + set_bit(BTN_MIDDLE, psmouse->dev->keybit); + set_bit(REL_WHEEL, psmouse->dev->relbit); + set_bit(REL_HWHEEL, 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]; + static const unsigned char seq[] = { 20, 60, 40, 20, 20, 60, 40, 20, 20 }; + 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; i < ARRAY_SIZE(seq); i++) { + param[0] = seq[i]; + ps2_command(ps2dev, param, 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 (set_properties) { + if (!psmouse->vendor) psmouse->vendor = "Generic"; + if (!psmouse->name) psmouse->name = "Mouse"; + } + + return 0; +} + +/* + * Cortron PS/2 protocol detection. There's no special way to detect it, so it + * must be forced by sysfs protocol writing. + */ +static int cortron_detect(struct psmouse *psmouse, int set_properties) +{ + if (set_properties) { + psmouse->vendor = "Cortron"; + psmouse->name = "PS/2 Trackball"; + set_bit(BTN_SIDE, psmouse->dev->keybit); + } + + 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; + +/* + * We always check for lifebook because it does not disturb mouse + * (it only checks DMI information). + */ + if (lifebook_detect(psmouse, set_properties) == 0) { + if (max_proto > PSMOUSE_IMEX) { + if (!set_properties || lifebook_init(psmouse) == 0) + return PSMOUSE_LIFEBOOK; + } + } + +/* + * 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. Note that probing is done even if Synaptics protocol + * support is disabled in config - we need to know if it is synaptics so we + * can reset it properly after probing for intellimouse. + */ + 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; + } + } + +/* + * Try OLPC HGPK touchpad. + */ + if (max_proto > PSMOUSE_IMEX && + hgpk_detect(psmouse, set_properties) == 0) { + if (!set_properties || hgpk_init(psmouse) == 0) + return PSMOUSE_HGPK; +/* + * Init failed, try basic relative protocols + */ + max_proto = PSMOUSE_IMEX; + } + +/* + * Try Elantech touchpad. + */ + if (max_proto > PSMOUSE_IMEX && + elantech_detect(psmouse, set_properties) == 0) { + if (!set_properties || elantech_init(psmouse) == 0) + return PSMOUSE_ELANTECH; +/* + * Init failed, try basic relative protocols + */ + max_proto = PSMOUSE_IMEX; + } + + if (max_proto > PSMOUSE_IMEX) { + if (genius_detect(psmouse, set_properties) == 0) + return PSMOUSE_GENPS; + + if (ps2pp_init(psmouse, set_properties) == 0) + return PSMOUSE_PS2PP; + + if (trackpoint_detect(psmouse, set_properties) == 0) + return PSMOUSE_TRACKPOINT; + + if (touchkit_ps2_detect(psmouse, set_properties) == 0) + return PSMOUSE_TOUCHKIT_PS2; + } + +/* + * Reset to defaults in case the device got confused by extended + * protocol probes. Note that we follow up with full reset because + * some mice put themselves to sleep when they see PSMOUSE_RESET_DIS. + */ + ps2_command(&psmouse->ps2dev, NULL, PSMOUSE_CMD_RESET_DIS); + psmouse_reset(psmouse); + + 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); + } + + return PSMOUSE_PS2; +} + +static const struct psmouse_protocol psmouse_protocols[] = { + { + .type = PSMOUSE_PS2, + .name = "PS/2", + .alias = "bare", + .maxproto = 1, + .detect = ps2bare_detect, + }, +#ifdef CONFIG_MOUSE_PS2_LOGIPS2PP + { + .type = PSMOUSE_PS2PP, + .name = "PS2++", + .alias = "logitech", + .detect = ps2pp_init, + }, +#endif + { + .type = PSMOUSE_THINKPS, + .name = "ThinkPS/2", + .alias = "thinkps", + .detect = thinking_detect, + }, + { + .type = PSMOUSE_GENPS, + .name = "GenPS/2", + .alias = "genius", + .detect = genius_detect, + }, + { + .type = PSMOUSE_IMPS, + .name = "ImPS/2", + .alias = "imps", + .maxproto = 1, + .detect = intellimouse_detect, + }, + { + .type = PSMOUSE_IMEX, + .name = "ImExPS/2", + .alias = "exps", + .maxproto = 1, + .detect = im_explorer_detect, + }, +#ifdef CONFIG_MOUSE_PS2_SYNAPTICS + { + .type = PSMOUSE_SYNAPTICS, + .name = "SynPS/2", + .alias = "synaptics", + .detect = synaptics_detect, + .init = synaptics_init, + }, +#endif +#ifdef CONFIG_MOUSE_PS2_ALPS + { + .type = PSMOUSE_ALPS, + .name = "AlpsPS/2", + .alias = "alps", + .detect = alps_detect, + .init = alps_init, + }, +#endif +#ifdef CONFIG_MOUSE_PS2_LIFEBOOK + { + .type = PSMOUSE_LIFEBOOK, + .name = "LBPS/2", + .alias = "lifebook", + .init = lifebook_init, + }, +#endif +#ifdef CONFIG_MOUSE_PS2_TRACKPOINT + { + .type = PSMOUSE_TRACKPOINT, + .name = "TPPS/2", + .alias = "trackpoint", + .detect = trackpoint_detect, + }, +#endif +#ifdef CONFIG_MOUSE_PS2_TOUCHKIT + { + .type = PSMOUSE_TOUCHKIT_PS2, + .name = "touchkitPS/2", + .alias = "touchkit", + .detect = touchkit_ps2_detect, + }, +#endif +#ifdef CONFIG_MOUSE_PS2_OLPC + { + .type = PSMOUSE_HGPK, + .name = "OLPC HGPK", + .alias = "hgpk", + .detect = hgpk_detect, + }, +#endif +#ifdef CONFIG_MOUSE_PS2_ELANTECH + { + .type = PSMOUSE_ELANTECH, + .name = "ETPS/2", + .alias = "elantech", + .detect = elantech_detect, + .init = elantech_init, + }, + #endif + { + .type = PSMOUSE_CORTRON, + .name = "CortronPS/2", + .alias = "cortps", + .detect = cortron_detect, + }, + { + .type = PSMOUSE_AUTO, + .name = "auto", + .alias = "any", + .maxproto = 1, + }, +}; + +static const struct psmouse_protocol *psmouse_protocol_by_type(enum psmouse_type type) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(psmouse_protocols); i++) + if (psmouse_protocols[i].type == type) + return &psmouse_protocols[i]; + + WARN_ON(1); + return &psmouse_protocols[0]; +} + +static const struct psmouse_protocol *psmouse_protocol_by_name(const char *name, size_t len) +{ + const struct psmouse_protocol *p; + int i; + + for (i = 0; i < ARRAY_SIZE(psmouse_protocols); i++) { + p = &psmouse_protocols[i]; + + if ((strlen(p->name) == len && !strncmp(p->name, name, len)) || + (strlen(p->alias) == len && !strncmp(p->alias, name, len))) + return &psmouse_protocols[i]; + } + + return NULL; +} + + +/* + * 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. + * Sunrex K8561 IR Keyboard/Mouse reports 0xff on second and subsequent + * ID queries, probably due to a firmware bug. + */ + + param[0] = 0xa5; + if (ps2_command(ps2dev, param, PSMOUSE_CMD_GETID)) + return -1; + + if (param[0] != 0x00 && param[0] != 0x03 && + param[0] != 0x04 && param[0] != 0xff) + 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) +{ + static const unsigned char params[] = { 0, 1, 2, 2, 3 }; + unsigned char p; + + if (resolution == 0 || resolution > 200) + resolution = 200; + + p = params[resolution / 50]; + ps2_command(&psmouse->ps2dev, &p, PSMOUSE_CMD_SETRES); + psmouse->resolution = 25 << p; +} + +/* + * Here we set the mouse report rate. + */ + +static void psmouse_set_rate(struct psmouse *psmouse, unsigned int rate) +{ + static const unsigned char rates[] = { 200, 100, 80, 60, 40, 20, 10, 0 }; + unsigned char r; + int i = 0; + + while (rates[i] > rate) i++; + r = rates[i]; + ps2_command(&psmouse->ps2dev, &r, PSMOUSE_CMD_SETRATE); + psmouse->rate = r; +} + +/* + * psmouse_initialize() initializes the mouse to a sane state. + */ + +static void psmouse_initialize(struct psmouse *psmouse) +{ +/* + * 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_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 explicitly 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_poll() - default poll hanlder. Everyone except for ALPS uses it. + */ + +static int psmouse_poll(struct psmouse *psmouse) +{ + return ps2_command(&psmouse->ps2dev, psmouse->packet, + PSMOUSE_CMD_POLL | (psmouse->pktsize << 8)); +} + + +/* + * psmouse_resync() attempts to re-validate current protocol. + */ + +static void psmouse_resync(struct work_struct *work) +{ + struct psmouse *parent = NULL, *psmouse = + container_of(work, struct psmouse, resync_work.work); + struct serio *serio = psmouse->ps2dev.serio; + psmouse_ret_t rc = PSMOUSE_GOOD_DATA; + int failed = 0, enabled = 0; + int i; + + mutex_lock(&psmouse_mutex); + + if (psmouse->state != PSMOUSE_RESYNCING) + goto out; + + if (serio->parent && serio->id.type == SERIO_PS_PSTHRU) { + parent = serio_get_drvdata(serio->parent); + psmouse_deactivate(parent); + } + +/* + * Some mice don't ACK commands sent while they are in the middle of + * transmitting motion packet. To avoid delay we use ps2_sendbyte() + * instead of ps2_command() which would wait for 200ms for an ACK + * that may never come. + * As an additional quirk ALPS touchpads may not only forget to ACK + * disable command but will stop reporting taps, so if we see that + * mouse at least once ACKs disable we will do full reconnect if ACK + * is missing. + */ + psmouse->num_resyncs++; + + if (ps2_sendbyte(&psmouse->ps2dev, PSMOUSE_CMD_DISABLE, 20)) { + if (psmouse->num_resyncs < 3 || psmouse->acks_disable_command) + failed = 1; + } else + psmouse->acks_disable_command = 1; + +/* + * Poll the mouse. If it was reset the packet will be shorter than + * psmouse->pktsize and ps2_command will fail. We do not expect and + * do not handle scenario when mouse "upgrades" its protocol while + * disconnected since it would require additional delay. If we ever + * see a mouse that does it we'll adjust the code. + */ + if (!failed) { + if (psmouse->poll(psmouse)) + failed = 1; + else { + psmouse_set_state(psmouse, PSMOUSE_CMD_MODE); + for (i = 0; i < psmouse->pktsize; i++) { + psmouse->pktcnt++; + rc = psmouse->protocol_handler(psmouse); + if (rc != PSMOUSE_GOOD_DATA) + break; + } + if (rc != PSMOUSE_FULL_PACKET) + failed = 1; + psmouse_set_state(psmouse, PSMOUSE_RESYNCING); + } + } +/* + * Now try to enable mouse. We try to do that even if poll failed and also + * repeat our attempts 5 times, otherwise we may be left out with disabled + * mouse. + */ + for (i = 0; i < 5; i++) { + if (!ps2_command(&psmouse->ps2dev, NULL, PSMOUSE_CMD_ENABLE)) { + enabled = 1; + break; + } + msleep(200); + } + + if (!enabled) { + printk(KERN_WARNING "psmouse.c: failed to re-enable mouse on %s\n", + psmouse->ps2dev.serio->phys); + failed = 1; + } + + if (failed) { + psmouse_set_state(psmouse, PSMOUSE_IGNORE); + printk(KERN_INFO "psmouse.c: resync failed, issuing reconnect request\n"); + serio_reconnect(serio); + } else + psmouse_set_state(psmouse, PSMOUSE_ACTIVATED); + + if (parent) + psmouse_activate(parent); + out: + mutex_unlock(&psmouse_mutex); +} + +/* + * psmouse_cleanup() resets the mouse into power-on state. + */ + +static void psmouse_cleanup(struct serio *serio) +{ + struct psmouse *psmouse = serio_get_drvdata(serio); + struct psmouse *parent = NULL; + + mutex_lock(&psmouse_mutex); + + if (serio->parent && serio->id.type == SERIO_PS_PSTHRU) { + parent = serio_get_drvdata(serio->parent); + psmouse_deactivate(parent); + } + + psmouse_deactivate(psmouse); + + if (psmouse->cleanup) + psmouse->cleanup(psmouse); + + psmouse_reset(psmouse); + +/* + * Some boxes, such as HP nx7400, get terribly confused if mouse + * is not fully enabled before suspending/shutting down. + */ + ps2_command(&psmouse->ps2dev, NULL, PSMOUSE_CMD_ENABLE); + + if (parent) { + if (parent->pt_deactivate) + parent->pt_deactivate(parent); + + psmouse_activate(parent); + } + + mutex_unlock(&psmouse_mutex); +} + +/* + * psmouse_disconnect() closes and frees. + */ + +static void psmouse_disconnect(struct serio *serio) +{ + struct psmouse *psmouse, *parent = NULL; + + psmouse = serio_get_drvdata(serio); + + sysfs_remove_group(&serio->dev.kobj, &psmouse_attribute_group); + + mutex_lock(&psmouse_mutex); + + psmouse_set_state(psmouse, PSMOUSE_CMD_MODE); + + /* make sure we don't have a resync in progress */ + mutex_unlock(&psmouse_mutex); + flush_workqueue(kpsmoused_wq); + mutex_lock(&psmouse_mutex); + + if (serio->parent && serio->id.type == SERIO_PS_PSTHRU) { + parent = serio_get_drvdata(serio->parent); + psmouse_deactivate(parent); + } + + if (psmouse->disconnect) + psmouse->disconnect(psmouse); + + if (parent && parent->pt_deactivate) + parent->pt_deactivate(parent); + + psmouse_set_state(psmouse, PSMOUSE_IGNORE); + + serio_close(serio); + serio_set_drvdata(serio, NULL); + input_unregister_device(psmouse->dev); + kfree(psmouse); + + if (parent) + psmouse_activate(parent); + + mutex_unlock(&psmouse_mutex); +} + +static int psmouse_switch_protocol(struct psmouse *psmouse, const struct psmouse_protocol *proto) +{ + struct input_dev *input_dev = psmouse->dev; + + input_dev->dev.parent = &psmouse->ps2dev.serio->dev; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REL); + input_dev->keybit[BIT_WORD(BTN_MOUSE)] = BIT_MASK(BTN_LEFT) | + BIT_MASK(BTN_MIDDLE) | BIT_MASK(BTN_RIGHT); + input_dev->relbit[0] = BIT_MASK(REL_X) | BIT_MASK(REL_Y); + + psmouse->set_rate = psmouse_set_rate; + psmouse->set_resolution = psmouse_set_resolution; + psmouse->poll = psmouse_poll; + psmouse->protocol_handler = psmouse_process_byte; + psmouse->pktsize = 3; + + if (proto && (proto->detect || proto->init)) { + if (proto->detect && proto->detect(psmouse, 1) < 0) + return -1; + + if (proto->init && proto->init(psmouse) < 0) + return -1; + + psmouse->type = proto->type; + } + else + psmouse->type = psmouse_extensions(psmouse, psmouse_max_proto, 1); + + /* + * If mouse's packet size is 3 there is no point in polling the + * device in hopes to detect protocol reset - we won't get less + * than 3 bytes response anyhow. + */ + if (psmouse->pktsize == 3) + psmouse->resync_time = 0; + + /* + * Some smart KVMs fake response to POLL command returning just + * 3 bytes and messing up our resync logic, so if initial poll + * fails we won't try polling the device anymore. Hopefully + * such KVM will maintain initially selected protocol. + */ + if (psmouse->resync_time && psmouse->poll(psmouse)) + psmouse->resync_time = 0; + + snprintf(psmouse->devname, sizeof(psmouse->devname), "%s %s %s", + psmouse_protocol_by_type(psmouse->type)->name, psmouse->vendor, psmouse->name); + + input_dev->name = psmouse->devname; + input_dev->phys = psmouse->phys; + input_dev->id.bustype = BUS_I8042; + input_dev->id.vendor = 0x0002; + input_dev->id.product = psmouse->type; + input_dev->id.version = psmouse->model; + + return 0; +} + +/* + * 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; + struct input_dev *input_dev; + int retval = 0, error = -ENOMEM; + + mutex_lock(&psmouse_mutex); + + /* + * 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); + } + + psmouse = kzalloc(sizeof(struct psmouse), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!psmouse || !input_dev) + goto err_free; + + ps2_init(&psmouse->ps2dev, serio); + INIT_DELAYED_WORK(&psmouse->resync_work, psmouse_resync); + psmouse->dev = input_dev; + snprintf(psmouse->phys, sizeof(psmouse->phys), "%s/input0", serio->phys); + + psmouse_set_state(psmouse, PSMOUSE_INITIALIZING); + + serio_set_drvdata(serio, psmouse); + + error = serio_open(serio, drv); + if (error) + goto err_clear_drvdata; + + if (psmouse_probe(psmouse) < 0) { + error = -ENODEV; + goto err_close_serio; + } + + psmouse->rate = psmouse_rate; + psmouse->resolution = psmouse_resolution; + psmouse->resetafter = psmouse_resetafter; + psmouse->resync_time = parent ? 0 : psmouse_resync_time; + psmouse->smartscroll = psmouse_smartscroll; + + psmouse_switch_protocol(psmouse, NULL); + + psmouse_set_state(psmouse, PSMOUSE_CMD_MODE); + psmouse_initialize(psmouse); + + error = input_register_device(psmouse->dev); + if (error) + goto err_protocol_disconnect; + + if (parent && parent->pt_activate) + parent->pt_activate(parent); + + error = sysfs_create_group(&serio->dev.kobj, &psmouse_attribute_group); + if (error) + goto err_pt_deactivate; + + psmouse_activate(psmouse); + + out: + /* If this is a pass-through port the parent needs to be re-activated */ + if (parent) + psmouse_activate(parent); + + mutex_unlock(&psmouse_mutex); + return retval; + + err_pt_deactivate: + if (parent && parent->pt_deactivate) + parent->pt_deactivate(parent); + input_unregister_device(psmouse->dev); + input_dev = NULL; /* so we don't try to free it below */ + err_protocol_disconnect: + if (psmouse->disconnect) + psmouse->disconnect(psmouse); + psmouse_set_state(psmouse, PSMOUSE_IGNORE); + err_close_serio: + serio_close(serio); + err_clear_drvdata: + serio_set_drvdata(serio, NULL); + err_free: + input_free_device(input_dev); + kfree(psmouse); + + retval = error; + goto out; +} + + +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; + } + + mutex_lock(&psmouse_mutex); + + 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); + + mutex_unlock(&psmouse_mutex); + 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, struct device_attribute *devattr, + char *buf) +{ + struct serio *serio = to_serio_port(dev); + struct psmouse_attribute *attr = to_psmouse_attr(devattr); + struct psmouse *psmouse; + int retval; + + retval = serio_pin_driver(serio); + if (retval) + return retval; + + if (serio->drv != &psmouse_drv) { + retval = -ENODEV; + goto out; + } + + psmouse = serio_get_drvdata(serio); + + retval = attr->show(psmouse, attr->data, buf); + +out: + serio_unpin_driver(serio); + return retval; +} + +ssize_t psmouse_attr_set_helper(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct serio *serio = to_serio_port(dev); + struct psmouse_attribute *attr = to_psmouse_attr(devattr); + struct psmouse *psmouse, *parent = NULL; + int retval; + + retval = serio_pin_driver(serio); + if (retval) + return retval; + + if (serio->drv != &psmouse_drv) { + retval = -ENODEV; + goto out_unpin; + } + + retval = mutex_lock_interruptible(&psmouse_mutex); + if (retval) + goto out_unpin; + + psmouse = serio_get_drvdata(serio); + + if (attr->protect) { + if (psmouse->state == PSMOUSE_IGNORE) { + retval = -ENODEV; + goto out_unlock; + } + + if (serio->parent && serio->id.type == SERIO_PS_PSTHRU) { + parent = serio_get_drvdata(serio->parent); + psmouse_deactivate(parent); + } + + psmouse_deactivate(psmouse); + } + + retval = attr->set(psmouse, attr->data, buf, count); + + if (attr->protect) { + if (retval != -ENODEV) + psmouse_activate(psmouse); + + if (parent) + psmouse_activate(parent); + } + + out_unlock: + mutex_unlock(&psmouse_mutex); + out_unpin: + serio_unpin_driver(serio); + return retval; +} + +static ssize_t psmouse_show_int_attr(struct psmouse *psmouse, void *offset, char *buf) +{ + unsigned int *field = (unsigned int *)((char *)psmouse + (size_t)offset); + + return sprintf(buf, "%u\n", *field); +} + +static ssize_t psmouse_set_int_attr(struct psmouse *psmouse, void *offset, const char *buf, size_t count) +{ + unsigned int *field = (unsigned int *)((char *)psmouse + (size_t)offset); + unsigned long value; + + if (strict_strtoul(buf, 10, &value)) + return -EINVAL; + + if ((unsigned int)value != value) + return -EINVAL; + + *field = value; + + return count; +} + +static ssize_t psmouse_attr_show_protocol(struct psmouse *psmouse, void *data, char *buf) +{ + return sprintf(buf, "%s\n", psmouse_protocol_by_type(psmouse->type)->name); +} + +static ssize_t psmouse_attr_set_protocol(struct psmouse *psmouse, void *data, const char *buf, size_t count) +{ + struct serio *serio = psmouse->ps2dev.serio; + struct psmouse *parent = NULL; + struct input_dev *old_dev, *new_dev; + const struct psmouse_protocol *proto, *old_proto; + int error; + int retry = 0; + + proto = psmouse_protocol_by_name(buf, count); + if (!proto) + return -EINVAL; + + if (psmouse->type == proto->type) + return count; + + new_dev = input_allocate_device(); + if (!new_dev) + return -ENOMEM; + + while (serio->child) { + if (++retry > 3) { + printk(KERN_WARNING "psmouse: failed to destroy child port, protocol change aborted.\n"); + input_free_device(new_dev); + return -EIO; + } + + mutex_unlock(&psmouse_mutex); + serio_unpin_driver(serio); + serio_unregister_child_port(serio); + serio_pin_driver_uninterruptible(serio); + mutex_lock(&psmouse_mutex); + + if (serio->drv != &psmouse_drv) { + input_free_device(new_dev); + return -ENODEV; + } + + if (psmouse->type == proto->type) { + input_free_device(new_dev); + return count; /* switched by other thread */ + } + } + + if (serio->parent && serio->id.type == SERIO_PS_PSTHRU) { + parent = serio_get_drvdata(serio->parent); + if (parent->pt_deactivate) + parent->pt_deactivate(parent); + } + + old_dev = psmouse->dev; + old_proto = psmouse_protocol_by_type(psmouse->type); + + if (psmouse->disconnect) + psmouse->disconnect(psmouse); + + psmouse_set_state(psmouse, PSMOUSE_IGNORE); + + psmouse->dev = new_dev; + psmouse_set_state(psmouse, PSMOUSE_INITIALIZING); + + if (psmouse_switch_protocol(psmouse, proto) < 0) { + psmouse_reset(psmouse); + /* default to PSMOUSE_PS2 */ + psmouse_switch_protocol(psmouse, &psmouse_protocols[0]); + } + + psmouse_initialize(psmouse); + psmouse_set_state(psmouse, PSMOUSE_CMD_MODE); + + error = input_register_device(psmouse->dev); + if (error) { + if (psmouse->disconnect) + psmouse->disconnect(psmouse); + + psmouse_set_state(psmouse, PSMOUSE_IGNORE); + input_free_device(new_dev); + psmouse->dev = old_dev; + psmouse_set_state(psmouse, PSMOUSE_INITIALIZING); + psmouse_switch_protocol(psmouse, old_proto); + psmouse_initialize(psmouse); + psmouse_set_state(psmouse, PSMOUSE_CMD_MODE); + + return error; + } + + input_unregister_device(old_dev); + + if (parent && parent->pt_activate) + parent->pt_activate(parent); + + return count; +} + +static ssize_t psmouse_attr_set_rate(struct psmouse *psmouse, void *data, const char *buf, size_t count) +{ + unsigned long value; + + if (strict_strtoul(buf, 10, &value)) + return -EINVAL; + + psmouse->set_rate(psmouse, value); + return count; +} + +static ssize_t psmouse_attr_set_resolution(struct psmouse *psmouse, void *data, const char *buf, size_t count) +{ + unsigned long value; + + if (strict_strtoul(buf, 10, &value)) + return -EINVAL; + + psmouse->set_resolution(psmouse, value); + return count; +} + + +static int psmouse_set_maxproto(const char *val, struct kernel_param *kp) +{ + const struct psmouse_protocol *proto; + + if (!val) + return -EINVAL; + + proto = psmouse_protocol_by_name(val, strlen(val)); + + if (!proto || !proto->maxproto) + return -EINVAL; + + *((unsigned int *)kp->arg) = proto->type; + + return 0; +} + +static int psmouse_get_maxproto(char *buffer, struct kernel_param *kp) +{ + int type = *((unsigned int *)kp->arg); + + return sprintf(buffer, "%s\n", psmouse_protocol_by_type(type)->name); +} + +static int __init psmouse_init(void) +{ + int err; + + kpsmoused_wq = create_singlethread_workqueue("kpsmoused"); + if (!kpsmoused_wq) { + printk(KERN_ERR "psmouse: failed to create kpsmoused workqueue\n"); + return -ENOMEM; + } + + err = serio_register_driver(&psmouse_drv); + if (err) + destroy_workqueue(kpsmoused_wq); + + return err; +} + +static void __exit psmouse_exit(void) +{ + serio_unregister_driver(&psmouse_drv); + destroy_workqueue(kpsmoused_wq); +} + +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 0000000..54ed267 --- /dev/null +++ b/drivers/input/mouse/psmouse.h @@ -0,0 +1,140 @@ +#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 0x00eb /* caller sets number of bytes to receive */ +#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_RESYNCING, + 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; + struct delayed_work resync_work; + char *vendor; + char *name; + unsigned char packet[8]; + unsigned char badbyte; + unsigned char pktcnt; + unsigned char pktsize; + unsigned char type; + unsigned char acks_disable_command; + unsigned int model; + unsigned long last; + unsigned long out_of_sync; + unsigned long num_resyncs; + enum psmouse_state state; + char devname[64]; + char phys[32]; + + unsigned int rate; + unsigned int resolution; + unsigned int resetafter; + unsigned int resync_time; + unsigned int smartscroll; /* Logitech only */ + + psmouse_ret_t (*protocol_handler)(struct psmouse *psmouse); + 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 (*cleanup)(struct psmouse *psmouse); + int (*poll)(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, + PSMOUSE_LIFEBOOK, + PSMOUSE_TRACKPOINT, + PSMOUSE_TOUCHKIT_PS2, + PSMOUSE_CORTRON, + PSMOUSE_HGPK, + PSMOUSE_ELANTECH, + PSMOUSE_AUTO /* This one should always be last */ +}; + +void psmouse_queue_work(struct psmouse *psmouse, struct delayed_work *work, + unsigned long delay); +int psmouse_sliced_command(struct psmouse *psmouse, unsigned char command); +int psmouse_reset(struct psmouse *psmouse); +void psmouse_set_state(struct psmouse *psmouse, enum psmouse_state new_state); +void psmouse_set_resolution(struct psmouse *psmouse, unsigned int resolution); + +struct psmouse_attribute { + struct device_attribute dattr; + void *data; + ssize_t (*show)(struct psmouse *psmouse, void *data, char *buf); + ssize_t (*set)(struct psmouse *psmouse, void *data, + const char *buf, size_t count); + int protect; +}; +#define to_psmouse_attr(a) container_of((a), struct psmouse_attribute, dattr) + +ssize_t psmouse_attr_show_helper(struct device *dev, struct device_attribute *attr, + char *buf); +ssize_t psmouse_attr_set_helper(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count); + +#define __PSMOUSE_DEFINE_ATTR(_name, _mode, _data, _show, _set, _protect) \ +static ssize_t _show(struct psmouse *, void *data, char *); \ +static ssize_t _set(struct psmouse *, void *data, const char *, size_t); \ +static struct psmouse_attribute psmouse_attr_##_name = { \ + .dattr = { \ + .attr = { \ + .name = __stringify(_name), \ + .mode = _mode, \ + }, \ + .show = psmouse_attr_show_helper, \ + .store = psmouse_attr_set_helper, \ + }, \ + .data = _data, \ + .show = _show, \ + .set = _set, \ + .protect = _protect, \ +} + +#define PSMOUSE_DEFINE_ATTR(_name, _mode, _data, _show, _set) \ + __PSMOUSE_DEFINE_ATTR(_name, _mode, _data, _show, _set, 1) + +#endif /* _PSMOUSE_H */ diff --git a/drivers/input/mouse/rpcmouse.c b/drivers/input/mouse/rpcmouse.c new file mode 100644 index 0000000..56c079e --- /dev/null +++ b/drivers/input/mouse/rpcmouse.c @@ -0,0 +1,116 @@ +/* + * 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/ptrace.h> +#include <linux/interrupt.h> +#include <linux/init.h> +#include <linux/input.h> + +#include <mach/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; + +static irqreturn_t rpcmouse_irq(int irq, void *dev_id) +{ + 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_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) +{ + int err; + + rpcmouse_dev = input_allocate_device(); + if (!rpcmouse_dev) + return -ENOMEM; + + rpcmouse_dev->name = "Acorn RiscPC Mouse"; + rpcmouse_dev->phys = "rpcmouse/input0"; + rpcmouse_dev->id.bustype = BUS_HOST; + rpcmouse_dev->id.vendor = 0x0005; + rpcmouse_dev->id.product = 0x0001; + rpcmouse_dev->id.version = 0x0100; + + rpcmouse_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REL); + rpcmouse_dev->keybit[BIT_WORD(BTN_LEFT)] = BIT_MASK(BTN_LEFT) | + BIT_MASK(BTN_MIDDLE) | BIT_MASK(BTN_RIGHT); + rpcmouse_dev->relbit[0] = BIT_MASK(REL_X) | BIT_MASK(REL_Y); + + rpcmouse_lastx = (short) iomd_readl(IOMD_MOUSEX); + rpcmouse_lasty = (short) iomd_readl(IOMD_MOUSEY); + + if (request_irq(IRQ_VSYNCPULSE, rpcmouse_irq, IRQF_SHARED, "rpcmouse", rpcmouse_dev)) { + printk(KERN_ERR "rpcmouse: unable to allocate VSYNC interrupt\n"); + err = -EBUSY; + goto err_free_dev; + } + + err = input_register_device(rpcmouse_dev); + if (err) + goto err_free_irq; + + return 0; + + err_free_irq: + free_irq(IRQ_VSYNCPULSE, rpcmouse_dev); + err_free_dev: + input_free_device(rpcmouse_dev); + + return err; +} + +static void __exit rpcmouse_exit(void) +{ + free_irq(IRQ_VSYNCPULSE, rpcmouse_dev); + input_unregister_device(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 0000000..17ff137 --- /dev/null +++ b/drivers/input/mouse/sermouse.c @@ -0,0 +1,369 @@ +/* + * 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/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 const 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 input_dev *dev = sermouse->dev; + signed char *buf = sermouse->buf; + + 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->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 input_dev *dev = sermouse->dev; + signed char *buf = sermouse->buf; + + if (data & 0x40) + sermouse->count = 0; + else if (sermouse->count == 0) + return; + + 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 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); + else + sermouse_process_msc(sermouse, data); + + 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); + + serio_close(serio); + serio_set_drvdata(serio, NULL); + input_unregister_device(sermouse->dev); + 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; + struct input_dev *input_dev; + unsigned char c = serio->id.extra; + int err = -ENOMEM; + + sermouse = kzalloc(sizeof(struct sermouse), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!sermouse || !input_dev) + goto fail1; + + sermouse->dev = input_dev; + snprintf(sermouse->phys, sizeof(sermouse->phys), "%s/input0", serio->phys); + sermouse->type = serio->id.proto; + + input_dev->name = sermouse_protocols[sermouse->type]; + input_dev->phys = sermouse->phys; + input_dev->id.bustype = BUS_RS232; + input_dev->id.vendor = sermouse->type; + input_dev->id.product = c; + input_dev->id.version = 0x0100; + input_dev->dev.parent = &serio->dev; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REL); + input_dev->keybit[BIT_WORD(BTN_MOUSE)] = BIT_MASK(BTN_LEFT) | + BIT_MASK(BTN_RIGHT); + input_dev->relbit[0] = BIT_MASK(REL_X) | BIT_MASK(REL_Y); + + if (c & 0x01) set_bit(BTN_MIDDLE, input_dev->keybit); + if (c & 0x02) set_bit(BTN_SIDE, input_dev->keybit); + if (c & 0x04) set_bit(BTN_EXTRA, input_dev->keybit); + if (c & 0x10) set_bit(REL_WHEEL, input_dev->relbit); + if (c & 0x20) set_bit(REL_HWHEEL, input_dev->relbit); + + serio_set_drvdata(serio, sermouse); + + err = serio_open(serio, drv); + if (err) + goto fail2; + + err = input_register_device(sermouse->dev); + if (err) + goto fail3; + + return 0; + + fail3: serio_close(serio); + fail2: serio_set_drvdata(serio, NULL); + fail1: input_free_device(input_dev); + kfree(sermouse); + return err; +} + +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) +{ + return serio_register_driver(&sermouse_drv); +} + +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 0000000..d349c4a --- /dev/null +++ b/drivers/input/mouse/synaptics.c @@ -0,0 +1,708 @@ +/* + * 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 + + +/***************************************************************************** + * Stuff we need even when we do not want native Synaptics support + ****************************************************************************/ + +/* + * 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; +} + +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 -ENODEV; + + if (set_properties) { + psmouse->vendor = "Synaptics"; + psmouse->name = "TouchPad"; + } + + return 0; +} + +void synaptics_reset(struct psmouse *psmouse) +{ + /* reset touchpad back to relative mode, gestures enabled */ + synaptics_mode_cmd(psmouse, 0); +} + +#ifdef CONFIG_MOUSE_PS2_SYNAPTICS + +/***************************************************************************** + * 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; +} + +/* + * 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 int synaptics_query_hardware(struct psmouse *psmouse) +{ + int retries = 0; + + while ((retries++ < 3) && psmouse_reset(psmouse)) + /* empty */; + + 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); + serio_interrupt(ptport, packet[4], 0); + serio_interrupt(ptport, packet[5], 0); + if (child->pktsize == 4) + serio_interrupt(ptport, packet[2], 0); + } else + serio_interrupt(ptport, packet[1], 0); +} + +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->pktsize == 4) + 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 = kzalloc(sizeof(struct serio), GFP_KERNEL); + if (!serio) { + printk(KERN_ERR "synaptics: not enough memory to allocate pass-through port\n"); + return; + } + + 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 const unsigned char newabs_mask[] = { 0xC8, 0x00, 0x00, 0xC8, 0x00 }; + static const unsigned char newabs_rel_mask[] = { 0xC0, 0x00, 0x00, 0xC0, 0x00 }; + static const unsigned char newabs_rslt[] = { 0x80, 0x00, 0x00, 0xC0, 0x00 }; + static const unsigned char oldabs_mask[] = { 0xC0, 0x60, 0x00, 0xC0, 0x60 }; + static const 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 synaptics_data *priv = psmouse->private; + + 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); +} + +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; +} + +#if defined(__i386__) +#include <linux/dmi.h> +static const struct dmi_system_id toshiba_dmi_table[] = { + { + .ident = "Toshiba Satellite", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "TOSHIBA"), + DMI_MATCH(DMI_PRODUCT_NAME, "Satellite"), + }, + }, + { + .ident = "Toshiba Dynabook", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "TOSHIBA"), + DMI_MATCH(DMI_PRODUCT_NAME, "dynabook"), + }, + }, + { + .ident = "Toshiba Portege M300", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "TOSHIBA"), + DMI_MATCH(DMI_PRODUCT_NAME, "PORTEGE M300"), + }, + }, + { } +}; +#endif + +int synaptics_init(struct psmouse *psmouse) +{ + struct synaptics_data *priv; + + psmouse->private = priv = kzalloc(sizeof(struct synaptics_data), GFP_KERNEL); + if (!priv) + return -1; + + 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; + + printk(KERN_INFO "Synaptics Touchpad, model: %ld, fw: %ld.%ld, id: %#lx, caps: %#lx/%#lx\n", + SYN_ID_MODEL(priv->identity), + SYN_ID_MAJOR(priv->identity), SYN_ID_MINOR(priv->identity), + priv->model_id, priv->capabilities, priv->ext_cap); + + set_input_params(psmouse->dev, priv); + + /* + * Encode touchpad model so that it can be used to set + * input device->id.version and be visible to userspace. + * Because version is __u16 we have to drop something. + * Hardware info bits seem to be good candidates as they + * are documented to be for Synaptics corp. internal use. + */ + psmouse->model = ((priv->model_id & 0x00ff0000) >> 8) | + (priv->model_id & 0x000000ff); + + psmouse->protocol_handler = synaptics_process_byte; + psmouse->set_rate = synaptics_set_rate; + psmouse->disconnect = synaptics_disconnect; + psmouse->reconnect = synaptics_reconnect; + psmouse->cleanup = synaptics_reset; + psmouse->pktsize = 6; + /* Synaptics can usually stay in sync without extra help */ + psmouse->resync_time = 0; + + 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 %s detected, limiting rate to 40pps.\n", + dmi_get_system_info(DMI_PRODUCT_NAME)); + psmouse->rate = 40; + } +#endif + + return 0; + + init_fail: + kfree(priv); + return -1; +} + +#else /* CONFIG_MOUSE_PS2_SYNAPTICS */ + +int synaptics_init(struct psmouse *psmouse) +{ + return -ENOSYS; +} + +#endif /* CONFIG_MOUSE_PS2_SYNAPTICS */ + diff --git a/drivers/input/mouse/synaptics.h b/drivers/input/mouse/synaptics.h new file mode 100644 index 0000000..02aa4cf --- /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 + +/* 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; +}; + +int synaptics_detect(struct psmouse *psmouse, int set_properties); +int synaptics_init(struct psmouse *psmouse); +void synaptics_reset(struct psmouse *psmouse); + +#endif /* _SYNAPTICS_H */ diff --git a/drivers/input/mouse/touchkit_ps2.c b/drivers/input/mouse/touchkit_ps2.c new file mode 100644 index 0000000..3fadb2a --- /dev/null +++ b/drivers/input/mouse/touchkit_ps2.c @@ -0,0 +1,100 @@ +/* ---------------------------------------------------------------------------- + * touchkit_ps2.c -- Driver for eGalax TouchKit PS/2 Touchscreens + * + * Copyright (C) 2005 by Stefan Lucke + * Copyright (C) 2004 by Daniel Ritz + * Copyright (C) by Todd E. Johnson (mtouchusb.c) + * + * 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. + * + * Based upon touchkitusb.c + * + * Vendor documentation is available in support section of: + * http://www.egalax.com.tw/ + */ + +#include <linux/kernel.h> +#include <linux/slab.h> + +#include <linux/input.h> +#include <linux/serio.h> +#include <linux/libps2.h> + +#include "psmouse.h" +#include "touchkit_ps2.h" + +#define TOUCHKIT_MAX_XC 0x07ff +#define TOUCHKIT_MAX_YC 0x07ff + +#define TOUCHKIT_CMD 0x0a +#define TOUCHKIT_CMD_LENGTH 1 + +#define TOUCHKIT_CMD_ACTIVE 'A' +#define TOUCHKIT_CMD_FIRMWARE_VERSION 'D' +#define TOUCHKIT_CMD_CONTROLLER_TYPE 'E' + +#define TOUCHKIT_SEND_PARMS(s, r, c) ((s) << 12 | (r) << 8 | (c)) + +#define TOUCHKIT_GET_TOUCHED(packet) (((packet)[0]) & 0x01) +#define TOUCHKIT_GET_X(packet) (((packet)[1] << 7) | (packet)[2]) +#define TOUCHKIT_GET_Y(packet) (((packet)[3] << 7) | (packet)[4]) + +static psmouse_ret_t touchkit_ps2_process_byte(struct psmouse *psmouse) +{ + unsigned char *packet = psmouse->packet; + struct input_dev *dev = psmouse->dev; + + if (psmouse->pktcnt != 5) + return PSMOUSE_GOOD_DATA; + + input_report_abs(dev, ABS_X, TOUCHKIT_GET_X(packet)); + input_report_abs(dev, ABS_Y, TOUCHKIT_GET_Y(packet)); + input_report_key(dev, BTN_TOUCH, TOUCHKIT_GET_TOUCHED(packet)); + input_sync(dev); + + return PSMOUSE_FULL_PACKET; +} + +int touchkit_ps2_detect(struct psmouse *psmouse, int set_properties) +{ + struct input_dev *dev = psmouse->dev; + unsigned char param[3]; + int command; + + param[0] = TOUCHKIT_CMD_LENGTH; + param[1] = TOUCHKIT_CMD_ACTIVE; + command = TOUCHKIT_SEND_PARMS(2, 3, TOUCHKIT_CMD); + + if (ps2_command(&psmouse->ps2dev, param, command)) + return -ENODEV; + + if (param[0] != TOUCHKIT_CMD || param[1] != 0x01 || + param[2] != TOUCHKIT_CMD_ACTIVE) + return -ENODEV; + + if (set_properties) { + dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + set_bit(BTN_TOUCH, dev->keybit); + input_set_abs_params(dev, ABS_X, 0, TOUCHKIT_MAX_XC, 0, 0); + input_set_abs_params(dev, ABS_Y, 0, TOUCHKIT_MAX_YC, 0, 0); + + psmouse->vendor = "eGalax"; + psmouse->name = "Touchscreen"; + psmouse->protocol_handler = touchkit_ps2_process_byte; + psmouse->pktsize = 5; + } + + return 0; +} diff --git a/drivers/input/mouse/touchkit_ps2.h b/drivers/input/mouse/touchkit_ps2.h new file mode 100644 index 0000000..8a0dd35 --- /dev/null +++ b/drivers/input/mouse/touchkit_ps2.h @@ -0,0 +1,25 @@ +/* ---------------------------------------------------------------------------- + * touchkit_ps2.h -- Driver for eGalax TouchKit PS/2 Touchscreens + * + * Copyright (C) 2005 by Stefan Lucke + * Copyright (c) 2005 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. + */ + +#ifndef _TOUCHKIT_PS2_H +#define _TOUCHKIT_PS2_H + +#ifdef CONFIG_MOUSE_PS2_TOUCHKIT +int touchkit_ps2_detect(struct psmouse *psmouse, int set_properties); +#else +static inline int touchkit_ps2_detect(struct psmouse *psmouse, + int set_properties) +{ + return -ENOSYS; +} +#endif /* CONFIG_MOUSE_PS2_TOUCHKIT */ + +#endif diff --git a/drivers/input/mouse/trackpoint.c b/drivers/input/mouse/trackpoint.c new file mode 100644 index 0000000..e68c814 --- /dev/null +++ b/drivers/input/mouse/trackpoint.c @@ -0,0 +1,331 @@ +/* + * Stephen Evanchik <evanchsa@gmail.com> + * + * 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/delay.h> +#include <linux/serio.h> +#include <linux/module.h> +#include <linux/input.h> +#include <linux/libps2.h> +#include <linux/proc_fs.h> +#include <asm/uaccess.h> +#include "psmouse.h" +#include "trackpoint.h" + +/* + * Device IO: read, write and toggle bit + */ +static int trackpoint_read(struct ps2dev *ps2dev, unsigned char loc, unsigned char *results) +{ + if (ps2_command(ps2dev, NULL, MAKE_PS2_CMD(0, 0, TP_COMMAND)) || + ps2_command(ps2dev, results, MAKE_PS2_CMD(0, 1, loc))) { + return -1; + } + + return 0; +} + +static int trackpoint_write(struct ps2dev *ps2dev, unsigned char loc, unsigned char val) +{ + if (ps2_command(ps2dev, NULL, MAKE_PS2_CMD(0, 0, TP_COMMAND)) || + ps2_command(ps2dev, NULL, MAKE_PS2_CMD(0, 0, TP_WRITE_MEM)) || + ps2_command(ps2dev, NULL, MAKE_PS2_CMD(0, 0, loc)) || + ps2_command(ps2dev, NULL, MAKE_PS2_CMD(0, 0, val))) { + return -1; + } + + return 0; +} + +static int trackpoint_toggle_bit(struct ps2dev *ps2dev, unsigned char loc, unsigned char mask) +{ + /* Bad things will happen if the loc param isn't in this range */ + if (loc < 0x20 || loc >= 0x2F) + return -1; + + if (ps2_command(ps2dev, NULL, MAKE_PS2_CMD(0, 0, TP_COMMAND)) || + ps2_command(ps2dev, NULL, MAKE_PS2_CMD(0, 0, TP_TOGGLE)) || + ps2_command(ps2dev, NULL, MAKE_PS2_CMD(0, 0, loc)) || + ps2_command(ps2dev, NULL, MAKE_PS2_CMD(0, 0, mask))) { + return -1; + } + + return 0; +} + + +/* + * Trackpoint-specific attributes + */ +struct trackpoint_attr_data { + size_t field_offset; + unsigned char command; + unsigned char mask; + unsigned char inverted; +}; + +static ssize_t trackpoint_show_int_attr(struct psmouse *psmouse, void *data, char *buf) +{ + struct trackpoint_data *tp = psmouse->private; + struct trackpoint_attr_data *attr = data; + unsigned char value = *(unsigned char *)((char *)tp + attr->field_offset); + + if (attr->inverted) + value = !value; + + return sprintf(buf, "%u\n", value); +} + +static ssize_t trackpoint_set_int_attr(struct psmouse *psmouse, void *data, + const char *buf, size_t count) +{ + struct trackpoint_data *tp = psmouse->private; + struct trackpoint_attr_data *attr = data; + unsigned char *field = (unsigned char *)((char *)tp + attr->field_offset); + unsigned long value; + + if (strict_strtoul(buf, 10, &value) || value > 255) + return -EINVAL; + + *field = value; + trackpoint_write(&psmouse->ps2dev, attr->command, value); + + return count; +} + +#define TRACKPOINT_INT_ATTR(_name, _command) \ + static struct trackpoint_attr_data trackpoint_attr_##_name = { \ + .field_offset = offsetof(struct trackpoint_data, _name), \ + .command = _command, \ + }; \ + PSMOUSE_DEFINE_ATTR(_name, S_IWUSR | S_IRUGO, \ + &trackpoint_attr_##_name, \ + trackpoint_show_int_attr, trackpoint_set_int_attr) + +static ssize_t trackpoint_set_bit_attr(struct psmouse *psmouse, void *data, + const char *buf, size_t count) +{ + struct trackpoint_data *tp = psmouse->private; + struct trackpoint_attr_data *attr = data; + unsigned char *field = (unsigned char *)((char *)tp + attr->field_offset); + unsigned long value; + + if (strict_strtoul(buf, 10, &value) || value > 1) + return -EINVAL; + + if (attr->inverted) + value = !value; + + if (*field != value) { + *field = value; + trackpoint_toggle_bit(&psmouse->ps2dev, attr->command, attr->mask); + } + + return count; +} + + +#define TRACKPOINT_BIT_ATTR(_name, _command, _mask, _inv) \ + static struct trackpoint_attr_data trackpoint_attr_##_name = { \ + .field_offset = offsetof(struct trackpoint_data, _name), \ + .command = _command, \ + .mask = _mask, \ + .inverted = _inv, \ + }; \ + PSMOUSE_DEFINE_ATTR(_name, S_IWUSR | S_IRUGO, \ + &trackpoint_attr_##_name, \ + trackpoint_show_int_attr, trackpoint_set_bit_attr) + +TRACKPOINT_INT_ATTR(sensitivity, TP_SENS); +TRACKPOINT_INT_ATTR(speed, TP_SPEED); +TRACKPOINT_INT_ATTR(inertia, TP_INERTIA); +TRACKPOINT_INT_ATTR(reach, TP_REACH); +TRACKPOINT_INT_ATTR(draghys, TP_DRAGHYS); +TRACKPOINT_INT_ATTR(mindrag, TP_MINDRAG); +TRACKPOINT_INT_ATTR(thresh, TP_THRESH); +TRACKPOINT_INT_ATTR(upthresh, TP_UP_THRESH); +TRACKPOINT_INT_ATTR(ztime, TP_Z_TIME); +TRACKPOINT_INT_ATTR(jenks, TP_JENKS_CURV); + +TRACKPOINT_BIT_ATTR(press_to_select, TP_TOGGLE_PTSON, TP_MASK_PTSON, 0); +TRACKPOINT_BIT_ATTR(skipback, TP_TOGGLE_SKIPBACK, TP_MASK_SKIPBACK, 0); +TRACKPOINT_BIT_ATTR(ext_dev, TP_TOGGLE_EXT_DEV, TP_MASK_EXT_DEV, 1); + +static struct attribute *trackpoint_attrs[] = { + &psmouse_attr_sensitivity.dattr.attr, + &psmouse_attr_speed.dattr.attr, + &psmouse_attr_inertia.dattr.attr, + &psmouse_attr_reach.dattr.attr, + &psmouse_attr_draghys.dattr.attr, + &psmouse_attr_mindrag.dattr.attr, + &psmouse_attr_thresh.dattr.attr, + &psmouse_attr_upthresh.dattr.attr, + &psmouse_attr_ztime.dattr.attr, + &psmouse_attr_jenks.dattr.attr, + &psmouse_attr_press_to_select.dattr.attr, + &psmouse_attr_skipback.dattr.attr, + &psmouse_attr_ext_dev.dattr.attr, + NULL +}; + +static struct attribute_group trackpoint_attr_group = { + .attrs = trackpoint_attrs, +}; + +static int trackpoint_start_protocol(struct psmouse *psmouse, unsigned char *firmware_id) +{ + unsigned char param[2] = { 0 }; + + if (ps2_command(&psmouse->ps2dev, param, MAKE_PS2_CMD(0, 2, TP_READ_ID))) + return -1; + + if (param[0] != TP_MAGIC_IDENT) + return -1; + + if (firmware_id) + *firmware_id = param[1]; + + return 0; +} + +static int trackpoint_sync(struct psmouse *psmouse) +{ + struct trackpoint_data *tp = psmouse->private; + unsigned char toggle; + + /* Disable features that may make device unusable with this driver */ + trackpoint_read(&psmouse->ps2dev, TP_TOGGLE_TWOHAND, &toggle); + if (toggle & TP_MASK_TWOHAND) + trackpoint_toggle_bit(&psmouse->ps2dev, TP_TOGGLE_TWOHAND, TP_MASK_TWOHAND); + + trackpoint_read(&psmouse->ps2dev, TP_TOGGLE_SOURCE_TAG, &toggle); + if (toggle & TP_MASK_SOURCE_TAG) + trackpoint_toggle_bit(&psmouse->ps2dev, TP_TOGGLE_SOURCE_TAG, TP_MASK_SOURCE_TAG); + + trackpoint_read(&psmouse->ps2dev, TP_TOGGLE_MB, &toggle); + if (toggle & TP_MASK_MB) + trackpoint_toggle_bit(&psmouse->ps2dev, TP_TOGGLE_MB, TP_MASK_MB); + + /* Push the config to the device */ + trackpoint_write(&psmouse->ps2dev, TP_SENS, tp->sensitivity); + trackpoint_write(&psmouse->ps2dev, TP_INERTIA, tp->inertia); + trackpoint_write(&psmouse->ps2dev, TP_SPEED, tp->speed); + + trackpoint_write(&psmouse->ps2dev, TP_REACH, tp->reach); + trackpoint_write(&psmouse->ps2dev, TP_DRAGHYS, tp->draghys); + trackpoint_write(&psmouse->ps2dev, TP_MINDRAG, tp->mindrag); + + trackpoint_write(&psmouse->ps2dev, TP_THRESH, tp->thresh); + trackpoint_write(&psmouse->ps2dev, TP_UP_THRESH, tp->upthresh); + + trackpoint_write(&psmouse->ps2dev, TP_Z_TIME, tp->ztime); + trackpoint_write(&psmouse->ps2dev, TP_JENKS_CURV, tp->jenks); + + trackpoint_read(&psmouse->ps2dev, TP_TOGGLE_PTSON, &toggle); + if (((toggle & TP_MASK_PTSON) == TP_MASK_PTSON) != tp->press_to_select) + trackpoint_toggle_bit(&psmouse->ps2dev, TP_TOGGLE_PTSON, TP_MASK_PTSON); + + trackpoint_read(&psmouse->ps2dev, TP_TOGGLE_SKIPBACK, &toggle); + if (((toggle & TP_MASK_SKIPBACK) == TP_MASK_SKIPBACK) != tp->skipback) + trackpoint_toggle_bit(&psmouse->ps2dev, TP_TOGGLE_SKIPBACK, TP_MASK_SKIPBACK); + + trackpoint_read(&psmouse->ps2dev, TP_TOGGLE_EXT_DEV, &toggle); + if (((toggle & TP_MASK_EXT_DEV) == TP_MASK_EXT_DEV) != tp->ext_dev) + trackpoint_toggle_bit(&psmouse->ps2dev, TP_TOGGLE_EXT_DEV, TP_MASK_EXT_DEV); + + return 0; +} + +static void trackpoint_defaults(struct trackpoint_data *tp) +{ + tp->press_to_select = TP_DEF_PTSON; + tp->sensitivity = TP_DEF_SENS; + tp->speed = TP_DEF_SPEED; + tp->reach = TP_DEF_REACH; + + tp->draghys = TP_DEF_DRAGHYS; + tp->mindrag = TP_DEF_MINDRAG; + + tp->thresh = TP_DEF_THRESH; + tp->upthresh = TP_DEF_UP_THRESH; + + tp->ztime = TP_DEF_Z_TIME; + tp->jenks = TP_DEF_JENKS_CURV; + + tp->inertia = TP_DEF_INERTIA; + tp->skipback = TP_DEF_SKIPBACK; + tp->ext_dev = TP_DEF_EXT_DEV; +} + +static void trackpoint_disconnect(struct psmouse *psmouse) +{ + sysfs_remove_group(&psmouse->ps2dev.serio->dev.kobj, &trackpoint_attr_group); + + kfree(psmouse->private); + psmouse->private = NULL; +} + +static int trackpoint_reconnect(struct psmouse *psmouse) +{ + if (trackpoint_start_protocol(psmouse, NULL)) + return -1; + + if (trackpoint_sync(psmouse)) + return -1; + + return 0; +} + +int trackpoint_detect(struct psmouse *psmouse, int set_properties) +{ + struct trackpoint_data *priv; + struct ps2dev *ps2dev = &psmouse->ps2dev; + unsigned char firmware_id; + unsigned char button_info; + int error; + + if (trackpoint_start_protocol(psmouse, &firmware_id)) + return -1; + + if (!set_properties) + return 0; + + if (trackpoint_read(&psmouse->ps2dev, TP_EXT_BTN, &button_info)) { + printk(KERN_WARNING "trackpoint.c: failed to get extended button data\n"); + button_info = 0; + } + + psmouse->private = priv = kzalloc(sizeof(struct trackpoint_data), GFP_KERNEL); + if (!priv) + return -1; + + psmouse->vendor = "IBM"; + psmouse->name = "TrackPoint"; + + psmouse->reconnect = trackpoint_reconnect; + psmouse->disconnect = trackpoint_disconnect; + + trackpoint_defaults(priv); + trackpoint_sync(psmouse); + + error = sysfs_create_group(&ps2dev->serio->dev.kobj, &trackpoint_attr_group); + if (error) { + printk(KERN_ERR + "trackpoint.c: failed to create sysfs attributes, error: %d\n", + error); + kfree(priv); + return -1; + } + + printk(KERN_INFO "IBM TrackPoint firmware: 0x%02x, buttons: %d/%d\n", + firmware_id, (button_info & 0xf0) >> 4, button_info & 0x0f); + + return 0; +} + diff --git a/drivers/input/mouse/trackpoint.h b/drivers/input/mouse/trackpoint.h new file mode 100644 index 0000000..c10a6e7 --- /dev/null +++ b/drivers/input/mouse/trackpoint.h @@ -0,0 +1,154 @@ +/* + * IBM TrackPoint PS/2 mouse driver + * + * Stephen Evanchik <evanchsa@gmail.com> + * + * 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 _TRACKPOINT_H +#define _TRACKPOINT_H + +/* + * These constants are from the TrackPoint System + * Engineering documentation Version 4 from IBM Watson + * research: + * http://wwwcssrv.almaden.ibm.com/trackpoint/download.html + */ + +#define TP_COMMAND 0xE2 /* Commands start with this */ + +#define TP_READ_ID 0xE1 /* Sent for device identification */ +#define TP_MAGIC_IDENT 0x01 /* Sent after a TP_READ_ID followed */ + /* by the firmware ID */ + + +/* + * Commands + */ +#define TP_RECALIB 0x51 /* Recalibrate */ +#define TP_POWER_DOWN 0x44 /* Can only be undone through HW reset */ +#define TP_EXT_DEV 0x21 /* Determines if external device is connected (RO) */ +#define TP_EXT_BTN 0x4B /* Read extended button status */ +#define TP_POR 0x7F /* Execute Power on Reset */ +#define TP_POR_RESULTS 0x25 /* Read Power on Self test results */ +#define TP_DISABLE_EXT 0x40 /* Disable external pointing device */ +#define TP_ENABLE_EXT 0x41 /* Enable external pointing device */ + +/* + * Mode manipulation + */ +#define TP_SET_SOFT_TRANS 0x4E /* Set mode */ +#define TP_CANCEL_SOFT_TRANS 0xB9 /* Cancel mode */ +#define TP_SET_HARD_TRANS 0x45 /* Mode can only be set */ + + +/* + * Register oriented commands/properties + */ +#define TP_WRITE_MEM 0x81 +#define TP_READ_MEM 0x80 /* Not used in this implementation */ + +/* +* RAM Locations for properties + */ +#define TP_SENS 0x4A /* Sensitivity */ +#define TP_MB 0x4C /* Read Middle Button Status (RO) */ +#define TP_INERTIA 0x4D /* Negative Inertia */ +#define TP_SPEED 0x60 /* Speed of TP Cursor */ +#define TP_REACH 0x57 /* Backup for Z-axis press */ +#define TP_DRAGHYS 0x58 /* Drag Hysteresis */ + /* (how hard it is to drag */ + /* with Z-axis pressed) */ + +#define TP_MINDRAG 0x59 /* Minimum amount of force needed */ + /* to trigger dragging */ + +#define TP_THRESH 0x5C /* Minimum value for a Z-axis press */ +#define TP_UP_THRESH 0x5A /* Used to generate a 'click' on Z-axis */ +#define TP_Z_TIME 0x5E /* How sharp of a press */ +#define TP_JENKS_CURV 0x5D /* Minimum curvature for double click */ + +/* + * Toggling Flag bits + */ +#define TP_TOGGLE 0x47 /* Toggle command */ + +#define TP_TOGGLE_MB 0x23 /* Disable/Enable Middle Button */ +#define TP_MASK_MB 0x01 +#define TP_TOGGLE_EXT_DEV 0x23 /* Disable external device */ +#define TP_MASK_EXT_DEV 0x02 +#define TP_TOGGLE_DRIFT 0x23 /* Drift Correction */ +#define TP_MASK_DRIFT 0x80 +#define TP_TOGGLE_BURST 0x28 /* Burst Mode */ +#define TP_MASK_BURST 0x80 +#define TP_TOGGLE_PTSON 0x2C /* Press to Select */ +#define TP_MASK_PTSON 0x01 +#define TP_TOGGLE_HARD_TRANS 0x2C /* Alternate method to set Hard Transparency */ +#define TP_MASK_HARD_TRANS 0x80 +#define TP_TOGGLE_TWOHAND 0x2D /* Two handed */ +#define TP_MASK_TWOHAND 0x01 +#define TP_TOGGLE_STICKY_TWO 0x2D /* Sticky two handed */ +#define TP_MASK_STICKY_TWO 0x04 +#define TP_TOGGLE_SKIPBACK 0x2D /* Suppress movement after drag release */ +#define TP_MASK_SKIPBACK 0x08 +#define TP_TOGGLE_SOURCE_TAG 0x20 /* Bit 3 of the first packet will be set to + to the origin of the packet (external or TP) */ +#define TP_MASK_SOURCE_TAG 0x80 +#define TP_TOGGLE_EXT_TAG 0x22 /* Bit 3 of the first packet coming from the + external device will be forced to 1 */ +#define TP_MASK_EXT_TAG 0x04 + + +/* Power on Self Test Results */ +#define TP_POR_SUCCESS 0x3B + +/* + * Default power on values + */ +#define TP_DEF_SENS 0x80 +#define TP_DEF_INERTIA 0x06 +#define TP_DEF_SPEED 0x61 +#define TP_DEF_REACH 0x0A + +#define TP_DEF_DRAGHYS 0xFF +#define TP_DEF_MINDRAG 0x14 + +#define TP_DEF_THRESH 0x08 +#define TP_DEF_UP_THRESH 0xFF +#define TP_DEF_Z_TIME 0x26 +#define TP_DEF_JENKS_CURV 0x87 + +/* Toggles */ +#define TP_DEF_MB 0x00 +#define TP_DEF_PTSON 0x00 +#define TP_DEF_SKIPBACK 0x00 +#define TP_DEF_EXT_DEV 0x00 /* 0 means enabled */ + +#define MAKE_PS2_CMD(params, results, cmd) ((params<<12) | (results<<8) | (cmd)) + +struct trackpoint_data +{ + unsigned char sensitivity, speed, inertia, reach; + unsigned char draghys, mindrag; + unsigned char thresh, upthresh; + unsigned char ztime, jenks; + + unsigned char press_to_select; + unsigned char skipback; + + unsigned char ext_dev; +}; + +#ifdef CONFIG_MOUSE_PS2_TRACKPOINT +int trackpoint_detect(struct psmouse *psmouse, int set_properties); +#else +inline int trackpoint_detect(struct psmouse *psmouse, int set_properties) +{ + return -ENOSYS; +} +#endif /* CONFIG_MOUSE_PS2_TRACKPOINT */ + +#endif /* _TRACKPOINT_H */ diff --git a/drivers/input/mouse/vsxxxaa.c b/drivers/input/mouse/vsxxxaa.c new file mode 100644 index 0000000..404eedd --- /dev/null +++ b/drivers/input/mouse/vsxxxaa.c @@ -0,0 +1,587 @@ +/* + * 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/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: + strlcpy (mouse->name, "DEC VSXXX-AA/-GA mouse", + sizeof (mouse->name)); + break; + + case 0x04: + strlcpy (mouse->name, "DEC VSXXX-AB digitizer", + sizeof (mouse->name)); + break; + + default: + snprintf (mouse->name, sizeof (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 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_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 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_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 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 explicitly + * 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_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) +{ + 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); + 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); + 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); + 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 vsxxxaa *mouse = serio_get_drvdata (serio); + + vsxxxaa_queue_byte (mouse, data); + vsxxxaa_parse_buffer (mouse); + + return IRQ_HANDLED; +} + +static void +vsxxxaa_disconnect (struct serio *serio) +{ + struct vsxxxaa *mouse = serio_get_drvdata (serio); + + serio_close (serio); + serio_set_drvdata (serio, NULL); + input_unregister_device (mouse->dev); + kfree (mouse); +} + +static int +vsxxxaa_connect (struct serio *serio, struct serio_driver *drv) +{ + struct vsxxxaa *mouse; + struct input_dev *input_dev; + int err = -ENOMEM; + + mouse = kzalloc (sizeof (struct vsxxxaa), GFP_KERNEL); + input_dev = input_allocate_device (); + if (!mouse || !input_dev) + goto fail1; + + mouse->dev = input_dev; + mouse->serio = serio; + strlcat (mouse->name, "DEC VSXXX-AA/-GA mouse or VSXXX-AB digitizer", + sizeof (mouse->name)); + snprintf (mouse->phys, sizeof (mouse->phys), "%s/input0", serio->phys); + + input_dev->name = mouse->name; + input_dev->phys = mouse->phys; + input_dev->id.bustype = BUS_RS232; + input_dev->dev.parent = &serio->dev; + + set_bit (EV_KEY, input_dev->evbit); /* We have buttons */ + set_bit (EV_REL, input_dev->evbit); + set_bit (EV_ABS, input_dev->evbit); + set_bit (BTN_LEFT, input_dev->keybit); /* We have 3 buttons */ + set_bit (BTN_MIDDLE, input_dev->keybit); + set_bit (BTN_RIGHT, input_dev->keybit); + set_bit (BTN_TOUCH, input_dev->keybit); /* ...and Tablet */ + set_bit (REL_X, input_dev->relbit); + set_bit (REL_Y, input_dev->relbit); + input_set_abs_params (input_dev, ABS_X, 0, 1023, 0, 0); + input_set_abs_params (input_dev, ABS_Y, 0, 1023, 0, 0); + + serio_set_drvdata (serio, mouse); + + err = serio_open (serio, drv); + if (err) + goto fail2; + + /* + * Request selftest. Standard packet format and differential + * mode will be requested after the device ID'ed successfully. + */ + serio->write (serio, 'T'); /* Test */ + + err = input_register_device (input_dev); + if (err) + goto fail3; + + return 0; + + fail3: serio_close (serio); + fail2: serio_set_drvdata (serio, NULL); + fail1: input_free_device (input_dev); + kfree (mouse); + return err; +} + +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) +{ + return serio_register_driver(&vsxxxaa_drv); +} + +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 0000000..d8c056f --- /dev/null +++ b/drivers/input/mousedev.c @@ -0,0 +1,1110 @@ +/* + * 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/smp_lock.h> +#include <linux/poll.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/random.h> +#include <linux/major.h> +#include <linux/device.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, 0644); +MODULE_PARM_DESC(xres, "Horizontal screen resolution"); + +static int yres = CONFIG_INPUT_MOUSEDEV_SCREEN_Y; +module_param(yres, uint, 0644); +MODULE_PARM_DESC(yres, "Vertical screen resolution"); + +static unsigned tap_time = 200; +module_param(tap_time, uint, 0644); +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]; + struct input_handle handle; + wait_queue_head_t wait; + struct list_head client_list; + spinlock_t client_lock; /* protects client_list */ + struct mutex mutex; + struct device dev; + + struct list_head mixdev_node; + int mixdev_open; + + 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_client { + 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; + unsigned long last_buttons; +}; + +#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 DEFINE_MUTEX(mousedev_table_mutex); +static struct mousedev *mousedev_mix; +static LIST_HEAD(mousedev_mix_list); + +static void mixdev_open_devices(void); +static void mixdev_close_devices(void); + +#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 }; + + switch (code) { + + case ABS_X: + fx(0) = value; + if (mousedev->touch && mousedev->pkt_count >= 2) { + size = dev->absmax[ABS_X] - dev->absmin[ABS_X]; + if (size == 0) + size = 256 * 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->touch && mousedev->pkt_count >= 2) { + /* use X size to keep the same scale */ + size = dev->absmax[ABS_X] - dev->absmin[ABS_X]; + if (size == 0) + size = 256 * 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 ? : 1; + 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 ? : 1; + 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_LEFT: index = 0; break; + + case BTN_STYLUS: + case BTN_1: + case BTN_RIGHT: index = 1; break; + + case BTN_2: + case BTN_FORWARD: + 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_client *client; + struct mousedev_motion *p; + unsigned int new_head; + int wake_readers = 0; + + rcu_read_lock(); + list_for_each_entry_rcu(client, &mousedev->client_list, node) { + + /* Just acquire the lock, interrupts already disabled */ + spin_lock(&client->packet_lock); + + p = &client->packets[client->head]; + if (client->ready && p->buttons != mousedev->packet.buttons) { + new_head = (client->head + 1) % PACKET_QUEUE_LEN; + if (new_head != client->tail) { + p = &client->packets[client->head = new_head]; + memset(p, 0, sizeof(struct mousedev_motion)); + } + } + + if (packet->abs_event) { + p->dx += packet->x - client->pos_x; + p->dy += packet->y - client->pos_y; + client->pos_x = packet->x; + client->pos_y = packet->y; + } + + client->pos_x += packet->dx; + client->pos_x = client->pos_x < 0 ? + 0 : (client->pos_x >= xres ? xres : client->pos_x); + client->pos_y += packet->dy; + client->pos_y = client->pos_y < 0 ? + 0 : (client->pos_y >= yres ? yres : client->pos_y); + + p->dx += packet->dx; + p->dy += packet->dy; + p->dz += packet->dz; + p->buttons = mousedev->packet.buttons; + + if (p->dx || p->dy || p->dz || + p->buttons != client->last_buttons) + client->ready = 1; + + spin_unlock(&client->packet_lock); + + if (client->ready) { + kill_fasync(&client->fasync, SIGIO, POLL_IN); + wake_readers = 1; + } + } + rcu_read_unlock(); + + if (wake_readers) + 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_client *client = file->private_data; + + retval = fasync_helper(fd, file, on, &client->fasync); + + return retval < 0 ? retval : 0; +} + +static void mousedev_free(struct device *dev) +{ + struct mousedev *mousedev = container_of(dev, struct mousedev, dev); + + input_put_device(mousedev->handle.dev); + kfree(mousedev); +} + +static int mousedev_open_device(struct mousedev *mousedev) +{ + int retval; + + retval = mutex_lock_interruptible(&mousedev->mutex); + if (retval) + return retval; + + if (mousedev->minor == MOUSEDEV_MIX) + mixdev_open_devices(); + else if (!mousedev->exist) + retval = -ENODEV; + else if (!mousedev->open++) { + retval = input_open_device(&mousedev->handle); + if (retval) + mousedev->open--; + } + + mutex_unlock(&mousedev->mutex); + return retval; +} + +static void mousedev_close_device(struct mousedev *mousedev) +{ + mutex_lock(&mousedev->mutex); + + if (mousedev->minor == MOUSEDEV_MIX) + mixdev_close_devices(); + else if (mousedev->exist && !--mousedev->open) + input_close_device(&mousedev->handle); + + mutex_unlock(&mousedev->mutex); +} + +/* + * Open all available devices so they can all be multiplexed in one. + * stream. Note that this function is called with mousedev_mix->mutex + * held. + */ +static void mixdev_open_devices(void) +{ + struct mousedev *mousedev; + + if (mousedev_mix->open++) + return; + + list_for_each_entry(mousedev, &mousedev_mix_list, mixdev_node) { + if (!mousedev->mixdev_open) { + if (mousedev_open_device(mousedev)) + continue; + + mousedev->mixdev_open = 1; + } + } +} + +/* + * Close all devices that were opened as part of multiplexed + * device. Note that this function is called with mousedev_mix->mutex + * held. + */ +static void mixdev_close_devices(void) +{ + struct mousedev *mousedev; + + if (--mousedev_mix->open) + return; + + list_for_each_entry(mousedev, &mousedev_mix_list, mixdev_node) { + if (mousedev->mixdev_open) { + mousedev->mixdev_open = 0; + mousedev_close_device(mousedev); + } + } +} + + +static void mousedev_attach_client(struct mousedev *mousedev, + struct mousedev_client *client) +{ + spin_lock(&mousedev->client_lock); + list_add_tail_rcu(&client->node, &mousedev->client_list); + spin_unlock(&mousedev->client_lock); + synchronize_rcu(); +} + +static void mousedev_detach_client(struct mousedev *mousedev, + struct mousedev_client *client) +{ + spin_lock(&mousedev->client_lock); + list_del_rcu(&client->node); + spin_unlock(&mousedev->client_lock); + synchronize_rcu(); +} + +static int mousedev_release(struct inode *inode, struct file *file) +{ + struct mousedev_client *client = file->private_data; + struct mousedev *mousedev = client->mousedev; + + mousedev_detach_client(mousedev, client); + kfree(client); + + mousedev_close_device(mousedev); + put_device(&mousedev->dev); + + return 0; +} + +static int mousedev_open(struct inode *inode, struct file *file) +{ + struct mousedev_client *client; + struct mousedev *mousedev; + int error; + 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) + return -ENODEV; + + lock_kernel(); + error = mutex_lock_interruptible(&mousedev_table_mutex); + if (error) { + unlock_kernel(); + return error; + } + mousedev = mousedev_table[i]; + if (mousedev) + get_device(&mousedev->dev); + mutex_unlock(&mousedev_table_mutex); + + if (!mousedev) { + unlock_kernel(); + return -ENODEV; + } + + client = kzalloc(sizeof(struct mousedev_client), GFP_KERNEL); + if (!client) { + error = -ENOMEM; + goto err_put_mousedev; + } + + spin_lock_init(&client->packet_lock); + client->pos_x = xres / 2; + client->pos_y = yres / 2; + client->mousedev = mousedev; + mousedev_attach_client(mousedev, client); + + error = mousedev_open_device(mousedev); + if (error) + goto err_free_client; + + file->private_data = client; + unlock_kernel(); + return 0; + + err_free_client: + mousedev_detach_client(mousedev, client); + kfree(client); + err_put_mousedev: + put_device(&mousedev->dev); + unlock_kernel(); + return error; +} + +static inline int mousedev_limit_delta(int delta, int limit) +{ + return delta > limit ? limit : (delta < -limit ? -limit : delta); +} + +static void mousedev_packet(struct mousedev_client *client, + signed char *ps2_data) +{ + struct mousedev_motion *p = &client->packets[client->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 (client->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); + client->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]; + client->bufsiz = 4; + break; + + case MOUSEDEV_EMUL_PS2: + default: + ps2_data[0] |= + ((p->buttons & 0x10) >> 3) | ((p->buttons & 0x08) >> 1); + p->dz = 0; + client->bufsiz = 3; + break; + } + + if (!p->dx && !p->dy && !p->dz) { + if (client->tail == client->head) { + client->ready = 0; + client->last_buttons = p->buttons; + } else + client->tail = (client->tail + 1) % PACKET_QUEUE_LEN; + } +} + +static void mousedev_generate_response(struct mousedev_client *client, + int command) +{ + client->ps2[0] = 0xfa; /* ACK */ + + switch (command) { + + case 0xeb: /* Poll */ + mousedev_packet(client, &client->ps2[1]); + client->bufsiz++; /* account for leading ACK */ + break; + + case 0xf2: /* Get ID */ + switch (client->mode) { + case MOUSEDEV_EMUL_PS2: + client->ps2[1] = 0; + break; + case MOUSEDEV_EMUL_IMPS: + client->ps2[1] = 3; + break; + case MOUSEDEV_EMUL_EXPS: + client->ps2[1] = 4; + break; + } + client->bufsiz = 2; + break; + + case 0xe9: /* Get info */ + client->ps2[1] = 0x60; client->ps2[2] = 3; client->ps2[3] = 200; + client->bufsiz = 4; + break; + + case 0xff: /* Reset */ + client->impsseq = client->imexseq = 0; + client->mode = MOUSEDEV_EMUL_PS2; + client->ps2[1] = 0xaa; client->ps2[2] = 0x00; + client->bufsiz = 3; + break; + + default: + client->bufsiz = 1; + break; + } + client->buffer = client->bufsiz; +} + +static ssize_t mousedev_write(struct file *file, const char __user *buffer, + size_t count, loff_t *ppos) +{ + struct mousedev_client *client = file->private_data; + unsigned char c; + unsigned int i; + + for (i = 0; i < count; i++) { + + if (get_user(c, buffer + i)) + return -EFAULT; + + spin_lock_irq(&client->packet_lock); + + if (c == mousedev_imex_seq[client->imexseq]) { + if (++client->imexseq == MOUSEDEV_SEQ_LEN) { + client->imexseq = 0; + client->mode = MOUSEDEV_EMUL_EXPS; + } + } else + client->imexseq = 0; + + if (c == mousedev_imps_seq[client->impsseq]) { + if (++client->impsseq == MOUSEDEV_SEQ_LEN) { + client->impsseq = 0; + client->mode = MOUSEDEV_EMUL_IMPS; + } + } else + client->impsseq = 0; + + mousedev_generate_response(client, c); + + spin_unlock_irq(&client->packet_lock); + } + + kill_fasync(&client->fasync, SIGIO, POLL_IN); + wake_up_interruptible(&client->mousedev->wait); + + return count; +} + +static ssize_t mousedev_read(struct file *file, char __user *buffer, + size_t count, loff_t *ppos) +{ + struct mousedev_client *client = file->private_data; + struct mousedev *mousedev = client->mousedev; + signed char data[sizeof(client->ps2)]; + int retval = 0; + + if (!client->ready && !client->buffer && mousedev->exist && + (file->f_flags & O_NONBLOCK)) + return -EAGAIN; + + retval = wait_event_interruptible(mousedev->wait, + !mousedev->exist || client->ready || client->buffer); + if (retval) + return retval; + + if (!mousedev->exist) + return -ENODEV; + + spin_lock_irq(&client->packet_lock); + + if (!client->buffer && client->ready) { + mousedev_packet(client, client->ps2); + client->buffer = client->bufsiz; + } + + if (count > client->buffer) + count = client->buffer; + + memcpy(data, client->ps2 + client->bufsiz - client->buffer, count); + client->buffer -= count; + + spin_unlock_irq(&client->packet_lock); + + if (copy_to_user(buffer, data, count)) + return -EFAULT; + + return count; +} + +/* No kernel lock - fine */ +static unsigned int mousedev_poll(struct file *file, poll_table *wait) +{ + struct mousedev_client *client = file->private_data; + struct mousedev *mousedev = client->mousedev; + + poll_wait(file, &mousedev->wait, wait); + return ((client->ready || client->buffer) ? (POLLIN | POLLRDNORM) : 0) | + (mousedev->exist ? 0 : (POLLHUP | POLLERR)); +} + +static const 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 int mousedev_install_chrdev(struct mousedev *mousedev) +{ + mousedev_table[mousedev->minor] = mousedev; + return 0; +} + +static void mousedev_remove_chrdev(struct mousedev *mousedev) +{ + mutex_lock(&mousedev_table_mutex); + mousedev_table[mousedev->minor] = NULL; + mutex_unlock(&mousedev_table_mutex); +} + +/* + * Mark device non-existent. This disables writes, ioctls and + * prevents new users from opening the device. Already posted + * blocking reads will stay, however new ones will fail. + */ +static void mousedev_mark_dead(struct mousedev *mousedev) +{ + mutex_lock(&mousedev->mutex); + mousedev->exist = 0; + mutex_unlock(&mousedev->mutex); +} + +/* + * Wake up users waiting for IO so they can disconnect from + * dead device. + */ +static void mousedev_hangup(struct mousedev *mousedev) +{ + struct mousedev_client *client; + + spin_lock(&mousedev->client_lock); + list_for_each_entry(client, &mousedev->client_list, node) + kill_fasync(&client->fasync, SIGIO, POLL_HUP); + spin_unlock(&mousedev->client_lock); + + wake_up_interruptible(&mousedev->wait); +} + +static void mousedev_cleanup(struct mousedev *mousedev) +{ + struct input_handle *handle = &mousedev->handle; + + mousedev_mark_dead(mousedev); + mousedev_hangup(mousedev); + mousedev_remove_chrdev(mousedev); + + /* mousedev is marked dead so no one else accesses mousedev->open */ + if (mousedev->open) + input_close_device(handle); +} + +static struct mousedev *mousedev_create(struct input_dev *dev, + struct input_handler *handler, + int minor) +{ + struct mousedev *mousedev; + int error; + + mousedev = kzalloc(sizeof(struct mousedev), GFP_KERNEL); + if (!mousedev) { + error = -ENOMEM; + goto err_out; + } + + INIT_LIST_HEAD(&mousedev->client_list); + INIT_LIST_HEAD(&mousedev->mixdev_node); + spin_lock_init(&mousedev->client_lock); + mutex_init(&mousedev->mutex); + lockdep_set_subclass(&mousedev->mutex, + minor == MOUSEDEV_MIX ? MOUSEDEV_MIX : 0); + init_waitqueue_head(&mousedev->wait); + + if (minor == MOUSEDEV_MIX) + strlcpy(mousedev->name, "mice", sizeof(mousedev->name)); + else + snprintf(mousedev->name, sizeof(mousedev->name), + "mouse%d", minor); + + mousedev->minor = minor; + mousedev->exist = 1; + mousedev->handle.dev = input_get_device(dev); + mousedev->handle.name = mousedev->name; + mousedev->handle.handler = handler; + mousedev->handle.private = mousedev; + + strlcpy(mousedev->dev.bus_id, mousedev->name, + sizeof(mousedev->dev.bus_id)); + mousedev->dev.class = &input_class; + if (dev) + mousedev->dev.parent = &dev->dev; + mousedev->dev.devt = MKDEV(INPUT_MAJOR, MOUSEDEV_MINOR_BASE + minor); + mousedev->dev.release = mousedev_free; + device_initialize(&mousedev->dev); + + if (minor != MOUSEDEV_MIX) { + error = input_register_handle(&mousedev->handle); + if (error) + goto err_free_mousedev; + } + + error = mousedev_install_chrdev(mousedev); + if (error) + goto err_unregister_handle; + + error = device_add(&mousedev->dev); + if (error) + goto err_cleanup_mousedev; + + return mousedev; + + err_cleanup_mousedev: + mousedev_cleanup(mousedev); + err_unregister_handle: + if (minor != MOUSEDEV_MIX) + input_unregister_handle(&mousedev->handle); + err_free_mousedev: + put_device(&mousedev->dev); + err_out: + return ERR_PTR(error); +} + +static void mousedev_destroy(struct mousedev *mousedev) +{ + device_del(&mousedev->dev); + mousedev_cleanup(mousedev); + if (mousedev->minor != MOUSEDEV_MIX) + input_unregister_handle(&mousedev->handle); + put_device(&mousedev->dev); +} + +static int mixdev_add_device(struct mousedev *mousedev) +{ + int retval; + + retval = mutex_lock_interruptible(&mousedev_mix->mutex); + if (retval) + return retval; + + if (mousedev_mix->open) { + retval = mousedev_open_device(mousedev); + if (retval) + goto out; + + mousedev->mixdev_open = 1; + } + + get_device(&mousedev->dev); + list_add_tail(&mousedev->mixdev_node, &mousedev_mix_list); + + out: + mutex_unlock(&mousedev_mix->mutex); + return retval; +} + +static void mixdev_remove_device(struct mousedev *mousedev) +{ + mutex_lock(&mousedev_mix->mutex); + + if (mousedev->mixdev_open) { + mousedev->mixdev_open = 0; + mousedev_close_device(mousedev); + } + + list_del_init(&mousedev->mixdev_node); + mutex_unlock(&mousedev_mix->mutex); + + put_device(&mousedev->dev); +} + +static int mousedev_connect(struct input_handler *handler, + struct input_dev *dev, + const struct input_device_id *id) +{ + struct mousedev *mousedev; + int minor; + int error; + + for (minor = 0; minor < MOUSEDEV_MINORS; minor++) + if (!mousedev_table[minor]) + break; + + if (minor == MOUSEDEV_MINORS) { + printk(KERN_ERR "mousedev: no more free mousedev devices\n"); + return -ENFILE; + } + + mousedev = mousedev_create(dev, handler, minor); + if (IS_ERR(mousedev)) + return PTR_ERR(mousedev); + + error = mixdev_add_device(mousedev); + if (error) { + mousedev_destroy(mousedev); + return error; + } + + return 0; +} + +static void mousedev_disconnect(struct input_handle *handle) +{ + struct mousedev *mousedev = handle->private; + + mixdev_remove_device(mousedev); + mousedev_destroy(mousedev); +} + +static const struct input_device_id mousedev_ids[] = { + { + .flags = INPUT_DEVICE_ID_MATCH_EVBIT | + INPUT_DEVICE_ID_MATCH_KEYBIT | + INPUT_DEVICE_ID_MATCH_RELBIT, + .evbit = { BIT_MASK(EV_KEY) | BIT_MASK(EV_REL) }, + .keybit = { [BIT_WORD(BTN_LEFT)] = BIT_MASK(BTN_LEFT) }, + .relbit = { BIT_MASK(REL_X) | BIT_MASK(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_MASK(EV_KEY) | BIT_MASK(EV_REL) }, + .relbit = { BIT_MASK(REL_WHEEL) }, + }, /* A separate scrollwheel */ + { + .flags = INPUT_DEVICE_ID_MATCH_EVBIT | + INPUT_DEVICE_ID_MATCH_KEYBIT | + INPUT_DEVICE_ID_MATCH_ABSBIT, + .evbit = { BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS) }, + .keybit = { [BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH) }, + .absbit = { BIT_MASK(ABS_X) | BIT_MASK(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_MASK(EV_KEY) | BIT_MASK(EV_ABS) }, + .keybit = { [BIT_WORD(BTN_TOOL_FINGER)] = + BIT_MASK(BTN_TOOL_FINGER) }, + .absbit = { BIT_MASK(ABS_X) | BIT_MASK(ABS_Y) | + BIT_MASK(ABS_PRESSURE) | + BIT_MASK(ABS_TOOL_WIDTH) }, + }, /* A touchpad */ + { + .flags = INPUT_DEVICE_ID_MATCH_EVBIT | + INPUT_DEVICE_ID_MATCH_KEYBIT | + INPUT_DEVICE_ID_MATCH_ABSBIT, + .evbit = { BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS) }, + .keybit = { [BIT_WORD(BTN_LEFT)] = BIT_MASK(BTN_LEFT) }, + .absbit = { BIT_MASK(ABS_X) | BIT_MASK(ABS_Y) }, + }, /* Mouse-like device with absolute X and Y but ordinary + clicks, like hp ILO2 High Performance mouse */ + + { }, /* 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) +{ + int error; + + mousedev_mix = mousedev_create(NULL, &mousedev_handler, MOUSEDEV_MIX); + if (IS_ERR(mousedev_mix)) + return PTR_ERR(mousedev_mix); + + error = input_register_handler(&mousedev_handler); + if (error) { + mousedev_destroy(mousedev_mix); + return error; + } + +#ifdef CONFIG_INPUT_MOUSEDEV_PSAUX + error = misc_register(&psaux_mouse); + if (error) + printk(KERN_WARNING "mice: could not register psaux device, " + "error: %d\n", error); + else + psaux_registered = 1; +#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 + input_unregister_handler(&mousedev_handler); + mousedev_destroy(mousedev_mix); +} + +module_init(mousedev_init); +module_exit(mousedev_exit); diff --git a/drivers/input/serio/Kconfig b/drivers/input/serio/Kconfig new file mode 100644 index 0000000..27d70d3 --- /dev/null +++ b/drivers/input/serio/Kconfig @@ -0,0 +1,203 @@ +# +# 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 && !BLACKFIN + ---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_AT32PSIF + tristate "AVR32 PSIF PS/2 keyboard and mouse controller" + depends on AVR32 + help + Say Y here if you want to use the PSIF peripheral on AVR32 devices + and connect a PS/2 keyboard and/or mouse to it. + + To compile this driver as a module, choose M here: the module will + be called at32psif. + +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 || HP300) && SERIO + default y + ---help--- + This option enables support for the "System Device + Controller", an i8042 carrying microcode to manage a + few miscellaneous 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/drvctl + + To compile this driver as a module, choose M here: the + module will be called serio_raw. + +config SERIO_XILINX_XPS_PS2 + tristate "Xilinx XPS PS/2 Controller Support" + depends on PPC + help + This driver supports XPS PS/2 IP from the Xilinx EDK on + PowerPC platform. + + To compile this driver as a module, choose M here: the + module will be called xilinx_ps2. + +endif diff --git a/drivers/input/serio/Makefile b/drivers/input/serio/Makefile new file mode 100644 index 0000000..9b6c813 --- /dev/null +++ b/drivers/input/serio/Makefile @@ -0,0 +1,24 @@ +# +# 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_AT32PSIF) += at32psif.o +obj-$(CONFIG_SERIO_Q40KBD) += q40kbd.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 +obj-$(CONFIG_SERIO_XILINX_XPS_PS2) += xilinx_ps2.o diff --git a/drivers/input/serio/ambakmi.c b/drivers/input/serio/ambakmi.c new file mode 100644 index 0000000..b10ffae --- /dev/null +++ b/drivers/input/serio/ambakmi.c @@ -0,0 +1,222 @@ +/* + * 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 <linux/amba/bus.h> +#include <linux/amba/kmi.h> +#include <linux/clk.h> + +#include <asm/io.h> +#include <asm/irq.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 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); + 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_enable(kmi->clk); + if (ret) + goto out; + + 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); + 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); +} + +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 = kzalloc(sizeof(struct amba_kmi_port), GFP_KERNEL); + io = kzalloc(sizeof(struct serio), GFP_KERNEL); + if (!kmi || !io) { + ret = -ENOMEM; + goto out; + } + + + 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/at32psif.c b/drivers/input/serio/at32psif.c new file mode 100644 index 0000000..41fda8c --- /dev/null +++ b/drivers/input/serio/at32psif.c @@ -0,0 +1,375 @@ +/* + * Copyright (C) 2007 Atmel Corporation + * + * Driver for the AT32AP700X PS/2 controller (PSIF). + * + * 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/device.h> +#include <linux/init.h> +#include <linux/serio.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/clk.h> +#include <linux/platform_device.h> + +/* PSIF register offsets */ +#define PSIF_CR 0x00 +#define PSIF_RHR 0x04 +#define PSIF_THR 0x08 +#define PSIF_SR 0x10 +#define PSIF_IER 0x14 +#define PSIF_IDR 0x18 +#define PSIF_IMR 0x1c +#define PSIF_PSR 0x24 + +/* Bitfields in control register. */ +#define PSIF_CR_RXDIS_OFFSET 1 +#define PSIF_CR_RXDIS_SIZE 1 +#define PSIF_CR_RXEN_OFFSET 0 +#define PSIF_CR_RXEN_SIZE 1 +#define PSIF_CR_SWRST_OFFSET 15 +#define PSIF_CR_SWRST_SIZE 1 +#define PSIF_CR_TXDIS_OFFSET 9 +#define PSIF_CR_TXDIS_SIZE 1 +#define PSIF_CR_TXEN_OFFSET 8 +#define PSIF_CR_TXEN_SIZE 1 + +/* Bitfields in interrupt disable, enable, mask and status register. */ +#define PSIF_NACK_OFFSET 8 +#define PSIF_NACK_SIZE 1 +#define PSIF_OVRUN_OFFSET 5 +#define PSIF_OVRUN_SIZE 1 +#define PSIF_PARITY_OFFSET 9 +#define PSIF_PARITY_SIZE 1 +#define PSIF_RXRDY_OFFSET 4 +#define PSIF_RXRDY_SIZE 1 +#define PSIF_TXEMPTY_OFFSET 1 +#define PSIF_TXEMPTY_SIZE 1 +#define PSIF_TXRDY_OFFSET 0 +#define PSIF_TXRDY_SIZE 1 + +/* Bitfields in prescale register. */ +#define PSIF_PSR_PRSCV_OFFSET 0 +#define PSIF_PSR_PRSCV_SIZE 12 + +/* Bitfields in receive hold register. */ +#define PSIF_RHR_RXDATA_OFFSET 0 +#define PSIF_RHR_RXDATA_SIZE 8 + +/* Bitfields in transmit hold register. */ +#define PSIF_THR_TXDATA_OFFSET 0 +#define PSIF_THR_TXDATA_SIZE 8 + +/* Bit manipulation macros */ +#define PSIF_BIT(name) \ + (1 << PSIF_##name##_OFFSET) + +#define PSIF_BF(name, value) \ + (((value) & ((1 << PSIF_##name##_SIZE) - 1)) \ + << PSIF_##name##_OFFSET) + +#define PSIF_BFEXT(name, value) \ + (((value) >> PSIF_##name##_OFFSET) \ + & ((1 << PSIF_##name##_SIZE) - 1)) + +#define PSIF_BFINS(name, value, old) \ + (((old) & ~(((1 << PSIF_##name##_SIZE) - 1) \ + << PSIF_##name##_OFFSET)) \ + | PSIF_BF(name, value)) + +/* Register access macros */ +#define psif_readl(port, reg) \ + __raw_readl((port)->regs + PSIF_##reg) + +#define psif_writel(port, reg, value) \ + __raw_writel((value), (port)->regs + PSIF_##reg) + +struct psif { + struct platform_device *pdev; + struct clk *pclk; + struct serio *io; + void __iomem *regs; + unsigned int irq; + unsigned int open; + /* Prevent concurrent writes to PSIF THR. */ + spinlock_t lock; +}; + +static irqreturn_t psif_interrupt(int irq, void *_ptr) +{ + struct psif *psif = _ptr; + int retval = IRQ_NONE; + unsigned int io_flags = 0; + unsigned long status; + + status = psif_readl(psif, SR); + + if (status & PSIF_BIT(RXRDY)) { + unsigned char val = (unsigned char) psif_readl(psif, RHR); + + if (status & PSIF_BIT(PARITY)) + io_flags |= SERIO_PARITY; + if (status & PSIF_BIT(OVRUN)) + dev_err(&psif->pdev->dev, "overrun read error\n"); + + serio_interrupt(psif->io, val, io_flags); + + retval = IRQ_HANDLED; + } + + return retval; +} + +static int psif_write(struct serio *io, unsigned char val) +{ + struct psif *psif = io->port_data; + unsigned long flags; + int timeout = 10; + int retval = 0; + + spin_lock_irqsave(&psif->lock, flags); + + while (!(psif_readl(psif, SR) & PSIF_BIT(TXEMPTY)) && timeout--) + msleep(10); + + if (timeout >= 0) { + psif_writel(psif, THR, val); + } else { + dev_dbg(&psif->pdev->dev, "timeout writing to THR\n"); + retval = -EBUSY; + } + + spin_unlock_irqrestore(&psif->lock, flags); + + return retval; +} + +static int psif_open(struct serio *io) +{ + struct psif *psif = io->port_data; + int retval; + + retval = clk_enable(psif->pclk); + if (retval) + goto out; + + psif_writel(psif, CR, PSIF_BIT(CR_TXEN) | PSIF_BIT(CR_RXEN)); + psif_writel(psif, IER, PSIF_BIT(RXRDY)); + + psif->open = 1; +out: + return retval; +} + +static void psif_close(struct serio *io) +{ + struct psif *psif = io->port_data; + + psif->open = 0; + + psif_writel(psif, IDR, ~0UL); + psif_writel(psif, CR, PSIF_BIT(CR_TXDIS) | PSIF_BIT(CR_RXDIS)); + + clk_disable(psif->pclk); +} + +static void psif_set_prescaler(struct psif *psif) +{ + unsigned long prscv; + unsigned long rate = clk_get_rate(psif->pclk); + + /* PRSCV = Pulse length (100 us) * PSIF module frequency. */ + prscv = 100 * (rate / 1000000UL); + + if (prscv > ((1<<PSIF_PSR_PRSCV_SIZE) - 1)) { + prscv = (1<<PSIF_PSR_PRSCV_SIZE) - 1; + dev_dbg(&psif->pdev->dev, "pclk too fast, " + "prescaler set to max\n"); + } + + clk_enable(psif->pclk); + psif_writel(psif, PSR, prscv); + clk_disable(psif->pclk); +} + +static int __init psif_probe(struct platform_device *pdev) +{ + struct resource *regs; + struct psif *psif; + struct serio *io; + struct clk *pclk; + int irq; + int ret; + + psif = kzalloc(sizeof(struct psif), GFP_KERNEL); + if (!psif) { + dev_dbg(&pdev->dev, "out of memory\n"); + ret = -ENOMEM; + goto out; + } + psif->pdev = pdev; + + io = kzalloc(sizeof(struct serio), GFP_KERNEL); + if (!io) { + dev_dbg(&pdev->dev, "out of memory\n"); + ret = -ENOMEM; + goto out_free_psif; + } + psif->io = io; + + regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!regs) { + dev_dbg(&pdev->dev, "no mmio resources defined\n"); + ret = -ENOMEM; + goto out_free_io; + } + + psif->regs = ioremap(regs->start, regs->end - regs->start + 1); + if (!psif->regs) { + ret = -ENOMEM; + dev_dbg(&pdev->dev, "could not map I/O memory\n"); + goto out_free_io; + } + + pclk = clk_get(&pdev->dev, "pclk"); + if (IS_ERR(pclk)) { + dev_dbg(&pdev->dev, "could not get peripheral clock\n"); + ret = PTR_ERR(pclk); + goto out_iounmap; + } + psif->pclk = pclk; + + /* Reset the PSIF to enter at a known state. */ + ret = clk_enable(pclk); + if (ret) { + dev_dbg(&pdev->dev, "could not enable pclk\n"); + goto out_put_clk; + } + psif_writel(psif, CR, PSIF_BIT(CR_SWRST)); + clk_disable(pclk); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_dbg(&pdev->dev, "could not get irq\n"); + ret = -ENXIO; + goto out_put_clk; + } + ret = request_irq(irq, psif_interrupt, IRQF_SHARED, "at32psif", psif); + if (ret) { + dev_dbg(&pdev->dev, "could not request irq %d\n", irq); + goto out_put_clk; + } + psif->irq = irq; + + io->id.type = SERIO_8042; + io->write = psif_write; + io->open = psif_open; + io->close = psif_close; + snprintf(io->name, sizeof(io->name), "AVR32 PS/2 port%d", pdev->id); + snprintf(io->phys, sizeof(io->phys), "at32psif/serio%d", pdev->id); + io->port_data = psif; + io->dev.parent = &pdev->dev; + + psif_set_prescaler(psif); + + spin_lock_init(&psif->lock); + serio_register_port(psif->io); + platform_set_drvdata(pdev, psif); + + dev_info(&pdev->dev, "Atmel AVR32 PSIF PS/2 driver on 0x%08x irq %d\n", + (int)psif->regs, psif->irq); + + return 0; + +out_put_clk: + clk_put(psif->pclk); +out_iounmap: + iounmap(psif->regs); +out_free_io: + kfree(io); +out_free_psif: + kfree(psif); +out: + return ret; +} + +static int __exit psif_remove(struct platform_device *pdev) +{ + struct psif *psif = platform_get_drvdata(pdev); + + psif_writel(psif, IDR, ~0UL); + psif_writel(psif, CR, PSIF_BIT(CR_TXDIS) | PSIF_BIT(CR_RXDIS)); + + serio_unregister_port(psif->io); + iounmap(psif->regs); + free_irq(psif->irq, psif); + clk_put(psif->pclk); + kfree(psif); + + platform_set_drvdata(pdev, NULL); + + return 0; +} + +#ifdef CONFIG_PM +static int psif_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct psif *psif = platform_get_drvdata(pdev); + + if (psif->open) { + psif_writel(psif, CR, PSIF_BIT(CR_RXDIS) | PSIF_BIT(CR_TXDIS)); + clk_disable(psif->pclk); + } + + return 0; +} + +static int psif_resume(struct platform_device *pdev) +{ + struct psif *psif = platform_get_drvdata(pdev); + + if (psif->open) { + clk_enable(psif->pclk); + psif_set_prescaler(psif); + psif_writel(psif, CR, PSIF_BIT(CR_RXEN) | PSIF_BIT(CR_TXEN)); + } + + return 0; +} +#else +#define psif_suspend NULL +#define psif_resume NULL +#endif + +static struct platform_driver psif_driver = { + .remove = __exit_p(psif_remove), + .driver = { + .name = "atmel_psif", + }, + .suspend = psif_suspend, + .resume = psif_resume, +}; + +static int __init psif_init(void) +{ + return platform_driver_probe(&psif_driver, psif_probe); +} + +static void __exit psif_exit(void) +{ + platform_driver_unregister(&psif_driver); +} + +module_init(psif_init); +module_exit(psif_exit); + +MODULE_AUTHOR("Hans-Christian Egtvedt <hans-christian.egtvedt@atmel.com>"); +MODULE_DESCRIPTION("Atmel AVR32 PSIF PS/2 driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/serio/ct82c710.c b/drivers/input/serio/ct82c710.c new file mode 100644 index 0000000..d1380fc --- /dev/null +++ b/drivers/input/serio/ct82c710.c @@ -0,0 +1,260 @@ +/* + * 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/init.h> +#include <linux/interrupt.h> +#include <linux/serio.h> +#include <linux/errno.h> +#include <linux/err.h> +#include <linux/platform_device.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) +{ + return serio_interrupt(ct82c710_port, inb(CT82C710_DATA), 0); +} + +/* + * 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_detect(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 -ENODEV; /* 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 int __devinit ct82c710_probe(struct platform_device *dev) +{ + ct82c710_port = kzalloc(sizeof(struct serio), GFP_KERNEL); + if (!ct82c710_port) + return -ENOMEM; + + ct82c710_port->id.type = SERIO_8042; + ct82c710_port->dev.parent = &dev->dev; + ct82c710_port->open = ct82c710_open; + ct82c710_port->close = ct82c710_close; + ct82c710_port->write = ct82c710_write; + strlcpy(ct82c710_port->name, "C&T 82c710 mouse port", + sizeof(ct82c710_port->name)); + snprintf(ct82c710_port->phys, sizeof(ct82c710_port->phys), + "isa%16llx/serio0", (unsigned long long)CT82C710_DATA); + + serio_register_port(ct82c710_port); + + return 0; +} + +static int __devexit ct82c710_remove(struct platform_device *dev) +{ + serio_unregister_port(ct82c710_port); + + return 0; +} + +static struct platform_driver ct82c710_driver = { + .driver = { + .name = "ct82c710", + .owner = THIS_MODULE, + }, + .probe = ct82c710_probe, + .remove = __devexit_p(ct82c710_remove), +}; + + +static int __init ct82c710_init(void) +{ + int error; + + error = ct82c710_detect(); + if (error) + return error; + + error = platform_driver_register(&ct82c710_driver); + if (error) + return error; + + ct82c710_device = platform_device_alloc("ct82c710", -1); + if (!ct82c710_device) { + error = -ENOMEM; + goto err_unregister_driver; + } + + error = platform_device_add_resources(ct82c710_device, &ct82c710_iores, 1); + if (error) + goto err_free_device; + + error = platform_device_add(ct82c710_device); + if (error) + goto err_free_device; + + serio_register_port(ct82c710_port); + + printk(KERN_INFO "serio: C&T 82c710 mouse port at %#llx irq %d\n", + (unsigned long long)CT82C710_DATA, CT82C710_IRQ); + + return 0; + + err_free_device: + platform_device_put(ct82c710_device); + err_unregister_driver: + platform_driver_unregister(&ct82c710_driver); + return error; +} + +static void __exit ct82c710_exit(void) +{ + platform_device_unregister(ct82c710_device); + platform_driver_unregister(&ct82c710_driver); +} + +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 0000000..adc3bd6 --- /dev/null +++ b/drivers/input/serio/gscps2.c @@ -0,0 +1,463 @@ +/* + * drivers/input/serio/gscps2.c + * + * Copyright (c) 2004-2006 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/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); + +#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 success, 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); + + 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); +} + +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 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); + + } /* 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); + + /* enable it */ + gscps2_enable(ps2port, ENABLE); + + gscps2_interrupt(0, 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.start; + 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 = kzalloc(sizeof(struct gscps2port), GFP_KERNEL); + serio = kzalloc(sizeof(struct serio), GFP_KERNEL); + if (!ps2port || !serio) { + ret = -ENOMEM; + goto fail_nomem; + } + + dev_set_drvdata(&dev->dev, ps2port); + + ps2port->port = serio; + ps2port->padev = dev; + ps2port->addr = ioremap_nocache(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; + + ret = -EBUSY; + if (request_irq(dev->irq, gscps2_interrupt, IRQF_SHARED, 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); + + list_add_tail(&ps2port->node, &ps2port_list); + + return 0; + +fail: + free_irq(dev->irq, ps2port); + +fail_miserably: + iounmap(ps2port->addr); + release_mem_region(dev->hpa.start, 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 0000000..37586a6 --- /dev/null +++ b/drivers/input/serio/hil_mlc.c @@ -0,0 +1,1014 @@ +/* + * 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/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); +static 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 = 0; + + /* In-use slots are not eligible. */ + 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 = 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 = 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 && drv->interrupt) { + drv->interrupt(serio, 0, 0); + drv->interrupt(serio, HIL_ERR_INT >> 16, 0); + drv->interrupt(serio, HIL_PKT_CMD >> 8, 0); + drv->interrupt(serio, HIL_CMD_POL + cnt, 0); + } + + 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 && drv->interrupt) { + drv->interrupt(serio, (p >> 24), 0); + drv->interrupt(serio, (p >> 16) & 0xff, 0); + drv->interrupt(serio, (p >> 8) & ~HIL_PKT_ADDR_MASK, 0); + drv->interrupt(serio, p & 0xff, 0); + } + } +} + +/*************************** 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 && (tv.tv_sec - mlc->lcv_tv.tv_sec) < 5) + return -1; + + mlc->lcv_tv = tv; + mlc->lcv = 0; + + return 0; +} + +static int hilse_inc_lcv(hil_mlc *mlc, int lim) +{ + return mlc->lcv++ >= lim ? -1 : 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) +{ + BUG_ON(mlc->ddi >= 6); + 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; + + printk(KERN_INFO PREFIX "Device name gotten: %16s\n", + mlc->di_scratch.rnm); + + 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 }, + +static const 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, const 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)); + BUG_ON(down_trylock(&mlc->isem)); +} + +#ifdef HIL_MLC_DEBUG +static int doze; +static int seidx; /* For debug */ +#endif + +static int hilse_donode(hil_mlc *mlc) +{ + const 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 {%i}", doze, mlc->seidx); + doze = 0; + } + + seidx = mlc->seidx; +#endif + node = hil_mlc_se + mlc->seidx; + + switch (node->act) { + int rc; + hil_packet pack; + + case HILSE_FUNC: + BUG_ON(node->object.func == NULL); + 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: + write_lock_irqsave(&mlc->lock, flags); + nextidx = mlc->cts(mlc) ? node->bad : node->good; + write_unlock_irqrestore(&mlc->lock, flags); + break; + + default: + BUG(); + } + +#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 += USEC_PER_SEC * (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 / USEC_PER_SEC; + 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 *********************/ + +static 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; + BUG_ON(map == NULL); + + mlc = map->mlc; + BUG_ON(mlc == NULL); + + 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; + BUG_ON(drv == NULL); + + last = idx + 15; + while ((last != idx) && (*last == 0)) + last--; + + while (idx != last) { + drv->interrupt(serio, 0, 0); + drv->interrupt(serio, HIL_ERR_INT >> 16, 0); + drv->interrupt(serio, 0, 0); + drv->interrupt(serio, *idx, 0); + idx++; + } + drv->interrupt(serio, 0, 0); + drv->interrupt(serio, HIL_ERR_INT >> 16, 0); + drv->interrupt(serio, HIL_PKT_CMD >> 8, 0); + drv->interrupt(serio, *idx, 0); + + 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_get_drvdata(serio) != NULL) + return -EBUSY; + + map = serio->port_data; + BUG_ON(map == NULL); + + mlc = map->mlc; + BUG_ON(mlc == NULL); + + 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; + BUG_ON(map == NULL); + + mlc = map->mlc; + BUG_ON(mlc == NULL); + + serio_set_drvdata(serio, NULL); + serio->drv = NULL; + /* TODO wake up interruptable */ +} + +static const struct serio_device_id hil_mlc_serio_id = { + .type = SERIO_HIL_MLC, + .proto = SERIO_HIL, + .extra = SERIO_ANY, + .id = SERIO_ANY, +}; + +int hil_mlc_register(hil_mlc *mlc) +{ + int i; + unsigned long flags; + + BUG_ON(mlc == NULL); + + 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 = kzalloc(sizeof(*mlc_serio), GFP_KERNEL); + mlc->serio[i] = mlc_serio; + snprintf(mlc_serio->name, sizeof(mlc_serio->name)-1, "HIL_SERIO%d", i); + snprintf(mlc_serio->phys, sizeof(mlc_serio->phys)-1, "HIL%d", i); + mlc_serio->id = hil_mlc_serio_id; + 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; + + BUG_ON(mlc == NULL); + + 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 0000000..bfe4924 --- /dev/null +++ b/drivers/input/serio/hp_sdc.c @@ -0,0 +1,1114 @@ +/* + * 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/errno.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/ioport.h> +#include <linux/time.h> +#include <linux/semaphore.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_enqueue_transaction); +EXPORT_SYMBOL(hp_sdc_dequeue_transaction); + +static unsigned int hp_sdc_disabled; +module_param_named(no_hpsdc, hp_sdc_disabled, bool, 0); +MODULE_PARM_DESC(no_hpsdc, "Do not enable HP SDC driver."); + +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) +{ + 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) +{ + 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 += USEC_PER_SEC; + + 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", + (int)(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, NULL, 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,NULL,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) +{ + int i; + + if (this == NULL) { + BUG(); + return -EINVAL; + } + + /* 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; + tasklet_schedule(&hp_sdc.task); + return 0; + } + + printk(KERN_WARNING PREFIX "No free slot to add transaction.\n"); + return -EBUSY; + + fail: + printk(KERN_WARNING PREFIX "Transaction add failed: transaction already queued?\n"); + return -EINVAL; +} + +int hp_sdc_enqueue_transaction(hp_sdc_transaction *this) { + unsigned long flags; + int ret; + + write_lock_irqsave(&hp_sdc.lock, flags); + ret = __hp_sdc_enqueue_transaction(this); + write_unlock_irqrestore(&hp_sdc.lock,flags); + + return ret; +} + +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 const 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) +{ + 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; + + memset(&hp_sdc.tq, 0, sizeof(hp_sdc.tq)); + + 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, IRQF_SHARED|IRQF_SAMPLE_RANDOM, + "HP SDC", &hp_sdc)) + goto err1; + + errstr = "NMI not available for"; + if (request_irq(hp_sdc.nmi, &hp_sdc_nmisr, IRQF_SHARED, + "HP SDC NMI", &hp_sdc)) + 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, &hp_sdc); + 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.start; + hp_sdc.data_io = d->hpa.start + 0x800; + hp_sdc.status_io = d->hpa.start + 0x801; + + return hp_sdc_init(); +} + +#endif /* __hppa__ */ + +static void hp_sdc_exit(void) +{ + 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, &hp_sdc); + free_irq(hp_sdc.irq, &hp_sdc); + write_unlock_irq(&hp_sdc.lock); + + del_timer(&hp_sdc.kicker); + + tasklet_kill(&hp_sdc.task); + +#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 + + if (hp_sdc_disabled) { + printk(KERN_WARNING PREFIX "HP SDC driver disabled by no_hpsdc=1.\n"); + return -ENODEV; + } + + 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) { + const 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 0000000..b587e2d --- /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> +#include <linux/semaphore.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"); + +static 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 || !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) +{ + struct hp_sdc_mlc_priv_s *priv; + int rc = 2; + + priv = mlc->priv; + + /* 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 += USEC_PER_SEC * (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; + done: + return rc; +} + +static int hp_sdc_mlc_cts(hil_mlc *mlc) +{ + struct hp_sdc_mlc_priv_s *priv; + + priv = mlc->priv; + + /* Try to down the semaphores -- they should be up. */ + BUG_ON(down_trylock(&mlc->isem)); + BUG_ON(down_trylock(&mlc->osem)); + + up(&mlc->isem); + up(&mlc->osem); + + if (down_trylock(&mlc->csem)) { + if (priv->trans.act.semaphore != &mlc->csem) + goto poll; + else + 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: + return 1; + done: + priv->trans.act.semaphore = &mlc->osem; + up(&mlc->csem); + return 0; +} + +static void hp_sdc_mlc_out(hil_mlc *mlc) +{ + struct hp_sdc_mlc_priv_s *priv; + + priv = mlc->priv; + + /* Try to down the semaphore -- it should be up. */ + BUG_ON(down_trylock(&mlc->osem)); + + if (mlc->opacket & HIL_DO_ALTER_CTRL) + goto do_control; + + do_data: + if (priv->emtestmode) { + up(&mlc->osem); + return; + } + /* Shouldn't be sending commands when loop may be busy */ + BUG_ON(down_trylock(&mlc->csem)); + 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; + + /* we cannot emulate this, it should not be used. */ + BUG_ON((mlc->opacket & (HIL_CTRL_APE | HIL_CTRL_IPF)) == HIL_CTRL_APE); + + if ((mlc->opacket & HIL_CTRL_ONLY) == HIL_CTRL_ONLY) + goto control_only; + + /* Should not send command/data after engaging APE */ + BUG_ON(mlc->opacket & HIL_CTRL_APE); + + /* 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); +} + +static int __init hp_sdc_mlc_init(void) +{ + hil_mlc *mlc = &hp_sdc_mlc; + +#ifdef __mc68000__ + if (!MACH_IS_HP300) + return -ENODEV; +#endif + + 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; + mlc->priv = &hp_sdc_mlc_priv; + + if (hil_mlc_register(mlc)) { + printk(KERN_WARNING PREFIX "Failed to register MLC structure with hil_mlc\n"); + goto err0; + } + + 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 0000000..847f4aa --- /dev/null +++ b/drivers/input/serio/i8042-io.h @@ -0,0 +1,90 @@ +#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_SH_CAYMAN) +#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(CONFIG_PPC) + if (check_legacy_ioport(I8042_DATA_REG)) + return -ENODEV; +#endif +#if !defined(__sh__) && !defined(__alpha__) && !defined(__mips__) + if (!request_region(I8042_DATA_REG, 16, "i8042")) + return -EBUSY; +#endif + + i8042_reset = 1; + return 0; +} + +static inline void i8042_platform_exit(void) +{ +#if !defined(__sh__) && !defined(__alpha__) + 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 0000000..ee1ad27 --- /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 -EBUSY; +#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 0000000..13fd710 --- /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 -EBUSY; +#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 0000000..2906e1b --- /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-snirm.h b/drivers/input/serio/i8042-snirm.h new file mode 100644 index 0000000..409a934 --- /dev/null +++ b/drivers/input/serio/i8042-snirm.h @@ -0,0 +1,75 @@ +#ifndef _I8042_SNIRM_H +#define _I8042_SNIRM_H + +#include <asm/sni.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 "onboard/serio0" +#define I8042_AUX_PHYS_DESC "onboard/serio1" +#define I8042_MUX_PHYS_DESC "onboard/serio%d" + +/* + * IRQs. + */ +static int i8042_kbd_irq; +static int i8042_aux_irq; +#define I8042_KBD_IRQ i8042_kbd_irq +#define I8042_AUX_IRQ i8042_aux_irq + +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); +} +static inline int i8042_platform_init(void) +{ + /* RM200 is strange ... */ + if (sni_brd_type == SNI_BRD_RM200) { + kbd_iobase = ioremap(0x16000000, 4); + i8042_kbd_irq = 33; + i8042_aux_irq = 44; + } else { + kbd_iobase = ioremap(0x14000000, 4); + i8042_kbd_irq = 1; + i8042_aux_irq = 12; + } + if (!kbd_iobase) + return -ENOMEM; + + return 0; +} + +static inline void i8042_platform_exit(void) +{ + +} + +#endif /* _I8042_SNIRM_H */ diff --git a/drivers/input/serio/i8042-sparcio.h b/drivers/input/serio/i8042-sparcio.h new file mode 100644 index 0000000..5071af2 --- /dev/null +++ b/drivers/input/serio/i8042-sparcio.h @@ -0,0 +1,155 @@ +#ifndef _I8042_SPARCIO_H +#define _I8042_SPARCIO_H + +#include <linux/of_device.h> + +#include <asm/io.h> +#include <asm/oplib.h> +#include <asm/prom.h> + +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; +static struct resource *kbd_res; + +#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); +} + +#ifdef CONFIG_PCI + +#define OBP_PS2KBD_NAME1 "kb_ps2" +#define OBP_PS2KBD_NAME2 "keyboard" +#define OBP_PS2MS_NAME1 "kdmouse" +#define OBP_PS2MS_NAME2 "mouse" + +static int __devinit sparc_i8042_probe(struct of_device *op, const struct of_device_id *match) +{ + struct device_node *dp = op->node; + + dp = dp->child; + while (dp) { + if (!strcmp(dp->name, OBP_PS2KBD_NAME1) || + !strcmp(dp->name, OBP_PS2KBD_NAME2)) { + struct of_device *kbd = of_find_device_by_node(dp); + unsigned int irq = kbd->irqs[0]; + if (irq == 0xffffffff) + irq = op->irqs[0]; + i8042_kbd_irq = irq; + kbd_iobase = of_ioremap(&kbd->resource[0], + 0, 8, "kbd"); + kbd_res = &kbd->resource[0]; + } else if (!strcmp(dp->name, OBP_PS2MS_NAME1) || + !strcmp(dp->name, OBP_PS2MS_NAME2)) { + struct of_device *ms = of_find_device_by_node(dp); + unsigned int irq = ms->irqs[0]; + if (irq == 0xffffffff) + irq = op->irqs[0]; + i8042_aux_irq = irq; + } + + dp = dp->sibling; + } + + return 0; +} + +static int __devexit sparc_i8042_remove(struct of_device *op) +{ + of_iounmap(kbd_res, kbd_iobase, 8); + + return 0; +} + +static const struct of_device_id sparc_i8042_match[] = { + { + .name = "8042", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, sparc_i8042_match); + +static struct of_platform_driver sparc_i8042_driver = { + .name = "i8042", + .match_table = sparc_i8042_match, + .probe = sparc_i8042_probe, + .remove = __devexit_p(sparc_i8042_remove), +}; + +static int __init i8042_platform_init(void) +{ + struct device_node *root = of_find_node_by_path("/"); + + if (!strcmp(root->name, "SUNW,JavaStation-1")) { + /* Hardcoded values for MrCoffee. */ + i8042_kbd_irq = i8042_aux_irq = 13 | 0x20; + kbd_iobase = ioremap(0x71300060, 8); + if (!kbd_iobase) + return -ENODEV; + } else { + int err = of_register_driver(&sparc_i8042_driver, + &of_bus_type); + if (err) + return err; + + if (i8042_kbd_irq == -1 || + i8042_aux_irq == -1) { + if (kbd_iobase) { + of_iounmap(kbd_res, kbd_iobase, 8); + kbd_iobase = (void __iomem *) NULL; + } + return -ENODEV; + } + } + + i8042_reset = 1; + + return 0; +} + +static inline void i8042_platform_exit(void) +{ + struct device_node *root = of_find_node_by_path("/"); + + if (strcmp(root->name, "SUNW,JavaStation-1")) + of_unregister_driver(&sparc_i8042_driver); +} + +#else /* !CONFIG_PCI */ +static int __init i8042_platform_init(void) +{ + return -ENODEV; +} + +static inline void i8042_platform_exit(void) +{ +} +#endif /* !CONFIG_PCI */ + +#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 0000000..29e6863 --- /dev/null +++ b/drivers/input/serio/i8042-x86ia64io.h @@ -0,0 +1,696 @@ +#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); +} + +#ifdef CONFIG_X86 + +#include <linux/dmi.h> + +static struct dmi_system_id __initdata i8042_dmi_noloop_table[] = { + { + /* AUX LOOP command does not raise AUX IRQ */ + .ident = "Arima-Rioworks HDAMB", + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "RIOWORKS"), + DMI_MATCH(DMI_BOARD_NAME, "HDAMB"), + DMI_MATCH(DMI_BOARD_VERSION, "Rev E"), + }, + }, + { + /* AUX LOOP command does not raise AUX IRQ */ + .ident = "ASUS P65UP5", + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "ASUSTeK Computer INC."), + DMI_MATCH(DMI_BOARD_NAME, "P/I-P65UP5"), + DMI_MATCH(DMI_BOARD_VERSION, "REV 2.X"), + }, + }, + { + .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"), + }, + }, + { + .ident = "OQO Model 01", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "OQO"), + DMI_MATCH(DMI_PRODUCT_NAME, "ZEPTO"), + DMI_MATCH(DMI_PRODUCT_VERSION, "00"), + }, + }, + { + /* AUX LOOP does not work properly */ + .ident = "ULI EV4873", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "ULI"), + DMI_MATCH(DMI_PRODUCT_NAME, "EV4873"), + DMI_MATCH(DMI_PRODUCT_VERSION, "5a"), + }, + }, + { + .ident = "Microsoft Virtual Machine", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_MATCH(DMI_PRODUCT_NAME, "Virtual Machine"), + DMI_MATCH(DMI_PRODUCT_VERSION, "VS2005R2"), + }, + }, + { + .ident = "Medion MAM 2070", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Notebook"), + DMI_MATCH(DMI_PRODUCT_NAME, "MAM 2070"), + DMI_MATCH(DMI_PRODUCT_VERSION, "5a"), + }, + }, + { + .ident = "Blue FB5601", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "blue"), + DMI_MATCH(DMI_PRODUCT_NAME, "FB5601"), + DMI_MATCH(DMI_PRODUCT_VERSION, "M606"), + }, + }, + { } +}; + +/* + * Some Fujitsu notebooks are having trouble with touchpads if + * active multiplexing mode is activated. Luckily they don't have + * external PS/2 ports so we can safely disable it. + * ... apparently some Toshibas don't like MUX mode either and + * die horrible death on reboot. + */ +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 P7010", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"), + DMI_MATCH(DMI_PRODUCT_NAME, "0000000000"), + }, + }, + { + .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 Lifebook S6230", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"), + DMI_MATCH(DMI_PRODUCT_NAME, "LifeBook S6230"), + }, + }, + { + .ident = "Fujitsu T70H", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"), + DMI_MATCH(DMI_PRODUCT_NAME, "FMVLT70H"), + }, + }, + { + .ident = "Fujitsu-Siemens Lifebook T3010", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"), + DMI_MATCH(DMI_PRODUCT_NAME, "LIFEBOOK T3010"), + }, + }, + { + .ident = "Fujitsu-Siemens Lifebook E4010", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"), + DMI_MATCH(DMI_PRODUCT_NAME, "LIFEBOOK E4010"), + }, + }, + { + .ident = "Fujitsu-Siemens Amilo Pro 2010", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"), + DMI_MATCH(DMI_PRODUCT_NAME, "AMILO Pro V2010"), + }, + }, + { + .ident = "Fujitsu-Siemens Amilo Pro 2030", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"), + DMI_MATCH(DMI_PRODUCT_NAME, "AMILO PRO V2030"), + }, + }, + { + /* + * No data is coming from the touchscreen unless KBC + * is in legacy mode. + */ + .ident = "Panasonic CF-29", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Matsushita"), + DMI_MATCH(DMI_PRODUCT_NAME, "CF-29"), + }, + }, + { + /* + * Errors on MUX ports are reported without raising AUXDATA + * causing "spurious NAK" messages. + */ + .ident = "HP Pavilion DV4017EA", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Hewlett-Packard"), + DMI_MATCH(DMI_PRODUCT_NAME, "Pavilion dv4000 (EA032EA#ABF)"), + }, + }, + { + /* + * Like DV4017EA does not raise AUXERR for errors on MUX ports. + */ + .ident = "HP Pavilion ZT1000", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Hewlett-Packard"), + DMI_MATCH(DMI_PRODUCT_NAME, "HP Pavilion Notebook PC"), + DMI_MATCH(DMI_PRODUCT_VERSION, "HP Pavilion Notebook ZT1000"), + }, + }, + { + /* + * Like DV4017EA does not raise AUXERR for errors on MUX ports. + */ + .ident = "HP Pavilion DV4270ca", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Hewlett-Packard"), + DMI_MATCH(DMI_PRODUCT_NAME, "Pavilion dv4000 (EH476UA#ABL)"), + }, + }, + { + .ident = "Toshiba P10", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "TOSHIBA"), + DMI_MATCH(DMI_PRODUCT_NAME, "Satellite P10"), + }, + }, + { + .ident = "Toshiba Equium A110", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "TOSHIBA"), + DMI_MATCH(DMI_PRODUCT_NAME, "EQUIUM A110"), + }, + }, + { + .ident = "Alienware Sentia", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "ALIENWARE"), + DMI_MATCH(DMI_PRODUCT_NAME, "Sentia"), + }, + }, + { + .ident = "Sharp Actius MM20", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "SHARP"), + DMI_MATCH(DMI_PRODUCT_NAME, "PC-MM20 Series"), + }, + }, + { + .ident = "Sony Vaio FS-115b", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Sony Corporation"), + DMI_MATCH(DMI_PRODUCT_NAME, "VGN-FS115B"), + }, + }, + { + .ident = "Amoi M636/A737", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Amoi Electronics CO.,LTD."), + DMI_MATCH(DMI_PRODUCT_NAME, "M636/A737 platform"), + }, + }, + { + .ident = "Lenovo 3000 n100", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "076804U"), + }, + }, + { + .ident = "Acer Aspire 1360", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 1360"), + }, + }, + { + .ident = "Gericom Bellagio", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Gericom"), + DMI_MATCH(DMI_PRODUCT_NAME, "N34AS6"), + }, + }, + { + .ident = "IBM 2656", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "IBM"), + DMI_MATCH(DMI_PRODUCT_NAME, "2656"), + }, + }, + { + .ident = "Dell XPS M1530", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "XPS M1530"), + }, + }, + { + .ident = "Compal HEL80I", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "COMPAL"), + DMI_MATCH(DMI_PRODUCT_NAME, "HEL80I"), + }, + }, + { } +}; + +#ifdef CONFIG_PNP +static struct dmi_system_id __initdata i8042_dmi_nopnp_table[] = { + { + .ident = "Intel MBO Desktop D845PESV", + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "D845PESV"), + DMI_MATCH(DMI_BOARD_VENDOR, "Intel Corporation"), + }, + }, + { } +}; +#endif + +/* + * Some Wistron based laptops need us to explicitly enable the 'Dritek + * keyboard extension' to make their extra keys start generating scancodes. + * Originally, this was just confined to older laptops, but a few Acer laptops + * have turned up in 2007 that also need this again. + */ +static struct dmi_system_id __initdata i8042_dmi_dritek_table[] = { + { + .ident = "Acer Aspire 5630", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 5630"), + }, + }, + { + .ident = "Acer Aspire 5650", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 5650"), + }, + }, + { + .ident = "Acer Aspire 5680", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 5680"), + }, + }, + { + .ident = "Acer Aspire 5720", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 5720"), + }, + }, + { + .ident = "Acer Aspire 9110", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 9110"), + }, + }, + { + .ident = "Acer TravelMate 660", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate 660"), + }, + }, + { + .ident = "Acer TravelMate 2490", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate 2490"), + }, + }, + { + .ident = "Acer TravelMate 4280", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate 4280"), + }, + }, + { } +}; + +#endif /* CONFIG_X86 */ + +#ifdef CONFIG_PNP +#include <linux/pnp.h> + +static int i8042_pnp_kbd_registered; +static unsigned int i8042_pnp_kbd_devices; +static int i8042_pnp_aux_registered; +static unsigned int i8042_pnp_aux_devices; + +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); + + strlcpy(i8042_pnp_kbd_name, did->id, sizeof(i8042_pnp_kbd_name)); + if (strlen(pnp_dev_name(dev))) { + strlcat(i8042_pnp_kbd_name, ":", sizeof(i8042_pnp_kbd_name)); + strlcat(i8042_pnp_kbd_name, pnp_dev_name(dev), sizeof(i8042_pnp_kbd_name)); + } + + i8042_pnp_kbd_devices++; + 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); + + strlcpy(i8042_pnp_aux_name, did->id, sizeof(i8042_pnp_aux_name)); + if (strlen(pnp_dev_name(dev))) { + strlcat(i8042_pnp_aux_name, ":", sizeof(i8042_pnp_aux_name)); + strlcat(i8042_pnp_aux_name, pnp_dev_name(dev), sizeof(i8042_pnp_aux_name)); + } + + i8042_pnp_aux_devices++; + 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 = "FJC6000", .driver_data = 0 }, + { .id = "FJC6001", .driver_data = 0 }, + { .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) { + i8042_pnp_kbd_registered = 0; + pnp_unregister_driver(&i8042_pnp_kbd_driver); + } + + if (i8042_pnp_aux_registered) { + i8042_pnp_aux_registered = 0; + pnp_unregister_driver(&i8042_pnp_aux_driver); + } +} + +static int __init i8042_pnp_init(void) +{ + char kbd_irq_str[4] = { 0 }, aux_irq_str[4] = { 0 }; + int pnp_data_busted = 0; + int err; + +#ifdef CONFIG_X86 + if (dmi_check_system(i8042_dmi_nopnp_table)) + i8042_nopnp = 1; +#endif + + if (i8042_nopnp) { + printk(KERN_INFO "i8042: PNP detection disabled\n"); + return 0; + } + + err = pnp_register_driver(&i8042_pnp_kbd_driver); + if (!err) + i8042_pnp_kbd_registered = 1; + + err = pnp_register_driver(&i8042_pnp_aux_driver); + if (!err) + i8042_pnp_aux_registered = 1; + + if (!i8042_pnp_kbd_devices && !i8042_pnp_aux_devices) { + i8042_pnp_exit(); +#if defined(__ia64__) + return -ENODEV; +#else + printk(KERN_INFO "PNP: No PS/2 controller found. Probing ports directly.\n"); + return 0; +#endif + } + + if (i8042_pnp_kbd_devices) + snprintf(kbd_irq_str, sizeof(kbd_irq_str), + "%d", i8042_pnp_kbd_irq); + if (i8042_pnp_aux_devices) + snprintf(aux_irq_str, sizeof(aux_irq_str), + "%d", i8042_pnp_aux_irq); + + printk(KERN_INFO "PNP: PS/2 Controller [%s%s%s] at %#x,%#x irq %s%s%s\n", + i8042_pnp_kbd_name, (i8042_pnp_kbd_devices && i8042_pnp_aux_devices) ? "," : "", + i8042_pnp_aux_name, + i8042_pnp_data_reg, i8042_pnp_command_reg, + kbd_irq_str, (i8042_pnp_kbd_devices && i8042_pnp_aux_devices) ? "," : "", + aux_irq_str); + +#if defined(__ia64__) + if (!i8042_pnp_kbd_devices) + i8042_nokbd = 1; + if (!i8042_pnp_aux_devices) + i8042_noaux = 1; +#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; + pnp_data_busted = 1; + } + + 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; + pnp_data_busted = 1; + } + + if (!i8042_nokbd && !i8042_pnp_kbd_irq) { + printk(KERN_WARNING + "PNP: PS/2 controller doesn't have KBD irq; " + "using default %d\n", i8042_kbd_irq); + i8042_pnp_kbd_irq = i8042_kbd_irq; + pnp_data_busted = 1; + } + + if (!i8042_noaux && !i8042_pnp_aux_irq) { + if (!pnp_data_busted && i8042_pnp_kbd_irq) { + printk(KERN_WARNING + "PNP: PS/2 appears to have AUX port disabled, " + "if this is incorrect please boot with " + "i8042.nopnp\n"); + i8042_noaux = 1; + } else { + printk(KERN_WARNING + "PNP: PS/2 controller doesn't have AUX irq; " + "using default %d\n", i8042_aux_irq); + i8042_pnp_aux_irq = i8042_aux_irq; + } + } + + 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; + + return 0; +} + +#else +static inline int i8042_pnp_init(void) { return 0; } +static inline void i8042_pnp_exit(void) { } +#endif + +static int __init i8042_platform_init(void) +{ + int retval; + +/* + * 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 -EBUSY; + */ + + i8042_kbd_irq = I8042_MAP_IRQ(1); + i8042_aux_irq = I8042_MAP_IRQ(12); + + retval = i8042_pnp_init(); + if (retval) + return retval; + +#if defined(__ia64__) + i8042_reset = 1; +#endif + +#ifdef CONFIG_X86 + if (dmi_check_system(i8042_dmi_noloop_table)) + i8042_noloop = 1; + + if (dmi_check_system(i8042_dmi_nomux_table)) + i8042_nomux = 1; + + if (dmi_check_system(i8042_dmi_dritek_table)) + i8042_dritek = 1; +#endif /* CONFIG_X86 */ + + return retval; +} + +static inline void i8042_platform_exit(void) +{ + i8042_pnp_exit(); +} + +#endif /* _I8042_X86IA64IO_H */ diff --git a/drivers/input/serio/i8042.c b/drivers/input/serio/i8042.c new file mode 100644 index 0000000..170f71e --- /dev/null +++ b/drivers/input/serio/i8042.c @@ -0,0 +1,1293 @@ +/* + * 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/interrupt.h> +#include <linux/ioport.h> +#include <linux/init.h> +#include <linux/serio.h> +#include <linux/err.h> +#include <linux/rcupdate.h> +#include <linux/platform_device.h> +#include <linux/i8042.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_nokbd; +module_param_named(nokbd, i8042_nokbd, bool, 0); +MODULE_PARM_DESC(nokbd, "Do not probe or use KBD port."); + +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_X86 +static unsigned int i8042_dritek; +module_param_named(dritek, i8042_dritek, bool, 0); +MODULE_PARM_DESC(dritek, "Force enable the Dritek keyboard extension"); +#endif + +#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 + +#include "i8042.h" + +static DEFINE_SPINLOCK(i8042_lock); + +struct i8042_port { + struct serio *serio; + int irq; + unsigned char exists; + signed char mux; +}; + +#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]; + +static unsigned char i8042_initial_ctr; +static unsigned char i8042_ctr; +static unsigned char i8042_mux_present; +static unsigned char i8042_kbd_irq_registered; +static unsigned char i8042_aux_irq_registered; +static unsigned char i8042_suppress_kbd_ack; +static struct platform_device *i8042_platform_device; + +static irqreturn_t i8042_interrupt(int irq, void *dev_id); + +/* + * 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) +{ + int i, error; + + if (i8042_noloop && command == I8042_CMD_AUX_LOOP) + return -1; + + error = i8042_wait_write(); + if (error) + return error; + + dbg("%02x -> i8042 (command)", command & 0xff); + i8042_write_command(command & 0xff); + + for (i = 0; i < ((command >> 12) & 0xf); i++) { + error = i8042_wait_write(); + if (error) + return error; + dbg("%02x -> i8042 (parameter)", param[i]); + i8042_write_data(param[i]); + } + + for (i = 0; i < ((command >> 8) & 0xf); i++) { + error = i8042_wait_read(); + if (error) { + dbg(" -- i8042 (timeout)"); + return error; + } + + if (command == I8042_CMD_AUX_LOOP && + !(i8042_read_status() & I8042_STR_AUXDATA)) { + dbg(" -- i8042 (auxerr)"); + return -1; + } + + param[i] = i8042_read_data(); + dbg("%02x <- i8042 (return)", param[i]); + } + + return 0; +} + +int i8042_command(unsigned char *param, int command) +{ + unsigned long flags; + int retval; + + spin_lock_irqsave(&i8042_lock, flags); + retval = __i8042_command(param, command); + spin_unlock_irqrestore(&i8042_lock, flags); + + return retval; +} +EXPORT_SYMBOL(i8042_command); + +/* + * 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; + + return i8042_command(&c, port->mux == -1 ? + I8042_CMD_AUX_SEND : + I8042_CMD_MUX_SEND + port->mux); +} + +/* + * 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; + + /* + * We synchronize with both AUX and KBD IRQs because there is + * a (very unlikely) chance that AUX IRQ is raised for KBD port + * and vice versa. + */ + synchronize_irq(I8042_AUX_IRQ); + synchronize_irq(I8042_KBD_IRQ); + 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 i8042_port *port; + unsigned long flags; + unsigned char str, data; + unsigned int dfl; + unsigned int port_no; + int ret = 1; + + 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); +/* + * 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 KBCs also report 0xfc when there is + * nothing connected to the port while others sometimes 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 process 0xfc, 0xfd, 0xfe and 0xff as timeouts, and for the + * rest assume that the data came from the same serio last byte + * was transmitted (if transmission happened not too long ago). + */ + + switch (data) { + default: + if (time_before(jiffies, last_transmit + HZ/10)) { + str = last_str; + break; + } + /* fall through - report timeout */ + case 0xfc: + 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, %d, %d%s%s)", + data, port_no, irq, + dfl & SERIO_PARITY ? ", bad parity" : "", + dfl & SERIO_TIMEOUT ? ", timeout" : ""); + + if (unlikely(i8042_suppress_kbd_ack)) + if (port_no == I8042_KBD_PORT_NO && + (data == 0xfa || data == 0xfe)) { + i8042_suppress_kbd_ack--; + goto out; + } + + if (likely(port->exists)) + serio_interrupt(port->serio, data, dfl); + + out: + return IRQ_RETVAL(ret); +} + +/* + * i8042_enable_kbd_port enables keybaord port on chip + */ + +static int i8042_enable_kbd_port(void) +{ + i8042_ctr &= ~I8042_CTR_KBDDIS; + i8042_ctr |= I8042_CTR_KBDINT; + + if (i8042_command(&i8042_ctr, I8042_CMD_CTL_WCTR)) { + i8042_ctr &= ~I8042_CTR_KBDINT; + i8042_ctr |= I8042_CTR_KBDDIS; + printk(KERN_ERR "i8042.c: Failed to enable KBD port.\n"); + return -EIO; + } + + return 0; +} + +/* + * i8042_enable_aux_port enables AUX (mouse) port on chip + */ + +static int i8042_enable_aux_port(void) +{ + i8042_ctr &= ~I8042_CTR_AUXDIS; + i8042_ctr |= I8042_CTR_AUXINT; + + if (i8042_command(&i8042_ctr, I8042_CMD_CTL_WCTR)) { + i8042_ctr &= ~I8042_CTR_AUXINT; + i8042_ctr |= I8042_CTR_AUXDIS; + printk(KERN_ERR "i8042.c: Failed to enable AUX port.\n"); + return -EIO; + } + + 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; + + for (i = 0; i < I8042_NUM_MUX_PORTS; i++) { + i8042_command(¶m, I8042_CMD_MUX_PFX + i); + i8042_command(¶m, I8042_CMD_AUX_ENABLE); + } + + return i8042_enable_aux_port(); +} + +/* + * 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. + */ + + param = 0xf0; + if (i8042_command(¶m, I8042_CMD_AUX_LOOP) || param != 0xf0) + return -1; + param = mode ? 0x56 : 0xf6; + if (i8042_command(¶m, I8042_CMD_AUX_LOOP) || param != (mode ? 0x56 : 0xf6)) + return -1; + param = mode ? 0xa4 : 0xa5; + if (i8042_command(¶m, I8042_CMD_AUX_LOOP) || param == (mode ? 0xa4 : 0xa5)) + return -1; + + if (mux_version) + *mux_version = param; + + 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 __devinit 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); + +/* + * 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 -EIO; + } + + i8042_mux_present = 1; + + return 0; +} + +/* + * The following is used to test AUX IRQ delivery. + */ +static struct completion i8042_aux_irq_delivered __devinitdata; +static int i8042_irq_being_tested __devinitdata; + +static irqreturn_t __devinit i8042_aux_test_irq(int irq, void *dev_id) +{ + unsigned long flags; + unsigned char str, data; + int ret = 0; + + spin_lock_irqsave(&i8042_lock, flags); + str = i8042_read_status(); + if (str & I8042_STR_OBF) { + data = i8042_read_data(); + if (i8042_irq_being_tested && + data == 0xa5 && (str & I8042_STR_AUXDATA)) + complete(&i8042_aux_irq_delivered); + ret = 1; + } + spin_unlock_irqrestore(&i8042_lock, flags); + + return IRQ_RETVAL(ret); +} + +/* + * i8042_toggle_aux - enables or disables AUX port on i8042 via command and + * verifies success by readinng CTR. Used when testing for presence of AUX + * port. + */ +static int __devinit i8042_toggle_aux(int on) +{ + unsigned char param; + int i; + + if (i8042_command(¶m, + on ? I8042_CMD_AUX_ENABLE : I8042_CMD_AUX_DISABLE)) + return -1; + + /* some chips need some time to set the I8042_CTR_AUXDIS bit */ + for (i = 0; i < 100; i++) { + udelay(50); + + if (i8042_command(¶m, I8042_CMD_CTL_RCTR)) + return -1; + + if (!(param & I8042_CTR_AUXDIS) == on) + return 0; + } + + return -1; +} + +/* + * i8042_check_aux() applies as much paranoia as it can at detecting + * the presence of an AUX interface. + */ + +static int __devinit i8042_check_aux(void) +{ + int retval = -1; + int irq_registered = 0; + int aux_loop_broken = 0; + unsigned long flags; + unsigned char param; + +/* + * 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; + retval = i8042_command(¶m, I8042_CMD_AUX_LOOP); + if (retval || param != 0x5a) { + +/* + * 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; + +/* + * If AUX_LOOP completed without error but returned unexpected data + * mark it as broken + */ + if (!retval) + aux_loop_broken = 1; + } + +/* + * Bit assignment test - filters out PS/2 i8042's in AT mode + */ + + if (i8042_toggle_aux(0)) { + 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_toggle_aux(1)) + return -1; + +/* + * Test AUX IRQ delivery to make sure BIOS did not grab the IRQ and + * used it for a PCI card or somethig else. + */ + + if (i8042_noloop || aux_loop_broken) { +/* + * Without LOOP command we can't test AUX IRQ delivery. Assume the port + * is working and hope we are right. + */ + retval = 0; + goto out; + } + + if (request_irq(I8042_AUX_IRQ, i8042_aux_test_irq, IRQF_SHARED, + "i8042", i8042_platform_device)) + goto out; + + irq_registered = 1; + + if (i8042_enable_aux_port()) + goto out; + + spin_lock_irqsave(&i8042_lock, flags); + + init_completion(&i8042_aux_irq_delivered); + i8042_irq_being_tested = 1; + + param = 0xa5; + retval = __i8042_command(¶m, I8042_CMD_AUX_LOOP & 0xf0ff); + + spin_unlock_irqrestore(&i8042_lock, flags); + + if (retval) + goto out; + + if (wait_for_completion_timeout(&i8042_aux_irq_delivered, + msecs_to_jiffies(250)) == 0) { +/* + * AUX IRQ was never delivered so we need to flush the controller to + * get rid of the byte we put there; otherwise keyboard may not work. + */ + i8042_flush(); + retval = -1; + } + + out: + +/* + * Disable the interface. + */ + + i8042_ctr |= I8042_CTR_AUXDIS; + i8042_ctr &= ~I8042_CTR_AUXINT; + + if (i8042_command(&i8042_ctr, I8042_CMD_CTL_WCTR)) + retval = -1; + + if (irq_registered) + free_irq(I8042_AUX_IRQ, i8042_platform_device); + + return retval; +} + +static int i8042_controller_check(void) +{ + if (i8042_flush() == I8042_BUFFER_SIZE) { + printk(KERN_ERR "i8042.c: No controller found.\n"); + return -ENODEV; + } + + return 0; +} + +static int i8042_controller_selftest(void) +{ + unsigned char param; + + if (!i8042_reset) + return 0; + + if (i8042_command(¶m, I8042_CMD_CTL_TEST)) { + printk(KERN_ERR "i8042.c: i8042 controller self test timeout.\n"); + return -ENODEV; + } + + if (param != I8042_RET_CTL_TEST) { + printk(KERN_ERR "i8042.c: i8042 controller selftest failed. (%#x != %#x)\n", + param, I8042_RET_CTL_TEST); + return -EIO; + } + + return 0; +} + +/* + * 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; + +/* + * 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 -EIO; + } + + 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 -EIO; + } + + return 0; +} + + +/* + * Reset the controller and reset CRT to the original value set by BIOS. + */ + +static void i8042_controller_reset(void) +{ + i8042_flush(); + +/* + * Disable both KBD and AUX interfaces so they don't get in the way + */ + + i8042_ctr |= I8042_CTR_KBDDIS | I8042_CTR_AUXDIS; + i8042_ctr &= ~(I8042_CTR_KBDINT | I8042_CTR_AUXINT); + +/* + * Disable MUX mode if present. + */ + + if (i8042_mux_present) + i8042_set_mux_mode(0, NULL); + +/* + * Reset the controller if requested. + */ + + i8042_controller_selftest(); + +/* + * Restore the original control register setting. + */ + + if (i8042_command(&i8042_initial_ctr, I8042_CMD_CTL_WCTR)) + printk(KERN_WARNING "i8042.c: Can't restore CTR.\n"); +} + + +/* + * 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; + dbg("%02x -> i8042 (panic blink)", 0xed); + i8042_suppress_kbd_ack = 2; + i8042_write_data(0xed); /* set leds */ + DELAY; + while (i8042_read_status() & I8042_STR_IBF) + DELAY; + DELAY; + dbg("%02x -> i8042 (panic blink)", led); + i8042_write_data(led); + DELAY; + last_blink = count; + return delay; +} + +#undef DELAY + +#ifdef CONFIG_X86 +static void i8042_dritek_enable(void) +{ + char param = 0x90; + int error; + + error = i8042_command(¶m, 0x1059); + if (error) + printk(KERN_WARNING + "Failed to enable DRITEK extension: %d\n", + error); +} +#endif + +#ifdef CONFIG_PM +/* + * Here we try to restore the original BIOS settings. We only want to + * do that once, when we really suspend, not when we taking memory + * snapshot for swsusp (in this case we'll perform required cleanup + * as part of shutdown process). + */ + +static int i8042_suspend(struct platform_device *dev, pm_message_t state) +{ + if (dev->dev.power.power_state.event != state.event) { + if (state.event == PM_EVENT_SUSPEND) + i8042_controller_reset(); + + dev->dev.power.power_state = state; + } + + return 0; +} + + +/* + * Here we try to reset everything back to a state in which suspended + */ + +static int i8042_resume(struct platform_device *dev) +{ + int error; + +/* + * Do not bother with restoring state if we haven't suspened yet + */ + if (dev->dev.power.power_state.event == PM_EVENT_ON) + return 0; + + error = i8042_controller_check(); + if (error) + return error; + + error = i8042_controller_selftest(); + if (error) + return error; + +/* + * Restore original CTR value and disable all ports + */ + + i8042_ctr = i8042_initial_ctr; + if (i8042_direct) + i8042_ctr &= ~I8042_CTR_XLATE; + i8042_ctr |= I8042_CTR_AUXDIS | I8042_CTR_KBDDIS; + i8042_ctr &= ~(I8042_CTR_AUXINT | I8042_CTR_KBDINT); + if (i8042_command(&i8042_ctr, I8042_CMD_CTL_WCTR)) { + printk(KERN_WARNING "i8042: Can't write CTR to resume, retrying...\n"); + msleep(50); + if (i8042_command(&i8042_ctr, I8042_CMD_CTL_WCTR)) { + printk(KERN_ERR "i8042: CTR write retry failed\n"); + return -EIO; + } + } + + +#ifdef CONFIG_X86 + if (i8042_dritek) + i8042_dritek_enable(); +#endif + + 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"); + } else if (i8042_ports[I8042_AUX_PORT_NO].serio) + i8042_enable_aux_port(); + + if (i8042_ports[I8042_KBD_PORT_NO].serio) + i8042_enable_kbd_port(); + + i8042_interrupt(0, NULL); + + dev->dev.power.power_state = PMSG_ON; + + return 0; +} +#endif /* CONFIG_PM */ + +/* + * We need to reset the 8042 back to original mode on system shutdown, + * because otherwise BIOSes will be confused. + */ + +static void i8042_shutdown(struct platform_device *dev) +{ + i8042_controller_reset(); +} + +static int __devinit i8042_create_kbd_port(void) +{ + struct serio *serio; + struct i8042_port *port = &i8042_ports[I8042_KBD_PORT_NO]; + + serio = kzalloc(sizeof(struct serio), GFP_KERNEL); + if (!serio) + return -ENOMEM; + + serio->id.type = i8042_direct ? SERIO_8042 : SERIO_8042_XL; + serio->write = i8042_dumbkbd ? NULL : i8042_kbd_write; + 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; + port->irq = I8042_KBD_IRQ; + + return 0; +} + +static int __devinit i8042_create_aux_port(int idx) +{ + struct serio *serio; + int port_no = idx < 0 ? I8042_AUX_PORT_NO : I8042_MUX_PORT_NO + idx; + struct i8042_port *port = &i8042_ports[port_no]; + + serio = kzalloc(sizeof(struct serio), GFP_KERNEL); + if (!serio) + return -ENOMEM; + + serio->id.type = SERIO_8042; + serio->write = i8042_aux_write; + serio->start = i8042_start; + serio->stop = i8042_stop; + serio->port_data = port; + serio->dev.parent = &i8042_platform_device->dev; + if (idx < 0) { + strlcpy(serio->name, "i8042 AUX port", sizeof(serio->name)); + strlcpy(serio->phys, I8042_AUX_PHYS_DESC, sizeof(serio->phys)); + } else { + snprintf(serio->name, sizeof(serio->name), "i8042 AUX%d port", idx); + snprintf(serio->phys, sizeof(serio->phys), I8042_MUX_PHYS_DESC, idx + 1); + } + + port->serio = serio; + port->mux = idx; + port->irq = I8042_AUX_IRQ; + + return 0; +} + +static void __devinit i8042_free_kbd_port(void) +{ + kfree(i8042_ports[I8042_KBD_PORT_NO].serio); + i8042_ports[I8042_KBD_PORT_NO].serio = NULL; +} + +static void __devinit i8042_free_aux_ports(void) +{ + int i; + + for (i = I8042_AUX_PORT_NO; i < I8042_NUM_PORTS; i++) { + kfree(i8042_ports[i].serio); + i8042_ports[i].serio = NULL; + } +} + +static void __devinit i8042_register_ports(void) +{ + int i; + + for (i = 0; i < I8042_NUM_PORTS; i++) { + if (i8042_ports[i].serio) { + printk(KERN_INFO "serio: %s at %#lx,%#lx irq %d\n", + i8042_ports[i].serio->name, + (unsigned long) I8042_DATA_REG, + (unsigned long) I8042_COMMAND_REG, + i8042_ports[i].irq); + serio_register_port(i8042_ports[i].serio); + } + } +} + +static void __devexit i8042_unregister_ports(void) +{ + int i; + + for (i = 0; i < I8042_NUM_PORTS; i++) { + if (i8042_ports[i].serio) { + serio_unregister_port(i8042_ports[i].serio); + i8042_ports[i].serio = NULL; + } + } +} + +static void i8042_free_irqs(void) +{ + if (i8042_aux_irq_registered) + free_irq(I8042_AUX_IRQ, i8042_platform_device); + if (i8042_kbd_irq_registered) + free_irq(I8042_KBD_IRQ, i8042_platform_device); + + i8042_aux_irq_registered = i8042_kbd_irq_registered = 0; +} + +static int __devinit i8042_setup_aux(void) +{ + int (*aux_enable)(void); + int error; + int i; + + if (i8042_check_aux()) + return -ENODEV; + + if (i8042_nomux || i8042_check_mux()) { + error = i8042_create_aux_port(-1); + if (error) + goto err_free_ports; + aux_enable = i8042_enable_aux_port; + } else { + for (i = 0; i < I8042_NUM_MUX_PORTS; i++) { + error = i8042_create_aux_port(i); + if (error) + goto err_free_ports; + } + aux_enable = i8042_enable_mux_ports; + } + + error = request_irq(I8042_AUX_IRQ, i8042_interrupt, IRQF_SHARED, + "i8042", i8042_platform_device); + if (error) + goto err_free_ports; + + if (aux_enable()) + goto err_free_irq; + + i8042_aux_irq_registered = 1; + return 0; + + err_free_irq: + free_irq(I8042_AUX_IRQ, i8042_platform_device); + err_free_ports: + i8042_free_aux_ports(); + return error; +} + +static int __devinit i8042_setup_kbd(void) +{ + int error; + + error = i8042_create_kbd_port(); + if (error) + return error; + + error = request_irq(I8042_KBD_IRQ, i8042_interrupt, IRQF_SHARED, + "i8042", i8042_platform_device); + if (error) + goto err_free_port; + + error = i8042_enable_kbd_port(); + if (error) + goto err_free_irq; + + i8042_kbd_irq_registered = 1; + return 0; + + err_free_irq: + free_irq(I8042_KBD_IRQ, i8042_platform_device); + err_free_port: + i8042_free_kbd_port(); + return error; +} + +static int __devinit i8042_probe(struct platform_device *dev) +{ + int error; + + error = i8042_controller_selftest(); + if (error) + return error; + + error = i8042_controller_init(); + if (error) + return error; + +#ifdef CONFIG_X86 + if (i8042_dritek) + i8042_dritek_enable(); +#endif + + if (!i8042_noaux) { + error = i8042_setup_aux(); + if (error && error != -ENODEV && error != -EBUSY) + goto out_fail; + } + + if (!i8042_nokbd) { + error = i8042_setup_kbd(); + if (error) + goto out_fail; + } +/* + * Ok, everything is ready, let's register all serio ports + */ + i8042_register_ports(); + + return 0; + + out_fail: + i8042_free_aux_ports(); /* in case KBD failed but AUX not */ + i8042_free_irqs(); + i8042_controller_reset(); + + return error; +} + +static int __devexit i8042_remove(struct platform_device *dev) +{ + i8042_unregister_ports(); + i8042_free_irqs(); + i8042_controller_reset(); + + return 0; +} + +static struct platform_driver i8042_driver = { + .driver = { + .name = "i8042", + .owner = THIS_MODULE, + }, + .probe = i8042_probe, + .remove = __devexit_p(i8042_remove), + .shutdown = i8042_shutdown, +#ifdef CONFIG_PM + .suspend = i8042_suspend, + .resume = i8042_resume, +#endif +}; + +static int __init i8042_init(void) +{ + int err; + + dbg_init(); + + err = i8042_platform_init(); + if (err) + return err; + + err = i8042_controller_check(); + if (err) + goto err_platform_exit; + + err = platform_driver_register(&i8042_driver); + if (err) + goto err_platform_exit; + + i8042_platform_device = platform_device_alloc("i8042", -1); + if (!i8042_platform_device) { + err = -ENOMEM; + goto err_unregister_driver; + } + + err = platform_device_add(i8042_platform_device); + if (err) + goto err_free_device; + + panic_blink = i8042_panic_blink; + + return 0; + + err_free_device: + platform_device_put(i8042_platform_device); + err_unregister_driver: + platform_driver_unregister(&i8042_driver); + err_platform_exit: + i8042_platform_exit(); + + return err; +} + +static void __exit i8042_exit(void) +{ + platform_device_unregister(i8042_platform_device); + platform_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 0000000..cbc1beb --- /dev/null +++ b/drivers/input/serio/i8042.h @@ -0,0 +1,103 @@ +#ifndef _I8042_H +#define _I8042_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_HAS_I8042) +#include "i8042-ip22io.h" +#elif defined(CONFIG_SNI_RM) +#include "i8042-snirm.h" +#elif defined(CONFIG_PPC) +#include "i8042-ppcio.h" +#elif defined(CONFIG_SPARC) +#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 + +/* + * 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 + +/* + * 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 0000000..2b304c2 --- /dev/null +++ b/drivers/input/serio/libps2.c @@ -0,0 +1,334 @@ +/* + * 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/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"); + +/* + * ps2_sendbyte() sends a byte to the device and waits for acknowledge. + * It doesn't handle retransmission, though it could - because if there + * is a need for retransmissions device 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; +} +EXPORT_SYMBOL(ps2_sendbyte); + +/* + * ps2_drain() waits for device to transmit requested number of bytes + * and discards them. + */ + +void ps2_drain(struct ps2dev *ps2dev, int maxbytes, int timeout) +{ + if (maxbytes > sizeof(ps2dev->cmdbuf)) { + WARN_ON(1); + maxbytes = sizeof(ps2dev->cmdbuf); + } + + mutex_lock(&ps2dev->cmd_mutex); + + serio_pause_rx(ps2dev->serio); + ps2dev->flags = PS2_FLAG_CMD; + ps2dev->cmdcnt = maxbytes; + serio_continue_rx(ps2dev->serio); + + wait_event_timeout(ps2dev->wait, + !(ps2dev->flags & PS2_FLAG_CMD), + msecs_to_jiffies(timeout)); + mutex_unlock(&ps2dev->cmd_mutex); +} +EXPORT_SYMBOL(ps2_drain); + +/* + * ps2_is_keyboard_id() checks received ID byte against the list of + * known keyboard IDs. + */ + +int ps2_is_keyboard_id(char id_byte) +{ + static const char keyboard_ids[] = { + 0xab, /* Regular keyboards */ + 0xac, /* NCD Sun keyboard */ + 0x2b, /* Trust keyboard, translated */ + 0x5d, /* Trust keyboard */ + 0x60, /* NMB SGI keyboard, translated */ + 0x47, /* NMB SGI keyboard */ + }; + + return memchr(keyboard_ids, id_byte, sizeof(keyboard_ids)) != NULL; +} +EXPORT_SYMBOL(ps2_is_keyboard_id); + +/* + * ps2_adjust_timeout() is called after receiving 1st byte of command + * response and tries to reduce remaining timeout to speed up command + * completion. + */ + +static int ps2_adjust_timeout(struct ps2dev *ps2dev, int command, int timeout) +{ + switch (command) { + case PS2_CMD_RESET_BAT: + /* + * Device has sent the first response byte after + * reset command, reset is thus done, so we can + * shorten the timeout. + * The next byte will come soon (keyboard) or not + * at all (mouse). + */ + if (timeout > msecs_to_jiffies(100)) + timeout = msecs_to_jiffies(100); + break; + + case PS2_CMD_GETID: + /* + * Microsoft Natural Elite keyboard responds to + * the GET ID command as it were a mouse, with + * a single byte. Fail the command so atkbd will + * use alternative probe to detect it. + */ + if (ps2dev->cmdbuf[1] == 0xaa) { + serio_pause_rx(ps2dev->serio); + ps2dev->flags = 0; + serio_continue_rx(ps2dev->serio); + timeout = 0; + } + + /* + * If device behind the port is not a keyboard there + * won't be 2nd byte of ID response. + */ + if (!ps2_is_keyboard_id(ps2dev->cmdbuf[1])) { + serio_pause_rx(ps2dev->serio); + ps2dev->flags = ps2dev->cmdcnt = 0; + serio_continue_rx(ps2dev->serio); + timeout = 0; + } + break; + + default: + break; + } + + return timeout; +} + +/* + * 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; + + if (receive > sizeof(ps2dev->cmdbuf)) { + WARN_ON(1); + return -1; + } + + if (send && !param) { + WARN_ON(1); + return -1; + } + + mutex_lock(&ps2dev->cmd_mutex); + + 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 (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) { + + timeout = ps2_adjust_timeout(ps2dev, command, timeout); + 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); + + mutex_unlock(&ps2dev->cmd_mutex); + return rc; +} +EXPORT_SYMBOL(ps2_command); + +/* + * ps2_init() initializes ps2dev structure + */ + +void ps2_init(struct ps2dev *ps2dev, struct serio *serio) +{ + mutex_init(&ps2dev->cmd_mutex); + lockdep_set_subclass(&ps2dev->cmd_mutex, serio->depth); + init_waitqueue_head(&ps2dev->wait); + ps2dev->serio = serio; +} +EXPORT_SYMBOL(ps2_init); + +/* + * 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; +} +EXPORT_SYMBOL(ps2_handle_ack); + +/* + * 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; +} +EXPORT_SYMBOL(ps2_handle_response); + +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; +} +EXPORT_SYMBOL(ps2_cmd_aborted); diff --git a/drivers/input/serio/maceps2.c b/drivers/input/serio/maceps2.c new file mode 100644 index 0000000..558200e --- /dev/null +++ b/drivers/input/serio/maceps2.c @@ -0,0 +1,211 @@ +/* + * 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/platform_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 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); + } + + 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 * __devinit maceps2_allocate_port(int idx) +{ + struct serio *serio; + + serio = kzalloc(sizeof(struct serio), GFP_KERNEL); + if (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 __devinit maceps2_probe(struct platform_device *dev) +{ + 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]); + return -ENOMEM; + } + + serio_register_port(maceps2_port[0]); + serio_register_port(maceps2_port[1]); + + return 0; +} + +static int __devexit maceps2_remove(struct platform_device *dev) +{ + serio_unregister_port(maceps2_port[0]); + serio_unregister_port(maceps2_port[1]); + + return 0; +} + +static struct platform_driver maceps2_driver = { + .driver = { + .name = "maceps2", + .owner = THIS_MODULE, + }, + .probe = maceps2_probe, + .remove = __devexit_p(maceps2_remove), +}; + +static int __init maceps2_init(void) +{ + int error; + + error = platform_driver_register(&maceps2_driver); + if (error) + return error; + + maceps2_device = platform_device_alloc("maceps2", -1); + if (!maceps2_device) { + error = -ENOMEM; + goto err_unregister_driver; + } + + 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; + + error = platform_device_add(maceps2_device); + if (error) + goto err_free_device; + + return 0; + + err_free_device: + platform_device_put(maceps2_device); + err_unregister_driver: + platform_driver_unregister(&maceps2_driver); + return error; +} + +static void __exit maceps2_exit(void) +{ + platform_device_unregister(maceps2_device); + platform_driver_unregister(&maceps2_driver); +} + +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 0000000..b089977 --- /dev/null +++ b/drivers/input/serio/parkbd.c @@ -0,0 +1,217 @@ +/* + * 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(void *dev_id) +{ + + 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); + } + + 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 = kzalloc(sizeof(struct serio), GFP_KERNEL); + if (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 0000000..1b404f9 --- /dev/null +++ b/drivers/input/serio/pcips2.c @@ -0,0 +1,232 @@ +/* + * 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 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); + } 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, IRQF_SHARED, + "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 = kzalloc(sizeof(struct pcips2_data), GFP_KERNEL); + serio = kzalloc(sizeof(struct serio), GFP_KERNEL); + if (!ps2if || !serio) { + ret = -ENOMEM; + goto release; + } + + + 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 0000000..e36a090 --- /dev/null +++ b/drivers/input/serio/q40kbd.c @@ -0,0 +1,187 @@ +/* + * 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 <linux/platform_device.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"); + +static 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) +{ + 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); + + 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 -EBUSY; + } + + /* 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 int __devinit q40kbd_probe(struct platform_device *dev) +{ + q40kbd_port = kzalloc(sizeof(struct serio), GFP_KERNEL); + if (!q40kbd_port) + return -ENOMEM; + + q40kbd_port->id.type = SERIO_8042; + q40kbd_port->open = q40kbd_open; + q40kbd_port->close = q40kbd_close; + q40kbd_port->dev.parent = &dev->dev; + strlcpy(q40kbd_port->name, "Q40 Kbd Port", sizeof(q40kbd_port->name)); + strlcpy(q40kbd_port->phys, "Q40", sizeof(q40kbd_port->phys)); + + serio_register_port(q40kbd_port); + printk(KERN_INFO "serio: Q40 kbd registered\n"); + + return 0; +} + +static int __devexit q40kbd_remove(struct platform_device *dev) +{ + serio_unregister_port(q40kbd_port); + + return 0; +} + +static struct platform_driver q40kbd_driver = { + .driver = { + .name = "q40kbd", + .owner = THIS_MODULE, + }, + .probe = q40kbd_probe, + .remove = __devexit_p(q40kbd_remove), +}; + +static int __init q40kbd_init(void) +{ + int error; + + if (!MACH_IS_Q40) + return -ENODEV; + + error = platform_driver_register(&q40kbd_driver); + if (error) + return error; + + q40kbd_device = platform_device_alloc("q40kbd", -1); + if (!q40kbd_device) + goto err_unregister_driver; + + error = platform_device_add(q40kbd_device); + if (error) + goto err_free_device; + + return 0; + + err_free_device: + platform_device_put(q40kbd_device); + err_unregister_driver: + platform_driver_unregister(&q40kbd_driver); + return error; +} + +static void __exit q40kbd_exit(void) +{ + platform_device_unregister(q40kbd_device); + platform_driver_unregister(&q40kbd_driver); +} + +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 0000000..7f36edd --- /dev/null +++ b/drivers/input/serio/rpckbd.c @@ -0,0 +1,157 @@ +/* + * 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 <linux/platform_device.h> + +#include <asm/irq.h> +#include <mach/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"); +MODULE_ALIAS("platform:kart"); + +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 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); + handled = IRQ_HANDLED; + } + return handled; +} + +static irqreturn_t rpckbd_tx(int irq, void *dev_id) +{ + 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 platform_device *dev) +{ + struct serio *serio; + + serio = kzalloc(sizeof(struct serio), GFP_KERNEL); + if (!serio) + return -ENOMEM; + + serio->id.type = SERIO_8042; + serio->write = rpckbd_write; + serio->open = rpckbd_open; + serio->close = rpckbd_close; + serio->dev.parent = &dev->dev; + strlcpy(serio->name, "RiscPC PS/2 kbd port", sizeof(serio->name)); + strlcpy(serio->phys, "rpckbd/serio0", sizeof(serio->phys)); + + platform_set_drvdata(dev, serio); + serio_register_port(serio); + return 0; +} + +static int __devexit rpckbd_remove(struct platform_device *dev) +{ + struct serio *serio = platform_get_drvdata(dev); + serio_unregister_port(serio); + return 0; +} + +static struct platform_driver rpckbd_driver = { + .probe = rpckbd_probe, + .remove = __devexit_p(rpckbd_remove), + .driver = { + .name = "kart", + .owner = THIS_MODULE, + }, +}; + +static int __init rpckbd_init(void) +{ + return platform_driver_register(&rpckbd_driver); +} + +static void __exit rpckbd_exit(void) +{ + platform_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 0000000..2ad8878 --- /dev/null +++ b/drivers/input/serio/sa1111ps2.c @@ -0,0 +1,356 @@ +/* + * 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/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 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); + + 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 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 __devinit 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 __devinit ps2_probe(struct sa1111_dev *dev) +{ + struct ps2if *ps2if; + struct serio *serio; + int ret; + + ps2if = kzalloc(sizeof(struct ps2if), GFP_KERNEL); + serio = kzalloc(sizeof(struct serio), GFP_KERNEL); + if (!ps2if || !serio) { + ret = -ENOMEM; + goto free; + } + + + 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 0000000..2f12d60 --- /dev/null +++ b/drivers/input/serio/serio.c @@ -0,0 +1,1050 @@ +/* + * 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/sched.h> +#include <linux/slab.h> +#include <linux/kthread.h> +#include <linux/mutex.h> +#include <linux/freezer.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_child_port); +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_mutex protects entire serio subsystem and is taken every time + * serio port or driver registrered or unregistered. + */ +static DEFINE_MUTEX(serio_mutex); + +static LIST_HEAD(serio_list); + +static struct bus_type serio_bus; + +static void serio_add_port(struct serio *serio); +static int serio_reconnect_port(struct serio *serio); +static void serio_disconnect_port(struct serio *serio); +static void serio_reconnect_chain(struct serio *serio); +static void serio_attach_driver(struct serio_driver *drv); + +static int serio_connect_driver(struct serio *serio, struct serio_driver *drv) +{ + int retval; + + mutex_lock(&serio->drv_mutex); + retval = drv->connect(serio, drv); + mutex_unlock(&serio->drv_mutex); + + return retval; +} + +static int serio_reconnect_driver(struct serio *serio) +{ + int retval = -1; + + mutex_lock(&serio->drv_mutex); + if (serio->drv && serio->drv->reconnect) + retval = serio->drv->reconnect(serio); + mutex_unlock(&serio->drv_mutex); + + return retval; +} + +static void serio_disconnect_driver(struct serio *serio) +{ + mutex_lock(&serio->drv_mutex); + if (serio->drv) + serio->drv->disconnect(serio); + mutex_unlock(&serio->drv_mutex); +} + +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 int serio_bind_driver(struct serio *serio, struct serio_driver *drv) +{ + int error; + + if (serio_match_port(drv->id_table, serio)) { + + serio->dev.driver = &drv->driver; + if (serio_connect_driver(serio, drv)) { + serio->dev.driver = NULL; + return -ENODEV; + } + + error = device_bind_driver(&serio->dev); + if (error) { + printk(KERN_WARNING + "serio: device_bind_driver() failed " + "for %s (%s) and %s, error: %d\n", + serio->phys, serio->name, + drv->description, error); + serio_disconnect_driver(serio); + serio->dev.driver = NULL; + return error; + } + } + return 0; +} + +static void serio_find_driver(struct serio *serio) +{ + int error; + + error = device_attach(&serio->dev); + if (error < 0) + printk(KERN_WARNING + "serio: device_attach() failed for %s (%s), error: %d\n", + serio->phys, serio->name, error); +} + + +/* + * Serio event processing. + */ + +enum serio_event_type { + SERIO_RESCAN_PORT, + SERIO_RECONNECT_PORT, + SERIO_RECONNECT_CHAIN, + SERIO_REGISTER_PORT, + SERIO_ATTACH_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 struct task_struct *serio_task; + +static int serio_queue_event(void *object, struct module *owner, + enum serio_event_type event_type) +{ + unsigned long flags; + struct serio_event *event; + int retval = 0; + + 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; + } + } + + event = kmalloc(sizeof(struct serio_event), GFP_ATOMIC); + if (!event) { + printk(KERN_ERR + "serio: Not enough memory to queue event %d\n", + event_type); + retval = -ENOMEM; + goto out; + } + + if (!try_module_get(owner)) { + printk(KERN_WARNING + "serio: Can't get module reference, dropping event %d\n", + event_type); + kfree(event); + retval = -EINVAL; + goto out; + } + + event->type = event_type; + event->object = object; + event->owner = owner; + + list_add_tail(&event->node, &serio_event_list); + wake_up(&serio_wait); + +out: + spin_unlock_irqrestore(&serio_event_lock, flags); + return retval; +} + +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_event(void) +{ + struct serio_event *event; + + mutex_lock(&serio_mutex); + + /* + * Note that we handle only one event here to give swsusp + * a chance to freeze kseriod thread. Serio events should + * be pretty rare so we are not concerned about taking + * performance hit. + */ + if ((event = serio_get_event())) { + + switch (event->type) { + case SERIO_REGISTER_PORT: + serio_add_port(event->object); + break; + + case SERIO_RECONNECT_PORT: + serio_reconnect_port(event->object); + break; + + case SERIO_RESCAN_PORT: + serio_disconnect_port(event->object); + serio_find_driver(event->object); + break; + + case SERIO_RECONNECT_CHAIN: + serio_reconnect_chain(event->object); + break; + + case SERIO_ATTACH_DRIVER: + serio_attach_driver(event->object); + break; + + default: + break; + } + + serio_remove_duplicate_events(event); + serio_free_event(event); + } + + mutex_unlock(&serio_mutex); +} + +/* + * Remove all events that have been submitted for a given + * object, be it serio port or driver. + */ +static void serio_remove_pending_events(void *object) +{ + 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 == object) { + 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) +{ + set_freezable(); + do { + serio_handle_event(); + wait_event_freezable(serio_wait, + kthread_should_stop() || !list_empty(&serio_event_list)); + } while (!kthread_should_stop()); + + printk(KERN_DEBUG "serio: kseriod exiting\n"); + return 0; +} + + +/* + * Serio port operations + */ + +static ssize_t serio_show_description(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct serio *serio = to_serio_port(dev); + return sprintf(buf, "%s\n", serio->name); +} + +static ssize_t serio_show_modalias(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct serio *serio = to_serio_port(dev); + + return sprintf(buf, "serio:ty%02Xpr%02Xid%02Xex%02X\n", + serio->id.type, serio->id.proto, serio->id.id, serio->id.extra); +} + +static ssize_t serio_show_id_type(struct device *dev, struct device_attribute *attr, 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, struct device_attribute *attr, 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, struct device_attribute *attr, 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, struct device_attribute *attr, char *buf) +{ + struct serio *serio = to_serio_port(dev); + return sprintf(buf, "%02x\n", serio->id.extra); +} + +static DEVICE_ATTR(type, S_IRUGO, serio_show_id_type, NULL); +static DEVICE_ATTR(proto, S_IRUGO, serio_show_id_proto, NULL); +static DEVICE_ATTR(id, S_IRUGO, serio_show_id_id, NULL); +static DEVICE_ATTR(extra, S_IRUGO, serio_show_id_extra, NULL); + +static struct attribute *serio_device_id_attrs[] = { + &dev_attr_type.attr, + &dev_attr_proto.attr, + &dev_attr_id.attr, + &dev_attr_extra.attr, + NULL +}; + +static struct attribute_group serio_id_attr_group = { + .name = "id", + .attrs = serio_device_id_attrs, +}; + +static ssize_t serio_rebind_driver(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct serio *serio = to_serio_port(dev); + struct device_driver *drv; + int error; + + error = mutex_lock_interruptible(&serio_mutex); + if (error) + return error; + + if (!strncmp(buf, "none", count)) { + serio_disconnect_port(serio); + } else if (!strncmp(buf, "reconnect", count)) { + serio_reconnect_chain(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); + error = serio_bind_driver(serio, to_serio_driver(drv)); + put_driver(drv); + } else { + error = -EINVAL; + } + + mutex_unlock(&serio_mutex); + + return error ? error : count; +} + +static ssize_t serio_show_bind_mode(struct device *dev, struct device_attribute *attr, 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, struct device_attribute *attr, 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(modalias, S_IRUGO, serio_show_modalias, 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); + + INIT_LIST_HEAD(&serio->node); + spin_lock_init(&serio->lock); + mutex_init(&serio->drv_mutex); + 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; + serio->depth = serio->parent->depth + 1; + } else + serio->depth = 0; + lockdep_set_subclass(&serio->lock, serio->depth); +} + +/* + * Complete serio port registration. + * Driver core will attempt to find appropriate driver for the port. + */ +static void serio_add_port(struct serio *serio) +{ + int error; + + 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); + error = device_add(&serio->dev); + if (error) + printk(KERN_ERR + "serio: device_add() failed for %s (%s), error: %d\n", + serio->phys, serio->name, error); + else { + serio->registered = 1; + error = sysfs_create_group(&serio->dev.kobj, &serio_id_attr_group); + if (error) + printk(KERN_ERR + "serio: sysfs_create_group() failed for %s (%s), error: %d\n", + serio->phys, serio->name, error); + } +} + +/* + * 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) { + sysfs_remove_group(&serio->dev.kobj, &serio_id_attr_group); + device_del(&serio->dev); + serio->registered = 0; + } + + list_del_init(&serio->node); + serio_remove_pending_events(serio); + put_device(&serio->dev); +} + +/* + * Reconnect serio port (re-initialize attached device). + * If reconnect fails (old device is no longer attached or + * there was no device to begin with) we do full rescan in + * hope of finding a driver for the port. + */ +static int serio_reconnect_port(struct serio *serio) +{ + int error = serio_reconnect_driver(serio); + + if (error) { + serio_disconnect_port(serio); + serio_find_driver(serio); + } + + return error; +} + +/* + * Reconnect serio port and all its children (re-initialize attached devices) + */ +static void serio_reconnect_chain(struct serio *serio) +{ + do { + if (serio_reconnect_port(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; + + device_release_driver(&s->dev); + serio_destroy_port(s); + } while ((s = parent) != serio); + } + + /* + * Ok, no children left, now disconnect this port + */ + device_release_driver(&serio->dev); +} + +void serio_rescan(struct serio *serio) +{ + serio_queue_event(serio, NULL, SERIO_RESCAN_PORT); +} + +void serio_reconnect(struct serio *serio) +{ + serio_queue_event(serio, NULL, SERIO_RECONNECT_CHAIN); +} + +/* + * 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) +{ + mutex_lock(&serio_mutex); + serio_disconnect_port(serio); + serio_destroy_port(serio); + mutex_unlock(&serio_mutex); +} + +/* + * Safely unregisters child port if one is present. + */ +void serio_unregister_child_port(struct serio *serio) +{ + mutex_lock(&serio_mutex); + if (serio->child) { + serio_disconnect_port(serio->child); + serio_destroy_port(serio->child); + } + mutex_unlock(&serio_mutex); +} + + +/* + * 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 serio_connect_driver(serio, drv); +} + +static int serio_driver_remove(struct device *dev) +{ + struct serio *serio = to_serio_port(dev); + + serio_disconnect_driver(serio); + return 0; +} + +static void serio_cleanup(struct serio *serio) +{ + mutex_lock(&serio->drv_mutex); + if (serio->drv && serio->drv->cleanup) + serio->drv->cleanup(serio); + mutex_unlock(&serio->drv_mutex); +} + +static void serio_shutdown(struct device *dev) +{ + struct serio *serio = to_serio_port(dev); + + serio_cleanup(serio); +} + +static void serio_attach_driver(struct serio_driver *drv) +{ + int error; + + error = driver_attach(&drv->driver); + if (error) + printk(KERN_WARNING + "serio: driver_attach() failed for %s with error %d\n", + drv->driver.name, error); +} + +int __serio_register_driver(struct serio_driver *drv, struct module *owner, const char *mod_name) +{ + int manual_bind = drv->manual_bind; + int error; + + drv->driver.bus = &serio_bus; + drv->driver.owner = owner; + drv->driver.mod_name = mod_name; + + /* + * Temporarily disable automatic binding because probing + * takes long time and we are better off doing it in kseriod + */ + drv->manual_bind = 1; + + error = driver_register(&drv->driver); + if (error) { + printk(KERN_ERR + "serio: driver_register() failed for %s, error: %d\n", + drv->driver.name, error); + return error; + } + + /* + * Restore original bind mode and let kseriod bind the + * driver to free ports + */ + if (!manual_bind) { + drv->manual_bind = 0; + error = serio_queue_event(drv, NULL, SERIO_ATTACH_DRIVER); + if (error) { + driver_unregister(&drv->driver); + return error; + } + } + + return 0; +} + +void serio_unregister_driver(struct serio_driver *drv) +{ + struct serio *serio; + + mutex_lock(&serio_mutex); + + drv->manual_bind = 1; /* so serio_find_driver ignores it */ + serio_remove_pending_events(drv); + +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); + mutex_unlock(&serio_mutex); +} + +static void serio_set_drv(struct serio *serio, struct serio_driver *drv) +{ + serio_pause_rx(serio); + serio->drv = drv; + serio_continue_rx(serio); +} + +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 SERIO_ADD_UEVENT_VAR(fmt, val...) \ + do { \ + int err = add_uevent_var(env, fmt, val); \ + if (err) \ + return err; \ + } while (0) + +static int serio_uevent(struct device *dev, struct kobj_uevent_env *env) +{ + struct serio *serio; + + if (!dev) + return -ENODEV; + + serio = to_serio_port(dev); + + SERIO_ADD_UEVENT_VAR("SERIO_TYPE=%02x", serio->id.type); + SERIO_ADD_UEVENT_VAR("SERIO_PROTO=%02x", serio->id.proto); + SERIO_ADD_UEVENT_VAR("SERIO_ID=%02x", serio->id.id); + SERIO_ADD_UEVENT_VAR("SERIO_EXTRA=%02x", serio->id.extra); + SERIO_ADD_UEVENT_VAR("MODALIAS=serio:ty%02Xpr%02Xid%02Xex%02X", + serio->id.type, serio->id.proto, serio->id.id, serio->id.extra); + + return 0; +} +#undef SERIO_ADD_UEVENT_VAR + +#else + +static int serio_uevent(struct device *dev, struct kobj_uevent_env *env) +{ + return -ENODEV; +} + +#endif /* CONFIG_HOTPLUG */ + +#ifdef CONFIG_PM +static int serio_suspend(struct device *dev, pm_message_t state) +{ + if (dev->power.power_state.event != state.event) { + if (state.event == PM_EVENT_SUSPEND) + serio_cleanup(to_serio_port(dev)); + + dev->power.power_state = state; + } + + return 0; +} + +static int serio_resume(struct device *dev) +{ + /* + * Driver reconnect can take a while, so better let kseriod + * deal with it. + */ + if (dev->power.power_state.event != PM_EVENT_ON) { + dev->power.power_state = PMSG_ON; + serio_queue_event(to_serio_port(dev), NULL, + SERIO_RECONNECT_PORT); + } + + return 0; +} +#endif /* CONFIG_PM */ + +/* called from serio_driver->connect/disconnect methods under serio_mutex */ +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_mutex */ +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) +{ + 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); + } else if (!dfl && serio->registered) { + serio_rescan(serio); + ret = IRQ_HANDLED; + } + + spin_unlock_irqrestore(&serio->lock, flags); + + return ret; +} + +static struct bus_type serio_bus = { + .name = "serio", + .dev_attrs = serio_device_attrs, + .drv_attrs = serio_driver_attrs, + .match = serio_bus_match, + .uevent = serio_uevent, + .probe = serio_driver_probe, + .remove = serio_driver_remove, + .shutdown = serio_shutdown, +#ifdef CONFIG_PM + .suspend = serio_suspend, + .resume = serio_resume, +#endif +}; + +static int __init serio_init(void) +{ + int error; + + error = bus_register(&serio_bus); + if (error) { + printk(KERN_ERR "serio: failed to register serio bus, error: %d\n", error); + return error; + } + + serio_task = kthread_run(serio_thread, NULL, "kseriod"); + if (IS_ERR(serio_task)) { + bus_unregister(&serio_bus); + error = PTR_ERR(serio_task); + printk(KERN_ERR "serio: Failed to start kseriod, error: %d\n", error); + return error; + } + + return 0; +} + +static void __exit serio_exit(void) +{ + bus_unregister(&serio_bus); + kthread_stop(serio_task); +} + +subsys_initcall(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 0000000..06bbd0e --- /dev/null +++ b/drivers/input/serio/serio_raw.c @@ -0,0 +1,410 @@ +/* + * 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/smp_lock.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/miscdevice.h> +#include <linux/wait.h> +#include <linux/mutex.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 DEFINE_MUTEX(serio_raw_mutex); +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; + + lock_kernel(); + retval = mutex_lock_interruptible(&serio_raw_mutex); + if (retval) + goto out_bkl; + + if (!(serio_raw = serio_raw_locate(iminor(inode)))) { + retval = -ENODEV; + goto out; + } + + if (!serio_raw->serio) { + retval = -ENODEV; + goto out; + } + + if (!(list = kzalloc(sizeof(struct serio_raw_list), GFP_KERNEL))) { + retval = -ENOMEM; + goto out; + } + + list->serio_raw = serio_raw; + file->private_data = list; + + serio_raw->refcnt++; + list_add_tail(&list->node, &serio_raw->list); + +out: + mutex_unlock(&serio_raw_mutex); +out_bkl: + unlock_kernel(); + 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; + + mutex_lock(&serio_raw_mutex); + + serio_raw_cleanup(serio_raw); + + mutex_unlock(&serio_raw_mutex); + 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 uninitialized_var(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 = mutex_lock_interruptible(&serio_raw_mutex); + 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: + mutex_unlock(&serio_raw_mutex); + 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 const 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 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 = kzalloc(sizeof(struct serio_raw), GFP_KERNEL))) { + printk(KERN_ERR "serio_raw.c: can't allocate memory for a device\n"); + return -ENOMEM; + } + + mutex_lock(&serio_raw_mutex); + + 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.parent = &serio->dev; + 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: + mutex_unlock(&serio_raw_mutex); + 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; + + mutex_lock(&serio_raw_mutex); + + 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); + + mutex_unlock(&serio_raw_mutex); +} + +static struct serio_device_id serio_raw_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, + }, + { 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) +{ + return serio_register_driver(&serio_raw_drv); +} + +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 0000000..b9694b6 --- /dev/null +++ b/drivers/input/serio/serport.c @@ -0,0 +1,250 @@ +/* + * 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 +#define SERPORT_ACTIVE 2 +#define SERPORT_DEAD 3 + +struct serport { + struct tty_struct *tty; + wait_queue_head_t wait; + struct serio *serio; + struct serio_device_id id; + spinlock_t lock; + 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->ops->write(serport->tty, &data, 1) != 1); +} + +static int serport_serio_open(struct serio *serio) +{ + struct serport *serport = serio->port_data; + unsigned long flags; + + spin_lock_irqsave(&serport->lock, flags); + set_bit(SERPORT_ACTIVE, &serport->flags); + spin_unlock_irqrestore(&serport->lock, flags); + + return 0; +} + + +static void serport_serio_close(struct serio *serio) +{ + struct serport *serport = serio->port_data; + unsigned long flags; + + spin_lock_irqsave(&serport->lock, flags); + clear_bit(SERPORT_ACTIVE, &serport->flags); + set_bit(SERPORT_DEAD, &serport->flags); + spin_unlock_irqrestore(&serport->lock, flags); + + 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; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + serport = kzalloc(sizeof(struct serport), GFP_KERNEL); + if (!serport) + return -ENOMEM; + + serport->tty = tty; + spin_lock_init(&serport->lock); + init_waitqueue_head(&serport->wait); + + tty->disc_data = serport; + tty->receive_room = 256; + set_bit(TTY_DO_WRITE_WAKEUP, &tty->flags); + + 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. + */ + +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; + unsigned long flags; + int i; + + spin_lock_irqsave(&serport->lock, flags); + + if (!test_bit(SERPORT_ACTIVE, &serport->flags)) + goto out; + + for (i = 0; i < count; i++) + serio_interrupt(serport->serio, cp[i], 0); + +out: + spin_unlock_irqrestore(&serport->lock, flags); +} + +/* + * 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; + struct serio *serio; + char name[64]; + + if (test_and_set_bit(SERPORT_BUSY, &serport->flags)) + return -EBUSY; + + serport->serio = serio = kzalloc(sizeof(struct serio), GFP_KERNEL); + if (!serio) + return -ENOMEM; + + strlcpy(serio->name, "Serial port", sizeof(serio->name)); + snprintf(serio->phys, sizeof(serio->phys), "%s/serio0", tty_name(tty, name)); + serio->id = serport->id; + serio->id.type = SERIO_RS232; + serio->write = serport_serio_write; + serio->open = serport_serio_open; + serio->close = serport_serio_close; + serio->port_data = serport; + + serio_register_port(serport->serio); + printk(KERN_INFO "serio: Serial port %s\n", tty_name(tty, name)); + + wait_event_interruptible(serport->wait, test_bit(SERPORT_DEAD, &serport->flags)); + serio_unregister_port(serport->serio); + serport->serio = NULL; + + clear_bit(SERPORT_DEAD, &serport->flags); + 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; + unsigned long type; + + if (cmd == SPIOCSTYPE) { + if (get_user(type, (unsigned long __user *) arg)) + return -EFAULT; + + serport->id.proto = type & 0x000000ff; + serport->id.id = (type & 0x0000ff00) >> 8; + serport->id.extra = (type & 0x00ff0000) >> 16; + + return 0; + } + + return -EINVAL; +} + +static void serport_ldisc_write_wakeup(struct tty_struct * tty) +{ + struct serport *serport = (struct serport *) tty->disc_data; + unsigned long flags; + + spin_lock_irqsave(&serport->lock, flags); + if (test_bit(SERPORT_ACTIVE, &serport->flags)) + serio_drv_write_wakeup(serport->serio); + spin_unlock_irqrestore(&serport->lock, flags); +} + +/* + * The line discipline structure. + */ + +static struct tty_ldisc_ops 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, + .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_unregister_ldisc(N_MOUSE); +} + +module_init(serport_init); +module_exit(serport_exit); diff --git a/drivers/input/serio/xilinx_ps2.c b/drivers/input/serio/xilinx_ps2.c new file mode 100644 index 0000000..7650078 --- /dev/null +++ b/drivers/input/serio/xilinx_ps2.c @@ -0,0 +1,380 @@ +/* + * Xilinx XPS PS/2 device driver + * + * (c) 2005 MontaVista Software, Inc. + * (c) 2008 Xilinx, Inc. + * + * 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. + * + * 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 <linux/module.h> +#include <linux/serio.h> +#include <linux/interrupt.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/list.h> +#include <linux/io.h> + +#include <linux/of_device.h> +#include <linux/of_platform.h> + +#define DRIVER_NAME "xilinx_ps2" + +/* Register offsets for the xps2 device */ +#define XPS2_SRST_OFFSET 0x00000000 /* Software Reset register */ +#define XPS2_STATUS_OFFSET 0x00000004 /* Status register */ +#define XPS2_RX_DATA_OFFSET 0x00000008 /* Receive Data register */ +#define XPS2_TX_DATA_OFFSET 0x0000000C /* Transmit Data register */ +#define XPS2_GIER_OFFSET 0x0000002C /* Global Interrupt Enable reg */ +#define XPS2_IPISR_OFFSET 0x00000030 /* Interrupt Status register */ +#define XPS2_IPIER_OFFSET 0x00000038 /* Interrupt Enable register */ + +/* Reset Register Bit Definitions */ +#define XPS2_SRST_RESET 0x0000000A /* Software Reset */ + +/* Status Register Bit Positions */ +#define XPS2_STATUS_RX_FULL 0x00000001 /* Receive Full */ +#define XPS2_STATUS_TX_FULL 0x00000002 /* Transmit Full */ + +/* Bit definitions for ISR/IER registers. Both the registers have the same bit + * definitions and are only defined once. */ +#define XPS2_IPIXR_WDT_TOUT 0x00000001 /* Watchdog Timeout Interrupt */ +#define XPS2_IPIXR_TX_NOACK 0x00000002 /* Transmit No ACK Interrupt */ +#define XPS2_IPIXR_TX_ACK 0x00000004 /* Transmit ACK (Data) Interrupt */ +#define XPS2_IPIXR_RX_OVF 0x00000008 /* Receive Overflow Interrupt */ +#define XPS2_IPIXR_RX_ERR 0x00000010 /* Receive Error Interrupt */ +#define XPS2_IPIXR_RX_FULL 0x00000020 /* Receive Data Interrupt */ + +/* Mask for all the Transmit Interrupts */ +#define XPS2_IPIXR_TX_ALL (XPS2_IPIXR_TX_NOACK | XPS2_IPIXR_TX_ACK) + +/* Mask for all the Receive Interrupts */ +#define XPS2_IPIXR_RX_ALL (XPS2_IPIXR_RX_OVF | XPS2_IPIXR_RX_ERR | \ + XPS2_IPIXR_RX_FULL) + +/* Mask for all the Interrupts */ +#define XPS2_IPIXR_ALL (XPS2_IPIXR_TX_ALL | XPS2_IPIXR_RX_ALL | \ + XPS2_IPIXR_WDT_TOUT) + +/* Global Interrupt Enable mask */ +#define XPS2_GIER_GIE_MASK 0x80000000 + +struct xps2data { + int irq; + u32 phys_addr; + u32 remap_size; + spinlock_t lock; + u8 rxb; /* Rx buffer */ + void __iomem *base_address; /* virt. address of control registers */ + unsigned int dfl; + struct serio serio; /* serio */ +}; + +/************************************/ +/* XPS PS/2 data transmission calls */ +/************************************/ + +/* + * xps2_recv() will attempt to receive a byte of data from the PS/2 port. + */ +static int xps2_recv(struct xps2data *drvdata, u8 *byte) +{ + u32 sr; + int status = -1; + + /* If there is data available in the PS/2 receiver, read it */ + sr = in_be32(drvdata->base_address + XPS2_STATUS_OFFSET); + if (sr & XPS2_STATUS_RX_FULL) { + *byte = in_be32(drvdata->base_address + XPS2_RX_DATA_OFFSET); + status = 0; + } + + return status; +} + +/*********************/ +/* Interrupt handler */ +/*********************/ +static irqreturn_t xps2_interrupt(int irq, void *dev_id) +{ + struct xps2data *drvdata = dev_id; + u32 intr_sr; + u8 c; + int status; + + /* Get the PS/2 interrupts and clear them */ + intr_sr = in_be32(drvdata->base_address + XPS2_IPISR_OFFSET); + out_be32(drvdata->base_address + XPS2_IPISR_OFFSET, intr_sr); + + /* Check which interrupt is active */ + if (intr_sr & XPS2_IPIXR_RX_OVF) + printk(KERN_WARNING "%s: receive overrun error\n", + drvdata->serio.name); + + if (intr_sr & XPS2_IPIXR_RX_ERR) + drvdata->dfl |= SERIO_PARITY; + + if (intr_sr & (XPS2_IPIXR_TX_NOACK | XPS2_IPIXR_WDT_TOUT)) + drvdata->dfl |= SERIO_TIMEOUT; + + if (intr_sr & XPS2_IPIXR_RX_FULL) { + status = xps2_recv(drvdata, &drvdata->rxb); + + /* Error, if a byte is not received */ + if (status) { + printk(KERN_ERR + "%s: wrong rcvd byte count (%d)\n", + drvdata->serio.name, status); + } else { + c = drvdata->rxb; + serio_interrupt(&drvdata->serio, c, drvdata->dfl); + drvdata->dfl = 0; + } + } + + if (intr_sr & XPS2_IPIXR_TX_ACK) + drvdata->dfl = 0; + + return IRQ_HANDLED; +} + +/*******************/ +/* serio callbacks */ +/*******************/ + +/* + * sxps2_write() sends a byte out through the PS/2 interface. + */ +static int sxps2_write(struct serio *pserio, unsigned char c) +{ + struct xps2data *drvdata = pserio->port_data; + unsigned long flags; + u32 sr; + int status = -1; + + spin_lock_irqsave(&drvdata->lock, flags); + + /* If the PS/2 transmitter is empty send a byte of data */ + sr = in_be32(drvdata->base_address + XPS2_STATUS_OFFSET); + if (!(sr & XPS2_STATUS_TX_FULL)) { + out_be32(drvdata->base_address + XPS2_TX_DATA_OFFSET, c); + status = 0; + } + + spin_unlock_irqrestore(&drvdata->lock, flags); + + return status; +} + +/* + * sxps2_open() is called when a port is open by the higher layer. + */ +static int sxps2_open(struct serio *pserio) +{ + struct xps2data *drvdata = pserio->port_data; + int retval; + + retval = request_irq(drvdata->irq, &xps2_interrupt, 0, + DRIVER_NAME, drvdata); + if (retval) { + printk(KERN_ERR + "%s: Couldn't allocate interrupt %d\n", + drvdata->serio.name, drvdata->irq); + return retval; + } + + /* start reception by enabling the interrupts */ + out_be32(drvdata->base_address + XPS2_GIER_OFFSET, XPS2_GIER_GIE_MASK); + out_be32(drvdata->base_address + XPS2_IPIER_OFFSET, XPS2_IPIXR_RX_ALL); + (void)xps2_recv(drvdata, &drvdata->rxb); + + return 0; /* success */ +} + +/* + * sxps2_close() frees the interrupt. + */ +static void sxps2_close(struct serio *pserio) +{ + struct xps2data *drvdata = pserio->port_data; + + /* Disable the PS2 interrupts */ + out_be32(drvdata->base_address + XPS2_GIER_OFFSET, 0x00); + out_be32(drvdata->base_address + XPS2_IPIER_OFFSET, 0x00); + free_irq(drvdata->irq, drvdata); +} + +/*********************/ +/* Device setup code */ +/*********************/ + +static int xps2_setup(struct device *dev, struct resource *regs_res, + struct resource *irq_res) +{ + struct xps2data *drvdata; + struct serio *serio; + unsigned long remap_size; + int retval; + + if (!dev) + return -EINVAL; + + if (!regs_res || !irq_res) { + dev_err(dev, "IO resource(s) not found\n"); + return -EINVAL; + } + + drvdata = kzalloc(sizeof(struct xps2data), GFP_KERNEL); + if (!drvdata) { + dev_err(dev, "Couldn't allocate device private record\n"); + return -ENOMEM; + } + + dev_set_drvdata(dev, drvdata); + + spin_lock_init(&drvdata->lock); + drvdata->irq = irq_res->start; + + remap_size = regs_res->end - regs_res->start + 1; + if (!request_mem_region(regs_res->start, remap_size, DRIVER_NAME)) { + dev_err(dev, "Couldn't lock memory region at 0x%08X\n", + (unsigned int)regs_res->start); + retval = -EBUSY; + goto failed1; + } + + /* Fill in configuration data and add them to the list */ + drvdata->phys_addr = regs_res->start; + drvdata->remap_size = remap_size; + drvdata->base_address = ioremap(regs_res->start, remap_size); + if (drvdata->base_address == NULL) { + dev_err(dev, "Couldn't ioremap memory at 0x%08X\n", + (unsigned int)regs_res->start); + retval = -EFAULT; + goto failed2; + } + + /* Disable all the interrupts, just in case */ + out_be32(drvdata->base_address + XPS2_IPIER_OFFSET, 0); + + /* Reset the PS2 device and abort any current transaction, to make sure + * we have the PS2 in a good state */ + out_be32(drvdata->base_address + XPS2_SRST_OFFSET, XPS2_SRST_RESET); + + dev_info(dev, "Xilinx PS2 at 0x%08X mapped to 0x%p, irq=%d\n", + drvdata->phys_addr, drvdata->base_address, drvdata->irq); + + serio = &drvdata->serio; + serio->id.type = SERIO_8042; + serio->write = sxps2_write; + serio->open = sxps2_open; + serio->close = sxps2_close; + serio->port_data = drvdata; + serio->dev.parent = dev; + snprintf(serio->name, sizeof(serio->name), + "Xilinx XPS PS/2 at %08X", drvdata->phys_addr); + snprintf(serio->phys, sizeof(serio->phys), + "xilinxps2/serio at %08X", drvdata->phys_addr); + serio_register_port(serio); + + return 0; /* success */ + +failed2: + release_mem_region(regs_res->start, remap_size); +failed1: + kfree(drvdata); + dev_set_drvdata(dev, NULL); + + return retval; +} + +/***************************/ +/* OF Platform Bus Support */ +/***************************/ + +static int __devinit xps2_of_probe(struct of_device *ofdev, const struct + of_device_id * match) +{ + struct resource r_irq; /* Interrupt resources */ + struct resource r_mem; /* IO mem resources */ + int rc = 0; + + printk(KERN_INFO "Device Tree Probing \'%s\'\n", + ofdev->node->name); + + /* Get iospace for the device */ + rc = of_address_to_resource(ofdev->node, 0, &r_mem); + if (rc) { + dev_err(&ofdev->dev, "invalid address\n"); + return rc; + } + + /* Get IRQ for the device */ + rc = of_irq_to_resource(ofdev->node, 0, &r_irq); + if (rc == NO_IRQ) { + dev_err(&ofdev->dev, "no IRQ found\n"); + return rc; + } + + return xps2_setup(&ofdev->dev, &r_mem, &r_irq); +} + +static int __devexit xps2_of_remove(struct of_device *of_dev) +{ + struct device *dev = &of_dev->dev; + struct xps2data *drvdata; + + if (!dev) + return -EINVAL; + + drvdata = dev_get_drvdata(dev); + + serio_unregister_port(&drvdata->serio); + iounmap(drvdata->base_address); + release_mem_region(drvdata->phys_addr, drvdata->remap_size); + kfree(drvdata); + + dev_set_drvdata(dev, NULL); + + return 0; /* success */ +} + +/* Match table for of_platform binding */ +static struct of_device_id xps2_of_match[] __devinitdata = { + { .compatible = "xlnx,xps-ps2-1.00.a", }, + { /* end of list */ }, +}; +MODULE_DEVICE_TABLE(of, xps2_of_match); + +static struct of_platform_driver xps2_of_driver = { + .name = DRIVER_NAME, + .match_table = xps2_of_match, + .probe = xps2_of_probe, + .remove = __devexit_p(xps2_of_remove), +}; + +static int __init xps2_init(void) +{ + return of_register_platform_driver(&xps2_of_driver); +} + +static void __exit xps2_cleanup(void) +{ + of_unregister_platform_driver(&xps2_of_driver); +} + +module_init(xps2_init); +module_exit(xps2_cleanup); + +MODULE_AUTHOR("Xilinx, Inc."); +MODULE_DESCRIPTION("Xilinx XPS PS/2 driver"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/input/tablet/Kconfig b/drivers/input/tablet/Kconfig new file mode 100644 index 0000000..effb49e --- /dev/null +++ b/drivers/input/tablet/Kconfig @@ -0,0 +1,78 @@ +# +# Tablet driver configuration +# +menuconfig INPUT_TABLET + bool "Tablets" + help + Say Y here, and a list of supported tablets will be displayed. + This option doesn't affect the kernel. + + If unsure, say Y. + +if INPUT_TABLET + +config TABLET_USB_ACECAD + tristate "Acecad Flair tablet support (USB)" + depends on USB_ARCH_HAS_HCD + select USB + help + Say Y here if you want to use the USB version of the Acecad Flair + tablet. Make sure to say Y to "Mouse support" + (CONFIG_INPUT_MOUSEDEV) and/or "Event interface support" + (CONFIG_INPUT_EVDEV) as well. + + To compile this driver as a module, choose M here: the + module will be called acecad. + +config TABLET_USB_AIPTEK + tristate "Aiptek 6000U/8000U and Genius G_PEN tablet support (USB)" + depends on USB_ARCH_HAS_HCD + select USB + help + Say Y here if you want to use the USB version of the Aiptek 6000U, + Aiptek 8000U or Genius G-PEN 560 tablet. Make sure to say Y to + "Mouse support" (CONFIG_INPUT_MOUSEDEV) and/or "Event interface + support" (CONFIG_INPUT_EVDEV) as well. + + To compile this driver as a module, choose M here: the + module will be called aiptek. + +config TABLET_USB_GTCO + tristate "GTCO CalComp/InterWrite USB Support" + depends on USB && INPUT + help + Say Y here if you want to use the USB version of the GTCO + CalComp/InterWrite Tablet. Make sure to say Y to "Mouse support" + (CONFIG_INPUT_MOUSEDEV) and/or "Event interface support" + (CONFIG_INPUT_EVDEV) as well. + + To compile this driver as a module, choose M here: the + module will be called gtco. + +config TABLET_USB_KBTAB + tristate "KB Gear JamStudio tablet support (USB)" + depends on USB_ARCH_HAS_HCD + select USB + help + Say Y here if you want to use the USB version of the KB Gear + JamStudio tablet. Make sure to say Y to "Mouse support" + (CONFIG_INPUT_MOUSEDEV) and/or "Event interface support" + (CONFIG_INPUT_EVDEV) as well. + + To compile this driver as a module, choose M here: the + module will be called kbtab. + +config TABLET_USB_WACOM + tristate "Wacom Intuos/Graphire tablet support (USB)" + depends on USB_ARCH_HAS_HCD + select USB + help + Say Y here if you want to use the USB version of the Wacom Intuos + or Graphire tablet. Make sure to say Y to "Mouse support" + (CONFIG_INPUT_MOUSEDEV) and/or "Event interface support" + (CONFIG_INPUT_EVDEV) as well. + + To compile this driver as a module, choose M here: the + module will be called wacom. + +endif diff --git a/drivers/input/tablet/Makefile b/drivers/input/tablet/Makefile new file mode 100644 index 0000000..ce8b9a9 --- /dev/null +++ b/drivers/input/tablet/Makefile @@ -0,0 +1,12 @@ +# +# Makefile for the tablet drivers +# + +# Multipart objects. +wacom-objs := wacom_wac.o wacom_sys.o + +obj-$(CONFIG_TABLET_USB_ACECAD) += acecad.o +obj-$(CONFIG_TABLET_USB_AIPTEK) += aiptek.o +obj-$(CONFIG_TABLET_USB_GTCO) += gtco.o +obj-$(CONFIG_TABLET_USB_KBTAB) += kbtab.o +obj-$(CONFIG_TABLET_USB_WACOM) += wacom.o diff --git a/drivers/input/tablet/acecad.c b/drivers/input/tablet/acecad.c new file mode 100644 index 0000000..670c61c --- /dev/null +++ b/drivers/input/tablet/acecad.c @@ -0,0 +1,294 @@ +/* + * Copyright (c) 2001-2005 Edouard TISSERANT <edouard.tisserant@wanadoo.fr> + * Copyright (c) 2004-2005 Stephane VOLTZ <svoltz@numericable.fr> + * + * USB Acecad "Acecad Flair" tablet support + * + * Changelog: + * v3.2 - Added sysfs support + */ + +/* + * 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/slab.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/usb/input.h> + +/* + * Version Information + */ +#define DRIVER_VERSION "v3.2" +#define DRIVER_DESC "USB Acecad Flair tablet driver" +#define DRIVER_LICENSE "GPL" +#define DRIVER_AUTHOR "Edouard TISSERANT <edouard.tisserant@wanadoo.fr>" + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE(DRIVER_LICENSE); + +#define USB_VENDOR_ID_ACECAD 0x0460 +#define USB_DEVICE_ID_FLAIR 0x0004 +#define USB_DEVICE_ID_302 0x0008 + +struct usb_acecad { + char name[128]; + char phys[64]; + struct usb_device *usbdev; + struct input_dev *input; + struct urb *irq; + + unsigned char *data; + dma_addr_t data_dma; +}; + +static void usb_acecad_irq(struct urb *urb) +{ + struct usb_acecad *acecad = urb->context; + unsigned char *data = acecad->data; + struct input_dev *dev = acecad->input; + int prox, 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", __func__, urb->status); + return; + default: + dbg("%s - nonzero urb status received: %d", __func__, urb->status); + goto resubmit; + } + + prox = (data[0] & 0x04) >> 2; + input_report_key(dev, BTN_TOOL_PEN, prox); + + if (prox) { + int x = data[1] | (data[2] << 8); + int y = data[3] | (data[4] << 8); + /* Pressure should compute the same way for flair and 302 */ + int pressure = data[5] | (data[6] << 8); + int touch = data[0] & 0x01; + int stylus = (data[0] & 0x10) >> 4; + int stylus2 = (data[0] & 0x20) >> 5; + input_report_abs(dev, ABS_X, x); + input_report_abs(dev, ABS_Y, y); + input_report_abs(dev, ABS_PRESSURE, pressure); + input_report_key(dev, BTN_TOUCH, touch); + input_report_key(dev, BTN_STYLUS, stylus); + input_report_key(dev, BTN_STYLUS2, stylus2); + } + + /* event termination */ + input_sync(dev); + +resubmit: + status = usb_submit_urb(urb, GFP_ATOMIC); + if (status) + err("can't resubmit intr, %s-%s/input0, status %d", + acecad->usbdev->bus->bus_name, acecad->usbdev->devpath, status); +} + +static int usb_acecad_open(struct input_dev *dev) +{ + struct usb_acecad *acecad = input_get_drvdata(dev); + + acecad->irq->dev = acecad->usbdev; + if (usb_submit_urb(acecad->irq, GFP_KERNEL)) + return -EIO; + + return 0; +} + +static void usb_acecad_close(struct input_dev *dev) +{ + struct usb_acecad *acecad = input_get_drvdata(dev); + + usb_kill_urb(acecad->irq); +} + +static int usb_acecad_probe(struct usb_interface *intf, const struct usb_device_id *id) +{ + struct usb_device *dev = interface_to_usbdev(intf); + struct usb_host_interface *interface = intf->cur_altsetting; + struct usb_endpoint_descriptor *endpoint; + struct usb_acecad *acecad; + struct input_dev *input_dev; + int pipe, maxp; + int err = -ENOMEM; + + if (interface->desc.bNumEndpoints != 1) + return -ENODEV; + + endpoint = &interface->endpoint[0].desc; + + if (!usb_endpoint_is_int_in(endpoint)) + return -ENODEV; + + pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress); + maxp = usb_maxpacket(dev, pipe, usb_pipeout(pipe)); + + acecad = kzalloc(sizeof(struct usb_acecad), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!acecad || !input_dev) { + err = -ENOMEM; + goto fail1; + } + + acecad->data = usb_buffer_alloc(dev, 8, GFP_KERNEL, &acecad->data_dma); + if (!acecad->data) { + err= -ENOMEM; + goto fail1; + } + + acecad->irq = usb_alloc_urb(0, GFP_KERNEL); + if (!acecad->irq) { + err = -ENOMEM; + goto fail2; + } + + acecad->usbdev = dev; + acecad->input = input_dev; + + if (dev->manufacturer) + strlcpy(acecad->name, dev->manufacturer, sizeof(acecad->name)); + + if (dev->product) { + if (dev->manufacturer) + strlcat(acecad->name, " ", sizeof(acecad->name)); + strlcat(acecad->name, dev->product, sizeof(acecad->name)); + } + + usb_make_path(dev, acecad->phys, sizeof(acecad->phys)); + strlcat(acecad->phys, "/input0", sizeof(acecad->phys)); + + input_dev->name = acecad->name; + input_dev->phys = acecad->phys; + usb_to_input_id(dev, &input_dev->id); + input_dev->dev.parent = &intf->dev; + + input_set_drvdata(input_dev, acecad); + + input_dev->open = usb_acecad_open; + input_dev->close = usb_acecad_close; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + input_dev->absbit[0] = BIT_MASK(ABS_X) | BIT_MASK(ABS_Y) | + BIT_MASK(ABS_PRESSURE); + input_dev->keybit[BIT_WORD(BTN_LEFT)] = BIT_MASK(BTN_LEFT) | + BIT_MASK(BTN_RIGHT) | BIT_MASK(BTN_MIDDLE); + input_dev->keybit[BIT_WORD(BTN_DIGI)] = BIT_MASK(BTN_TOOL_PEN) | + BIT_MASK(BTN_TOUCH) | BIT_MASK(BTN_STYLUS) | + BIT_MASK(BTN_STYLUS2); + + switch (id->driver_info) { + case 0: + input_dev->absmax[ABS_X] = 5000; + input_dev->absmax[ABS_Y] = 3750; + input_dev->absmax[ABS_PRESSURE] = 512; + if (!strlen(acecad->name)) + snprintf(acecad->name, sizeof(acecad->name), + "USB Acecad Flair Tablet %04x:%04x", + le16_to_cpu(dev->descriptor.idVendor), + le16_to_cpu(dev->descriptor.idProduct)); + break; + case 1: + input_dev->absmax[ABS_X] = 3000; + input_dev->absmax[ABS_Y] = 2250; + input_dev->absmax[ABS_PRESSURE] = 1024; + if (!strlen(acecad->name)) + snprintf(acecad->name, sizeof(acecad->name), + "USB Acecad 302 Tablet %04x:%04x", + le16_to_cpu(dev->descriptor.idVendor), + le16_to_cpu(dev->descriptor.idProduct)); + break; + } + + input_dev->absfuzz[ABS_X] = 4; + input_dev->absfuzz[ABS_Y] = 4; + + usb_fill_int_urb(acecad->irq, dev, pipe, + acecad->data, maxp > 8 ? 8 : maxp, + usb_acecad_irq, acecad, endpoint->bInterval); + acecad->irq->transfer_dma = acecad->data_dma; + acecad->irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + + err = input_register_device(acecad->input); + if (err) + goto fail2; + + usb_set_intfdata(intf, acecad); + + return 0; + + fail2: usb_buffer_free(dev, 8, acecad->data, acecad->data_dma); + fail1: input_free_device(input_dev); + kfree(acecad); + return err; +} + +static void usb_acecad_disconnect(struct usb_interface *intf) +{ + struct usb_acecad *acecad = usb_get_intfdata(intf); + + usb_set_intfdata(intf, NULL); + if (acecad) { + usb_kill_urb(acecad->irq); + input_unregister_device(acecad->input); + usb_free_urb(acecad->irq); + usb_buffer_free(interface_to_usbdev(intf), 10, acecad->data, acecad->data_dma); + kfree(acecad); + } +} + +static struct usb_device_id usb_acecad_id_table [] = { + { USB_DEVICE(USB_VENDOR_ID_ACECAD, USB_DEVICE_ID_FLAIR), .driver_info = 0 }, + { USB_DEVICE(USB_VENDOR_ID_ACECAD, USB_DEVICE_ID_302), .driver_info = 1 }, + { } +}; + +MODULE_DEVICE_TABLE(usb, usb_acecad_id_table); + +static struct usb_driver usb_acecad_driver = { + .name = "usb_acecad", + .probe = usb_acecad_probe, + .disconnect = usb_acecad_disconnect, + .id_table = usb_acecad_id_table, +}; + +static int __init usb_acecad_init(void) +{ + int result = usb_register(&usb_acecad_driver); + if (result == 0) + printk(KERN_INFO KBUILD_MODNAME ": " DRIVER_VERSION ":" + DRIVER_DESC "\n"); + return result; +} + +static void __exit usb_acecad_exit(void) +{ + usb_deregister(&usb_acecad_driver); +} + +module_init(usb_acecad_init); +module_exit(usb_acecad_exit); diff --git a/drivers/input/tablet/aiptek.c b/drivers/input/tablet/aiptek.c new file mode 100644 index 0000000..7d005a3 --- /dev/null +++ b/drivers/input/tablet/aiptek.c @@ -0,0 +1,1952 @@ +/* + * Native support for the Aiptek HyperPen USB Tablets + * (4000U/5000U/6000U/8000U/12000U) + * + * Copyright (c) 2001 Chris Atenasio <chris@crud.net> + * Copyright (c) 2002-2004 Bryan W. Headley <bwheadley@earthlink.net> + * + * based on wacom.c by + * Vojtech Pavlik <vojtech@suse.cz> + * Andreas Bach Aaen <abach@stofanet.dk> + * Clifford Wolf <clifford@clifford.at> + * Sam Mosel <sam.mosel@computer.org> + * James E. Blair <corvus@gnu.org> + * Daniel Egger <egger@suse.de> + * + * Many thanks to Oliver Kuechemann for his support. + * + * ChangeLog: + * v0.1 - Initial release + * v0.2 - Hack to get around fake event 28's. (Bryan W. Headley) + * v0.3 - Make URB dynamic (Bryan W. Headley, Jun-8-2002) + * Released to Linux 2.4.19 and 2.5.x + * v0.4 - Rewrote substantial portions of the code to deal with + * corrected control sequences, timing, dynamic configuration, + * support of 6000U - 12000U, procfs, and macro key support + * (Jan-1-2003 - Feb-5-2003, Bryan W. Headley) + * v1.0 - Added support for diagnostic messages, count of messages + * received from URB - Mar-8-2003, Bryan W. Headley + * v1.1 - added support for tablet resolution, changed DV and proximity + * some corrections - Jun-22-2003, martin schneebacher + * - Added support for the sysfs interface, deprecating the + * procfs interface for 2.5.x kernel. Also added support for + * Wheel command. Bryan W. Headley July-15-2003. + * v1.2 - Reworked jitter timer as a kernel thread. + * Bryan W. Headley November-28-2003/Jan-10-2004. + * v1.3 - Repaired issue of kernel thread going nuts on single-processor + * machines, introduced programmableDelay as a command line + * parameter. Feb 7 2004, Bryan W. Headley. + * v1.4 - Re-wire jitter so it does not require a thread. Courtesy of + * Rene van Paassen. Added reporting of physical pointer device + * (e.g., stylus, mouse in reports 2, 3, 4, 5. We don't know + * for reports 1, 6.) + * what physical device reports for reports 1, 6.) Also enabled + * MOUSE and LENS tool button modes. Renamed "rubber" to "eraser". + * Feb 20, 2004, Bryan W. Headley. + * v1.5 - Added previousJitterable, so we don't do jitter delay when the + * user is holding a button down for periods of time. + * + * NOTE: + * This kernel driver is augmented by the "Aiptek" XFree86 input + * driver for your X server, as well as the Gaiptek GUI Front-end + * "Tablet Manager". + * These three products are highly interactive with one another, + * so therefore it's easier to document them all as one subsystem. + * Please visit the project's "home page", located at, + * http://aiptektablet.sourceforge.net. + * + * 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/jiffies.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/usb/input.h> +#include <asm/uaccess.h> +#include <asm/unaligned.h> + +/* + * Version Information + */ +#define DRIVER_VERSION "v2.3 (May 2, 2007)" +#define DRIVER_AUTHOR "Bryan W. Headley/Chris Atenasio/Cedric Brun/Rene van Paassen" +#define DRIVER_DESC "Aiptek HyperPen USB Tablet Driver (Linux 2.6.x)" + +/* + * Aiptek status packet: + * + * (returned as Report 1 - relative coordinates from mouse and stylus) + * + * bit7 bit6 bit5 bit4 bit3 bit2 bit1 bit0 + * byte0 0 0 0 0 0 0 0 1 + * byte1 0 0 0 0 0 BS2 BS Tip + * byte2 X7 X6 X5 X4 X3 X2 X1 X0 + * byte3 Y7 Y6 Y5 Y4 Y3 Y2 Y1 Y0 + * + * (returned as Report 2 - absolute coordinates from the stylus) + * + * bit7 bit6 bit5 bit4 bit3 bit2 bit1 bit0 + * byte0 0 0 0 0 0 0 1 0 + * byte1 X7 X6 X5 X4 X3 X2 X1 X0 + * byte2 X15 X14 X13 X12 X11 X10 X9 X8 + * byte3 Y7 Y6 Y5 Y4 Y3 Y2 Y1 Y0 + * byte4 Y15 Y14 Y13 Y12 Y11 Y10 Y9 Y8 + * byte5 * * * BS2 BS1 Tip IR DV + * byte6 P7 P6 P5 P4 P3 P2 P1 P0 + * byte7 P15 P14 P13 P12 P11 P10 P9 P8 + * + * (returned as Report 3 - absolute coordinates from the mouse) + * + * bit7 bit6 bit5 bit4 bit3 bit2 bit1 bit0 + * byte0 0 0 0 0 0 0 1 1 + * byte1 X7 X6 X5 X4 X3 X2 X1 X0 + * byte2 X15 X14 X13 X12 X11 X10 X9 X8 + * byte3 Y7 Y6 Y5 Y4 Y3 Y2 Y1 Y0 + * byte4 Y15 Y14 Y13 Y12 Y11 Y10 Y9 Y8 + * byte5 * * * BS2 BS1 Tip IR DV + * byte6 P7 P6 P5 P4 P3 P2 P1 P0 + * byte7 P15 P14 P13 P12 P11 P10 P9 P8 + * + * (returned as Report 4 - macrokeys from the stylus) + * + * bit7 bit6 bit5 bit4 bit3 bit2 bit1 bit0 + * byte0 0 0 0 0 0 1 0 0 + * byte1 0 0 0 BS2 BS Tip IR DV + * byte2 0 0 0 0 0 0 1 0 + * byte3 0 0 0 K4 K3 K2 K1 K0 + * byte4 P7 P6 P5 P4 P3 P2 P1 P0 + * byte5 P15 P14 P13 P12 P11 P10 P9 P8 + * + * (returned as Report 5 - macrokeys from the mouse) + * + * bit7 bit6 bit5 bit4 bit3 bit2 bit1 bit0 + * byte0 0 0 0 0 0 1 0 1 + * byte1 0 0 0 BS2 BS Tip IR DV + * byte2 0 0 0 0 0 0 1 0 + * byte3 0 0 0 K4 K3 K2 K1 K0 + * byte4 P7 P6 P5 P4 P3 P2 P1 P0 + * byte5 P15 P14 P13 P12 P11 P10 P9 P8 + * + * IR: In Range = Proximity on + * DV = Data Valid + * BS = Barrel Switch (as in, macro keys) + * BS2 also referred to as Tablet Pick + * + * Command Summary: + * + * Use report_type CONTROL (3) + * Use report_id 2 + * + * Command/Data Description Return Bytes Return Value + * 0x10/0x00 SwitchToMouse 0 + * 0x10/0x01 SwitchToTablet 0 + * 0x18/0x04 SetResolution 0 + * 0x12/0xFF AutoGainOn 0 + * 0x17/0x00 FilterOn 0 + * 0x01/0x00 GetXExtension 2 MaxX + * 0x01/0x01 GetYExtension 2 MaxY + * 0x02/0x00 GetModelCode 2 ModelCode = LOBYTE + * 0x03/0x00 GetODMCode 2 ODMCode + * 0x08/0x00 GetPressureLevels 2 =512 + * 0x04/0x00 GetFirmwareVersion 2 Firmware Version + * 0x11/0x02 EnableMacroKeys 0 + * + * To initialize the tablet: + * + * (1) Send Resolution500LPI (Command) + * (2) Query for Model code (Option Report) + * (3) Query for ODM code (Option Report) + * (4) Query for firmware (Option Report) + * (5) Query for GetXExtension (Option Report) + * (6) Query for GetYExtension (Option Report) + * (7) Query for GetPressureLevels (Option Report) + * (8) SwitchToTablet for Absolute coordinates, or + * SwitchToMouse for Relative coordinates (Command) + * (9) EnableMacroKeys (Command) + * (10) FilterOn (Command) + * (11) AutoGainOn (Command) + * + * (Step 9 can be omitted, but you'll then have no function keys.) + */ + +#define USB_VENDOR_ID_AIPTEK 0x08ca +#define USB_VENDOR_ID_KYE 0x0458 +#define USB_REQ_GET_REPORT 0x01 +#define USB_REQ_SET_REPORT 0x09 + + /* PointerMode codes + */ +#define AIPTEK_POINTER_ONLY_MOUSE_MODE 0 +#define AIPTEK_POINTER_ONLY_STYLUS_MODE 1 +#define AIPTEK_POINTER_EITHER_MODE 2 + +#define AIPTEK_POINTER_ALLOW_MOUSE_MODE(a) \ + (a == AIPTEK_POINTER_ONLY_MOUSE_MODE || \ + a == AIPTEK_POINTER_EITHER_MODE) +#define AIPTEK_POINTER_ALLOW_STYLUS_MODE(a) \ + (a == AIPTEK_POINTER_ONLY_STYLUS_MODE || \ + a == AIPTEK_POINTER_EITHER_MODE) + + /* CoordinateMode code + */ +#define AIPTEK_COORDINATE_RELATIVE_MODE 0 +#define AIPTEK_COORDINATE_ABSOLUTE_MODE 1 + + /* XTilt and YTilt values + */ +#define AIPTEK_TILT_MIN (-128) +#define AIPTEK_TILT_MAX 127 +#define AIPTEK_TILT_DISABLE (-10101) + + /* Wheel values + */ +#define AIPTEK_WHEEL_MIN 0 +#define AIPTEK_WHEEL_MAX 1024 +#define AIPTEK_WHEEL_DISABLE (-10101) + + /* ToolCode values, which BTW are 0x140 .. 0x14f + * We have things set up such that if the tool button has changed, + * the tools get reset. + */ + /* toolMode codes + */ +#define AIPTEK_TOOL_BUTTON_PEN_MODE BTN_TOOL_PEN +#define AIPTEK_TOOL_BUTTON_PEN_MODE BTN_TOOL_PEN +#define AIPTEK_TOOL_BUTTON_PENCIL_MODE BTN_TOOL_PENCIL +#define AIPTEK_TOOL_BUTTON_BRUSH_MODE BTN_TOOL_BRUSH +#define AIPTEK_TOOL_BUTTON_AIRBRUSH_MODE BTN_TOOL_AIRBRUSH +#define AIPTEK_TOOL_BUTTON_ERASER_MODE BTN_TOOL_RUBBER +#define AIPTEK_TOOL_BUTTON_MOUSE_MODE BTN_TOOL_MOUSE +#define AIPTEK_TOOL_BUTTON_LENS_MODE BTN_TOOL_LENS + + /* Diagnostic message codes + */ +#define AIPTEK_DIAGNOSTIC_NA 0 +#define AIPTEK_DIAGNOSTIC_SENDING_RELATIVE_IN_ABSOLUTE 1 +#define AIPTEK_DIAGNOSTIC_SENDING_ABSOLUTE_IN_RELATIVE 2 +#define AIPTEK_DIAGNOSTIC_TOOL_DISALLOWED 3 + + /* Time to wait (in ms) to help mask hand jittering + * when pressing the stylus buttons. + */ +#define AIPTEK_JITTER_DELAY_DEFAULT 50 + + /* Time to wait (in ms) in-between sending the tablet + * a command and beginning the process of reading the return + * sequence from the tablet. + */ +#define AIPTEK_PROGRAMMABLE_DELAY_25 25 +#define AIPTEK_PROGRAMMABLE_DELAY_50 50 +#define AIPTEK_PROGRAMMABLE_DELAY_100 100 +#define AIPTEK_PROGRAMMABLE_DELAY_200 200 +#define AIPTEK_PROGRAMMABLE_DELAY_300 300 +#define AIPTEK_PROGRAMMABLE_DELAY_400 400 +#define AIPTEK_PROGRAMMABLE_DELAY_DEFAULT AIPTEK_PROGRAMMABLE_DELAY_400 + + /* Mouse button programming + */ +#define AIPTEK_MOUSE_LEFT_BUTTON 0x04 +#define AIPTEK_MOUSE_RIGHT_BUTTON 0x08 +#define AIPTEK_MOUSE_MIDDLE_BUTTON 0x10 + + /* Stylus button programming + */ +#define AIPTEK_STYLUS_LOWER_BUTTON 0x08 +#define AIPTEK_STYLUS_UPPER_BUTTON 0x10 + + /* Length of incoming packet from the tablet + */ +#define AIPTEK_PACKET_LENGTH 8 + + /* We report in EV_MISC both the proximity and + * whether the report came from the stylus, tablet mouse + * or "unknown" -- Unknown when the tablet is in relative + * mode, because we only get report 1's. + */ +#define AIPTEK_REPORT_TOOL_UNKNOWN 0x10 +#define AIPTEK_REPORT_TOOL_STYLUS 0x20 +#define AIPTEK_REPORT_TOOL_MOUSE 0x40 + +static int programmableDelay = AIPTEK_PROGRAMMABLE_DELAY_DEFAULT; +static int jitterDelay = AIPTEK_JITTER_DELAY_DEFAULT; + +struct aiptek_features { + int odmCode; /* Tablet manufacturer code */ + int modelCode; /* Tablet model code (not unique) */ + int firmwareCode; /* prom/eeprom version */ + char usbPath[64 + 1]; /* device's physical usb path */ +}; + +struct aiptek_settings { + int pointerMode; /* stylus-, mouse-only or either */ + int coordinateMode; /* absolute/relative coords */ + int toolMode; /* pen, pencil, brush, etc. tool */ + int xTilt; /* synthetic xTilt amount */ + int yTilt; /* synthetic yTilt amount */ + int wheel; /* synthetic wheel amount */ + int stylusButtonUpper; /* stylus upper btn delivers... */ + int stylusButtonLower; /* stylus lower btn delivers... */ + int mouseButtonLeft; /* mouse left btn delivers... */ + int mouseButtonMiddle; /* mouse middle btn delivers... */ + int mouseButtonRight; /* mouse right btn delivers... */ + int programmableDelay; /* delay for tablet programming */ + int jitterDelay; /* delay for hand jittering */ +}; + +struct aiptek { + struct input_dev *inputdev; /* input device struct */ + struct usb_device *usbdev; /* usb device struct */ + struct urb *urb; /* urb for incoming reports */ + dma_addr_t data_dma; /* our dma stuffage */ + struct aiptek_features features; /* tablet's array of features */ + struct aiptek_settings curSetting; /* tablet's current programmable */ + struct aiptek_settings newSetting; /* ... and new param settings */ + unsigned int ifnum; /* interface number for IO */ + int diagnostic; /* tablet diagnostic codes */ + unsigned long eventCount; /* event count */ + int inDelay; /* jitter: in jitter delay? */ + unsigned long endDelay; /* jitter: time when delay ends */ + int previousJitterable; /* jitterable prev value */ + + int lastMacro; /* macro key to reset */ + int previousToolMode; /* pen, pencil, brush, etc. tool */ + unsigned char *data; /* incoming packet data */ +}; + +static const int eventTypes[] = { + EV_KEY, EV_ABS, EV_REL, EV_MSC, +}; + +static const int absEvents[] = { + ABS_X, ABS_Y, ABS_PRESSURE, ABS_TILT_X, ABS_TILT_Y, + ABS_WHEEL, ABS_MISC, +}; + +static const int relEvents[] = { + REL_X, REL_Y, REL_WHEEL, +}; + +static const int buttonEvents[] = { + BTN_LEFT, BTN_RIGHT, BTN_MIDDLE, + BTN_TOOL_PEN, BTN_TOOL_RUBBER, BTN_TOOL_PENCIL, BTN_TOOL_AIRBRUSH, + BTN_TOOL_BRUSH, BTN_TOOL_MOUSE, BTN_TOOL_LENS, BTN_TOUCH, + BTN_STYLUS, BTN_STYLUS2, +}; + +/* + * Permit easy lookup of keyboard events to send, versus + * the bitmap which comes from the tablet. This hides the + * issue that the F_keys are not sequentially numbered. + */ +static const int macroKeyEvents[] = { + KEY_ESC, KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5, + KEY_F6, KEY_F7, KEY_F8, KEY_F9, KEY_F10, KEY_F11, + KEY_F12, KEY_F13, KEY_F14, KEY_F15, KEY_F16, KEY_F17, + KEY_F18, KEY_F19, KEY_F20, KEY_F21, KEY_F22, KEY_F23, + KEY_F24, KEY_STOP, KEY_AGAIN, KEY_PROPS, KEY_UNDO, + KEY_FRONT, KEY_COPY, KEY_OPEN, KEY_PASTE, 0 +}; + +/*********************************************************************** + * Map values to strings and back. Every map shoudl have the following + * as its last element: { NULL, AIPTEK_INVALID_VALUE }. + */ +#define AIPTEK_INVALID_VALUE -1 + +struct aiptek_map { + const char *string; + int value; +}; + +static int map_str_to_val(const struct aiptek_map *map, const char *str, size_t count) +{ + const struct aiptek_map *p; + + if (str[count - 1] == '\n') + count--; + + for (p = map; p->string; p++) + if (!strncmp(str, p->string, count)) + return p->value; + + return AIPTEK_INVALID_VALUE; +} + +static const char *map_val_to_str(const struct aiptek_map *map, int val) +{ + const struct aiptek_map *p; + + for (p = map; p->value != AIPTEK_INVALID_VALUE; p++) + if (val == p->value) + return p->string; + + return "unknown"; +} + +/*********************************************************************** + * aiptek_irq can receive one of six potential reports. + * The documentation for each is in the body of the function. + * + * The tablet reports on several attributes per invocation of + * aiptek_irq. Because the Linux Input Event system allows the + * transmission of ONE attribute per input_report_xxx() call, + * collation has to be done on the other end to reconstitute + * a complete tablet report. Further, the number of Input Event reports + * submitted varies, depending on what USB report type, and circumstance. + * To deal with this, EV_MSC is used to indicate an 'end-of-report' + * message. This has been an undocumented convention understood by the kernel + * tablet driver and clients such as gpm and XFree86's tablet drivers. + * + * Of the information received from the tablet, the one piece I + * cannot transmit is the proximity bit (without resorting to an EV_MSC + * convention above.) I therefore have taken over REL_MISC and ABS_MISC + * (for relative and absolute reports, respectively) for communicating + * Proximity. Why two events? I thought it interesting to know if the + * Proximity event occurred while the tablet was in absolute or relative + * mode. + * Update: REL_MISC proved not to be such a good idea. With REL_MISC you + * get an event transmitted each time. ABS_MISC works better, since it + * can be set and re-set. Thus, only using ABS_MISC from now on. + * + * Other tablets use the notion of a certain minimum stylus pressure + * to infer proximity. While that could have been done, that is yet + * another 'by convention' behavior, the documentation for which + * would be spread between two (or more) pieces of software. + * + * EV_MSC usage was terminated for this purpose in Linux 2.5.x, and + * replaced with the input_sync() method (which emits EV_SYN.) + */ + +static void aiptek_irq(struct urb *urb) +{ + struct aiptek *aiptek = urb->context; + unsigned char *data = aiptek->data; + struct input_dev *inputdev = aiptek->inputdev; + int jitterable = 0; + int retval, macro, x, y, z, left, right, middle, p, dv, tip, bs, pck; + + 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", + __func__, urb->status); + return; + + default: + dbg("%s - nonzero urb status received: %d", + __func__, urb->status); + goto exit; + } + + /* See if we are in a delay loop -- throw out report if true. + */ + if (aiptek->inDelay == 1 && time_after(aiptek->endDelay, jiffies)) { + goto exit; + } + + aiptek->inDelay = 0; + aiptek->eventCount++; + + /* Report 1 delivers relative coordinates with either a stylus + * or the mouse. You do not know, however, which input + * tool generated the event. + */ + if (data[0] == 1) { + if (aiptek->curSetting.coordinateMode == + AIPTEK_COORDINATE_ABSOLUTE_MODE) { + aiptek->diagnostic = + AIPTEK_DIAGNOSTIC_SENDING_RELATIVE_IN_ABSOLUTE; + } else { + x = (signed char) data[2]; + y = (signed char) data[3]; + + /* jitterable keeps track of whether any button has been pressed. + * We're also using it to remap the physical mouse button mask + * to pseudo-settings. (We don't specifically care about it's + * value after moving/transposing mouse button bitmasks, except + * that a non-zero value indicates that one or more + * mouse button was pressed.) + */ + jitterable = data[1] & 0x07; + + left = (data[1] & aiptek->curSetting.mouseButtonLeft >> 2) != 0 ? 1 : 0; + right = (data[1] & aiptek->curSetting.mouseButtonRight >> 2) != 0 ? 1 : 0; + middle = (data[1] & aiptek->curSetting.mouseButtonMiddle >> 2) != 0 ? 1 : 0; + + input_report_key(inputdev, BTN_LEFT, left); + input_report_key(inputdev, BTN_MIDDLE, middle); + input_report_key(inputdev, BTN_RIGHT, right); + + input_report_abs(inputdev, ABS_MISC, + 1 | AIPTEK_REPORT_TOOL_UNKNOWN); + input_report_rel(inputdev, REL_X, x); + input_report_rel(inputdev, REL_Y, y); + + /* Wheel support is in the form of a single-event + * firing. + */ + if (aiptek->curSetting.wheel != AIPTEK_WHEEL_DISABLE) { + input_report_rel(inputdev, REL_WHEEL, + aiptek->curSetting.wheel); + aiptek->curSetting.wheel = AIPTEK_WHEEL_DISABLE; + } + if (aiptek->lastMacro != -1) { + input_report_key(inputdev, + macroKeyEvents[aiptek->lastMacro], 0); + aiptek->lastMacro = -1; + } + input_sync(inputdev); + } + } + /* Report 2 is delivered only by the stylus, and delivers + * absolute coordinates. + */ + else if (data[0] == 2) { + if (aiptek->curSetting.coordinateMode == AIPTEK_COORDINATE_RELATIVE_MODE) { + aiptek->diagnostic = AIPTEK_DIAGNOSTIC_SENDING_ABSOLUTE_IN_RELATIVE; + } else if (!AIPTEK_POINTER_ALLOW_STYLUS_MODE + (aiptek->curSetting.pointerMode)) { + aiptek->diagnostic = AIPTEK_DIAGNOSTIC_TOOL_DISALLOWED; + } else { + x = get_unaligned_le16(data + 1); + y = get_unaligned_le16(data + 3); + z = get_unaligned_le16(data + 6); + + dv = (data[5] & 0x01) != 0 ? 1 : 0; + p = (data[5] & 0x02) != 0 ? 1 : 0; + tip = (data[5] & 0x04) != 0 ? 1 : 0; + + /* Use jitterable to re-arrange button masks + */ + jitterable = data[5] & 0x18; + + bs = (data[5] & aiptek->curSetting.stylusButtonLower) != 0 ? 1 : 0; + pck = (data[5] & aiptek->curSetting.stylusButtonUpper) != 0 ? 1 : 0; + + /* dv indicates 'data valid' (e.g., the tablet is in sync + * and has delivered a "correct" report) We will ignore + * all 'bad' reports... + */ + if (dv != 0) { + /* If the selected tool changed, reset the old + * tool key, and set the new one. + */ + if (aiptek->previousToolMode != + aiptek->curSetting.toolMode) { + input_report_key(inputdev, + aiptek->previousToolMode, 0); + input_report_key(inputdev, + aiptek->curSetting.toolMode, + 1); + aiptek->previousToolMode = + aiptek->curSetting.toolMode; + } + + if (p != 0) { + input_report_abs(inputdev, ABS_X, x); + input_report_abs(inputdev, ABS_Y, y); + input_report_abs(inputdev, ABS_PRESSURE, z); + + input_report_key(inputdev, BTN_TOUCH, tip); + input_report_key(inputdev, BTN_STYLUS, bs); + input_report_key(inputdev, BTN_STYLUS2, pck); + + if (aiptek->curSetting.xTilt != + AIPTEK_TILT_DISABLE) { + input_report_abs(inputdev, + ABS_TILT_X, + aiptek->curSetting.xTilt); + } + if (aiptek->curSetting.yTilt != AIPTEK_TILT_DISABLE) { + input_report_abs(inputdev, + ABS_TILT_Y, + aiptek->curSetting.yTilt); + } + + /* Wheel support is in the form of a single-event + * firing. + */ + if (aiptek->curSetting.wheel != + AIPTEK_WHEEL_DISABLE) { + input_report_abs(inputdev, + ABS_WHEEL, + aiptek->curSetting.wheel); + aiptek->curSetting.wheel = AIPTEK_WHEEL_DISABLE; + } + } + input_report_abs(inputdev, ABS_MISC, p | AIPTEK_REPORT_TOOL_STYLUS); + if (aiptek->lastMacro != -1) { + input_report_key(inputdev, + macroKeyEvents[aiptek->lastMacro], 0); + aiptek->lastMacro = -1; + } + input_sync(inputdev); + } + } + } + /* Report 3's come from the mouse in absolute mode. + */ + else if (data[0] == 3) { + if (aiptek->curSetting.coordinateMode == AIPTEK_COORDINATE_RELATIVE_MODE) { + aiptek->diagnostic = AIPTEK_DIAGNOSTIC_SENDING_ABSOLUTE_IN_RELATIVE; + } else if (!AIPTEK_POINTER_ALLOW_MOUSE_MODE + (aiptek->curSetting.pointerMode)) { + aiptek->diagnostic = AIPTEK_DIAGNOSTIC_TOOL_DISALLOWED; + } else { + x = get_unaligned_le16(data + 1); + y = get_unaligned_le16(data + 3); + + jitterable = data[5] & 0x1c; + + dv = (data[5] & 0x01) != 0 ? 1 : 0; + p = (data[5] & 0x02) != 0 ? 1 : 0; + left = (data[5] & aiptek->curSetting.mouseButtonLeft) != 0 ? 1 : 0; + right = (data[5] & aiptek->curSetting.mouseButtonRight) != 0 ? 1 : 0; + middle = (data[5] & aiptek->curSetting.mouseButtonMiddle) != 0 ? 1 : 0; + + if (dv != 0) { + /* If the selected tool changed, reset the old + * tool key, and set the new one. + */ + if (aiptek->previousToolMode != + aiptek->curSetting.toolMode) { + input_report_key(inputdev, + aiptek->previousToolMode, 0); + input_report_key(inputdev, + aiptek->curSetting.toolMode, + 1); + aiptek->previousToolMode = + aiptek->curSetting.toolMode; + } + + if (p != 0) { + input_report_abs(inputdev, ABS_X, x); + input_report_abs(inputdev, ABS_Y, y); + + input_report_key(inputdev, BTN_LEFT, left); + input_report_key(inputdev, BTN_MIDDLE, middle); + input_report_key(inputdev, BTN_RIGHT, right); + + /* Wheel support is in the form of a single-event + * firing. + */ + if (aiptek->curSetting.wheel != AIPTEK_WHEEL_DISABLE) { + input_report_abs(inputdev, + ABS_WHEEL, + aiptek->curSetting.wheel); + aiptek->curSetting.wheel = AIPTEK_WHEEL_DISABLE; + } + } + input_report_abs(inputdev, ABS_MISC, p | AIPTEK_REPORT_TOOL_MOUSE); + if (aiptek->lastMacro != -1) { + input_report_key(inputdev, + macroKeyEvents[aiptek->lastMacro], 0); + aiptek->lastMacro = -1; + } + input_sync(inputdev); + } + } + } + /* Report 4s come from the macro keys when pressed by stylus + */ + else if (data[0] == 4) { + jitterable = data[1] & 0x18; + + dv = (data[1] & 0x01) != 0 ? 1 : 0; + p = (data[1] & 0x02) != 0 ? 1 : 0; + tip = (data[1] & 0x04) != 0 ? 1 : 0; + bs = (data[1] & aiptek->curSetting.stylusButtonLower) != 0 ? 1 : 0; + pck = (data[1] & aiptek->curSetting.stylusButtonUpper) != 0 ? 1 : 0; + + macro = dv && p && tip && !(data[3] & 1) ? (data[3] >> 1) : -1; + z = get_unaligned_le16(data + 4); + + if (dv) { + /* If the selected tool changed, reset the old + * tool key, and set the new one. + */ + if (aiptek->previousToolMode != + aiptek->curSetting.toolMode) { + input_report_key(inputdev, + aiptek->previousToolMode, 0); + input_report_key(inputdev, + aiptek->curSetting.toolMode, + 1); + aiptek->previousToolMode = + aiptek->curSetting.toolMode; + } + } + + if (aiptek->lastMacro != -1 && aiptek->lastMacro != macro) { + input_report_key(inputdev, macroKeyEvents[aiptek->lastMacro], 0); + aiptek->lastMacro = -1; + } + + if (macro != -1 && macro != aiptek->lastMacro) { + input_report_key(inputdev, macroKeyEvents[macro], 1); + aiptek->lastMacro = macro; + } + input_report_abs(inputdev, ABS_MISC, + p | AIPTEK_REPORT_TOOL_STYLUS); + input_sync(inputdev); + } + /* Report 5s come from the macro keys when pressed by mouse + */ + else if (data[0] == 5) { + jitterable = data[1] & 0x1c; + + dv = (data[1] & 0x01) != 0 ? 1 : 0; + p = (data[1] & 0x02) != 0 ? 1 : 0; + left = (data[1]& aiptek->curSetting.mouseButtonLeft) != 0 ? 1 : 0; + right = (data[1] & aiptek->curSetting.mouseButtonRight) != 0 ? 1 : 0; + middle = (data[1] & aiptek->curSetting.mouseButtonMiddle) != 0 ? 1 : 0; + macro = dv && p && left && !(data[3] & 1) ? (data[3] >> 1) : 0; + + if (dv) { + /* If the selected tool changed, reset the old + * tool key, and set the new one. + */ + if (aiptek->previousToolMode != + aiptek->curSetting.toolMode) { + input_report_key(inputdev, + aiptek->previousToolMode, 0); + input_report_key(inputdev, + aiptek->curSetting.toolMode, 1); + aiptek->previousToolMode = aiptek->curSetting.toolMode; + } + } + + if (aiptek->lastMacro != -1 && aiptek->lastMacro != macro) { + input_report_key(inputdev, macroKeyEvents[aiptek->lastMacro], 0); + aiptek->lastMacro = -1; + } + + if (macro != -1 && macro != aiptek->lastMacro) { + input_report_key(inputdev, macroKeyEvents[macro], 1); + aiptek->lastMacro = macro; + } + + input_report_abs(inputdev, ABS_MISC, + p | AIPTEK_REPORT_TOOL_MOUSE); + input_sync(inputdev); + } + /* We have no idea which tool can generate a report 6. Theoretically, + * neither need to, having been given reports 4 & 5 for such use. + * However, report 6 is the 'official-looking' report for macroKeys; + * reports 4 & 5 supposively are used to support unnamed, unknown + * hat switches (which just so happen to be the macroKeys.) + */ + else if (data[0] == 6) { + macro = get_unaligned_le16(data + 1); + if (macro > 0) { + input_report_key(inputdev, macroKeyEvents[macro - 1], + 0); + } + if (macro < 25) { + input_report_key(inputdev, macroKeyEvents[macro + 1], + 0); + } + + /* If the selected tool changed, reset the old + tool key, and set the new one. + */ + if (aiptek->previousToolMode != + aiptek->curSetting.toolMode) { + input_report_key(inputdev, + aiptek->previousToolMode, 0); + input_report_key(inputdev, + aiptek->curSetting.toolMode, + 1); + aiptek->previousToolMode = + aiptek->curSetting.toolMode; + } + + input_report_key(inputdev, macroKeyEvents[macro], 1); + input_report_abs(inputdev, ABS_MISC, + 1 | AIPTEK_REPORT_TOOL_UNKNOWN); + input_sync(inputdev); + } else { + dbg("Unknown report %d", data[0]); + } + + /* Jitter may occur when the user presses a button on the stlyus + * or the mouse. What we do to prevent that is wait 'x' milliseconds + * following a 'jitterable' event, which should give the hand some time + * stabilize itself. + * + * We just introduced aiptek->previousJitterable to carry forth the + * notion that jitter occurs when the button state changes from on to off: + * a person drawing, holding a button down is not subject to jittering. + * With that in mind, changing from upper button depressed to lower button + * WILL transition through a jitter delay. + */ + + if (aiptek->previousJitterable != jitterable && + aiptek->curSetting.jitterDelay != 0 && aiptek->inDelay != 1) { + aiptek->endDelay = jiffies + + ((aiptek->curSetting.jitterDelay * HZ) / 1000); + aiptek->inDelay = 1; + } + aiptek->previousJitterable = jitterable; + +exit: + retval = usb_submit_urb(urb, GFP_ATOMIC); + if (retval != 0) { + err("%s - usb_submit_urb failed with result %d", + __func__, retval); + } +} + +/*********************************************************************** + * These are the USB id's known so far. We do not identify them to + * specific Aiptek model numbers, because there has been overlaps, + * use, and reuse of id's in existing models. Certain models have + * been known to use more than one ID, indicative perhaps of + * manufacturing revisions. In any event, we consider these + * IDs to not be model-specific nor unique. + */ +static const struct usb_device_id aiptek_ids[] = { + {USB_DEVICE(USB_VENDOR_ID_AIPTEK, 0x01)}, + {USB_DEVICE(USB_VENDOR_ID_AIPTEK, 0x10)}, + {USB_DEVICE(USB_VENDOR_ID_AIPTEK, 0x20)}, + {USB_DEVICE(USB_VENDOR_ID_AIPTEK, 0x21)}, + {USB_DEVICE(USB_VENDOR_ID_AIPTEK, 0x22)}, + {USB_DEVICE(USB_VENDOR_ID_AIPTEK, 0x23)}, + {USB_DEVICE(USB_VENDOR_ID_AIPTEK, 0x24)}, + {USB_DEVICE(USB_VENDOR_ID_KYE, 0x5003)}, + {} +}; + +MODULE_DEVICE_TABLE(usb, aiptek_ids); + +/*********************************************************************** + * Open an instance of the tablet driver. + */ +static int aiptek_open(struct input_dev *inputdev) +{ + struct aiptek *aiptek = input_get_drvdata(inputdev); + + aiptek->urb->dev = aiptek->usbdev; + if (usb_submit_urb(aiptek->urb, GFP_KERNEL) != 0) + return -EIO; + + return 0; +} + +/*********************************************************************** + * Close an instance of the tablet driver. + */ +static void aiptek_close(struct input_dev *inputdev) +{ + struct aiptek *aiptek = input_get_drvdata(inputdev); + + usb_kill_urb(aiptek->urb); +} + +/*********************************************************************** + * aiptek_set_report and aiptek_get_report() are borrowed from Linux 2.4.x, + * where they were known as usb_set_report and usb_get_report. + */ +static int +aiptek_set_report(struct aiptek *aiptek, + unsigned char report_type, + unsigned char report_id, void *buffer, int size) +{ + return usb_control_msg(aiptek->usbdev, + usb_sndctrlpipe(aiptek->usbdev, 0), + USB_REQ_SET_REPORT, + USB_TYPE_CLASS | USB_RECIP_INTERFACE | + USB_DIR_OUT, (report_type << 8) + report_id, + aiptek->ifnum, buffer, size, 5000); +} + +static int +aiptek_get_report(struct aiptek *aiptek, + unsigned char report_type, + unsigned char report_id, void *buffer, int size) +{ + return usb_control_msg(aiptek->usbdev, + usb_rcvctrlpipe(aiptek->usbdev, 0), + USB_REQ_GET_REPORT, + USB_TYPE_CLASS | USB_RECIP_INTERFACE | + USB_DIR_IN, (report_type << 8) + report_id, + aiptek->ifnum, buffer, size, 5000); +} + +/*********************************************************************** + * Send a command to the tablet. + */ +static int +aiptek_command(struct aiptek *aiptek, unsigned char command, unsigned char data) +{ + const int sizeof_buf = 3 * sizeof(u8); + int ret; + u8 *buf; + + buf = kmalloc(sizeof_buf, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + buf[0] = 2; + buf[1] = command; + buf[2] = data; + + if ((ret = + aiptek_set_report(aiptek, 3, 2, buf, sizeof_buf)) != sizeof_buf) { + dbg("aiptek_program: failed, tried to send: 0x%02x 0x%02x", + command, data); + } + kfree(buf); + return ret < 0 ? ret : 0; +} + +/*********************************************************************** + * Retrieve information from the tablet. Querying info is defined as first + * sending the {command,data} sequence as a command, followed by a wait + * (aka, "programmaticDelay") and then a "read" request. + */ +static int +aiptek_query(struct aiptek *aiptek, unsigned char command, unsigned char data) +{ + const int sizeof_buf = 3 * sizeof(u8); + int ret; + u8 *buf; + + buf = kmalloc(sizeof_buf, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + buf[0] = 2; + buf[1] = command; + buf[2] = data; + + if (aiptek_command(aiptek, command, data) != 0) { + kfree(buf); + return -EIO; + } + msleep(aiptek->curSetting.programmableDelay); + + if ((ret = + aiptek_get_report(aiptek, 3, 2, buf, sizeof_buf)) != sizeof_buf) { + dbg("aiptek_query failed: returned 0x%02x 0x%02x 0x%02x", + buf[0], buf[1], buf[2]); + ret = -EIO; + } else { + ret = get_unaligned_le16(buf + 1); + } + kfree(buf); + return ret; +} + +/*********************************************************************** + * Program the tablet into either absolute or relative mode. + * We also get information about the tablet's size. + */ +static int aiptek_program_tablet(struct aiptek *aiptek) +{ + int ret; + /* Execute Resolution500LPI */ + if ((ret = aiptek_command(aiptek, 0x18, 0x04)) < 0) + return ret; + + /* Query getModelCode */ + if ((ret = aiptek_query(aiptek, 0x02, 0x00)) < 0) + return ret; + aiptek->features.modelCode = ret & 0xff; + + /* Query getODMCode */ + if ((ret = aiptek_query(aiptek, 0x03, 0x00)) < 0) + return ret; + aiptek->features.odmCode = ret; + + /* Query getFirmwareCode */ + if ((ret = aiptek_query(aiptek, 0x04, 0x00)) < 0) + return ret; + aiptek->features.firmwareCode = ret; + + /* Query getXextension */ + if ((ret = aiptek_query(aiptek, 0x01, 0x00)) < 0) + return ret; + aiptek->inputdev->absmin[ABS_X] = 0; + aiptek->inputdev->absmax[ABS_X] = ret - 1; + + /* Query getYextension */ + if ((ret = aiptek_query(aiptek, 0x01, 0x01)) < 0) + return ret; + aiptek->inputdev->absmin[ABS_Y] = 0; + aiptek->inputdev->absmax[ABS_Y] = ret - 1; + + /* Query getPressureLevels */ + if ((ret = aiptek_query(aiptek, 0x08, 0x00)) < 0) + return ret; + aiptek->inputdev->absmin[ABS_PRESSURE] = 0; + aiptek->inputdev->absmax[ABS_PRESSURE] = ret - 1; + + /* Depending on whether we are in absolute or relative mode, we will + * do a switchToTablet(absolute) or switchToMouse(relative) command. + */ + if (aiptek->curSetting.coordinateMode == + AIPTEK_COORDINATE_ABSOLUTE_MODE) { + /* Execute switchToTablet */ + if ((ret = aiptek_command(aiptek, 0x10, 0x01)) < 0) { + return ret; + } + } else { + /* Execute switchToMouse */ + if ((ret = aiptek_command(aiptek, 0x10, 0x00)) < 0) { + return ret; + } + } + + /* Enable the macro keys */ + if ((ret = aiptek_command(aiptek, 0x11, 0x02)) < 0) + return ret; +#if 0 + /* Execute FilterOn */ + if ((ret = aiptek_command(aiptek, 0x17, 0x00)) < 0) + return ret; +#endif + + /* Execute AutoGainOn */ + if ((ret = aiptek_command(aiptek, 0x12, 0xff)) < 0) + return ret; + + /* Reset the eventCount, so we track events from last (re)programming + */ + aiptek->diagnostic = AIPTEK_DIAGNOSTIC_NA; + aiptek->eventCount = 0; + + return 0; +} + +/*********************************************************************** + * Sysfs functions. Sysfs prefers that individually-tunable parameters + * exist in their separate pseudo-files. Summary data that is immutable + * may exist in a singular file so long as you don't define a writeable + * interface. + */ + +/*********************************************************************** + * support the 'size' file -- display support + */ +static ssize_t show_tabletSize(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct aiptek *aiptek = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%dx%d\n", + aiptek->inputdev->absmax[ABS_X] + 1, + aiptek->inputdev->absmax[ABS_Y] + 1); +} + +/* These structs define the sysfs files, param #1 is the name of the + * file, param 2 is the file permissions, param 3 & 4 are to the + * output generator and input parser routines. Absence of a routine is + * permitted -- it only means can't either 'cat' the file, or send data + * to it. + */ +static DEVICE_ATTR(size, S_IRUGO, show_tabletSize, NULL); + +/*********************************************************************** + * support routines for the 'pointer_mode' file. Note that this file + * both displays current setting and allows reprogramming. + */ +static struct aiptek_map pointer_mode_map[] = { + { "stylus", AIPTEK_POINTER_ONLY_STYLUS_MODE }, + { "mouse", AIPTEK_POINTER_ONLY_MOUSE_MODE }, + { "either", AIPTEK_POINTER_EITHER_MODE }, + { NULL, AIPTEK_INVALID_VALUE } +}; + +static ssize_t show_tabletPointerMode(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct aiptek *aiptek = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%s\n", + map_val_to_str(pointer_mode_map, + aiptek->curSetting.pointerMode)); +} + +static ssize_t +store_tabletPointerMode(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct aiptek *aiptek = dev_get_drvdata(dev); + int new_mode = map_str_to_val(pointer_mode_map, buf, count); + + if (new_mode == AIPTEK_INVALID_VALUE) + return -EINVAL; + + aiptek->newSetting.pointerMode = new_mode; + return count; +} + +static DEVICE_ATTR(pointer_mode, + S_IRUGO | S_IWUGO, + show_tabletPointerMode, store_tabletPointerMode); + +/*********************************************************************** + * support routines for the 'coordinate_mode' file. Note that this file + * both displays current setting and allows reprogramming. + */ + +static struct aiptek_map coordinate_mode_map[] = { + { "absolute", AIPTEK_COORDINATE_ABSOLUTE_MODE }, + { "relative", AIPTEK_COORDINATE_RELATIVE_MODE }, + { NULL, AIPTEK_INVALID_VALUE } +}; + +static ssize_t show_tabletCoordinateMode(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct aiptek *aiptek = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%s\n", + map_val_to_str(coordinate_mode_map, + aiptek->curSetting.coordinateMode)); +} + +static ssize_t +store_tabletCoordinateMode(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct aiptek *aiptek = dev_get_drvdata(dev); + int new_mode = map_str_to_val(coordinate_mode_map, buf, count); + + if (new_mode == AIPTEK_INVALID_VALUE) + return -EINVAL; + + aiptek->newSetting.coordinateMode = new_mode; + return count; +} + +static DEVICE_ATTR(coordinate_mode, + S_IRUGO | S_IWUGO, + show_tabletCoordinateMode, store_tabletCoordinateMode); + +/*********************************************************************** + * support routines for the 'tool_mode' file. Note that this file + * both displays current setting and allows reprogramming. + */ + +static struct aiptek_map tool_mode_map[] = { + { "mouse", AIPTEK_TOOL_BUTTON_MOUSE_MODE }, + { "eraser", AIPTEK_TOOL_BUTTON_ERASER_MODE }, + { "pencil", AIPTEK_TOOL_BUTTON_PENCIL_MODE }, + { "pen", AIPTEK_TOOL_BUTTON_PEN_MODE }, + { "brush", AIPTEK_TOOL_BUTTON_BRUSH_MODE }, + { "airbrush", AIPTEK_TOOL_BUTTON_AIRBRUSH_MODE }, + { "lens", AIPTEK_TOOL_BUTTON_LENS_MODE }, + { NULL, AIPTEK_INVALID_VALUE } +}; + +static ssize_t show_tabletToolMode(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct aiptek *aiptek = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%s\n", + map_val_to_str(tool_mode_map, + aiptek->curSetting.toolMode)); +} + +static ssize_t +store_tabletToolMode(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct aiptek *aiptek = dev_get_drvdata(dev); + int new_mode = map_str_to_val(tool_mode_map, buf, count); + + if (new_mode == AIPTEK_INVALID_VALUE) + return -EINVAL; + + aiptek->newSetting.toolMode = new_mode; + return count; +} + +static DEVICE_ATTR(tool_mode, + S_IRUGO | S_IWUGO, + show_tabletToolMode, store_tabletToolMode); + +/*********************************************************************** + * support routines for the 'xtilt' file. Note that this file + * both displays current setting and allows reprogramming. + */ +static ssize_t show_tabletXtilt(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct aiptek *aiptek = dev_get_drvdata(dev); + + if (aiptek->curSetting.xTilt == AIPTEK_TILT_DISABLE) { + return snprintf(buf, PAGE_SIZE, "disable\n"); + } else { + return snprintf(buf, PAGE_SIZE, "%d\n", + aiptek->curSetting.xTilt); + } +} + +static ssize_t +store_tabletXtilt(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct aiptek *aiptek = dev_get_drvdata(dev); + long x; + + if (strict_strtol(buf, 10, &x)) { + size_t len = buf[count - 1] == '\n' ? count - 1 : count; + + if (strncmp(buf, "disable", len)) + return -EINVAL; + + aiptek->newSetting.xTilt = AIPTEK_TILT_DISABLE; + } else { + if (x < AIPTEK_TILT_MIN || x > AIPTEK_TILT_MAX) + return -EINVAL; + + aiptek->newSetting.xTilt = x; + } + + return count; +} + +static DEVICE_ATTR(xtilt, + S_IRUGO | S_IWUGO, show_tabletXtilt, store_tabletXtilt); + +/*********************************************************************** + * support routines for the 'ytilt' file. Note that this file + * both displays current setting and allows reprogramming. + */ +static ssize_t show_tabletYtilt(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct aiptek *aiptek = dev_get_drvdata(dev); + + if (aiptek->curSetting.yTilt == AIPTEK_TILT_DISABLE) { + return snprintf(buf, PAGE_SIZE, "disable\n"); + } else { + return snprintf(buf, PAGE_SIZE, "%d\n", + aiptek->curSetting.yTilt); + } +} + +static ssize_t +store_tabletYtilt(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct aiptek *aiptek = dev_get_drvdata(dev); + long y; + + if (strict_strtol(buf, 10, &y)) { + size_t len = buf[count - 1] == '\n' ? count - 1 : count; + + if (strncmp(buf, "disable", len)) + return -EINVAL; + + aiptek->newSetting.yTilt = AIPTEK_TILT_DISABLE; + } else { + if (y < AIPTEK_TILT_MIN || y > AIPTEK_TILT_MAX) + return -EINVAL; + + aiptek->newSetting.yTilt = y; + } + + return count; +} + +static DEVICE_ATTR(ytilt, + S_IRUGO | S_IWUGO, show_tabletYtilt, store_tabletYtilt); + +/*********************************************************************** + * support routines for the 'jitter' file. Note that this file + * both displays current setting and allows reprogramming. + */ +static ssize_t show_tabletJitterDelay(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct aiptek *aiptek = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%d\n", aiptek->curSetting.jitterDelay); +} + +static ssize_t +store_tabletJitterDelay(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct aiptek *aiptek = dev_get_drvdata(dev); + long j; + + if (strict_strtol(buf, 10, &j)) + return -EINVAL; + + aiptek->newSetting.jitterDelay = (int)j; + return count; +} + +static DEVICE_ATTR(jitter, + S_IRUGO | S_IWUGO, + show_tabletJitterDelay, store_tabletJitterDelay); + +/*********************************************************************** + * support routines for the 'delay' file. Note that this file + * both displays current setting and allows reprogramming. + */ +static ssize_t show_tabletProgrammableDelay(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct aiptek *aiptek = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%d\n", + aiptek->curSetting.programmableDelay); +} + +static ssize_t +store_tabletProgrammableDelay(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct aiptek *aiptek = dev_get_drvdata(dev); + long d; + + if (strict_strtol(buf, 10, &d)) + return -EINVAL; + + aiptek->newSetting.programmableDelay = (int)d; + return count; +} + +static DEVICE_ATTR(delay, + S_IRUGO | S_IWUGO, + show_tabletProgrammableDelay, store_tabletProgrammableDelay); + +/*********************************************************************** + * support routines for the 'event_count' file. Note that this file + * only displays current setting. + */ +static ssize_t show_tabletEventsReceived(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct aiptek *aiptek = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%ld\n", aiptek->eventCount); +} + +static DEVICE_ATTR(event_count, S_IRUGO, show_tabletEventsReceived, NULL); + +/*********************************************************************** + * support routines for the 'diagnostic' file. Note that this file + * only displays current setting. + */ +static ssize_t show_tabletDiagnosticMessage(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct aiptek *aiptek = dev_get_drvdata(dev); + char *retMsg; + + switch (aiptek->diagnostic) { + case AIPTEK_DIAGNOSTIC_NA: + retMsg = "no errors\n"; + break; + + case AIPTEK_DIAGNOSTIC_SENDING_RELATIVE_IN_ABSOLUTE: + retMsg = "Error: receiving relative reports\n"; + break; + + case AIPTEK_DIAGNOSTIC_SENDING_ABSOLUTE_IN_RELATIVE: + retMsg = "Error: receiving absolute reports\n"; + break; + + case AIPTEK_DIAGNOSTIC_TOOL_DISALLOWED: + if (aiptek->curSetting.pointerMode == + AIPTEK_POINTER_ONLY_MOUSE_MODE) { + retMsg = "Error: receiving stylus reports\n"; + } else { + retMsg = "Error: receiving mouse reports\n"; + } + break; + + default: + return 0; + } + return snprintf(buf, PAGE_SIZE, retMsg); +} + +static DEVICE_ATTR(diagnostic, S_IRUGO, show_tabletDiagnosticMessage, NULL); + +/*********************************************************************** + * support routines for the 'stylus_upper' file. Note that this file + * both displays current setting and allows for setting changing. + */ + +static struct aiptek_map stylus_button_map[] = { + { "upper", AIPTEK_STYLUS_UPPER_BUTTON }, + { "lower", AIPTEK_STYLUS_LOWER_BUTTON }, + { NULL, AIPTEK_INVALID_VALUE } +}; + +static ssize_t show_tabletStylusUpper(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct aiptek *aiptek = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%s\n", + map_val_to_str(stylus_button_map, + aiptek->curSetting.stylusButtonUpper)); +} + +static ssize_t +store_tabletStylusUpper(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct aiptek *aiptek = dev_get_drvdata(dev); + int new_button = map_str_to_val(stylus_button_map, buf, count); + + if (new_button == AIPTEK_INVALID_VALUE) + return -EINVAL; + + aiptek->newSetting.stylusButtonUpper = new_button; + return count; +} + +static DEVICE_ATTR(stylus_upper, + S_IRUGO | S_IWUGO, + show_tabletStylusUpper, store_tabletStylusUpper); + +/*********************************************************************** + * support routines for the 'stylus_lower' file. Note that this file + * both displays current setting and allows for setting changing. + */ + +static ssize_t show_tabletStylusLower(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct aiptek *aiptek = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%s\n", + map_val_to_str(stylus_button_map, + aiptek->curSetting.stylusButtonLower)); +} + +static ssize_t +store_tabletStylusLower(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct aiptek *aiptek = dev_get_drvdata(dev); + int new_button = map_str_to_val(stylus_button_map, buf, count); + + if (new_button == AIPTEK_INVALID_VALUE) + return -EINVAL; + + aiptek->newSetting.stylusButtonLower = new_button; + return count; +} + +static DEVICE_ATTR(stylus_lower, + S_IRUGO | S_IWUGO, + show_tabletStylusLower, store_tabletStylusLower); + +/*********************************************************************** + * support routines for the 'mouse_left' file. Note that this file + * both displays current setting and allows for setting changing. + */ + +static struct aiptek_map mouse_button_map[] = { + { "left", AIPTEK_MOUSE_LEFT_BUTTON }, + { "middle", AIPTEK_MOUSE_MIDDLE_BUTTON }, + { "right", AIPTEK_MOUSE_RIGHT_BUTTON }, + { NULL, AIPTEK_INVALID_VALUE } +}; + +static ssize_t show_tabletMouseLeft(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct aiptek *aiptek = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%s\n", + map_val_to_str(mouse_button_map, + aiptek->curSetting.mouseButtonLeft)); +} + +static ssize_t +store_tabletMouseLeft(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct aiptek *aiptek = dev_get_drvdata(dev); + int new_button = map_str_to_val(mouse_button_map, buf, count); + + if (new_button == AIPTEK_INVALID_VALUE) + return -EINVAL; + + aiptek->newSetting.mouseButtonLeft = new_button; + return count; +} + +static DEVICE_ATTR(mouse_left, + S_IRUGO | S_IWUGO, + show_tabletMouseLeft, store_tabletMouseLeft); + +/*********************************************************************** + * support routines for the 'mouse_middle' file. Note that this file + * both displays current setting and allows for setting changing. + */ +static ssize_t show_tabletMouseMiddle(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct aiptek *aiptek = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%s\n", + map_val_to_str(mouse_button_map, + aiptek->curSetting.mouseButtonMiddle)); +} + +static ssize_t +store_tabletMouseMiddle(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct aiptek *aiptek = dev_get_drvdata(dev); + int new_button = map_str_to_val(mouse_button_map, buf, count); + + if (new_button == AIPTEK_INVALID_VALUE) + return -EINVAL; + + aiptek->newSetting.mouseButtonMiddle = new_button; + return count; +} + +static DEVICE_ATTR(mouse_middle, + S_IRUGO | S_IWUGO, + show_tabletMouseMiddle, store_tabletMouseMiddle); + +/*********************************************************************** + * support routines for the 'mouse_right' file. Note that this file + * both displays current setting and allows for setting changing. + */ +static ssize_t show_tabletMouseRight(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct aiptek *aiptek = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%s\n", + map_val_to_str(mouse_button_map, + aiptek->curSetting.mouseButtonRight)); +} + +static ssize_t +store_tabletMouseRight(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct aiptek *aiptek = dev_get_drvdata(dev); + int new_button = map_str_to_val(mouse_button_map, buf, count); + + if (new_button == AIPTEK_INVALID_VALUE) + return -EINVAL; + + aiptek->newSetting.mouseButtonRight = new_button; + return count; +} + +static DEVICE_ATTR(mouse_right, + S_IRUGO | S_IWUGO, + show_tabletMouseRight, store_tabletMouseRight); + +/*********************************************************************** + * support routines for the 'wheel' file. Note that this file + * both displays current setting and allows for setting changing. + */ +static ssize_t show_tabletWheel(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct aiptek *aiptek = dev_get_drvdata(dev); + + if (aiptek->curSetting.wheel == AIPTEK_WHEEL_DISABLE) { + return snprintf(buf, PAGE_SIZE, "disable\n"); + } else { + return snprintf(buf, PAGE_SIZE, "%d\n", + aiptek->curSetting.wheel); + } +} + +static ssize_t +store_tabletWheel(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct aiptek *aiptek = dev_get_drvdata(dev); + long w; + + if (strict_strtol(buf, 10, &w)) return -EINVAL; + + aiptek->newSetting.wheel = (int)w; + return count; +} + +static DEVICE_ATTR(wheel, + S_IRUGO | S_IWUGO, show_tabletWheel, store_tabletWheel); + +/*********************************************************************** + * support routines for the 'execute' file. Note that this file + * both displays current setting and allows for setting changing. + */ +static ssize_t show_tabletExecute(struct device *dev, struct device_attribute *attr, char *buf) +{ + /* There is nothing useful to display, so a one-line manual + * is in order... + */ + return snprintf(buf, PAGE_SIZE, + "Write anything to this file to program your tablet.\n"); +} + +static ssize_t +store_tabletExecute(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct aiptek *aiptek = dev_get_drvdata(dev); + + /* We do not care what you write to this file. Merely the action + * of writing to this file triggers a tablet reprogramming. + */ + memcpy(&aiptek->curSetting, &aiptek->newSetting, + sizeof(struct aiptek_settings)); + + if (aiptek_program_tablet(aiptek) < 0) + return -EIO; + + return count; +} + +static DEVICE_ATTR(execute, + S_IRUGO | S_IWUGO, show_tabletExecute, store_tabletExecute); + +/*********************************************************************** + * support routines for the 'odm_code' file. Note that this file + * only displays current setting. + */ +static ssize_t show_tabletODMCode(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct aiptek *aiptek = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "0x%04x\n", aiptek->features.odmCode); +} + +static DEVICE_ATTR(odm_code, S_IRUGO, show_tabletODMCode, NULL); + +/*********************************************************************** + * support routines for the 'model_code' file. Note that this file + * only displays current setting. + */ +static ssize_t show_tabletModelCode(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct aiptek *aiptek = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "0x%04x\n", aiptek->features.modelCode); +} + +static DEVICE_ATTR(model_code, S_IRUGO, show_tabletModelCode, NULL); + +/*********************************************************************** + * support routines for the 'firmware_code' file. Note that this file + * only displays current setting. + */ +static ssize_t show_firmwareCode(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct aiptek *aiptek = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%04x\n", + aiptek->features.firmwareCode); +} + +static DEVICE_ATTR(firmware_code, S_IRUGO, show_firmwareCode, NULL); + +static struct attribute *aiptek_attributes[] = { + &dev_attr_size.attr, + &dev_attr_pointer_mode.attr, + &dev_attr_coordinate_mode.attr, + &dev_attr_tool_mode.attr, + &dev_attr_xtilt.attr, + &dev_attr_ytilt.attr, + &dev_attr_jitter.attr, + &dev_attr_delay.attr, + &dev_attr_event_count.attr, + &dev_attr_diagnostic.attr, + &dev_attr_odm_code.attr, + &dev_attr_model_code.attr, + &dev_attr_firmware_code.attr, + &dev_attr_stylus_lower.attr, + &dev_attr_stylus_upper.attr, + &dev_attr_mouse_left.attr, + &dev_attr_mouse_middle.attr, + &dev_attr_mouse_right.attr, + &dev_attr_wheel.attr, + &dev_attr_execute.attr, + NULL +}; + +static struct attribute_group aiptek_attribute_group = { + .attrs = aiptek_attributes, +}; + +/*********************************************************************** + * This routine is called when a tablet has been identified. It basically + * sets up the tablet and the driver's internal structures. + */ +static int +aiptek_probe(struct usb_interface *intf, const struct usb_device_id *id) +{ + struct usb_device *usbdev = interface_to_usbdev(intf); + struct usb_endpoint_descriptor *endpoint; + struct aiptek *aiptek; + struct input_dev *inputdev; + int i; + int speeds[] = { 0, + AIPTEK_PROGRAMMABLE_DELAY_50, + AIPTEK_PROGRAMMABLE_DELAY_400, + AIPTEK_PROGRAMMABLE_DELAY_25, + AIPTEK_PROGRAMMABLE_DELAY_100, + AIPTEK_PROGRAMMABLE_DELAY_200, + AIPTEK_PROGRAMMABLE_DELAY_300 + }; + int err = -ENOMEM; + + /* programmableDelay is where the command-line specified + * delay is kept. We make it the first element of speeds[], + * so therefore, your override speed is tried first, then the + * remainder. Note that the default value of 400ms will be tried + * if you do not specify any command line parameter. + */ + speeds[0] = programmableDelay; + + aiptek = kzalloc(sizeof(struct aiptek), GFP_KERNEL); + inputdev = input_allocate_device(); + if (!aiptek || !inputdev) { + dev_warn(&intf->dev, + "cannot allocate memory or input device\n"); + goto fail1; + } + + aiptek->data = usb_buffer_alloc(usbdev, AIPTEK_PACKET_LENGTH, + GFP_ATOMIC, &aiptek->data_dma); + if (!aiptek->data) { + dev_warn(&intf->dev, "cannot allocate usb buffer\n"); + goto fail1; + } + + aiptek->urb = usb_alloc_urb(0, GFP_KERNEL); + if (!aiptek->urb) { + dev_warn(&intf->dev, "cannot allocate urb\n"); + goto fail2; + } + + aiptek->inputdev = inputdev; + aiptek->usbdev = usbdev; + aiptek->ifnum = intf->altsetting[0].desc.bInterfaceNumber; + aiptek->inDelay = 0; + aiptek->endDelay = 0; + aiptek->previousJitterable = 0; + aiptek->lastMacro = -1; + + /* Set up the curSettings struct. Said struct contains the current + * programmable parameters. The newSetting struct contains changes + * the user makes to the settings via the sysfs interface. Those + * changes are not "committed" to curSettings until the user + * writes to the sysfs/.../execute file. + */ + aiptek->curSetting.pointerMode = AIPTEK_POINTER_EITHER_MODE; + aiptek->curSetting.coordinateMode = AIPTEK_COORDINATE_ABSOLUTE_MODE; + aiptek->curSetting.toolMode = AIPTEK_TOOL_BUTTON_PEN_MODE; + aiptek->curSetting.xTilt = AIPTEK_TILT_DISABLE; + aiptek->curSetting.yTilt = AIPTEK_TILT_DISABLE; + aiptek->curSetting.mouseButtonLeft = AIPTEK_MOUSE_LEFT_BUTTON; + aiptek->curSetting.mouseButtonMiddle = AIPTEK_MOUSE_MIDDLE_BUTTON; + aiptek->curSetting.mouseButtonRight = AIPTEK_MOUSE_RIGHT_BUTTON; + aiptek->curSetting.stylusButtonUpper = AIPTEK_STYLUS_UPPER_BUTTON; + aiptek->curSetting.stylusButtonLower = AIPTEK_STYLUS_LOWER_BUTTON; + aiptek->curSetting.jitterDelay = jitterDelay; + aiptek->curSetting.programmableDelay = programmableDelay; + + /* Both structs should have equivalent settings + */ + aiptek->newSetting = aiptek->curSetting; + + /* Determine the usb devices' physical path. + * Asketh not why we always pretend we're using "../input0", + * but I suspect this will have to be refactored one + * day if a single USB device can be a keyboard & a mouse + * & a tablet, and the inputX number actually will tell + * us something... + */ + usb_make_path(usbdev, aiptek->features.usbPath, + sizeof(aiptek->features.usbPath)); + strlcat(aiptek->features.usbPath, "/input0", + sizeof(aiptek->features.usbPath)); + + /* Set up client data, pointers to open and close routines + * for the input device. + */ + inputdev->name = "Aiptek"; + inputdev->phys = aiptek->features.usbPath; + usb_to_input_id(usbdev, &inputdev->id); + inputdev->dev.parent = &intf->dev; + + input_set_drvdata(inputdev, aiptek); + + inputdev->open = aiptek_open; + inputdev->close = aiptek_close; + + /* Now program the capacities of the tablet, in terms of being + * an input device. + */ + for (i = 0; i < ARRAY_SIZE(eventTypes); ++i) + __set_bit(eventTypes[i], inputdev->evbit); + + for (i = 0; i < ARRAY_SIZE(absEvents); ++i) + __set_bit(absEvents[i], inputdev->absbit); + + for (i = 0; i < ARRAY_SIZE(relEvents); ++i) + __set_bit(relEvents[i], inputdev->relbit); + + __set_bit(MSC_SERIAL, inputdev->mscbit); + + /* Set up key and button codes */ + for (i = 0; i < ARRAY_SIZE(buttonEvents); ++i) + __set_bit(buttonEvents[i], inputdev->keybit); + + for (i = 0; i < ARRAY_SIZE(macroKeyEvents); ++i) + __set_bit(macroKeyEvents[i], inputdev->keybit); + + /* + * Program the input device coordinate capacities. We do not yet + * know what maximum X, Y, and Z values are, so we're putting fake + * values in. Later, we'll ask the tablet to put in the correct + * values. + */ + input_set_abs_params(inputdev, ABS_X, 0, 2999, 0, 0); + input_set_abs_params(inputdev, ABS_Y, 0, 2249, 0, 0); + input_set_abs_params(inputdev, ABS_PRESSURE, 0, 511, 0, 0); + input_set_abs_params(inputdev, ABS_TILT_X, AIPTEK_TILT_MIN, AIPTEK_TILT_MAX, 0, 0); + input_set_abs_params(inputdev, ABS_TILT_Y, AIPTEK_TILT_MIN, AIPTEK_TILT_MAX, 0, 0); + input_set_abs_params(inputdev, ABS_WHEEL, AIPTEK_WHEEL_MIN, AIPTEK_WHEEL_MAX - 1, 0, 0); + + endpoint = &intf->altsetting[0].endpoint[0].desc; + + /* Go set up our URB, which is called when the tablet receives + * input. + */ + usb_fill_int_urb(aiptek->urb, + aiptek->usbdev, + usb_rcvintpipe(aiptek->usbdev, + endpoint->bEndpointAddress), + aiptek->data, 8, aiptek_irq, aiptek, + endpoint->bInterval); + + aiptek->urb->transfer_dma = aiptek->data_dma; + aiptek->urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + + /* Program the tablet. This sets the tablet up in the mode + * specified in newSetting, and also queries the tablet's + * physical capacities. + * + * Sanity check: if a tablet doesn't like the slow programmatic + * delay, we often get sizes of 0x0. Let's use that as an indicator + * to try faster delays, up to 25 ms. If that logic fails, well, you'll + * have to explain to us how your tablet thinks it's 0x0, and yet that's + * not an error :-) + */ + + for (i = 0; i < ARRAY_SIZE(speeds); ++i) { + aiptek->curSetting.programmableDelay = speeds[i]; + (void)aiptek_program_tablet(aiptek); + if (aiptek->inputdev->absmax[ABS_X] > 0) { + dev_info(&intf->dev, + "Aiptek using %d ms programming speed\n", + aiptek->curSetting.programmableDelay); + break; + } + } + + /* Murphy says that some day someone will have a tablet that fails the + above test. That's you, Frederic Rodrigo */ + if (i == ARRAY_SIZE(speeds)) { + dev_info(&intf->dev, + "Aiptek tried all speeds, no sane response\n"); + goto fail2; + } + + /* Associate this driver's struct with the usb interface. + */ + usb_set_intfdata(intf, aiptek); + + /* Set up the sysfs files + */ + err = sysfs_create_group(&intf->dev.kobj, &aiptek_attribute_group); + if (err) { + dev_warn(&intf->dev, "cannot create sysfs group err: %d\n", + err); + goto fail3; + } + + /* Register the tablet as an Input Device + */ + err = input_register_device(aiptek->inputdev); + if (err) { + dev_warn(&intf->dev, + "input_register_device returned err: %d\n", err); + goto fail4; + } + return 0; + + fail4: sysfs_remove_group(&intf->dev.kobj, &aiptek_attribute_group); + fail3: usb_free_urb(aiptek->urb); + fail2: usb_buffer_free(usbdev, AIPTEK_PACKET_LENGTH, aiptek->data, + aiptek->data_dma); + fail1: usb_set_intfdata(intf, NULL); + input_free_device(inputdev); + kfree(aiptek); + return err; +} + +/*********************************************************************** + * Deal with tablet disconnecting from the system. + */ +static void aiptek_disconnect(struct usb_interface *intf) +{ + struct aiptek *aiptek = usb_get_intfdata(intf); + + /* Disassociate driver's struct with usb interface + */ + usb_set_intfdata(intf, NULL); + if (aiptek != NULL) { + /* Free & unhook everything from the system. + */ + usb_kill_urb(aiptek->urb); + input_unregister_device(aiptek->inputdev); + sysfs_remove_group(&intf->dev.kobj, &aiptek_attribute_group); + usb_free_urb(aiptek->urb); + usb_buffer_free(interface_to_usbdev(intf), + AIPTEK_PACKET_LENGTH, + aiptek->data, aiptek->data_dma); + kfree(aiptek); + } +} + +static struct usb_driver aiptek_driver = { + .name = "aiptek", + .probe = aiptek_probe, + .disconnect = aiptek_disconnect, + .id_table = aiptek_ids, +}; + +static int __init aiptek_init(void) +{ + int result = usb_register(&aiptek_driver); + if (result == 0) { + printk(KERN_INFO KBUILD_MODNAME ": " DRIVER_VERSION ":" + DRIVER_DESC "\n"); + printk(KERN_INFO KBUILD_MODNAME ": " DRIVER_AUTHOR "\n"); + } + return result; +} + +static void __exit aiptek_exit(void) +{ + usb_deregister(&aiptek_driver); +} + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +module_param(programmableDelay, int, 0); +MODULE_PARM_DESC(programmableDelay, "delay used during tablet programming"); +module_param(jitterDelay, int, 0); +MODULE_PARM_DESC(jitterDelay, "stylus/mouse settlement delay"); + +module_init(aiptek_init); +module_exit(aiptek_exit); diff --git a/drivers/input/tablet/gtco.c b/drivers/input/tablet/gtco.c new file mode 100644 index 0000000..5524e01 --- /dev/null +++ b/drivers/input/tablet/gtco.c @@ -0,0 +1,1053 @@ +/* -*- linux-c -*- + +GTCO digitizer USB driver + +Use the err() and dbg() macros from usb.h for system logging + +TO CHECK: Is pressure done right on report 5? + +Copyright (C) 2006 GTCO CalComp + +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; version 2 +of the License. + +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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +Permission to use, copy, modify, distribute, and sell this software and its +documentation for any purpose is hereby granted without fee, provided that +the above copyright notice appear in all copies and that both that +copyright notice and this permission notice appear in supporting +documentation, and that the name of GTCO-CalComp not be used in advertising +or publicity pertaining to distribution of the software without specific, +written prior permission. GTCO-CalComp makes no representations about the +suitability of this software for any purpose. It is provided "as is" +without express or implied warranty. + +GTCO-CALCOMP DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, +INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO +EVENT SHALL GTCO-CALCOMP BE LIABLE FOR ANY SPECIAL, INDIRECT OR +CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, +DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTIONS, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. + +GTCO CalComp, Inc. +7125 Riverwood Drive +Columbia, MD 21046 + +Jeremy Roberson jroberson@gtcocalcomp.com +Scott Hill shill@gtcocalcomp.com +*/ + + + +/*#define DEBUG*/ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/usb.h> +#include <asm/uaccess.h> +#include <asm/unaligned.h> +#include <asm/byteorder.h> + + +#include <linux/usb/input.h> + +/* Version with a Major number of 2 is for kernel inclusion only. */ +#define GTCO_VERSION "2.00.0006" + + +/* MACROS */ + +#define VENDOR_ID_GTCO 0x078C +#define PID_400 0x400 +#define PID_401 0x401 +#define PID_1000 0x1000 +#define PID_1001 0x1001 +#define PID_1002 0x1002 + +/* Max size of a single report */ +#define REPORT_MAX_SIZE 10 + + +/* Bitmask whether pen is in range */ +#define MASK_INRANGE 0x20 +#define MASK_BUTTON 0x01F + +#define PATHLENGTH 64 + +/* DATA STRUCTURES */ + +/* Device table */ +static struct usb_device_id gtco_usbid_table [] = { + { USB_DEVICE(VENDOR_ID_GTCO, PID_400) }, + { USB_DEVICE(VENDOR_ID_GTCO, PID_401) }, + { USB_DEVICE(VENDOR_ID_GTCO, PID_1000) }, + { USB_DEVICE(VENDOR_ID_GTCO, PID_1001) }, + { USB_DEVICE(VENDOR_ID_GTCO, PID_1002) }, + { } +}; +MODULE_DEVICE_TABLE (usb, gtco_usbid_table); + + +/* Structure to hold all of our device specific stuff */ +struct gtco { + + struct input_dev *inputdevice; /* input device struct pointer */ + struct usb_device *usbdev; /* the usb device for this device */ + struct urb *urbinfo; /* urb for incoming reports */ + dma_addr_t buf_dma; /* dma addr of the data buffer*/ + unsigned char * buffer; /* databuffer for reports */ + + char usbpath[PATHLENGTH]; + int openCount; + + /* Information pulled from Report Descriptor */ + u32 usage; + u32 min_X; + u32 max_X; + u32 min_Y; + u32 max_Y; + s8 mintilt_X; + s8 maxtilt_X; + s8 mintilt_Y; + s8 maxtilt_Y; + u32 maxpressure; + u32 minpressure; +}; + + + +/* Code for parsing the HID REPORT DESCRIPTOR */ + +/* From HID1.11 spec */ +struct hid_descriptor +{ + struct usb_descriptor_header header; + __le16 bcdHID; + u8 bCountryCode; + u8 bNumDescriptors; + u8 bDescriptorType; + __le16 wDescriptorLength; +} __attribute__ ((packed)); + + +#define HID_DESCRIPTOR_SIZE 9 +#define HID_DEVICE_TYPE 33 +#define REPORT_DEVICE_TYPE 34 + + +#define PREF_TAG(x) ((x)>>4) +#define PREF_TYPE(x) ((x>>2)&0x03) +#define PREF_SIZE(x) ((x)&0x03) + +#define TYPE_MAIN 0 +#define TYPE_GLOBAL 1 +#define TYPE_LOCAL 2 +#define TYPE_RESERVED 3 + +#define TAG_MAIN_INPUT 0x8 +#define TAG_MAIN_OUTPUT 0x9 +#define TAG_MAIN_FEATURE 0xB +#define TAG_MAIN_COL_START 0xA +#define TAG_MAIN_COL_END 0xC + +#define TAG_GLOB_USAGE 0 +#define TAG_GLOB_LOG_MIN 1 +#define TAG_GLOB_LOG_MAX 2 +#define TAG_GLOB_PHYS_MIN 3 +#define TAG_GLOB_PHYS_MAX 4 +#define TAG_GLOB_UNIT_EXP 5 +#define TAG_GLOB_UNIT 6 +#define TAG_GLOB_REPORT_SZ 7 +#define TAG_GLOB_REPORT_ID 8 +#define TAG_GLOB_REPORT_CNT 9 +#define TAG_GLOB_PUSH 10 +#define TAG_GLOB_POP 11 + +#define TAG_GLOB_MAX 12 + +#define DIGITIZER_USAGE_TIP_PRESSURE 0x30 +#define DIGITIZER_USAGE_TILT_X 0x3D +#define DIGITIZER_USAGE_TILT_Y 0x3E + + +/* + * This is an abbreviated parser for the HID Report Descriptor. We + * know what devices we are talking to, so this is by no means meant + * to be generic. We can make some safe assumptions: + * + * - We know there are no LONG tags, all short + * - We know that we have no MAIN Feature and MAIN Output items + * - We know what the IRQ reports are supposed to look like. + * + * The main purpose of this is to use the HID report desc to figure + * out the mins and maxs of the fields in the IRQ reports. The IRQ + * reports for 400/401 change slightly if the max X is bigger than 64K. + * + */ +static void parse_hid_report_descriptor(struct gtco *device, char * report, + int length) +{ + int x, i = 0; + + /* Tag primitive vars */ + __u8 prefix; + __u8 size; + __u8 tag; + __u8 type; + __u8 data = 0; + __u16 data16 = 0; + __u32 data32 = 0; + + /* For parsing logic */ + int inputnum = 0; + __u32 usage = 0; + + /* Global Values, indexed by TAG */ + __u32 globalval[TAG_GLOB_MAX]; + __u32 oldval[TAG_GLOB_MAX]; + + /* Debug stuff */ + char maintype = 'x'; + char globtype[12]; + int indent = 0; + char indentstr[10] = ""; + + + dbg("======>>>>>>PARSE<<<<<<======"); + + /* Walk this report and pull out the info we need */ + while (i < length) { + prefix = report[i]; + + /* Skip over prefix */ + i++; + + /* Determine data size and save the data in the proper variable */ + size = PREF_SIZE(prefix); + switch (size) { + case 1: + data = report[i]; + break; + case 2: + data16 = get_unaligned_le16(&report[i]); + break; + case 3: + size = 4; + data32 = get_unaligned_le32(&report[i]); + break; + } + + /* Skip size of data */ + i += size; + + /* What we do depends on the tag type */ + tag = PREF_TAG(prefix); + type = PREF_TYPE(prefix); + switch (type) { + case TYPE_MAIN: + strcpy(globtype, ""); + switch (tag) { + + case TAG_MAIN_INPUT: + /* + * The INPUT MAIN tag signifies this is + * information from a report. We need to + * figure out what it is and store the + * min/max values + */ + + maintype = 'I'; + if (data == 2) + strcpy(globtype, "Variable"); + else if (data == 3) + strcpy(globtype, "Var|Const"); + + dbg("::::: Saving Report: %d input #%d Max: 0x%X(%d) Min:0x%X(%d) of %d bits", + globalval[TAG_GLOB_REPORT_ID], inputnum, + globalval[TAG_GLOB_LOG_MAX], globalval[TAG_GLOB_LOG_MAX], + globalval[TAG_GLOB_LOG_MIN], globalval[TAG_GLOB_LOG_MIN], + globalval[TAG_GLOB_REPORT_SZ] * globalval[TAG_GLOB_REPORT_CNT]); + + + /* + We can assume that the first two input items + are always the X and Y coordinates. After + that, we look for everything else by + local usage value + */ + switch (inputnum) { + case 0: /* X coord */ + dbg("GER: X Usage: 0x%x", usage); + if (device->max_X == 0) { + device->max_X = globalval[TAG_GLOB_LOG_MAX]; + device->min_X = globalval[TAG_GLOB_LOG_MIN]; + } + break; + + case 1: /* Y coord */ + dbg("GER: Y Usage: 0x%x", usage); + if (device->max_Y == 0) { + device->max_Y = globalval[TAG_GLOB_LOG_MAX]; + device->min_Y = globalval[TAG_GLOB_LOG_MIN]; + } + break; + + default: + /* Tilt X */ + if (usage == DIGITIZER_USAGE_TILT_X) { + if (device->maxtilt_X == 0) { + device->maxtilt_X = globalval[TAG_GLOB_LOG_MAX]; + device->mintilt_X = globalval[TAG_GLOB_LOG_MIN]; + } + } + + /* Tilt Y */ + if (usage == DIGITIZER_USAGE_TILT_Y) { + if (device->maxtilt_Y == 0) { + device->maxtilt_Y = globalval[TAG_GLOB_LOG_MAX]; + device->mintilt_Y = globalval[TAG_GLOB_LOG_MIN]; + } + } + + /* Pressure */ + if (usage == DIGITIZER_USAGE_TIP_PRESSURE) { + if (device->maxpressure == 0) { + device->maxpressure = globalval[TAG_GLOB_LOG_MAX]; + device->minpressure = globalval[TAG_GLOB_LOG_MIN]; + } + } + + break; + } + + inputnum++; + break; + + case TAG_MAIN_OUTPUT: + maintype = 'O'; + break; + + case TAG_MAIN_FEATURE: + maintype = 'F'; + break; + + case TAG_MAIN_COL_START: + maintype = 'S'; + + if (data == 0) { + dbg("======>>>>>> Physical"); + strcpy(globtype, "Physical"); + } else + dbg("======>>>>>>"); + + /* Indent the debug output */ + indent++; + for (x = 0; x < indent; x++) + indentstr[x] = '-'; + indentstr[x] = 0; + + /* Save global tags */ + for (x = 0; x < TAG_GLOB_MAX; x++) + oldval[x] = globalval[x]; + + break; + + case TAG_MAIN_COL_END: + dbg("<<<<<<======"); + maintype = 'E'; + indent--; + for (x = 0; x < indent; x++) + indentstr[x] = '-'; + indentstr[x] = 0; + + /* Copy global tags back */ + for (x = 0; x < TAG_GLOB_MAX; x++) + globalval[x] = oldval[x]; + + break; + } + + switch (size) { + case 1: + dbg("%sMAINTAG:(%d) %c SIZE: %d Data: %s 0x%x", + indentstr, tag, maintype, size, globtype, data); + break; + + case 2: + dbg("%sMAINTAG:(%d) %c SIZE: %d Data: %s 0x%x", + indentstr, tag, maintype, size, globtype, data16); + break; + + case 4: + dbg("%sMAINTAG:(%d) %c SIZE: %d Data: %s 0x%x", + indentstr, tag, maintype, size, globtype, data32); + break; + } + break; + + case TYPE_GLOBAL: + switch (tag) { + case TAG_GLOB_USAGE: + /* + * First time we hit the global usage tag, + * it should tell us the type of device + */ + if (device->usage == 0) + device->usage = data; + + strcpy(globtype, "USAGE"); + break; + + case TAG_GLOB_LOG_MIN: + strcpy(globtype, "LOG_MIN"); + break; + + case TAG_GLOB_LOG_MAX: + strcpy(globtype, "LOG_MAX"); + break; + + case TAG_GLOB_PHYS_MIN: + strcpy(globtype, "PHYS_MIN"); + break; + + case TAG_GLOB_PHYS_MAX: + strcpy(globtype, "PHYS_MAX"); + break; + + case TAG_GLOB_UNIT_EXP: + strcpy(globtype, "EXP"); + break; + + case TAG_GLOB_UNIT: + strcpy(globtype, "UNIT"); + break; + + case TAG_GLOB_REPORT_SZ: + strcpy(globtype, "REPORT_SZ"); + break; + + case TAG_GLOB_REPORT_ID: + strcpy(globtype, "REPORT_ID"); + /* New report, restart numbering */ + inputnum = 0; + break; + + case TAG_GLOB_REPORT_CNT: + strcpy(globtype, "REPORT_CNT"); + break; + + case TAG_GLOB_PUSH: + strcpy(globtype, "PUSH"); + break; + + case TAG_GLOB_POP: + strcpy(globtype, "POP"); + break; + } + + /* Check to make sure we have a good tag number + so we don't overflow array */ + if (tag < TAG_GLOB_MAX) { + switch (size) { + case 1: + dbg("%sGLOBALTAG:%s(%d) SIZE: %d Data: 0x%x", + indentstr, globtype, tag, size, data); + globalval[tag] = data; + break; + + case 2: + dbg("%sGLOBALTAG:%s(%d) SIZE: %d Data: 0x%x", + indentstr, globtype, tag, size, data16); + globalval[tag] = data16; + break; + + case 4: + dbg("%sGLOBALTAG:%s(%d) SIZE: %d Data: 0x%x", + indentstr, globtype, tag, size, data32); + globalval[tag] = data32; + break; + } + } else { + dbg("%sGLOBALTAG: ILLEGAL TAG:%d SIZE: %d ", + indentstr, tag, size); + } + break; + + case TYPE_LOCAL: + switch (tag) { + case TAG_GLOB_USAGE: + strcpy(globtype, "USAGE"); + /* Always 1 byte */ + usage = data; + break; + + case TAG_GLOB_LOG_MIN: + strcpy(globtype, "MIN"); + break; + + case TAG_GLOB_LOG_MAX: + strcpy(globtype, "MAX"); + break; + + default: + strcpy(globtype, "UNKNOWN"); + break; + } + + switch (size) { + case 1: + dbg("%sLOCALTAG:(%d) %s SIZE: %d Data: 0x%x", + indentstr, tag, globtype, size, data); + break; + + case 2: + dbg("%sLOCALTAG:(%d) %s SIZE: %d Data: 0x%x", + indentstr, tag, globtype, size, data16); + break; + + case 4: + dbg("%sLOCALTAG:(%d) %s SIZE: %d Data: 0x%x", + indentstr, tag, globtype, size, data32); + break; + } + + break; + } + } +} + +/* INPUT DRIVER Routines */ + +/* + * Called when opening the input device. This will submit the URB to + * the usb system so we start getting reports + */ +static int gtco_input_open(struct input_dev *inputdev) +{ + struct gtco *device = input_get_drvdata(inputdev); + + device->urbinfo->dev = device->usbdev; + if (usb_submit_urb(device->urbinfo, GFP_KERNEL)) + return -EIO; + + return 0; +} + +/* + * Called when closing the input device. This will unlink the URB + */ +static void gtco_input_close(struct input_dev *inputdev) +{ + struct gtco *device = input_get_drvdata(inputdev); + + usb_kill_urb(device->urbinfo); +} + + +/* + * Setup input device capabilities. Tell the input system what this + * device is capable of generating. + * + * This information is based on what is read from the HID report and + * placed in the struct gtco structure + * + */ +static void gtco_setup_caps(struct input_dev *inputdev) +{ + struct gtco *device = input_get_drvdata(inputdev); + + /* Which events */ + inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS) | + BIT_MASK(EV_MSC); + + /* Misc event menu block */ + inputdev->mscbit[0] = BIT_MASK(MSC_SCAN) | BIT_MASK(MSC_SERIAL) | + BIT_MASK(MSC_RAW); + + /* Absolute values based on HID report info */ + input_set_abs_params(inputdev, ABS_X, device->min_X, device->max_X, + 0, 0); + input_set_abs_params(inputdev, ABS_Y, device->min_Y, device->max_Y, + 0, 0); + + /* Proximity */ + input_set_abs_params(inputdev, ABS_DISTANCE, 0, 1, 0, 0); + + /* Tilt & pressure */ + input_set_abs_params(inputdev, ABS_TILT_X, device->mintilt_X, + device->maxtilt_X, 0, 0); + input_set_abs_params(inputdev, ABS_TILT_Y, device->mintilt_Y, + device->maxtilt_Y, 0, 0); + input_set_abs_params(inputdev, ABS_PRESSURE, device->minpressure, + device->maxpressure, 0, 0); + + /* Transducer */ + input_set_abs_params(inputdev, ABS_MISC, 0, 0xFF, 0, 0); +} + +/* USB Routines */ + +/* + * URB callback routine. Called when we get IRQ reports from the + * digitizer. + * + * This bridges the USB and input device worlds. It generates events + * on the input device based on the USB reports. + */ +static void gtco_urb_callback(struct urb *urbinfo) +{ + struct gtco *device = urbinfo->context; + struct input_dev *inputdev; + int rc; + u32 val = 0; + s8 valsigned = 0; + char le_buffer[2]; + + inputdev = device->inputdevice; + + /* Was callback OK? */ + if (urbinfo->status == -ECONNRESET || + urbinfo->status == -ENOENT || + urbinfo->status == -ESHUTDOWN) { + + /* Shutdown is occurring. Return and don't queue up any more */ + return; + } + + if (urbinfo->status != 0) { + /* + * Some unknown error. Hopefully temporary. Just go and + * requeue an URB + */ + goto resubmit; + } + + /* + * Good URB, now process + */ + + /* PID dependent when we interpret the report */ + if (inputdev->id.product == PID_1000 || + inputdev->id.product == PID_1001 || + inputdev->id.product == PID_1002) { + + /* + * Switch on the report ID + * Conveniently, the reports have more information, the higher + * the report number. We can just fall through the case + * statements if we start with the highest number report + */ + switch (device->buffer[0]) { + case 5: + /* Pressure is 9 bits */ + val = ((u16)(device->buffer[8]) << 1); + val |= (u16)(device->buffer[7] >> 7); + input_report_abs(inputdev, ABS_PRESSURE, + device->buffer[8]); + + /* Mask out the Y tilt value used for pressure */ + device->buffer[7] = (u8)((device->buffer[7]) & 0x7F); + + /* Fall thru */ + case 4: + /* Tilt */ + + /* Sign extend these 7 bit numbers. */ + if (device->buffer[6] & 0x40) + device->buffer[6] |= 0x80; + + if (device->buffer[7] & 0x40) + device->buffer[7] |= 0x80; + + + valsigned = (device->buffer[6]); + input_report_abs(inputdev, ABS_TILT_X, (s32)valsigned); + + valsigned = (device->buffer[7]); + input_report_abs(inputdev, ABS_TILT_Y, (s32)valsigned); + + /* Fall thru */ + case 2: + case 3: + /* Convert buttons, only 5 bits possible */ + val = (device->buffer[5]) & MASK_BUTTON; + + /* We don't apply any meaning to the bitmask, + just report */ + input_event(inputdev, EV_MSC, MSC_SERIAL, val); + + /* Fall thru */ + case 1: + /* All reports have X and Y coords in the same place */ + val = get_unaligned_le16(&device->buffer[1]); + input_report_abs(inputdev, ABS_X, val); + + val = get_unaligned_le16(&device->buffer[3]); + input_report_abs(inputdev, ABS_Y, val); + + /* Ditto for proximity bit */ + val = device->buffer[5] & MASK_INRANGE ? 1 : 0; + input_report_abs(inputdev, ABS_DISTANCE, val); + + /* Report 1 is an exception to how we handle buttons */ + /* Buttons are an index, not a bitmask */ + if (device->buffer[0] == 1) { + + /* + * Convert buttons, 5 bit index + * Report value of index set as one, + * the rest as 0 + */ + val = device->buffer[5] & MASK_BUTTON; + dbg("======>>>>>>REPORT 1: val 0x%X(%d)", + val, val); + + /* + * We don't apply any meaning to the button + * index, just report it + */ + input_event(inputdev, EV_MSC, MSC_SERIAL, val); + } + break; + + case 7: + /* Menu blocks */ + input_event(inputdev, EV_MSC, MSC_SCAN, + device->buffer[1]); + break; + } + } + + /* Other pid class */ + if (inputdev->id.product == PID_400 || + inputdev->id.product == PID_401) { + + /* Report 2 */ + if (device->buffer[0] == 2) { + /* Menu blocks */ + input_event(inputdev, EV_MSC, MSC_SCAN, device->buffer[1]); + } + + /* Report 1 */ + if (device->buffer[0] == 1) { + char buttonbyte; + + /* IF X max > 64K, we still a bit from the y report */ + if (device->max_X > 0x10000) { + + val = (u16)(((u16)(device->buffer[2] << 8)) | (u8)device->buffer[1]); + val |= (u32)(((u8)device->buffer[3] & 0x1) << 16); + + input_report_abs(inputdev, ABS_X, val); + + le_buffer[0] = (u8)((u8)(device->buffer[3]) >> 1); + le_buffer[0] |= (u8)((device->buffer[3] & 0x1) << 7); + + le_buffer[1] = (u8)(device->buffer[4] >> 1); + le_buffer[1] |= (u8)((device->buffer[5] & 0x1) << 7); + + val = get_unaligned_le16(le_buffer); + input_report_abs(inputdev, ABS_Y, val); + + /* + * Shift the button byte right by one to + * make it look like the standard report + */ + buttonbyte = device->buffer[5] >> 1; + } else { + + val = get_unaligned_le16(&device->buffer[1]); + input_report_abs(inputdev, ABS_X, val); + + val = get_unaligned_le16(&device->buffer[3]); + input_report_abs(inputdev, ABS_Y, val); + + buttonbyte = device->buffer[5]; + } + + /* BUTTONS and PROXIMITY */ + val = buttonbyte & MASK_INRANGE ? 1 : 0; + input_report_abs(inputdev, ABS_DISTANCE, val); + + /* Convert buttons, only 4 bits possible */ + val = buttonbyte & 0x0F; +#ifdef USE_BUTTONS + for (i = 0; i < 5; i++) + input_report_key(inputdev, BTN_DIGI + i, val & (1 << i)); +#else + /* We don't apply any meaning to the bitmask, just report */ + input_event(inputdev, EV_MSC, MSC_SERIAL, val); +#endif + + /* TRANSDUCER */ + input_report_abs(inputdev, ABS_MISC, device->buffer[6]); + } + } + + /* Everybody gets report ID's */ + input_event(inputdev, EV_MSC, MSC_RAW, device->buffer[0]); + + /* Sync it up */ + input_sync(inputdev); + + resubmit: + rc = usb_submit_urb(urbinfo, GFP_ATOMIC); + if (rc != 0) + err("usb_submit_urb failed rc=0x%x", rc); +} + +/* + * The probe routine. This is called when the kernel find the matching USB + * vendor/product. We do the following: + * + * - Allocate mem for a local structure to manage the device + * - Request a HID Report Descriptor from the device and parse it to + * find out the device parameters + * - Create an input device and assign it attributes + * - Allocate an URB so the device can talk to us when the input + * queue is open + */ +static int gtco_probe(struct usb_interface *usbinterface, + const struct usb_device_id *id) +{ + + struct gtco *gtco; + struct input_dev *input_dev; + struct hid_descriptor *hid_desc; + char *report; + int result = 0, retry; + int error; + struct usb_endpoint_descriptor *endpoint; + + /* Allocate memory for device structure */ + gtco = kzalloc(sizeof(struct gtco), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!gtco || !input_dev) { + err("No more memory"); + error = -ENOMEM; + goto err_free_devs; + } + + /* Set pointer to the input device */ + gtco->inputdevice = input_dev; + + /* Save interface information */ + gtco->usbdev = usb_get_dev(interface_to_usbdev(usbinterface)); + + /* Allocate some data for incoming reports */ + gtco->buffer = usb_buffer_alloc(gtco->usbdev, REPORT_MAX_SIZE, + GFP_KERNEL, >co->buf_dma); + if (!gtco->buffer) { + err("No more memory for us buffers"); + error = -ENOMEM; + goto err_free_devs; + } + + /* Allocate URB for reports */ + gtco->urbinfo = usb_alloc_urb(0, GFP_KERNEL); + if (!gtco->urbinfo) { + err("Failed to allocate URB"); + error = -ENOMEM; + goto err_free_buf; + } + + /* + * The endpoint is always altsetting 0, we know this since we know + * this device only has one interrupt endpoint + */ + endpoint = &usbinterface->altsetting[0].endpoint[0].desc; + + /* Some debug */ + dbg("gtco # interfaces: %d", usbinterface->num_altsetting); + dbg("num endpoints: %d", usbinterface->cur_altsetting->desc.bNumEndpoints); + dbg("interface class: %d", usbinterface->cur_altsetting->desc.bInterfaceClass); + dbg("endpoint: attribute:0x%x type:0x%x", endpoint->bmAttributes, endpoint->bDescriptorType); + if ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == USB_ENDPOINT_XFER_INT) + dbg("endpoint: we have interrupt endpoint\n"); + + dbg("endpoint extra len:%d ", usbinterface->altsetting[0].extralen); + + /* + * Find the HID descriptor so we can find out the size of the + * HID report descriptor + */ + if (usb_get_extra_descriptor(usbinterface->cur_altsetting, + HID_DEVICE_TYPE, &hid_desc) != 0){ + err("Can't retrieve exta USB descriptor to get hid report descriptor length"); + error = -EIO; + goto err_free_urb; + } + + dbg("Extra descriptor success: type:%d len:%d", + hid_desc->bDescriptorType, hid_desc->wDescriptorLength); + + report = kzalloc(le16_to_cpu(hid_desc->wDescriptorLength), GFP_KERNEL); + if (!report) { + err("No more memory for report"); + error = -ENOMEM; + goto err_free_urb; + } + + /* Couple of tries to get reply */ + for (retry = 0; retry < 3; retry++) { + result = usb_control_msg(gtco->usbdev, + usb_rcvctrlpipe(gtco->usbdev, 0), + USB_REQ_GET_DESCRIPTOR, + USB_RECIP_INTERFACE | USB_DIR_IN, + REPORT_DEVICE_TYPE << 8, + 0, /* interface */ + report, + le16_to_cpu(hid_desc->wDescriptorLength), + 5000); /* 5 secs */ + + dbg("usb_control_msg result: %d", result); + if (result == le16_to_cpu(hid_desc->wDescriptorLength)) { + parse_hid_report_descriptor(gtco, report, result); + break; + } + } + + kfree(report); + + /* If we didn't get the report, fail */ + if (result != le16_to_cpu(hid_desc->wDescriptorLength)) { + err("Failed to get HID Report Descriptor of size: %d", + hid_desc->wDescriptorLength); + error = -EIO; + goto err_free_urb; + } + + /* Create a device file node */ + usb_make_path(gtco->usbdev, gtco->usbpath, sizeof(gtco->usbpath)); + strlcat(gtco->usbpath, "/input0", sizeof(gtco->usbpath)); + + /* Set Input device functions */ + input_dev->open = gtco_input_open; + input_dev->close = gtco_input_close; + + /* Set input device information */ + input_dev->name = "GTCO_CalComp"; + input_dev->phys = gtco->usbpath; + + input_set_drvdata(input_dev, gtco); + + /* Now set up all the input device capabilities */ + gtco_setup_caps(input_dev); + + /* Set input device required ID information */ + usb_to_input_id(gtco->usbdev, &input_dev->id); + input_dev->dev.parent = &usbinterface->dev; + + /* Setup the URB, it will be posted later on open of input device */ + endpoint = &usbinterface->altsetting[0].endpoint[0].desc; + + usb_fill_int_urb(gtco->urbinfo, + gtco->usbdev, + usb_rcvintpipe(gtco->usbdev, + endpoint->bEndpointAddress), + gtco->buffer, + REPORT_MAX_SIZE, + gtco_urb_callback, + gtco, + endpoint->bInterval); + + gtco->urbinfo->transfer_dma = gtco->buf_dma; + gtco->urbinfo->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + + /* Save gtco pointer in USB interface gtco */ + usb_set_intfdata(usbinterface, gtco); + + /* All done, now register the input device */ + error = input_register_device(input_dev); + if (error) + goto err_free_urb; + + return 0; + + err_free_urb: + usb_free_urb(gtco->urbinfo); + err_free_buf: + usb_buffer_free(gtco->usbdev, REPORT_MAX_SIZE, + gtco->buffer, gtco->buf_dma); + err_free_devs: + input_free_device(input_dev); + kfree(gtco); + return error; +} + +/* + * This function is a standard USB function called when the USB device + * is disconnected. We will get rid of the URV, de-register the input + * device, and free up allocated memory + */ +static void gtco_disconnect(struct usb_interface *interface) +{ + /* Grab private device ptr */ + struct gtco *gtco = usb_get_intfdata(interface); + + /* Now reverse all the registration stuff */ + if (gtco) { + input_unregister_device(gtco->inputdevice); + usb_kill_urb(gtco->urbinfo); + usb_free_urb(gtco->urbinfo); + usb_buffer_free(gtco->usbdev, REPORT_MAX_SIZE, + gtco->buffer, gtco->buf_dma); + kfree(gtco); + } + + dev_info(&interface->dev, "gtco driver disconnected\n"); +} + +/* STANDARD MODULE LOAD ROUTINES */ + +static struct usb_driver gtco_driverinfo_table = { + .name = "gtco", + .id_table = gtco_usbid_table, + .probe = gtco_probe, + .disconnect = gtco_disconnect, +}; + +/* + * Register this module with the USB subsystem + */ +static int __init gtco_init(void) +{ + int error; + + error = usb_register(>co_driverinfo_table); + if (error) { + err("usb_register() failed rc=0x%x", error); + return error; + } + + printk("GTCO usb driver version: %s", GTCO_VERSION); + return 0; +} + +/* + * Deregister this module with the USB subsystem + */ +static void __exit gtco_exit(void) +{ + usb_deregister(>co_driverinfo_table); +} + +module_init(gtco_init); +module_exit(gtco_exit); + +MODULE_LICENSE("GPL"); diff --git a/drivers/input/tablet/kbtab.c b/drivers/input/tablet/kbtab.c new file mode 100644 index 0000000..6682b17 --- /dev/null +++ b/drivers/input/tablet/kbtab.c @@ -0,0 +1,230 @@ +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/usb/input.h> +#include <asm/unaligned.h> + +/* + * Version Information + * v0.0.1 - Original, extremely basic version, 2.4.xx only + * v0.0.2 - Updated, works with 2.5.62 and 2.4.20; + * - added pressure-threshold modules param code from + * Alex Perry <alex.perry@ieee.org> + */ + +#define DRIVER_VERSION "v0.0.2" +#define DRIVER_AUTHOR "Josh Myer <josh@joshisanerd.com>" +#define DRIVER_DESC "USB KB Gear JamStudio Tablet driver" +#define DRIVER_LICENSE "GPL" + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE(DRIVER_LICENSE); + +#define USB_VENDOR_ID_KBGEAR 0x084e + +static int kb_pressure_click = 0x10; +module_param(kb_pressure_click, int, 0); +MODULE_PARM_DESC(kb_pressure_click, "pressure threshold for clicks"); + +struct kbtab { + unsigned char *data; + dma_addr_t data_dma; + struct input_dev *dev; + struct usb_device *usbdev; + struct urb *irq; + int x, y; + int button; + int pressure; + __u32 serial[2]; + char phys[32]; +}; + +static void kbtab_irq(struct urb *urb) +{ + struct kbtab *kbtab = urb->context; + unsigned char *data = kbtab->data; + struct input_dev *dev = kbtab->dev; + int retval; + + 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", __func__, urb->status); + return; + default: + dbg("%s - nonzero urb status received: %d", __func__, urb->status); + goto exit; + } + + kbtab->x = get_unaligned_le16(&data[1]); + kbtab->y = get_unaligned_le16(&data[3]); + + kbtab->pressure = (data[5]); + + input_report_key(dev, BTN_TOOL_PEN, 1); + + input_report_abs(dev, ABS_X, kbtab->x); + input_report_abs(dev, ABS_Y, kbtab->y); + + /*input_report_key(dev, BTN_TOUCH , data[0] & 0x01);*/ + input_report_key(dev, BTN_RIGHT, data[0] & 0x02); + + if (-1 == kb_pressure_click) { + input_report_abs(dev, ABS_PRESSURE, kbtab->pressure); + } else { + input_report_key(dev, BTN_LEFT, (kbtab->pressure > kb_pressure_click) ? 1 : 0); + }; + + input_sync(dev); + + exit: + retval = usb_submit_urb (urb, GFP_ATOMIC); + if (retval) + err ("%s - usb_submit_urb failed with result %d", + __func__, retval); +} + +static struct usb_device_id kbtab_ids[] = { + { USB_DEVICE(USB_VENDOR_ID_KBGEAR, 0x1001), .driver_info = 0 }, + { } +}; + +MODULE_DEVICE_TABLE(usb, kbtab_ids); + +static int kbtab_open(struct input_dev *dev) +{ + struct kbtab *kbtab = input_get_drvdata(dev); + + kbtab->irq->dev = kbtab->usbdev; + if (usb_submit_urb(kbtab->irq, GFP_KERNEL)) + return -EIO; + + return 0; +} + +static void kbtab_close(struct input_dev *dev) +{ + struct kbtab *kbtab = input_get_drvdata(dev); + + usb_kill_urb(kbtab->irq); +} + +static int kbtab_probe(struct usb_interface *intf, const struct usb_device_id *id) +{ + struct usb_device *dev = interface_to_usbdev(intf); + struct usb_endpoint_descriptor *endpoint; + struct kbtab *kbtab; + struct input_dev *input_dev; + int error = -ENOMEM; + + kbtab = kzalloc(sizeof(struct kbtab), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!kbtab || !input_dev) + goto fail1; + + kbtab->data = usb_buffer_alloc(dev, 8, GFP_KERNEL, &kbtab->data_dma); + if (!kbtab->data) + goto fail1; + + kbtab->irq = usb_alloc_urb(0, GFP_KERNEL); + if (!kbtab->irq) + goto fail2; + + kbtab->usbdev = dev; + kbtab->dev = input_dev; + + usb_make_path(dev, kbtab->phys, sizeof(kbtab->phys)); + strlcat(kbtab->phys, "/input0", sizeof(kbtab->phys)); + + input_dev->name = "KB Gear Tablet"; + input_dev->phys = kbtab->phys; + usb_to_input_id(dev, &input_dev->id); + input_dev->dev.parent = &intf->dev; + + input_set_drvdata(input_dev, kbtab); + + input_dev->open = kbtab_open; + input_dev->close = kbtab_close; + + input_dev->evbit[0] |= BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS) | + BIT_MASK(EV_MSC); + input_dev->keybit[BIT_WORD(BTN_LEFT)] |= BIT_MASK(BTN_LEFT) | + BIT_MASK(BTN_RIGHT) | BIT_MASK(BTN_MIDDLE); + input_dev->keybit[BIT_WORD(BTN_DIGI)] |= BIT_MASK(BTN_TOOL_PEN) | + BIT_MASK(BTN_TOUCH); + input_dev->mscbit[0] |= BIT_MASK(MSC_SERIAL); + input_set_abs_params(input_dev, ABS_X, 0, 0x2000, 4, 0); + input_set_abs_params(input_dev, ABS_Y, 0, 0x1750, 4, 0); + input_set_abs_params(input_dev, ABS_PRESSURE, 0, 0xff, 0, 0); + + endpoint = &intf->cur_altsetting->endpoint[0].desc; + + usb_fill_int_urb(kbtab->irq, dev, + usb_rcvintpipe(dev, endpoint->bEndpointAddress), + kbtab->data, 8, + kbtab_irq, kbtab, endpoint->bInterval); + kbtab->irq->transfer_dma = kbtab->data_dma; + kbtab->irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + + error = input_register_device(kbtab->dev); + if (error) + goto fail3; + + usb_set_intfdata(intf, kbtab); + + return 0; + + fail3: usb_free_urb(kbtab->irq); + fail2: usb_buffer_free(dev, 10, kbtab->data, kbtab->data_dma); + fail1: input_free_device(input_dev); + kfree(kbtab); + return error; +} + +static void kbtab_disconnect(struct usb_interface *intf) +{ + struct kbtab *kbtab = usb_get_intfdata(intf); + + usb_set_intfdata(intf, NULL); + if (kbtab) { + usb_kill_urb(kbtab->irq); + input_unregister_device(kbtab->dev); + usb_free_urb(kbtab->irq); + usb_buffer_free(interface_to_usbdev(intf), 10, kbtab->data, kbtab->data_dma); + kfree(kbtab); + } +} + +static struct usb_driver kbtab_driver = { + .name = "kbtab", + .probe = kbtab_probe, + .disconnect = kbtab_disconnect, + .id_table = kbtab_ids, +}; + +static int __init kbtab_init(void) +{ + int retval; + retval = usb_register(&kbtab_driver); + if (retval) + goto out; + printk(KERN_INFO KBUILD_MODNAME ": " DRIVER_VERSION ":" + DRIVER_DESC "\n"); +out: + return retval; +} + +static void __exit kbtab_exit(void) +{ + usb_deregister(&kbtab_driver); +} + +module_init(kbtab_init); +module_exit(kbtab_exit); diff --git a/drivers/input/tablet/wacom.h b/drivers/input/tablet/wacom.h new file mode 100644 index 0000000..677680e --- /dev/null +++ b/drivers/input/tablet/wacom.h @@ -0,0 +1,139 @@ +/* + * drivers/input/tablet/wacom.h + * + * USB Wacom Graphire and Wacom Intuos tablet support + * + * Copyright (c) 2000-2004 Vojtech Pavlik <vojtech@ucw.cz> + * Copyright (c) 2000 Andreas Bach Aaen <abach@stofanet.dk> + * Copyright (c) 2000 Clifford Wolf <clifford@clifford.at> + * Copyright (c) 2000 Sam Mosel <sam.mosel@computer.org> + * Copyright (c) 2000 James E. Blair <corvus@gnu.org> + * Copyright (c) 2000 Daniel Egger <egger@suse.de> + * Copyright (c) 2001 Frederic Lepied <flepied@mandrakesoft.com> + * Copyright (c) 2004 Panagiotis Issaris <panagiotis.issaris@mech.kuleuven.ac.be> + * Copyright (c) 2002-2008 Ping Cheng <pingc@wacom.com> + * + * ChangeLog: + * v0.1 (vp) - Initial release + * v0.2 (aba) - Support for all buttons / combinations + * v0.3 (vp) - Support for Intuos added + * v0.4 (sm) - Support for more Intuos models, menustrip + * relative mode, proximity. + * v0.5 (vp) - Big cleanup, nifty features removed, + * they belong in userspace + * v1.8 (vp) - Submit URB only when operating, moved to CVS, + * use input_report_key instead of report_btn and + * other cleanups + * v1.11 (vp) - Add URB ->dev setting for new kernels + * v1.11 (jb) - Add support for the 4D Mouse & Lens + * v1.12 (de) - Add support for two more inking pen IDs + * v1.14 (vp) - Use new USB device id probing scheme. + * Fix Wacom Graphire mouse wheel + * v1.18 (vp) - Fix mouse wheel direction + * Make mouse relative + * v1.20 (fl) - Report tool id for Intuos devices + * - Multi tools support + * - Corrected Intuos protocol decoding (airbrush, 4D mouse, lens cursor...) + * - Add PL models support + * - Fix Wacom Graphire mouse wheel again + * v1.21 (vp) - Removed protocol descriptions + * - Added MISC_SERIAL for tool serial numbers + * (gb) - Identify version on module load. + * v1.21.1 (fl) - added Graphire2 support + * v1.21.2 (fl) - added Intuos2 support + * - added all the PL ids + * v1.21.3 (fl) - added another eraser id from Neil Okamoto + * - added smooth filter for Graphire from Peri Hankey + * - added PenPartner support from Olaf van Es + * - new tool ids from Ole Martin Bjoerndalen + * v1.29 (pc) - Add support for more tablets + * - Fix pressure reporting + * v1.30 (vp) - Merge 2.4 and 2.5 drivers + * - Since 2.5 now has input_sync(), remove MSC_SERIAL abuse + * - Cleanups here and there + * v1.30.1 (pi) - Added Graphire3 support + * v1.40 (pc) - Add support for several new devices, fix eraser reporting, ... + * v1.43 (pc) - Added support for Cintiq 21UX + * - Fixed a Graphire bug + * - Merged wacom_intuos3_irq into wacom_intuos_irq + * v1.44 (pc) - Added support for Graphire4, Cintiq 710, Intuos3 6x11, etc. + * - Report Device IDs + * v1.45 (pc) - Added support for DTF 521, Intuos3 12x12 and 12x19 + * - Minor data report fix + * v1.46 (pc) - Split wacom.c into wacom_sys.c and wacom_wac.c, + * - where wacom_sys.c deals with system specific code, + * - and wacom_wac.c deals with Wacom specific code + * - Support Intuos3 4x6 + * v1.47 (pc) - Added support for Bamboo + * v1.48 (pc) - Added support for Bamboo1, BambooFun, and Cintiq 12WX + * v1.49 (pc) - Added support for USB Tablet PC (0x90, 0x93, and 0x9A) + */ + +/* + * 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. + */ +#ifndef WACOM_H +#define WACOM_H +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/usb/input.h> +#include <asm/unaligned.h> + +/* + * Version Information + */ +#define DRIVER_VERSION "v1.49" +#define DRIVER_AUTHOR "Vojtech Pavlik <vojtech@ucw.cz>" +#define DRIVER_DESC "USB Wacom Graphire and Wacom Intuos tablet driver" +#define DRIVER_LICENSE "GPL" + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE(DRIVER_LICENSE); + +#define USB_VENDOR_ID_WACOM 0x056a + +struct wacom { + dma_addr_t data_dma; + struct input_dev *dev; + struct usb_device *usbdev; + struct usb_interface *intf; + struct urb *irq; + struct wacom_wac *wacom_wac; + struct mutex lock; + unsigned int open:1; + char phys[32]; +}; + +struct wacom_combo { + struct wacom *wacom; + struct urb *urb; +}; + +extern int wacom_wac_irq(struct wacom_wac * wacom_wac, void * wcombo); +extern void wacom_report_abs(void *wcombo, unsigned int abs_type, int abs_data); +extern void wacom_report_rel(void *wcombo, unsigned int rel_type, int rel_data); +extern void wacom_report_key(void *wcombo, unsigned int key_type, int key_data); +extern void wacom_input_event(void *wcombo, unsigned int type, unsigned int code, int value); +extern void wacom_input_sync(void *wcombo); +extern void wacom_init_input_dev(struct input_dev *input_dev, struct wacom_wac *wacom_wac); +extern void input_dev_g4(struct input_dev *input_dev, struct wacom_wac *wacom_wac); +extern void input_dev_g(struct input_dev *input_dev, struct wacom_wac *wacom_wac); +extern void input_dev_i3s(struct input_dev *input_dev, struct wacom_wac *wacom_wac); +extern void input_dev_i3(struct input_dev *input_dev, struct wacom_wac *wacom_wac); +extern void input_dev_i(struct input_dev *input_dev, struct wacom_wac *wacom_wac); +extern void input_dev_pl(struct input_dev *input_dev, struct wacom_wac *wacom_wac); +extern void input_dev_pt(struct input_dev *input_dev, struct wacom_wac *wacom_wac); +extern void input_dev_mo(struct input_dev *input_dev, struct wacom_wac *wacom_wac); +extern void input_dev_bee(struct input_dev *input_dev, struct wacom_wac *wacom_wac); +extern __u16 wacom_le16_to_cpu(unsigned char *data); +extern __u16 wacom_be16_to_cpu(unsigned char *data); +extern struct wacom_features *get_wacom_feature(const struct usb_device_id *id); +extern const struct usb_device_id *get_device_table(void); + +#endif diff --git a/drivers/input/tablet/wacom_sys.c b/drivers/input/tablet/wacom_sys.c new file mode 100644 index 0000000..484496d --- /dev/null +++ b/drivers/input/tablet/wacom_sys.c @@ -0,0 +1,569 @@ +/* + * drivers/input/tablet/wacom_sys.c + * + * USB Wacom Graphire and Wacom Intuos tablet support - system specific code + */ + +/* + * 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 "wacom.h" +#include "wacom_wac.h" + +/* defines to get HID report descriptor */ +#define HID_DEVICET_HID (USB_TYPE_CLASS | 0x01) +#define HID_DEVICET_REPORT (USB_TYPE_CLASS | 0x02) +#define HID_USAGE_UNDEFINED 0x00 +#define HID_USAGE_PAGE 0x05 +#define HID_USAGE_PAGE_DIGITIZER 0x0d +#define HID_USAGE_PAGE_DESKTOP 0x01 +#define HID_USAGE 0x09 +#define HID_USAGE_X 0x30 +#define HID_USAGE_Y 0x31 +#define HID_USAGE_X_TILT 0x3d +#define HID_USAGE_Y_TILT 0x3e +#define HID_USAGE_FINGER 0x22 +#define HID_USAGE_STYLUS 0x20 +#define HID_COLLECTION 0xc0 + +enum { + WCM_UNDEFINED = 0, + WCM_DESKTOP, + WCM_DIGITIZER, +}; + +struct hid_descriptor { + struct usb_descriptor_header header; + __le16 bcdHID; + u8 bCountryCode; + u8 bNumDescriptors; + u8 bDescriptorType; + __le16 wDescriptorLength; +} __attribute__ ((packed)); + +/* defines to get/set USB message */ +#define USB_REQ_GET_REPORT 0x01 +#define USB_REQ_SET_REPORT 0x09 +#define WAC_HID_FEATURE_REPORT 0x03 + +static int usb_get_report(struct usb_interface *intf, unsigned char type, + unsigned char id, void *buf, int size) +{ + return usb_control_msg(interface_to_usbdev(intf), + usb_rcvctrlpipe(interface_to_usbdev(intf), 0), + USB_REQ_GET_REPORT, USB_TYPE_CLASS | USB_RECIP_INTERFACE, + (type << 8) + id, intf->altsetting[0].desc.bInterfaceNumber, + buf, size, 100); +} + +static int usb_set_report(struct usb_interface *intf, unsigned char type, + unsigned char id, void *buf, int size) +{ + return usb_control_msg(interface_to_usbdev(intf), + usb_sndctrlpipe(interface_to_usbdev(intf), 0), + USB_REQ_SET_REPORT, USB_TYPE_CLASS | USB_RECIP_INTERFACE, + (type << 8) + id, intf->altsetting[0].desc.bInterfaceNumber, + buf, size, 1000); +} + +static struct input_dev * get_input_dev(struct wacom_combo *wcombo) +{ + return wcombo->wacom->dev; +} + +static void wacom_sys_irq(struct urb *urb) +{ + struct wacom *wacom = urb->context; + struct wacom_combo wcombo; + int retval; + + 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", __func__, urb->status); + return; + default: + dbg("%s - nonzero urb status received: %d", __func__, urb->status); + goto exit; + } + + wcombo.wacom = wacom; + wcombo.urb = urb; + + if (wacom_wac_irq(wacom->wacom_wac, (void *)&wcombo)) + input_sync(get_input_dev(&wcombo)); + + exit: + usb_mark_last_busy(wacom->usbdev); + retval = usb_submit_urb (urb, GFP_ATOMIC); + if (retval) + err ("%s - usb_submit_urb failed with result %d", + __func__, retval); +} + +void wacom_report_key(void *wcombo, unsigned int key_type, int key_data) +{ + input_report_key(get_input_dev((struct wacom_combo *)wcombo), key_type, key_data); +} + +void wacom_report_abs(void *wcombo, unsigned int abs_type, int abs_data) +{ + input_report_abs(get_input_dev((struct wacom_combo *)wcombo), abs_type, abs_data); +} + +void wacom_report_rel(void *wcombo, unsigned int rel_type, int rel_data) +{ + input_report_rel(get_input_dev((struct wacom_combo *)wcombo), rel_type, rel_data); +} + +void wacom_input_event(void *wcombo, unsigned int type, unsigned int code, int value) +{ + input_event(get_input_dev((struct wacom_combo *)wcombo), type, code, value); +} + +__u16 wacom_be16_to_cpu(unsigned char *data) +{ + __u16 value; + value = be16_to_cpu(*(__be16 *) data); + return value; +} + +__u16 wacom_le16_to_cpu(unsigned char *data) +{ + __u16 value; + value = le16_to_cpu(*(__le16 *) data); + return value; +} + +void wacom_input_sync(void *wcombo) +{ + input_sync(get_input_dev((struct wacom_combo *)wcombo)); +} + +static int wacom_open(struct input_dev *dev) +{ + struct wacom *wacom = input_get_drvdata(dev); + + mutex_lock(&wacom->lock); + + wacom->irq->dev = wacom->usbdev; + + if (usb_autopm_get_interface(wacom->intf) < 0) { + mutex_unlock(&wacom->lock); + return -EIO; + } + + if (usb_submit_urb(wacom->irq, GFP_KERNEL)) { + usb_autopm_put_interface(wacom->intf); + mutex_unlock(&wacom->lock); + return -EIO; + } + + wacom->open = 1; + wacom->intf->needs_remote_wakeup = 1; + + mutex_unlock(&wacom->lock); + return 0; +} + +static void wacom_close(struct input_dev *dev) +{ + struct wacom *wacom = input_get_drvdata(dev); + + mutex_lock(&wacom->lock); + usb_kill_urb(wacom->irq); + wacom->open = 0; + wacom->intf->needs_remote_wakeup = 0; + mutex_unlock(&wacom->lock); +} + +void input_dev_mo(struct input_dev *input_dev, struct wacom_wac *wacom_wac) +{ + input_dev->keybit[BIT_WORD(BTN_MISC)] |= BIT_MASK(BTN_1) | + BIT_MASK(BTN_5); + input_set_abs_params(input_dev, ABS_WHEEL, 0, 71, 0, 0); +} + +void input_dev_g4(struct input_dev *input_dev, struct wacom_wac *wacom_wac) +{ + input_dev->evbit[0] |= BIT_MASK(EV_MSC); + input_dev->mscbit[0] |= BIT_MASK(MSC_SERIAL); + input_dev->keybit[BIT_WORD(BTN_DIGI)] |= BIT_MASK(BTN_TOOL_FINGER); + input_dev->keybit[BIT_WORD(BTN_MISC)] |= BIT_MASK(BTN_0) | + BIT_MASK(BTN_4); +} + +void input_dev_g(struct input_dev *input_dev, struct wacom_wac *wacom_wac) +{ + input_dev->evbit[0] |= BIT_MASK(EV_REL); + input_dev->relbit[0] |= BIT_MASK(REL_WHEEL); + input_dev->keybit[BIT_WORD(BTN_MOUSE)] |= BIT_MASK(BTN_LEFT) | + BIT_MASK(BTN_RIGHT) | BIT_MASK(BTN_MIDDLE); + input_dev->keybit[BIT_WORD(BTN_DIGI)] |= BIT_MASK(BTN_TOOL_RUBBER) | + BIT_MASK(BTN_TOOL_MOUSE) | BIT_MASK(BTN_STYLUS2); + input_set_abs_params(input_dev, ABS_DISTANCE, 0, wacom_wac->features->distance_max, 0, 0); +} + +void input_dev_i3s(struct input_dev *input_dev, struct wacom_wac *wacom_wac) +{ + input_dev->keybit[BIT_WORD(BTN_DIGI)] |= BIT_MASK(BTN_TOOL_FINGER); + input_dev->keybit[BIT_WORD(BTN_MISC)] |= BIT_MASK(BTN_0) | + BIT_MASK(BTN_1) | BIT_MASK(BTN_2) | BIT_MASK(BTN_3); + input_set_abs_params(input_dev, ABS_RX, 0, 4096, 0, 0); + input_set_abs_params(input_dev, ABS_Z, -900, 899, 0, 0); +} + +void input_dev_i3(struct input_dev *input_dev, struct wacom_wac *wacom_wac) +{ + input_dev->keybit[BIT_WORD(BTN_MISC)] |= BIT_MASK(BTN_4) | + BIT_MASK(BTN_5) | BIT_MASK(BTN_6) | BIT_MASK(BTN_7); + input_set_abs_params(input_dev, ABS_RY, 0, 4096, 0, 0); +} + +void input_dev_bee(struct input_dev *input_dev, struct wacom_wac *wacom_wac) +{ + input_dev->keybit[BIT_WORD(BTN_MISC)] |= BIT_MASK(BTN_8) | BIT_MASK(BTN_9); +} + +void input_dev_i(struct input_dev *input_dev, struct wacom_wac *wacom_wac) +{ + input_dev->evbit[0] |= BIT_MASK(EV_MSC) | BIT_MASK(EV_REL); + input_dev->mscbit[0] |= BIT_MASK(MSC_SERIAL); + input_dev->relbit[0] |= BIT_MASK(REL_WHEEL); + input_dev->keybit[BIT_WORD(BTN_MOUSE)] |= BIT_MASK(BTN_LEFT) | + BIT_MASK(BTN_RIGHT) | BIT_MASK(BTN_MIDDLE) | + BIT_MASK(BTN_SIDE) | BIT_MASK(BTN_EXTRA); + input_dev->keybit[BIT_WORD(BTN_DIGI)] |= BIT_MASK(BTN_TOOL_RUBBER) | + BIT_MASK(BTN_TOOL_MOUSE) | BIT_MASK(BTN_TOOL_BRUSH) | + BIT_MASK(BTN_TOOL_PENCIL) | BIT_MASK(BTN_TOOL_AIRBRUSH) | + BIT_MASK(BTN_TOOL_LENS) | BIT_MASK(BTN_STYLUS2); + input_set_abs_params(input_dev, ABS_DISTANCE, 0, wacom_wac->features->distance_max, 0, 0); + input_set_abs_params(input_dev, ABS_WHEEL, 0, 1023, 0, 0); + input_set_abs_params(input_dev, ABS_TILT_X, 0, 127, 0, 0); + input_set_abs_params(input_dev, ABS_TILT_Y, 0, 127, 0, 0); + input_set_abs_params(input_dev, ABS_RZ, -900, 899, 0, 0); + input_set_abs_params(input_dev, ABS_THROTTLE, -1023, 1023, 0, 0); +} + +void input_dev_pl(struct input_dev *input_dev, struct wacom_wac *wacom_wac) +{ + input_dev->keybit[BIT_WORD(BTN_DIGI)] |= BIT_MASK(BTN_STYLUS2); +} + +void input_dev_pt(struct input_dev *input_dev, struct wacom_wac *wacom_wac) +{ + input_dev->keybit[BIT_WORD(BTN_DIGI)] |= BIT_MASK(BTN_TOOL_RUBBER); +} + +static int wacom_parse_hid(struct usb_interface *intf, struct hid_descriptor *hid_desc, + struct wacom_wac *wacom_wac) +{ + struct usb_device *dev = interface_to_usbdev(intf); + struct wacom_features *features = wacom_wac->features; + char limit = 0, result = 0; + int i = 0, usage = WCM_UNDEFINED, finger = 0, pen = 0; + unsigned char *report; + + report = kzalloc(hid_desc->wDescriptorLength, GFP_KERNEL); + if (!report) + return -ENOMEM; + + /* retrive report descriptors */ + do { + result = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), + USB_REQ_GET_DESCRIPTOR, + USB_RECIP_INTERFACE | USB_DIR_IN, + HID_DEVICET_REPORT << 8, + intf->altsetting[0].desc.bInterfaceNumber, /* interface */ + report, + hid_desc->wDescriptorLength, + 5000); /* 5 secs */ + } while (result < 0 && limit++ < 5); + + if (result < 0) + goto out; + + for (i = 0; i < hid_desc->wDescriptorLength; i++) { + + switch (report[i]) { + case HID_USAGE_PAGE: + switch (report[i + 1]) { + case HID_USAGE_PAGE_DIGITIZER: + usage = WCM_DIGITIZER; + i++; + break; + + case HID_USAGE_PAGE_DESKTOP: + usage = WCM_DESKTOP; + i++; + break; + } + break; + + case HID_USAGE: + switch (report[i + 1]) { + case HID_USAGE_X: + if (usage == WCM_DESKTOP) { + if (finger) { + features->touch_x_max = + features->touch_y_max = + wacom_le16_to_cpu(&report[i + 3]); + features->x_max = + wacom_le16_to_cpu(&report[i + 6]); + i += 7; + } else if (pen) { + features->x_max = + wacom_le16_to_cpu(&report[i + 3]); + i += 4; + } + } else if (usage == WCM_DIGITIZER) { + /* max pressure isn't reported + features->pressure_max = (unsigned short) + (report[i+4] << 8 | report[i + 3]); + */ + features->pressure_max = 255; + i += 4; + } + break; + + case HID_USAGE_Y: + if (usage == WCM_DESKTOP) + features->y_max = + wacom_le16_to_cpu(&report[i + 3]); + i += 4; + break; + + case HID_USAGE_FINGER: + finger = 1; + i++; + break; + + case HID_USAGE_STYLUS: + pen = 1; + i++; + break; + + case HID_USAGE_UNDEFINED: + if (usage == WCM_DESKTOP && finger) /* capacity */ + features->pressure_max = + wacom_le16_to_cpu(&report[i + 3]); + i += 4; + break; + } + break; + + case HID_COLLECTION: + /* reset UsagePage ans Finger */ + finger = usage = 0; + break; + } + } + + result = 0; + + out: + kfree(report); + return result; +} + +static int wacom_probe(struct usb_interface *intf, const struct usb_device_id *id) +{ + struct usb_device *dev = interface_to_usbdev(intf); + struct usb_host_interface *interface = intf->cur_altsetting; + struct usb_endpoint_descriptor *endpoint; + struct wacom *wacom; + struct wacom_wac *wacom_wac; + struct wacom_features *features; + struct input_dev *input_dev; + int error = -ENOMEM; + char rep_data[2], limit = 0; + struct hid_descriptor *hid_desc; + + wacom = kzalloc(sizeof(struct wacom), GFP_KERNEL); + wacom_wac = kzalloc(sizeof(struct wacom_wac), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!wacom || !input_dev || !wacom_wac) + goto fail1; + + wacom_wac->data = usb_buffer_alloc(dev, 10, GFP_KERNEL, &wacom->data_dma); + if (!wacom_wac->data) + goto fail1; + + wacom->irq = usb_alloc_urb(0, GFP_KERNEL); + if (!wacom->irq) + goto fail2; + + wacom->usbdev = dev; + wacom->dev = input_dev; + wacom->intf = intf; + mutex_init(&wacom->lock); + usb_make_path(dev, wacom->phys, sizeof(wacom->phys)); + strlcat(wacom->phys, "/input0", sizeof(wacom->phys)); + + wacom_wac->features = features = get_wacom_feature(id); + BUG_ON(features->pktlen > 10); + + input_dev->name = wacom_wac->features->name; + wacom->wacom_wac = wacom_wac; + usb_to_input_id(dev, &input_dev->id); + + input_dev->dev.parent = &intf->dev; + + input_set_drvdata(input_dev, wacom); + + input_dev->open = wacom_open; + input_dev->close = wacom_close; + + endpoint = &intf->cur_altsetting->endpoint[0].desc; + + /* TabletPC need to retrieve the physical and logical maximum from report descriptor */ + if (wacom_wac->features->type == TABLETPC) { + if (usb_get_extra_descriptor(interface, HID_DEVICET_HID, &hid_desc)) { + if (usb_get_extra_descriptor(&interface->endpoint[0], + HID_DEVICET_REPORT, &hid_desc)) { + printk("wacom: can not retrive extra class descriptor\n"); + goto fail2; + } + } + error = wacom_parse_hid(intf, hid_desc, wacom_wac); + if (error) + goto fail2; + } + + input_dev->evbit[0] |= BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + input_dev->keybit[BIT_WORD(BTN_DIGI)] |= BIT_MASK(BTN_TOOL_PEN) | + BIT_MASK(BTN_TOUCH) | BIT_MASK(BTN_STYLUS); + input_set_abs_params(input_dev, ABS_X, 0, features->x_max, 4, 0); + input_set_abs_params(input_dev, ABS_Y, 0, features->y_max, 4, 0); + input_set_abs_params(input_dev, ABS_PRESSURE, 0, features->pressure_max, 0, 0); + if (features->type == TABLETPC) { + input_dev->keybit[BIT_WORD(BTN_DIGI)] |= BIT_MASK(BTN_TOOL_DOUBLETAP); + input_set_abs_params(input_dev, ABS_RX, 0, features->touch_x_max, 4, 0); + input_set_abs_params(input_dev, ABS_RY, 0, features->touch_y_max, 4, 0); + } + input_dev->absbit[BIT_WORD(ABS_MISC)] |= BIT_MASK(ABS_MISC); + + wacom_init_input_dev(input_dev, wacom_wac); + + usb_fill_int_urb(wacom->irq, dev, + usb_rcvintpipe(dev, endpoint->bEndpointAddress), + wacom_wac->data, wacom_wac->features->pktlen, + wacom_sys_irq, wacom, endpoint->bInterval); + wacom->irq->transfer_dma = wacom->data_dma; + wacom->irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + + error = input_register_device(wacom->dev); + if (error) + goto fail3; + + /* + * Ask the tablet to report tablet data if it is not a Tablet PC. + * Repeat until it succeeds + */ + if (wacom_wac->features->type != TABLETPC) { + do { + rep_data[0] = 2; + rep_data[1] = 2; + error = usb_set_report(intf, WAC_HID_FEATURE_REPORT, + 2, rep_data, 2); + if (error >= 0) + error = usb_get_report(intf, + WAC_HID_FEATURE_REPORT, 2, + rep_data, 2); + } while ((error < 0 || rep_data[1] != 2) && limit++ < 5); + } + + usb_set_intfdata(intf, wacom); + return 0; + + fail3: usb_free_urb(wacom->irq); + fail2: usb_buffer_free(dev, 10, wacom_wac->data, wacom->data_dma); + fail1: input_free_device(input_dev); + kfree(wacom); + kfree(wacom_wac); + return error; +} + +static void wacom_disconnect(struct usb_interface *intf) +{ + struct wacom *wacom = usb_get_intfdata(intf); + + usb_set_intfdata(intf, NULL); + + usb_kill_urb(wacom->irq); + input_unregister_device(wacom->dev); + usb_free_urb(wacom->irq); + usb_buffer_free(interface_to_usbdev(intf), 10, + wacom->wacom_wac->data, wacom->data_dma); + kfree(wacom->wacom_wac); + kfree(wacom); +} + +static int wacom_suspend(struct usb_interface *intf, pm_message_t message) +{ + struct wacom *wacom = usb_get_intfdata(intf); + + mutex_lock(&wacom->lock); + usb_kill_urb(wacom->irq); + mutex_unlock(&wacom->lock); + + return 0; +} + +static int wacom_resume(struct usb_interface *intf) +{ + struct wacom *wacom = usb_get_intfdata(intf); + int rv; + + mutex_lock(&wacom->lock); + if (wacom->open) + rv = usb_submit_urb(wacom->irq, GFP_NOIO); + else + rv = 0; + mutex_unlock(&wacom->lock); + + return rv; +} + +static int wacom_reset_resume(struct usb_interface *intf) +{ + return wacom_resume(intf); +} + +static struct usb_driver wacom_driver = { + .name = "wacom", + .probe = wacom_probe, + .disconnect = wacom_disconnect, + .suspend = wacom_suspend, + .resume = wacom_resume, + .reset_resume = wacom_reset_resume, + .supports_autosuspend = 1, +}; + +static int __init wacom_init(void) +{ + int result; + wacom_driver.id_table = get_device_table(); + result = usb_register(&wacom_driver); + if (result == 0) + printk(KERN_INFO KBUILD_MODNAME ": " DRIVER_VERSION ":" + DRIVER_DESC "\n"); + return result; +} + +static void __exit wacom_exit(void) +{ + usb_deregister(&wacom_driver); +} + +module_init(wacom_init); +module_exit(wacom_exit); diff --git a/drivers/input/tablet/wacom_wac.c b/drivers/input/tablet/wacom_wac.c new file mode 100644 index 0000000..8dc8d1e --- /dev/null +++ b/drivers/input/tablet/wacom_wac.c @@ -0,0 +1,854 @@ +/* + * drivers/input/tablet/wacom_wac.c + * + * USB Wacom Graphire and Wacom Intuos tablet support - Wacom specific code + * + */ + +/* + * 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 "wacom.h" +#include "wacom_wac.h" + +static int wacom_penpartner_irq(struct wacom_wac *wacom, void *wcombo) +{ + unsigned char *data = wacom->data; + + switch (data[0]) { + case 1: + if (data[5] & 0x80) { + wacom->tool[0] = (data[5] & 0x20) ? BTN_TOOL_RUBBER : BTN_TOOL_PEN; + wacom->id[0] = (data[5] & 0x20) ? ERASER_DEVICE_ID : STYLUS_DEVICE_ID; + wacom_report_key(wcombo, wacom->tool[0], 1); + wacom_report_abs(wcombo, ABS_MISC, wacom->id[0]); /* report tool id */ + wacom_report_abs(wcombo, ABS_X, wacom_le16_to_cpu(&data[1])); + wacom_report_abs(wcombo, ABS_Y, wacom_le16_to_cpu(&data[3])); + wacom_report_abs(wcombo, ABS_PRESSURE, (signed char)data[6] + 127); + wacom_report_key(wcombo, BTN_TOUCH, ((signed char)data[6] > -127)); + wacom_report_key(wcombo, BTN_STYLUS, (data[5] & 0x40)); + } else { + wacom_report_key(wcombo, wacom->tool[0], 0); + wacom_report_abs(wcombo, ABS_MISC, 0); /* report tool id */ + wacom_report_abs(wcombo, ABS_PRESSURE, -1); + wacom_report_key(wcombo, BTN_TOUCH, 0); + } + break; + case 2: + wacom_report_key(wcombo, BTN_TOOL_PEN, 1); + wacom_report_abs(wcombo, ABS_MISC, STYLUS_DEVICE_ID); /* report tool id */ + wacom_report_abs(wcombo, ABS_X, wacom_le16_to_cpu(&data[1])); + wacom_report_abs(wcombo, ABS_Y, wacom_le16_to_cpu(&data[3])); + wacom_report_abs(wcombo, ABS_PRESSURE, (signed char)data[6] + 127); + wacom_report_key(wcombo, BTN_TOUCH, ((signed char)data[6] > -80) && !(data[5] & 0x20)); + wacom_report_key(wcombo, BTN_STYLUS, (data[5] & 0x40)); + break; + default: + printk(KERN_INFO "wacom_penpartner_irq: received unknown report #%d\n", data[0]); + return 0; + } + return 1; +} + +static int wacom_pl_irq(struct wacom_wac *wacom, void *wcombo) +{ + unsigned char *data = wacom->data; + int prox, pressure; + + if (data[0] != 2) { + dbg("wacom_pl_irq: received unknown report #%d", data[0]); + return 0; + } + + prox = data[1] & 0x40; + + wacom->id[0] = ERASER_DEVICE_ID; + if (prox) { + + pressure = (signed char)((data[7] << 1) | ((data[4] >> 2) & 1)); + if (wacom->features->pressure_max > 255) + pressure = (pressure << 1) | ((data[4] >> 6) & 1); + pressure += (wacom->features->pressure_max + 1) / 2; + + /* + * if going from out of proximity into proximity select between the eraser + * and the pen based on the state of the stylus2 button, choose eraser if + * pressed else choose pen. if not a proximity change from out to in, send + * an out of proximity for previous tool then a in for new tool. + */ + if (!wacom->tool[0]) { + /* Eraser bit set for DTF */ + if (data[1] & 0x10) + wacom->tool[1] = BTN_TOOL_RUBBER; + else + /* Going into proximity select tool */ + wacom->tool[1] = (data[4] & 0x20) ? BTN_TOOL_RUBBER : BTN_TOOL_PEN; + } else { + /* was entered with stylus2 pressed */ + if (wacom->tool[1] == BTN_TOOL_RUBBER && !(data[4] & 0x20)) { + /* report out proximity for previous tool */ + wacom_report_key(wcombo, wacom->tool[1], 0); + wacom_input_sync(wcombo); + wacom->tool[1] = BTN_TOOL_PEN; + return 0; + } + } + if (wacom->tool[1] != BTN_TOOL_RUBBER) { + /* Unknown tool selected default to pen tool */ + wacom->tool[1] = BTN_TOOL_PEN; + wacom->id[0] = STYLUS_DEVICE_ID; + } + wacom_report_key(wcombo, wacom->tool[1], prox); /* report in proximity for tool */ + wacom_report_abs(wcombo, ABS_MISC, wacom->id[0]); /* report tool id */ + wacom_report_abs(wcombo, ABS_X, data[3] | (data[2] << 7) | ((data[1] & 0x03) << 14)); + wacom_report_abs(wcombo, ABS_Y, data[6] | (data[5] << 7) | ((data[4] & 0x03) << 14)); + wacom_report_abs(wcombo, ABS_PRESSURE, pressure); + + wacom_report_key(wcombo, BTN_TOUCH, data[4] & 0x08); + wacom_report_key(wcombo, BTN_STYLUS, data[4] & 0x10); + /* Only allow the stylus2 button to be reported for the pen tool. */ + wacom_report_key(wcombo, BTN_STYLUS2, (wacom->tool[1] == BTN_TOOL_PEN) && (data[4] & 0x20)); + } else { + /* report proximity-out of a (valid) tool */ + if (wacom->tool[1] != BTN_TOOL_RUBBER) { + /* Unknown tool selected default to pen tool */ + wacom->tool[1] = BTN_TOOL_PEN; + } + wacom_report_key(wcombo, wacom->tool[1], prox); + } + + wacom->tool[0] = prox; /* Save proximity state */ + return 1; +} + +static int wacom_ptu_irq(struct wacom_wac *wacom, void *wcombo) +{ + unsigned char *data = wacom->data; + + if (data[0] != 2) { + printk(KERN_INFO "wacom_ptu_irq: received unknown report #%d\n", data[0]); + return 0; + } + + if (data[1] & 0x04) { + wacom_report_key(wcombo, BTN_TOOL_RUBBER, data[1] & 0x20); + wacom_report_key(wcombo, BTN_TOUCH, data[1] & 0x08); + wacom->id[0] = ERASER_DEVICE_ID; + } else { + wacom_report_key(wcombo, BTN_TOOL_PEN, data[1] & 0x20); + wacom_report_key(wcombo, BTN_TOUCH, data[1] & 0x01); + wacom->id[0] = STYLUS_DEVICE_ID; + } + wacom_report_abs(wcombo, ABS_MISC, wacom->id[0]); /* report tool id */ + wacom_report_abs(wcombo, ABS_X, wacom_le16_to_cpu(&data[2])); + wacom_report_abs(wcombo, ABS_Y, wacom_le16_to_cpu(&data[4])); + wacom_report_abs(wcombo, ABS_PRESSURE, wacom_le16_to_cpu(&data[6])); + wacom_report_key(wcombo, BTN_STYLUS, data[1] & 0x02); + wacom_report_key(wcombo, BTN_STYLUS2, data[1] & 0x10); + return 1; +} + +static int wacom_graphire_irq(struct wacom_wac *wacom, void *wcombo) +{ + unsigned char *data = wacom->data; + int x, y, rw; + + if (data[0] != 2) { + dbg("wacom_graphire_irq: received unknown report #%d", data[0]); + return 0; + } + + if (data[1] & 0x80) { + /* in prox and not a pad data */ + + switch ((data[1] >> 5) & 3) { + + case 0: /* Pen */ + wacom->tool[0] = BTN_TOOL_PEN; + wacom->id[0] = STYLUS_DEVICE_ID; + break; + + case 1: /* Rubber */ + wacom->tool[0] = BTN_TOOL_RUBBER; + wacom->id[0] = ERASER_DEVICE_ID; + break; + + case 2: /* Mouse with wheel */ + wacom_report_key(wcombo, BTN_MIDDLE, data[1] & 0x04); + if (wacom->features->type == WACOM_G4 || + wacom->features->type == WACOM_MO) { + rw = data[7] & 0x04 ? (data[7] & 0x03)-4 : (data[7] & 0x03); + wacom_report_rel(wcombo, REL_WHEEL, -rw); + } else + wacom_report_rel(wcombo, REL_WHEEL, -(signed char) data[6]); + /* fall through */ + + case 3: /* Mouse without wheel */ + wacom->tool[0] = BTN_TOOL_MOUSE; + wacom->id[0] = CURSOR_DEVICE_ID; + wacom_report_key(wcombo, BTN_LEFT, data[1] & 0x01); + wacom_report_key(wcombo, BTN_RIGHT, data[1] & 0x02); + if (wacom->features->type == WACOM_G4 || + wacom->features->type == WACOM_MO) + wacom_report_abs(wcombo, ABS_DISTANCE, data[6] & 0x3f); + else + wacom_report_abs(wcombo, ABS_DISTANCE, data[7] & 0x3f); + break; + } + x = wacom_le16_to_cpu(&data[2]); + y = wacom_le16_to_cpu(&data[4]); + wacom_report_abs(wcombo, ABS_X, x); + wacom_report_abs(wcombo, ABS_Y, y); + if (wacom->tool[0] != BTN_TOOL_MOUSE) { + wacom_report_abs(wcombo, ABS_PRESSURE, data[6] | ((data[7] & 0x01) << 8)); + wacom_report_key(wcombo, BTN_TOUCH, data[1] & 0x01); + wacom_report_key(wcombo, BTN_STYLUS, data[1] & 0x02); + wacom_report_key(wcombo, BTN_STYLUS2, data[1] & 0x04); + } + wacom_report_abs(wcombo, ABS_MISC, wacom->id[0]); /* report tool id */ + wacom_report_key(wcombo, wacom->tool[0], 1); + } else if (wacom->id[0]) { + wacom_report_abs(wcombo, ABS_X, 0); + wacom_report_abs(wcombo, ABS_Y, 0); + if (wacom->tool[0] == BTN_TOOL_MOUSE) { + wacom_report_key(wcombo, BTN_LEFT, 0); + wacom_report_key(wcombo, BTN_RIGHT, 0); + wacom_report_abs(wcombo, ABS_DISTANCE, 0); + } else { + wacom_report_abs(wcombo, ABS_PRESSURE, 0); + wacom_report_key(wcombo, BTN_TOUCH, 0); + wacom_report_key(wcombo, BTN_STYLUS, 0); + wacom_report_key(wcombo, BTN_STYLUS2, 0); + } + wacom->id[0] = 0; + wacom_report_abs(wcombo, ABS_MISC, 0); /* reset tool id */ + wacom_report_key(wcombo, wacom->tool[0], 0); + } + + /* send pad data */ + switch (wacom->features->type) { + case WACOM_G4: + if (data[7] & 0xf8) { + wacom_input_sync(wcombo); /* sync last event */ + wacom->id[1] = PAD_DEVICE_ID; + wacom_report_key(wcombo, BTN_0, (data[7] & 0x40)); + wacom_report_key(wcombo, BTN_4, (data[7] & 0x80)); + rw = ((data[7] & 0x18) >> 3) - ((data[7] & 0x20) >> 3); + wacom_report_rel(wcombo, REL_WHEEL, rw); + wacom_report_key(wcombo, BTN_TOOL_FINGER, 0xf0); + wacom_report_abs(wcombo, ABS_MISC, wacom->id[1]); + wacom_input_event(wcombo, EV_MSC, MSC_SERIAL, 0xf0); + } else if (wacom->id[1]) { + wacom_input_sync(wcombo); /* sync last event */ + wacom->id[1] = 0; + wacom_report_key(wcombo, BTN_0, (data[7] & 0x40)); + wacom_report_key(wcombo, BTN_4, (data[7] & 0x80)); + wacom_report_key(wcombo, BTN_TOOL_FINGER, 0); + wacom_report_abs(wcombo, ABS_MISC, 0); + wacom_input_event(wcombo, EV_MSC, MSC_SERIAL, 0xf0); + } + break; + case WACOM_MO: + if ((data[7] & 0xf8) || (data[8] & 0xff)) { + wacom_input_sync(wcombo); /* sync last event */ + wacom->id[1] = PAD_DEVICE_ID; + wacom_report_key(wcombo, BTN_0, (data[7] & 0x08)); + wacom_report_key(wcombo, BTN_1, (data[7] & 0x20)); + wacom_report_key(wcombo, BTN_4, (data[7] & 0x10)); + wacom_report_key(wcombo, BTN_5, (data[7] & 0x40)); + wacom_report_abs(wcombo, ABS_WHEEL, (data[8] & 0x7f)); + wacom_report_key(wcombo, BTN_TOOL_FINGER, 0xf0); + wacom_report_abs(wcombo, ABS_MISC, wacom->id[1]); + wacom_input_event(wcombo, EV_MSC, MSC_SERIAL, 0xf0); + } else if (wacom->id[1]) { + wacom_input_sync(wcombo); /* sync last event */ + wacom->id[1] = 0; + wacom_report_key(wcombo, BTN_0, (data[7] & 0x08)); + wacom_report_key(wcombo, BTN_1, (data[7] & 0x20)); + wacom_report_key(wcombo, BTN_4, (data[7] & 0x10)); + wacom_report_key(wcombo, BTN_5, (data[7] & 0x40)); + wacom_report_abs(wcombo, ABS_WHEEL, (data[8] & 0x7f)); + wacom_report_key(wcombo, BTN_TOOL_FINGER, 0); + wacom_report_abs(wcombo, ABS_MISC, 0); + wacom_input_event(wcombo, EV_MSC, MSC_SERIAL, 0xf0); + } + break; + } + return 1; +} + +static int wacom_intuos_inout(struct wacom_wac *wacom, void *wcombo) +{ + unsigned char *data = wacom->data; + int idx; + + /* tool number */ + idx = data[1] & 0x01; + + /* Enter report */ + if ((data[1] & 0xfc) == 0xc0) { + /* serial number of the tool */ + wacom->serial[idx] = ((data[3] & 0x0f) << 28) + + (data[4] << 20) + (data[5] << 12) + + (data[6] << 4) + (data[7] >> 4); + + wacom->id[idx] = (data[2] << 4) | (data[3] >> 4); + switch (wacom->id[idx]) { + case 0x812: /* Inking pen */ + case 0x801: /* Intuos3 Inking pen */ + case 0x012: + wacom->tool[idx] = BTN_TOOL_PENCIL; + break; + case 0x822: /* Pen */ + case 0x842: + case 0x852: + case 0x823: /* Intuos3 Grip Pen */ + case 0x813: /* Intuos3 Classic Pen */ + case 0x885: /* Intuos3 Marker Pen */ + case 0x022: + wacom->tool[idx] = BTN_TOOL_PEN; + break; + case 0x832: /* Stroke pen */ + case 0x032: + wacom->tool[idx] = BTN_TOOL_BRUSH; + break; + case 0x007: /* Mouse 4D and 2D */ + case 0x09c: + case 0x094: + case 0x017: /* Intuos3 2D Mouse */ + wacom->tool[idx] = BTN_TOOL_MOUSE; + break; + case 0x096: /* Lens cursor */ + case 0x097: /* Intuos3 Lens cursor */ + wacom->tool[idx] = BTN_TOOL_LENS; + break; + case 0x82a: /* Eraser */ + case 0x85a: + case 0x91a: + case 0xd1a: + case 0x0fa: + case 0x82b: /* Intuos3 Grip Pen Eraser */ + case 0x81b: /* Intuos3 Classic Pen Eraser */ + case 0x91b: /* Intuos3 Airbrush Eraser */ + wacom->tool[idx] = BTN_TOOL_RUBBER; + break; + case 0xd12: + case 0x912: + case 0x112: + case 0x913: /* Intuos3 Airbrush */ + wacom->tool[idx] = BTN_TOOL_AIRBRUSH; + break; + default: /* Unknown tool */ + wacom->tool[idx] = BTN_TOOL_PEN; + } + return 1; + } + + /* Exit report */ + if ((data[1] & 0xfe) == 0x80) { + wacom_report_abs(wcombo, ABS_X, 0); + wacom_report_abs(wcombo, ABS_Y, 0); + wacom_report_abs(wcombo, ABS_DISTANCE, 0); + if (wacom->tool[idx] >= BTN_TOOL_MOUSE) { + wacom_report_key(wcombo, BTN_LEFT, 0); + wacom_report_key(wcombo, BTN_MIDDLE, 0); + wacom_report_key(wcombo, BTN_RIGHT, 0); + wacom_report_key(wcombo, BTN_SIDE, 0); + wacom_report_key(wcombo, BTN_EXTRA, 0); + wacom_report_abs(wcombo, ABS_THROTTLE, 0); + wacom_report_abs(wcombo, ABS_RZ, 0); + } else { + wacom_report_abs(wcombo, ABS_PRESSURE, 0); + wacom_report_abs(wcombo, ABS_TILT_X, 0); + wacom_report_abs(wcombo, ABS_TILT_Y, 0); + wacom_report_key(wcombo, BTN_STYLUS, 0); + wacom_report_key(wcombo, BTN_STYLUS2, 0); + wacom_report_key(wcombo, BTN_TOUCH, 0); + wacom_report_abs(wcombo, ABS_WHEEL, 0); + } + wacom_report_key(wcombo, wacom->tool[idx], 0); + wacom_report_abs(wcombo, ABS_MISC, 0); /* reset tool id */ + wacom_input_event(wcombo, EV_MSC, MSC_SERIAL, wacom->serial[idx]); + return 2; + } + return 0; +} + +static void wacom_intuos_general(struct wacom_wac *wacom, void *wcombo) +{ + unsigned char *data = wacom->data; + unsigned int t; + + /* general pen packet */ + if ((data[1] & 0xb8) == 0xa0) { + t = (data[6] << 2) | ((data[7] >> 6) & 3); + wacom_report_abs(wcombo, ABS_PRESSURE, t); + wacom_report_abs(wcombo, ABS_TILT_X, + ((data[7] << 1) & 0x7e) | (data[8] >> 7)); + wacom_report_abs(wcombo, ABS_TILT_Y, data[8] & 0x7f); + wacom_report_key(wcombo, BTN_STYLUS, data[1] & 2); + wacom_report_key(wcombo, BTN_STYLUS2, data[1] & 4); + wacom_report_key(wcombo, BTN_TOUCH, t > 10); + } + + /* airbrush second packet */ + if ((data[1] & 0xbc) == 0xb4) { + wacom_report_abs(wcombo, ABS_WHEEL, + (data[6] << 2) | ((data[7] >> 6) & 3)); + wacom_report_abs(wcombo, ABS_TILT_X, + ((data[7] << 1) & 0x7e) | (data[8] >> 7)); + wacom_report_abs(wcombo, ABS_TILT_Y, data[8] & 0x7f); + } + return; +} + +static int wacom_intuos_irq(struct wacom_wac *wacom, void *wcombo) +{ + unsigned char *data = wacom->data; + unsigned int t; + int idx, result; + + if (data[0] != 2 && data[0] != 5 && data[0] != 6 && data[0] != 12) { + dbg("wacom_intuos_irq: received unknown report #%d", data[0]); + return 0; + } + + /* tool number */ + idx = data[1] & 0x01; + + /* pad packets. Works as a second tool and is always in prox */ + if (data[0] == 12) { + /* initiate the pad as a device */ + if (wacom->tool[1] != BTN_TOOL_FINGER) + wacom->tool[1] = BTN_TOOL_FINGER; + + wacom_report_key(wcombo, BTN_0, (data[5] & 0x01)); + wacom_report_key(wcombo, BTN_1, (data[5] & 0x02)); + wacom_report_key(wcombo, BTN_2, (data[5] & 0x04)); + wacom_report_key(wcombo, BTN_3, (data[5] & 0x08)); + wacom_report_key(wcombo, BTN_4, (data[6] & 0x01)); + wacom_report_key(wcombo, BTN_5, (data[6] & 0x02)); + wacom_report_key(wcombo, BTN_6, (data[6] & 0x04)); + wacom_report_key(wcombo, BTN_7, (data[6] & 0x08)); + wacom_report_key(wcombo, BTN_8, (data[5] & 0x10)); + wacom_report_key(wcombo, BTN_9, (data[6] & 0x10)); + wacom_report_abs(wcombo, ABS_RX, ((data[1] & 0x1f) << 8) | data[2]); + wacom_report_abs(wcombo, ABS_RY, ((data[3] & 0x1f) << 8) | data[4]); + + if ((data[5] & 0x1f) | (data[6] & 0x1f) | (data[1] & 0x1f) | + data[2] | (data[3] & 0x1f) | data[4]) + wacom_report_key(wcombo, wacom->tool[1], 1); + else + wacom_report_key(wcombo, wacom->tool[1], 0); + wacom_report_abs(wcombo, ABS_MISC, PAD_DEVICE_ID); + wacom_input_event(wcombo, EV_MSC, MSC_SERIAL, 0xffffffff); + return 1; + } + + /* process in/out prox events */ + result = wacom_intuos_inout(wacom, wcombo); + if (result) + return result-1; + + /* Only large I3 and I1 & I2 support Lense Cursor */ + if ((wacom->tool[idx] == BTN_TOOL_LENS) + && ((wacom->features->type == INTUOS3) + || (wacom->features->type == INTUOS3S))) + return 0; + + /* Cintiq doesn't send data when RDY bit isn't set */ + if ((wacom->features->type == CINTIQ) && !(data[1] & 0x40)) + return 0; + + if (wacom->features->type >= INTUOS3S) { + wacom_report_abs(wcombo, ABS_X, (data[2] << 9) | (data[3] << 1) | ((data[9] >> 1) & 1)); + wacom_report_abs(wcombo, ABS_Y, (data[4] << 9) | (data[5] << 1) | (data[9] & 1)); + wacom_report_abs(wcombo, ABS_DISTANCE, ((data[9] >> 2) & 0x3f)); + } else { + wacom_report_abs(wcombo, ABS_X, wacom_be16_to_cpu(&data[2])); + wacom_report_abs(wcombo, ABS_Y, wacom_be16_to_cpu(&data[4])); + wacom_report_abs(wcombo, ABS_DISTANCE, ((data[9] >> 3) & 0x1f)); + } + + /* process general packets */ + wacom_intuos_general(wacom, wcombo); + + /* 4D mouse, 2D mouse, marker pen rotation, or Lens cursor packets */ + if ((data[1] & 0xbc) == 0xa8 || (data[1] & 0xbe) == 0xb0) { + + if (data[1] & 0x02) { + /* Rotation packet */ + if (wacom->features->type >= INTUOS3S) { + /* I3 marker pen rotation */ + t = (data[6] << 3) | ((data[7] >> 5) & 7); + t = (data[7] & 0x20) ? ((t > 900) ? ((t-1) / 2 - 1350) : + ((t-1) / 2 + 450)) : (450 - t / 2) ; + wacom_report_abs(wcombo, ABS_Z, t); + } else { + /* 4D mouse rotation packet */ + t = (data[6] << 3) | ((data[7] >> 5) & 7); + wacom_report_abs(wcombo, ABS_RZ, (data[7] & 0x20) ? + ((t - 1) / 2) : -t / 2); + } + + } else if (!(data[1] & 0x10) && wacom->features->type < INTUOS3S) { + /* 4D mouse packet */ + wacom_report_key(wcombo, BTN_LEFT, data[8] & 0x01); + wacom_report_key(wcombo, BTN_MIDDLE, data[8] & 0x02); + wacom_report_key(wcombo, BTN_RIGHT, data[8] & 0x04); + + wacom_report_key(wcombo, BTN_SIDE, data[8] & 0x20); + wacom_report_key(wcombo, BTN_EXTRA, data[8] & 0x10); + t = (data[6] << 2) | ((data[7] >> 6) & 3); + wacom_report_abs(wcombo, ABS_THROTTLE, (data[8] & 0x08) ? -t : t); + + } else if (wacom->tool[idx] == BTN_TOOL_MOUSE) { + /* 2D mouse packet */ + wacom_report_key(wcombo, BTN_LEFT, data[8] & 0x04); + wacom_report_key(wcombo, BTN_MIDDLE, data[8] & 0x08); + wacom_report_key(wcombo, BTN_RIGHT, data[8] & 0x10); + wacom_report_rel(wcombo, REL_WHEEL, (data[8] & 0x01) + - ((data[8] & 0x02) >> 1)); + + /* I3 2D mouse side buttons */ + if (wacom->features->type >= INTUOS3S && wacom->features->type <= INTUOS3L) { + wacom_report_key(wcombo, BTN_SIDE, data[8] & 0x40); + wacom_report_key(wcombo, BTN_EXTRA, data[8] & 0x20); + } + + } else if (wacom->features->type < INTUOS3S || wacom->features->type == INTUOS3L) { + /* Lens cursor packets */ + wacom_report_key(wcombo, BTN_LEFT, data[8] & 0x01); + wacom_report_key(wcombo, BTN_MIDDLE, data[8] & 0x02); + wacom_report_key(wcombo, BTN_RIGHT, data[8] & 0x04); + wacom_report_key(wcombo, BTN_SIDE, data[8] & 0x10); + wacom_report_key(wcombo, BTN_EXTRA, data[8] & 0x08); + } + } + + wacom_report_abs(wcombo, ABS_MISC, wacom->id[idx]); /* report tool id */ + wacom_report_key(wcombo, wacom->tool[idx], 1); + wacom_input_event(wcombo, EV_MSC, MSC_SERIAL, wacom->serial[idx]); + return 1; +} + +int wacom_tpc_irq(struct wacom_wac *wacom, void *wcombo) +{ + char *data = wacom->data; + int prox = 0, pressure; + static int stylusInProx, touchInProx = 1, touchOut; + struct urb *urb = ((struct wacom_combo *)wcombo)->urb; + + dbg("wacom_tpc_irq: received report #%d", data[0]); + + if (urb->actual_length == 5 || data[0] == 6) { /* Touch data */ + if (urb->actual_length == 5) { /* with touch */ + prox = data[0] & 0x03; + } else { /* with capacity */ + prox = data[1] & 0x03; + } + + if (!stylusInProx) { /* stylus not in prox */ + if (prox) { + if (touchInProx) { + wacom->tool[1] = BTN_TOOL_DOUBLETAP; + wacom->id[0] = TOUCH_DEVICE_ID; + if (urb->actual_length != 5) { + wacom_report_abs(wcombo, ABS_X, wacom_le16_to_cpu(&data[2])); + wacom_report_abs(wcombo, ABS_Y, wacom_le16_to_cpu(&data[4])); + wacom_report_abs(wcombo, ABS_PRESSURE, wacom_le16_to_cpu(&data[6])); + wacom_report_key(wcombo, BTN_TOUCH, wacom_le16_to_cpu(&data[6])); + } else { + wacom_report_abs(wcombo, ABS_X, wacom_le16_to_cpu(&data[1])); + wacom_report_abs(wcombo, ABS_Y, wacom_le16_to_cpu(&data[3])); + wacom_report_key(wcombo, BTN_TOUCH, 1); + } + wacom_report_abs(wcombo, ABS_MISC, wacom->id[0]); + wacom_report_key(wcombo, wacom->tool[1], prox & 0x01); + touchOut = 1; + return 1; + } + } else { + wacom_report_abs(wcombo, ABS_MISC, wacom->id[0]); + wacom_report_key(wcombo, wacom->tool[1], prox & 0x01); + wacom_report_key(wcombo, BTN_TOUCH, 0); + touchOut = 0; + touchInProx = 1; + return 1; + } + } else if (touchOut || !prox) { /* force touch out-prox */ + wacom_report_abs(wcombo, ABS_MISC, TOUCH_DEVICE_ID); + wacom_report_key(wcombo, BTN_TOUCH, 0); + touchOut = 0; + touchInProx = 1; + return 1; + } + } else if (data[0] == 2) { /* Penabled */ + prox = data[1] & 0x20; + + touchInProx = 0; + + wacom->id[0] = ERASER_DEVICE_ID; + + /* + * if going from out of proximity into proximity select between the eraser + * and the pen based on the state of the stylus2 button, choose eraser if + * pressed else choose pen. if not a proximity change from out to in, send + * an out of proximity for previous tool then a in for new tool. + */ + if (prox) { /* in prox */ + if (!wacom->tool[0]) { + /* Going into proximity select tool */ + wacom->tool[1] = (data[1] & 0x08) ? BTN_TOOL_RUBBER : BTN_TOOL_PEN; + if (wacom->tool[1] == BTN_TOOL_PEN) + wacom->id[0] = STYLUS_DEVICE_ID; + } else if (wacom->tool[1] == BTN_TOOL_RUBBER && !(data[1] & 0x08)) { + /* + * was entered with stylus2 pressed + * report out proximity for previous tool + */ + wacom_report_abs(wcombo, ABS_MISC, wacom->id[0]); + wacom_report_key(wcombo, wacom->tool[1], 0); + wacom_input_sync(wcombo); + + /* set new tool */ + wacom->tool[1] = BTN_TOOL_PEN; + wacom->id[0] = STYLUS_DEVICE_ID; + return 0; + } + if (wacom->tool[1] != BTN_TOOL_RUBBER) { + /* Unknown tool selected default to pen tool */ + wacom->tool[1] = BTN_TOOL_PEN; + wacom->id[0] = STYLUS_DEVICE_ID; + } + wacom_report_key(wcombo, BTN_STYLUS, data[1] & 0x02); + wacom_report_key(wcombo, BTN_STYLUS2, data[1] & 0x10); + wacom_report_abs(wcombo, ABS_X, wacom_le16_to_cpu(&data[2])); + wacom_report_abs(wcombo, ABS_Y, wacom_le16_to_cpu(&data[4])); + pressure = ((data[7] & 0x01) << 8) | data[6]; + if (pressure < 0) + pressure = wacom->features->pressure_max + pressure + 1; + wacom_report_abs(wcombo, ABS_PRESSURE, pressure); + wacom_report_key(wcombo, BTN_TOUCH, pressure); + } else { + wacom_report_abs(wcombo, ABS_PRESSURE, 0); + wacom_report_key(wcombo, BTN_STYLUS, 0); + wacom_report_key(wcombo, BTN_STYLUS2, 0); + wacom_report_key(wcombo, BTN_TOUCH, 0); + } + wacom_report_key(wcombo, wacom->tool[1], prox); + wacom_report_abs(wcombo, ABS_MISC, wacom->id[0]); + stylusInProx = prox; + wacom->tool[0] = prox; + return 1; + } + return 0; +} + +int wacom_wac_irq(struct wacom_wac *wacom_wac, void *wcombo) +{ + switch (wacom_wac->features->type) { + case PENPARTNER: + return wacom_penpartner_irq(wacom_wac, wcombo); + + case PL: + return wacom_pl_irq(wacom_wac, wcombo); + + case WACOM_G4: + case GRAPHIRE: + case WACOM_MO: + return wacom_graphire_irq(wacom_wac, wcombo); + + case PTU: + return wacom_ptu_irq(wacom_wac, wcombo); + + case INTUOS: + case INTUOS3S: + case INTUOS3: + case INTUOS3L: + case CINTIQ: + case WACOM_BEE: + return wacom_intuos_irq(wacom_wac, wcombo); + + case TABLETPC: + return wacom_tpc_irq(wacom_wac, wcombo); + + default: + return 0; + } + return 0; +} + +void wacom_init_input_dev(struct input_dev *input_dev, struct wacom_wac *wacom_wac) +{ + switch (wacom_wac->features->type) { + case WACOM_MO: + input_dev_mo(input_dev, wacom_wac); + case WACOM_G4: + input_dev_g4(input_dev, wacom_wac); + /* fall through */ + case GRAPHIRE: + input_dev_g(input_dev, wacom_wac); + break; + case WACOM_BEE: + input_dev_bee(input_dev, wacom_wac); + case INTUOS3: + case INTUOS3L: + case CINTIQ: + input_dev_i3(input_dev, wacom_wac); + /* fall through */ + case INTUOS3S: + input_dev_i3s(input_dev, wacom_wac); + /* fall through */ + case INTUOS: + input_dev_i(input_dev, wacom_wac); + break; + case PL: + case PTU: + case TABLETPC: + input_dev_pl(input_dev, wacom_wac); + /* fall through */ + case PENPARTNER: + input_dev_pt(input_dev, wacom_wac); + break; + } + return; +} + +static struct wacom_features wacom_features[] = { + { "Wacom Penpartner", 7, 5040, 3780, 255, 0, PENPARTNER }, + { "Wacom Graphire", 8, 10206, 7422, 511, 63, GRAPHIRE }, + { "Wacom Graphire2 4x5", 8, 10206, 7422, 511, 63, GRAPHIRE }, + { "Wacom Graphire2 5x7", 8, 13918, 10206, 511, 63, GRAPHIRE }, + { "Wacom Graphire3", 8, 10208, 7424, 511, 63, GRAPHIRE }, + { "Wacom Graphire3 6x8", 8, 16704, 12064, 511, 63, GRAPHIRE }, + { "Wacom Graphire4 4x5", 8, 10208, 7424, 511, 63, WACOM_G4 }, + { "Wacom Graphire4 6x8", 8, 16704, 12064, 511, 63, WACOM_G4 }, + { "Wacom BambooFun 4x5", 9, 14760, 9225, 511, 63, WACOM_MO }, + { "Wacom BambooFun 6x8", 9, 21648, 13530, 511, 63, WACOM_MO }, + { "Wacom Bamboo1 Medium",8, 16704, 12064, 511, 63, GRAPHIRE }, + { "Wacom Volito", 8, 5104, 3712, 511, 63, GRAPHIRE }, + { "Wacom PenStation2", 8, 3250, 2320, 255, 63, GRAPHIRE }, + { "Wacom Volito2 4x5", 8, 5104, 3712, 511, 63, GRAPHIRE }, + { "Wacom Volito2 2x3", 8, 3248, 2320, 511, 63, GRAPHIRE }, + { "Wacom PenPartner2", 8, 3250, 2320, 511, 63, GRAPHIRE }, + { "Wacom Bamboo", 9, 14760, 9225, 511, 63, WACOM_MO }, + { "Wacom Bamboo1", 8, 5104, 3712, 511, 63, GRAPHIRE }, + { "Wacom Intuos 4x5", 10, 12700, 10600, 1023, 31, INTUOS }, + { "Wacom Intuos 6x8", 10, 20320, 16240, 1023, 31, INTUOS }, + { "Wacom Intuos 9x12", 10, 30480, 24060, 1023, 31, INTUOS }, + { "Wacom Intuos 12x12", 10, 30480, 31680, 1023, 31, INTUOS }, + { "Wacom Intuos 12x18", 10, 45720, 31680, 1023, 31, INTUOS }, + { "Wacom PL400", 8, 5408, 4056, 255, 0, PL }, + { "Wacom PL500", 8, 6144, 4608, 255, 0, PL }, + { "Wacom PL600", 8, 6126, 4604, 255, 0, PL }, + { "Wacom PL600SX", 8, 6260, 5016, 255, 0, PL }, + { "Wacom PL550", 8, 6144, 4608, 511, 0, PL }, + { "Wacom PL800", 8, 7220, 5780, 511, 0, PL }, + { "Wacom PL700", 8, 6758, 5406, 511, 0, PL }, + { "Wacom PL510", 8, 6282, 4762, 511, 0, PL }, + { "Wacom DTU710", 8, 34080, 27660, 511, 0, PL }, + { "Wacom DTF521", 8, 6282, 4762, 511, 0, PL }, + { "Wacom DTF720", 8, 6858, 5506, 511, 0, PL }, + { "Wacom Cintiq Partner",8, 20480, 15360, 511, 0, PTU }, + { "Wacom Intuos2 4x5", 10, 12700, 10600, 1023, 31, INTUOS }, + { "Wacom Intuos2 6x8", 10, 20320, 16240, 1023, 31, INTUOS }, + { "Wacom Intuos2 9x12", 10, 30480, 24060, 1023, 31, INTUOS }, + { "Wacom Intuos2 12x12", 10, 30480, 31680, 1023, 31, INTUOS }, + { "Wacom Intuos2 12x18", 10, 45720, 31680, 1023, 31, INTUOS }, + { "Wacom Intuos3 4x5", 10, 25400, 20320, 1023, 63, INTUOS3S }, + { "Wacom Intuos3 6x8", 10, 40640, 30480, 1023, 63, INTUOS3 }, + { "Wacom Intuos3 9x12", 10, 60960, 45720, 1023, 63, INTUOS3 }, + { "Wacom Intuos3 12x12", 10, 60960, 60960, 1023, 63, INTUOS3L }, + { "Wacom Intuos3 12x19", 10, 97536, 60960, 1023, 63, INTUOS3L }, + { "Wacom Intuos3 6x11", 10, 54204, 31750, 1023, 63, INTUOS3 }, + { "Wacom Intuos3 4x6", 10, 31496, 19685, 1023, 63, INTUOS3S }, + { "Wacom Cintiq 21UX", 10, 87200, 65600, 1023, 63, CINTIQ }, + { "Wacom Cintiq 20WSX", 10, 86680, 54180, 1023, 63, WACOM_BEE }, + { "Wacom Cintiq 12WX", 10, 53020, 33440, 1023, 63, WACOM_BEE }, + { "Wacom DTU1931", 8, 37832, 30305, 511, 0, PL }, + { "Wacom ISDv4 90", 8, 26202, 16325, 255, 0, TABLETPC }, + { "Wacom ISDv4 93", 8, 26202, 16325, 255, 0, TABLETPC }, + { "Wacom ISDv4 9A", 8, 26202, 16325, 255, 0, TABLETPC }, + { "Wacom Intuos2 6x8", 10, 20320, 16240, 1023, 31, INTUOS }, + { } +}; + +static struct usb_device_id wacom_ids[] = { + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x00) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x10) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x11) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x12) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x13) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x14) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x15) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x16) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x17) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x18) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x19) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x60) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x61) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x62) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x63) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x64) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x65) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x69) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x20) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x21) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x22) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x23) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x24) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x30) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x31) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x32) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x33) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x34) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x35) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x37) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x38) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x39) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0xC0) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0xC4) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x03) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x41) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x42) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x43) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x44) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x45) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0xB0) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0xB1) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0xB2) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0xB3) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0xB4) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0xB5) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0xB7) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x3F) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0xC5) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0xC6) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0xC7) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x90) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x93) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x9A) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x47) }, + { } +}; + +const struct usb_device_id *get_device_table(void) +{ + const struct usb_device_id *id_table = wacom_ids; + + return id_table; +} + +struct wacom_features * get_wacom_feature(const struct usb_device_id *id) +{ + int index = id - wacom_ids; + struct wacom_features *wf = &wacom_features[index]; + + return wf; +} + +MODULE_DEVICE_TABLE(usb, wacom_ids); diff --git a/drivers/input/tablet/wacom_wac.h b/drivers/input/tablet/wacom_wac.h new file mode 100644 index 0000000..f9c8b69 --- /dev/null +++ b/drivers/input/tablet/wacom_wac.h @@ -0,0 +1,55 @@ +/* + * drivers/input/tablet/wacom_wac.h + * + * 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. + */ +#ifndef WACOM_WAC_H +#define WACOM_WAC_H + +#define STYLUS_DEVICE_ID 0x02 +#define TOUCH_DEVICE_ID 0x03 +#define CURSOR_DEVICE_ID 0x06 +#define ERASER_DEVICE_ID 0x0A +#define PAD_DEVICE_ID 0x0F + +enum { + PENPARTNER = 0, + GRAPHIRE, + WACOM_G4, + PTU, + PL, + INTUOS, + INTUOS3S, + INTUOS3, + INTUOS3L, + CINTIQ, + WACOM_BEE, + WACOM_MO, + TABLETPC, + MAX_TYPE +}; + +struct wacom_features { + char *name; + int pktlen; + int x_max; + int y_max; + int pressure_max; + int distance_max; + int type; + int touch_x_max; + int touch_y_max; +}; + +struct wacom_wac { + unsigned char *data; + int tool[2]; + int id[2]; + __u32 serial[2]; + struct wacom_features *features; +}; + +#endif diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig new file mode 100644 index 0000000..3d1ab8f --- /dev/null +++ b/drivers/input/touchscreen/Kconfig @@ -0,0 +1,379 @@ +# +# Touchscreen 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_ADS7846 + tristate "ADS7846/TSC2046 and ADS7843 based touchscreens" + depends on SPI_MASTER + depends on HWMON = n || HWMON + help + Say Y here if you have a touchscreen interface using the + ADS7846/TSC2046 or ADS7843 controller, and your board-specific + setup code includes that in its table of SPI devices. + + If HWMON is selected, and the driver is told the reference voltage + on your board, you will also get hwmon interfaces for the voltage + (and on ads7846/tsc2046, temperature) sensors of this chip. + + If unsure, say N (but it's safe to say "Y"). + + To compile this driver as a module, choose M here: the + module will be called ads7846. + +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 "SharpSL (Corgi and Spitz series) touchscreen driver (DEPRECATED)" + depends on PXA_SHARPSL + select CORGI_SSP_DEPRECATED + default y + help + Say Y here to enable the driver for the touchscreen on the + Sharp SL-C7xx and SL-Cxx00 series of PDAs. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called corgi_ts. + + NOTE: this driver is deprecated, try enable SPI and generic + ADS7846-based touchscreen driver. + +config TOUCHSCREEN_FUJITSU + tristate "Fujitsu serial touchscreen" + select SERIO + help + Say Y here if you have the Fujitsu touchscreen (such as one + installed in Lifebook P series laptop) connected to your + system. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called fujitsu-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 elo. + +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_INEXIO + tristate "iNexio serial touchscreens" + select SERIO + help + Say Y here if you have an iNexio 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 inexio. + +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 6xx touchscreen" + depends on SH_HP6XX && SH_ADC + help + Say Y here if you have a HP Jornada 620/660/680/690 and want to + support the built-in touchscreen. + + To compile this driver as a module, choose M here: the + module will be called hp680_ts_input. + +config TOUCHSCREEN_HP7XX + tristate "HP Jornada 7xx touchscreen" + depends on SA1100_JORNADA720_SSP + help + Say Y here if you have a HP Jornada 710/720/728 and want + to support the built-in touchscreen. + + To compile this driver as a module, choose M here: the + module will be called jornada720_ts. + +config TOUCHSCREEN_HTCPEN + tristate "HTC Shift X9500 touchscreen" + depends on ISA + help + Say Y here if you have an HTC Shift UMPC also known as HTC X9500 + Clio / Shangrila 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 htcpen. + +config TOUCHSCREEN_PENMOUNT + tristate "Penmount serial touchscreen" + select SERIO + help + Say Y here if you have a Penmount 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 penmount. + +config TOUCHSCREEN_MIGOR + tristate "Renesas MIGO-R touchscreen" + depends on SH_MIGOR && I2C + help + Say Y here to enable MIGO-R touchscreen support. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called migor_ts. + +config TOUCHSCREEN_TOUCHRIGHT + tristate "Touchright serial touchscreen" + select SERIO + help + Say Y here if you have a Touchright 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 touchright. + +config TOUCHSCREEN_TOUCHWIN + tristate "Touchwin serial touchscreen" + select SERIO + help + Say Y here if you have a Touchwin 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 touchwin. + +config TOUCHSCREEN_ATMEL_TSADCC + tristate "Atmel Touchscreen Interface" + depends on ARCH_AT91SAM9RL + help + Say Y here if you have a 4-wire touchscreen connected to the + ADC Controller on your Atmel SoC (such as the AT91SAM9RL). + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called atmel_tsadcc. + +config TOUCHSCREEN_UCB1400 + tristate "Philips UCB1400 touchscreen" + depends on AC97_BUS + depends on UCB1400_CORE + help + This enables support for the Philips UCB1400 touchscreen interface. + The UCB1400 is an AC97 audio codec. The touchscreen interface + will be initialized only after the ALSA subsystem has been + brought up and the UCB1400 detected. You therefore have to + configure ALSA support as well (either built-in or modular, + independently of whether this driver is itself built-in or + modular) for this driver to work. + + To compile this driver as a module, choose M here: the + module will be called ucb1400_ts. + +config TOUCHSCREEN_WM97XX + tristate "Support for WM97xx AC97 touchscreen controllers" + depends on AC97_BUS + help + Say Y here if you have a Wolfson Microelectronics WM97xx + touchscreen connected to your system. Note that this option + only enables core driver, you will also need to select + support for appropriate chip below. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called wm97xx-ts. + +config TOUCHSCREEN_WM9705 + bool "WM9705 Touchscreen interface support" + depends on TOUCHSCREEN_WM97XX + default y + help + Say Y here to enable support for the Wolfson Microelectronics + WM9705 touchscreen controller. + +config TOUCHSCREEN_WM9712 + bool "WM9712 Touchscreen interface support" + depends on TOUCHSCREEN_WM97XX + default y + help + Say Y here to enable support for the Wolfson Microelectronics + WM9712 touchscreen controller. + +config TOUCHSCREEN_WM9713 + bool "WM9713 Touchscreen interface support" + depends on TOUCHSCREEN_WM97XX + default y + help + Say Y here to enable support for the Wolfson Microelectronics + WM9713 touchscreen controller. + +config TOUCHSCREEN_WM97XX_MAINSTONE + tristate "WM97xx Mainstone accelerated touch" + depends on TOUCHSCREEN_WM97XX && ARCH_PXA + help + Say Y here for support for streaming mode with WM97xx touchscreens + on Mainstone systems. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called mainstone-wm97xx. + +config TOUCHSCREEN_USB_COMPOSITE + tristate "USB Touchscreen Driver" + depends on USB_ARCH_HAS_HCD + select USB + help + USB Touchscreen driver for: + - eGalax Touchkit USB (also includes eTurboTouch CT-410/510/700) + - PanJit TouchSet USB + - 3M MicroTouch USB (EX II series) + - ITM + - some other eTurboTouch + - Gunze AHL61 + - DMC TSC-10/25 + - IRTOUCHSYSTEMS/UNITOP + - IdealTEK URTC1000 + - GoTop Super_Q2/GogoPen/PenPower tablets + + Have a look at <http://linux.chapter7.ch/touchkit/> for + a usage description and the required user-space stuff. + + To compile this driver as a module, choose M here: the + module will be called usbtouchscreen. + +config TOUCHSCREEN_USB_EGALAX + default y + bool "eGalax, eTurboTouch CT-410/510/700 device support" if EMBEDDED + depends on TOUCHSCREEN_USB_COMPOSITE + +config TOUCHSCREEN_USB_PANJIT + default y + bool "PanJit device support" if EMBEDDED + depends on TOUCHSCREEN_USB_COMPOSITE + +config TOUCHSCREEN_USB_3M + default y + bool "3M/Microtouch EX II series device support" if EMBEDDED + depends on TOUCHSCREEN_USB_COMPOSITE + +config TOUCHSCREEN_USB_ITM + default y + bool "ITM device support" if EMBEDDED + depends on TOUCHSCREEN_USB_COMPOSITE + +config TOUCHSCREEN_USB_ETURBO + default y + bool "eTurboTouch (non-eGalax compatible) device support" if EMBEDDED + depends on TOUCHSCREEN_USB_COMPOSITE + +config TOUCHSCREEN_USB_GUNZE + default y + bool "Gunze AHL61 device support" if EMBEDDED + depends on TOUCHSCREEN_USB_COMPOSITE + +config TOUCHSCREEN_USB_DMC_TSC10 + default y + bool "DMC TSC-10/25 device support" if EMBEDDED + depends on TOUCHSCREEN_USB_COMPOSITE + +config TOUCHSCREEN_USB_IRTOUCH + default y + bool "IRTOUCHSYSTEMS/UNITOP device support" if EMBEDDED + depends on TOUCHSCREEN_USB_COMPOSITE + +config TOUCHSCREEN_USB_IDEALTEK + default y + bool "IdealTEK URTC1000 device support" if EMBEDDED + depends on TOUCHSCREEN_USB_COMPOSITE + +config TOUCHSCREEN_USB_GENERAL_TOUCH + default y + bool "GeneralTouch Touchscreen device support" if EMBEDDED + depends on TOUCHSCREEN_USB_COMPOSITE + +config TOUCHSCREEN_USB_GOTOP + default y + bool "GoTop Super_Q2/GogoPen/PenPower tablet device support" if EMBEDDED + depends on TOUCHSCREEN_USB_COMPOSITE + +config TOUCHSCREEN_TOUCHIT213 + tristate "Sahara TouchIT-213 touchscreen" + select SERIO + help + Say Y here if you have a Sahara TouchIT-213 Tablet PC. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called touchit213. + +endif diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile new file mode 100644 index 0000000..15cf290 --- /dev/null +++ b/drivers/input/touchscreen/Makefile @@ -0,0 +1,33 @@ +# +# Makefile for the touchscreen drivers. +# + +# Each configuration option enables a list of files. + +wm97xx-ts-y := wm97xx-core.o + +obj-$(CONFIG_TOUCHSCREEN_ADS7846) += ads7846.o +obj-$(CONFIG_TOUCHSCREEN_ATMEL_TSADCC) += atmel_tsadcc.o +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_FUJITSU) += fujitsu_ts.o +obj-$(CONFIG_TOUCHSCREEN_INEXIO) += inexio.o +obj-$(CONFIG_TOUCHSCREEN_MIGOR) += migor_ts.o +obj-$(CONFIG_TOUCHSCREEN_MTOUCH) += mtouch.o +obj-$(CONFIG_TOUCHSCREEN_MK712) += mk712.o +obj-$(CONFIG_TOUCHSCREEN_HP600) += hp680_ts_input.o +obj-$(CONFIG_TOUCHSCREEN_HP7XX) += jornada720_ts.o +obj-$(CONFIG_TOUCHSCREEN_HTCPEN) += htcpen.o +obj-$(CONFIG_TOUCHSCREEN_USB_COMPOSITE) += usbtouchscreen.o +obj-$(CONFIG_TOUCHSCREEN_PENMOUNT) += penmount.o +obj-$(CONFIG_TOUCHSCREEN_TOUCHIT213) += touchit213.o +obj-$(CONFIG_TOUCHSCREEN_TOUCHRIGHT) += touchright.o +obj-$(CONFIG_TOUCHSCREEN_TOUCHWIN) += touchwin.o +obj-$(CONFIG_TOUCHSCREEN_UCB1400) += ucb1400_ts.o +obj-$(CONFIG_TOUCHSCREEN_WM97XX) += wm97xx-ts.o +wm97xx-ts-$(CONFIG_TOUCHSCREEN_WM9705) += wm9705.o +wm97xx-ts-$(CONFIG_TOUCHSCREEN_WM9712) += wm9712.o +wm97xx-ts-$(CONFIG_TOUCHSCREEN_WM9713) += wm9713.o +obj-$(CONFIG_TOUCHSCREEN_WM97XX_MAINSTONE) += mainstone-wm97xx.o diff --git a/drivers/input/touchscreen/ads7846.c b/drivers/input/touchscreen/ads7846.c new file mode 100644 index 0000000..b9b7fc6 --- /dev/null +++ b/drivers/input/touchscreen/ads7846.c @@ -0,0 +1,1233 @@ +/* + * ADS7846 based touchscreen and sensor driver + * + * Copyright (c) 2005 David Brownell + * Copyright (c) 2006 Nokia Corporation + * Various changes: Imre Deak <imre.deak@nokia.com> + * + * Using code from: + * - corgi_ts.c + * Copyright (C) 2004-2005 Richard Purdie + * - omap_ts.[hc], ads7846.h, ts_osk.c + * Copyright (C) 2002 MontaVista Software + * Copyright (C) 2004 Texas Instruments + * Copyright (C) 2005 Dirk Behme + * + * 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/hwmon.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/delay.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/gpio.h> +#include <linux/spi/spi.h> +#include <linux/spi/ads7846.h> +#include <asm/irq.h> + + +/* + * This code has been heavily tested on a Nokia 770, and lightly + * tested on other ads7846 devices (OSK/Mistral, Lubbock). + * TSC2046 is just newer ads7846 silicon. + * Support for ads7843 tested on Atmel at91sam926x-EK. + * Support for ads7845 has only been stubbed in. + * + * IRQ handling needs a workaround because of a shortcoming in handling + * edge triggered IRQs on some platforms like the OMAP1/2. These + * platforms don't handle the ARM lazy IRQ disabling properly, thus we + * have to maintain our own SW IRQ disabled status. This should be + * removed as soon as the affected platform's IRQ handling is fixed. + * + * app note sbaa036 talks in more detail about accurate sampling... + * that ought to help in situations like LCDs inducing noise (which + * can also be helped by using synch signals) and more generally. + * This driver tries to utilize the measures described in the app + * note. The strength of filtering can be set in the board-* specific + * files. + */ + +#define TS_POLL_DELAY (1 * 1000000) /* ns delay before the first sample */ +#define TS_POLL_PERIOD (5 * 1000000) /* ns delay between samples */ + +/* this driver doesn't aim at the peak continuous sample rate */ +#define SAMPLE_BITS (8 /*cmd*/ + 16 /*sample*/ + 2 /* before, after */) + +struct ts_event { + /* For portability, we can't read 12 bit values using SPI (which + * would make the controller deliver them as native byteorder u16 + * with msbs zeroed). Instead, we read them as two 8-bit values, + * *** WHICH NEED BYTESWAPPING *** and range adjustment. + */ + u16 x; + u16 y; + u16 z1, z2; + int ignore; +}; + +/* + * We allocate this separately to avoid cache line sharing issues when + * driver is used with DMA-based SPI controllers (like atmel_spi) on + * systems where main memory is not DMA-coherent (most non-x86 boards). + */ +struct ads7846_packet { + u8 read_x, read_y, read_z1, read_z2, pwrdown; + u16 dummy; /* for the pwrdown read */ + struct ts_event tc; +}; + +struct ads7846 { + struct input_dev *input; + char phys[32]; + + struct spi_device *spi; + +#if defined(CONFIG_HWMON) || defined(CONFIG_HWMON_MODULE) + struct attribute_group *attr_group; + struct device *hwmon; +#endif + + u16 model; + u16 vref_mv; + u16 vref_delay_usecs; + u16 x_plate_ohms; + u16 pressure_max; + + struct ads7846_packet *packet; + + struct spi_transfer xfer[18]; + struct spi_message msg[5]; + struct spi_message *last_msg; + int msg_idx; + int read_cnt; + int read_rep; + int last_read; + + u16 debounce_max; + u16 debounce_tol; + u16 debounce_rep; + + u16 penirq_recheck_delay_usecs; + + spinlock_t lock; + struct hrtimer timer; + unsigned pendown:1; /* P: lock */ + unsigned pending:1; /* P: lock */ +// FIXME remove "irq_disabled" + unsigned irq_disabled:1; /* P: lock */ + unsigned disabled:1; + unsigned is_suspended:1; + + int (*filter)(void *data, int data_idx, int *val); + void *filter_data; + void (*filter_cleanup)(void *data); + int (*get_pendown_state)(void); + int gpio_pendown; +}; + +/* leave chip selected when we're done, for quicker re-select? */ +#if 0 +#define CS_CHANGE(xfer) ((xfer).cs_change = 1) +#else +#define CS_CHANGE(xfer) ((xfer).cs_change = 0) +#endif + +/*--------------------------------------------------------------------------*/ + +/* The ADS7846 has touchscreen and other sensors. + * Earlier ads784x chips are somewhat compatible. + */ +#define ADS_START (1 << 7) +#define ADS_A2A1A0_d_y (1 << 4) /* differential */ +#define ADS_A2A1A0_d_z1 (3 << 4) /* differential */ +#define ADS_A2A1A0_d_z2 (4 << 4) /* differential */ +#define ADS_A2A1A0_d_x (5 << 4) /* differential */ +#define ADS_A2A1A0_temp0 (0 << 4) /* non-differential */ +#define ADS_A2A1A0_vbatt (2 << 4) /* non-differential */ +#define ADS_A2A1A0_vaux (6 << 4) /* non-differential */ +#define ADS_A2A1A0_temp1 (7 << 4) /* non-differential */ +#define ADS_8_BIT (1 << 3) +#define ADS_12_BIT (0 << 3) +#define ADS_SER (1 << 2) /* non-differential */ +#define ADS_DFR (0 << 2) /* differential */ +#define ADS_PD10_PDOWN (0 << 0) /* lowpower mode + penirq */ +#define ADS_PD10_ADC_ON (1 << 0) /* ADC on */ +#define ADS_PD10_REF_ON (2 << 0) /* vREF on + penirq */ +#define ADS_PD10_ALL_ON (3 << 0) /* ADC + vREF on */ + +#define MAX_12BIT ((1<<12)-1) + +/* leave ADC powered up (disables penirq) between differential samples */ +#define READ_12BIT_DFR(x, adc, vref) (ADS_START | ADS_A2A1A0_d_ ## x \ + | ADS_12_BIT | ADS_DFR | \ + (adc ? ADS_PD10_ADC_ON : 0) | (vref ? ADS_PD10_REF_ON : 0)) + +#define READ_Y(vref) (READ_12BIT_DFR(y, 1, vref)) +#define READ_Z1(vref) (READ_12BIT_DFR(z1, 1, vref)) +#define READ_Z2(vref) (READ_12BIT_DFR(z2, 1, vref)) + +#define READ_X(vref) (READ_12BIT_DFR(x, 1, vref)) +#define PWRDOWN (READ_12BIT_DFR(y, 0, 0)) /* LAST */ + +/* single-ended samples need to first power up reference voltage; + * we leave both ADC and VREF powered + */ +#define READ_12BIT_SER(x) (ADS_START | ADS_A2A1A0_ ## x \ + | ADS_12_BIT | ADS_SER) + +#define REF_ON (READ_12BIT_DFR(x, 1, 1)) +#define REF_OFF (READ_12BIT_DFR(y, 0, 0)) + +/*--------------------------------------------------------------------------*/ + +/* + * Non-touchscreen sensors only use single-ended conversions. + * The range is GND..vREF. The ads7843 and ads7835 must use external vREF; + * ads7846 lets that pin be unconnected, to use internal vREF. + */ + +struct ser_req { + u8 ref_on; + u8 command; + u8 ref_off; + u16 scratch; + __be16 sample; + struct spi_message msg; + struct spi_transfer xfer[6]; +}; + +static void ads7846_enable(struct ads7846 *ts); +static void ads7846_disable(struct ads7846 *ts); + +static int device_suspended(struct device *dev) +{ + struct ads7846 *ts = dev_get_drvdata(dev); + return ts->is_suspended || ts->disabled; +} + +static int ads7846_read12_ser(struct device *dev, unsigned command) +{ + struct spi_device *spi = to_spi_device(dev); + struct ads7846 *ts = dev_get_drvdata(dev); + struct ser_req *req = kzalloc(sizeof *req, GFP_KERNEL); + int status; + int use_internal; + + if (!req) + return -ENOMEM; + + spi_message_init(&req->msg); + + /* FIXME boards with ads7846 might use external vref instead ... */ + use_internal = (ts->model == 7846); + + /* maybe turn on internal vREF, and let it settle */ + if (use_internal) { + req->ref_on = REF_ON; + req->xfer[0].tx_buf = &req->ref_on; + req->xfer[0].len = 1; + spi_message_add_tail(&req->xfer[0], &req->msg); + + req->xfer[1].rx_buf = &req->scratch; + req->xfer[1].len = 2; + + /* for 1uF, settle for 800 usec; no cap, 100 usec. */ + req->xfer[1].delay_usecs = ts->vref_delay_usecs; + spi_message_add_tail(&req->xfer[1], &req->msg); + } + + /* take sample */ + req->command = (u8) command; + req->xfer[2].tx_buf = &req->command; + req->xfer[2].len = 1; + spi_message_add_tail(&req->xfer[2], &req->msg); + + req->xfer[3].rx_buf = &req->sample; + req->xfer[3].len = 2; + spi_message_add_tail(&req->xfer[3], &req->msg); + + /* REVISIT: take a few more samples, and compare ... */ + + /* converter in low power mode & enable PENIRQ */ + req->ref_off = PWRDOWN; + req->xfer[4].tx_buf = &req->ref_off; + req->xfer[4].len = 1; + spi_message_add_tail(&req->xfer[4], &req->msg); + + req->xfer[5].rx_buf = &req->scratch; + req->xfer[5].len = 2; + CS_CHANGE(req->xfer[5]); + spi_message_add_tail(&req->xfer[5], &req->msg); + + ts->irq_disabled = 1; + disable_irq(spi->irq); + status = spi_sync(spi, &req->msg); + ts->irq_disabled = 0; + enable_irq(spi->irq); + + if (status == 0) { + /* on-wire is a must-ignore bit, a BE12 value, then padding */ + status = be16_to_cpu(req->sample); + status = status >> 3; + status &= 0x0fff; + } + + kfree(req); + return status; +} + +#if defined(CONFIG_HWMON) || defined(CONFIG_HWMON_MODULE) + +#define SHOW(name, var, adjust) static ssize_t \ +name ## _show(struct device *dev, struct device_attribute *attr, char *buf) \ +{ \ + struct ads7846 *ts = dev_get_drvdata(dev); \ + ssize_t v = ads7846_read12_ser(dev, \ + READ_12BIT_SER(var) | ADS_PD10_ALL_ON); \ + if (v < 0) \ + return v; \ + return sprintf(buf, "%u\n", adjust(ts, v)); \ +} \ +static DEVICE_ATTR(name, S_IRUGO, name ## _show, NULL); + + +/* Sysfs conventions report temperatures in millidegrees Celcius. + * ADS7846 could use the low-accuracy two-sample scheme, but can't do the high + * accuracy scheme without calibration data. For now we won't try either; + * userspace sees raw sensor values, and must scale/calibrate appropriately. + */ +static inline unsigned null_adjust(struct ads7846 *ts, ssize_t v) +{ + return v; +} + +SHOW(temp0, temp0, null_adjust) /* temp1_input */ +SHOW(temp1, temp1, null_adjust) /* temp2_input */ + + +/* sysfs conventions report voltages in millivolts. We can convert voltages + * if we know vREF. userspace may need to scale vAUX to match the board's + * external resistors; we assume that vBATT only uses the internal ones. + */ +static inline unsigned vaux_adjust(struct ads7846 *ts, ssize_t v) +{ + unsigned retval = v; + + /* external resistors may scale vAUX into 0..vREF */ + retval *= ts->vref_mv; + retval = retval >> 12; + return retval; +} + +static inline unsigned vbatt_adjust(struct ads7846 *ts, ssize_t v) +{ + unsigned retval = vaux_adjust(ts, v); + + /* ads7846 has a resistor ladder to scale this signal down */ + if (ts->model == 7846) + retval *= 4; + return retval; +} + +SHOW(in0_input, vaux, vaux_adjust) +SHOW(in1_input, vbatt, vbatt_adjust) + + +static struct attribute *ads7846_attributes[] = { + &dev_attr_temp0.attr, + &dev_attr_temp1.attr, + &dev_attr_in0_input.attr, + &dev_attr_in1_input.attr, + NULL, +}; + +static struct attribute_group ads7846_attr_group = { + .attrs = ads7846_attributes, +}; + +static struct attribute *ads7843_attributes[] = { + &dev_attr_in0_input.attr, + &dev_attr_in1_input.attr, + NULL, +}; + +static struct attribute_group ads7843_attr_group = { + .attrs = ads7843_attributes, +}; + +static struct attribute *ads7845_attributes[] = { + &dev_attr_in0_input.attr, + NULL, +}; + +static struct attribute_group ads7845_attr_group = { + .attrs = ads7845_attributes, +}; + +static int ads784x_hwmon_register(struct spi_device *spi, struct ads7846 *ts) +{ + struct device *hwmon; + int err; + + /* hwmon sensors need a reference voltage */ + switch (ts->model) { + case 7846: + if (!ts->vref_mv) { + dev_dbg(&spi->dev, "assuming 2.5V internal vREF\n"); + ts->vref_mv = 2500; + } + break; + case 7845: + case 7843: + if (!ts->vref_mv) { + dev_warn(&spi->dev, + "external vREF for ADS%d not specified\n", + ts->model); + return 0; + } + break; + } + + /* different chips have different sensor groups */ + switch (ts->model) { + case 7846: + ts->attr_group = &ads7846_attr_group; + break; + case 7845: + ts->attr_group = &ads7845_attr_group; + break; + case 7843: + ts->attr_group = &ads7843_attr_group; + break; + default: + dev_dbg(&spi->dev, "ADS%d not recognized\n", ts->model); + return 0; + } + + err = sysfs_create_group(&spi->dev.kobj, ts->attr_group); + if (err) + return err; + + hwmon = hwmon_device_register(&spi->dev); + if (IS_ERR(hwmon)) { + sysfs_remove_group(&spi->dev.kobj, ts->attr_group); + return PTR_ERR(hwmon); + } + + ts->hwmon = hwmon; + return 0; +} + +static void ads784x_hwmon_unregister(struct spi_device *spi, + struct ads7846 *ts) +{ + if (ts->hwmon) { + sysfs_remove_group(&spi->dev.kobj, ts->attr_group); + hwmon_device_unregister(ts->hwmon); + } +} + +#else +static inline int ads784x_hwmon_register(struct spi_device *spi, + struct ads7846 *ts) +{ + return 0; +} + +static inline void ads784x_hwmon_unregister(struct spi_device *spi, + struct ads7846 *ts) +{ +} +#endif + +static int is_pen_down(struct device *dev) +{ + struct ads7846 *ts = dev_get_drvdata(dev); + + return ts->pendown; +} + +static ssize_t ads7846_pen_down_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%u\n", is_pen_down(dev)); +} + +static DEVICE_ATTR(pen_down, S_IRUGO, ads7846_pen_down_show, NULL); + +static ssize_t ads7846_disable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ads7846 *ts = dev_get_drvdata(dev); + + return sprintf(buf, "%u\n", ts->disabled); +} + +static ssize_t ads7846_disable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ads7846 *ts = dev_get_drvdata(dev); + long i; + + if (strict_strtoul(buf, 10, &i)) + return -EINVAL; + + spin_lock_irq(&ts->lock); + + if (i) + ads7846_disable(ts); + else + ads7846_enable(ts); + + spin_unlock_irq(&ts->lock); + + return count; +} + +static DEVICE_ATTR(disable, 0664, ads7846_disable_show, ads7846_disable_store); + +static struct attribute *ads784x_attributes[] = { + &dev_attr_pen_down.attr, + &dev_attr_disable.attr, + NULL, +}; + +static struct attribute_group ads784x_attr_group = { + .attrs = ads784x_attributes, +}; + +/*--------------------------------------------------------------------------*/ + +static int get_pendown_state(struct ads7846 *ts) +{ + if (ts->get_pendown_state) + return ts->get_pendown_state(); + + return !gpio_get_value(ts->gpio_pendown); +} + +/* + * PENIRQ only kicks the timer. The timer only reissues the SPI transfer, + * to retrieve touchscreen status. + * + * The SPI transfer completion callback does the real work. It reports + * touchscreen events and reactivates the timer (or IRQ) as appropriate. + */ + +static void ads7846_rx(void *ads) +{ + struct ads7846 *ts = ads; + struct ads7846_packet *packet = ts->packet; + unsigned Rt; + u16 x, y, z1, z2; + + /* ads7846_rx_val() did in-place conversion (including byteswap) from + * on-the-wire format as part of debouncing to get stable readings. + */ + x = packet->tc.x; + y = packet->tc.y; + z1 = packet->tc.z1; + z2 = packet->tc.z2; + + /* range filtering */ + if (x == MAX_12BIT) + x = 0; + + if (ts->model == 7843) { + Rt = ts->pressure_max / 2; + } else if (likely(x && z1)) { + /* compute touch pressure resistance using equation #2 */ + Rt = z2; + Rt -= z1; + Rt *= x; + Rt *= ts->x_plate_ohms; + Rt /= z1; + Rt = (Rt + 2047) >> 12; + } else { + Rt = 0; + } + + /* Sample found inconsistent by debouncing or pressure is beyond + * the maximum. Don't report it to user space, repeat at least + * once more the measurement + */ + if (packet->tc.ignore || Rt > ts->pressure_max) { +#ifdef VERBOSE + pr_debug("%s: ignored %d pressure %d\n", + ts->spi->dev.bus_id, packet->tc.ignore, Rt); +#endif + hrtimer_start(&ts->timer, ktime_set(0, TS_POLL_PERIOD), + HRTIMER_MODE_REL); + return; + } + + /* Maybe check the pendown state before reporting. This discards + * false readings when the pen is lifted. + */ + if (ts->penirq_recheck_delay_usecs) { + udelay(ts->penirq_recheck_delay_usecs); + if (!get_pendown_state(ts)) + Rt = 0; + } + + /* NOTE: We can't rely on the pressure to determine the pen down + * state, even this controller has a pressure sensor. The pressure + * value can fluctuate for quite a while after lifting the pen and + * in some cases may not even settle at the expected value. + * + * The only safe way to check for the pen up condition is in the + * timer by reading the pen signal state (it's a GPIO _and_ IRQ). + */ + if (Rt) { + struct input_dev *input = ts->input; + + if (!ts->pendown) { + input_report_key(input, BTN_TOUCH, 1); + ts->pendown = 1; +#ifdef VERBOSE + dev_dbg(&ts->spi->dev, "DOWN\n"); +#endif + } + input_report_abs(input, ABS_X, x); + input_report_abs(input, ABS_Y, y); + input_report_abs(input, ABS_PRESSURE, Rt); + + input_sync(input); +#ifdef VERBOSE + dev_dbg(&ts->spi->dev, "%4d/%4d/%4d\n", x, y, Rt); +#endif + } + + hrtimer_start(&ts->timer, ktime_set(0, TS_POLL_PERIOD), + HRTIMER_MODE_REL); +} + +static int ads7846_debounce(void *ads, int data_idx, int *val) +{ + struct ads7846 *ts = ads; + + if (!ts->read_cnt || (abs(ts->last_read - *val) > ts->debounce_tol)) { + /* Start over collecting consistent readings. */ + ts->read_rep = 0; + /* Repeat it, if this was the first read or the read + * wasn't consistent enough. */ + if (ts->read_cnt < ts->debounce_max) { + ts->last_read = *val; + ts->read_cnt++; + return ADS7846_FILTER_REPEAT; + } else { + /* Maximum number of debouncing reached and still + * not enough number of consistent readings. Abort + * the whole sample, repeat it in the next sampling + * period. + */ + ts->read_cnt = 0; + return ADS7846_FILTER_IGNORE; + } + } else { + if (++ts->read_rep > ts->debounce_rep) { + /* Got a good reading for this coordinate, + * go for the next one. */ + ts->read_cnt = 0; + ts->read_rep = 0; + return ADS7846_FILTER_OK; + } else { + /* Read more values that are consistent. */ + ts->read_cnt++; + return ADS7846_FILTER_REPEAT; + } + } +} + +static int ads7846_no_filter(void *ads, int data_idx, int *val) +{ + return ADS7846_FILTER_OK; +} + +static void ads7846_rx_val(void *ads) +{ + struct ads7846 *ts = ads; + struct ads7846_packet *packet = ts->packet; + struct spi_message *m; + struct spi_transfer *t; + int val; + int action; + int status; + + m = &ts->msg[ts->msg_idx]; + t = list_entry(m->transfers.prev, struct spi_transfer, transfer_list); + + /* adjust: on-wire is a must-ignore bit, a BE12 value, then padding; + * built from two 8 bit values written msb-first. + */ + val = be16_to_cpup((__be16 *)t->rx_buf) >> 3; + + action = ts->filter(ts->filter_data, ts->msg_idx, &val); + switch (action) { + case ADS7846_FILTER_REPEAT: + break; + case ADS7846_FILTER_IGNORE: + packet->tc.ignore = 1; + /* Last message will contain ads7846_rx() as the + * completion function. + */ + m = ts->last_msg; + break; + case ADS7846_FILTER_OK: + *(u16 *)t->rx_buf = val; + packet->tc.ignore = 0; + m = &ts->msg[++ts->msg_idx]; + break; + default: + BUG(); + } + status = spi_async(ts->spi, m); + if (status) + dev_err(&ts->spi->dev, "spi_async --> %d\n", + status); +} + +static enum hrtimer_restart ads7846_timer(struct hrtimer *handle) +{ + struct ads7846 *ts = container_of(handle, struct ads7846, timer); + int status = 0; + + spin_lock_irq(&ts->lock); + + if (unlikely(!get_pendown_state(ts) || + device_suspended(&ts->spi->dev))) { + if (ts->pendown) { + struct input_dev *input = ts->input; + + input_report_key(input, BTN_TOUCH, 0); + input_report_abs(input, ABS_PRESSURE, 0); + input_sync(input); + + ts->pendown = 0; +#ifdef VERBOSE + dev_dbg(&ts->spi->dev, "UP\n"); +#endif + } + + /* measurement cycle ended */ + if (!device_suspended(&ts->spi->dev)) { + ts->irq_disabled = 0; + enable_irq(ts->spi->irq); + } + ts->pending = 0; + } else { + /* pen is still down, continue with the measurement */ + ts->msg_idx = 0; + status = spi_async(ts->spi, &ts->msg[0]); + if (status) + dev_err(&ts->spi->dev, "spi_async --> %d\n", status); + } + + spin_unlock_irq(&ts->lock); + return HRTIMER_NORESTART; +} + +static irqreturn_t ads7846_irq(int irq, void *handle) +{ + struct ads7846 *ts = handle; + unsigned long flags; + + spin_lock_irqsave(&ts->lock, flags); + if (likely(get_pendown_state(ts))) { + if (!ts->irq_disabled) { + /* The ARM do_simple_IRQ() dispatcher doesn't act + * like the other dispatchers: it will report IRQs + * even after they've been disabled. We work around + * that here. (The "generic irq" framework may help...) + */ + ts->irq_disabled = 1; + disable_irq(ts->spi->irq); + ts->pending = 1; + hrtimer_start(&ts->timer, ktime_set(0, TS_POLL_DELAY), + HRTIMER_MODE_REL); + } + } + spin_unlock_irqrestore(&ts->lock, flags); + + return IRQ_HANDLED; +} + +/*--------------------------------------------------------------------------*/ + +/* Must be called with ts->lock held */ +static void ads7846_disable(struct ads7846 *ts) +{ + if (ts->disabled) + return; + + ts->disabled = 1; + + /* are we waiting for IRQ, or polling? */ + if (!ts->pending) { + ts->irq_disabled = 1; + disable_irq(ts->spi->irq); + } else { + /* the timer will run at least once more, and + * leave everything in a clean state, IRQ disabled + */ + while (ts->pending) { + spin_unlock_irq(&ts->lock); + msleep(1); + spin_lock_irq(&ts->lock); + } + } + + /* we know the chip's in lowpower mode since we always + * leave it that way after every request + */ +} + +/* Must be called with ts->lock held */ +static void ads7846_enable(struct ads7846 *ts) +{ + if (!ts->disabled) + return; + + ts->disabled = 0; + ts->irq_disabled = 0; + enable_irq(ts->spi->irq); +} + +static int ads7846_suspend(struct spi_device *spi, pm_message_t message) +{ + struct ads7846 *ts = dev_get_drvdata(&spi->dev); + + spin_lock_irq(&ts->lock); + + ts->is_suspended = 1; + ads7846_disable(ts); + + spin_unlock_irq(&ts->lock); + + return 0; + +} + +static int ads7846_resume(struct spi_device *spi) +{ + struct ads7846 *ts = dev_get_drvdata(&spi->dev); + + spin_lock_irq(&ts->lock); + + ts->is_suspended = 0; + ads7846_enable(ts); + + spin_unlock_irq(&ts->lock); + + return 0; +} + +static int __devinit setup_pendown(struct spi_device *spi, struct ads7846 *ts) +{ + struct ads7846_platform_data *pdata = spi->dev.platform_data; + int err; + + /* REVISIT when the irq can be triggered active-low, or if for some + * reason the touchscreen isn't hooked up, we don't need to access + * the pendown state. + */ + if (!pdata->get_pendown_state && !gpio_is_valid(pdata->gpio_pendown)) { + dev_err(&spi->dev, "no get_pendown_state nor gpio_pendown?\n"); + return -EINVAL; + } + + if (pdata->get_pendown_state) { + ts->get_pendown_state = pdata->get_pendown_state; + return 0; + } + + err = gpio_request(pdata->gpio_pendown, "ads7846_pendown"); + if (err) { + dev_err(&spi->dev, "failed to request pendown GPIO%d\n", + pdata->gpio_pendown); + return err; + } + + ts->gpio_pendown = pdata->gpio_pendown; + return 0; +} + +static int __devinit ads7846_probe(struct spi_device *spi) +{ + struct ads7846 *ts; + struct ads7846_packet *packet; + struct input_dev *input_dev; + struct ads7846_platform_data *pdata = spi->dev.platform_data; + struct spi_message *m; + struct spi_transfer *x; + int vref; + int err; + + if (!spi->irq) { + dev_dbg(&spi->dev, "no IRQ?\n"); + return -ENODEV; + } + + if (!pdata) { + dev_dbg(&spi->dev, "no platform data?\n"); + return -ENODEV; + } + + /* don't exceed max specified sample rate */ + if (spi->max_speed_hz > (125000 * SAMPLE_BITS)) { + dev_dbg(&spi->dev, "f(sample) %d KHz?\n", + (spi->max_speed_hz/SAMPLE_BITS)/1000); + return -EINVAL; + } + + /* We'd set TX wordsize 8 bits and RX wordsize to 13 bits ... except + * that even if the hardware can do that, the SPI controller driver + * may not. So we stick to very-portable 8 bit words, both RX and TX. + */ + spi->bits_per_word = 8; + spi->mode = SPI_MODE_0; + err = spi_setup(spi); + if (err < 0) + return err; + + ts = kzalloc(sizeof(struct ads7846), GFP_KERNEL); + packet = kzalloc(sizeof(struct ads7846_packet), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!ts || !packet || !input_dev) { + err = -ENOMEM; + goto err_free_mem; + } + + dev_set_drvdata(&spi->dev, ts); + + ts->packet = packet; + ts->spi = spi; + ts->input = input_dev; + ts->vref_mv = pdata->vref_mv; + + hrtimer_init(&ts->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + ts->timer.function = ads7846_timer; + + spin_lock_init(&ts->lock); + + ts->model = pdata->model ? : 7846; + ts->vref_delay_usecs = pdata->vref_delay_usecs ? : 100; + ts->x_plate_ohms = pdata->x_plate_ohms ? : 400; + ts->pressure_max = pdata->pressure_max ? : ~0; + + if (pdata->filter != NULL) { + if (pdata->filter_init != NULL) { + err = pdata->filter_init(pdata, &ts->filter_data); + if (err < 0) + goto err_free_mem; + } + ts->filter = pdata->filter; + ts->filter_cleanup = pdata->filter_cleanup; + } else if (pdata->debounce_max) { + ts->debounce_max = pdata->debounce_max; + if (ts->debounce_max < 2) + ts->debounce_max = 2; + ts->debounce_tol = pdata->debounce_tol; + ts->debounce_rep = pdata->debounce_rep; + ts->filter = ads7846_debounce; + ts->filter_data = ts; + } else + ts->filter = ads7846_no_filter; + + err = setup_pendown(spi, ts); + if (err) + goto err_cleanup_filter; + + if (pdata->penirq_recheck_delay_usecs) + ts->penirq_recheck_delay_usecs = + pdata->penirq_recheck_delay_usecs; + + snprintf(ts->phys, sizeof(ts->phys), "%s/input0", spi->dev.bus_id); + + input_dev->name = "ADS784x Touchscreen"; + input_dev->phys = ts->phys; + input_dev->dev.parent = &spi->dev; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); + input_set_abs_params(input_dev, ABS_X, + pdata->x_min ? : 0, + pdata->x_max ? : MAX_12BIT, + 0, 0); + input_set_abs_params(input_dev, ABS_Y, + pdata->y_min ? : 0, + pdata->y_max ? : MAX_12BIT, + 0, 0); + input_set_abs_params(input_dev, ABS_PRESSURE, + pdata->pressure_min, pdata->pressure_max, 0, 0); + + vref = pdata->keep_vref_on; + + /* set up the transfers to read touchscreen state; this assumes we + * use formula #2 for pressure, not #3. + */ + m = &ts->msg[0]; + x = ts->xfer; + + spi_message_init(m); + + /* y- still on; turn on only y+ (and ADC) */ + packet->read_y = READ_Y(vref); + x->tx_buf = &packet->read_y; + x->len = 1; + spi_message_add_tail(x, m); + + x++; + x->rx_buf = &packet->tc.y; + x->len = 2; + spi_message_add_tail(x, m); + + /* the first sample after switching drivers can be low quality; + * optionally discard it, using a second one after the signals + * have had enough time to stabilize. + */ + if (pdata->settle_delay_usecs) { + x->delay_usecs = pdata->settle_delay_usecs; + + x++; + x->tx_buf = &packet->read_y; + x->len = 1; + spi_message_add_tail(x, m); + + x++; + x->rx_buf = &packet->tc.y; + x->len = 2; + spi_message_add_tail(x, m); + } + + m->complete = ads7846_rx_val; + m->context = ts; + + m++; + spi_message_init(m); + + /* turn y- off, x+ on, then leave in lowpower */ + x++; + packet->read_x = READ_X(vref); + x->tx_buf = &packet->read_x; + x->len = 1; + spi_message_add_tail(x, m); + + x++; + x->rx_buf = &packet->tc.x; + x->len = 2; + spi_message_add_tail(x, m); + + /* ... maybe discard first sample ... */ + if (pdata->settle_delay_usecs) { + x->delay_usecs = pdata->settle_delay_usecs; + + x++; + x->tx_buf = &packet->read_x; + x->len = 1; + spi_message_add_tail(x, m); + + x++; + x->rx_buf = &packet->tc.x; + x->len = 2; + spi_message_add_tail(x, m); + } + + m->complete = ads7846_rx_val; + m->context = ts; + + /* turn y+ off, x- on; we'll use formula #2 */ + if (ts->model == 7846) { + m++; + spi_message_init(m); + + x++; + packet->read_z1 = READ_Z1(vref); + x->tx_buf = &packet->read_z1; + x->len = 1; + spi_message_add_tail(x, m); + + x++; + x->rx_buf = &packet->tc.z1; + x->len = 2; + spi_message_add_tail(x, m); + + /* ... maybe discard first sample ... */ + if (pdata->settle_delay_usecs) { + x->delay_usecs = pdata->settle_delay_usecs; + + x++; + x->tx_buf = &packet->read_z1; + x->len = 1; + spi_message_add_tail(x, m); + + x++; + x->rx_buf = &packet->tc.z1; + x->len = 2; + spi_message_add_tail(x, m); + } + + m->complete = ads7846_rx_val; + m->context = ts; + + m++; + spi_message_init(m); + + x++; + packet->read_z2 = READ_Z2(vref); + x->tx_buf = &packet->read_z2; + x->len = 1; + spi_message_add_tail(x, m); + + x++; + x->rx_buf = &packet->tc.z2; + x->len = 2; + spi_message_add_tail(x, m); + + /* ... maybe discard first sample ... */ + if (pdata->settle_delay_usecs) { + x->delay_usecs = pdata->settle_delay_usecs; + + x++; + x->tx_buf = &packet->read_z2; + x->len = 1; + spi_message_add_tail(x, m); + + x++; + x->rx_buf = &packet->tc.z2; + x->len = 2; + spi_message_add_tail(x, m); + } + + m->complete = ads7846_rx_val; + m->context = ts; + } + + /* power down */ + m++; + spi_message_init(m); + + x++; + packet->pwrdown = PWRDOWN; + x->tx_buf = &packet->pwrdown; + x->len = 1; + spi_message_add_tail(x, m); + + x++; + x->rx_buf = &packet->dummy; + x->len = 2; + CS_CHANGE(*x); + spi_message_add_tail(x, m); + + m->complete = ads7846_rx; + m->context = ts; + + ts->last_msg = m; + + if (request_irq(spi->irq, ads7846_irq, IRQF_TRIGGER_FALLING, + spi->dev.driver->name, ts)) { + dev_dbg(&spi->dev, "irq %d busy?\n", spi->irq); + err = -EBUSY; + goto err_free_gpio; + } + + err = ads784x_hwmon_register(spi, ts); + if (err) + goto err_free_irq; + + dev_info(&spi->dev, "touchscreen, irq %d\n", spi->irq); + + /* take a first sample, leaving nPENIRQ active and vREF off; avoid + * the touchscreen, in case it's not connected. + */ + (void) ads7846_read12_ser(&spi->dev, + READ_12BIT_SER(vaux) | ADS_PD10_ALL_ON); + + err = sysfs_create_group(&spi->dev.kobj, &ads784x_attr_group); + if (err) + goto err_remove_hwmon; + + err = input_register_device(input_dev); + if (err) + goto err_remove_attr_group; + + return 0; + + err_remove_attr_group: + sysfs_remove_group(&spi->dev.kobj, &ads784x_attr_group); + err_remove_hwmon: + ads784x_hwmon_unregister(spi, ts); + err_free_irq: + free_irq(spi->irq, ts); + err_free_gpio: + if (ts->gpio_pendown != -1) + gpio_free(ts->gpio_pendown); + err_cleanup_filter: + if (ts->filter_cleanup) + ts->filter_cleanup(ts->filter_data); + err_free_mem: + input_free_device(input_dev); + kfree(packet); + kfree(ts); + return err; +} + +static int __devexit ads7846_remove(struct spi_device *spi) +{ + struct ads7846 *ts = dev_get_drvdata(&spi->dev); + + ads784x_hwmon_unregister(spi, ts); + input_unregister_device(ts->input); + + ads7846_suspend(spi, PMSG_SUSPEND); + + sysfs_remove_group(&spi->dev.kobj, &ads784x_attr_group); + + free_irq(ts->spi->irq, ts); + /* suspend left the IRQ disabled */ + enable_irq(ts->spi->irq); + + if (ts->gpio_pendown != -1) + gpio_free(ts->gpio_pendown); + + if (ts->filter_cleanup) + ts->filter_cleanup(ts->filter_data); + + kfree(ts->packet); + kfree(ts); + + dev_dbg(&spi->dev, "unregistered touchscreen\n"); + return 0; +} + +static struct spi_driver ads7846_driver = { + .driver = { + .name = "ads7846", + .bus = &spi_bus_type, + .owner = THIS_MODULE, + }, + .probe = ads7846_probe, + .remove = __devexit_p(ads7846_remove), + .suspend = ads7846_suspend, + .resume = ads7846_resume, +}; + +static int __init ads7846_init(void) +{ + return spi_register_driver(&ads7846_driver); +} +module_init(ads7846_init); + +static void __exit ads7846_exit(void) +{ + spi_unregister_driver(&ads7846_driver); +} +module_exit(ads7846_exit); + +MODULE_DESCRIPTION("ADS7846 TouchScreen Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/touchscreen/atmel_tsadcc.c b/drivers/input/touchscreen/atmel_tsadcc.c new file mode 100644 index 0000000..a89a6a8 --- /dev/null +++ b/drivers/input/touchscreen/atmel_tsadcc.c @@ -0,0 +1,343 @@ +/* + * Atmel Touch Screen Driver + * + * Copyright (c) 2008 ATMEL + * Copyright (c) 2008 Dan Liang + * Copyright (c) 2008 TimeSys Corporation + * Copyright (c) 2008 Justin Waters + * + * Based on touchscreen code from Atmel Corporation. + * + * 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/err.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/input.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/clk.h> +#include <linux/platform_device.h> +#include <linux/io.h> + +/* Register definitions based on AT91SAM9RL64 preliminary draft datasheet */ + +#define ATMEL_TSADCC_CR 0x00 /* Control register */ +#define ATMEL_TSADCC_SWRST (1 << 0) /* Software Reset*/ +#define ATMEL_TSADCC_START (1 << 1) /* Start conversion */ + +#define ATMEL_TSADCC_MR 0x04 /* Mode register */ +#define ATMEL_TSADCC_TSAMOD (3 << 0) /* ADC mode */ +#define ATMEL_TSADCC_TSAMOD_ADC_ONLY_MODE (0x0) /* ADC Mode */ +#define ATMEL_TSADCC_TSAMOD_TS_ONLY_MODE (0x1) /* Touch Screen Only Mode */ +#define ATMEL_TSADCC_LOWRES (1 << 4) /* Resolution selection */ +#define ATMEL_TSADCC_SLEEP (1 << 5) /* Sleep mode */ +#define ATMEL_TSADCC_PENDET (1 << 6) /* Pen Detect selection */ +#define ATMEL_TSADCC_PRESCAL (0x3f << 8) /* Prescalar Rate Selection */ +#define ATMEL_TSADCC_STARTUP (0x7f << 16) /* Start Up time */ +#define ATMEL_TSADCC_SHTIM (0xf << 24) /* Sample & Hold time */ +#define ATMEL_TSADCC_PENDBC (0xf << 28) /* Pen Detect debouncing time */ + +#define ATMEL_TSADCC_TRGR 0x08 /* Trigger register */ +#define ATMEL_TSADCC_TRGMOD (7 << 0) /* Trigger mode */ +#define ATMEL_TSADCC_TRGMOD_NONE (0 << 0) +#define ATMEL_TSADCC_TRGMOD_EXT_RISING (1 << 0) +#define ATMEL_TSADCC_TRGMOD_EXT_FALLING (2 << 0) +#define ATMEL_TSADCC_TRGMOD_EXT_ANY (3 << 0) +#define ATMEL_TSADCC_TRGMOD_PENDET (4 << 0) +#define ATMEL_TSADCC_TRGMOD_PERIOD (5 << 0) +#define ATMEL_TSADCC_TRGMOD_CONTINUOUS (6 << 0) +#define ATMEL_TSADCC_TRGPER (0xffff << 16) /* Trigger period */ + +#define ATMEL_TSADCC_TSR 0x0C /* Touch Screen register */ +#define ATMEL_TSADCC_TSFREQ (0xf << 0) /* TS Frequency in Interleaved mode */ +#define ATMEL_TSADCC_TSSHTIM (0xf << 24) /* Sample & Hold time */ + +#define ATMEL_TSADCC_CHER 0x10 /* Channel Enable register */ +#define ATMEL_TSADCC_CHDR 0x14 /* Channel Disable register */ +#define ATMEL_TSADCC_CHSR 0x18 /* Channel Status register */ +#define ATMEL_TSADCC_CH(n) (1 << (n)) /* Channel number */ + +#define ATMEL_TSADCC_SR 0x1C /* Status register */ +#define ATMEL_TSADCC_EOC(n) (1 << ((n)+0)) /* End of conversion for channel N */ +#define ATMEL_TSADCC_OVRE(n) (1 << ((n)+8)) /* Overrun error for channel N */ +#define ATMEL_TSADCC_DRDY (1 << 16) /* Data Ready */ +#define ATMEL_TSADCC_GOVRE (1 << 17) /* General Overrun Error */ +#define ATMEL_TSADCC_ENDRX (1 << 18) /* End of RX Buffer */ +#define ATMEL_TSADCC_RXBUFF (1 << 19) /* TX Buffer full */ +#define ATMEL_TSADCC_PENCNT (1 << 20) /* Pen contact */ +#define ATMEL_TSADCC_NOCNT (1 << 21) /* No contact */ + +#define ATMEL_TSADCC_LCDR 0x20 /* Last Converted Data register */ +#define ATMEL_TSADCC_DATA (0x3ff << 0) /* Channel data */ + +#define ATMEL_TSADCC_IER 0x24 /* Interrupt Enable register */ +#define ATMEL_TSADCC_IDR 0x28 /* Interrupt Disable register */ +#define ATMEL_TSADCC_IMR 0x2C /* Interrupt Mask register */ +#define ATMEL_TSADCC_CDR0 0x30 /* Channel Data 0 */ +#define ATMEL_TSADCC_CDR1 0x34 /* Channel Data 1 */ +#define ATMEL_TSADCC_CDR2 0x38 /* Channel Data 2 */ +#define ATMEL_TSADCC_CDR3 0x3C /* Channel Data 3 */ +#define ATMEL_TSADCC_CDR4 0x40 /* Channel Data 4 */ +#define ATMEL_TSADCC_CDR5 0x44 /* Channel Data 5 */ + +#define ADC_CLOCK 1000000 + +struct atmel_tsadcc { + struct input_dev *input; + char phys[32]; + struct clk *clk; + int irq; + unsigned int prev_absx; + unsigned int prev_absy; + unsigned char bufferedmeasure; +}; + +static void __iomem *tsc_base; + +#define atmel_tsadcc_read(reg) __raw_readl(tsc_base + (reg)) +#define atmel_tsadcc_write(reg, val) __raw_writel((val), tsc_base + (reg)) + +static irqreturn_t atmel_tsadcc_interrupt(int irq, void *dev) +{ + struct atmel_tsadcc *ts_dev = (struct atmel_tsadcc *)dev; + struct input_dev *input_dev = ts_dev->input; + + unsigned int status; + unsigned int reg; + + status = atmel_tsadcc_read(ATMEL_TSADCC_SR); + status &= atmel_tsadcc_read(ATMEL_TSADCC_IMR); + + if (status & ATMEL_TSADCC_NOCNT) { + /* Contact lost */ + reg = atmel_tsadcc_read(ATMEL_TSADCC_MR) | ATMEL_TSADCC_PENDBC; + + atmel_tsadcc_write(ATMEL_TSADCC_MR, reg); + atmel_tsadcc_write(ATMEL_TSADCC_TRGR, ATMEL_TSADCC_TRGMOD_NONE); + atmel_tsadcc_write(ATMEL_TSADCC_IDR, + ATMEL_TSADCC_EOC(3) | ATMEL_TSADCC_NOCNT); + atmel_tsadcc_write(ATMEL_TSADCC_IER, ATMEL_TSADCC_PENCNT); + + input_report_key(input_dev, BTN_TOUCH, 0); + ts_dev->bufferedmeasure = 0; + input_sync(input_dev); + + } else if (status & ATMEL_TSADCC_PENCNT) { + /* Pen detected */ + reg = atmel_tsadcc_read(ATMEL_TSADCC_MR); + reg &= ~ATMEL_TSADCC_PENDBC; + + atmel_tsadcc_write(ATMEL_TSADCC_IDR, ATMEL_TSADCC_PENCNT); + atmel_tsadcc_write(ATMEL_TSADCC_MR, reg); + atmel_tsadcc_write(ATMEL_TSADCC_IER, + ATMEL_TSADCC_EOC(3) | ATMEL_TSADCC_NOCNT); + atmel_tsadcc_write(ATMEL_TSADCC_TRGR, + ATMEL_TSADCC_TRGMOD_PERIOD | (0x0FFF << 16)); + + } else if (status & ATMEL_TSADCC_EOC(3)) { + /* Conversion finished */ + + if (ts_dev->bufferedmeasure) { + /* Last measurement is always discarded, since it can + * be erroneous. + * Always report previous measurement */ + input_report_abs(input_dev, ABS_X, ts_dev->prev_absx); + input_report_abs(input_dev, ABS_Y, ts_dev->prev_absy); + input_report_key(input_dev, BTN_TOUCH, 1); + input_sync(input_dev); + } else + ts_dev->bufferedmeasure = 1; + + /* Now make new measurement */ + ts_dev->prev_absx = atmel_tsadcc_read(ATMEL_TSADCC_CDR3) << 10; + ts_dev->prev_absx /= atmel_tsadcc_read(ATMEL_TSADCC_CDR2); + + ts_dev->prev_absy = atmel_tsadcc_read(ATMEL_TSADCC_CDR1) << 10; + ts_dev->prev_absy /= atmel_tsadcc_read(ATMEL_TSADCC_CDR0); + } + + return IRQ_HANDLED; +} + +/* + * The functions for inserting/removing us as a module. + */ + +static int __devinit atmel_tsadcc_probe(struct platform_device *pdev) +{ + struct atmel_tsadcc *ts_dev; + struct input_dev *input_dev; + struct resource *res; + int err = 0; + unsigned int prsc; + unsigned int reg; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "no mmio resource defined.\n"); + return -ENXIO; + } + + /* Allocate memory for device */ + ts_dev = kzalloc(sizeof(struct atmel_tsadcc), GFP_KERNEL); + if (!ts_dev) { + dev_err(&pdev->dev, "failed to allocate memory.\n"); + return -ENOMEM; + } + platform_set_drvdata(pdev, ts_dev); + + input_dev = input_allocate_device(); + if (!input_dev) { + dev_err(&pdev->dev, "failed to allocate input device.\n"); + err = -EBUSY; + goto err_free_mem; + } + + ts_dev->irq = platform_get_irq(pdev, 0); + if (ts_dev->irq < 0) { + dev_err(&pdev->dev, "no irq ID is designated.\n"); + err = -ENODEV; + goto err_free_dev; + } + + if (!request_mem_region(res->start, res->end - res->start + 1, + "atmel tsadcc regs")) { + dev_err(&pdev->dev, "resources is unavailable.\n"); + err = -EBUSY; + goto err_free_dev; + } + + tsc_base = ioremap(res->start, res->end - res->start + 1); + if (!tsc_base) { + dev_err(&pdev->dev, "failed to map registers.\n"); + err = -ENOMEM; + goto err_release_mem; + } + + err = request_irq(ts_dev->irq, atmel_tsadcc_interrupt, IRQF_DISABLED, + pdev->dev.driver->name, ts_dev); + if (err) { + dev_err(&pdev->dev, "failed to allocate irq.\n"); + goto err_unmap_regs; + } + + ts_dev->clk = clk_get(&pdev->dev, "tsc_clk"); + if (IS_ERR(ts_dev->clk)) { + dev_err(&pdev->dev, "failed to get ts_clk\n"); + err = PTR_ERR(ts_dev->clk); + goto err_free_irq; + } + + ts_dev->input = input_dev; + ts_dev->bufferedmeasure = 0; + + snprintf(ts_dev->phys, sizeof(ts_dev->phys), + "%s/input0", pdev->dev.bus_id); + + input_dev->name = "atmel touch screen controller"; + input_dev->phys = ts_dev->phys; + input_dev->dev.parent = &pdev->dev; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); + + input_set_abs_params(input_dev, ABS_X, 0, 0x3FF, 0, 0); + input_set_abs_params(input_dev, ABS_Y, 0, 0x3FF, 0, 0); + + /* clk_enable() always returns 0, no need to check it */ + clk_enable(ts_dev->clk); + + prsc = clk_get_rate(ts_dev->clk); + dev_info(&pdev->dev, "Master clock is set at: %d Hz\n", prsc); + + prsc = prsc / ADC_CLOCK / 2 - 1; + + reg = ATMEL_TSADCC_TSAMOD_TS_ONLY_MODE | + ((0x00 << 5) & ATMEL_TSADCC_SLEEP) | /* Normal Mode */ + ((0x01 << 6) & ATMEL_TSADCC_PENDET) | /* Enable Pen Detect */ + ((prsc << 8) & ATMEL_TSADCC_PRESCAL) | /* PRESCAL */ + ((0x13 << 16) & ATMEL_TSADCC_STARTUP) | /* STARTUP */ + ((0x0F << 28) & ATMEL_TSADCC_PENDBC); /* PENDBC */ + + atmel_tsadcc_write(ATMEL_TSADCC_CR, ATMEL_TSADCC_SWRST); + atmel_tsadcc_write(ATMEL_TSADCC_MR, reg); + atmel_tsadcc_write(ATMEL_TSADCC_TRGR, ATMEL_TSADCC_TRGMOD_NONE); + atmel_tsadcc_write(ATMEL_TSADCC_TSR, (0x3 << 24) & ATMEL_TSADCC_TSSHTIM); + + atmel_tsadcc_read(ATMEL_TSADCC_SR); + atmel_tsadcc_write(ATMEL_TSADCC_IER, ATMEL_TSADCC_PENCNT); + + /* All went ok, so register to the input system */ + err = input_register_device(input_dev); + if (err) + goto err_fail; + + return 0; + +err_fail: + clk_disable(ts_dev->clk); + clk_put(ts_dev->clk); +err_free_irq: + free_irq(ts_dev->irq, ts_dev); +err_unmap_regs: + iounmap(tsc_base); +err_release_mem: + release_mem_region(res->start, res->end - res->start + 1); +err_free_dev: + input_free_device(ts_dev->input); +err_free_mem: + kfree(ts_dev); + return err; +} + +static int __devexit atmel_tsadcc_remove(struct platform_device *pdev) +{ + struct atmel_tsadcc *ts_dev = dev_get_drvdata(&pdev->dev); + struct resource *res; + + free_irq(ts_dev->irq, ts_dev); + + input_unregister_device(ts_dev->input); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + iounmap(tsc_base); + release_mem_region(res->start, res->end - res->start + 1); + + clk_disable(ts_dev->clk); + clk_put(ts_dev->clk); + + kfree(ts_dev); + + return 0; +} + +static struct platform_driver atmel_tsadcc_driver = { + .probe = atmel_tsadcc_probe, + .remove = __devexit_p(atmel_tsadcc_remove), + .driver = { + .name = "atmel_tsadcc", + }, +}; + +static int __init atmel_tsadcc_init(void) +{ + return platform_driver_register(&atmel_tsadcc_driver); +} + +static void __exit atmel_tsadcc_exit(void) +{ + platform_driver_unregister(&atmel_tsadcc_driver); +} + +module_init(atmel_tsadcc_init); +module_exit(atmel_tsadcc_exit); + + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Atmel TouchScreen Driver"); +MODULE_AUTHOR("Dan Liang <dan.liang@atmel.com>"); + diff --git a/drivers/input/touchscreen/corgi_ts.c b/drivers/input/touchscreen/corgi_ts.c new file mode 100644 index 0000000..65202c9 --- /dev/null +++ b/drivers/input/touchscreen/corgi_ts.c @@ -0,0 +1,385 @@ +/* + * Touchscreen driver for Sharp SL-C7xx and SL-Cxx00 models + * + * 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/platform_device.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/irq.h> + +#include <mach/sharpsl.h> +#include <mach/hardware.h> +#include <mach/pxa-regs.h> +#include <mach/pxa2xx-gpio.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 { + struct input_dev *input; + struct timer_list timer; + struct ts_event tc; + int pendown; + int power_mode; + int irq_gpio; + struct corgits_machinfo *machinfo; +}; + +#ifdef CONFIG_PXA25x +#define CCNT(a) asm volatile ("mrc p14, 0, %0, C1, C0, 0" : "=r"(a)) +#define PMNC_GET(x) asm volatile ("mrc p14, 0, %0, C0, C0, 0" : "=r"(x)) +#define PMNC_SET(x) asm volatile ("mcr p14, 0, %0, C0, C0, 0" : : "r"(x)) +#endif +#ifdef CONFIG_PXA27x +#define CCNT(a) asm volatile ("mrc p14, 0, %0, C1, C1, 0" : "=r"(a)) +#define PMNC_GET(x) asm volatile ("mrc p14, 0, %0, C0, C1, 0" : "=r"(x)) +#define PMNC_SET(x) asm volatile ("mcr p14, 0, %0, C0, C1, 0" : : "r"(x)) +#endif + +/* 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 unsigned int get_clk_frequency_khz(int info); + +static unsigned long calc_waittime(struct corgi_ts *corgi_ts) +{ + unsigned long hsync_invperiod = corgi_ts->machinfo->get_hsync_invperiod(); + + if (hsync_invperiod) + return get_clk_frequency_khz(0)*1000/hsync_invperiod; + else + return 0; +} + +static int sync_receive_data_send_cmd(struct corgi_ts *corgi_ts, int doRecive, int doSend, + unsigned int address, unsigned long wait_time) +{ + unsigned long timer1 = 0, timer2, pmnc = 0; + int pos = 0; + + if (wait_time && doSend) { + PMNC_GET(pmnc); + if (!(pmnc & 0x01)) + PMNC_SET(0x01); + + /* polling HSync */ + corgi_ts->machinfo->wait_hsync(); + /* 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 (wait_time) { + /* Wait after HSync */ + CCNT(timer2); + if (timer2-timer1 > wait_time) { + /* too slow - timeout, try again */ + corgi_ts->machinfo->wait_hsync(); + /* get CCNT */ + CCNT(timer1); + /* Wait after HSync */ + CCNT(timer2); + } + while (timer2 - timer1 < wait_time) + CCNT(timer2); + } + corgi_ssp_ads7846_put(cmd); + if (wait_time && !(pmnc & 0x01)) + PMNC_SET(pmnc); + } + 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(corgi_ts); + + /* Y-axis */ + sync_receive_data_send_cmd(corgi_ts, 0, 1, 1u, wait_time); + + /* Y-axis */ + sync_receive_data_send_cmd(corgi_ts, 1, 1, 1u, wait_time); + + /* X-axis */ + y = sync_receive_data_send_cmd(corgi_ts, 1, 1, 5u, wait_time); + + /* Z1 */ + x = sync_receive_data_send_cmd(corgi_ts, 1, 1, 3u, wait_time); + + /* Z2 */ + z1 = sync_receive_data_send_cmd(corgi_ts, 1, 1, 4u, wait_time); + z2 = sync_receive_data_send_cmd(corgi_ts, 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 input_dev *dev = corgi_ts->input; + + if (corgi_ts->power_mode != PWR_MODE_ACTIVE) + return; + + if (!corgi_ts->tc.pressure && corgi_ts->pendown == 0) + return; + + input_report_abs(dev, ABS_X, corgi_ts->tc.x); + input_report_abs(dev, ABS_Y, corgi_ts->tc.y); + input_report_abs(dev, ABS_PRESSURE, corgi_ts->tc.pressure); + input_report_key(dev, BTN_TOUCH, corgi_ts->pendown); + input_sync(dev); +} + +static void ts_interrupt_main(struct corgi_ts *corgi_ts, int isTimer) +{ + if ((GPLR(IRQ_TO_GPIO(corgi_ts->irq_gpio)) & GPIO_bit(IRQ_TO_GPIO(corgi_ts->irq_gpio))) == 0) { + /* Disable Interrupt */ + set_irq_type(corgi_ts->irq_gpio, IRQ_TYPE_NONE); + if (read_xydata(corgi_ts)) { + corgi_ts->pendown = 1; + new_data(corgi_ts); + } + 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); + } + + /* Enable Falling Edge */ + set_irq_type(corgi_ts->irq_gpio, IRQ_TYPE_EDGE_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); +} + +static irqreturn_t ts_interrupt(int irq, void *dev_id) +{ + struct corgi_ts *corgits_data = dev_id; + + ts_interrupt_main(corgits_data, 0); + return IRQ_HANDLED; +} + +#ifdef CONFIG_PM +static int corgits_suspend(struct platform_device *dev, pm_message_t state) +{ + struct corgi_ts *corgi_ts = platform_get_drvdata(dev); + + if (corgi_ts->pendown) { + del_timer_sync(&corgi_ts->timer); + corgi_ts->tc.pressure = 0; + new_data(corgi_ts); + 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 platform_device *dev) +{ + struct corgi_ts *corgi_ts = platform_get_drvdata(dev); + + corgi_ssp_ads7846_putget((4u << ADSCTRL_ADR_SH) | ADSCTRL_STS); + /* Enable Falling Edge */ + set_irq_type(corgi_ts->irq_gpio, IRQ_TYPE_EDGE_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 platform_device *pdev) +{ + struct corgi_ts *corgi_ts; + struct input_dev *input_dev; + int err = -ENOMEM; + + corgi_ts = kzalloc(sizeof(struct corgi_ts), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!corgi_ts || !input_dev) + goto fail1; + + platform_set_drvdata(pdev, corgi_ts); + + corgi_ts->machinfo = pdev->dev.platform_data; + corgi_ts->irq_gpio = platform_get_irq(pdev, 0); + + if (corgi_ts->irq_gpio < 0) { + err = -ENODEV; + goto fail1; + } + + corgi_ts->input = input_dev; + + init_timer(&corgi_ts->timer); + corgi_ts->timer.data = (unsigned long) corgi_ts; + corgi_ts->timer.function = corgi_ts_timer; + + input_dev->name = "Corgi Touchscreen"; + input_dev->phys = "corgits/input0"; + input_dev->id.bustype = BUS_HOST; + input_dev->id.vendor = 0x0001; + input_dev->id.product = 0x0002; + input_dev->id.version = 0x0100; + input_dev->dev.parent = &pdev->dev; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); + input_set_abs_params(input_dev, ABS_X, X_AXIS_MIN, X_AXIS_MAX, 0, 0); + input_set_abs_params(input_dev, ABS_Y, Y_AXIS_MIN, Y_AXIS_MAX, 0, 0); + input_set_abs_params(input_dev, ABS_PRESSURE, PRESSURE_MIN, PRESSURE_MAX, 0, 0); + + pxa_gpio_mode(IRQ_TO_GPIO(corgi_ts->irq_gpio) | 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); + + if (request_irq(corgi_ts->irq_gpio, ts_interrupt, IRQF_DISABLED, "ts", corgi_ts)) { + err = -EBUSY; + goto fail1; + } + + err = input_register_device(corgi_ts->input); + if (err) + goto fail2; + + corgi_ts->power_mode = PWR_MODE_ACTIVE; + + /* Enable Falling Edge */ + set_irq_type(corgi_ts->irq_gpio, IRQ_TYPE_EDGE_FALLING); + + return 0; + + fail2: free_irq(corgi_ts->irq_gpio, corgi_ts); + fail1: input_free_device(input_dev); + kfree(corgi_ts); + return err; +} + +static int corgits_remove(struct platform_device *pdev) +{ + struct corgi_ts *corgi_ts = platform_get_drvdata(pdev); + + free_irq(corgi_ts->irq_gpio, corgi_ts); + del_timer_sync(&corgi_ts->timer); + corgi_ts->machinfo->put_hsync(); + input_unregister_device(corgi_ts->input); + kfree(corgi_ts); + return 0; +} + +static struct platform_driver corgits_driver = { + .probe = corgits_probe, + .remove = corgits_remove, + .suspend = corgits_suspend, + .resume = corgits_resume, + .driver = { + .name = "corgi-ts", + .owner = THIS_MODULE, + }, +}; + +static int __devinit corgits_init(void) +{ + return platform_driver_register(&corgits_driver); +} + +static void __exit corgits_exit(void) +{ + platform_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"); +MODULE_ALIAS("platform:corgi-ts"); diff --git a/drivers/input/touchscreen/elo.c b/drivers/input/touchscreen/elo.c new file mode 100644 index 0000000..8f38c5e --- /dev/null +++ b/drivers/input/touchscreen/elo.c @@ -0,0 +1,408 @@ +/* + * 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> +#include <linux/ctype.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 + +#define ELO10_PACKET_LEN 8 +#define ELO10_TOUCH 0x03 +#define ELO10_PRESSURE 0x80 + +#define ELO10_LEAD_BYTE 'U' + +#define ELO10_ID_CMD 'i' + +#define ELO10_TOUCH_PACKET 'T' +#define ELO10_ACK_PACKET 'A' +#define ELI10_ID_PACKET 'I' + +/* + * Per-touchscreen data. + */ + +struct elo { + struct input_dev *dev; + struct serio *serio; + struct mutex cmd_mutex; + struct completion cmd_done; + int id; + int idx; + unsigned char expected_packet; + unsigned char csum; + unsigned char data[ELO_MAX_LENGTH]; + unsigned char response[ELO10_PACKET_LEN]; + char phys[32]; +}; + +static void elo_process_data_10(struct elo *elo, unsigned char data) +{ + struct input_dev *dev = elo->dev; + + elo->data[elo->idx] = data; + switch (elo->idx++) { + case 0: + elo->csum = 0xaa; + if (data != ELO10_LEAD_BYTE) { + pr_debug("elo: unsynchronized data: 0x%02x\n", data); + elo->idx = 0; + } + break; + + case 9: + elo->idx = 0; + if (data != elo->csum) { + pr_debug("elo: bad checksum: 0x%02x, expected 0x%02x\n", + data, elo->csum); + break; + } + if (elo->data[1] != elo->expected_packet) { + if (elo->data[1] != ELO10_TOUCH_PACKET) + pr_debug("elo: unexpected packet: 0x%02x\n", + elo->data[1]); + break; + } + if (likely(elo->data[1] == ELO10_TOUCH_PACKET)) { + 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]); + if (elo->data[2] & ELO10_PRESSURE) + input_report_abs(dev, ABS_PRESSURE, + (elo->data[8] << 8) | elo->data[7]); + input_report_key(dev, BTN_TOUCH, elo->data[2] & ELO10_TOUCH); + input_sync(dev); + } else if (elo->data[1] == ELO10_ACK_PACKET) { + if (elo->data[2] == '0') + elo->expected_packet = ELO10_TOUCH_PACKET; + complete(&elo->cmd_done); + } else { + memcpy(elo->response, &elo->data[1], ELO10_PACKET_LEN); + elo->expected_packet = ELO10_ACK_PACKET; + } + break; + } + elo->csum += data; +} + +static void elo_process_data_6(struct elo *elo, unsigned char data) +{ + 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_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 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_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 elo *elo = serio_get_drvdata(serio); + + switch(elo->id) { + case 0: + elo_process_data_10(elo, data); + break; + + case 1: + case 2: + elo_process_data_6(elo, data); + break; + + case 3: + elo_process_data_3(elo, data); + break; + } + + return IRQ_HANDLED; +} + +static int elo_command_10(struct elo *elo, unsigned char *packet) +{ + int rc = -1; + int i; + unsigned char csum = 0xaa + ELO10_LEAD_BYTE; + + mutex_lock(&elo->cmd_mutex); + + serio_pause_rx(elo->serio); + elo->expected_packet = toupper(packet[0]); + init_completion(&elo->cmd_done); + serio_continue_rx(elo->serio); + + if (serio_write(elo->serio, ELO10_LEAD_BYTE)) + goto out; + + for (i = 0; i < ELO10_PACKET_LEN; i++) { + csum += packet[i]; + if (serio_write(elo->serio, packet[i])) + goto out; + } + + if (serio_write(elo->serio, csum)) + goto out; + + wait_for_completion_timeout(&elo->cmd_done, HZ); + + if (elo->expected_packet == ELO10_TOUCH_PACKET) { + /* We are back in reporting mode, the command was ACKed */ + memcpy(packet, elo->response, ELO10_PACKET_LEN); + rc = 0; + } + + out: + mutex_unlock(&elo->cmd_mutex); + return rc; +} + +static int elo_setup_10(struct elo *elo) +{ + static const char *elo_types[] = { "Accu", "Dura", "Intelli", "Carroll" }; + struct input_dev *dev = elo->dev; + unsigned char packet[ELO10_PACKET_LEN] = { ELO10_ID_CMD }; + + if (elo_command_10(elo, packet)) + return -1; + + dev->id.version = (packet[5] << 8) | packet[4]; + + input_set_abs_params(dev, ABS_X, 96, 4000, 0, 0); + input_set_abs_params(dev, ABS_Y, 96, 4000, 0, 0); + if (packet[3] & ELO10_PRESSURE) + input_set_abs_params(dev, ABS_PRESSURE, 0, 255, 0, 0); + + printk(KERN_INFO "elo: %sTouch touchscreen, fw: %02x.%02x, " + "features: 0x%02x, controller: 0x%02x\n", + elo_types[(packet[1] -'0') & 0x03], + packet[5], packet[4], packet[3], packet[7]); + + return 0; +} + +/* + * elo_disconnect() is the opposite of elo_connect() + */ + +static void elo_disconnect(struct serio *serio) +{ + struct elo *elo = serio_get_drvdata(serio); + + input_get_device(elo->dev); + input_unregister_device(elo->dev); + serio_close(serio); + serio_set_drvdata(serio, NULL); + input_put_device(elo->dev); + 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; + struct input_dev *input_dev; + int err; + + elo = kzalloc(sizeof(struct elo), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!elo || !input_dev) { + err = -ENOMEM; + goto fail1; + } + + elo->serio = serio; + elo->id = serio->id.id; + elo->dev = input_dev; + elo->expected_packet = ELO10_TOUCH_PACKET; + mutex_init(&elo->cmd_mutex); + init_completion(&elo->cmd_done); + snprintf(elo->phys, sizeof(elo->phys), "%s/input0", serio->phys); + + input_dev->name = "Elo Serial TouchScreen"; + input_dev->phys = elo->phys; + input_dev->id.bustype = BUS_RS232; + input_dev->id.vendor = SERIO_ELO; + input_dev->id.product = elo->id; + input_dev->id.version = 0x0100; + input_dev->dev.parent = &serio->dev; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); + + serio_set_drvdata(serio, elo); + err = serio_open(serio, drv); + if (err) + goto fail2; + + switch (elo->id) { + + case 0: /* 10-byte protocol */ + if (elo_setup_10(elo)) + goto fail3; + + break; + + case 1: /* 6-byte protocol */ + input_set_abs_params(input_dev, ABS_PRESSURE, 0, 15, 0, 0); + + case 2: /* 4-byte protocol */ + input_set_abs_params(input_dev, ABS_X, 96, 4000, 0, 0); + input_set_abs_params(input_dev, ABS_Y, 96, 4000, 0, 0); + break; + + case 3: /* 3-byte protocol */ + input_set_abs_params(input_dev, ABS_X, 0, 255, 0, 0); + input_set_abs_params(input_dev, ABS_Y, 0, 255, 0, 0); + break; + } + + err = input_register_device(elo->dev); + if (err) + goto fail3; + + return 0; + + fail3: serio_close(serio); + fail2: serio_set_drvdata(serio, NULL); + fail1: input_free_device(input_dev); + kfree(elo); + return err; +} + +/* + * 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) +{ + return serio_register_driver(&elo_drv); +} + +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/fujitsu_ts.c b/drivers/input/touchscreen/fujitsu_ts.c new file mode 100644 index 0000000..80b2180 --- /dev/null +++ b/drivers/input/touchscreen/fujitsu_ts.c @@ -0,0 +1,189 @@ +/* + * Fujitsu serial touchscreen driver + * + * Copyright (c) Dmitry Torokhov <dtor@mail.ru> + */ + +/* + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#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 "Fujitsu serial touchscreen driver" + +MODULE_AUTHOR("Dmitry Torokhov <dtor@mail.ru>"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +#define FUJITSU_LENGTH 5 + +/* + * Per-touchscreen data. + */ +struct fujitsu { + struct input_dev *dev; + struct serio *serio; + int idx; + unsigned char data[FUJITSU_LENGTH]; + char phys[32]; +}; + +/* + * Decode serial data (5 bytes per packet) + * First byte + * 1 C 0 0 R S S S + * Where C is 1 while in calibration mode (which we don't use) + * R is 1 when no coordinate corection was done. + * S are button state + */ +static irqreturn_t fujitsu_interrupt(struct serio *serio, + unsigned char data, unsigned int flags) +{ + struct fujitsu *fujitsu = serio_get_drvdata(serio); + struct input_dev *dev = fujitsu->dev; + + if (fujitsu->idx == 0) { + /* resync skip until start of frame */ + if ((data & 0xf0) != 0x80) + return IRQ_HANDLED; + } else { + /* resync skip garbage */ + if (data & 0x80) { + fujitsu->idx = 0; + return IRQ_HANDLED; + } + } + + fujitsu->data[fujitsu->idx++] = data; + if (fujitsu->idx == FUJITSU_LENGTH) { + input_report_abs(dev, ABS_X, + (fujitsu->data[2] << 7) | fujitsu->data[1]); + input_report_abs(dev, ABS_Y, + (fujitsu->data[4] << 7) | fujitsu->data[3]); + input_report_key(dev, BTN_TOUCH, + (fujitsu->data[0] & 0x03) != 2); + input_sync(dev); + fujitsu->idx = 0; + } + + return IRQ_HANDLED; +} + +/* + * fujitsu_disconnect() is the opposite of fujitsu_connect() + */ +static void fujitsu_disconnect(struct serio *serio) +{ + struct fujitsu *fujitsu = serio_get_drvdata(serio); + + input_get_device(fujitsu->dev); + input_unregister_device(fujitsu->dev); + serio_close(serio); + serio_set_drvdata(serio, NULL); + input_put_device(fujitsu->dev); + kfree(fujitsu); +} + +/* + * fujitsu_connect() is the routine that is called when someone adds a + * new serio device that supports the Fujitsu protocol and registers it + * as input device. + */ +static int fujitsu_connect(struct serio *serio, struct serio_driver *drv) +{ + struct fujitsu *fujitsu; + struct input_dev *input_dev; + int err; + + fujitsu = kzalloc(sizeof(struct fujitsu), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!fujitsu || !input_dev) { + err = -ENOMEM; + goto fail1; + } + + fujitsu->serio = serio; + fujitsu->dev = input_dev; + snprintf(fujitsu->phys, sizeof(fujitsu->phys), + "%s/input0", serio->phys); + + input_dev->name = "Fujitsu Serial Touchscreen"; + input_dev->phys = fujitsu->phys; + input_dev->id.bustype = BUS_RS232; + input_dev->id.vendor = SERIO_FUJITSU; + input_dev->id.product = 0; + input_dev->id.version = 0x0100; + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); + + input_set_abs_params(input_dev, ABS_X, 0, 4096, 0, 0); + input_set_abs_params(input_dev, ABS_Y, 0, 4096, 0, 0); + serio_set_drvdata(serio, fujitsu); + + err = serio_open(serio, drv); + if (err) + goto fail2; + + err = input_register_device(fujitsu->dev); + if (err) + goto fail3; + + return 0; + + fail3: + serio_close(serio); + fail2: + serio_set_drvdata(serio, NULL); + fail1: + input_free_device(input_dev); + kfree(fujitsu); + return err; +} + +/* + * The serio driver structure. + */ +static struct serio_device_id fujitsu_serio_ids[] = { + { + .type = SERIO_RS232, + .proto = SERIO_FUJITSU, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { 0 } +}; + +MODULE_DEVICE_TABLE(serio, fujitsu_serio_ids); + +static struct serio_driver fujitsu_drv = { + .driver = { + .name = "fujitsu_ts", + }, + .description = DRIVER_DESC, + .id_table = fujitsu_serio_ids, + .interrupt = fujitsu_interrupt, + .connect = fujitsu_connect, + .disconnect = fujitsu_disconnect, +}; + +static int __init fujitsu_init(void) +{ + return serio_register_driver(&fujitsu_drv); +} + +static void __exit fujitsu_exit(void) +{ + serio_unregister_driver(&fujitsu_drv); +} + +module_init(fujitsu_init); +module_exit(fujitsu_exit); diff --git a/drivers/input/touchscreen/gunze.c b/drivers/input/touchscreen/gunze.c new file mode 100644 index 0000000..a54f90e --- /dev/null +++ b/drivers/input/touchscreen/gunze.c @@ -0,0 +1,204 @@ +/* + * 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 + +/* + * 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 input_dev *dev = gunze->dev; + + if (gunze->idx != GUNZE_MAX_LENGTH || gunze->data[5] != ',' || + (gunze->data[0] != 'T' && gunze->data[0] != 'R')) { + printk(KERN_WARNING "gunze.c: bad packet: >%.*s<\n", GUNZE_MAX_LENGTH, gunze->data); + return; + } + + 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 gunze* gunze = serio_get_drvdata(serio); + + if (data == '\r') { + gunze_process_packet(gunze); + 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_get_device(gunze->dev); + input_unregister_device(gunze->dev); + serio_close(serio); + serio_set_drvdata(serio, NULL); + input_put_device(gunze->dev); + 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; + struct input_dev *input_dev; + int err; + + gunze = kzalloc(sizeof(struct gunze), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!gunze || !input_dev) { + err = -ENOMEM; + goto fail1; + } + + gunze->serio = serio; + gunze->dev = input_dev; + snprintf(gunze->phys, sizeof(serio->phys), "%s/input0", serio->phys); + + input_dev->name = "Gunze AHL-51S TouchScreen"; + input_dev->phys = gunze->phys; + input_dev->id.bustype = BUS_RS232; + input_dev->id.vendor = SERIO_GUNZE; + input_dev->id.product = 0x0051; + input_dev->id.version = 0x0100; + input_dev->dev.parent = &serio->dev; + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); + input_set_abs_params(input_dev, ABS_X, 24, 1000, 0, 0); + input_set_abs_params(input_dev, ABS_Y, 24, 1000, 0, 0); + + serio_set_drvdata(serio, gunze); + + err = serio_open(serio, drv); + if (err) + goto fail2; + + err = input_register_device(gunze->dev); + if (err) + goto fail3; + + return 0; + + fail3: serio_close(serio); + fail2: serio_set_drvdata(serio, NULL); + fail1: input_free_device(input_dev); + kfree(gunze); + return err; +} + +/* + * 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) +{ + return serio_register_driver(&gunze_drv); +} + +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 0000000..4d3139e --- /dev/null +++ b/drivers/input/touchscreen/h3600_ts_input.c @@ -0,0 +1,490 @@ +/* + * 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> + +/* SA1100 serial defines */ +#include <mach/hardware.h> +#include <mach/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 */ + +/* + * Per-touchscreen data. + */ +struct h3600_dev { + struct input_dev *dev; + struct serio *serio; + 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) +{ + int down = (GPLR & GPIO_BITSY_ACTION_BUTTON) ? 0 : 1; + struct input_dev *dev = dev_id; + + input_report_key(dev, KEY_ENTER, down); + input_sync(dev); + + return IRQ_HANDLED; +} + +static irqreturn_t npower_button_handler(int irq, void *dev_id) +{ + int down = (GPLR & GPIO_BITSY_NPOWER_BUTTON) ? 0 : 1; + struct input_dev *dev = dev_id; + + /* + * This interrupt is only called when we release the key. So we have + * to fake a key press. + */ + 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 = input_get_drvdata(dev); + + /* 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; +} + +#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 input_dev *dev = ts->dev; + static int touched = 0; + int key, down = 0; + + 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) +{ +#if 0 + struct h3600_dev *ts = input_get_drvdata(dev); + + switch (type) { + case EV_LED: { + // ts->serio->write(ts->serio, SOME_CMD); + return 0; + } + } + return -1; +#endif + return 0; +} + +/* + 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 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); + 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; + struct input_dev *input_dev; + int err; + + ts = kzalloc(sizeof(struct h3600_dev), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!ts || !input_dev) { + err = -ENOMEM; + goto fail1; + } + + ts->serio = serio; + ts->dev = input_dev; + snprintf(ts->phys, sizeof(ts->phys), "%s/input0", serio->phys); + + input_dev->name = "H3600 TouchScreen"; + input_dev->phys = ts->phys; + input_dev->id.bustype = BUS_RS232; + input_dev->id.vendor = SERIO_H3600; + input_dev->id.product = 0x0666; /* FIXME !!! We can ask the hardware */ + input_dev->id.version = 0x0100; + input_dev->dev.parent = &serio->dev; + + input_set_drvdata(input_dev, ts); + + input_dev->event = h3600ts_event; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS) | + BIT_MASK(EV_LED) | BIT_MASK(EV_PWR); + input_dev->ledbit[0] = BIT_MASK(LED_SLEEP); + input_set_abs_params(input_dev, ABS_X, 60, 985, 0, 0); + input_set_abs_params(input_dev, ABS_Y, 35, 1024, 0, 0); + + set_bit(KEY_RECORD, input_dev->keybit); + set_bit(KEY_Q, input_dev->keybit); + set_bit(KEY_PROG1, input_dev->keybit); + set_bit(KEY_PROG2, input_dev->keybit); + set_bit(KEY_PROG3, input_dev->keybit); + set_bit(KEY_UP, input_dev->keybit); + set_bit(KEY_RIGHT, input_dev->keybit); + set_bit(KEY_LEFT, input_dev->keybit); + set_bit(KEY_DOWN, input_dev->keybit); + set_bit(KEY_ENTER, input_dev->keybit); + set_bit(KEY_SUSPEND, input_dev->keybit); + set_bit(BTN_TOUCH, input_dev->keybit); + + /* 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, + IRQF_SHARED | IRQF_DISABLED, "h3600_action", &ts->dev)) { + printk(KERN_ERR "h3600ts.c: Could not allocate Action Button IRQ!\n"); + err = -EBUSY; + goto fail2; + } + + if (request_irq(IRQ_GPIO_BITSY_NPOWER_BUTTON, npower_button_handler, + IRQF_SHARED | IRQF_DISABLED, "h3600_suspend", &ts->dev)) { + printk(KERN_ERR "h3600ts.c: Could not allocate Power Button IRQ!\n"); + err = -EBUSY; + goto fail3; + } + + serio_set_drvdata(serio, ts); + + err = serio_open(serio, drv); + if (err) + return err; + + //h3600_flite_control(1, 25); /* default brightness */ + input_register_device(ts->dev); + + return 0; + +fail3: free_irq(IRQ_GPIO_BITSY_NPOWER_BUTTON, ts->dev); +fail2: free_irq(IRQ_GPIO_BITSY_ACTION_BUTTON, ts->dev); +fail1: serio_set_drvdata(serio, NULL); + input_free_device(input_dev); + kfree(ts); + return err; +} + +/* + * 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_get_device(ts->dev); + input_unregister_device(ts->dev); + serio_close(serio); + serio_set_drvdata(serio, NULL); + input_put_device(ts->dev); + 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) +{ + return serio_register_driver(&h3600ts_drv); +} + +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 0000000..a89700e --- /dev/null +++ b/drivers/input/touchscreen/hp680_ts_input.c @@ -0,0 +1,129 @@ +#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 <mach/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 PHDR 0xa400012e +#define SCPDR 0xa4000136 + +static void do_softint(struct work_struct *work); + +static struct input_dev *hp680_ts_dev; +static DECLARE_DELAYED_WORK(work, do_softint); + +static void do_softint(struct work_struct *work) +{ + 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) +{ + disable_irq_nosync(irq); + schedule_delayed_work(&work, HZ / 20); + + return IRQ_HANDLED; +} + +static int __init hp680_ts_init(void) +{ + int err; + + hp680_ts_dev = input_allocate_device(); + if (!hp680_ts_dev) + return -ENOMEM; + + hp680_ts_dev->evbit[0] = BIT_MASK(EV_ABS) | BIT_MASK(EV_KEY); + hp680_ts_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); + + input_set_abs_params(hp680_ts_dev, ABS_X, + HP680_TS_ABS_X_MIN, HP680_TS_ABS_X_MAX, 0, 0); + input_set_abs_params(hp680_ts_dev, ABS_Y, + HP680_TS_ABS_Y_MIN, HP680_TS_ABS_Y_MAX, 0, 0); + + hp680_ts_dev->name = "HP Jornada touchscreen"; + hp680_ts_dev->phys = "hp680_ts/input0"; + + if (request_irq(HP680_TS_IRQ, hp680_ts_interrupt, + IRQF_DISABLED, MODNAME, 0) < 0) { + printk(KERN_ERR "hp680_touchscreen.c: Can't allocate irq %d\n", + HP680_TS_IRQ); + err = -EBUSY; + goto fail1; + } + + err = input_register_device(hp680_ts_dev); + if (err) + goto fail2; + + return 0; + + fail2: free_irq(HP680_TS_IRQ, NULL); + cancel_delayed_work(&work); + flush_scheduled_work(); + fail1: input_free_device(hp680_ts_dev); + return err; +} + +static void __exit hp680_ts_exit(void) +{ + free_irq(HP680_TS_IRQ, NULL); + 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/htcpen.c b/drivers/input/touchscreen/htcpen.c new file mode 100644 index 0000000..62811de --- /dev/null +++ b/drivers/input/touchscreen/htcpen.c @@ -0,0 +1,255 @@ +/* + * HTC Shift touchscreen driver + * + * Copyright (C) 2008 Pau Oliva Fora <pof@eslack.org> + * + * 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/errno.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/init.h> +#include <linux/irq.h> +#include <linux/isa.h> +#include <linux/ioport.h> +#include <linux/dmi.h> + +MODULE_AUTHOR("Pau Oliva Fora <pau@eslack.org>"); +MODULE_DESCRIPTION("HTC Shift touchscreen driver"); +MODULE_LICENSE("GPL"); + +#define HTCPEN_PORT_IRQ_CLEAR 0x068 +#define HTCPEN_PORT_INIT 0x06c +#define HTCPEN_PORT_INDEX 0x0250 +#define HTCPEN_PORT_DATA 0x0251 +#define HTCPEN_IRQ 3 + +#define DEVICE_ENABLE 0xa2 +#define DEVICE_DISABLE 0xa3 + +#define X_INDEX 3 +#define Y_INDEX 5 +#define TOUCH_INDEX 0xb +#define LSB_XY_INDEX 0xc +#define X_AXIS_MAX 2040 +#define Y_AXIS_MAX 2040 + +static int invert_x; +module_param(invert_x, bool, 0644); +MODULE_PARM_DESC(invert_x, "If set, X axis is inverted"); +static int invert_y; +module_param(invert_y, bool, 0644); +MODULE_PARM_DESC(invert_y, "If set, Y axis is inverted"); + +static struct pnp_device_id pnp_ids[] = { + { .id = "PNP0cc0" }, + { .id = "" } +}; +MODULE_DEVICE_TABLE(pnp, pnp_ids); + +static irqreturn_t htcpen_interrupt(int irq, void *handle) +{ + struct input_dev *htcpen_dev = handle; + unsigned short x, y, xy; + + /* 0 = press; 1 = release */ + outb_p(TOUCH_INDEX, HTCPEN_PORT_INDEX); + + if (inb_p(HTCPEN_PORT_DATA)) { + input_report_key(htcpen_dev, BTN_TOUCH, 0); + } else { + outb_p(X_INDEX, HTCPEN_PORT_INDEX); + x = inb_p(HTCPEN_PORT_DATA); + + outb_p(Y_INDEX, HTCPEN_PORT_INDEX); + y = inb_p(HTCPEN_PORT_DATA); + + outb_p(LSB_XY_INDEX, HTCPEN_PORT_INDEX); + xy = inb_p(HTCPEN_PORT_DATA); + + /* get high resolution value of X and Y using LSB */ + x = X_AXIS_MAX - ((x * 8) + ((xy >> 4) & 0xf)); + y = (y * 8) + (xy & 0xf); + if (invert_x) + x = X_AXIS_MAX - x; + if (invert_y) + y = Y_AXIS_MAX - y; + + if (x != X_AXIS_MAX && x != 0) { + input_report_key(htcpen_dev, BTN_TOUCH, 1); + input_report_abs(htcpen_dev, ABS_X, x); + input_report_abs(htcpen_dev, ABS_Y, y); + } + } + + input_sync(htcpen_dev); + + inb_p(HTCPEN_PORT_IRQ_CLEAR); + + return IRQ_HANDLED; +} + +static int htcpen_open(struct input_dev *dev) +{ + outb_p(DEVICE_ENABLE, HTCPEN_PORT_INIT); + + return 0; +} + +static void htcpen_close(struct input_dev *dev) +{ + outb_p(DEVICE_DISABLE, HTCPEN_PORT_INIT); + synchronize_irq(HTCPEN_IRQ); +} + +static int __devinit htcpen_isa_probe(struct device *dev, unsigned int id) +{ + struct input_dev *htcpen_dev; + int err = -EBUSY; + + if (!request_region(HTCPEN_PORT_IRQ_CLEAR, 1, "htcpen")) { + printk(KERN_ERR "htcpen: unable to get IO region 0x%x\n", + HTCPEN_PORT_IRQ_CLEAR); + goto request_region1_failed; + } + + if (!request_region(HTCPEN_PORT_INIT, 1, "htcpen")) { + printk(KERN_ERR "htcpen: unable to get IO region 0x%x\n", + HTCPEN_PORT_INIT); + goto request_region2_failed; + } + + if (!request_region(HTCPEN_PORT_INDEX, 2, "htcpen")) { + printk(KERN_ERR "htcpen: unable to get IO region 0x%x\n", + HTCPEN_PORT_INDEX); + goto request_region3_failed; + } + + htcpen_dev = input_allocate_device(); + if (!htcpen_dev) { + printk(KERN_ERR "htcpen: can't allocate device\n"); + err = -ENOMEM; + goto input_alloc_failed; + } + + htcpen_dev->name = "HTC Shift EC TouchScreen"; + htcpen_dev->id.bustype = BUS_ISA; + + htcpen_dev->evbit[0] = BIT_MASK(EV_ABS) | BIT_MASK(EV_KEY); + htcpen_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); + input_set_abs_params(htcpen_dev, ABS_X, 0, X_AXIS_MAX, 0, 0); + input_set_abs_params(htcpen_dev, ABS_Y, 0, Y_AXIS_MAX, 0, 0); + + htcpen_dev->open = htcpen_open; + htcpen_dev->close = htcpen_close; + + err = request_irq(HTCPEN_IRQ, htcpen_interrupt, 0, "htcpen", + htcpen_dev); + if (err) { + printk(KERN_ERR "htcpen: irq busy\n"); + goto request_irq_failed; + } + + inb_p(HTCPEN_PORT_IRQ_CLEAR); + + err = input_register_device(htcpen_dev); + if (err) + goto input_register_failed; + + dev_set_drvdata(dev, htcpen_dev); + + return 0; + + input_register_failed: + free_irq(HTCPEN_IRQ, htcpen_dev); + request_irq_failed: + input_free_device(htcpen_dev); + input_alloc_failed: + release_region(HTCPEN_PORT_INDEX, 2); + request_region3_failed: + release_region(HTCPEN_PORT_INIT, 1); + request_region2_failed: + release_region(HTCPEN_PORT_IRQ_CLEAR, 1); + request_region1_failed: + return err; +} + +static int __devexit htcpen_isa_remove(struct device *dev, unsigned int id) +{ + struct input_dev *htcpen_dev = dev_get_drvdata(dev); + + input_unregister_device(htcpen_dev); + + free_irq(HTCPEN_IRQ, htcpen_dev); + + release_region(HTCPEN_PORT_INDEX, 2); + release_region(HTCPEN_PORT_INIT, 1); + release_region(HTCPEN_PORT_IRQ_CLEAR, 1); + + dev_set_drvdata(dev, NULL); + + return 0; +} + +#ifdef CONFIG_PM +static int htcpen_isa_suspend(struct device *dev, unsigned int n, + pm_message_t state) +{ + outb_p(DEVICE_DISABLE, HTCPEN_PORT_INIT); + + return 0; +} + +static int htcpen_isa_resume(struct device *dev, unsigned int n) +{ + outb_p(DEVICE_ENABLE, HTCPEN_PORT_INIT); + + return 0; +} +#endif + +static struct isa_driver htcpen_isa_driver = { + .probe = htcpen_isa_probe, + .remove = __devexit_p(htcpen_isa_remove), +#ifdef CONFIG_PM + .suspend = htcpen_isa_suspend, + .resume = htcpen_isa_resume, +#endif + .driver = { + .owner = THIS_MODULE, + .name = "htcpen", + } +}; + +static struct dmi_system_id __initdata htcshift_dmi_table[] = { + { + .ident = "Shift", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "High Tech Computer Corp"), + DMI_MATCH(DMI_PRODUCT_NAME, "Shift"), + }, + }, + { } +}; + +static int __init htcpen_isa_init(void) +{ + if (!dmi_check_system(htcshift_dmi_table)) + return -ENODEV; + + return isa_register_driver(&htcpen_isa_driver, 1); +} + +static void __exit htcpen_isa_exit(void) +{ + isa_unregister_driver(&htcpen_isa_driver); +} + +module_init(htcpen_isa_init); +module_exit(htcpen_isa_exit); diff --git a/drivers/input/touchscreen/inexio.c b/drivers/input/touchscreen/inexio.c new file mode 100644 index 0000000..192ade0 --- /dev/null +++ b/drivers/input/touchscreen/inexio.c @@ -0,0 +1,207 @@ +/* + * iNexio serial touchscreen driver + * + * Copyright (c) 2008 Richard Lemon + * Based on the mtouch driver (c) Vojtech Pavlik and Dan Streetman + * + */ + +/* + * 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. + */ + +/* + * 2008/06/19 Richard Lemon <richard@codelemon.com> + * Copied mtouch.c and edited for iNexio 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 "iNexio serial touchscreen driver" + +MODULE_AUTHOR("Richard Lemon <richard@codelemon.com>"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +/* + * Definitions & global arrays. + */ + +#define INEXIO_FORMAT_TOUCH_BIT 0x01 +#define INEXIO_FORMAT_LENGTH 5 +#define INEXIO_RESPONSE_BEGIN_BYTE 0x80 + +/* todo: check specs for max length of all responses */ +#define INEXIO_MAX_LENGTH 16 + +#define INEXIO_MIN_XC 0 +#define INEXIO_MAX_XC 0x3fff +#define INEXIO_MIN_YC 0 +#define INEXIO_MAX_YC 0x3fff + +#define INEXIO_GET_XC(data) (((data[1])<<7) | data[2]) +#define INEXIO_GET_YC(data) (((data[3])<<7) | data[4]) +#define INEXIO_GET_TOUCHED(data) (INEXIO_FORMAT_TOUCH_BIT & data[0]) + +/* + * Per-touchscreen data. + */ + +struct inexio { + struct input_dev *dev; + struct serio *serio; + int idx; + unsigned char data[INEXIO_MAX_LENGTH]; + char phys[32]; +}; + +static void inexio_process_data(struct inexio *pinexio) +{ + struct input_dev *dev = pinexio->dev; + + if (INEXIO_FORMAT_LENGTH == ++pinexio->idx) { + input_report_abs(dev, ABS_X, INEXIO_GET_XC(pinexio->data)); + input_report_abs(dev, ABS_Y, INEXIO_GET_YC(pinexio->data)); + input_report_key(dev, BTN_TOUCH, INEXIO_GET_TOUCHED(pinexio->data)); + input_sync(dev); + + pinexio->idx = 0; + } +} + +static irqreturn_t inexio_interrupt(struct serio *serio, + unsigned char data, unsigned int flags) +{ + struct inexio* pinexio = serio_get_drvdata(serio); + + pinexio->data[pinexio->idx] = data; + + if (INEXIO_RESPONSE_BEGIN_BYTE&pinexio->data[0]) + inexio_process_data(pinexio); + else + printk(KERN_DEBUG "inexio.c: unknown/unsynchronized data from device, byte %x\n",pinexio->data[0]); + + return IRQ_HANDLED; +} + +/* + * inexio_disconnect() is the opposite of inexio_connect() + */ + +static void inexio_disconnect(struct serio *serio) +{ + struct inexio* pinexio = serio_get_drvdata(serio); + + input_get_device(pinexio->dev); + input_unregister_device(pinexio->dev); + serio_close(serio); + serio_set_drvdata(serio, NULL); + input_put_device(pinexio->dev); + kfree(pinexio); +} + +/* + * inexio_connect() is the routine that is called when someone adds a + * new serio device that supports iNexio protocol and registers it as + * an input device. This is usually accomplished using inputattach. + */ + +static int inexio_connect(struct serio *serio, struct serio_driver *drv) +{ + struct inexio *pinexio; + struct input_dev *input_dev; + int err; + + pinexio = kzalloc(sizeof(struct inexio), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!pinexio || !input_dev) { + err = -ENOMEM; + goto fail1; + } + + pinexio->serio = serio; + pinexio->dev = input_dev; + snprintf(pinexio->phys, sizeof(pinexio->phys), "%s/input0", serio->phys); + + input_dev->name = "iNexio Serial TouchScreen"; + input_dev->phys = pinexio->phys; + input_dev->id.bustype = BUS_RS232; + input_dev->id.vendor = SERIO_INEXIO; + input_dev->id.product = 0; + input_dev->id.version = 0x0001; + input_dev->dev.parent = &serio->dev; + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); + input_set_abs_params(pinexio->dev, ABS_X, INEXIO_MIN_XC, INEXIO_MAX_XC, 0, 0); + input_set_abs_params(pinexio->dev, ABS_Y, INEXIO_MIN_YC, INEXIO_MAX_YC, 0, 0); + + serio_set_drvdata(serio, pinexio); + + err = serio_open(serio, drv); + if (err) + goto fail2; + + err = input_register_device(pinexio->dev); + if (err) + goto fail3; + + return 0; + + fail3: serio_close(serio); + fail2: serio_set_drvdata(serio, NULL); + fail1: input_free_device(input_dev); + kfree(pinexio); + return err; +} + +/* + * The serio driver structure. + */ + +static struct serio_device_id inexio_serio_ids[] = { + { + .type = SERIO_RS232, + .proto = SERIO_INEXIO, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { 0 } +}; + +MODULE_DEVICE_TABLE(serio, inexio_serio_ids); + +static struct serio_driver inexio_drv = { + .driver = { + .name = "inexio", + }, + .description = DRIVER_DESC, + .id_table = inexio_serio_ids, + .interrupt = inexio_interrupt, + .connect = inexio_connect, + .disconnect = inexio_disconnect, +}; + +/* + * The functions for inserting/removing us as a module. + */ + +static int __init inexio_init(void) +{ + return serio_register_driver(&inexio_drv); +} + +static void __exit inexio_exit(void) +{ + serio_unregister_driver(&inexio_drv); +} + +module_init(inexio_init); +module_exit(inexio_exit); diff --git a/drivers/input/touchscreen/jornada720_ts.c b/drivers/input/touchscreen/jornada720_ts.c new file mode 100644 index 0000000..c8b7e8a --- /dev/null +++ b/drivers/input/touchscreen/jornada720_ts.c @@ -0,0 +1,186 @@ +/* + * drivers/input/touchscreen/jornada720_ts.c + * + * Copyright (C) 2007 Kristoffer Ericson <Kristoffer.Ericson@gmail.com> + * + * Copyright (C) 2006 Filip Zyzniewski <filip.zyzniewski@tefnet.pl> + * based on HP Jornada 56x touchscreen driver by Alex Lange <chicken@handhelds.org> + * + * 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. + * + * HP Jornada 710/720/729 Touchscreen Driver + */ + +#include <linux/platform_device.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/module.h> + +#include <mach/hardware.h> +#include <mach/jornada720.h> + +MODULE_AUTHOR("Kristoffer Ericson <kristoffer.ericson@gmail.com>"); +MODULE_DESCRIPTION("HP Jornada 710/720/728 touchscreen driver"); +MODULE_LICENSE("GPL v2"); + +struct jornada_ts { + struct input_dev *dev; + int x_data[4]; /* X sample values */ + int y_data[4]; /* Y sample values */ +}; + +static void jornada720_ts_collect_data(struct jornada_ts *jornada_ts) +{ + + /* 3 low word X samples */ + jornada_ts->x_data[0] = jornada_ssp_byte(TXDUMMY); + jornada_ts->x_data[1] = jornada_ssp_byte(TXDUMMY); + jornada_ts->x_data[2] = jornada_ssp_byte(TXDUMMY); + + /* 3 low word Y samples */ + jornada_ts->y_data[0] = jornada_ssp_byte(TXDUMMY); + jornada_ts->y_data[1] = jornada_ssp_byte(TXDUMMY); + jornada_ts->y_data[2] = jornada_ssp_byte(TXDUMMY); + + /* combined x samples bits */ + jornada_ts->x_data[3] = jornada_ssp_byte(TXDUMMY); + + /* combined y samples bits */ + jornada_ts->y_data[3] = jornada_ssp_byte(TXDUMMY); +} + +static int jornada720_ts_average(int coords[4]) +{ + int coord, high_bits = coords[3]; + + coord = coords[0] | ((high_bits & 0x03) << 8); + coord += coords[1] | ((high_bits & 0x0c) << 6); + coord += coords[2] | ((high_bits & 0x30) << 4); + + return coord / 3; +} + +static irqreturn_t jornada720_ts_interrupt(int irq, void *dev_id) +{ + struct platform_device *pdev = dev_id; + struct jornada_ts *jornada_ts = platform_get_drvdata(pdev); + struct input_dev *input = jornada_ts->dev; + int x, y; + + /* If GPIO_GPIO9 is set to high then report pen up */ + if (GPLR & GPIO_GPIO(9)) { + input_report_key(input, BTN_TOUCH, 0); + input_sync(input); + } else { + jornada_ssp_start(); + + /* proper reply to request is always TXDUMMY */ + if (jornada_ssp_inout(GETTOUCHSAMPLES) == TXDUMMY) { + jornada720_ts_collect_data(jornada_ts); + + x = jornada720_ts_average(jornada_ts->x_data); + y = jornada720_ts_average(jornada_ts->y_data); + + input_report_key(input, BTN_TOUCH, 1); + input_report_abs(input, ABS_X, x); + input_report_abs(input, ABS_Y, y); + input_sync(input); + } + + jornada_ssp_end(); + } + + return IRQ_HANDLED; +} + +static int __devinit jornada720_ts_probe(struct platform_device *pdev) +{ + struct jornada_ts *jornada_ts; + struct input_dev *input_dev; + int error; + + jornada_ts = kzalloc(sizeof(struct jornada_ts), GFP_KERNEL); + input_dev = input_allocate_device(); + + if (!jornada_ts || !input_dev) { + error = -ENOMEM; + goto fail1; + } + + platform_set_drvdata(pdev, jornada_ts); + + jornada_ts->dev = input_dev; + + input_dev->name = "HP Jornada 7xx Touchscreen"; + input_dev->phys = "jornadats/input0"; + input_dev->id.bustype = BUS_HOST; + input_dev->dev.parent = &pdev->dev; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); + input_set_abs_params(input_dev, ABS_X, 270, 3900, 0, 0); + input_set_abs_params(input_dev, ABS_Y, 180, 3700, 0, 0); + + error = request_irq(IRQ_GPIO9, + jornada720_ts_interrupt, + IRQF_DISABLED | IRQF_TRIGGER_RISING, + "HP7XX Touchscreen driver", pdev); + if (error) { + printk(KERN_INFO "HP7XX TS : Unable to acquire irq!\n"); + goto fail1; + } + + error = input_register_device(jornada_ts->dev); + if (error) + goto fail2; + + return 0; + + fail2: + free_irq(IRQ_GPIO9, pdev); + fail1: + platform_set_drvdata(pdev, NULL); + input_free_device(input_dev); + kfree(jornada_ts); + return error; +} + +static int __devexit jornada720_ts_remove(struct platform_device *pdev) +{ + struct jornada_ts *jornada_ts = platform_get_drvdata(pdev); + + free_irq(IRQ_GPIO9, pdev); + platform_set_drvdata(pdev, NULL); + input_unregister_device(jornada_ts->dev); + kfree(jornada_ts); + + return 0; +} + +/* work with hotplug and coldplug */ +MODULE_ALIAS("platform:jornada_ts"); + +static struct platform_driver jornada720_ts_driver = { + .probe = jornada720_ts_probe, + .remove = __devexit_p(jornada720_ts_remove), + .driver = { + .name = "jornada_ts", + .owner = THIS_MODULE, + }, +}; + +static int __init jornada720_ts_init(void) +{ + return platform_driver_register(&jornada720_ts_driver); +} + +static void __exit jornada720_ts_exit(void) +{ + platform_driver_unregister(&jornada720_ts_driver); +} + +module_init(jornada720_ts_init); +module_exit(jornada720_ts_exit); diff --git a/drivers/input/touchscreen/mainstone-wm97xx.c b/drivers/input/touchscreen/mainstone-wm97xx.c new file mode 100644 index 0000000..ba64875 --- /dev/null +++ b/drivers/input/touchscreen/mainstone-wm97xx.c @@ -0,0 +1,300 @@ +/* + * mainstone-wm97xx.c -- Mainstone Continuous Touch screen driver for + * Wolfson WM97xx AC97 Codecs. + * + * Copyright 2004, 2007 Wolfson Microelectronics PLC. + * Author: Liam Girdwood <lrg@slimlogic.co.uk> + * Parts Copyright : Ian Molton <spyro@f2s.com> + * Andrew Zabolotny <zap@homelink.ru> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * Notes: + * This is a wm97xx extended touch driver to capture touch + * data in a continuous manner on the Intel XScale archictecture + * + * Features: + * - codecs supported:- WM9705, WM9712, WM9713 + * - processors supported:- Intel XScale PXA25x, PXA26x, PXA27x + * + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/wm97xx.h> +#include <linux/io.h> +#include <mach/pxa-regs.h> + +#define VERSION "0.13" + +struct continuous { + u16 id; /* codec id */ + u8 code; /* continuous code */ + u8 reads; /* number of coord reads per read cycle */ + u32 speed; /* number of coords per second */ +}; + +#define WM_READS(sp) ((sp / HZ) + 1) + +static const struct continuous cinfo[] = { + {WM9705_ID2, 0, WM_READS(94), 94}, + {WM9705_ID2, 1, WM_READS(188), 188}, + {WM9705_ID2, 2, WM_READS(375), 375}, + {WM9705_ID2, 3, WM_READS(750), 750}, + {WM9712_ID2, 0, WM_READS(94), 94}, + {WM9712_ID2, 1, WM_READS(188), 188}, + {WM9712_ID2, 2, WM_READS(375), 375}, + {WM9712_ID2, 3, WM_READS(750), 750}, + {WM9713_ID2, 0, WM_READS(94), 94}, + {WM9713_ID2, 1, WM_READS(120), 120}, + {WM9713_ID2, 2, WM_READS(154), 154}, + {WM9713_ID2, 3, WM_READS(188), 188}, +}; + +/* continuous speed index */ +static int sp_idx; +static u16 last, tries; + +/* + * Pen sampling frequency (Hz) in continuous mode. + */ +static int cont_rate = 200; +module_param(cont_rate, int, 0); +MODULE_PARM_DESC(cont_rate, "Sampling rate in continuous mode (Hz)"); + +/* + * Pen down detection. + * + * This driver can either poll or use an interrupt to indicate a pen down + * event. If the irq request fails then it will fall back to polling mode. + */ +static int pen_int; +module_param(pen_int, int, 0); +MODULE_PARM_DESC(pen_int, "Pen down detection (1 = interrupt, 0 = polling)"); + +/* + * Pressure readback. + * + * Set to 1 to read back pen down pressure + */ +static int pressure; +module_param(pressure, int, 0); +MODULE_PARM_DESC(pressure, "Pressure readback (1 = pressure, 0 = no pressure)"); + +/* + * AC97 touch data slot. + * + * Touch screen readback data ac97 slot + */ +static int ac97_touch_slot = 5; +module_param(ac97_touch_slot, int, 0); +MODULE_PARM_DESC(ac97_touch_slot, "Touch screen data slot AC97 number"); + + +/* flush AC97 slot 5 FIFO on pxa machines */ +#ifdef CONFIG_PXA27x +static void wm97xx_acc_pen_up(struct wm97xx *wm) +{ + schedule_timeout_uninterruptible(1); + + while (MISR & (1 << 2)) + MODR; +} +#else +static void wm97xx_acc_pen_up(struct wm97xx *wm) +{ + int count = 16; + schedule_timeout_uninterruptible(1); + + while (count < 16) { + MODR; + count--; + } +} +#endif + +static int wm97xx_acc_pen_down(struct wm97xx *wm) +{ + u16 x, y, p = 0x100 | WM97XX_ADCSEL_PRES; + int reads = 0; + + /* When the AC97 queue has been drained we need to allow time + * to buffer up samples otherwise we end up spinning polling + * for samples. The controller can't have a suitably low + * threashold set to use the notifications it gives. + */ + schedule_timeout_uninterruptible(1); + + if (tries > 5) { + tries = 0; + return RC_PENUP; + } + + x = MODR; + if (x == last) { + tries++; + return RC_AGAIN; + } + last = x; + do { + if (reads) + x = MODR; + y = MODR; + if (pressure) + p = MODR; + + /* are samples valid */ + if ((x & WM97XX_ADCSRC_MASK) != WM97XX_ADCSEL_X || + (y & WM97XX_ADCSRC_MASK) != WM97XX_ADCSEL_Y || + (p & WM97XX_ADCSRC_MASK) != WM97XX_ADCSEL_PRES) + goto up; + + /* coordinate is good */ + tries = 0; + input_report_abs(wm->input_dev, ABS_X, x & 0xfff); + input_report_abs(wm->input_dev, ABS_Y, y & 0xfff); + input_report_abs(wm->input_dev, ABS_PRESSURE, p & 0xfff); + input_sync(wm->input_dev); + reads++; + } while (reads < cinfo[sp_idx].reads); +up: + return RC_PENDOWN | RC_AGAIN; +} + +static int wm97xx_acc_startup(struct wm97xx *wm) +{ + int idx = 0; + + /* check we have a codec */ + if (wm->ac97 == NULL) + return -ENODEV; + + /* Go you big red fire engine */ + for (idx = 0; idx < ARRAY_SIZE(cinfo); idx++) { + if (wm->id != cinfo[idx].id) + continue; + sp_idx = idx; + if (cont_rate <= cinfo[idx].speed) + break; + } + wm->acc_rate = cinfo[sp_idx].code; + wm->acc_slot = ac97_touch_slot; + dev_info(wm->dev, + "mainstone accelerated touchscreen driver, %d samples/sec\n", + cinfo[sp_idx].speed); + + /* codec specific irq config */ + if (pen_int) { + switch (wm->id) { + case WM9705_ID2: + wm->pen_irq = IRQ_GPIO(4); + set_irq_type(IRQ_GPIO(4), IRQ_TYPE_EDGE_BOTH); + break; + case WM9712_ID2: + case WM9713_ID2: + /* enable pen down interrupt */ + /* use PEN_DOWN GPIO 13 to assert IRQ on GPIO line 2 */ + wm->pen_irq = MAINSTONE_AC97_IRQ; + wm97xx_config_gpio(wm, WM97XX_GPIO_13, WM97XX_GPIO_IN, + WM97XX_GPIO_POL_HIGH, + WM97XX_GPIO_STICKY, + WM97XX_GPIO_WAKE); + wm97xx_config_gpio(wm, WM97XX_GPIO_2, WM97XX_GPIO_OUT, + WM97XX_GPIO_POL_HIGH, + WM97XX_GPIO_NOTSTICKY, + WM97XX_GPIO_NOWAKE); + break; + default: + dev_err(wm->dev, + "pen down irq not supported on this device\n"); + pen_int = 0; + break; + } + } + + return 0; +} + +static void wm97xx_acc_shutdown(struct wm97xx *wm) +{ + /* codec specific deconfig */ + if (pen_int) { + switch (wm->id & 0xffff) { + case WM9705_ID2: + wm->pen_irq = 0; + break; + case WM9712_ID2: + case WM9713_ID2: + /* disable interrupt */ + wm->pen_irq = 0; + break; + } + } +} + +static void wm97xx_irq_enable(struct wm97xx *wm, int enable) +{ + if (enable) + enable_irq(wm->pen_irq); + else + disable_irq(wm->pen_irq); +} + +static struct wm97xx_mach_ops mainstone_mach_ops = { + .acc_enabled = 1, + .acc_pen_up = wm97xx_acc_pen_up, + .acc_pen_down = wm97xx_acc_pen_down, + .acc_startup = wm97xx_acc_startup, + .acc_shutdown = wm97xx_acc_shutdown, + .irq_enable = wm97xx_irq_enable, + .irq_gpio = WM97XX_GPIO_2, +}; + +static int mainstone_wm97xx_probe(struct platform_device *pdev) +{ + struct wm97xx *wm = platform_get_drvdata(pdev); + + return wm97xx_register_mach_ops(wm, &mainstone_mach_ops); +} + +static int mainstone_wm97xx_remove(struct platform_device *pdev) +{ + struct wm97xx *wm = platform_get_drvdata(pdev); + + wm97xx_unregister_mach_ops(wm); + return 0; +} + +static struct platform_driver mainstone_wm97xx_driver = { + .probe = mainstone_wm97xx_probe, + .remove = mainstone_wm97xx_remove, + .driver = { + .name = "wm97xx-touch", + }, +}; + +static int __init mainstone_wm97xx_init(void) +{ + return platform_driver_register(&mainstone_wm97xx_driver); +} + +static void __exit mainstone_wm97xx_exit(void) +{ + platform_driver_unregister(&mainstone_wm97xx_driver); +} + +module_init(mainstone_wm97xx_init); +module_exit(mainstone_wm97xx_exit); + +/* Module information */ +MODULE_AUTHOR("Liam Girdwood <lrg@slimlogic.co.uk>"); +MODULE_DESCRIPTION("wm97xx continuous touch driver for mainstone"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/touchscreen/migor_ts.c b/drivers/input/touchscreen/migor_ts.c new file mode 100644 index 0000000..504ca11 --- /dev/null +++ b/drivers/input/touchscreen/migor_ts.c @@ -0,0 +1,257 @@ +/* + * Touch Screen driver for Renesas MIGO-R Platform + * + * Copyright (c) 2008 Magnus Damm + * Copyright (c) 2007 Ujjwal Pande <ujjwal@kenati.com>, + * Kenati Technologies Pvt Ltd. + * + * This file 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 file 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 library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <asm/io.h> +#include <linux/i2c.h> +#include <linux/timer.h> + +#define EVENT_PENDOWN 1 +#define EVENT_REPEAT 2 +#define EVENT_PENUP 3 + +struct migor_ts_priv { + struct i2c_client *client; + struct input_dev *input; + struct delayed_work work; + int irq; +}; + +static const u_int8_t migor_ts_ena_seq[17] = { 0x33, 0x22, 0x11, + 0x01, 0x06, 0x07, }; +static const u_int8_t migor_ts_dis_seq[17] = { }; + +static void migor_ts_poscheck(struct work_struct *work) +{ + struct migor_ts_priv *priv = container_of(work, + struct migor_ts_priv, + work.work); + unsigned short xpos, ypos; + unsigned char event; + u_int8_t buf[16]; + + memset(buf, 0, sizeof(buf)); + + /* Set Index 0 */ + buf[0] = 0; + if (i2c_master_send(priv->client, buf, 1) != 1) { + dev_err(&priv->client->dev, "Unable to write i2c index\n"); + goto out; + } + + /* Now do Page Read */ + if (i2c_master_recv(priv->client, buf, sizeof(buf)) != sizeof(buf)) { + dev_err(&priv->client->dev, "Unable to read i2c page\n"); + goto out; + } + + ypos = ((buf[9] & 0x03) << 8 | buf[8]); + xpos = ((buf[11] & 0x03) << 8 | buf[10]); + event = buf[12]; + + if (event == EVENT_PENDOWN || event == EVENT_REPEAT) { + input_report_key(priv->input, BTN_TOUCH, 1); + input_report_abs(priv->input, ABS_X, ypos); /*X-Y swap*/ + input_report_abs(priv->input, ABS_Y, xpos); + input_sync(priv->input); + } else if (event == EVENT_PENUP) { + input_report_key(priv->input, BTN_TOUCH, 0); + input_sync(priv->input); + } + out: + enable_irq(priv->irq); +} + +static irqreturn_t migor_ts_isr(int irq, void *dev_id) +{ + struct migor_ts_priv *priv = dev_id; + + /* the touch screen controller chip is hooked up to the cpu + * using i2c and a single interrupt line. the interrupt line + * is pulled low whenever someone taps the screen. to deassert + * the interrupt line we need to acknowledge the interrupt by + * communicating with the controller over the slow i2c bus. + * + * we can't acknowledge from interrupt context since the i2c + * bus controller may sleep, so we just disable the interrupt + * here and handle the acknowledge using delayed work. + */ + + disable_irq_nosync(irq); + schedule_delayed_work(&priv->work, HZ / 20); + + return IRQ_HANDLED; +} + + +static int migor_ts_open(struct input_dev *dev) +{ + struct migor_ts_priv *priv = input_get_drvdata(dev); + struct i2c_client *client = priv->client; + int count; + + /* enable controller */ + count = i2c_master_send(client, migor_ts_ena_seq, + sizeof(migor_ts_ena_seq)); + if (count != sizeof(migor_ts_ena_seq)) { + dev_err(&client->dev, "Unable to enable touchscreen.\n"); + return -ENXIO; + } + + return 0; +} + +static void migor_ts_close(struct input_dev *dev) +{ + struct migor_ts_priv *priv = input_get_drvdata(dev); + struct i2c_client *client = priv->client; + + disable_irq(priv->irq); + + /* cancel pending work and wait for migor_ts_poscheck() to finish */ + if (cancel_delayed_work_sync(&priv->work)) { + /* + * if migor_ts_poscheck was canceled we need to enable IRQ + * here to balance disable done in migor_ts_isr. + */ + enable_irq(priv->irq); + } + + /* disable controller */ + i2c_master_send(client, migor_ts_dis_seq, sizeof(migor_ts_dis_seq)); + + enable_irq(priv->irq); +} + +static int migor_ts_probe(struct i2c_client *client, + const struct i2c_device_id *idp) +{ + struct migor_ts_priv *priv; + struct input_dev *input; + int error; + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) { + dev_err(&client->dev, "failed to allocate driver data\n"); + error = -ENOMEM; + goto err0; + } + + dev_set_drvdata(&client->dev, priv); + + input = input_allocate_device(); + if (!input) { + dev_err(&client->dev, "Failed to allocate input device.\n"); + error = -ENOMEM; + goto err1; + } + + input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + input->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); + + input_set_abs_params(input, ABS_X, 95, 955, 0, 0); + input_set_abs_params(input, ABS_Y, 85, 935, 0, 0); + + input->name = client->name; + input->id.bustype = BUS_I2C; + input->dev.parent = &client->dev; + + input->open = migor_ts_open; + input->close = migor_ts_close; + + input_set_drvdata(input, priv); + + priv->client = client; + priv->input = input; + INIT_DELAYED_WORK(&priv->work, migor_ts_poscheck); + priv->irq = client->irq; + + error = input_register_device(input); + if (error) + goto err1; + + error = request_irq(priv->irq, migor_ts_isr, IRQF_TRIGGER_LOW, + client->name, priv); + if (error) { + dev_err(&client->dev, "Unable to request touchscreen IRQ.\n"); + goto err2; + } + + return 0; + + err2: + input_unregister_device(input); + input = NULL; /* so we dont try to free it below */ + err1: + input_free_device(input); + kfree(priv); + err0: + dev_set_drvdata(&client->dev, NULL); + return error; +} + +static int migor_ts_remove(struct i2c_client *client) +{ + struct migor_ts_priv *priv = dev_get_drvdata(&client->dev); + + free_irq(priv->irq, priv); + input_unregister_device(priv->input); + kfree(priv); + + dev_set_drvdata(&client->dev, NULL); + + return 0; +} + +static const struct i2c_device_id migor_ts_id[] = { + { "migor_ts", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, migor_ts); + +static struct i2c_driver migor_ts_driver = { + .driver = { + .name = "migor_ts", + }, + .probe = migor_ts_probe, + .remove = migor_ts_remove, + .id_table = migor_ts_id, +}; + +static int __init migor_ts_init(void) +{ + return i2c_add_driver(&migor_ts_driver); +} + +static void __exit migor_ts_exit(void) +{ + i2c_del_driver(&migor_ts_driver); +} + +MODULE_DESCRIPTION("MigoR Touchscreen driver"); +MODULE_AUTHOR("Magnus Damm <damm@opensource.se>"); +MODULE_LICENSE("GPL"); + +module_init(migor_ts_init); +module_exit(migor_ts_exit); diff --git a/drivers/input/touchscreen/mk712.c b/drivers/input/touchscreen/mk712.c new file mode 100644 index 0000000..efd3aeb --- /dev/null +++ b/drivers/input/touchscreen/mk712.c @@ -0,0 +1,219 @@ +/* + * 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/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 struct input_dev *mk712_dev; +static DEFINE_SPINLOCK(mk712_lock); + +static irqreturn_t mk712_interrupt(int irq, void *dev_id) +{ + unsigned char status; + static int debounce = 1; + static unsigned short last_x; + static unsigned short last_y; + + spin_lock(&mk712_lock); + + 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); + + 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); + + outb(0, mk712_io + MK712_CONTROL); + + spin_unlock_irqrestore(&mk712_lock, flags); +} + +static int __init mk712_init(void) +{ + int err; + + 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"); + err = -ENODEV; + goto fail1; + } + + mk712_dev = input_allocate_device(); + if (!mk712_dev) { + printk(KERN_ERR "mk712: not enough memory\n"); + err = -ENOMEM; + goto fail1; + } + + mk712_dev->name = "ICS MicroClock MK712 TouchScreen"; + mk712_dev->phys = "isa0260/input0"; + mk712_dev->id.bustype = BUS_ISA; + mk712_dev->id.vendor = 0x0005; + mk712_dev->id.product = 0x0001; + mk712_dev->id.version = 0x0100; + + mk712_dev->open = mk712_open; + mk712_dev->close = mk712_close; + + mk712_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + mk712_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); + input_set_abs_params(mk712_dev, ABS_X, 0, 0xfff, 88, 0); + input_set_abs_params(mk712_dev, ABS_Y, 0, 0xfff, 88, 0); + + if (request_irq(mk712_irq, mk712_interrupt, 0, "mk712", mk712_dev)) { + printk(KERN_WARNING "mk712: unable to get IRQ\n"); + err = -EBUSY; + goto fail1; + } + + err = input_register_device(mk712_dev); + if (err) + goto fail2; + + return 0; + + fail2: free_irq(mk712_irq, mk712_dev); + fail1: input_free_device(mk712_dev); + release_region(mk712_io, 8); + return err; +} + +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 0000000..9077228 --- /dev/null +++ b/drivers/input/touchscreen/mtouch.c @@ -0,0 +1,220 @@ +/* + * 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]) + +/* + * 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 input_dev *dev = mtouch->dev; + + if (MTOUCH_FORMAT_TABLET_LENGTH == ++mtouch->idx) { + 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) +{ + 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 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); + else if (MTOUCH_RESPONSE_BEGIN_BYTE == mtouch->data[0]) + mtouch_process_response(mtouch); + 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_get_device(mtouch->dev); + input_unregister_device(mtouch->dev); + serio_close(serio); + serio_set_drvdata(serio, NULL); + input_put_device(mtouch->dev); + 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; + struct input_dev *input_dev; + int err; + + mtouch = kzalloc(sizeof(struct mtouch), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!mtouch || !input_dev) { + err = -ENOMEM; + goto fail1; + } + + mtouch->serio = serio; + mtouch->dev = input_dev; + snprintf(mtouch->phys, sizeof(mtouch->phys), "%s/input0", serio->phys); + + input_dev->name = "MicroTouch Serial TouchScreen"; + input_dev->phys = mtouch->phys; + input_dev->id.bustype = BUS_RS232; + input_dev->id.vendor = SERIO_MICROTOUCH; + input_dev->id.product = 0; + input_dev->id.version = 0x0100; + input_dev->dev.parent = &serio->dev; + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(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); + + serio_set_drvdata(serio, mtouch); + + err = serio_open(serio, drv); + if (err) + goto fail2; + + err = input_register_device(mtouch->dev); + if (err) + goto fail3; + + return 0; + + fail3: serio_close(serio); + fail2: serio_set_drvdata(serio, NULL); + fail1: input_free_device(input_dev); + kfree(mtouch); + return err; +} + +/* + * 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) +{ + return serio_register_driver(&mtouch_drv); +} + +static void __exit mtouch_exit(void) +{ + serio_unregister_driver(&mtouch_drv); +} + +module_init(mtouch_init); +module_exit(mtouch_exit); diff --git a/drivers/input/touchscreen/penmount.c b/drivers/input/touchscreen/penmount.c new file mode 100644 index 0000000..c7f9ceb --- /dev/null +++ b/drivers/input/touchscreen/penmount.c @@ -0,0 +1,182 @@ +/* + * Penmount serial touchscreen driver + * + * Copyright (c) 2006 Rick Koch <n1gp@hotmail.com> + * + * Based on ELO driver (drivers/input/touchscreen/elo.c) + * 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. + */ + +#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 "Penmount serial touchscreen driver" + +MODULE_AUTHOR("Rick Koch <n1gp@hotmail.com>"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +/* + * Definitions & global arrays. + */ + +#define PM_MAX_LENGTH 5 + +/* + * Per-touchscreen data. + */ + +struct pm { + struct input_dev *dev; + struct serio *serio; + int idx; + unsigned char data[PM_MAX_LENGTH]; + char phys[32]; +}; + +static irqreturn_t pm_interrupt(struct serio *serio, + unsigned char data, unsigned int flags) +{ + struct pm *pm = serio_get_drvdata(serio); + struct input_dev *dev = pm->dev; + + pm->data[pm->idx] = data; + + if (pm->data[0] & 0x80) { + if (PM_MAX_LENGTH == ++pm->idx) { + input_report_abs(dev, ABS_X, pm->data[2] * 128 + pm->data[1]); + input_report_abs(dev, ABS_Y, pm->data[4] * 128 + pm->data[3]); + input_report_key(dev, BTN_TOUCH, !!(pm->data[0] & 0x40)); + input_sync(dev); + pm->idx = 0; + } + } + + return IRQ_HANDLED; +} + +/* + * pm_disconnect() is the opposite of pm_connect() + */ + +static void pm_disconnect(struct serio *serio) +{ + struct pm *pm = serio_get_drvdata(serio); + + input_get_device(pm->dev); + input_unregister_device(pm->dev); + serio_close(serio); + serio_set_drvdata(serio, NULL); + input_put_device(pm->dev); + kfree(pm); +} + +/* + * pm_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 pm_connect(struct serio *serio, struct serio_driver *drv) +{ + struct pm *pm; + struct input_dev *input_dev; + int err; + + pm = kzalloc(sizeof(struct pm), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!pm || !input_dev) { + err = -ENOMEM; + goto fail1; + } + + pm->serio = serio; + pm->dev = input_dev; + snprintf(pm->phys, sizeof(pm->phys), "%s/input0", serio->phys); + + input_dev->name = "Penmount Serial TouchScreen"; + input_dev->phys = pm->phys; + input_dev->id.bustype = BUS_RS232; + input_dev->id.vendor = SERIO_PENMOUNT; + input_dev->id.product = 0; + input_dev->id.version = 0x0100; + input_dev->dev.parent = &serio->dev; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); + input_set_abs_params(pm->dev, ABS_X, 0, 0x3ff, 0, 0); + input_set_abs_params(pm->dev, ABS_Y, 0, 0x3ff, 0, 0); + + serio_set_drvdata(serio, pm); + + err = serio_open(serio, drv); + if (err) + goto fail2; + + err = input_register_device(pm->dev); + if (err) + goto fail3; + + return 0; + + fail3: serio_close(serio); + fail2: serio_set_drvdata(serio, NULL); + fail1: input_free_device(input_dev); + kfree(pm); + return err; +} + +/* + * The serio driver structure. + */ + +static struct serio_device_id pm_serio_ids[] = { + { + .type = SERIO_RS232, + .proto = SERIO_PENMOUNT, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { 0 } +}; + +MODULE_DEVICE_TABLE(serio, pm_serio_ids); + +static struct serio_driver pm_drv = { + .driver = { + .name = "penmountlpc", + }, + .description = DRIVER_DESC, + .id_table = pm_serio_ids, + .interrupt = pm_interrupt, + .connect = pm_connect, + .disconnect = pm_disconnect, +}; + +/* + * The functions for inserting/removing us as a module. + */ + +static int __init pm_init(void) +{ + return serio_register_driver(&pm_drv); +} + +static void __exit pm_exit(void) +{ + serio_unregister_driver(&pm_drv); +} + +module_init(pm_init); +module_exit(pm_exit); diff --git a/drivers/input/touchscreen/touchit213.c b/drivers/input/touchscreen/touchit213.c new file mode 100644 index 0000000..d1297ba --- /dev/null +++ b/drivers/input/touchscreen/touchit213.c @@ -0,0 +1,234 @@ +/* + * Sahara TouchIT-213 serial touchscreen driver + * + * Copyright (c) 2007-2008 Claudio Nieder <private@claudio.ch> + * + * Based on Touchright driver (drivers/input/touchscreen/touchright.c) + * Copyright (c) 2006 Rick Koch <n1gp@hotmail.com> + * Copyright (c) 2004 Vojtech Pavlik + * and Dan Streetman <ddstreet@ieee.org> + */ + +/* + * 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/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 "Sahara TouchIT-213 serial touchscreen driver" + +MODULE_AUTHOR("Claudio Nieder <private@claudio.ch>"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +/* + * Definitions & global arrays. + */ + +/* + * Data is received through COM1 at 9600bit/s,8bit,no parity in packets + * of 5 byte each. + * + * +--------+ +--------+ +--------+ +--------+ +--------+ + * |1000000p| |0xxxxxxx| |0xxxxxxx| |0yyyyyyy| |0yyyyyyy| + * +--------+ +--------+ +--------+ +--------+ +--------+ + * MSB LSB MSB LSB + * + * The value of p is 1 as long as the screen is touched and 0 when + * reporting the location where touching stopped, e.g. where the pen was + * lifted from the screen. + * + * When holding the screen in landscape mode as the BIOS text output is + * presented, x is the horizontal axis with values growing from left to + * right and y is the vertical axis with values growing from top to + * bottom. + * + * When holding the screen in portrait mode with the Sahara logo in its + * correct position, x ist the vertical axis with values growing from + * top to bottom and y is the horizontal axis with values growing from + * right to left. + */ + +#define T213_FORMAT_TOUCH_BIT 0x01 +#define T213_FORMAT_STATUS_BYTE 0x80 +#define T213_FORMAT_STATUS_MASK ~T213_FORMAT_TOUCH_BIT + +/* + * On my Sahara Touch-IT 213 I have observed x values from 0 to 0x7f0 + * and y values from 0x1d to 0x7e9, so the actual measurement is + * probably done with an 11 bit precision. + */ +#define T213_MIN_XC 0 +#define T213_MAX_XC 0x07ff +#define T213_MIN_YC 0 +#define T213_MAX_YC 0x07ff + +/* + * Per-touchscreen data. + */ + +struct touchit213 { + struct input_dev *dev; + struct serio *serio; + int idx; + unsigned char csum; + unsigned char data[5]; + char phys[32]; +}; + +static irqreturn_t touchit213_interrupt(struct serio *serio, + unsigned char data, unsigned int flags) +{ + struct touchit213 *touchit213 = serio_get_drvdata(serio); + struct input_dev *dev = touchit213->dev; + + touchit213->data[touchit213->idx] = data; + + switch (touchit213->idx++) { + case 0: + if ((touchit213->data[0] & T213_FORMAT_STATUS_MASK) != + T213_FORMAT_STATUS_BYTE) { + pr_debug("unsynchronized data: 0x%02x\n", data); + touchit213->idx = 0; + } + break; + + case 4: + touchit213->idx = 0; + input_report_abs(dev, ABS_X, + (touchit213->data[1] << 7) | touchit213->data[2]); + input_report_abs(dev, ABS_Y, + (touchit213->data[3] << 7) | touchit213->data[4]); + input_report_key(dev, BTN_TOUCH, + touchit213->data[0] & T213_FORMAT_TOUCH_BIT); + input_sync(dev); + break; + } + + return IRQ_HANDLED; +} + +/* + * touchit213_disconnect() is the opposite of touchit213_connect() + */ + +static void touchit213_disconnect(struct serio *serio) +{ + struct touchit213 *touchit213 = serio_get_drvdata(serio); + + input_get_device(touchit213->dev); + input_unregister_device(touchit213->dev); + serio_close(serio); + serio_set_drvdata(serio, NULL); + input_put_device(touchit213->dev); + kfree(touchit213); +} + +/* + * touchit213_connect() is the routine that is called when someone adds a + * new serio device that supports the Touchright protocol and registers it as + * an input device. + */ + +static int touchit213_connect(struct serio *serio, struct serio_driver *drv) +{ + struct touchit213 *touchit213; + struct input_dev *input_dev; + int err; + + touchit213 = kzalloc(sizeof(struct touchit213), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!touchit213 || !input_dev) { + err = -ENOMEM; + goto fail1; + } + + touchit213->serio = serio; + touchit213->dev = input_dev; + snprintf(touchit213->phys, sizeof(touchit213->phys), + "%s/input0", serio->phys); + + input_dev->name = "Sahara Touch-iT213 Serial TouchScreen"; + input_dev->phys = touchit213->phys; + input_dev->id.bustype = BUS_RS232; + input_dev->id.vendor = SERIO_TOUCHIT213; + input_dev->id.product = 0; + input_dev->id.version = 0x0100; + input_dev->dev.parent = &serio->dev; + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); + input_set_abs_params(touchit213->dev, ABS_X, + T213_MIN_XC, T213_MAX_XC, 0, 0); + input_set_abs_params(touchit213->dev, ABS_Y, + T213_MIN_YC, T213_MAX_YC, 0, 0); + + serio_set_drvdata(serio, touchit213); + + err = serio_open(serio, drv); + if (err) + goto fail2; + + err = input_register_device(touchit213->dev); + if (err) + goto fail3; + + return 0; + + fail3: serio_close(serio); + fail2: serio_set_drvdata(serio, NULL); + fail1: input_free_device(input_dev); + kfree(touchit213); + return err; +} + +/* + * The serio driver structure. + */ + +static struct serio_device_id touchit213_serio_ids[] = { + { + .type = SERIO_RS232, + .proto = SERIO_TOUCHIT213, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { 0 } +}; + +MODULE_DEVICE_TABLE(serio, touchit213_serio_ids); + +static struct serio_driver touchit213_drv = { + .driver = { + .name = "touchit213", + }, + .description = DRIVER_DESC, + .id_table = touchit213_serio_ids, + .interrupt = touchit213_interrupt, + .connect = touchit213_connect, + .disconnect = touchit213_disconnect, +}; + +/* + * The functions for inserting/removing us as a module. + */ + +static int __init touchit213_init(void) +{ + return serio_register_driver(&touchit213_drv); +} + +static void __exit touchit213_exit(void) +{ + serio_unregister_driver(&touchit213_drv); +} + +module_init(touchit213_init); +module_exit(touchit213_exit); diff --git a/drivers/input/touchscreen/touchright.c b/drivers/input/touchscreen/touchright.c new file mode 100644 index 0000000..3a5c142 --- /dev/null +++ b/drivers/input/touchscreen/touchright.c @@ -0,0 +1,194 @@ +/* + * Touchright serial touchscreen driver + * + * Copyright (c) 2006 Rick Koch <n1gp@hotmail.com> + * + * Based on MicroTouch driver (drivers/input/touchscreen/mtouch.c) + * Copyright (c) 2004 Vojtech Pavlik + * and Dan Streetman <ddstreet@ieee.org> + */ + +/* + * 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/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 "Touchright serial touchscreen driver" + +MODULE_AUTHOR("Rick Koch <n1gp@hotmail.com>"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +/* + * Definitions & global arrays. + */ + +#define TR_FORMAT_TOUCH_BIT 0x01 +#define TR_FORMAT_STATUS_BYTE 0x40 +#define TR_FORMAT_STATUS_MASK ~TR_FORMAT_TOUCH_BIT + +#define TR_LENGTH 5 + +#define TR_MIN_XC 0 +#define TR_MAX_XC 0x1ff +#define TR_MIN_YC 0 +#define TR_MAX_YC 0x1ff + +/* + * Per-touchscreen data. + */ + +struct tr { + struct input_dev *dev; + struct serio *serio; + int idx; + unsigned char data[TR_LENGTH]; + char phys[32]; +}; + +static irqreturn_t tr_interrupt(struct serio *serio, + unsigned char data, unsigned int flags) +{ + struct tr *tr = serio_get_drvdata(serio); + struct input_dev *dev = tr->dev; + + tr->data[tr->idx] = data; + + if ((tr->data[0] & TR_FORMAT_STATUS_MASK) == TR_FORMAT_STATUS_BYTE) { + if (++tr->idx == TR_LENGTH) { + input_report_abs(dev, ABS_X, + (tr->data[1] << 5) | (tr->data[2] >> 1)); + input_report_abs(dev, ABS_Y, + (tr->data[3] << 5) | (tr->data[4] >> 1)); + input_report_key(dev, BTN_TOUCH, + tr->data[0] & TR_FORMAT_TOUCH_BIT); + input_sync(dev); + tr->idx = 0; + } + } + + return IRQ_HANDLED; +} + +/* + * tr_disconnect() is the opposite of tr_connect() + */ + +static void tr_disconnect(struct serio *serio) +{ + struct tr *tr = serio_get_drvdata(serio); + + input_get_device(tr->dev); + input_unregister_device(tr->dev); + serio_close(serio); + serio_set_drvdata(serio, NULL); + input_put_device(tr->dev); + kfree(tr); +} + +/* + * tr_connect() is the routine that is called when someone adds a + * new serio device that supports the Touchright protocol and registers it as + * an input device. + */ + +static int tr_connect(struct serio *serio, struct serio_driver *drv) +{ + struct tr *tr; + struct input_dev *input_dev; + int err; + + tr = kzalloc(sizeof(struct tr), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!tr || !input_dev) { + err = -ENOMEM; + goto fail1; + } + + tr->serio = serio; + tr->dev = input_dev; + snprintf(tr->phys, sizeof(tr->phys), "%s/input0", serio->phys); + + input_dev->name = "Touchright Serial TouchScreen"; + input_dev->phys = tr->phys; + input_dev->id.bustype = BUS_RS232; + input_dev->id.vendor = SERIO_TOUCHRIGHT; + input_dev->id.product = 0; + input_dev->id.version = 0x0100; + input_dev->dev.parent = &serio->dev; + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); + input_set_abs_params(tr->dev, ABS_X, TR_MIN_XC, TR_MAX_XC, 0, 0); + input_set_abs_params(tr->dev, ABS_Y, TR_MIN_YC, TR_MAX_YC, 0, 0); + + serio_set_drvdata(serio, tr); + + err = serio_open(serio, drv); + if (err) + goto fail2; + + err = input_register_device(tr->dev); + if (err) + goto fail3; + + return 0; + + fail3: serio_close(serio); + fail2: serio_set_drvdata(serio, NULL); + fail1: input_free_device(input_dev); + kfree(tr); + return err; +} + +/* + * The serio driver structure. + */ + +static struct serio_device_id tr_serio_ids[] = { + { + .type = SERIO_RS232, + .proto = SERIO_TOUCHRIGHT, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { 0 } +}; + +MODULE_DEVICE_TABLE(serio, tr_serio_ids); + +static struct serio_driver tr_drv = { + .driver = { + .name = "touchright", + }, + .description = DRIVER_DESC, + .id_table = tr_serio_ids, + .interrupt = tr_interrupt, + .connect = tr_connect, + .disconnect = tr_disconnect, +}; + +/* + * The functions for inserting/removing us as a module. + */ + +static int __init tr_init(void) +{ + return serio_register_driver(&tr_drv); +} + +static void __exit tr_exit(void) +{ + serio_unregister_driver(&tr_drv); +} + +module_init(tr_init); +module_exit(tr_exit); diff --git a/drivers/input/touchscreen/touchwin.c b/drivers/input/touchscreen/touchwin.c new file mode 100644 index 0000000..763a656 --- /dev/null +++ b/drivers/input/touchscreen/touchwin.c @@ -0,0 +1,201 @@ +/* + * Touchwindow serial touchscreen driver + * + * Copyright (c) 2006 Rick Koch <n1gp@hotmail.com> + * + * Based on MicroTouch driver (drivers/input/touchscreen/mtouch.c) + * Copyright (c) 2004 Vojtech Pavlik + * and Dan Streetman <ddstreet@ieee.org> + */ + +/* + * 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 Rick Koch: + * The Touchwindow I used is made by Edmark Corp. and + * constantly outputs a stream of 0's unless it is touched. + * It then outputs 3 bytes: X, Y, and a copy of Y. + */ + +#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 "Touchwindow serial touchscreen driver" + +MODULE_AUTHOR("Rick Koch <n1gp@hotmail.com>"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +/* + * Definitions & global arrays. + */ + +#define TW_LENGTH 3 + +#define TW_MIN_XC 0 +#define TW_MAX_XC 0xff +#define TW_MIN_YC 0 +#define TW_MAX_YC 0xff + +/* + * Per-touchscreen data. + */ + +struct tw { + struct input_dev *dev; + struct serio *serio; + int idx; + int touched; + unsigned char data[TW_LENGTH]; + char phys[32]; +}; + +static irqreturn_t tw_interrupt(struct serio *serio, + unsigned char data, unsigned int flags) +{ + struct tw *tw = serio_get_drvdata(serio); + struct input_dev *dev = tw->dev; + + if (data) { /* touch */ + tw->touched = 1; + tw->data[tw->idx++] = data; + /* verify length and that the two Y's are the same */ + if (tw->idx == TW_LENGTH && tw->data[1] == tw->data[2]) { + input_report_abs(dev, ABS_X, tw->data[0]); + input_report_abs(dev, ABS_Y, tw->data[1]); + input_report_key(dev, BTN_TOUCH, 1); + input_sync(dev); + tw->idx = 0; + } + } else if (tw->touched) { /* untouch */ + input_report_key(dev, BTN_TOUCH, 0); + input_sync(dev); + tw->idx = 0; + tw->touched = 0; + } + + return IRQ_HANDLED; +} + +/* + * tw_disconnect() is the opposite of tw_connect() + */ + +static void tw_disconnect(struct serio *serio) +{ + struct tw *tw = serio_get_drvdata(serio); + + input_get_device(tw->dev); + input_unregister_device(tw->dev); + serio_close(serio); + serio_set_drvdata(serio, NULL); + input_put_device(tw->dev); + kfree(tw); +} + +/* + * tw_connect() is the routine that is called when someone adds a + * new serio device that supports the Touchwin protocol and registers it as + * an input device. + */ + +static int tw_connect(struct serio *serio, struct serio_driver *drv) +{ + struct tw *tw; + struct input_dev *input_dev; + int err; + + tw = kzalloc(sizeof(struct tw), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!tw || !input_dev) { + err = -ENOMEM; + goto fail1; + } + + tw->serio = serio; + tw->dev = input_dev; + snprintf(tw->phys, sizeof(tw->phys), "%s/input0", serio->phys); + + input_dev->name = "Touchwindow Serial TouchScreen"; + input_dev->phys = tw->phys; + input_dev->id.bustype = BUS_RS232; + input_dev->id.vendor = SERIO_TOUCHWIN; + input_dev->id.product = 0; + input_dev->id.version = 0x0100; + input_dev->dev.parent = &serio->dev; + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); + input_set_abs_params(tw->dev, ABS_X, TW_MIN_XC, TW_MAX_XC, 0, 0); + input_set_abs_params(tw->dev, ABS_Y, TW_MIN_YC, TW_MAX_YC, 0, 0); + + serio_set_drvdata(serio, tw); + + err = serio_open(serio, drv); + if (err) + goto fail2; + + err = input_register_device(tw->dev); + if (err) + goto fail3; + + return 0; + + fail3: serio_close(serio); + fail2: serio_set_drvdata(serio, NULL); + fail1: input_free_device(input_dev); + kfree(tw); + return err; +} + +/* + * The serio driver structure. + */ + +static struct serio_device_id tw_serio_ids[] = { + { + .type = SERIO_RS232, + .proto = SERIO_TOUCHWIN, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { 0 } +}; + +MODULE_DEVICE_TABLE(serio, tw_serio_ids); + +static struct serio_driver tw_drv = { + .driver = { + .name = "touchwin", + }, + .description = DRIVER_DESC, + .id_table = tw_serio_ids, + .interrupt = tw_interrupt, + .connect = tw_connect, + .disconnect = tw_disconnect, +}; + +/* + * The functions for inserting/removing us as a module. + */ + +static int __init tw_init(void) +{ + return serio_register_driver(&tw_drv); +} + +static void __exit tw_exit(void) +{ + serio_unregister_driver(&tw_drv); +} + +module_init(tw_init); +module_exit(tw_exit); diff --git a/drivers/input/touchscreen/ucb1400_ts.c b/drivers/input/touchscreen/ucb1400_ts.c new file mode 100644 index 0000000..5498662 --- /dev/null +++ b/drivers/input/touchscreen/ucb1400_ts.c @@ -0,0 +1,471 @@ +/* + * Philips UCB1400 touchscreen driver + * + * Author: Nicolas Pitre + * Created: September 25, 2006 + * Copyright: MontaVista Software, Inc. + * + * Spliting done by: Marek Vasut <marek.vasut@gmail.com> + * If something doesnt work and it worked before spliting, e-mail me, + * dont bother Nicolas please ;-) + * + * 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 code is heavily based on ucb1x00-*.c copyrighted by Russell King + * covering the UCB1100, UCB1200 and UCB1300.. Support for the UCB1400 has + * been made separate from ucb1x00-core/ucb1x00-ts on Russell's request. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/completion.h> +#include <linux/delay.h> +#include <linux/input.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/suspend.h> +#include <linux/slab.h> +#include <linux/kthread.h> +#include <linux/freezer.h> +#include <linux/ucb1400.h> + +static int adcsync; +static int ts_delay = 55; /* us */ +static int ts_delay_pressure; /* us */ + +/* Switch to interrupt mode. */ +static inline void ucb1400_ts_mode_int(struct snd_ac97 *ac97) +{ + ucb1400_reg_write(ac97, UCB_TS_CR, + UCB_TS_CR_TSMX_POW | UCB_TS_CR_TSPX_POW | + UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_GND | + UCB_TS_CR_MODE_INT); +} + +/* + * Switch to pressure mode, and read pressure. We don't need to wait + * here, since both plates are being driven. + */ +static inline unsigned int ucb1400_ts_read_pressure(struct ucb1400_ts *ucb) +{ + ucb1400_reg_write(ucb->ac97, UCB_TS_CR, + UCB_TS_CR_TSMX_POW | UCB_TS_CR_TSPX_POW | + UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_GND | + UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA); + udelay(ts_delay_pressure); + return ucb1400_adc_read(ucb->ac97, UCB_ADC_INP_TSPY, adcsync); +} + +/* + * Switch to X position mode and measure Y plate. We switch the plate + * configuration in pressure mode, then switch to position mode. This + * gives a faster response time. Even so, we need to wait about 55us + * for things to stabilise. + */ +static inline unsigned int ucb1400_ts_read_xpos(struct ucb1400_ts *ucb) +{ + ucb1400_reg_write(ucb->ac97, UCB_TS_CR, + UCB_TS_CR_TSMX_GND | UCB_TS_CR_TSPX_POW | + UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA); + ucb1400_reg_write(ucb->ac97, UCB_TS_CR, + UCB_TS_CR_TSMX_GND | UCB_TS_CR_TSPX_POW | + UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA); + ucb1400_reg_write(ucb->ac97, UCB_TS_CR, + UCB_TS_CR_TSMX_GND | UCB_TS_CR_TSPX_POW | + UCB_TS_CR_MODE_POS | UCB_TS_CR_BIAS_ENA); + + udelay(ts_delay); + + return ucb1400_adc_read(ucb->ac97, UCB_ADC_INP_TSPY, adcsync); +} + +/* + * Switch to Y position mode and measure X plate. We switch the plate + * configuration in pressure mode, then switch to position mode. This + * gives a faster response time. Even so, we need to wait about 55us + * for things to stabilise. + */ +static inline unsigned int ucb1400_ts_read_ypos(struct ucb1400_ts *ucb) +{ + ucb1400_reg_write(ucb->ac97, UCB_TS_CR, + UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_POW | + UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA); + ucb1400_reg_write(ucb->ac97, UCB_TS_CR, + UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_POW | + UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA); + ucb1400_reg_write(ucb->ac97, UCB_TS_CR, + UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_POW | + UCB_TS_CR_MODE_POS | UCB_TS_CR_BIAS_ENA); + + udelay(ts_delay); + + return ucb1400_adc_read(ucb->ac97, UCB_ADC_INP_TSPX, adcsync); +} + +/* + * Switch to X plate resistance mode. Set MX to ground, PX to + * supply. Measure current. + */ +static inline unsigned int ucb1400_ts_read_xres(struct ucb1400_ts *ucb) +{ + ucb1400_reg_write(ucb->ac97, UCB_TS_CR, + UCB_TS_CR_TSMX_GND | UCB_TS_CR_TSPX_POW | + UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA); + return ucb1400_adc_read(ucb->ac97, 0, adcsync); +} + +/* + * Switch to Y plate resistance mode. Set MY to ground, PY to + * supply. Measure current. + */ +static inline unsigned int ucb1400_ts_read_yres(struct ucb1400_ts *ucb) +{ + ucb1400_reg_write(ucb->ac97, UCB_TS_CR, + UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_POW | + UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA); + return ucb1400_adc_read(ucb->ac97, 0, adcsync); +} + +static inline int ucb1400_ts_pen_down(struct snd_ac97 *ac97) +{ + unsigned short val = ucb1400_reg_read(ac97, UCB_TS_CR); + return val & (UCB_TS_CR_TSPX_LOW | UCB_TS_CR_TSMX_LOW); +} + +static inline void ucb1400_ts_irq_enable(struct snd_ac97 *ac97) +{ + ucb1400_reg_write(ac97, UCB_IE_CLEAR, UCB_IE_TSPX); + ucb1400_reg_write(ac97, UCB_IE_CLEAR, 0); + ucb1400_reg_write(ac97, UCB_IE_FAL, UCB_IE_TSPX); +} + +static inline void ucb1400_ts_irq_disable(struct snd_ac97 *ac97) +{ + ucb1400_reg_write(ac97, UCB_IE_FAL, 0); +} + +static void ucb1400_ts_evt_add(struct input_dev *idev, u16 pressure, u16 x, u16 y) +{ + input_report_abs(idev, ABS_X, x); + input_report_abs(idev, ABS_Y, y); + input_report_abs(idev, ABS_PRESSURE, pressure); + input_sync(idev); +} + +static void ucb1400_ts_event_release(struct input_dev *idev) +{ + input_report_abs(idev, ABS_PRESSURE, 0); + input_sync(idev); +} + +static void ucb1400_handle_pending_irq(struct ucb1400_ts *ucb) +{ + unsigned int isr; + + isr = ucb1400_reg_read(ucb->ac97, UCB_IE_STATUS); + ucb1400_reg_write(ucb->ac97, UCB_IE_CLEAR, isr); + ucb1400_reg_write(ucb->ac97, UCB_IE_CLEAR, 0); + + if (isr & UCB_IE_TSPX) { + ucb1400_ts_irq_disable(ucb->ac97); + enable_irq(ucb->irq); + } else + printk(KERN_ERR "ucb1400: unexpected IE_STATUS = %#x\n", isr); +} + +static int ucb1400_ts_thread(void *_ucb) +{ + struct ucb1400_ts *ucb = _ucb; + struct task_struct *tsk = current; + int valid = 0; + struct sched_param param = { .sched_priority = 1 }; + + sched_setscheduler(tsk, SCHED_FIFO, ¶m); + + set_freezable(); + while (!kthread_should_stop()) { + unsigned int x, y, p; + long timeout; + + ucb->ts_restart = 0; + + if (ucb->irq_pending) { + ucb->irq_pending = 0; + ucb1400_handle_pending_irq(ucb); + } + + ucb1400_adc_enable(ucb->ac97); + x = ucb1400_ts_read_xpos(ucb); + y = ucb1400_ts_read_ypos(ucb); + p = ucb1400_ts_read_pressure(ucb); + ucb1400_adc_disable(ucb->ac97); + + /* Switch back to interrupt mode. */ + ucb1400_ts_mode_int(ucb->ac97); + + msleep(10); + + if (ucb1400_ts_pen_down(ucb->ac97)) { + ucb1400_ts_irq_enable(ucb->ac97); + + /* + * If we spat out a valid sample set last time, + * spit out a "pen off" sample here. + */ + if (valid) { + ucb1400_ts_event_release(ucb->ts_idev); + valid = 0; + } + + timeout = MAX_SCHEDULE_TIMEOUT; + } else { + valid = 1; + ucb1400_ts_evt_add(ucb->ts_idev, p, x, y); + timeout = msecs_to_jiffies(10); + } + + wait_event_freezable_timeout(ucb->ts_wait, + ucb->irq_pending || ucb->ts_restart || + kthread_should_stop(), timeout); + } + + /* Send the "pen off" if we are stopping with the pen still active */ + if (valid) + ucb1400_ts_event_release(ucb->ts_idev); + + ucb->ts_task = NULL; + return 0; +} + +/* + * A restriction with interrupts exists when using the ucb1400, as + * the codec read/write routines may sleep while waiting for codec + * access completion and uses semaphores for access control to the + * AC97 bus. A complete codec read cycle could take anywhere from + * 60 to 100uSec so we *definitely* don't want to spin inside the + * interrupt handler waiting for codec access. So, we handle the + * interrupt by scheduling a RT kernel thread to run in process + * context instead of interrupt context. + */ +static irqreturn_t ucb1400_hard_irq(int irqnr, void *devid) +{ + struct ucb1400_ts *ucb = devid; + + if (irqnr == ucb->irq) { + disable_irq(ucb->irq); + ucb->irq_pending = 1; + wake_up(&ucb->ts_wait); + return IRQ_HANDLED; + } + return IRQ_NONE; +} + +static int ucb1400_ts_open(struct input_dev *idev) +{ + struct ucb1400_ts *ucb = input_get_drvdata(idev); + int ret = 0; + + BUG_ON(ucb->ts_task); + + ucb->ts_task = kthread_run(ucb1400_ts_thread, ucb, "UCB1400_ts"); + if (IS_ERR(ucb->ts_task)) { + ret = PTR_ERR(ucb->ts_task); + ucb->ts_task = NULL; + } + + return ret; +} + +static void ucb1400_ts_close(struct input_dev *idev) +{ + struct ucb1400_ts *ucb = input_get_drvdata(idev); + + if (ucb->ts_task) + kthread_stop(ucb->ts_task); + + ucb1400_ts_irq_disable(ucb->ac97); + ucb1400_reg_write(ucb->ac97, UCB_TS_CR, 0); +} + +#ifndef NO_IRQ +#define NO_IRQ 0 +#endif + +/* + * Try to probe our interrupt, rather than relying on lots of + * hard-coded machine dependencies. + */ +static int ucb1400_ts_detect_irq(struct ucb1400_ts *ucb) +{ + unsigned long mask, timeout; + + mask = probe_irq_on(); + + /* Enable the ADC interrupt. */ + ucb1400_reg_write(ucb->ac97, UCB_IE_RIS, UCB_IE_ADC); + ucb1400_reg_write(ucb->ac97, UCB_IE_FAL, UCB_IE_ADC); + ucb1400_reg_write(ucb->ac97, UCB_IE_CLEAR, 0xffff); + ucb1400_reg_write(ucb->ac97, UCB_IE_CLEAR, 0); + + /* Cause an ADC interrupt. */ + ucb1400_reg_write(ucb->ac97, UCB_ADC_CR, UCB_ADC_ENA); + ucb1400_reg_write(ucb->ac97, UCB_ADC_CR, UCB_ADC_ENA | UCB_ADC_START); + + /* Wait for the conversion to complete. */ + timeout = jiffies + HZ/2; + while (!(ucb1400_reg_read(ucb->ac97, UCB_ADC_DATA) & + UCB_ADC_DAT_VALID)) { + cpu_relax(); + if (time_after(jiffies, timeout)) { + printk(KERN_ERR "ucb1400: timed out in IRQ probe\n"); + probe_irq_off(mask); + return -ENODEV; + } + } + ucb1400_reg_write(ucb->ac97, UCB_ADC_CR, 0); + + /* Disable and clear interrupt. */ + ucb1400_reg_write(ucb->ac97, UCB_IE_RIS, 0); + ucb1400_reg_write(ucb->ac97, UCB_IE_FAL, 0); + ucb1400_reg_write(ucb->ac97, UCB_IE_CLEAR, 0xffff); + ucb1400_reg_write(ucb->ac97, UCB_IE_CLEAR, 0); + + /* Read triggered interrupt. */ + ucb->irq = probe_irq_off(mask); + if (ucb->irq < 0 || ucb->irq == NO_IRQ) + return -ENODEV; + + return 0; +} + +static int ucb1400_ts_probe(struct platform_device *dev) +{ + int error, x_res, y_res; + struct ucb1400_ts *ucb = dev->dev.platform_data; + + ucb->ts_idev = input_allocate_device(); + if (!ucb->ts_idev) { + error = -ENOMEM; + goto err; + } + + error = ucb1400_ts_detect_irq(ucb); + if (error) { + printk(KERN_ERR "UCB1400: IRQ probe failed\n"); + goto err_free_devs; + } + + init_waitqueue_head(&ucb->ts_wait); + + error = request_irq(ucb->irq, ucb1400_hard_irq, IRQF_TRIGGER_RISING, + "UCB1400", ucb); + if (error) { + printk(KERN_ERR "ucb1400: unable to grab irq%d: %d\n", + ucb->irq, error); + goto err_free_devs; + } + printk(KERN_DEBUG "UCB1400: found IRQ %d\n", ucb->irq); + + input_set_drvdata(ucb->ts_idev, ucb); + + ucb->ts_idev->dev.parent = &dev->dev; + ucb->ts_idev->name = "UCB1400 touchscreen interface"; + ucb->ts_idev->id.vendor = ucb1400_reg_read(ucb->ac97, + AC97_VENDOR_ID1); + ucb->ts_idev->id.product = ucb->id; + ucb->ts_idev->open = ucb1400_ts_open; + ucb->ts_idev->close = ucb1400_ts_close; + ucb->ts_idev->evbit[0] = BIT_MASK(EV_ABS); + + ucb1400_adc_enable(ucb->ac97); + x_res = ucb1400_ts_read_xres(ucb); + y_res = ucb1400_ts_read_yres(ucb); + ucb1400_adc_disable(ucb->ac97); + printk(KERN_DEBUG "UCB1400: x/y = %d/%d\n", x_res, y_res); + + input_set_abs_params(ucb->ts_idev, ABS_X, 0, x_res, 0, 0); + input_set_abs_params(ucb->ts_idev, ABS_Y, 0, y_res, 0, 0); + input_set_abs_params(ucb->ts_idev, ABS_PRESSURE, 0, 0, 0, 0); + + error = input_register_device(ucb->ts_idev); + if (error) + goto err_free_irq; + + return 0; + +err_free_irq: + free_irq(ucb->irq, ucb); +err_free_devs: + input_free_device(ucb->ts_idev); +err: + return error; + +} + +static int ucb1400_ts_remove(struct platform_device *dev) +{ + struct ucb1400_ts *ucb = dev->dev.platform_data; + + free_irq(ucb->irq, ucb); + input_unregister_device(ucb->ts_idev); + return 0; +} + +#ifdef CONFIG_PM +static int ucb1400_ts_resume(struct platform_device *dev) +{ + struct ucb1400_ts *ucb = platform_get_drvdata(dev); + + if (ucb->ts_task) { + /* + * Restart the TS thread to ensure the + * TS interrupt mode is set up again + * after sleep. + */ + ucb->ts_restart = 1; + wake_up(&ucb->ts_wait); + } + return 0; +} +#else +#define ucb1400_ts_resume NULL +#endif + +static struct platform_driver ucb1400_ts_driver = { + .probe = ucb1400_ts_probe, + .remove = ucb1400_ts_remove, + .resume = ucb1400_ts_resume, + .driver = { + .name = "ucb1400_ts", + }, +}; + +static int __init ucb1400_ts_init(void) +{ + return platform_driver_register(&ucb1400_ts_driver); +} + +static void __exit ucb1400_ts_exit(void) +{ + platform_driver_unregister(&ucb1400_ts_driver); +} + +module_param(adcsync, bool, 0444); +MODULE_PARM_DESC(adcsync, "Synchronize touch readings with ADCSYNC pin."); + +module_param(ts_delay, int, 0444); +MODULE_PARM_DESC(ts_delay, "Delay between panel setup and" + " position read. Default = 55us."); + +module_param(ts_delay_pressure, int, 0444); +MODULE_PARM_DESC(ts_delay_pressure, + "delay between panel setup and pressure read." + " Default = 0us."); + +module_init(ucb1400_ts_init); +module_exit(ucb1400_ts_exit); + +MODULE_DESCRIPTION("Philips UCB1400 touchscreen driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/touchscreen/usbtouchscreen.c b/drivers/input/touchscreen/usbtouchscreen.c new file mode 100644 index 0000000..fdd645c --- /dev/null +++ b/drivers/input/touchscreen/usbtouchscreen.c @@ -0,0 +1,1029 @@ +/****************************************************************************** + * usbtouchscreen.c + * Driver for USB Touchscreens, supporting those devices: + * - eGalax Touchkit + * includes eTurboTouch CT-410/510/700 + * - 3M/Microtouch EX II series + * - ITM + * - PanJit TouchSet + * - eTurboTouch + * - Gunze AHL61 + * - DMC TSC-10/25 + * - IRTOUCHSYSTEMS/UNITOP + * - IdealTEK URTC1000 + * - General Touch + * - GoTop Super_Q2/GogoPen/PenPower tablets + * + * Copyright (C) 2004-2007 by Daniel Ritz <daniel.ritz@gmx.ch> + * Copyright (C) by Todd E. Johnson (mtouchusb.c) + * + * 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. + * + * Driver is based on touchkitusb.c + * - ITM parts are from itmtouch.c + * - 3M parts are from mtouchusb.c + * - PanJit parts are from an unmerged driver by Lanslott Gish + * - DMC TSC 10/25 are from Holger Schurig, with ideas from an unmerged + * driver from Marius Vollmer + * + *****************************************************************************/ + +//#define DEBUG + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/usb.h> +#include <linux/usb/input.h> +#include <linux/hid.h> + + +#define DRIVER_VERSION "v0.6" +#define DRIVER_AUTHOR "Daniel Ritz <daniel.ritz@gmx.ch>" +#define DRIVER_DESC "USB Touchscreen Driver" + +static int swap_xy; +module_param(swap_xy, bool, 0644); +MODULE_PARM_DESC(swap_xy, "If set X and Y axes are swapped."); + +/* device specifc data/functions */ +struct usbtouch_usb; +struct usbtouch_device_info { + int min_xc, max_xc; + int min_yc, max_yc; + int min_press, max_press; + int rept_size; + + void (*process_pkt) (struct usbtouch_usb *usbtouch, unsigned char *pkt, int len); + + /* + * used to get the packet len. possible return values: + * > 0: packet len + * = 0: skip one byte + * < 0: -return value more bytes needed + */ + int (*get_pkt_len) (unsigned char *pkt, int len); + + int (*read_data) (struct usbtouch_usb *usbtouch, unsigned char *pkt); + int (*init) (struct usbtouch_usb *usbtouch); +}; + +/* a usbtouch device */ +struct usbtouch_usb { + unsigned char *data; + dma_addr_t data_dma; + unsigned char *buffer; + int buf_len; + struct urb *irq; + struct usb_device *udev; + struct input_dev *input; + struct usbtouch_device_info *type; + char name[128]; + char phys[64]; + + int x, y; + int touch, press; +}; + + +/* device types */ +enum { + DEVTYPE_IGNORE = -1, + DEVTYPE_EGALAX, + DEVTYPE_PANJIT, + DEVTYPE_3M, + DEVTYPE_ITM, + DEVTYPE_ETURBO, + DEVTYPE_GUNZE, + DEVTYPE_DMC_TSC10, + DEVTYPE_IRTOUCH, + DEVTYPE_IDEALTEK, + DEVTYPE_GENERAL_TOUCH, + DEVTYPE_GOTOP, +}; + +#define USB_DEVICE_HID_CLASS(vend, prod) \ + .match_flags = USB_DEVICE_ID_MATCH_INT_CLASS \ + | USB_DEVICE_ID_MATCH_DEVICE, \ + .idVendor = (vend), \ + .idProduct = (prod), \ + .bInterfaceClass = USB_INTERFACE_CLASS_HID, \ + .bInterfaceProtocol = USB_INTERFACE_PROTOCOL_MOUSE + +static struct usb_device_id usbtouch_devices[] = { +#ifdef CONFIG_TOUCHSCREEN_USB_EGALAX + /* ignore the HID capable devices, handled by usbhid */ + {USB_DEVICE_HID_CLASS(0x0eef, 0x0001), .driver_info = DEVTYPE_IGNORE}, + {USB_DEVICE_HID_CLASS(0x0eef, 0x0002), .driver_info = DEVTYPE_IGNORE}, + + /* normal device IDs */ + {USB_DEVICE(0x3823, 0x0001), .driver_info = DEVTYPE_EGALAX}, + {USB_DEVICE(0x3823, 0x0002), .driver_info = DEVTYPE_EGALAX}, + {USB_DEVICE(0x0123, 0x0001), .driver_info = DEVTYPE_EGALAX}, + {USB_DEVICE(0x0eef, 0x0001), .driver_info = DEVTYPE_EGALAX}, + {USB_DEVICE(0x0eef, 0x0002), .driver_info = DEVTYPE_EGALAX}, + {USB_DEVICE(0x1234, 0x0001), .driver_info = DEVTYPE_EGALAX}, + {USB_DEVICE(0x1234, 0x0002), .driver_info = DEVTYPE_EGALAX}, +#endif + +#ifdef CONFIG_TOUCHSCREEN_USB_PANJIT + {USB_DEVICE(0x134c, 0x0001), .driver_info = DEVTYPE_PANJIT}, + {USB_DEVICE(0x134c, 0x0002), .driver_info = DEVTYPE_PANJIT}, + {USB_DEVICE(0x134c, 0x0003), .driver_info = DEVTYPE_PANJIT}, + {USB_DEVICE(0x134c, 0x0004), .driver_info = DEVTYPE_PANJIT}, +#endif + +#ifdef CONFIG_TOUCHSCREEN_USB_3M + {USB_DEVICE(0x0596, 0x0001), .driver_info = DEVTYPE_3M}, +#endif + +#ifdef CONFIG_TOUCHSCREEN_USB_ITM + {USB_DEVICE(0x0403, 0xf9e9), .driver_info = DEVTYPE_ITM}, +#endif + +#ifdef CONFIG_TOUCHSCREEN_USB_ETURBO + {USB_DEVICE(0x1234, 0x5678), .driver_info = DEVTYPE_ETURBO}, +#endif + +#ifdef CONFIG_TOUCHSCREEN_USB_GUNZE + {USB_DEVICE(0x0637, 0x0001), .driver_info = DEVTYPE_GUNZE}, +#endif + +#ifdef CONFIG_TOUCHSCREEN_USB_DMC_TSC10 + {USB_DEVICE(0x0afa, 0x03e8), .driver_info = DEVTYPE_DMC_TSC10}, +#endif + +#ifdef CONFIG_TOUCHSCREEN_USB_IRTOUCH + {USB_DEVICE(0x595a, 0x0001), .driver_info = DEVTYPE_IRTOUCH}, + {USB_DEVICE(0x6615, 0x0001), .driver_info = DEVTYPE_IRTOUCH}, +#endif + +#ifdef CONFIG_TOUCHSCREEN_USB_IDEALTEK + {USB_DEVICE(0x1391, 0x1000), .driver_info = DEVTYPE_IDEALTEK}, +#endif + +#ifdef CONFIG_TOUCHSCREEN_USB_GENERAL_TOUCH + {USB_DEVICE(0x0dfc, 0x0001), .driver_info = DEVTYPE_GENERAL_TOUCH}, +#endif + +#ifdef CONFIG_TOUCHSCREEN_USB_GOTOP + {USB_DEVICE(0x08f2, 0x007f), .driver_info = DEVTYPE_GOTOP}, + {USB_DEVICE(0x08f2, 0x00ce), .driver_info = DEVTYPE_GOTOP}, + {USB_DEVICE(0x08f2, 0x00f4), .driver_info = DEVTYPE_GOTOP}, +#endif + + {} +}; + + +/***************************************************************************** + * eGalax part + */ + +#ifdef CONFIG_TOUCHSCREEN_USB_EGALAX + +#ifndef MULTI_PACKET +#define MULTI_PACKET +#endif + +#define EGALAX_PKT_TYPE_MASK 0xFE +#define EGALAX_PKT_TYPE_REPT 0x80 +#define EGALAX_PKT_TYPE_DIAG 0x0A + +static int egalax_read_data(struct usbtouch_usb *dev, unsigned char *pkt) +{ + if ((pkt[0] & EGALAX_PKT_TYPE_MASK) != EGALAX_PKT_TYPE_REPT) + return 0; + + dev->x = ((pkt[3] & 0x0F) << 7) | (pkt[4] & 0x7F); + dev->y = ((pkt[1] & 0x0F) << 7) | (pkt[2] & 0x7F); + dev->touch = pkt[0] & 0x01; + + return 1; +} + +static int egalax_get_pkt_len(unsigned char *buf, int len) +{ + switch (buf[0] & EGALAX_PKT_TYPE_MASK) { + case EGALAX_PKT_TYPE_REPT: + return 5; + + case EGALAX_PKT_TYPE_DIAG: + if (len < 2) + return -1; + + return buf[1] + 2; + } + + return 0; +} +#endif + + +/***************************************************************************** + * PanJit Part + */ +#ifdef CONFIG_TOUCHSCREEN_USB_PANJIT +static int panjit_read_data(struct usbtouch_usb *dev, unsigned char *pkt) +{ + dev->x = ((pkt[2] & 0x0F) << 8) | pkt[1]; + dev->y = ((pkt[4] & 0x0F) << 8) | pkt[3]; + dev->touch = pkt[0] & 0x01; + + return 1; +} +#endif + + +/***************************************************************************** + * 3M/Microtouch Part + */ +#ifdef CONFIG_TOUCHSCREEN_USB_3M + +#define MTOUCHUSB_ASYNC_REPORT 1 +#define MTOUCHUSB_RESET 7 +#define MTOUCHUSB_REQ_CTRLLR_ID 10 + +static int mtouch_read_data(struct usbtouch_usb *dev, unsigned char *pkt) +{ + dev->x = (pkt[8] << 8) | pkt[7]; + dev->y = (pkt[10] << 8) | pkt[9]; + dev->touch = (pkt[2] & 0x40) ? 1 : 0; + + return 1; +} + +static int mtouch_init(struct usbtouch_usb *usbtouch) +{ + int ret, i; + + ret = usb_control_msg(usbtouch->udev, usb_rcvctrlpipe(usbtouch->udev, 0), + MTOUCHUSB_RESET, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 1, 0, NULL, 0, USB_CTRL_SET_TIMEOUT); + dbg("%s - usb_control_msg - MTOUCHUSB_RESET - bytes|err: %d", + __func__, ret); + if (ret < 0) + return ret; + msleep(150); + + for (i = 0; i < 3; i++) { + ret = usb_control_msg(usbtouch->udev, usb_rcvctrlpipe(usbtouch->udev, 0), + MTOUCHUSB_ASYNC_REPORT, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 1, 1, NULL, 0, USB_CTRL_SET_TIMEOUT); + dbg("%s - usb_control_msg - MTOUCHUSB_ASYNC_REPORT - bytes|err: %d", + __func__, ret); + if (ret >= 0) + break; + if (ret != -EPIPE) + return ret; + } + + return 0; +} +#endif + + +/***************************************************************************** + * ITM Part + */ +#ifdef CONFIG_TOUCHSCREEN_USB_ITM +static int itm_read_data(struct usbtouch_usb *dev, unsigned char *pkt) +{ + int touch; + /* + * ITM devices report invalid x/y data if not touched. + * if the screen was touched before but is not touched any more + * report touch as 0 with the last valid x/y data once. then stop + * reporting data until touched again. + */ + dev->press = ((pkt[2] & 0x01) << 7) | (pkt[5] & 0x7F); + + touch = ~pkt[7] & 0x20; + if (!touch) { + if (dev->touch) { + dev->touch = 0; + return 1; + } + + return 0; + } + + dev->x = ((pkt[0] & 0x1F) << 7) | (pkt[3] & 0x7F); + dev->y = ((pkt[1] & 0x1F) << 7) | (pkt[4] & 0x7F); + dev->touch = touch; + + return 1; +} +#endif + + +/***************************************************************************** + * eTurboTouch part + */ +#ifdef CONFIG_TOUCHSCREEN_USB_ETURBO +#ifndef MULTI_PACKET +#define MULTI_PACKET +#endif +static int eturbo_read_data(struct usbtouch_usb *dev, unsigned char *pkt) +{ + unsigned int shift; + + /* packets should start with sync */ + if (!(pkt[0] & 0x80)) + return 0; + + shift = (6 - (pkt[0] & 0x03)); + dev->x = ((pkt[3] << 7) | pkt[4]) >> shift; + dev->y = ((pkt[1] << 7) | pkt[2]) >> shift; + dev->touch = (pkt[0] & 0x10) ? 1 : 0; + + return 1; +} + +static int eturbo_get_pkt_len(unsigned char *buf, int len) +{ + if (buf[0] & 0x80) + return 5; + if (buf[0] == 0x01) + return 3; + return 0; +} +#endif + + +/***************************************************************************** + * Gunze part + */ +#ifdef CONFIG_TOUCHSCREEN_USB_GUNZE +static int gunze_read_data(struct usbtouch_usb *dev, unsigned char *pkt) +{ + if (!(pkt[0] & 0x80) || ((pkt[1] | pkt[2] | pkt[3]) & 0x80)) + return 0; + + dev->x = ((pkt[0] & 0x1F) << 7) | (pkt[2] & 0x7F); + dev->y = ((pkt[1] & 0x1F) << 7) | (pkt[3] & 0x7F); + dev->touch = pkt[0] & 0x20; + + return 1; +} +#endif + +/***************************************************************************** + * DMC TSC-10/25 Part + * + * Documentation about the controller and it's protocol can be found at + * http://www.dmccoltd.com/files/controler/tsc10usb_pi_e.pdf + * http://www.dmccoltd.com/files/controler/tsc25_usb_e.pdf + */ +#ifdef CONFIG_TOUCHSCREEN_USB_DMC_TSC10 + +/* supported data rates. currently using 130 */ +#define TSC10_RATE_POINT 0x50 +#define TSC10_RATE_30 0x40 +#define TSC10_RATE_50 0x41 +#define TSC10_RATE_80 0x42 +#define TSC10_RATE_100 0x43 +#define TSC10_RATE_130 0x44 +#define TSC10_RATE_150 0x45 + +/* commands */ +#define TSC10_CMD_RESET 0x55 +#define TSC10_CMD_RATE 0x05 +#define TSC10_CMD_DATA1 0x01 + +static int dmc_tsc10_init(struct usbtouch_usb *usbtouch) +{ + struct usb_device *dev = usbtouch->udev; + int ret = -ENOMEM; + unsigned char *buf; + + buf = kmalloc(2, GFP_KERNEL); + if (!buf) + goto err_nobuf; + /* reset */ + buf[0] = buf[1] = 0xFF; + ret = usb_control_msg(dev, usb_rcvctrlpipe (dev, 0), + TSC10_CMD_RESET, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0, 0, buf, 2, USB_CTRL_SET_TIMEOUT); + if (ret < 0) + goto err_out; + if (buf[0] != 0x06 || buf[1] != 0x00) { + ret = -ENODEV; + goto err_out; + } + + /* set coordinate output rate */ + buf[0] = buf[1] = 0xFF; + ret = usb_control_msg(dev, usb_rcvctrlpipe (dev, 0), + TSC10_CMD_RATE, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + TSC10_RATE_150, 0, buf, 2, USB_CTRL_SET_TIMEOUT); + if (ret < 0) + goto err_out; + if ((buf[0] != 0x06 || buf[1] != 0x00) && + (buf[0] != 0x15 || buf[1] != 0x01)) { + ret = -ENODEV; + goto err_out; + } + + /* start sending data */ + ret = usb_control_msg(dev, usb_rcvctrlpipe (dev, 0), + TSC10_CMD_DATA1, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0, 0, NULL, 0, USB_CTRL_SET_TIMEOUT); +err_out: + kfree(buf); +err_nobuf: + return ret; +} + + +static int dmc_tsc10_read_data(struct usbtouch_usb *dev, unsigned char *pkt) +{ + dev->x = ((pkt[2] & 0x03) << 8) | pkt[1]; + dev->y = ((pkt[4] & 0x03) << 8) | pkt[3]; + dev->touch = pkt[0] & 0x01; + + return 1; +} +#endif + + +/***************************************************************************** + * IRTOUCH Part + */ +#ifdef CONFIG_TOUCHSCREEN_USB_IRTOUCH +static int irtouch_read_data(struct usbtouch_usb *dev, unsigned char *pkt) +{ + dev->x = (pkt[3] << 8) | pkt[2]; + dev->y = (pkt[5] << 8) | pkt[4]; + dev->touch = (pkt[1] & 0x03) ? 1 : 0; + + return 1; +} +#endif + + +/***************************************************************************** + * IdealTEK URTC1000 Part + */ +#ifdef CONFIG_TOUCHSCREEN_USB_IDEALTEK +#ifndef MULTI_PACKET +#define MULTI_PACKET +#endif +static int idealtek_get_pkt_len(unsigned char *buf, int len) +{ + if (buf[0] & 0x80) + return 5; + if (buf[0] == 0x01) + return len; + return 0; +} + +static int idealtek_read_data(struct usbtouch_usb *dev, unsigned char *pkt) +{ + switch (pkt[0] & 0x98) { + case 0x88: + /* touch data in IdealTEK mode */ + dev->x = (pkt[1] << 5) | (pkt[2] >> 2); + dev->y = (pkt[3] << 5) | (pkt[4] >> 2); + dev->touch = (pkt[0] & 0x40) ? 1 : 0; + return 1; + + case 0x98: + /* touch data in MT emulation mode */ + dev->x = (pkt[2] << 5) | (pkt[1] >> 2); + dev->y = (pkt[4] << 5) | (pkt[3] >> 2); + dev->touch = (pkt[0] & 0x40) ? 1 : 0; + return 1; + + default: + return 0; + } +} +#endif + +/***************************************************************************** + * General Touch Part + */ +#ifdef CONFIG_TOUCHSCREEN_USB_GENERAL_TOUCH +static int general_touch_read_data(struct usbtouch_usb *dev, unsigned char *pkt) +{ + dev->x = ((pkt[2] & 0x0F) << 8) | pkt[1] ; + dev->y = ((pkt[4] & 0x0F) << 8) | pkt[3] ; + dev->press = pkt[5] & 0xff; + dev->touch = pkt[0] & 0x01; + + return 1; +} +#endif + +/***************************************************************************** + * GoTop Part + */ +#ifdef CONFIG_TOUCHSCREEN_USB_GOTOP +static int gotop_read_data(struct usbtouch_usb *dev, unsigned char *pkt) +{ + dev->x = ((pkt[1] & 0x38) << 4) | pkt[2]; + dev->y = ((pkt[1] & 0x07) << 7) | pkt[3]; + dev->touch = pkt[0] & 0x01; + return 1; +} +#endif + + +/***************************************************************************** + * the different device descriptors + */ +#ifdef MULTI_PACKET +static void usbtouch_process_multi(struct usbtouch_usb *usbtouch, + unsigned char *pkt, int len); +#endif + +static struct usbtouch_device_info usbtouch_dev_info[] = { +#ifdef CONFIG_TOUCHSCREEN_USB_EGALAX + [DEVTYPE_EGALAX] = { + .min_xc = 0x0, + .max_xc = 0x07ff, + .min_yc = 0x0, + .max_yc = 0x07ff, + .rept_size = 16, + .process_pkt = usbtouch_process_multi, + .get_pkt_len = egalax_get_pkt_len, + .read_data = egalax_read_data, + }, +#endif + +#ifdef CONFIG_TOUCHSCREEN_USB_PANJIT + [DEVTYPE_PANJIT] = { + .min_xc = 0x0, + .max_xc = 0x0fff, + .min_yc = 0x0, + .max_yc = 0x0fff, + .rept_size = 8, + .read_data = panjit_read_data, + }, +#endif + +#ifdef CONFIG_TOUCHSCREEN_USB_3M + [DEVTYPE_3M] = { + .min_xc = 0x0, + .max_xc = 0x4000, + .min_yc = 0x0, + .max_yc = 0x4000, + .rept_size = 11, + .read_data = mtouch_read_data, + .init = mtouch_init, + }, +#endif + +#ifdef CONFIG_TOUCHSCREEN_USB_ITM + [DEVTYPE_ITM] = { + .min_xc = 0x0, + .max_xc = 0x0fff, + .min_yc = 0x0, + .max_yc = 0x0fff, + .max_press = 0xff, + .rept_size = 8, + .read_data = itm_read_data, + }, +#endif + +#ifdef CONFIG_TOUCHSCREEN_USB_ETURBO + [DEVTYPE_ETURBO] = { + .min_xc = 0x0, + .max_xc = 0x07ff, + .min_yc = 0x0, + .max_yc = 0x07ff, + .rept_size = 8, + .process_pkt = usbtouch_process_multi, + .get_pkt_len = eturbo_get_pkt_len, + .read_data = eturbo_read_data, + }, +#endif + +#ifdef CONFIG_TOUCHSCREEN_USB_GUNZE + [DEVTYPE_GUNZE] = { + .min_xc = 0x0, + .max_xc = 0x0fff, + .min_yc = 0x0, + .max_yc = 0x0fff, + .rept_size = 4, + .read_data = gunze_read_data, + }, +#endif + +#ifdef CONFIG_TOUCHSCREEN_USB_DMC_TSC10 + [DEVTYPE_DMC_TSC10] = { + .min_xc = 0x0, + .max_xc = 0x03ff, + .min_yc = 0x0, + .max_yc = 0x03ff, + .rept_size = 5, + .init = dmc_tsc10_init, + .read_data = dmc_tsc10_read_data, + }, +#endif + +#ifdef CONFIG_TOUCHSCREEN_USB_IRTOUCH + [DEVTYPE_IRTOUCH] = { + .min_xc = 0x0, + .max_xc = 0x0fff, + .min_yc = 0x0, + .max_yc = 0x0fff, + .rept_size = 8, + .read_data = irtouch_read_data, + }, +#endif + +#ifdef CONFIG_TOUCHSCREEN_USB_IDEALTEK + [DEVTYPE_IDEALTEK] = { + .min_xc = 0x0, + .max_xc = 0x0fff, + .min_yc = 0x0, + .max_yc = 0x0fff, + .rept_size = 8, + .process_pkt = usbtouch_process_multi, + .get_pkt_len = idealtek_get_pkt_len, + .read_data = idealtek_read_data, + }, +#endif + +#ifdef CONFIG_TOUCHSCREEN_USB_GENERAL_TOUCH + [DEVTYPE_GENERAL_TOUCH] = { + .min_xc = 0x0, + .max_xc = 0x0500, + .min_yc = 0x0, + .max_yc = 0x0500, + .rept_size = 7, + .read_data = general_touch_read_data, + }, +#endif + +#ifdef CONFIG_TOUCHSCREEN_USB_GOTOP + [DEVTYPE_GOTOP] = { + .min_xc = 0x0, + .max_xc = 0x03ff, + .min_yc = 0x0, + .max_yc = 0x03ff, + .rept_size = 4, + .read_data = gotop_read_data, + }, +#endif +}; + + +/***************************************************************************** + * Generic Part + */ +static void usbtouch_process_pkt(struct usbtouch_usb *usbtouch, + unsigned char *pkt, int len) +{ + struct usbtouch_device_info *type = usbtouch->type; + + if (!type->read_data(usbtouch, pkt)) + return; + + input_report_key(usbtouch->input, BTN_TOUCH, usbtouch->touch); + + if (swap_xy) { + input_report_abs(usbtouch->input, ABS_X, usbtouch->y); + input_report_abs(usbtouch->input, ABS_Y, usbtouch->x); + } else { + input_report_abs(usbtouch->input, ABS_X, usbtouch->x); + input_report_abs(usbtouch->input, ABS_Y, usbtouch->y); + } + if (type->max_press) + input_report_abs(usbtouch->input, ABS_PRESSURE, usbtouch->press); + input_sync(usbtouch->input); +} + + +#ifdef MULTI_PACKET +static void usbtouch_process_multi(struct usbtouch_usb *usbtouch, + unsigned char *pkt, int len) +{ + unsigned char *buffer; + int pkt_len, pos, buf_len, tmp; + + /* process buffer */ + if (unlikely(usbtouch->buf_len)) { + /* try to get size */ + pkt_len = usbtouch->type->get_pkt_len( + usbtouch->buffer, usbtouch->buf_len); + + /* drop? */ + if (unlikely(!pkt_len)) + goto out_flush_buf; + + /* need to append -pkt_len bytes before able to get size */ + if (unlikely(pkt_len < 0)) { + int append = -pkt_len; + if (unlikely(append > len)) + append = len; + if (usbtouch->buf_len + append >= usbtouch->type->rept_size) + goto out_flush_buf; + memcpy(usbtouch->buffer + usbtouch->buf_len, pkt, append); + usbtouch->buf_len += append; + + pkt_len = usbtouch->type->get_pkt_len( + usbtouch->buffer, usbtouch->buf_len); + if (pkt_len < 0) + return; + } + + /* append */ + tmp = pkt_len - usbtouch->buf_len; + if (usbtouch->buf_len + tmp >= usbtouch->type->rept_size) + goto out_flush_buf; + memcpy(usbtouch->buffer + usbtouch->buf_len, pkt, tmp); + usbtouch_process_pkt(usbtouch, usbtouch->buffer, pkt_len); + + buffer = pkt + tmp; + buf_len = len - tmp; + } else { + buffer = pkt; + buf_len = len; + } + + /* loop over the received packet, process */ + pos = 0; + while (pos < buf_len) { + /* get packet len */ + pkt_len = usbtouch->type->get_pkt_len(buffer + pos, + buf_len - pos); + + /* unknown packet: skip one byte */ + if (unlikely(!pkt_len)) { + pos++; + continue; + } + + /* full packet: process */ + if (likely((pkt_len > 0) && (pkt_len <= buf_len - pos))) { + usbtouch_process_pkt(usbtouch, buffer + pos, pkt_len); + } else { + /* incomplete packet: save in buffer */ + memcpy(usbtouch->buffer, buffer + pos, buf_len - pos); + usbtouch->buf_len = buf_len - pos; + return; + } + pos += pkt_len; + } + +out_flush_buf: + usbtouch->buf_len = 0; + return; +} +#endif + + +static void usbtouch_irq(struct urb *urb) +{ + struct usbtouch_usb *usbtouch = urb->context; + int retval; + + switch (urb->status) { + case 0: + /* success */ + break; + case -ETIME: + /* this urb is timing out */ + dbg("%s - urb timed out - was the device unplugged?", + __func__); + return; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + /* this urb is terminated, clean up */ + dbg("%s - urb shutting down with status: %d", + __func__, urb->status); + return; + default: + dbg("%s - nonzero urb status received: %d", + __func__, urb->status); + goto exit; + } + + usbtouch->type->process_pkt(usbtouch, usbtouch->data, urb->actual_length); + +exit: + retval = usb_submit_urb(urb, GFP_ATOMIC); + if (retval) + err("%s - usb_submit_urb failed with result: %d", + __func__, retval); +} + +static int usbtouch_open(struct input_dev *input) +{ + struct usbtouch_usb *usbtouch = input_get_drvdata(input); + + usbtouch->irq->dev = usbtouch->udev; + + if (usb_submit_urb(usbtouch->irq, GFP_KERNEL)) + return -EIO; + + return 0; +} + +static void usbtouch_close(struct input_dev *input) +{ + struct usbtouch_usb *usbtouch = input_get_drvdata(input); + + usb_kill_urb(usbtouch->irq); +} + + +static void usbtouch_free_buffers(struct usb_device *udev, + struct usbtouch_usb *usbtouch) +{ + usb_buffer_free(udev, usbtouch->type->rept_size, + usbtouch->data, usbtouch->data_dma); + kfree(usbtouch->buffer); +} + + +static int usbtouch_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usbtouch_usb *usbtouch; + struct input_dev *input_dev; + struct usb_host_interface *interface; + struct usb_endpoint_descriptor *endpoint; + struct usb_device *udev = interface_to_usbdev(intf); + struct usbtouch_device_info *type; + int err = -ENOMEM; + + /* some devices are ignored */ + if (id->driver_info == DEVTYPE_IGNORE) + return -ENODEV; + + interface = intf->cur_altsetting; + endpoint = &interface->endpoint[0].desc; + + usbtouch = kzalloc(sizeof(struct usbtouch_usb), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!usbtouch || !input_dev) + goto out_free; + + type = &usbtouch_dev_info[id->driver_info]; + usbtouch->type = type; + if (!type->process_pkt) + type->process_pkt = usbtouch_process_pkt; + + usbtouch->data = usb_buffer_alloc(udev, type->rept_size, + GFP_KERNEL, &usbtouch->data_dma); + if (!usbtouch->data) + goto out_free; + + if (type->get_pkt_len) { + usbtouch->buffer = kmalloc(type->rept_size, GFP_KERNEL); + if (!usbtouch->buffer) + goto out_free_buffers; + } + + usbtouch->irq = usb_alloc_urb(0, GFP_KERNEL); + if (!usbtouch->irq) { + dbg("%s - usb_alloc_urb failed: usbtouch->irq", __func__); + goto out_free_buffers; + } + + usbtouch->udev = udev; + usbtouch->input = input_dev; + + if (udev->manufacturer) + strlcpy(usbtouch->name, udev->manufacturer, sizeof(usbtouch->name)); + + if (udev->product) { + if (udev->manufacturer) + strlcat(usbtouch->name, " ", sizeof(usbtouch->name)); + strlcat(usbtouch->name, udev->product, sizeof(usbtouch->name)); + } + + if (!strlen(usbtouch->name)) + snprintf(usbtouch->name, sizeof(usbtouch->name), + "USB Touchscreen %04x:%04x", + le16_to_cpu(udev->descriptor.idVendor), + le16_to_cpu(udev->descriptor.idProduct)); + + usb_make_path(udev, usbtouch->phys, sizeof(usbtouch->phys)); + strlcat(usbtouch->phys, "/input0", sizeof(usbtouch->phys)); + + input_dev->name = usbtouch->name; + input_dev->phys = usbtouch->phys; + usb_to_input_id(udev, &input_dev->id); + input_dev->dev.parent = &intf->dev; + + input_set_drvdata(input_dev, usbtouch); + + input_dev->open = usbtouch_open; + input_dev->close = usbtouch_close; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); + input_set_abs_params(input_dev, ABS_X, type->min_xc, type->max_xc, 0, 0); + input_set_abs_params(input_dev, ABS_Y, type->min_yc, type->max_yc, 0, 0); + if (type->max_press) + input_set_abs_params(input_dev, ABS_PRESSURE, type->min_press, + type->max_press, 0, 0); + + usb_fill_int_urb(usbtouch->irq, usbtouch->udev, + usb_rcvintpipe(usbtouch->udev, endpoint->bEndpointAddress), + usbtouch->data, type->rept_size, + usbtouch_irq, usbtouch, endpoint->bInterval); + + usbtouch->irq->dev = usbtouch->udev; + usbtouch->irq->transfer_dma = usbtouch->data_dma; + usbtouch->irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + + /* device specific init */ + if (type->init) { + err = type->init(usbtouch); + if (err) { + dbg("%s - type->init() failed, err: %d", __func__, err); + goto out_free_buffers; + } + } + + err = input_register_device(usbtouch->input); + if (err) { + dbg("%s - input_register_device failed, err: %d", __func__, err); + goto out_free_buffers; + } + + usb_set_intfdata(intf, usbtouch); + + return 0; + +out_free_buffers: + usbtouch_free_buffers(udev, usbtouch); +out_free: + input_free_device(input_dev); + kfree(usbtouch); + return err; +} + +static void usbtouch_disconnect(struct usb_interface *intf) +{ + struct usbtouch_usb *usbtouch = usb_get_intfdata(intf); + + dbg("%s - called", __func__); + + if (!usbtouch) + return; + + dbg("%s - usbtouch is initialized, cleaning up", __func__); + usb_set_intfdata(intf, NULL); + usb_kill_urb(usbtouch->irq); + input_unregister_device(usbtouch->input); + usb_free_urb(usbtouch->irq); + usbtouch_free_buffers(interface_to_usbdev(intf), usbtouch); + kfree(usbtouch); +} + +MODULE_DEVICE_TABLE(usb, usbtouch_devices); + +static struct usb_driver usbtouch_driver = { + .name = "usbtouchscreen", + .probe = usbtouch_probe, + .disconnect = usbtouch_disconnect, + .id_table = usbtouch_devices, +}; + +static int __init usbtouch_init(void) +{ + return usb_register(&usbtouch_driver); +} + +static void __exit usbtouch_cleanup(void) +{ + usb_deregister(&usbtouch_driver); +} + +module_init(usbtouch_init); +module_exit(usbtouch_cleanup); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +MODULE_ALIAS("touchkitusb"); +MODULE_ALIAS("itmtouch"); +MODULE_ALIAS("mtouchusb"); diff --git a/drivers/input/touchscreen/wm9705.c b/drivers/input/touchscreen/wm9705.c new file mode 100644 index 0000000..6b5be74 --- /dev/null +++ b/drivers/input/touchscreen/wm9705.c @@ -0,0 +1,351 @@ +/* + * wm9705.c -- Codec driver for Wolfson WM9705 AC97 Codec. + * + * Copyright 2003, 2004, 2005, 2006, 2007 Wolfson Microelectronics PLC. + * Author: Liam Girdwood <lrg@slimlogic.co.uk> + * Parts Copyright : Ian Molton <spyro@f2s.com> + * Andrew Zabolotny <zap@homelink.ru> + * Russell King <rmk@arm.linux.org.uk> + * + * 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/moduleparam.h> +#include <linux/kernel.h> +#include <linux/input.h> +#include <linux/delay.h> +#include <linux/bitops.h> +#include <linux/wm97xx.h> + +#define TS_NAME "wm97xx" +#define WM9705_VERSION "1.00" +#define DEFAULT_PRESSURE 0xb0c0 + +/* + * Module parameters + */ + +/* + * Set current used for pressure measurement. + * + * Set pil = 2 to use 400uA + * pil = 1 to use 200uA and + * pil = 0 to disable pressure measurement. + * + * This is used to increase the range of values returned by the adc + * when measureing touchpanel pressure. + */ +static int pil; +module_param(pil, int, 0); +MODULE_PARM_DESC(pil, "Set current used for pressure measurement."); + +/* + * Set threshold for pressure measurement. + * + * Pen down pressure below threshold is ignored. + */ +static int pressure = DEFAULT_PRESSURE & 0xfff; +module_param(pressure, int, 0); +MODULE_PARM_DESC(pressure, "Set threshold for pressure measurement."); + +/* + * Set adc sample delay. + * + * For accurate touchpanel measurements, some settling time may be + * required between the switch matrix applying a voltage across the + * touchpanel plate and the ADC sampling the signal. + * + * This delay can be set by setting delay = n, where n is the array + * position of the delay in the array delay_table below. + * Long delays > 1ms are supported for completeness, but are not + * recommended. + */ +static int delay = 4; +module_param(delay, int, 0); +MODULE_PARM_DESC(delay, "Set adc sample delay."); + +/* + * Pen detect comparator threshold. + * + * 0 to Vmid in 15 steps, 0 = use zero power comparator with Vmid threshold + * i.e. 1 = Vmid/15 threshold + * 15 = Vmid/1 threshold + * + * Adjust this value if you are having problems with pen detect not + * detecting any down events. + */ +static int pdd = 8; +module_param(pdd, int, 0); +MODULE_PARM_DESC(pdd, "Set pen detect comparator threshold"); + +/* + * Set adc mask function. + * + * Sources of glitch noise, such as signals driving an LCD display, may feed + * through to the touch screen plates and affect measurement accuracy. In + * order to minimise this, a signal may be applied to the MASK pin to delay or + * synchronise the sampling. + * + * 0 = No delay or sync + * 1 = High on pin stops conversions + * 2 = Edge triggered, edge on pin delays conversion by delay param (above) + * 3 = Edge triggered, edge on pin starts conversion after delay param + */ +static int mask; +module_param(mask, int, 0); +MODULE_PARM_DESC(mask, "Set adc mask function."); + +/* + * ADC sample delay times in uS + */ +static const int delay_table[] = { + 21, /* 1 AC97 Link frames */ + 42, /* 2 */ + 84, /* 4 */ + 167, /* 8 */ + 333, /* 16 */ + 667, /* 32 */ + 1000, /* 48 */ + 1333, /* 64 */ + 2000, /* 96 */ + 2667, /* 128 */ + 3333, /* 160 */ + 4000, /* 192 */ + 4667, /* 224 */ + 5333, /* 256 */ + 6000, /* 288 */ + 0 /* No delay, switch matrix always on */ +}; + +/* + * Delay after issuing a POLL command. + * + * The delay is 3 AC97 link frames + the touchpanel settling delay + */ +static inline void poll_delay(int d) +{ + udelay(3 * AC97_LINK_FRAME + delay_table[d]); +} + +/* + * set up the physical settings of the WM9705 + */ +static void wm9705_phy_init(struct wm97xx *wm) +{ + u16 dig1 = 0, dig2 = WM97XX_RPR; + + /* + * mute VIDEO and AUX as they share X and Y touchscreen + * inputs on the WM9705 + */ + wm97xx_reg_write(wm, AC97_AUX, 0x8000); + wm97xx_reg_write(wm, AC97_VIDEO, 0x8000); + + /* touchpanel pressure current*/ + if (pil == 2) { + dig2 |= WM9705_PIL; + dev_dbg(wm->dev, + "setting pressure measurement current to 400uA."); + } else if (pil) + dev_dbg(wm->dev, + "setting pressure measurement current to 200uA."); + if (!pil) + pressure = 0; + + /* polling mode sample settling delay */ + if (delay != 4) { + if (delay < 0 || delay > 15) { + dev_dbg(wm->dev, "supplied delay out of range."); + delay = 4; + } + } + dig1 &= 0xff0f; + dig1 |= WM97XX_DELAY(delay); + dev_dbg(wm->dev, "setting adc sample delay to %d u Secs.", + delay_table[delay]); + + /* WM9705 pdd */ + dig2 |= (pdd & 0x000f); + dev_dbg(wm->dev, "setting pdd to Vmid/%d", 1 - (pdd & 0x000f)); + + /* mask */ + dig2 |= ((mask & 0x3) << 4); + + wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER1, dig1); + wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER2, dig2); +} + +static void wm9705_dig_enable(struct wm97xx *wm, int enable) +{ + if (enable) { + wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER2, + wm->dig[2] | WM97XX_PRP_DET_DIG); + wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD); /* dummy read */ + } else + wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER2, + wm->dig[2] & ~WM97XX_PRP_DET_DIG); +} + +static void wm9705_aux_prepare(struct wm97xx *wm) +{ + memcpy(wm->dig_save, wm->dig, sizeof(wm->dig)); + wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER1, 0); + wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER2, WM97XX_PRP_DET_DIG); +} + +static void wm9705_dig_restore(struct wm97xx *wm) +{ + wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER1, wm->dig_save[1]); + wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER2, wm->dig_save[2]); +} + +static inline int is_pden(struct wm97xx *wm) +{ + return wm->dig[2] & WM9705_PDEN; +} + +/* + * Read a sample from the WM9705 adc in polling mode. + */ +static int wm9705_poll_sample(struct wm97xx *wm, int adcsel, int *sample) +{ + int timeout = 5 * delay; + + if (!wm->pen_probably_down) { + u16 data = wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD); + if (!(data & WM97XX_PEN_DOWN)) + return RC_PENUP; + wm->pen_probably_down = 1; + } + + /* set up digitiser */ + if (adcsel & 0x8000) + adcsel = ((adcsel & 0x7fff) + 3) << 12; + + if (wm->mach_ops && wm->mach_ops->pre_sample) + wm->mach_ops->pre_sample(adcsel); + wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER1, + adcsel | WM97XX_POLL | WM97XX_DELAY(delay)); + + /* wait 3 AC97 time slots + delay for conversion */ + poll_delay(delay); + + /* wait for POLL to go low */ + while ((wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER1) & WM97XX_POLL) + && timeout) { + udelay(AC97_LINK_FRAME); + timeout--; + } + + if (timeout == 0) { + /* If PDEN is set, we can get a timeout when pen goes up */ + if (is_pden(wm)) + wm->pen_probably_down = 0; + else + dev_dbg(wm->dev, "adc sample timeout"); + return RC_PENUP; + } + + *sample = wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD); + if (wm->mach_ops && wm->mach_ops->post_sample) + wm->mach_ops->post_sample(adcsel); + + /* check we have correct sample */ + if ((*sample & WM97XX_ADCSEL_MASK) != adcsel) { + dev_dbg(wm->dev, "adc wrong sample, read %x got %x", adcsel, + *sample & WM97XX_ADCSEL_MASK); + return RC_PENUP; + } + + if (!(*sample & WM97XX_PEN_DOWN)) { + wm->pen_probably_down = 0; + return RC_PENUP; + } + + return RC_VALID; +} + +/* + * Sample the WM9705 touchscreen in polling mode + */ +static int wm9705_poll_touch(struct wm97xx *wm, struct wm97xx_data *data) +{ + int rc; + + rc = wm9705_poll_sample(wm, WM97XX_ADCSEL_X, &data->x); + if (rc != RC_VALID) + return rc; + rc = wm9705_poll_sample(wm, WM97XX_ADCSEL_Y, &data->y); + if (rc != RC_VALID) + return rc; + if (pil) { + rc = wm9705_poll_sample(wm, WM97XX_ADCSEL_PRES, &data->p); + if (rc != RC_VALID) + return rc; + } else + data->p = DEFAULT_PRESSURE; + + return RC_VALID; +} + +/* + * Enable WM9705 continuous mode, i.e. touch data is streamed across + * an AC97 slot + */ +static int wm9705_acc_enable(struct wm97xx *wm, int enable) +{ + u16 dig1, dig2; + int ret = 0; + + dig1 = wm->dig[1]; + dig2 = wm->dig[2]; + + if (enable) { + /* continous mode */ + if (wm->mach_ops->acc_startup && + (ret = wm->mach_ops->acc_startup(wm)) < 0) + return ret; + dig1 &= ~(WM97XX_CM_RATE_MASK | WM97XX_ADCSEL_MASK | + WM97XX_DELAY_MASK | WM97XX_SLT_MASK); + dig1 |= WM97XX_CTC | WM97XX_COO | WM97XX_SLEN | + WM97XX_DELAY(delay) | + WM97XX_SLT(wm->acc_slot) | + WM97XX_RATE(wm->acc_rate); + if (pil) + dig1 |= WM97XX_ADCSEL_PRES; + dig2 |= WM9705_PDEN; + } else { + dig1 &= ~(WM97XX_CTC | WM97XX_COO | WM97XX_SLEN); + dig2 &= ~WM9705_PDEN; + if (wm->mach_ops->acc_shutdown) + wm->mach_ops->acc_shutdown(wm); + } + + wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER1, dig1); + wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER2, dig2); + + return ret; +} + +struct wm97xx_codec_drv wm9705_codec = { + .id = WM9705_ID2, + .name = "wm9705", + .poll_sample = wm9705_poll_sample, + .poll_touch = wm9705_poll_touch, + .acc_enable = wm9705_acc_enable, + .phy_init = wm9705_phy_init, + .dig_enable = wm9705_dig_enable, + .dig_restore = wm9705_dig_restore, + .aux_prepare = wm9705_aux_prepare, +}; +EXPORT_SYMBOL_GPL(wm9705_codec); + +/* Module information */ +MODULE_AUTHOR("Liam Girdwood <lrg@slimlogic.co.uk>"); +MODULE_DESCRIPTION("WM9705 Touch Screen Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/touchscreen/wm9712.c b/drivers/input/touchscreen/wm9712.c new file mode 100644 index 0000000..7490b05 --- /dev/null +++ b/drivers/input/touchscreen/wm9712.c @@ -0,0 +1,466 @@ +/* + * wm9712.c -- Codec driver for Wolfson WM9712 AC97 Codecs. + * + * Copyright 2003, 2004, 2005, 2006, 2007 Wolfson Microelectronics PLC. + * Author: Liam Girdwood <lrg@slimlogic.co.uk> + * Parts Copyright : Ian Molton <spyro@f2s.com> + * Andrew Zabolotny <zap@homelink.ru> + * Russell King <rmk@arm.linux.org.uk> + * + * 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/moduleparam.h> +#include <linux/kernel.h> +#include <linux/input.h> +#include <linux/delay.h> +#include <linux/bitops.h> +#include <linux/wm97xx.h> + +#define TS_NAME "wm97xx" +#define WM9712_VERSION "1.00" +#define DEFAULT_PRESSURE 0xb0c0 + +/* + * Module parameters + */ + +/* + * Set internal pull up for pen detect. + * + * Pull up is in the range 1.02k (least sensitive) to 64k (most sensitive) + * i.e. pull up resistance = 64k Ohms / rpu. + * + * Adjust this value if you are having problems with pen detect not + * detecting any down event. + */ +static int rpu = 8; +module_param(rpu, int, 0); +MODULE_PARM_DESC(rpu, "Set internal pull up resitor for pen detect."); + +/* + * Set current used for pressure measurement. + * + * Set pil = 2 to use 400uA + * pil = 1 to use 200uA and + * pil = 0 to disable pressure measurement. + * + * This is used to increase the range of values returned by the adc + * when measureing touchpanel pressure. + */ +static int pil; +module_param(pil, int, 0); +MODULE_PARM_DESC(pil, "Set current used for pressure measurement."); + +/* + * Set threshold for pressure measurement. + * + * Pen down pressure below threshold is ignored. + */ +static int pressure = DEFAULT_PRESSURE & 0xfff; +module_param(pressure, int, 0); +MODULE_PARM_DESC(pressure, "Set threshold for pressure measurement."); + +/* + * Set adc sample delay. + * + * For accurate touchpanel measurements, some settling time may be + * required between the switch matrix applying a voltage across the + * touchpanel plate and the ADC sampling the signal. + * + * This delay can be set by setting delay = n, where n is the array + * position of the delay in the array delay_table below. + * Long delays > 1ms are supported for completeness, but are not + * recommended. + */ +static int delay = 3; +module_param(delay, int, 0); +MODULE_PARM_DESC(delay, "Set adc sample delay."); + +/* + * Set five_wire = 1 to use a 5 wire touchscreen. + * + * NOTE: Five wire mode does not allow for readback of pressure. + */ +static int five_wire; +module_param(five_wire, int, 0); +MODULE_PARM_DESC(five_wire, "Set to '1' to use 5-wire touchscreen."); + +/* + * Set adc mask function. + * + * Sources of glitch noise, such as signals driving an LCD display, may feed + * through to the touch screen plates and affect measurement accuracy. In + * order to minimise this, a signal may be applied to the MASK pin to delay or + * synchronise the sampling. + * + * 0 = No delay or sync + * 1 = High on pin stops conversions + * 2 = Edge triggered, edge on pin delays conversion by delay param (above) + * 3 = Edge triggered, edge on pin starts conversion after delay param + */ +static int mask; +module_param(mask, int, 0); +MODULE_PARM_DESC(mask, "Set adc mask function."); + +/* + * Coordinate Polling Enable. + * + * Set to 1 to enable coordinate polling. e.g. x,y[,p] is sampled together + * for every poll. + */ +static int coord; +module_param(coord, int, 0); +MODULE_PARM_DESC(coord, "Polling coordinate mode"); + +/* + * ADC sample delay times in uS + */ +static const int delay_table[] = { + 21, /* 1 AC97 Link frames */ + 42, /* 2 */ + 84, /* 4 */ + 167, /* 8 */ + 333, /* 16 */ + 667, /* 32 */ + 1000, /* 48 */ + 1333, /* 64 */ + 2000, /* 96 */ + 2667, /* 128 */ + 3333, /* 160 */ + 4000, /* 192 */ + 4667, /* 224 */ + 5333, /* 256 */ + 6000, /* 288 */ + 0 /* No delay, switch matrix always on */ +}; + +/* + * Delay after issuing a POLL command. + * + * The delay is 3 AC97 link frames + the touchpanel settling delay + */ +static inline void poll_delay(int d) +{ + udelay(3 * AC97_LINK_FRAME + delay_table[d]); +} + +/* + * set up the physical settings of the WM9712 + */ +static void wm9712_phy_init(struct wm97xx *wm) +{ + u16 dig1 = 0; + u16 dig2 = WM97XX_RPR | WM9712_RPU(1); + + /* WM9712 rpu */ + if (rpu) { + dig2 &= 0xffc0; + dig2 |= WM9712_RPU(rpu); + dev_dbg(wm->dev, "setting pen detect pull-up to %d Ohms", + 64000 / rpu); + } + + /* WM9712 five wire */ + if (five_wire) { + dig2 |= WM9712_45W; + dev_dbg(wm->dev, "setting 5-wire touchscreen mode."); + + if (pil) { + dev_warn(wm->dev, "pressure measurement is not " + "supported in 5-wire mode\n"); + pil = 0; + } + } + + /* touchpanel pressure current*/ + if (pil == 2) { + dig2 |= WM9712_PIL; + dev_dbg(wm->dev, + "setting pressure measurement current to 400uA."); + } else if (pil) + dev_dbg(wm->dev, + "setting pressure measurement current to 200uA."); + if (!pil) + pressure = 0; + + /* polling mode sample settling delay */ + if (delay < 0 || delay > 15) { + dev_dbg(wm->dev, "supplied delay out of range."); + delay = 4; + } + dig1 &= 0xff0f; + dig1 |= WM97XX_DELAY(delay); + dev_dbg(wm->dev, "setting adc sample delay to %d u Secs.", + delay_table[delay]); + + /* mask */ + dig2 |= ((mask & 0x3) << 6); + if (mask) { + u16 reg; + /* Set GPIO4 as Mask Pin*/ + reg = wm97xx_reg_read(wm, AC97_MISC_AFE); + wm97xx_reg_write(wm, AC97_MISC_AFE, reg | WM97XX_GPIO_4); + reg = wm97xx_reg_read(wm, AC97_GPIO_CFG); + wm97xx_reg_write(wm, AC97_GPIO_CFG, reg | WM97XX_GPIO_4); + } + + /* wait - coord mode */ + if (coord) + dig2 |= WM9712_WAIT; + + wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER1, dig1); + wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER2, dig2); +} + +static void wm9712_dig_enable(struct wm97xx *wm, int enable) +{ + u16 dig2 = wm->dig[2]; + + if (enable) { + wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER2, + dig2 | WM97XX_PRP_DET_DIG); + wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD); /* dummy read */ + } else + wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER2, + dig2 & ~WM97XX_PRP_DET_DIG); +} + +static void wm9712_aux_prepare(struct wm97xx *wm) +{ + memcpy(wm->dig_save, wm->dig, sizeof(wm->dig)); + wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER1, 0); + wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER2, WM97XX_PRP_DET_DIG); +} + +static void wm9712_dig_restore(struct wm97xx *wm) +{ + wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER1, wm->dig_save[1]); + wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER2, wm->dig_save[2]); +} + +static inline int is_pden(struct wm97xx *wm) +{ + return wm->dig[2] & WM9712_PDEN; +} + +/* + * Read a sample from the WM9712 adc in polling mode. + */ +static int wm9712_poll_sample(struct wm97xx *wm, int adcsel, int *sample) +{ + int timeout = 5 * delay; + + if (!wm->pen_probably_down) { + u16 data = wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD); + if (!(data & WM97XX_PEN_DOWN)) + return RC_PENUP; + wm->pen_probably_down = 1; + } + + /* set up digitiser */ + if (adcsel & 0x8000) + adcsel = ((adcsel & 0x7fff) + 3) << 12; + + if (wm->mach_ops && wm->mach_ops->pre_sample) + wm->mach_ops->pre_sample(adcsel); + wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER1, + adcsel | WM97XX_POLL | WM97XX_DELAY(delay)); + + /* wait 3 AC97 time slots + delay for conversion */ + poll_delay(delay); + + /* wait for POLL to go low */ + while ((wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER1) & WM97XX_POLL) + && timeout) { + udelay(AC97_LINK_FRAME); + timeout--; + } + + if (timeout <= 0) { + /* If PDEN is set, we can get a timeout when pen goes up */ + if (is_pden(wm)) + wm->pen_probably_down = 0; + else + dev_dbg(wm->dev, "adc sample timeout"); + return RC_PENUP; + } + + *sample = wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD); + if (wm->mach_ops && wm->mach_ops->post_sample) + wm->mach_ops->post_sample(adcsel); + + /* check we have correct sample */ + if ((*sample & WM97XX_ADCSEL_MASK) != adcsel) { + dev_dbg(wm->dev, "adc wrong sample, read %x got %x", adcsel, + *sample & WM97XX_ADCSEL_MASK); + return RC_PENUP; + } + + if (!(*sample & WM97XX_PEN_DOWN)) { + wm->pen_probably_down = 0; + return RC_PENUP; + } + + return RC_VALID; +} + +/* + * Read a coord from the WM9712 adc in polling mode. + */ +static int wm9712_poll_coord(struct wm97xx *wm, struct wm97xx_data *data) +{ + int timeout = 5 * delay; + + if (!wm->pen_probably_down) { + u16 data_rd = wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD); + if (!(data_rd & WM97XX_PEN_DOWN)) + return RC_PENUP; + wm->pen_probably_down = 1; + } + + /* set up digitiser */ + if (wm->mach_ops && wm->mach_ops->pre_sample) + wm->mach_ops->pre_sample(WM97XX_ADCSEL_X | WM97XX_ADCSEL_Y); + + wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER1, + WM97XX_COO | WM97XX_POLL | WM97XX_DELAY(delay)); + + /* wait 3 AC97 time slots + delay for conversion and read x */ + poll_delay(delay); + data->x = wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD); + /* wait for POLL to go low */ + while ((wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER1) & WM97XX_POLL) + && timeout) { + udelay(AC97_LINK_FRAME); + timeout--; + } + + if (timeout <= 0) { + /* If PDEN is set, we can get a timeout when pen goes up */ + if (is_pden(wm)) + wm->pen_probably_down = 0; + else + dev_dbg(wm->dev, "adc sample timeout"); + return RC_PENUP; + } + + /* read back y data */ + data->y = wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD); + if (pil) + data->p = wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD); + else + data->p = DEFAULT_PRESSURE; + + if (wm->mach_ops && wm->mach_ops->post_sample) + wm->mach_ops->post_sample(WM97XX_ADCSEL_X | WM97XX_ADCSEL_Y); + + /* check we have correct sample */ + if (!(data->x & WM97XX_ADCSEL_X) || !(data->y & WM97XX_ADCSEL_Y)) + goto err; + if (pil && !(data->p & WM97XX_ADCSEL_PRES)) + goto err; + + if (!(data->x & WM97XX_PEN_DOWN) || !(data->y & WM97XX_PEN_DOWN)) { + wm->pen_probably_down = 0; + return RC_PENUP; + } + return RC_VALID; +err: + return 0; +} + +/* + * Sample the WM9712 touchscreen in polling mode + */ +static int wm9712_poll_touch(struct wm97xx *wm, struct wm97xx_data *data) +{ + int rc; + + if (coord) { + rc = wm9712_poll_coord(wm, data); + if (rc != RC_VALID) + return rc; + } else { + rc = wm9712_poll_sample(wm, WM97XX_ADCSEL_X, &data->x); + if (rc != RC_VALID) + return rc; + + rc = wm9712_poll_sample(wm, WM97XX_ADCSEL_Y, &data->y); + if (rc != RC_VALID) + return rc; + + if (pil && !five_wire) { + rc = wm9712_poll_sample(wm, WM97XX_ADCSEL_PRES, + &data->p); + if (rc != RC_VALID) + return rc; + } else + data->p = DEFAULT_PRESSURE; + } + return RC_VALID; +} + +/* + * Enable WM9712 continuous mode, i.e. touch data is streamed across + * an AC97 slot + */ +static int wm9712_acc_enable(struct wm97xx *wm, int enable) +{ + u16 dig1, dig2; + int ret = 0; + + dig1 = wm->dig[1]; + dig2 = wm->dig[2]; + + if (enable) { + /* continous mode */ + if (wm->mach_ops->acc_startup) { + ret = wm->mach_ops->acc_startup(wm); + if (ret < 0) + return ret; + } + dig1 &= ~(WM97XX_CM_RATE_MASK | WM97XX_ADCSEL_MASK | + WM97XX_DELAY_MASK | WM97XX_SLT_MASK); + dig1 |= WM97XX_CTC | WM97XX_COO | WM97XX_SLEN | + WM97XX_DELAY(delay) | + WM97XX_SLT(wm->acc_slot) | + WM97XX_RATE(wm->acc_rate); + if (pil) + dig1 |= WM97XX_ADCSEL_PRES; + dig2 |= WM9712_PDEN; + } else { + dig1 &= ~(WM97XX_CTC | WM97XX_COO | WM97XX_SLEN); + dig2 &= ~WM9712_PDEN; + if (wm->mach_ops->acc_shutdown) + wm->mach_ops->acc_shutdown(wm); + } + + wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER1, dig1); + wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER2, dig2); + + return 0; +} + +struct wm97xx_codec_drv wm9712_codec = { + .id = WM9712_ID2, + .name = "wm9712", + .poll_sample = wm9712_poll_sample, + .poll_touch = wm9712_poll_touch, + .acc_enable = wm9712_acc_enable, + .phy_init = wm9712_phy_init, + .dig_enable = wm9712_dig_enable, + .dig_restore = wm9712_dig_restore, + .aux_prepare = wm9712_aux_prepare, +}; +EXPORT_SYMBOL_GPL(wm9712_codec); + +/* Module information */ +MODULE_AUTHOR("Liam Girdwood <lrg@slimlogic.co.uk>"); +MODULE_DESCRIPTION("WM9712 Touch Screen Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/touchscreen/wm9713.c b/drivers/input/touchscreen/wm9713.c new file mode 100644 index 0000000..238b513 --- /dev/null +++ b/drivers/input/touchscreen/wm9713.c @@ -0,0 +1,480 @@ +/* + * wm9713.c -- Codec touch driver for Wolfson WM9713 AC97 Codec. + * + * Copyright 2003, 2004, 2005, 2006, 2007, 2008 Wolfson Microelectronics PLC. + * Author: Liam Girdwood <lrg@slimlogic.co.uk> + * Parts Copyright : Ian Molton <spyro@f2s.com> + * Andrew Zabolotny <zap@homelink.ru> + * Russell King <rmk@arm.linux.org.uk> + * + * 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/moduleparam.h> +#include <linux/kernel.h> +#include <linux/input.h> +#include <linux/delay.h> +#include <linux/bitops.h> +#include <linux/wm97xx.h> + +#define TS_NAME "wm97xx" +#define WM9713_VERSION "1.00" +#define DEFAULT_PRESSURE 0xb0c0 + +/* + * Module parameters + */ + +/* + * Set internal pull up for pen detect. + * + * Pull up is in the range 1.02k (least sensitive) to 64k (most sensitive) + * i.e. pull up resistance = 64k Ohms / rpu. + * + * Adjust this value if you are having problems with pen detect not + * detecting any down event. + */ +static int rpu = 8; +module_param(rpu, int, 0); +MODULE_PARM_DESC(rpu, "Set internal pull up resitor for pen detect."); + +/* + * Set current used for pressure measurement. + * + * Set pil = 2 to use 400uA + * pil = 1 to use 200uA and + * pil = 0 to disable pressure measurement. + * + * This is used to increase the range of values returned by the adc + * when measureing touchpanel pressure. + */ +static int pil; +module_param(pil, int, 0); +MODULE_PARM_DESC(pil, "Set current used for pressure measurement."); + +/* + * Set threshold for pressure measurement. + * + * Pen down pressure below threshold is ignored. + */ +static int pressure = DEFAULT_PRESSURE & 0xfff; +module_param(pressure, int, 0); +MODULE_PARM_DESC(pressure, "Set threshold for pressure measurement."); + +/* + * Set adc sample delay. + * + * For accurate touchpanel measurements, some settling time may be + * required between the switch matrix applying a voltage across the + * touchpanel plate and the ADC sampling the signal. + * + * This delay can be set by setting delay = n, where n is the array + * position of the delay in the array delay_table below. + * Long delays > 1ms are supported for completeness, but are not + * recommended. + */ +static int delay = 4; +module_param(delay, int, 0); +MODULE_PARM_DESC(delay, "Set adc sample delay."); + +/* + * Set five_wire = 1 to use a 5 wire touchscreen. + * + * NOTE: Five wire mode does not allow for readback of pressure. + */ +static int five_wire; +module_param(five_wire, int, 0); +MODULE_PARM_DESC(five_wire, "Set to '1' to use 5-wire touchscreen."); + +/* + * Set adc mask function. + * + * Sources of glitch noise, such as signals driving an LCD display, may feed + * through to the touch screen plates and affect measurement accuracy. In + * order to minimise this, a signal may be applied to the MASK pin to delay or + * synchronise the sampling. + * + * 0 = No delay or sync + * 1 = High on pin stops conversions + * 2 = Edge triggered, edge on pin delays conversion by delay param (above) + * 3 = Edge triggered, edge on pin starts conversion after delay param + */ +static int mask; +module_param(mask, int, 0); +MODULE_PARM_DESC(mask, "Set adc mask function."); + +/* + * Coordinate Polling Enable. + * + * Set to 1 to enable coordinate polling. e.g. x,y[,p] is sampled together + * for every poll. + */ +static int coord; +module_param(coord, int, 0); +MODULE_PARM_DESC(coord, "Polling coordinate mode"); + +/* + * ADC sample delay times in uS + */ +static const int delay_table[] = { + 21, /* 1 AC97 Link frames */ + 42, /* 2 */ + 84, /* 4 */ + 167, /* 8 */ + 333, /* 16 */ + 667, /* 32 */ + 1000, /* 48 */ + 1333, /* 64 */ + 2000, /* 96 */ + 2667, /* 128 */ + 3333, /* 160 */ + 4000, /* 192 */ + 4667, /* 224 */ + 5333, /* 256 */ + 6000, /* 288 */ + 0 /* No delay, switch matrix always on */ +}; + +/* + * Delay after issuing a POLL command. + * + * The delay is 3 AC97 link frames + the touchpanel settling delay + */ +static inline void poll_delay(int d) +{ + udelay(3 * AC97_LINK_FRAME + delay_table[d]); +} + +/* + * set up the physical settings of the WM9713 + */ +static void wm9713_phy_init(struct wm97xx *wm) +{ + u16 dig1 = 0, dig2, dig3; + + /* default values */ + dig2 = WM97XX_DELAY(4) | WM97XX_SLT(5); + dig3 = WM9712_RPU(1); + + /* rpu */ + if (rpu) { + dig3 &= 0xffc0; + dig3 |= WM9712_RPU(rpu); + dev_info(wm->dev, "setting pen detect pull-up to %d Ohms\n", + 64000 / rpu); + } + + /* Five wire panel? */ + if (five_wire) { + dig3 |= WM9713_45W; + dev_info(wm->dev, "setting 5-wire touchscreen mode."); + + if (pil) { + dev_warn(wm->dev, + "Pressure measurement not supported in 5 " + "wire mode, disabling\n"); + pil = 0; + } + } + + /* touchpanel pressure */ + if (pil == 2) { + dig3 |= WM9712_PIL; + dev_info(wm->dev, + "setting pressure measurement current to 400uA."); + } else if (pil) + dev_info(wm->dev, + "setting pressure measurement current to 200uA."); + if (!pil) + pressure = 0; + + /* sample settling delay */ + if (delay < 0 || delay > 15) { + dev_info(wm->dev, "supplied delay out of range."); + delay = 4; + dev_info(wm->dev, "setting adc sample delay to %d u Secs.", + delay_table[delay]); + } + dig2 &= 0xff0f; + dig2 |= WM97XX_DELAY(delay); + + /* mask */ + dig3 |= ((mask & 0x3) << 4); + if (coord) + dig3 |= WM9713_WAIT; + + wm->misc = wm97xx_reg_read(wm, 0x5a); + + wm97xx_reg_write(wm, AC97_WM9713_DIG1, dig1); + wm97xx_reg_write(wm, AC97_WM9713_DIG2, dig2); + wm97xx_reg_write(wm, AC97_WM9713_DIG3, dig3); + wm97xx_reg_write(wm, AC97_GPIO_STICKY, 0x0); +} + +static void wm9713_dig_enable(struct wm97xx *wm, int enable) +{ + u16 val; + + if (enable) { + val = wm97xx_reg_read(wm, AC97_EXTENDED_MID); + wm97xx_reg_write(wm, AC97_EXTENDED_MID, val & 0x7fff); + wm97xx_reg_write(wm, AC97_WM9713_DIG3, wm->dig[2] | + WM97XX_PRP_DET_DIG); + wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD); /* dummy read */ + } else { + wm97xx_reg_write(wm, AC97_WM9713_DIG3, wm->dig[2] & + ~WM97XX_PRP_DET_DIG); + val = wm97xx_reg_read(wm, AC97_EXTENDED_MID); + wm97xx_reg_write(wm, AC97_EXTENDED_MID, val | 0x8000); + } +} + +static void wm9713_dig_restore(struct wm97xx *wm) +{ + wm97xx_reg_write(wm, AC97_WM9713_DIG1, wm->dig_save[0]); + wm97xx_reg_write(wm, AC97_WM9713_DIG2, wm->dig_save[1]); + wm97xx_reg_write(wm, AC97_WM9713_DIG3, wm->dig_save[2]); +} + +static void wm9713_aux_prepare(struct wm97xx *wm) +{ + memcpy(wm->dig_save, wm->dig, sizeof(wm->dig)); + wm97xx_reg_write(wm, AC97_WM9713_DIG1, 0); + wm97xx_reg_write(wm, AC97_WM9713_DIG2, 0); + wm97xx_reg_write(wm, AC97_WM9713_DIG3, WM97XX_PRP_DET_DIG); +} + +static inline int is_pden(struct wm97xx *wm) +{ + return wm->dig[2] & WM9713_PDEN; +} + +/* + * Read a sample from the WM9713 adc in polling mode. + */ +static int wm9713_poll_sample(struct wm97xx *wm, int adcsel, int *sample) +{ + u16 dig1; + int timeout = 5 * delay; + + if (!wm->pen_probably_down) { + u16 data = wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD); + if (!(data & WM97XX_PEN_DOWN)) + return RC_PENUP; + wm->pen_probably_down = 1; + } + + /* set up digitiser */ + if (adcsel & 0x8000) + adcsel = 1 << ((adcsel & 0x7fff) + 3); + + dig1 = wm97xx_reg_read(wm, AC97_WM9713_DIG1); + dig1 &= ~WM9713_ADCSEL_MASK; + + if (wm->mach_ops && wm->mach_ops->pre_sample) + wm->mach_ops->pre_sample(adcsel); + wm97xx_reg_write(wm, AC97_WM9713_DIG1, dig1 | adcsel | WM9713_POLL); + + /* wait 3 AC97 time slots + delay for conversion */ + poll_delay(delay); + + /* wait for POLL to go low */ + while ((wm97xx_reg_read(wm, AC97_WM9713_DIG1) & WM9713_POLL) && + timeout) { + udelay(AC97_LINK_FRAME); + timeout--; + } + + if (timeout <= 0) { + /* If PDEN is set, we can get a timeout when pen goes up */ + if (is_pden(wm)) + wm->pen_probably_down = 0; + else + dev_dbg(wm->dev, "adc sample timeout"); + return RC_PENUP; + } + + *sample = wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD); + if (wm->mach_ops && wm->mach_ops->post_sample) + wm->mach_ops->post_sample(adcsel); + + /* check we have correct sample */ + if ((*sample & WM97XX_ADCSRC_MASK) != ffs(adcsel >> 1) << 12) { + dev_dbg(wm->dev, "adc wrong sample, read %x got %x", adcsel, + *sample & WM97XX_ADCSRC_MASK); + return RC_PENUP; + } + + if (!(*sample & WM97XX_PEN_DOWN)) { + wm->pen_probably_down = 0; + return RC_PENUP; + } + + return RC_VALID; +} + +/* + * Read a coordinate from the WM9713 adc in polling mode. + */ +static int wm9713_poll_coord(struct wm97xx *wm, struct wm97xx_data *data) +{ + u16 dig1; + int timeout = 5 * delay; + + if (!wm->pen_probably_down) { + u16 val = wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD); + if (!(val & WM97XX_PEN_DOWN)) + return RC_PENUP; + wm->pen_probably_down = 1; + } + + /* set up digitiser */ + dig1 = wm97xx_reg_read(wm, AC97_WM9713_DIG1); + dig1 &= ~WM9713_ADCSEL_MASK; + if (pil) + dig1 |= WM9713_ADCSEL_PRES; + + if (wm->mach_ops && wm->mach_ops->pre_sample) + wm->mach_ops->pre_sample(WM97XX_ADCSEL_X | WM97XX_ADCSEL_Y); + wm97xx_reg_write(wm, AC97_WM9713_DIG1, + dig1 | WM9713_POLL | WM9713_COO); + + /* wait 3 AC97 time slots + delay for conversion */ + poll_delay(delay); + data->x = wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD); + /* wait for POLL to go low */ + while ((wm97xx_reg_read(wm, AC97_WM9713_DIG1) & WM9713_POLL) + && timeout) { + udelay(AC97_LINK_FRAME); + timeout--; + } + + if (timeout <= 0) { + /* If PDEN is set, we can get a timeout when pen goes up */ + if (is_pden(wm)) + wm->pen_probably_down = 0; + else + dev_dbg(wm->dev, "adc sample timeout"); + return RC_PENUP; + } + + /* read back data */ + data->y = wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD); + if (pil) + data->p = wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD); + else + data->p = DEFAULT_PRESSURE; + + if (wm->mach_ops && wm->mach_ops->post_sample) + wm->mach_ops->post_sample(WM97XX_ADCSEL_X | WM97XX_ADCSEL_Y); + + /* check we have correct sample */ + if (!(data->x & WM97XX_ADCSEL_X) || !(data->y & WM97XX_ADCSEL_Y)) + goto err; + if (pil && !(data->p & WM97XX_ADCSEL_PRES)) + goto err; + + if (!(data->x & WM97XX_PEN_DOWN) || !(data->y & WM97XX_PEN_DOWN)) { + wm->pen_probably_down = 0; + return RC_PENUP; + } + return RC_VALID; +err: + return 0; +} + +/* + * Sample the WM9713 touchscreen in polling mode + */ +static int wm9713_poll_touch(struct wm97xx *wm, struct wm97xx_data *data) +{ + int rc; + + if (coord) { + rc = wm9713_poll_coord(wm, data); + if (rc != RC_VALID) + return rc; + } else { + rc = wm9713_poll_sample(wm, WM9713_ADCSEL_X, &data->x); + if (rc != RC_VALID) + return rc; + rc = wm9713_poll_sample(wm, WM9713_ADCSEL_Y, &data->y); + if (rc != RC_VALID) + return rc; + if (pil) { + rc = wm9713_poll_sample(wm, WM9713_ADCSEL_PRES, + &data->p); + if (rc != RC_VALID) + return rc; + } else + data->p = DEFAULT_PRESSURE; + } + return RC_VALID; +} + +/* + * Enable WM9713 continuous mode, i.e. touch data is streamed across + * an AC97 slot + */ +static int wm9713_acc_enable(struct wm97xx *wm, int enable) +{ + u16 dig1, dig2, dig3; + int ret = 0; + + dig1 = wm->dig[0]; + dig2 = wm->dig[1]; + dig3 = wm->dig[2]; + + if (enable) { + /* continous mode */ + if (wm->mach_ops->acc_startup && + (ret = wm->mach_ops->acc_startup(wm)) < 0) + return ret; + + dig1 &= ~WM9713_ADCSEL_MASK; + dig1 |= WM9713_CTC | WM9713_COO | WM9713_ADCSEL_X | + WM9713_ADCSEL_Y; + if (pil) + dig1 |= WM9713_ADCSEL_PRES; + dig2 &= ~(WM97XX_DELAY_MASK | WM97XX_SLT_MASK | + WM97XX_CM_RATE_MASK); + dig2 |= WM97XX_SLEN | WM97XX_DELAY(delay) | + WM97XX_SLT(wm->acc_slot) | WM97XX_RATE(wm->acc_rate); + dig3 |= WM9713_PDEN; + } else { + dig1 &= ~(WM9713_CTC | WM9713_COO); + dig2 &= ~WM97XX_SLEN; + dig3 &= ~WM9713_PDEN; + if (wm->mach_ops->acc_shutdown) + wm->mach_ops->acc_shutdown(wm); + } + + wm97xx_reg_write(wm, AC97_WM9713_DIG1, dig1); + wm97xx_reg_write(wm, AC97_WM9713_DIG2, dig2); + wm97xx_reg_write(wm, AC97_WM9713_DIG3, dig3); + + return ret; +} + +struct wm97xx_codec_drv wm9713_codec = { + .id = WM9713_ID2, + .name = "wm9713", + .poll_sample = wm9713_poll_sample, + .poll_touch = wm9713_poll_touch, + .acc_enable = wm9713_acc_enable, + .phy_init = wm9713_phy_init, + .dig_enable = wm9713_dig_enable, + .dig_restore = wm9713_dig_restore, + .aux_prepare = wm9713_aux_prepare, +}; +EXPORT_SYMBOL_GPL(wm9713_codec); + +/* Module information */ +MODULE_AUTHOR("Liam Girdwood <lrg@slimlogic.co.uk>"); +MODULE_DESCRIPTION("WM9713 Touch Screen Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/touchscreen/wm97xx-core.c b/drivers/input/touchscreen/wm97xx-core.c new file mode 100644 index 0000000..d15aa11 --- /dev/null +++ b/drivers/input/touchscreen/wm97xx-core.c @@ -0,0 +1,828 @@ +/* + * wm97xx-core.c -- Touch screen driver core for Wolfson WM9705, WM9712 + * and WM9713 AC97 Codecs. + * + * Copyright 2003, 2004, 2005, 2006, 2007, 2008 Wolfson Microelectronics PLC. + * Author: Liam Girdwood <lrg@slimlogic.co.uk> + * Parts Copyright : Ian Molton <spyro@f2s.com> + * Andrew Zabolotny <zap@homelink.ru> + * Russell King <rmk@arm.linux.org.uk> + * + * 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. + * + * Notes: + * + * Features: + * - supports WM9705, WM9712, WM9713 + * - polling mode + * - continuous mode (arch-dependent) + * - adjustable rpu/dpp settings + * - adjustable pressure current + * - adjustable sample settle delay + * - 4 and 5 wire touchscreens (5 wire is WM9712 only) + * - pen down detection + * - battery monitor + * - sample AUX adcs + * - power management + * - codec GPIO + * - codec event notification + * Todo + * - Support for async sampling control for noisy LCDs. + * + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/string.h> +#include <linux/proc_fs.h> +#include <linux/pm.h> +#include <linux/interrupt.h> +#include <linux/bitops.h> +#include <linux/workqueue.h> +#include <linux/wm97xx.h> +#include <linux/uaccess.h> +#include <linux/io.h> + +#define TS_NAME "wm97xx" +#define WM_CORE_VERSION "1.00" +#define DEFAULT_PRESSURE 0xb0c0 + + +/* + * Touchscreen absolute values + * + * These parameters are used to help the input layer discard out of + * range readings and reduce jitter etc. + * + * o min, max:- indicate the min and max values your touch screen returns + * o fuzz:- use a higher number to reduce jitter + * + * The default values correspond to Mainstone II in QVGA mode + * + * Please read + * Documentation/input/input-programming.txt for more details. + */ + +static int abs_x[3] = {350, 3900, 5}; +module_param_array(abs_x, int, NULL, 0); +MODULE_PARM_DESC(abs_x, "Touchscreen absolute X min, max, fuzz"); + +static int abs_y[3] = {320, 3750, 40}; +module_param_array(abs_y, int, NULL, 0); +MODULE_PARM_DESC(abs_y, "Touchscreen absolute Y min, max, fuzz"); + +static int abs_p[3] = {0, 150, 4}; +module_param_array(abs_p, int, NULL, 0); +MODULE_PARM_DESC(abs_p, "Touchscreen absolute Pressure min, max, fuzz"); + +/* + * wm97xx IO access, all IO locking done by AC97 layer + */ +int wm97xx_reg_read(struct wm97xx *wm, u16 reg) +{ + if (wm->ac97) + return wm->ac97->bus->ops->read(wm->ac97, reg); + else + return -1; +} +EXPORT_SYMBOL_GPL(wm97xx_reg_read); + +void wm97xx_reg_write(struct wm97xx *wm, u16 reg, u16 val) +{ + /* cache digitiser registers */ + if (reg >= AC97_WM9713_DIG1 && reg <= AC97_WM9713_DIG3) + wm->dig[(reg - AC97_WM9713_DIG1) >> 1] = val; + + /* cache gpio regs */ + if (reg >= AC97_GPIO_CFG && reg <= AC97_MISC_AFE) + wm->gpio[(reg - AC97_GPIO_CFG) >> 1] = val; + + /* wm9713 irq reg */ + if (reg == 0x5a) + wm->misc = val; + + if (wm->ac97) + wm->ac97->bus->ops->write(wm->ac97, reg, val); +} +EXPORT_SYMBOL_GPL(wm97xx_reg_write); + +/** + * wm97xx_read_aux_adc - Read the aux adc. + * @wm: wm97xx device. + * @adcsel: codec ADC to be read + * + * Reads the selected AUX ADC. + */ + +int wm97xx_read_aux_adc(struct wm97xx *wm, u16 adcsel) +{ + int power_adc = 0, auxval; + u16 power = 0; + + /* get codec */ + mutex_lock(&wm->codec_mutex); + + /* When the touchscreen is not in use, we may have to power up + * the AUX ADC before we can use sample the AUX inputs-> + */ + if (wm->id == WM9713_ID2 && + (power = wm97xx_reg_read(wm, AC97_EXTENDED_MID)) & 0x8000) { + power_adc = 1; + wm97xx_reg_write(wm, AC97_EXTENDED_MID, power & 0x7fff); + } + + /* Prepare the codec for AUX reading */ + wm->codec->aux_prepare(wm); + + /* Turn polling mode on to read AUX ADC */ + wm->pen_probably_down = 1; + wm->codec->poll_sample(wm, adcsel, &auxval); + + if (power_adc) + wm97xx_reg_write(wm, AC97_EXTENDED_MID, power | 0x8000); + + wm->codec->dig_restore(wm); + + wm->pen_probably_down = 0; + + mutex_unlock(&wm->codec_mutex); + return auxval & 0xfff; +} +EXPORT_SYMBOL_GPL(wm97xx_read_aux_adc); + +/** + * wm97xx_get_gpio - Get the status of a codec GPIO. + * @wm: wm97xx device. + * @gpio: gpio + * + * Get the status of a codec GPIO pin + */ + +enum wm97xx_gpio_status wm97xx_get_gpio(struct wm97xx *wm, u32 gpio) +{ + u16 status; + enum wm97xx_gpio_status ret; + + mutex_lock(&wm->codec_mutex); + status = wm97xx_reg_read(wm, AC97_GPIO_STATUS); + + if (status & gpio) + ret = WM97XX_GPIO_HIGH; + else + ret = WM97XX_GPIO_LOW; + + mutex_unlock(&wm->codec_mutex); + return ret; +} +EXPORT_SYMBOL_GPL(wm97xx_get_gpio); + +/** + * wm97xx_set_gpio - Set the status of a codec GPIO. + * @wm: wm97xx device. + * @gpio: gpio + * + * + * Set the status of a codec GPIO pin + */ + +void wm97xx_set_gpio(struct wm97xx *wm, u32 gpio, + enum wm97xx_gpio_status status) +{ + u16 reg; + + mutex_lock(&wm->codec_mutex); + reg = wm97xx_reg_read(wm, AC97_GPIO_STATUS); + + if (status & WM97XX_GPIO_HIGH) + reg |= gpio; + else + reg &= ~gpio; + + if (wm->id == WM9712_ID2) + wm97xx_reg_write(wm, AC97_GPIO_STATUS, reg << 1); + else + wm97xx_reg_write(wm, AC97_GPIO_STATUS, reg); + mutex_unlock(&wm->codec_mutex); +} +EXPORT_SYMBOL_GPL(wm97xx_set_gpio); + +/* + * Codec GPIO pin configuration, this sets pin direction, polarity, + * stickyness and wake up. + */ +void wm97xx_config_gpio(struct wm97xx *wm, u32 gpio, enum wm97xx_gpio_dir dir, + enum wm97xx_gpio_pol pol, enum wm97xx_gpio_sticky sticky, + enum wm97xx_gpio_wake wake) +{ + u16 reg; + + mutex_lock(&wm->codec_mutex); + reg = wm97xx_reg_read(wm, AC97_GPIO_POLARITY); + + if (pol == WM97XX_GPIO_POL_HIGH) + reg |= gpio; + else + reg &= ~gpio; + + wm97xx_reg_write(wm, AC97_GPIO_POLARITY, reg); + reg = wm97xx_reg_read(wm, AC97_GPIO_STICKY); + + if (sticky == WM97XX_GPIO_STICKY) + reg |= gpio; + else + reg &= ~gpio; + + wm97xx_reg_write(wm, AC97_GPIO_STICKY, reg); + reg = wm97xx_reg_read(wm, AC97_GPIO_WAKEUP); + + if (wake == WM97XX_GPIO_WAKE) + reg |= gpio; + else + reg &= ~gpio; + + wm97xx_reg_write(wm, AC97_GPIO_WAKEUP, reg); + reg = wm97xx_reg_read(wm, AC97_GPIO_CFG); + + if (dir == WM97XX_GPIO_IN) + reg |= gpio; + else + reg &= ~gpio; + + wm97xx_reg_write(wm, AC97_GPIO_CFG, reg); + mutex_unlock(&wm->codec_mutex); +} +EXPORT_SYMBOL_GPL(wm97xx_config_gpio); + +/* + * Configure the WM97XX_PRP value to use while system is suspended. + * If a value other than 0 is set then WM97xx pen detection will be + * left enabled in the configured mode while the system is in suspend, + * the device has users and suspend has not been disabled via the + * wakeup sysfs entries. + * + * @wm: WM97xx device to configure + * @mode: WM97XX_PRP value to configure while suspended + */ +void wm97xx_set_suspend_mode(struct wm97xx *wm, u16 mode) +{ + wm->suspend_mode = mode; + device_init_wakeup(&wm->input_dev->dev, mode != 0); +} +EXPORT_SYMBOL_GPL(wm97xx_set_suspend_mode); + +/* + * Handle a pen down interrupt. + */ +static void wm97xx_pen_irq_worker(struct work_struct *work) +{ + struct wm97xx *wm = container_of(work, struct wm97xx, pen_event_work); + int pen_was_down = wm->pen_is_down; + + /* do we need to enable the touch panel reader */ + if (wm->id == WM9705_ID2) { + if (wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD) & + WM97XX_PEN_DOWN) + wm->pen_is_down = 1; + else + wm->pen_is_down = 0; + } else { + u16 status, pol; + mutex_lock(&wm->codec_mutex); + status = wm97xx_reg_read(wm, AC97_GPIO_STATUS); + pol = wm97xx_reg_read(wm, AC97_GPIO_POLARITY); + + if (WM97XX_GPIO_13 & pol & status) { + wm->pen_is_down = 1; + wm97xx_reg_write(wm, AC97_GPIO_POLARITY, pol & + ~WM97XX_GPIO_13); + } else { + wm->pen_is_down = 0; + wm97xx_reg_write(wm, AC97_GPIO_POLARITY, pol | + WM97XX_GPIO_13); + } + + if (wm->id == WM9712_ID2) + wm97xx_reg_write(wm, AC97_GPIO_STATUS, (status & + ~WM97XX_GPIO_13) << 1); + else + wm97xx_reg_write(wm, AC97_GPIO_STATUS, status & + ~WM97XX_GPIO_13); + mutex_unlock(&wm->codec_mutex); + } + + /* If the system is not using continuous mode or it provides a + * pen down operation then we need to schedule polls while the + * pen is down. Otherwise the machine driver is responsible + * for scheduling reads. + */ + if (!wm->mach_ops->acc_enabled || wm->mach_ops->acc_pen_down) { + if (wm->pen_is_down && !pen_was_down) { + /* Data is not availiable immediately on pen down */ + queue_delayed_work(wm->ts_workq, &wm->ts_reader, 1); + } + + /* Let ts_reader report the pen up for debounce. */ + if (!wm->pen_is_down && pen_was_down) + wm->pen_is_down = 1; + } + + if (!wm->pen_is_down && wm->mach_ops->acc_enabled) + wm->mach_ops->acc_pen_up(wm); + + wm->mach_ops->irq_enable(wm, 1); +} + +/* + * Codec PENDOWN irq handler + * + * We have to disable the codec interrupt in the handler because it + * can take upto 1ms to clear the interrupt source. We schedule a task + * in a work queue to do the actual interaction with the chip. The + * interrupt is then enabled again in the slow handler when the source + * has been cleared. + */ +static irqreturn_t wm97xx_pen_interrupt(int irq, void *dev_id) +{ + struct wm97xx *wm = dev_id; + + if (!work_pending(&wm->pen_event_work)) { + wm->mach_ops->irq_enable(wm, 0); + queue_work(wm->ts_workq, &wm->pen_event_work); + } + + return IRQ_HANDLED; +} + +/* + * initialise pen IRQ handler and workqueue + */ +static int wm97xx_init_pen_irq(struct wm97xx *wm) +{ + u16 reg; + + /* If an interrupt is supplied an IRQ enable operation must also be + * provided. */ + BUG_ON(!wm->mach_ops->irq_enable); + + if (request_irq(wm->pen_irq, wm97xx_pen_interrupt, + IRQF_SHARED | IRQF_SAMPLE_RANDOM, + "wm97xx-pen", wm)) { + dev_err(wm->dev, + "Failed to register pen down interrupt, polling"); + wm->pen_irq = 0; + return -EINVAL; + } + + /* Configure GPIO as interrupt source on WM971x */ + if (wm->id != WM9705_ID2) { + BUG_ON(!wm->mach_ops->irq_gpio); + reg = wm97xx_reg_read(wm, AC97_MISC_AFE); + wm97xx_reg_write(wm, AC97_MISC_AFE, + reg & ~(wm->mach_ops->irq_gpio)); + reg = wm97xx_reg_read(wm, 0x5a); + wm97xx_reg_write(wm, 0x5a, reg & ~0x0001); + } + + return 0; +} + +static int wm97xx_read_samples(struct wm97xx *wm) +{ + struct wm97xx_data data; + int rc; + + mutex_lock(&wm->codec_mutex); + + if (wm->mach_ops && wm->mach_ops->acc_enabled) + rc = wm->mach_ops->acc_pen_down(wm); + else + rc = wm->codec->poll_touch(wm, &data); + + if (rc & RC_PENUP) { + if (wm->pen_is_down) { + wm->pen_is_down = 0; + dev_dbg(wm->dev, "pen up\n"); + input_report_abs(wm->input_dev, ABS_PRESSURE, 0); + input_sync(wm->input_dev); + } else if (!(rc & RC_AGAIN)) { + /* We need high frequency updates only while + * pen is down, the user never will be able to + * touch screen faster than a few times per + * second... On the other hand, when the user + * is actively working with the touchscreen we + * don't want to lose the quick response. So we + * will slowly increase sleep time after the + * pen is up and quicky restore it to ~one task + * switch when pen is down again. + */ + if (wm->ts_reader_interval < HZ / 10) + wm->ts_reader_interval++; + } + + } else if (rc & RC_VALID) { + dev_dbg(wm->dev, + "pen down: x=%x:%d, y=%x:%d, pressure=%x:%d\n", + data.x >> 12, data.x & 0xfff, data.y >> 12, + data.y & 0xfff, data.p >> 12, data.p & 0xfff); + input_report_abs(wm->input_dev, ABS_X, data.x & 0xfff); + input_report_abs(wm->input_dev, ABS_Y, data.y & 0xfff); + input_report_abs(wm->input_dev, ABS_PRESSURE, data.p & 0xfff); + input_sync(wm->input_dev); + wm->pen_is_down = 1; + wm->ts_reader_interval = wm->ts_reader_min_interval; + } else if (rc & RC_PENDOWN) { + dev_dbg(wm->dev, "pen down\n"); + wm->pen_is_down = 1; + wm->ts_reader_interval = wm->ts_reader_min_interval; + } + + mutex_unlock(&wm->codec_mutex); + return rc; +} + +/* +* The touchscreen sample reader. +*/ +static void wm97xx_ts_reader(struct work_struct *work) +{ + int rc; + struct wm97xx *wm = container_of(work, struct wm97xx, ts_reader.work); + + BUG_ON(!wm->codec); + + do { + rc = wm97xx_read_samples(wm); + } while (rc & RC_AGAIN); + + if (wm->pen_is_down || !wm->pen_irq) + queue_delayed_work(wm->ts_workq, &wm->ts_reader, + wm->ts_reader_interval); +} + +/** + * wm97xx_ts_input_open - Open the touch screen input device. + * @idev: Input device to be opened. + * + * Called by the input sub system to open a wm97xx touchscreen device. + * Starts the touchscreen thread and touch digitiser. + */ +static int wm97xx_ts_input_open(struct input_dev *idev) +{ + struct wm97xx *wm = input_get_drvdata(idev); + + wm->ts_workq = create_singlethread_workqueue("kwm97xx"); + if (wm->ts_workq == NULL) { + dev_err(wm->dev, + "Failed to create workqueue\n"); + return -EINVAL; + } + + /* start digitiser */ + if (wm->mach_ops && wm->mach_ops->acc_enabled) + wm->codec->acc_enable(wm, 1); + wm->codec->dig_enable(wm, 1); + + INIT_DELAYED_WORK(&wm->ts_reader, wm97xx_ts_reader); + INIT_WORK(&wm->pen_event_work, wm97xx_pen_irq_worker); + + wm->ts_reader_min_interval = HZ >= 100 ? HZ / 100 : 1; + if (wm->ts_reader_min_interval < 1) + wm->ts_reader_min_interval = 1; + wm->ts_reader_interval = wm->ts_reader_min_interval; + + wm->pen_is_down = 0; + if (wm->pen_irq) + wm97xx_init_pen_irq(wm); + else + dev_err(wm->dev, "No IRQ specified\n"); + + /* If we either don't have an interrupt for pen down events or + * failed to acquire it then we need to poll. + */ + if (wm->pen_irq == 0) + queue_delayed_work(wm->ts_workq, &wm->ts_reader, + wm->ts_reader_interval); + + return 0; +} + +/** + * wm97xx_ts_input_close - Close the touch screen input device. + * @idev: Input device to be closed. + * + * Called by the input sub system to close a wm97xx touchscreen + * device. Kills the touchscreen thread and stops the touch + * digitiser. + */ + +static void wm97xx_ts_input_close(struct input_dev *idev) +{ + struct wm97xx *wm = input_get_drvdata(idev); + u16 reg; + + if (wm->pen_irq) { + /* Return the interrupt to GPIO usage (disabling it) */ + if (wm->id != WM9705_ID2) { + BUG_ON(!wm->mach_ops->irq_gpio); + reg = wm97xx_reg_read(wm, AC97_MISC_AFE); + wm97xx_reg_write(wm, AC97_MISC_AFE, + reg | wm->mach_ops->irq_gpio); + } + + free_irq(wm->pen_irq, wm); + } + + wm->pen_is_down = 0; + + /* Balance out interrupt disables/enables */ + if (cancel_work_sync(&wm->pen_event_work)) + wm->mach_ops->irq_enable(wm, 1); + + /* ts_reader rearms itself so we need to explicitly stop it + * before we destroy the workqueue. + */ + cancel_delayed_work_sync(&wm->ts_reader); + + destroy_workqueue(wm->ts_workq); + + /* stop digitiser */ + wm->codec->dig_enable(wm, 0); + if (wm->mach_ops && wm->mach_ops->acc_enabled) + wm->codec->acc_enable(wm, 0); +} + +static int wm97xx_probe(struct device *dev) +{ + struct wm97xx *wm; + int ret = 0, id = 0; + + wm = kzalloc(sizeof(struct wm97xx), GFP_KERNEL); + if (!wm) + return -ENOMEM; + mutex_init(&wm->codec_mutex); + + wm->dev = dev; + dev->driver_data = wm; + wm->ac97 = to_ac97_t(dev); + + /* check that we have a supported codec */ + id = wm97xx_reg_read(wm, AC97_VENDOR_ID1); + if (id != WM97XX_ID1) { + dev_err(dev, "Device with vendor %04x is not a wm97xx\n", id); + ret = -ENODEV; + goto alloc_err; + } + + wm->id = wm97xx_reg_read(wm, AC97_VENDOR_ID2); + + dev_info(wm->dev, "detected a wm97%02x codec\n", wm->id & 0xff); + + switch (wm->id & 0xff) { +#ifdef CONFIG_TOUCHSCREEN_WM9705 + case 0x05: + wm->codec = &wm9705_codec; + break; +#endif +#ifdef CONFIG_TOUCHSCREEN_WM9712 + case 0x12: + wm->codec = &wm9712_codec; + break; +#endif +#ifdef CONFIG_TOUCHSCREEN_WM9713 + case 0x13: + wm->codec = &wm9713_codec; + break; +#endif + default: + dev_err(wm->dev, "Support for wm97%02x not compiled in.\n", + wm->id & 0xff); + ret = -ENODEV; + goto alloc_err; + } + + /* set up physical characteristics */ + wm->codec->phy_init(wm); + + /* load gpio cache */ + wm->gpio[0] = wm97xx_reg_read(wm, AC97_GPIO_CFG); + wm->gpio[1] = wm97xx_reg_read(wm, AC97_GPIO_POLARITY); + wm->gpio[2] = wm97xx_reg_read(wm, AC97_GPIO_STICKY); + wm->gpio[3] = wm97xx_reg_read(wm, AC97_GPIO_WAKEUP); + wm->gpio[4] = wm97xx_reg_read(wm, AC97_GPIO_STATUS); + wm->gpio[5] = wm97xx_reg_read(wm, AC97_MISC_AFE); + + wm->input_dev = input_allocate_device(); + if (wm->input_dev == NULL) { + ret = -ENOMEM; + goto alloc_err; + } + + /* set up touch configuration */ + wm->input_dev->name = "wm97xx touchscreen"; + wm->input_dev->phys = "wm97xx"; + wm->input_dev->open = wm97xx_ts_input_open; + wm->input_dev->close = wm97xx_ts_input_close; + set_bit(EV_ABS, wm->input_dev->evbit); + set_bit(ABS_X, wm->input_dev->absbit); + set_bit(ABS_Y, wm->input_dev->absbit); + set_bit(ABS_PRESSURE, wm->input_dev->absbit); + input_set_abs_params(wm->input_dev, ABS_X, abs_x[0], abs_x[1], + abs_x[2], 0); + input_set_abs_params(wm->input_dev, ABS_Y, abs_y[0], abs_y[1], + abs_y[2], 0); + input_set_abs_params(wm->input_dev, ABS_PRESSURE, abs_p[0], abs_p[1], + abs_p[2], 0); + input_set_drvdata(wm->input_dev, wm); + wm->input_dev->dev.parent = dev; + ret = input_register_device(wm->input_dev); + if (ret < 0) + goto dev_alloc_err; + + /* register our battery device */ + wm->battery_dev = platform_device_alloc("wm97xx-battery", -1); + if (!wm->battery_dev) { + ret = -ENOMEM; + goto batt_err; + } + platform_set_drvdata(wm->battery_dev, wm); + wm->battery_dev->dev.parent = dev; + ret = platform_device_add(wm->battery_dev); + if (ret < 0) + goto batt_reg_err; + + /* register our extended touch device (for machine specific + * extensions) */ + wm->touch_dev = platform_device_alloc("wm97xx-touch", -1); + if (!wm->touch_dev) { + ret = -ENOMEM; + goto touch_err; + } + platform_set_drvdata(wm->touch_dev, wm); + wm->touch_dev->dev.parent = dev; + ret = platform_device_add(wm->touch_dev); + if (ret < 0) + goto touch_reg_err; + + return ret; + + touch_reg_err: + platform_device_put(wm->touch_dev); + touch_err: + platform_device_unregister(wm->battery_dev); + wm->battery_dev = NULL; + batt_reg_err: + platform_device_put(wm->battery_dev); + batt_err: + input_unregister_device(wm->input_dev); + wm->input_dev = NULL; + dev_alloc_err: + input_free_device(wm->input_dev); + alloc_err: + kfree(wm); + + return ret; +} + +static int wm97xx_remove(struct device *dev) +{ + struct wm97xx *wm = dev_get_drvdata(dev); + + platform_device_unregister(wm->battery_dev); + platform_device_unregister(wm->touch_dev); + input_unregister_device(wm->input_dev); + kfree(wm); + + return 0; +} + +#ifdef CONFIG_PM +static int wm97xx_suspend(struct device *dev, pm_message_t state) +{ + struct wm97xx *wm = dev_get_drvdata(dev); + u16 reg; + int suspend_mode; + + if (device_may_wakeup(&wm->input_dev->dev)) + suspend_mode = wm->suspend_mode; + else + suspend_mode = 0; + + if (wm->input_dev->users) + cancel_delayed_work_sync(&wm->ts_reader); + + /* Power down the digitiser (bypassing the cache for resume) */ + reg = wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER2); + reg &= ~WM97XX_PRP_DET_DIG; + if (wm->input_dev->users) + reg |= suspend_mode; + wm->ac97->bus->ops->write(wm->ac97, AC97_WM97XX_DIGITISER2, reg); + + /* WM9713 has an additional power bit - turn it off if there + * are no users or if suspend mode is zero. */ + if (wm->id == WM9713_ID2 && + (!wm->input_dev->users || !suspend_mode)) { + reg = wm97xx_reg_read(wm, AC97_EXTENDED_MID) | 0x8000; + wm97xx_reg_write(wm, AC97_EXTENDED_MID, reg); + } + + return 0; +} + +static int wm97xx_resume(struct device *dev) +{ + struct wm97xx *wm = dev_get_drvdata(dev); + + /* restore digitiser and gpios */ + if (wm->id == WM9713_ID2) { + wm97xx_reg_write(wm, AC97_WM9713_DIG1, wm->dig[0]); + wm97xx_reg_write(wm, 0x5a, wm->misc); + if (wm->input_dev->users) { + u16 reg; + reg = wm97xx_reg_read(wm, AC97_EXTENDED_MID) & 0x7fff; + wm97xx_reg_write(wm, AC97_EXTENDED_MID, reg); + } + } + + wm97xx_reg_write(wm, AC97_WM9713_DIG2, wm->dig[1]); + wm97xx_reg_write(wm, AC97_WM9713_DIG3, wm->dig[2]); + + wm97xx_reg_write(wm, AC97_GPIO_CFG, wm->gpio[0]); + wm97xx_reg_write(wm, AC97_GPIO_POLARITY, wm->gpio[1]); + wm97xx_reg_write(wm, AC97_GPIO_STICKY, wm->gpio[2]); + wm97xx_reg_write(wm, AC97_GPIO_WAKEUP, wm->gpio[3]); + wm97xx_reg_write(wm, AC97_GPIO_STATUS, wm->gpio[4]); + wm97xx_reg_write(wm, AC97_MISC_AFE, wm->gpio[5]); + + if (wm->input_dev->users && !wm->pen_irq) { + wm->ts_reader_interval = wm->ts_reader_min_interval; + queue_delayed_work(wm->ts_workq, &wm->ts_reader, + wm->ts_reader_interval); + } + + return 0; +} + +#else +#define wm97xx_suspend NULL +#define wm97xx_resume NULL +#endif + +/* + * Machine specific operations + */ +int wm97xx_register_mach_ops(struct wm97xx *wm, + struct wm97xx_mach_ops *mach_ops) +{ + mutex_lock(&wm->codec_mutex); + if (wm->mach_ops) { + mutex_unlock(&wm->codec_mutex); + return -EINVAL; + } + wm->mach_ops = mach_ops; + mutex_unlock(&wm->codec_mutex); + + return 0; +} +EXPORT_SYMBOL_GPL(wm97xx_register_mach_ops); + +void wm97xx_unregister_mach_ops(struct wm97xx *wm) +{ + mutex_lock(&wm->codec_mutex); + wm->mach_ops = NULL; + mutex_unlock(&wm->codec_mutex); +} +EXPORT_SYMBOL_GPL(wm97xx_unregister_mach_ops); + +static struct device_driver wm97xx_driver = { + .name = "wm97xx-ts", + .bus = &ac97_bus_type, + .owner = THIS_MODULE, + .probe = wm97xx_probe, + .remove = wm97xx_remove, + .suspend = wm97xx_suspend, + .resume = wm97xx_resume, +}; + +static int __init wm97xx_init(void) +{ + return driver_register(&wm97xx_driver); +} + +static void __exit wm97xx_exit(void) +{ + driver_unregister(&wm97xx_driver); +} + +module_init(wm97xx_init); +module_exit(wm97xx_exit); + +/* Module information */ +MODULE_AUTHOR("Liam Girdwood <lrg@slimlogic.co.uk>"); +MODULE_DESCRIPTION("WM97xx Core - Touch Screen / AUX ADC / GPIO Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/xen-kbdfront.c b/drivers/input/xen-kbdfront.c new file mode 100644 index 0000000..928d2ed --- /dev/null +++ b/drivers/input/xen-kbdfront.c @@ -0,0 +1,358 @@ +/* + * Xen para-virtual input device + * + * Copyright (C) 2005 Anthony Liguori <aliguori@us.ibm.com> + * Copyright (C) 2006-2008 Red Hat, Inc., Markus Armbruster <armbru@redhat.com> + * + * Based on linux/drivers/input/mouse/sermouse.c + * + * 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: + * + * Switch to grant tables together with xen-fbfront.c. + */ + +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/module.h> +#include <linux/input.h> +#include <asm/xen/hypervisor.h> +#include <xen/events.h> +#include <xen/page.h> +#include <xen/interface/io/fbif.h> +#include <xen/interface/io/kbdif.h> +#include <xen/xenbus.h> + +struct xenkbd_info { + struct input_dev *kbd; + struct input_dev *ptr; + struct xenkbd_page *page; + int irq; + struct xenbus_device *xbdev; + char phys[32]; +}; + +static int xenkbd_remove(struct xenbus_device *); +static int xenkbd_connect_backend(struct xenbus_device *, struct xenkbd_info *); +static void xenkbd_disconnect_backend(struct xenkbd_info *); + +/* + * Note: if you need to send out events, see xenfb_do_update() for how + * to do that. + */ + +static irqreturn_t input_handler(int rq, void *dev_id) +{ + struct xenkbd_info *info = dev_id; + struct xenkbd_page *page = info->page; + __u32 cons, prod; + + prod = page->in_prod; + if (prod == page->in_cons) + return IRQ_HANDLED; + rmb(); /* ensure we see ring contents up to prod */ + for (cons = page->in_cons; cons != prod; cons++) { + union xenkbd_in_event *event; + struct input_dev *dev; + event = &XENKBD_IN_RING_REF(page, cons); + + dev = info->ptr; + switch (event->type) { + case XENKBD_TYPE_MOTION: + input_report_rel(dev, REL_X, event->motion.rel_x); + input_report_rel(dev, REL_Y, event->motion.rel_y); + if (event->motion.rel_z) + input_report_rel(dev, REL_WHEEL, + -event->motion.rel_z); + break; + case XENKBD_TYPE_KEY: + dev = NULL; + if (test_bit(event->key.keycode, info->kbd->keybit)) + dev = info->kbd; + if (test_bit(event->key.keycode, info->ptr->keybit)) + dev = info->ptr; + if (dev) + input_report_key(dev, event->key.keycode, + event->key.pressed); + else + printk(KERN_WARNING + "xenkbd: unhandled keycode 0x%x\n", + event->key.keycode); + break; + case XENKBD_TYPE_POS: + input_report_abs(dev, ABS_X, event->pos.abs_x); + input_report_abs(dev, ABS_Y, event->pos.abs_y); + if (event->pos.rel_z) + input_report_rel(dev, REL_WHEEL, + -event->pos.rel_z); + break; + } + if (dev) + input_sync(dev); + } + mb(); /* ensure we got ring contents */ + page->in_cons = cons; + notify_remote_via_irq(info->irq); + + return IRQ_HANDLED; +} + +static int __devinit xenkbd_probe(struct xenbus_device *dev, + const struct xenbus_device_id *id) +{ + int ret, i; + struct xenkbd_info *info; + struct input_dev *kbd, *ptr; + + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) { + xenbus_dev_fatal(dev, -ENOMEM, "allocating info structure"); + return -ENOMEM; + } + dev->dev.driver_data = info; + info->xbdev = dev; + info->irq = -1; + snprintf(info->phys, sizeof(info->phys), "xenbus/%s", dev->nodename); + + info->page = (void *)__get_free_page(GFP_KERNEL | __GFP_ZERO); + if (!info->page) + goto error_nomem; + + /* keyboard */ + kbd = input_allocate_device(); + if (!kbd) + goto error_nomem; + kbd->name = "Xen Virtual Keyboard"; + kbd->phys = info->phys; + kbd->id.bustype = BUS_PCI; + kbd->id.vendor = 0x5853; + kbd->id.product = 0xffff; + kbd->evbit[0] = BIT(EV_KEY); + for (i = KEY_ESC; i < KEY_UNKNOWN; i++) + set_bit(i, kbd->keybit); + for (i = KEY_OK; i < KEY_MAX; i++) + set_bit(i, kbd->keybit); + + ret = input_register_device(kbd); + if (ret) { + input_free_device(kbd); + xenbus_dev_fatal(dev, ret, "input_register_device(kbd)"); + goto error; + } + info->kbd = kbd; + + /* pointing device */ + ptr = input_allocate_device(); + if (!ptr) + goto error_nomem; + ptr->name = "Xen Virtual Pointer"; + ptr->phys = info->phys; + ptr->id.bustype = BUS_PCI; + ptr->id.vendor = 0x5853; + ptr->id.product = 0xfffe; + ptr->evbit[0] = BIT(EV_KEY) | BIT(EV_REL) | BIT(EV_ABS); + for (i = BTN_LEFT; i <= BTN_TASK; i++) + set_bit(i, ptr->keybit); + ptr->relbit[0] = BIT(REL_X) | BIT(REL_Y) | BIT(REL_WHEEL); + input_set_abs_params(ptr, ABS_X, 0, XENFB_WIDTH, 0, 0); + input_set_abs_params(ptr, ABS_Y, 0, XENFB_HEIGHT, 0, 0); + + ret = input_register_device(ptr); + if (ret) { + input_free_device(ptr); + xenbus_dev_fatal(dev, ret, "input_register_device(ptr)"); + goto error; + } + info->ptr = ptr; + + ret = xenkbd_connect_backend(dev, info); + if (ret < 0) + goto error; + + return 0; + + error_nomem: + ret = -ENOMEM; + xenbus_dev_fatal(dev, ret, "allocating device memory"); + error: + xenkbd_remove(dev); + return ret; +} + +static int xenkbd_resume(struct xenbus_device *dev) +{ + struct xenkbd_info *info = dev->dev.driver_data; + + xenkbd_disconnect_backend(info); + memset(info->page, 0, PAGE_SIZE); + return xenkbd_connect_backend(dev, info); +} + +static int xenkbd_remove(struct xenbus_device *dev) +{ + struct xenkbd_info *info = dev->dev.driver_data; + + xenkbd_disconnect_backend(info); + if (info->kbd) + input_unregister_device(info->kbd); + if (info->ptr) + input_unregister_device(info->ptr); + free_page((unsigned long)info->page); + kfree(info); + return 0; +} + +static int xenkbd_connect_backend(struct xenbus_device *dev, + struct xenkbd_info *info) +{ + int ret, evtchn; + struct xenbus_transaction xbt; + + ret = xenbus_alloc_evtchn(dev, &evtchn); + if (ret) + return ret; + ret = bind_evtchn_to_irqhandler(evtchn, input_handler, + 0, dev->devicetype, info); + if (ret < 0) { + xenbus_free_evtchn(dev, evtchn); + xenbus_dev_fatal(dev, ret, "bind_evtchn_to_irqhandler"); + return ret; + } + info->irq = ret; + + again: + ret = xenbus_transaction_start(&xbt); + if (ret) { + xenbus_dev_fatal(dev, ret, "starting transaction"); + return ret; + } + ret = xenbus_printf(xbt, dev->nodename, "page-ref", "%lu", + virt_to_mfn(info->page)); + if (ret) + goto error_xenbus; + ret = xenbus_printf(xbt, dev->nodename, "event-channel", "%u", + evtchn); + if (ret) + goto error_xenbus; + ret = xenbus_transaction_end(xbt, 0); + if (ret) { + if (ret == -EAGAIN) + goto again; + xenbus_dev_fatal(dev, ret, "completing transaction"); + return ret; + } + + xenbus_switch_state(dev, XenbusStateInitialised); + return 0; + + error_xenbus: + xenbus_transaction_end(xbt, 1); + xenbus_dev_fatal(dev, ret, "writing xenstore"); + return ret; +} + +static void xenkbd_disconnect_backend(struct xenkbd_info *info) +{ + if (info->irq >= 0) + unbind_from_irqhandler(info->irq, info); + info->irq = -1; +} + +static void xenkbd_backend_changed(struct xenbus_device *dev, + enum xenbus_state backend_state) +{ + struct xenkbd_info *info = dev->dev.driver_data; + int ret, val; + + switch (backend_state) { + case XenbusStateInitialising: + case XenbusStateInitialised: + case XenbusStateUnknown: + case XenbusStateClosed: + break; + + case XenbusStateInitWait: +InitWait: + ret = xenbus_scanf(XBT_NIL, info->xbdev->otherend, + "feature-abs-pointer", "%d", &val); + if (ret < 0) + val = 0; + if (val) { + ret = xenbus_printf(XBT_NIL, info->xbdev->nodename, + "request-abs-pointer", "1"); + if (ret) + printk(KERN_WARNING + "xenkbd: can't request abs-pointer"); + } + xenbus_switch_state(dev, XenbusStateConnected); + break; + + case XenbusStateConnected: + /* + * Work around xenbus race condition: If backend goes + * through InitWait to Connected fast enough, we can + * get Connected twice here. + */ + if (dev->state != XenbusStateConnected) + goto InitWait; /* no InitWait seen yet, fudge it */ + + /* Set input abs params to match backend screen res */ + if (xenbus_scanf(XBT_NIL, info->xbdev->otherend, + "width", "%d", &val) > 0) + input_set_abs_params(info->ptr, ABS_X, 0, val, 0, 0); + + if (xenbus_scanf(XBT_NIL, info->xbdev->otherend, + "height", "%d", &val) > 0) + input_set_abs_params(info->ptr, ABS_Y, 0, val, 0, 0); + + break; + + case XenbusStateClosing: + xenbus_frontend_closed(dev); + break; + } +} + +static struct xenbus_device_id xenkbd_ids[] = { + { "vkbd" }, + { "" } +}; + +static struct xenbus_driver xenkbd_driver = { + .name = "vkbd", + .owner = THIS_MODULE, + .ids = xenkbd_ids, + .probe = xenkbd_probe, + .remove = xenkbd_remove, + .resume = xenkbd_resume, + .otherend_changed = xenkbd_backend_changed, +}; + +static int __init xenkbd_init(void) +{ + if (!xen_domain()) + return -ENODEV; + + /* Nothing to do if running in dom0. */ + if (xen_initial_domain()) + return -ENODEV; + + return xenbus_register_frontend(&xenkbd_driver); +} + +static void __exit xenkbd_cleanup(void) +{ + xenbus_unregister_driver(&xenkbd_driver); +} + +module_init(xenkbd_init); +module_exit(xenkbd_cleanup); + +MODULE_DESCRIPTION("Xen virtual keyboard/pointer device frontend"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("xen:vkbd"); |