1c49c3363SDario Pagani // SPDX-License-Identifier: GPL-2.0 2c49c3363SDario Pagani /** 3c49c3363SDario Pagani * When connected to the machine, the Thrustmaster wheels appear as 4c49c3363SDario Pagani * a «generic» hid gamepad called "Thrustmaster FFB Wheel". 5c49c3363SDario Pagani * 6c49c3363SDario Pagani * When in this mode not every functionality of the wheel, like the force feedback, 7c49c3363SDario Pagani * are available. To enable all functionalities of a Thrustmaster wheel we have to send 8c49c3363SDario Pagani * to it a specific USB CONTROL request with a code different for each wheel. 9c49c3363SDario Pagani * 10c49c3363SDario Pagani * This driver tries to understand which model of Thrustmaster wheel the generic 11c49c3363SDario Pagani * "Thrustmaster FFB Wheel" really is and then sends the appropriate control code. 12c49c3363SDario Pagani * 13c49c3363SDario Pagani * Copyright (c) 2020-2021 Dario Pagani <dario.pagani.146+linuxk@gmail.com> 14c49c3363SDario Pagani * Copyright (c) 2020-2021 Kim Kuparinen <kimi.h.kuparinen@gmail.com> 15c49c3363SDario Pagani */ 16c49c3363SDario Pagani #include <linux/hid.h> 17c49c3363SDario Pagani #include <linux/usb.h> 18c49c3363SDario Pagani #include <linux/input.h> 19c49c3363SDario Pagani #include <linux/slab.h> 20c49c3363SDario Pagani #include <linux/module.h> 21c49c3363SDario Pagani 22c49c3363SDario Pagani /** 23c49c3363SDario Pagani * These interrupts are used to prevent a nasty crash when initializing the 24c49c3363SDario Pagani * T300RS. Used in thrustmaster_interrupts(). 25c49c3363SDario Pagani */ 26c49c3363SDario Pagani static const u8 setup_0[] = { 0x42, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; 27c49c3363SDario Pagani static const u8 setup_1[] = { 0x0a, 0x04, 0x90, 0x03, 0x00, 0x00, 0x00, 0x00 }; 28c49c3363SDario Pagani static const u8 setup_2[] = { 0x0a, 0x04, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00 }; 29c49c3363SDario Pagani static const u8 setup_3[] = { 0x0a, 0x04, 0x12, 0x10, 0x00, 0x00, 0x00, 0x00 }; 30c49c3363SDario Pagani static const u8 setup_4[] = { 0x0a, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00 }; 31c49c3363SDario Pagani static const u8 *const setup_arr[] = { setup_0, setup_1, setup_2, setup_3, setup_4 }; 32c49c3363SDario Pagani static const unsigned int setup_arr_sizes[] = { 33c49c3363SDario Pagani ARRAY_SIZE(setup_0), 34c49c3363SDario Pagani ARRAY_SIZE(setup_1), 35c49c3363SDario Pagani ARRAY_SIZE(setup_2), 36c49c3363SDario Pagani ARRAY_SIZE(setup_3), 37c49c3363SDario Pagani ARRAY_SIZE(setup_4) 38c49c3363SDario Pagani }; 39c49c3363SDario Pagani /** 40c49c3363SDario Pagani * This struct contains for each type of 41c49c3363SDario Pagani * Thrustmaster wheel 42c49c3363SDario Pagani * 43c49c3363SDario Pagani * Note: The values are stored in the CPU 44c49c3363SDario Pagani * endianness, the USB protocols always use 45c49c3363SDario Pagani * little endian; the macro cpu_to_le[BIT]() 46c49c3363SDario Pagani * must be used when preparing USB packets 47c49c3363SDario Pagani * and vice-versa 48c49c3363SDario Pagani */ 49c49c3363SDario Pagani struct tm_wheel_info { 50c49c3363SDario Pagani uint16_t wheel_type; 51c49c3363SDario Pagani 52c49c3363SDario Pagani /** 53c49c3363SDario Pagani * See when the USB control out packet is prepared... 54c49c3363SDario Pagani * @TODO The TMX seems to require multiple control codes to switch. 55c49c3363SDario Pagani */ 56c49c3363SDario Pagani uint16_t switch_value; 57c49c3363SDario Pagani 58c49c3363SDario Pagani char const *const wheel_name; 59c49c3363SDario Pagani }; 60c49c3363SDario Pagani 61c49c3363SDario Pagani /** 62c49c3363SDario Pagani * Known wheels. 63c49c3363SDario Pagani * Note: TMX does not work as it requires 2 control packets 64c49c3363SDario Pagani */ 65c49c3363SDario Pagani static const struct tm_wheel_info tm_wheels_infos[] = { 66c49c3363SDario Pagani {0x0306, 0x0006, "Thrustmaster T150RS"}, 67c49c3363SDario Pagani {0x0206, 0x0005, "Thrustmaster T300RS"}, 68c49c3363SDario Pagani {0x0204, 0x0005, "Thrustmaster T300 Ferrari Alcantara Edition"}, 69c49c3363SDario Pagani {0x0002, 0x0002, "Thrustmaster T500RS"} 70c49c3363SDario Pagani //{0x0407, 0x0001, "Thrustmaster TMX"} 71c49c3363SDario Pagani }; 72c49c3363SDario Pagani 73c49c3363SDario Pagani static const uint8_t tm_wheels_infos_length = 4; 74c49c3363SDario Pagani 75c49c3363SDario Pagani /** 76c49c3363SDario Pagani * This structs contains (in little endian) the response data 77c49c3363SDario Pagani * of the wheel to the request 73 78c49c3363SDario Pagani * 79c49c3363SDario Pagani * A sufficient research to understand what each field does is not 80c49c3363SDario Pagani * beign conducted yet. The position and meaning of fields are a 81c49c3363SDario Pagani * just a very optimistic guess based on instinct.... 82c49c3363SDario Pagani */ 83c49c3363SDario Pagani struct __packed tm_wheel_response 84c49c3363SDario Pagani { 85c49c3363SDario Pagani /** 86c49c3363SDario Pagani * Seems to be the type of packet 87c49c3363SDario Pagani * - 0x0049 if is data.a (15 bytes) 88c49c3363SDario Pagani * - 0x0047 if is data.b (7 bytes) 89c49c3363SDario Pagani */ 90c49c3363SDario Pagani uint16_t type; 91c49c3363SDario Pagani 92c49c3363SDario Pagani union { 93c49c3363SDario Pagani struct __packed { 94c49c3363SDario Pagani uint16_t field0; 95c49c3363SDario Pagani uint16_t field1; 96c49c3363SDario Pagani /** 97c49c3363SDario Pagani * Seems to be the model code of the wheel 98c49c3363SDario Pagani * Read table thrustmaster_wheels to values 99c49c3363SDario Pagani */ 100c49c3363SDario Pagani uint16_t model; 101c49c3363SDario Pagani 102c49c3363SDario Pagani uint16_t field2; 103c49c3363SDario Pagani uint16_t field3; 104c49c3363SDario Pagani uint16_t field4; 105c49c3363SDario Pagani uint16_t field5; 106c49c3363SDario Pagani } a; 107c49c3363SDario Pagani struct __packed { 108c49c3363SDario Pagani uint16_t field0; 109c49c3363SDario Pagani uint16_t field1; 110c49c3363SDario Pagani uint16_t model; 111c49c3363SDario Pagani } b; 112c49c3363SDario Pagani } data; 113c49c3363SDario Pagani }; 114c49c3363SDario Pagani 115c49c3363SDario Pagani struct tm_wheel { 116c49c3363SDario Pagani struct usb_device *usb_dev; 117c49c3363SDario Pagani struct urb *urb; 118c49c3363SDario Pagani 119c49c3363SDario Pagani struct usb_ctrlrequest *model_request; 120c49c3363SDario Pagani struct tm_wheel_response *response; 121c49c3363SDario Pagani 122c49c3363SDario Pagani struct usb_ctrlrequest *change_request; 123c49c3363SDario Pagani }; 124c49c3363SDario Pagani 125c49c3363SDario Pagani /** The control packet to send to wheel */ 126c49c3363SDario Pagani static const struct usb_ctrlrequest model_request = { 127c49c3363SDario Pagani .bRequestType = 0xc1, 128c49c3363SDario Pagani .bRequest = 73, 129c49c3363SDario Pagani .wValue = 0, 130c49c3363SDario Pagani .wIndex = 0, 131c49c3363SDario Pagani .wLength = cpu_to_le16(0x0010) 132c49c3363SDario Pagani }; 133c49c3363SDario Pagani 134c49c3363SDario Pagani static const struct usb_ctrlrequest change_request = { 135c49c3363SDario Pagani .bRequestType = 0x41, 136c49c3363SDario Pagani .bRequest = 83, 137c49c3363SDario Pagani .wValue = 0, // Will be filled by the driver 138c49c3363SDario Pagani .wIndex = 0, 139c49c3363SDario Pagani .wLength = 0 140c49c3363SDario Pagani }; 141c49c3363SDario Pagani 142c49c3363SDario Pagani /** 143c49c3363SDario Pagani * On some setups initializing the T300RS crashes the kernel, 144c49c3363SDario Pagani * these interrupts fix that particular issue. So far they haven't caused any 145c49c3363SDario Pagani * adverse effects in other wheels. 146c49c3363SDario Pagani */ 147c49c3363SDario Pagani static void thrustmaster_interrupts(struct hid_device *hdev) 148c49c3363SDario Pagani { 149c49c3363SDario Pagani int ret, trans, i, b_ep; 150c49c3363SDario Pagani u8 *send_buf = kmalloc(256, GFP_KERNEL); 151c49c3363SDario Pagani struct usb_host_endpoint *ep; 152c49c3363SDario Pagani struct device *dev = &hdev->dev; 153c49c3363SDario Pagani struct usb_interface *usbif = to_usb_interface(dev->parent); 154c49c3363SDario Pagani struct usb_device *usbdev = interface_to_usbdev(usbif); 155c49c3363SDario Pagani 156c49c3363SDario Pagani if (!send_buf) { 157c49c3363SDario Pagani hid_err(hdev, "failed allocating send buffer\n"); 158c49c3363SDario Pagani return; 159c49c3363SDario Pagani } 160c49c3363SDario Pagani 161c49c3363SDario Pagani ep = &usbif->cur_altsetting->endpoint[1]; 162c49c3363SDario Pagani b_ep = ep->desc.bEndpointAddress; 163c49c3363SDario Pagani 164c49c3363SDario Pagani for (i = 0; i < ARRAY_SIZE(setup_arr); ++i) { 165c49c3363SDario Pagani memcpy(send_buf, setup_arr[i], setup_arr_sizes[i]); 166c49c3363SDario Pagani 167c49c3363SDario Pagani ret = usb_interrupt_msg(usbdev, 168c49c3363SDario Pagani usb_sndintpipe(usbdev, b_ep), 169c49c3363SDario Pagani send_buf, 170c49c3363SDario Pagani setup_arr_sizes[i], 171c49c3363SDario Pagani &trans, 172c49c3363SDario Pagani USB_CTRL_SET_TIMEOUT); 173c49c3363SDario Pagani 174c49c3363SDario Pagani if (ret) { 175c49c3363SDario Pagani hid_err(hdev, "setup data couldn't be sent\n"); 176c49c3363SDario Pagani return; 177c49c3363SDario Pagani } 178c49c3363SDario Pagani } 179c49c3363SDario Pagani 180c49c3363SDario Pagani kfree(send_buf); 181c49c3363SDario Pagani } 182c49c3363SDario Pagani 183c49c3363SDario Pagani static void thrustmaster_change_handler(struct urb *urb) 184c49c3363SDario Pagani { 185c49c3363SDario Pagani struct hid_device *hdev = urb->context; 186c49c3363SDario Pagani 187c49c3363SDario Pagani // The wheel seems to kill himself before answering the host and therefore is violating the USB protocol... 188c49c3363SDario Pagani if (urb->status == 0 || urb->status == -EPROTO || urb->status == -EPIPE) 189c49c3363SDario Pagani hid_info(hdev, "Success?! The wheel should have been initialized!\n"); 190c49c3363SDario Pagani else 191c49c3363SDario Pagani hid_warn(hdev, "URB to change wheel mode seems to have failed with error %d\n", urb->status); 192c49c3363SDario Pagani } 193c49c3363SDario Pagani 194c49c3363SDario Pagani /** 195c49c3363SDario Pagani * Called by the USB subsystem when the wheel responses to our request 196c49c3363SDario Pagani * to get [what it seems to be] the wheel's model. 197c49c3363SDario Pagani * 198c49c3363SDario Pagani * If the model id is recognized then we send an opportune USB CONTROL REQUEST 199c49c3363SDario Pagani * to switch the wheel to its full capabilities 200c49c3363SDario Pagani */ 201c49c3363SDario Pagani static void thrustmaster_model_handler(struct urb *urb) 202c49c3363SDario Pagani { 203c49c3363SDario Pagani struct hid_device *hdev = urb->context; 204c49c3363SDario Pagani struct tm_wheel *tm_wheel = hid_get_drvdata(hdev); 205c49c3363SDario Pagani uint16_t model = 0; 206c49c3363SDario Pagani int i, ret; 207c49c3363SDario Pagani const struct tm_wheel_info *twi = 0; 208c49c3363SDario Pagani 209c49c3363SDario Pagani if (urb->status) { 210c49c3363SDario Pagani hid_err(hdev, "URB to get model id failed with error %d\n", urb->status); 211c49c3363SDario Pagani return; 212c49c3363SDario Pagani } 213c49c3363SDario Pagani 214c49c3363SDario Pagani if (tm_wheel->response->type == cpu_to_le16(0x49)) 215c49c3363SDario Pagani model = le16_to_cpu(tm_wheel->response->data.a.model); 216c49c3363SDario Pagani else if (tm_wheel->response->type == cpu_to_le16(0x47)) 217c49c3363SDario Pagani model = le16_to_cpu(tm_wheel->response->data.b.model); 218c49c3363SDario Pagani else { 219c49c3363SDario Pagani hid_err(hdev, "Unknown packet type 0x%x, unable to proceed further with wheel init\n", tm_wheel->response->type); 220c49c3363SDario Pagani return; 221c49c3363SDario Pagani } 222c49c3363SDario Pagani 223c49c3363SDario Pagani for (i = 0; i < tm_wheels_infos_length && !twi; i++) 224c49c3363SDario Pagani if (tm_wheels_infos[i].wheel_type == model) 225c49c3363SDario Pagani twi = tm_wheels_infos + i; 226c49c3363SDario Pagani 227c49c3363SDario Pagani if (twi) 228c49c3363SDario Pagani hid_info(hdev, "Wheel with model id 0x%x is a %s\n", model, twi->wheel_name); 229c49c3363SDario Pagani else { 230c49c3363SDario Pagani hid_err(hdev, "Unknown wheel's model id 0x%x, unable to proceed further with wheel init\n", model); 231c49c3363SDario Pagani return; 232c49c3363SDario Pagani } 233c49c3363SDario Pagani 234c49c3363SDario Pagani tm_wheel->change_request->wValue = cpu_to_le16(twi->switch_value); 235c49c3363SDario Pagani usb_fill_control_urb( 236c49c3363SDario Pagani tm_wheel->urb, 237c49c3363SDario Pagani tm_wheel->usb_dev, 238c49c3363SDario Pagani usb_sndctrlpipe(tm_wheel->usb_dev, 0), 239c49c3363SDario Pagani (char *)tm_wheel->change_request, 240c49c3363SDario Pagani 0, 0, // We do not expect any response from the wheel 241c49c3363SDario Pagani thrustmaster_change_handler, 242c49c3363SDario Pagani hdev 243c49c3363SDario Pagani ); 244c49c3363SDario Pagani 245c49c3363SDario Pagani ret = usb_submit_urb(tm_wheel->urb, GFP_ATOMIC); 246c49c3363SDario Pagani if (ret) 247c49c3363SDario Pagani hid_err(hdev, "Error %d while submitting the change URB. I am unable to initialize this wheel...\n", ret); 248c49c3363SDario Pagani } 249c49c3363SDario Pagani 250c49c3363SDario Pagani static void thrustmaster_remove(struct hid_device *hdev) 251c49c3363SDario Pagani { 252c49c3363SDario Pagani struct tm_wheel *tm_wheel = hid_get_drvdata(hdev); 253c49c3363SDario Pagani 254c49c3363SDario Pagani usb_kill_urb(tm_wheel->urb); 255c49c3363SDario Pagani 256c49c3363SDario Pagani kfree(tm_wheel->response); 257c49c3363SDario Pagani kfree(tm_wheel->model_request); 258c49c3363SDario Pagani usb_free_urb(tm_wheel->urb); 259c49c3363SDario Pagani kfree(tm_wheel); 260c49c3363SDario Pagani 261c49c3363SDario Pagani hid_hw_stop(hdev); 262c49c3363SDario Pagani } 263c49c3363SDario Pagani 264c49c3363SDario Pagani /** 265c49c3363SDario Pagani * Function called by HID when a hid Thrustmaster FFB wheel is connected to the host. 266c49c3363SDario Pagani * This function starts the hid dev, tries to allocate the tm_wheel data structure and 267c49c3363SDario Pagani * finally send an USB CONTROL REQUEST to the wheel to get [what it seems to be] its 268c49c3363SDario Pagani * model type. 269c49c3363SDario Pagani */ 270c49c3363SDario Pagani static int thrustmaster_probe(struct hid_device *hdev, const struct hid_device_id *id) 271c49c3363SDario Pagani { 272c49c3363SDario Pagani int ret = 0; 273c49c3363SDario Pagani struct tm_wheel *tm_wheel = 0; 274c49c3363SDario Pagani 275c49c3363SDario Pagani ret = hid_parse(hdev); 276c49c3363SDario Pagani if (ret) { 277c49c3363SDario Pagani hid_err(hdev, "parse failed with error %d\n", ret); 278c49c3363SDario Pagani goto error0; 279c49c3363SDario Pagani } 280c49c3363SDario Pagani 281c49c3363SDario Pagani ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF); 282c49c3363SDario Pagani if (ret) { 283c49c3363SDario Pagani hid_err(hdev, "hw start failed with error %d\n", ret); 284c49c3363SDario Pagani goto error0; 285c49c3363SDario Pagani } 286c49c3363SDario Pagani 287c49c3363SDario Pagani // Now we allocate the tm_wheel 288c49c3363SDario Pagani tm_wheel = kzalloc(sizeof(struct tm_wheel), GFP_KERNEL); 289c49c3363SDario Pagani if (!tm_wheel) { 290c49c3363SDario Pagani ret = -ENOMEM; 291c49c3363SDario Pagani goto error1; 292c49c3363SDario Pagani } 293c49c3363SDario Pagani 294c49c3363SDario Pagani tm_wheel->urb = usb_alloc_urb(0, GFP_ATOMIC); 295c49c3363SDario Pagani if (!tm_wheel->urb) { 296c49c3363SDario Pagani ret = -ENOMEM; 297c49c3363SDario Pagani goto error2; 298c49c3363SDario Pagani } 299c49c3363SDario Pagani 300*66ff8994Skernel test robot tm_wheel->model_request = kmemdup(&model_request, 301*66ff8994Skernel test robot sizeof(struct usb_ctrlrequest), 302*66ff8994Skernel test robot GFP_KERNEL); 303c49c3363SDario Pagani if (!tm_wheel->model_request) { 304c49c3363SDario Pagani ret = -ENOMEM; 305c49c3363SDario Pagani goto error3; 306c49c3363SDario Pagani } 307c49c3363SDario Pagani 308c49c3363SDario Pagani tm_wheel->response = kzalloc(sizeof(struct tm_wheel_response), GFP_KERNEL); 309c49c3363SDario Pagani if (!tm_wheel->response) { 310c49c3363SDario Pagani ret = -ENOMEM; 311c49c3363SDario Pagani goto error4; 312c49c3363SDario Pagani } 313c49c3363SDario Pagani 314c49c3363SDario Pagani tm_wheel->change_request = kzalloc(sizeof(struct usb_ctrlrequest), GFP_KERNEL); 315c49c3363SDario Pagani if (!tm_wheel->model_request) { 316c49c3363SDario Pagani ret = -ENOMEM; 317c49c3363SDario Pagani goto error5; 318c49c3363SDario Pagani } 319c49c3363SDario Pagani memcpy(tm_wheel->change_request, &change_request, sizeof(struct usb_ctrlrequest)); 320c49c3363SDario Pagani 321c49c3363SDario Pagani tm_wheel->usb_dev = interface_to_usbdev(to_usb_interface(hdev->dev.parent)); 322c49c3363SDario Pagani hid_set_drvdata(hdev, tm_wheel); 323c49c3363SDario Pagani 324c49c3363SDario Pagani thrustmaster_interrupts(hdev); 325c49c3363SDario Pagani 326c49c3363SDario Pagani usb_fill_control_urb( 327c49c3363SDario Pagani tm_wheel->urb, 328c49c3363SDario Pagani tm_wheel->usb_dev, 329c49c3363SDario Pagani usb_rcvctrlpipe(tm_wheel->usb_dev, 0), 330c49c3363SDario Pagani (char *)tm_wheel->model_request, 331c49c3363SDario Pagani tm_wheel->response, 332c49c3363SDario Pagani sizeof(struct tm_wheel_response), 333c49c3363SDario Pagani thrustmaster_model_handler, 334c49c3363SDario Pagani hdev 335c49c3363SDario Pagani ); 336c49c3363SDario Pagani 337c49c3363SDario Pagani ret = usb_submit_urb(tm_wheel->urb, GFP_ATOMIC); 338c49c3363SDario Pagani if (ret) 339c49c3363SDario Pagani hid_err(hdev, "Error %d while submitting the URB. I am unable to initialize this wheel...\n", ret); 340c49c3363SDario Pagani 341c49c3363SDario Pagani return ret; 342c49c3363SDario Pagani 343c49c3363SDario Pagani error5: kfree(tm_wheel->response); 344c49c3363SDario Pagani error4: kfree(tm_wheel->model_request); 345c49c3363SDario Pagani error3: usb_free_urb(tm_wheel->urb); 346c49c3363SDario Pagani error2: kfree(tm_wheel); 347c49c3363SDario Pagani error1: hid_hw_stop(hdev); 348c49c3363SDario Pagani error0: 349c49c3363SDario Pagani return ret; 350c49c3363SDario Pagani } 351c49c3363SDario Pagani 352c49c3363SDario Pagani static const struct hid_device_id thrustmaster_devices[] = { 353c49c3363SDario Pagani { HID_USB_DEVICE(0x044f, 0xb65d)}, 354c49c3363SDario Pagani {} 355c49c3363SDario Pagani }; 356c49c3363SDario Pagani 357c49c3363SDario Pagani MODULE_DEVICE_TABLE(hid, thrustmaster_devices); 358c49c3363SDario Pagani 359c49c3363SDario Pagani static struct hid_driver thrustmaster_driver = { 360c49c3363SDario Pagani .name = "hid-thrustmaster", 361c49c3363SDario Pagani .id_table = thrustmaster_devices, 362c49c3363SDario Pagani .probe = thrustmaster_probe, 363c49c3363SDario Pagani .remove = thrustmaster_remove, 364c49c3363SDario Pagani }; 365c49c3363SDario Pagani 366c49c3363SDario Pagani module_hid_driver(thrustmaster_driver); 367c49c3363SDario Pagani 368c49c3363SDario Pagani MODULE_AUTHOR("Dario Pagani <dario.pagani.146+linuxk@gmail.com>"); 369c49c3363SDario Pagani MODULE_LICENSE("GPL"); 370c49c3363SDario Pagani MODULE_DESCRIPTION("Driver to initialize some steering wheel joysticks from Thrustmaster"); 371c49c3363SDario Pagani 372