18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * HID driver for Gembird Joypad, "PC Game Controller" 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (c) 2015 Red Hat, Inc 68c2ecf20Sopenharmony_ci * Copyright (c) 2015 Benjamin Tissoires 78c2ecf20Sopenharmony_ci */ 88c2ecf20Sopenharmony_ci 98c2ecf20Sopenharmony_ci/* 108c2ecf20Sopenharmony_ci */ 118c2ecf20Sopenharmony_ci 128c2ecf20Sopenharmony_ci#include <linux/device.h> 138c2ecf20Sopenharmony_ci#include <linux/hid.h> 148c2ecf20Sopenharmony_ci#include <linux/module.h> 158c2ecf20Sopenharmony_ci 168c2ecf20Sopenharmony_ci#include "hid-ids.h" 178c2ecf20Sopenharmony_ci 188c2ecf20Sopenharmony_ci#define GEMBIRD_START_FAULTY_RDESC 8 198c2ecf20Sopenharmony_ci 208c2ecf20Sopenharmony_cistatic const __u8 gembird_jpd_faulty_rdesc[] = { 218c2ecf20Sopenharmony_ci 0x75, 0x08, /* Report Size (8) */ 228c2ecf20Sopenharmony_ci 0x95, 0x05, /* Report Count (5) */ 238c2ecf20Sopenharmony_ci 0x15, 0x00, /* Logical Minimum (0) */ 248c2ecf20Sopenharmony_ci 0x26, 0xff, 0x00, /* Logical Maximum (255) */ 258c2ecf20Sopenharmony_ci 0x35, 0x00, /* Physical Minimum (0) */ 268c2ecf20Sopenharmony_ci 0x46, 0xff, 0x00, /* Physical Maximum (255) */ 278c2ecf20Sopenharmony_ci 0x09, 0x30, /* Usage (X) */ 288c2ecf20Sopenharmony_ci 0x09, 0x31, /* Usage (Y) */ 298c2ecf20Sopenharmony_ci 0x09, 0x32, /* Usage (Z) */ 308c2ecf20Sopenharmony_ci 0x09, 0x32, /* Usage (Z) */ 318c2ecf20Sopenharmony_ci 0x09, 0x35, /* Usage (Rz) */ 328c2ecf20Sopenharmony_ci 0x81, 0x02, /* Input (Data,Var,Abs) */ 338c2ecf20Sopenharmony_ci}; 348c2ecf20Sopenharmony_ci 358c2ecf20Sopenharmony_ci/* 368c2ecf20Sopenharmony_ci * we fix the report descriptor by: 378c2ecf20Sopenharmony_ci * - marking the first Z axis as constant (so it is ignored by HID) 388c2ecf20Sopenharmony_ci * - assign the original second Z to Rx 398c2ecf20Sopenharmony_ci * - assign the original Rz to Ry 408c2ecf20Sopenharmony_ci */ 418c2ecf20Sopenharmony_cistatic const __u8 gembird_jpd_fixed_rdesc[] = { 428c2ecf20Sopenharmony_ci 0x75, 0x08, /* Report Size (8) */ 438c2ecf20Sopenharmony_ci 0x95, 0x02, /* Report Count (2) */ 448c2ecf20Sopenharmony_ci 0x15, 0x00, /* Logical Minimum (0) */ 458c2ecf20Sopenharmony_ci 0x26, 0xff, 0x00, /* Logical Maximum (255) */ 468c2ecf20Sopenharmony_ci 0x35, 0x00, /* Physical Minimum (0) */ 478c2ecf20Sopenharmony_ci 0x46, 0xff, 0x00, /* Physical Maximum (255) */ 488c2ecf20Sopenharmony_ci 0x09, 0x30, /* Usage (X) */ 498c2ecf20Sopenharmony_ci 0x09, 0x31, /* Usage (Y) */ 508c2ecf20Sopenharmony_ci 0x81, 0x02, /* Input (Data,Var,Abs) */ 518c2ecf20Sopenharmony_ci 0x95, 0x01, /* Report Count (1) */ 528c2ecf20Sopenharmony_ci 0x09, 0x32, /* Usage (Z) */ 538c2ecf20Sopenharmony_ci 0x81, 0x01, /* Input (Cnst,Arr,Abs) */ 548c2ecf20Sopenharmony_ci 0x95, 0x02, /* Report Count (2) */ 558c2ecf20Sopenharmony_ci 0x09, 0x33, /* Usage (Rx) */ 568c2ecf20Sopenharmony_ci 0x09, 0x34, /* Usage (Ry) */ 578c2ecf20Sopenharmony_ci 0x81, 0x02, /* Input (Data,Var,Abs) */ 588c2ecf20Sopenharmony_ci}; 598c2ecf20Sopenharmony_ci 608c2ecf20Sopenharmony_cistatic __u8 *gembird_report_fixup(struct hid_device *hdev, __u8 *rdesc, 618c2ecf20Sopenharmony_ci unsigned int *rsize) 628c2ecf20Sopenharmony_ci{ 638c2ecf20Sopenharmony_ci __u8 *new_rdesc; 648c2ecf20Sopenharmony_ci /* delta_size is > 0 */ 658c2ecf20Sopenharmony_ci size_t delta_size = sizeof(gembird_jpd_fixed_rdesc) - 668c2ecf20Sopenharmony_ci sizeof(gembird_jpd_faulty_rdesc); 678c2ecf20Sopenharmony_ci size_t new_size = *rsize + delta_size; 688c2ecf20Sopenharmony_ci 698c2ecf20Sopenharmony_ci if (*rsize >= 31 && !memcmp(&rdesc[GEMBIRD_START_FAULTY_RDESC], 708c2ecf20Sopenharmony_ci gembird_jpd_faulty_rdesc, 718c2ecf20Sopenharmony_ci sizeof(gembird_jpd_faulty_rdesc))) { 728c2ecf20Sopenharmony_ci new_rdesc = devm_kzalloc(&hdev->dev, new_size, GFP_KERNEL); 738c2ecf20Sopenharmony_ci if (new_rdesc == NULL) 748c2ecf20Sopenharmony_ci return rdesc; 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_ci dev_info(&hdev->dev, 778c2ecf20Sopenharmony_ci "fixing Gembird JPD-DualForce 2 report descriptor.\n"); 788c2ecf20Sopenharmony_ci 798c2ecf20Sopenharmony_ci /* start by copying the end of the rdesc */ 808c2ecf20Sopenharmony_ci memcpy(new_rdesc + delta_size, rdesc, *rsize); 818c2ecf20Sopenharmony_ci 828c2ecf20Sopenharmony_ci /* add the correct beginning */ 838c2ecf20Sopenharmony_ci memcpy(new_rdesc, rdesc, GEMBIRD_START_FAULTY_RDESC); 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_ci /* replace the faulty part with the fixed one */ 868c2ecf20Sopenharmony_ci memcpy(new_rdesc + GEMBIRD_START_FAULTY_RDESC, 878c2ecf20Sopenharmony_ci gembird_jpd_fixed_rdesc, 888c2ecf20Sopenharmony_ci sizeof(gembird_jpd_fixed_rdesc)); 898c2ecf20Sopenharmony_ci 908c2ecf20Sopenharmony_ci *rsize = new_size; 918c2ecf20Sopenharmony_ci rdesc = new_rdesc; 928c2ecf20Sopenharmony_ci } 938c2ecf20Sopenharmony_ci 948c2ecf20Sopenharmony_ci return rdesc; 958c2ecf20Sopenharmony_ci} 968c2ecf20Sopenharmony_ci 978c2ecf20Sopenharmony_cistatic const struct hid_device_id gembird_devices[] = { 988c2ecf20Sopenharmony_ci { HID_USB_DEVICE(USB_VENDOR_ID_GEMBIRD, 998c2ecf20Sopenharmony_ci USB_DEVICE_ID_GEMBIRD_JPD_DUALFORCE2) }, 1008c2ecf20Sopenharmony_ci { } 1018c2ecf20Sopenharmony_ci}; 1028c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(hid, gembird_devices); 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_cistatic struct hid_driver gembird_driver = { 1058c2ecf20Sopenharmony_ci .name = "gembird", 1068c2ecf20Sopenharmony_ci .id_table = gembird_devices, 1078c2ecf20Sopenharmony_ci .report_fixup = gembird_report_fixup, 1088c2ecf20Sopenharmony_ci}; 1098c2ecf20Sopenharmony_cimodule_hid_driver(gembird_driver); 1108c2ecf20Sopenharmony_ci 1118c2ecf20Sopenharmony_ciMODULE_AUTHOR("Benjamin Tissoires <benjamin.tissoires@gmail.com>"); 1128c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("HID Gembird joypad driver"); 1138c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 114