hid-pl.c 5.2 KB
Newer Older
1
/*
2 3 4 5 6 7 8 9 10 11
 *  Force feedback support for PantherLord/GreenAsia based devices
 *
 *  The devices are distributed under various names and the same USB device ID
 *  can be used in both adapters and actual game controllers.
 *
 *  0810:0001 "Twin USB Joystick"
 *   - tested with PantherLord USB/PS2 2in1 Adapter
 *   - contains two reports, one for each port (HID_QUIRK_MULTI_INPUT)
 *
 *  0e8f:0003 "GreenAsia Inc.    USB Joystick     "
J
Jiri Slaby 已提交
12
 *   - tested with K??ng Gaming gamepad
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
 *
 *  Copyright (c) 2007 Anssi Hannula <anssi.hannula@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
 */


/* #define DEBUG */

#define debug(format, arg...) pr_debug("hid-plff: " format "\n" , ## arg)

#include <linux/input.h>
#include <linux/usb.h>
#include <linux/hid.h>
J
Jiri Slaby 已提交
41 42 43 44 45

#include "hid-ids.h"

#ifdef CONFIG_PANTHERLORD_FF
#include "usbhid/usbhid.h"
46 47 48 49 50 51 52 53

struct plff_device {
	struct hid_report *report;
};

static int hid_plff_play(struct input_dev *dev, void *data,
			 struct ff_effect *effect)
{
54
	struct hid_device *hid = input_get_drvdata(dev);
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
	struct plff_device *plff = data;
	int left, right;

	left = effect->u.rumble.strong_magnitude;
	right = effect->u.rumble.weak_magnitude;
	debug("called with 0x%04x 0x%04x", left, right);

	left = left * 0x7f / 0xffff;
	right = right * 0x7f / 0xffff;

	plff->report->field[0]->value[2] = left;
	plff->report->field[0]->value[3] = right;
	debug("running with 0x%02x 0x%02x", left, right);
	usbhid_submit_report(hid, plff->report, USB_DIR_OUT);

	return 0;
}

J
Jiri Slaby 已提交
73
static int plff_init(struct hid_device *hid)
74 75 76 77 78 79 80 81 82 83
{
	struct plff_device *plff;
	struct hid_report *report;
	struct hid_input *hidinput;
	struct list_head *report_list =
			&hid->report_enum[HID_OUTPUT_REPORT].report_list;
	struct list_head *report_ptr = report_list;
	struct input_dev *dev;
	int error;

84 85 86
	/* The device contains one output report per physical device, all
	   containing 1 field, which contains 4 ff00.0002 usages and 4 16bit
	   absolute values.
87

88
	   The input reports also contain a field which contains
89 90 91 92
	   8 ff00.0001 usages and 8 boolean values. Their meaning is
	   currently unknown. */

	if (list_empty(report_list)) {
J
Jiri Slaby 已提交
93
		dev_err(&hid->dev, "no output reports found\n");
94 95 96 97 98 99 100 101
		return -ENODEV;
	}

	list_for_each_entry(hidinput, &hid->inputs, list) {

		report_ptr = report_ptr->next;

		if (report_ptr == report_list) {
J
Jiri Slaby 已提交
102 103
			dev_err(&hid->dev, "required output report is "
					"missing\n");
104 105 106 107 108
			return -ENODEV;
		}

		report = list_entry(report_ptr, struct hid_report, list);
		if (report->maxfield < 1) {
J
Jiri Slaby 已提交
109
			dev_err(&hid->dev, "no fields in the report\n");
110 111 112 113
			return -ENODEV;
		}

		if (report->field[0]->report_count < 4) {
J
Jiri Slaby 已提交
114
			dev_err(&hid->dev, "not enough values in the field\n");
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139
			return -ENODEV;
		}

		plff = kzalloc(sizeof(struct plff_device), GFP_KERNEL);
		if (!plff)
			return -ENOMEM;

		dev = hidinput->input;

		set_bit(FF_RUMBLE, dev->ffbit);

		error = input_ff_create_memless(dev, plff, hid_plff_play);
		if (error) {
			kfree(plff);
			return error;
		}

		plff->report = report;
		plff->report->field[0]->value[0] = 0x00;
		plff->report->field[0]->value[1] = 0x00;
		plff->report->field[0]->value[2] = 0x00;
		plff->report->field[0]->value[3] = 0x00;
		usbhid_submit_report(hid, plff->report, USB_DIR_OUT);
	}

J
Jiri Slaby 已提交
140
	dev_info(&hid->dev, "Force feedback for PantherLord/GreenAsia "
141
	       "devices by Anssi Hannula <anssi.hannula@gmail.com>\n");
142 143 144

	return 0;
}
J
Jiri Slaby 已提交
145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180
#else
static inline int plff_init(struct hid_device *hid)
{
	return 0;
}
#endif

static int pl_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
	int ret;

	if (id->driver_data)
		hdev->quirks |= HID_QUIRK_MULTI_INPUT;

	ret = hid_parse(hdev);
	if (ret) {
		dev_err(&hdev->dev, "parse failed\n");
		goto err;
	}

	ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
	if (ret) {
		dev_err(&hdev->dev, "hw start failed\n");
		goto err;
	}

	plff_init(hdev);

	return 0;
err:
	return ret;
}

static const struct hid_device_id pl_devices[] = {
	{ HID_USB_DEVICE(USB_VENDOR_ID_GAMERON, USB_DEVICE_ID_GAMERON_DUAL_PSX_ADAPTOR),
		.driver_data = 1 }, /* Twin USB Joystick */
181 182
	{ HID_USB_DEVICE(USB_VENDOR_ID_GAMERON, USB_DEVICE_ID_GAMERON_DUAL_PCS_ADAPTOR),
		.driver_data = 1 }, /* Twin USB Joystick */
J
Jiri Slaby 已提交
183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208
	{ HID_USB_DEVICE(USB_VENDOR_ID_GREENASIA, 0x0003), }, /* GreenAsia Inc. USB Joystick */
	{ }
};
MODULE_DEVICE_TABLE(hid, pl_devices);

static struct hid_driver pl_driver = {
	.name = "pantherlord",
	.id_table = pl_devices,
	.probe = pl_probe,
};

static int pl_init(void)
{
	return hid_register_driver(&pl_driver);
}

static void pl_exit(void)
{
	hid_unregister_driver(&pl_driver);
}

module_init(pl_init);
module_exit(pl_exit);
MODULE_LICENSE("GPL");

HID_COMPAT_LOAD_DRIVER(pantherlord);