lkml.org 
[lkml]   [2006]   [Aug]   [15]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
Patch in this message
/
Date
From
Subject[PATCH] usb: Betop BTP-2118 joystick force-feedback driver

Changelogs:

1. the first release.

NOTE: This driver requires:
1. [PATCH] usb: The HID Simple Driver Interface 0.3.1 (core)
2. [PATCH] usb: HID Simple Driver Interface 0.3.1 (Kconfig and Makefile)

It also can apply on 2.6.17.6 and 2.6.17.8 at least.

PS: I have not Subscribe linux-usb-devel maillist, please CC me your reply, thanks.

Signed-off-by: Liyu <raise.sail@gmail.com>

diff -Naurp linux-2.6.17.7/drivers/usb/input.orig/btp2118.c linux-2.6.17.7/drivers/usb/input/btp2118.c
--- linux-2.6.17.7/drivers/usb/input.orig/btp2118.c 1970-01-01 08:00:00.000000000 +0800
+++ linux-2.6.17.7/drivers/usb/input/btp2118.c 2006-08-15 15:41:43.000000000 +0800
@@ -0,0 +1,438 @@
+/*
+ * Betop BTP-2118 joystick force-feedback driver
+ *
+ * Version: 0.1.0
+ *
+ * Copyright (c) 2006 Liyu <raise.sail@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.
+ */
+
+#include <linux/kernel.h>
+#include <linux/input.h>
+#include <linux/spinlock.h>
+#include <linux/timer.h>
+#include <linux/sched.h>
+#include "hid.h"
+#include "hid-simple.h"
+
+#define USB_ID_VENDOR 0x12bd
+#define USB_ID_PRODUCT 0xc003
+
+#define USAGE_PAGE_BTP_FF 0x008c0000 /* Bar Code Scanner ? */
+#define USAGE_BTP_FF 0x0002
+
+#define BTP_EFFECT_NONE (-1)
+
+/* usb_btp_info->flags list */
+#define BTP_DISCONNECTING 0
+#define BTP_URB_DONE 1
+#define BTP_BUSY 2 /* avoid to resend urb */
+
+#define IS_BTP_DISCONNECTING(info) (test_bit(BTP_DISCONNECTING, &(info)->flags))
+#define IS_BTP_URB_DONE(info) \
+ (test_bit(BTP_URB_DONE|BTP_DISCONNECTING, &(info)->flags))
+#define IS_BTP_BUSY(info) (test_bit(BTP_BUSY, &(info)->flags))
+
+#define BTP_SET_DISCONNECTING(info) (set_bit(BTP_DISCONNECTING, &(info)->flags))
+#define BTP_SET_URB_DONE(info) (set_bit(BTP_URB_DONE, &(info)->flags))
+#define BTP_SET_BUSY(info) (set_bit(BTP_BUSY, &(info)->flags))
+
+#define BTP_CLR_BUSY(info) (clear_bit(BTP_BUSY, &(info)->flags))
+
+static spinlock_t btp_lock;
+
+struct usb_btp_info {
+ struct timer_list timer;
+ unsigned long flags;
+ /* default shock strength */
+ unsigned char left_strength;
+ unsigned char right_strength;
+ unsigned char start_packet[8];
+ unsigned char stop_packet[8];
+ /* ff data */
+ struct ff_effect effects[8];
+ int running_effect;
+ int repeat;
+ /* usb data */
+ struct usb_ctrlrequest req;
+ struct usb_device *dev;
+ struct urb *ctrl0;
+ unsigned int pipe0;
+ int timeout;
+};
+
+static struct usb_device_id btp_id_table[] = {
+ {
+ USB_DEVICE(USB_ID_VENDOR, USB_ID_PRODUCT)
+ },
+ {}
+};
+
+MODULE_DEVICE_TABLE(usb, btp_id_table);
+
+static char driver_name[] = "BETOP Vibration Gamepad (BTP-2118) Driver";
+
+/* This gamepad reports three usages, but all are same. */
+static struct usage_block btp_ff_usage_block[] = {
+ USAGE_BLOCK(USAGE_BTP_FF, 0, EV_FF, FF_GAIN, 0),
+ USAGE_BLOCK(USAGE_BTP_FF, 0, EV_FF, FF_RUMBLE, 0),
+ USAGE_BLOCK(USAGE_BTP_FF, 0, EV_FF, FF_CONSTANT, 0),
+ USAGE_BLOCK(USAGE_BTP_FF, 0, EV_FF, FF_SPRING, 0),
+ USAGE_BLOCK(USAGE_BTP_FF, 0, EV_FF, FF_FRICTION, 0),
+ USAGE_BLOCK(USAGE_BTP_FF, 0, EV_FF, FF_DAMPER, 0),
+ USAGE_BLOCK(USAGE_BTP_FF, 0, EV_FF, FF_INERTIA, 0),
+ USAGE_BLOCK(USAGE_BTP_FF, 0, EV_FF, FF_RAMP, 0),
+ USAGE_BLOCK(USAGE_BTP_FF, 0, EV_FF_STATUS, 0, FF_STATUS_STOPPED),
+ USAGE_BLOCK(USAGE_BTP_FF, 0, EV_FF_STATUS, 0, FF_STATUS_PLAYING),
+ USAGE_BLOCK_NULL
+};
+
+static struct usage_page_block btp_ff_usage_page_blockes[] = {
+ USAGE_PAGE_BLOCK(USAGE_PAGE_BTP_FF, btp_ff_usage_block),
+ USAGE_PAGE_BLOCK_NULL
+};
+
+/* usb_complete_t */
+static void urb_complete(struct urb *urb, struct pt_regs *regs)
+{
+ struct usb_btp_info *info = urb->context;
+
+ info = urb->context;
+ usb_unlink_urb(urb);
+
+ if (IS_BTP_DISCONNECTING(info))
+ BTP_SET_URB_DONE(info);
+ BTP_CLR_BUSY(info);
+}
+
+static int inline btp_effect_request(struct usb_btp_info *info, char *packet)
+{
+ if (IS_BTP_BUSY(info))
+ return -EBUSY;
+
+ usb_fill_control_urb (info->ctrl0, info->dev, info->pipe0,
+ (char*)&info->req,
+ packet,
+ info->req.wLength,
+ urb_complete,
+ info);
+ BTP_SET_BUSY(info);
+ usb_submit_urb(info->ctrl0, GFP_ATOMIC);
+ return 0;
+}
+
+/* run by usb_btp_info->timer */
+static void btp_effect_repeat(unsigned long data)
+{
+ struct usb_btp_info *info = (struct usb_btp_info*)data;
+
+ if (IS_BTP_DISCONNECTING(info))
+ return;
+
+ spin_lock(&btp_lock);
+ if (!info->repeat) {
+ info->running_effect = BTP_EFFECT_NONE;
+ } else {
+ mod_timer(&info->timer, jiffies+4*HZ);
+ BTP_CLR_BUSY(info);
+ btp_effect_request(info, info->start_packet);
+ --info->repeat;
+ }
+ spin_unlock(&btp_lock);
+}
+
+/* the caller must hold btp_lock first */
+static int btp_effect_start(struct hid_input *hidinput)
+{
+ struct usb_btp_info *info;
+
+ info = hidinput->private;
+ if (info)
+ return btp_effect_request(info, info->start_packet);
+ return -ENODEV;
+}
+
+/* the caller must hold btp_lock first */
+static int btp_effect_stop(struct hid_input *hidinput)
+{
+ struct usb_btp_info *info;
+
+ info = hidinput->private;
+ if (info)
+ return btp_effect_request(info, info->stop_packet);
+ return -ENODEV;
+}
+
+static int btp_connect(struct hid_device *hid, struct hid_input *hidinput)
+{
+ struct usb_btp_info *info;
+
+ info = kzalloc(sizeof(struct usb_btp_info), GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+ info->ctrl0 = usb_alloc_urb(0, GFP_KERNEL);
+ if (!info->ctrl0) {
+ kfree(info);
+ return -ENOMEM;
+ }
+ /* Betop-2118 joystick default parameter */
+ info->left_strength = '\x14';
+ info->right_strength = '\x14';
+ info->req.bRequest = 0x9;
+ info->req.bRequestType = (USB_TYPE_CLASS|USB_RECIP_INTERFACE);
+ info->req.wIndex = 0x0;
+ info->req.wValue = 0x200;
+ info->req.wLength = 0x3;
+ info->timeout = USB_CTRL_SET_TIMEOUT;
+ info->dev = hid->dev;
+ info->pipe0 = usb_sndctrlpipe(hid->dev, 0);
+ info->start_packet[0] = info->left_strength;
+ info->start_packet[1] = info->right_strength;
+ info->running_effect = BTP_EFFECT_NONE;
+ init_timer(&info->timer);
+ info->timer.data = (unsigned long)info;
+ info->timer.function = btp_effect_repeat;
+ hidinput->private = info;
+ spin_lock_init(&btp_lock);
+ return 0;
+}
+
+static void inline btp_wait_for_effect(struct usb_btp_info *info)
+{
+ while ( info->flags && !IS_BTP_URB_DONE(info) )
+ schedule();
+ return;
+}
+
+static void btp_disconnect(struct hid_device *hid, struct hid_input *hidinput)
+{
+ struct usb_btp_info *info;
+ unsigned long flags;
+
+ if (!hidinput)
+ return;
+
+ spin_lock_irqsave(&btp_lock, flags);
+ info = hidinput->private;
+ if (IS_BTP_BUSY(info))
+ BTP_SET_DISCONNECTING(info);
+ info->repeat = 0;
+ spin_unlock_irqrestore(&btp_lock, flags);
+
+ del_timer_sync(&info->timer);
+ btp_wait_for_effect(info);
+
+ spin_lock_irqsave(&btp_lock, flags);
+ hidinput->private = NULL;
+ spin_unlock_irqrestore(&btp_lock, flags);
+
+ usb_free_urb(info->ctrl0);
+ kfree(info);
+}
+
+static void usb_btp_update_effect(struct hid_input *hidinput, int offset)
+{
+ struct usb_btp_info *info;
+
+ spin_lock(&btp_lock);
+ info = hidinput->private;
+ if (offset == info->running_effect) {
+ if (btp_effect_stop(hidinput))
+ goto exit;
+ if (info->effects[offset].type) {
+ if (btp_effect_start(hidinput))
+ goto exit;
+ }
+ else /* remove effect */
+ del_timer(&info->timer);
+ }
+exit:
+ spin_unlock(&btp_lock);
+}
+
+static int btp_FF_GAIN_handler(struct hid_input *hidinput, int value)
+{
+ int offset;
+ unsigned char strength;
+ struct usb_btp_info *info;
+
+ if (value < 0 || value > 99)
+ return -EINVAL;
+ offset = (value>49); /*0 - left motor, 1 - right motor */
+ if (offset)
+ value -= 50;
+ if (!value) {
+ strength = 0;
+ goto exit0;
+ }
+ /* the range of value is from 1 to 50 */
+ /* this mapping value to shock strength (from 0xa to 0x1f) */
+ strength = ((21*value)+459)/48;
+exit0:
+ spin_lock(&btp_lock);
+ info = hidinput->private;
+ if (info)
+ info->start_packet[offset] = strength;
+ spin_unlock(&btp_lock);
+ return 0;
+}
+
+static int btp_ff_event(struct input_dev *dev, int type, int code, int value)
+{
+ struct hid_input *hidinput;
+ struct usb_btp_info *info;
+ int ret = 0;
+ int running_effect;
+
+ hidinput = hidinput_simple_inputdev_to_hidinput(dev);
+ if (!hidinput)
+ return -ENODEV;
+
+ spin_lock(&btp_lock);
+ info = hidinput->private;
+ if (!info || IS_BTP_DISCONNECTING(info)) {
+ ret = -ENODEV;
+ goto unlock_exit;
+ }
+ running_effect = info->running_effect;
+ spin_unlock(&btp_lock);
+
+ if (type == EV_FF_STATUS) {
+ if (code == running_effect)
+ input_report_ff_status(dev, code, FF_STATUS_PLAYING);
+ else
+ input_report_ff_status(dev, code, FF_STATUS_STOPPED);
+ }
+
+ if (type != EV_FF)
+ return -EINVAL;
+
+ switch (code) {
+ case FF_GAIN:
+ return btp_FF_GAIN_handler(hidinput, value);
+ default:
+ spin_lock(&btp_lock);
+ info = hidinput->private;
+ if (!info) {
+ ret = -ENODEV;
+ goto unlock_exit;
+ }
+ info->repeat = value;
+ if (value) {
+ if (btp_effect_start(hidinput))
+ goto unlock_exit;
+ if (value>1) {
+ del_timer(&info->timer);
+ mod_timer(&info->timer, jiffies+4*HZ);
+ }
+ info->running_effect = code;
+ }
+ else {
+ del_timer(&info->timer);
+ if (btp_effect_stop(hidinput))
+ goto unlock_exit;
+ info->running_effect = BTP_EFFECT_NONE;
+ }
+ }
+
+unlock_exit:
+ spin_unlock(&btp_lock);
+ return ret;
+}
+
+static int inline btp_new_effect_id(void)
+{
+ static atomic_t effect_id= ATOMIC_INIT(0);
+
+ atomic_inc(&effect_id);
+ return atomic_read(&effect_id);
+}
+
+static int btp_upload_effect(struct input_dev *dev, struct ff_effect *effect)
+{
+ struct hid_input *hidinput;
+ struct usb_btp_info *info;
+ int offset;
+
+ hidinput = hidinput_simple_inputdev_to_hidinput(dev);
+ if (!hidinput) {
+ return -ENODEV;
+ }
+ offset = effect->type - FF_RUMBLE;
+ effect->id = ((effect->id != -1) ?: btp_new_effect_id());
+ if (offset>=0 && offset<8) {
+ spin_lock(&btp_lock);
+ info = hidinput->private;
+ if (!info || IS_BTP_DISCONNECTING(info)) {
+ spin_unlock(&btp_lock);
+ return -ENODEV;
+ }
+ memcpy(info->effects+offset, effect, sizeof(struct ff_effect));
+ spin_unlock(&btp_lock);
+ usb_btp_update_effect(hidinput, effect->type);
+ return 0;
+ }
+ return -EINVAL;
+}
+
+static int btp_erase_effect(struct input_dev *dev, int effect_id)
+{
+ struct hid_input *hidinput;
+ struct usb_btp_info *info;
+ int offset, ret=0;
+
+ hidinput = hidinput_simple_inputdev_to_hidinput(dev);
+ if (!hidinput) {
+ return -ENODEV;
+ }
+
+ spin_lock(&btp_lock);
+ info = hidinput->private;
+ if (!info || IS_BTP_DISCONNECTING(info)) {
+ spin_unlock(&btp_lock);
+ return -ENODEV;
+ }
+ for (offset=0; offset<8; ++offset) {
+ if (effect_id == info->effects[offset].id) {
+ memset(info->effects+offset, 0, sizeof(struct ff_effect));
+ break;
+ }
+ }
+ spin_unlock(&btp_lock);
+ usb_btp_update_effect(hidinput, offset);
+ return ret;
+}
+
+static struct hidinput_simple_driver btp_driver = {
+ __SIMPLE_DRIVER_INIT
+ .name = driver_name,
+ .connect = btp_connect,
+ .disconnect = btp_disconnect,
+ .ff_event = btp_ff_event,
+ .upload_effect = btp_upload_effect,
+ .erase_effect = btp_erase_effect,
+ .id_table = btp_id_table,
+ .usage_page_table = btp_ff_usage_page_blockes,
+ .private = NULL,
+};
+
+static int __init btp_init(void)
+{
+ return hidinput_register_simple_driver(&btp_driver);
+}
+
+static void __exit btp_exit(void)
+{
+ hidinput_unregister_simple_driver(&btp_driver);
+}
+
+module_init(btp_init);
+module_exit(btp_exit);
+
+MODULE_LICENSE("GPL");


-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/

\
 
 \ /
  Last update: 2006-08-16 04:13    [W:0.041 / U:0.052 seconds]
©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site