18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci *  Force feedback support for PantherLord/GreenAsia based devices
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci *  The devices are distributed under various names and the same USB device ID
68c2ecf20Sopenharmony_ci *  can be used in both adapters and actual game controllers.
78c2ecf20Sopenharmony_ci *
88c2ecf20Sopenharmony_ci *  0810:0001 "Twin USB Joystick"
98c2ecf20Sopenharmony_ci *   - tested with PantherLord USB/PS2 2in1 Adapter
108c2ecf20Sopenharmony_ci *   - contains two reports, one for each port (HID_QUIRK_MULTI_INPUT)
118c2ecf20Sopenharmony_ci *
128c2ecf20Sopenharmony_ci *  0e8f:0003 "GreenAsia Inc.    USB Joystick     "
138c2ecf20Sopenharmony_ci *   - tested with König Gaming gamepad
148c2ecf20Sopenharmony_ci *
158c2ecf20Sopenharmony_ci *  0e8f:0003 "GASIA USB Gamepad"
168c2ecf20Sopenharmony_ci *   - another version of the König gamepad
178c2ecf20Sopenharmony_ci *
188c2ecf20Sopenharmony_ci *  0f30:0111 "Saitek Color Rumble Pad"
198c2ecf20Sopenharmony_ci *
208c2ecf20Sopenharmony_ci *  Copyright (c) 2007, 2009 Anssi Hannula <anssi.hannula@gmail.com>
218c2ecf20Sopenharmony_ci */
228c2ecf20Sopenharmony_ci
238c2ecf20Sopenharmony_ci/*
248c2ecf20Sopenharmony_ci */
258c2ecf20Sopenharmony_ci
268c2ecf20Sopenharmony_ci
278c2ecf20Sopenharmony_ci/* #define DEBUG */
288c2ecf20Sopenharmony_ci
298c2ecf20Sopenharmony_ci#define debug(format, arg...) pr_debug("hid-plff: " format "\n" , ## arg)
308c2ecf20Sopenharmony_ci
318c2ecf20Sopenharmony_ci#include <linux/input.h>
328c2ecf20Sopenharmony_ci#include <linux/slab.h>
338c2ecf20Sopenharmony_ci#include <linux/module.h>
348c2ecf20Sopenharmony_ci#include <linux/hid.h>
358c2ecf20Sopenharmony_ci
368c2ecf20Sopenharmony_ci#include "hid-ids.h"
378c2ecf20Sopenharmony_ci
388c2ecf20Sopenharmony_ci#ifdef CONFIG_PANTHERLORD_FF
398c2ecf20Sopenharmony_ci
408c2ecf20Sopenharmony_cistruct plff_device {
418c2ecf20Sopenharmony_ci	struct hid_report *report;
428c2ecf20Sopenharmony_ci	s32 maxval;
438c2ecf20Sopenharmony_ci	s32 *strong;
448c2ecf20Sopenharmony_ci	s32 *weak;
458c2ecf20Sopenharmony_ci};
468c2ecf20Sopenharmony_ci
478c2ecf20Sopenharmony_cistatic int hid_plff_play(struct input_dev *dev, void *data,
488c2ecf20Sopenharmony_ci			 struct ff_effect *effect)
498c2ecf20Sopenharmony_ci{
508c2ecf20Sopenharmony_ci	struct hid_device *hid = input_get_drvdata(dev);
518c2ecf20Sopenharmony_ci	struct plff_device *plff = data;
528c2ecf20Sopenharmony_ci	int left, right;
538c2ecf20Sopenharmony_ci
548c2ecf20Sopenharmony_ci	left = effect->u.rumble.strong_magnitude;
558c2ecf20Sopenharmony_ci	right = effect->u.rumble.weak_magnitude;
568c2ecf20Sopenharmony_ci	debug("called with 0x%04x 0x%04x", left, right);
578c2ecf20Sopenharmony_ci
588c2ecf20Sopenharmony_ci	left = left * plff->maxval / 0xffff;
598c2ecf20Sopenharmony_ci	right = right * plff->maxval / 0xffff;
608c2ecf20Sopenharmony_ci
618c2ecf20Sopenharmony_ci	*plff->strong = left;
628c2ecf20Sopenharmony_ci	*plff->weak = right;
638c2ecf20Sopenharmony_ci	debug("running with 0x%02x 0x%02x", left, right);
648c2ecf20Sopenharmony_ci	hid_hw_request(hid, plff->report, HID_REQ_SET_REPORT);
658c2ecf20Sopenharmony_ci
668c2ecf20Sopenharmony_ci	return 0;
678c2ecf20Sopenharmony_ci}
688c2ecf20Sopenharmony_ci
698c2ecf20Sopenharmony_cistatic int plff_init(struct hid_device *hid)
708c2ecf20Sopenharmony_ci{
718c2ecf20Sopenharmony_ci	struct plff_device *plff;
728c2ecf20Sopenharmony_ci	struct hid_report *report;
738c2ecf20Sopenharmony_ci	struct hid_input *hidinput;
748c2ecf20Sopenharmony_ci	struct list_head *report_list =
758c2ecf20Sopenharmony_ci			&hid->report_enum[HID_OUTPUT_REPORT].report_list;
768c2ecf20Sopenharmony_ci	struct list_head *report_ptr = report_list;
778c2ecf20Sopenharmony_ci	struct input_dev *dev;
788c2ecf20Sopenharmony_ci	int error;
798c2ecf20Sopenharmony_ci	s32 maxval;
808c2ecf20Sopenharmony_ci	s32 *strong;
818c2ecf20Sopenharmony_ci	s32 *weak;
828c2ecf20Sopenharmony_ci
838c2ecf20Sopenharmony_ci	/* The device contains one output report per physical device, all
848c2ecf20Sopenharmony_ci	   containing 1 field, which contains 4 ff00.0002 usages and 4 16bit
858c2ecf20Sopenharmony_ci	   absolute values.
868c2ecf20Sopenharmony_ci
878c2ecf20Sopenharmony_ci	   The input reports also contain a field which contains
888c2ecf20Sopenharmony_ci	   8 ff00.0001 usages and 8 boolean values. Their meaning is
898c2ecf20Sopenharmony_ci	   currently unknown.
908c2ecf20Sopenharmony_ci
918c2ecf20Sopenharmony_ci	   A version of the 0e8f:0003 exists that has all the values in
928c2ecf20Sopenharmony_ci	   separate fields and misses the extra input field, thus resembling
938c2ecf20Sopenharmony_ci	   Zeroplus (hid-zpff) devices.
948c2ecf20Sopenharmony_ci	*/
958c2ecf20Sopenharmony_ci
968c2ecf20Sopenharmony_ci	if (list_empty(report_list)) {
978c2ecf20Sopenharmony_ci		hid_err(hid, "no output reports found\n");
988c2ecf20Sopenharmony_ci		return -ENODEV;
998c2ecf20Sopenharmony_ci	}
1008c2ecf20Sopenharmony_ci
1018c2ecf20Sopenharmony_ci	list_for_each_entry(hidinput, &hid->inputs, list) {
1028c2ecf20Sopenharmony_ci
1038c2ecf20Sopenharmony_ci		report_ptr = report_ptr->next;
1048c2ecf20Sopenharmony_ci
1058c2ecf20Sopenharmony_ci		if (report_ptr == report_list) {
1068c2ecf20Sopenharmony_ci			hid_err(hid, "required output report is missing\n");
1078c2ecf20Sopenharmony_ci			return -ENODEV;
1088c2ecf20Sopenharmony_ci		}
1098c2ecf20Sopenharmony_ci
1108c2ecf20Sopenharmony_ci		report = list_entry(report_ptr, struct hid_report, list);
1118c2ecf20Sopenharmony_ci		if (report->maxfield < 1) {
1128c2ecf20Sopenharmony_ci			hid_err(hid, "no fields in the report\n");
1138c2ecf20Sopenharmony_ci			return -ENODEV;
1148c2ecf20Sopenharmony_ci		}
1158c2ecf20Sopenharmony_ci
1168c2ecf20Sopenharmony_ci		maxval = 0x7f;
1178c2ecf20Sopenharmony_ci		if (report->field[0]->report_count >= 4) {
1188c2ecf20Sopenharmony_ci			report->field[0]->value[0] = 0x00;
1198c2ecf20Sopenharmony_ci			report->field[0]->value[1] = 0x00;
1208c2ecf20Sopenharmony_ci			strong = &report->field[0]->value[2];
1218c2ecf20Sopenharmony_ci			weak = &report->field[0]->value[3];
1228c2ecf20Sopenharmony_ci			debug("detected single-field device");
1238c2ecf20Sopenharmony_ci		} else if (report->field[0]->maxusage == 1 &&
1248c2ecf20Sopenharmony_ci			   report->field[0]->usage[0].hid ==
1258c2ecf20Sopenharmony_ci				(HID_UP_LED | 0x43) &&
1268c2ecf20Sopenharmony_ci			   report->maxfield >= 4 &&
1278c2ecf20Sopenharmony_ci			   report->field[0]->report_count >= 1 &&
1288c2ecf20Sopenharmony_ci			   report->field[1]->report_count >= 1 &&
1298c2ecf20Sopenharmony_ci			   report->field[2]->report_count >= 1 &&
1308c2ecf20Sopenharmony_ci			   report->field[3]->report_count >= 1) {
1318c2ecf20Sopenharmony_ci			report->field[0]->value[0] = 0x00;
1328c2ecf20Sopenharmony_ci			report->field[1]->value[0] = 0x00;
1338c2ecf20Sopenharmony_ci			strong = &report->field[2]->value[0];
1348c2ecf20Sopenharmony_ci			weak = &report->field[3]->value[0];
1358c2ecf20Sopenharmony_ci			if (hid->vendor == USB_VENDOR_ID_JESS2)
1368c2ecf20Sopenharmony_ci				maxval = 0xff;
1378c2ecf20Sopenharmony_ci			debug("detected 4-field device");
1388c2ecf20Sopenharmony_ci		} else {
1398c2ecf20Sopenharmony_ci			hid_err(hid, "not enough fields or values\n");
1408c2ecf20Sopenharmony_ci			return -ENODEV;
1418c2ecf20Sopenharmony_ci		}
1428c2ecf20Sopenharmony_ci
1438c2ecf20Sopenharmony_ci		plff = kzalloc(sizeof(struct plff_device), GFP_KERNEL);
1448c2ecf20Sopenharmony_ci		if (!plff)
1458c2ecf20Sopenharmony_ci			return -ENOMEM;
1468c2ecf20Sopenharmony_ci
1478c2ecf20Sopenharmony_ci		dev = hidinput->input;
1488c2ecf20Sopenharmony_ci
1498c2ecf20Sopenharmony_ci		set_bit(FF_RUMBLE, dev->ffbit);
1508c2ecf20Sopenharmony_ci
1518c2ecf20Sopenharmony_ci		error = input_ff_create_memless(dev, plff, hid_plff_play);
1528c2ecf20Sopenharmony_ci		if (error) {
1538c2ecf20Sopenharmony_ci			kfree(plff);
1548c2ecf20Sopenharmony_ci			return error;
1558c2ecf20Sopenharmony_ci		}
1568c2ecf20Sopenharmony_ci
1578c2ecf20Sopenharmony_ci		plff->report = report;
1588c2ecf20Sopenharmony_ci		plff->strong = strong;
1598c2ecf20Sopenharmony_ci		plff->weak = weak;
1608c2ecf20Sopenharmony_ci		plff->maxval = maxval;
1618c2ecf20Sopenharmony_ci
1628c2ecf20Sopenharmony_ci		*strong = 0x00;
1638c2ecf20Sopenharmony_ci		*weak = 0x00;
1648c2ecf20Sopenharmony_ci		hid_hw_request(hid, plff->report, HID_REQ_SET_REPORT);
1658c2ecf20Sopenharmony_ci	}
1668c2ecf20Sopenharmony_ci
1678c2ecf20Sopenharmony_ci	hid_info(hid, "Force feedback for PantherLord/GreenAsia devices by Anssi Hannula <anssi.hannula@gmail.com>\n");
1688c2ecf20Sopenharmony_ci
1698c2ecf20Sopenharmony_ci	return 0;
1708c2ecf20Sopenharmony_ci}
1718c2ecf20Sopenharmony_ci#else
1728c2ecf20Sopenharmony_cistatic inline int plff_init(struct hid_device *hid)
1738c2ecf20Sopenharmony_ci{
1748c2ecf20Sopenharmony_ci	return 0;
1758c2ecf20Sopenharmony_ci}
1768c2ecf20Sopenharmony_ci#endif
1778c2ecf20Sopenharmony_ci
1788c2ecf20Sopenharmony_cistatic int pl_probe(struct hid_device *hdev, const struct hid_device_id *id)
1798c2ecf20Sopenharmony_ci{
1808c2ecf20Sopenharmony_ci	int ret;
1818c2ecf20Sopenharmony_ci
1828c2ecf20Sopenharmony_ci	if (id->driver_data)
1838c2ecf20Sopenharmony_ci		hdev->quirks |= HID_QUIRK_MULTI_INPUT;
1848c2ecf20Sopenharmony_ci
1858c2ecf20Sopenharmony_ci	ret = hid_parse(hdev);
1868c2ecf20Sopenharmony_ci	if (ret) {
1878c2ecf20Sopenharmony_ci		hid_err(hdev, "parse failed\n");
1888c2ecf20Sopenharmony_ci		goto err;
1898c2ecf20Sopenharmony_ci	}
1908c2ecf20Sopenharmony_ci
1918c2ecf20Sopenharmony_ci	ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
1928c2ecf20Sopenharmony_ci	if (ret) {
1938c2ecf20Sopenharmony_ci		hid_err(hdev, "hw start failed\n");
1948c2ecf20Sopenharmony_ci		goto err;
1958c2ecf20Sopenharmony_ci	}
1968c2ecf20Sopenharmony_ci
1978c2ecf20Sopenharmony_ci	plff_init(hdev);
1988c2ecf20Sopenharmony_ci
1998c2ecf20Sopenharmony_ci	return 0;
2008c2ecf20Sopenharmony_cierr:
2018c2ecf20Sopenharmony_ci	return ret;
2028c2ecf20Sopenharmony_ci}
2038c2ecf20Sopenharmony_ci
2048c2ecf20Sopenharmony_cistatic const struct hid_device_id pl_devices[] = {
2058c2ecf20Sopenharmony_ci	{ HID_USB_DEVICE(USB_VENDOR_ID_GAMERON, USB_DEVICE_ID_GAMERON_DUAL_PSX_ADAPTOR),
2068c2ecf20Sopenharmony_ci		.driver_data = 1 }, /* Twin USB Joystick */
2078c2ecf20Sopenharmony_ci	{ HID_USB_DEVICE(USB_VENDOR_ID_GAMERON, USB_DEVICE_ID_GAMERON_DUAL_PCS_ADAPTOR),
2088c2ecf20Sopenharmony_ci		.driver_data = 1 }, /* Twin USB Joystick */
2098c2ecf20Sopenharmony_ci	{ HID_USB_DEVICE(USB_VENDOR_ID_GREENASIA, 0x0003), },
2108c2ecf20Sopenharmony_ci	{ HID_USB_DEVICE(USB_VENDOR_ID_JESS2, USB_DEVICE_ID_JESS2_COLOR_RUMBLE_PAD), },
2118c2ecf20Sopenharmony_ci	{ }
2128c2ecf20Sopenharmony_ci};
2138c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(hid, pl_devices);
2148c2ecf20Sopenharmony_ci
2158c2ecf20Sopenharmony_cistatic struct hid_driver pl_driver = {
2168c2ecf20Sopenharmony_ci	.name = "pantherlord",
2178c2ecf20Sopenharmony_ci	.id_table = pl_devices,
2188c2ecf20Sopenharmony_ci	.probe = pl_probe,
2198c2ecf20Sopenharmony_ci};
2208c2ecf20Sopenharmony_cimodule_hid_driver(pl_driver);
2218c2ecf20Sopenharmony_ci
2228c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
223