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