/*
* 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);
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_fasync(-1, file, 0);
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);