[PATCH] usb: Betop BTP-2118 joystick force-feedback driver

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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 <[email protected]>

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 <[email protected]>
+ */
+
+/*
+ * 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 [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

[Index of Archives]     [Kernel Newbies]     [Netfilter]     [Bugtraq]     [Photo]     [Stuff]     [Gimp]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Video 4 Linux]     [Linux for the blind]     [Linux Resources]
  Powered by Linux