From 75006183d0a379a2b0fa844fc32b3e27d229f368 Mon Sep 17 00:00:00 2001 From: RaySlash Date: Mon, 2 Oct 2023 13:08:52 +1000 Subject: src: initial refactoring --- .gitmodules | 6 +- Kbuild | 2 - Makefile | 16 - README.md | 65 +- TODO | 7 - dkms-install.sh | 19 - dkms.conf | 18 - dkms/dkms-install.sh | 19 + dkms/dkms.conf | 18 + docs/CONTRIBUTING.md | 41 + docs/FFBEffects-T248.md | 7 + docs/FFBEffects-T300RS.md | 542 +++++++++++++ docs/TODO.md | 10 + force-effects.txt | 462 ----------- hid-tmff2.c | 749 ----------------- hid-tmff2.h | 147 ---- hid-tminit | 1 - hid-tmt248.c | 335 -------- hid-tmt300rs.c | 1714 --------------------------------------- hid-tmt300rs.h | 9 - hid-tmtx.c | 323 -------- ps4-input.txt | 54 -- src/Kbuild | 2 + src/Makefile | 16 + src/hid-tmff2.c | 749 +++++++++++++++++ src/hid-tmff2.h | 147 ++++ src/hid-tminit | 1 + src/hid-tmt248/hid-tmt248.c | 335 ++++++++ src/hid-tmt300rs/hid-tmt300rs.c | 1714 +++++++++++++++++++++++++++++++++++++++ src/hid-tmt300rs/hid-tmt300rs.h | 9 + src/hid-tmtx/hid-tmtx.c | 323 ++++++++ t248-effects.txt | 2 - 32 files changed, 3962 insertions(+), 3900 deletions(-) delete mode 100644 Kbuild delete mode 100644 Makefile delete mode 100644 TODO delete mode 100755 dkms-install.sh delete mode 100644 dkms.conf create mode 100755 dkms/dkms-install.sh create mode 100644 dkms/dkms.conf create mode 100644 docs/CONTRIBUTING.md create mode 100644 docs/FFBEffects-T248.md create mode 100644 docs/FFBEffects-T300RS.md create mode 100644 docs/TODO.md delete mode 100644 force-effects.txt delete mode 100644 hid-tmff2.c delete mode 100644 hid-tmff2.h delete mode 160000 hid-tminit delete mode 100644 hid-tmt248.c delete mode 100644 hid-tmt300rs.c delete mode 100644 hid-tmt300rs.h delete mode 100644 hid-tmtx.c delete mode 100644 ps4-input.txt create mode 100644 src/Kbuild create mode 100644 src/Makefile create mode 100644 src/hid-tmff2.c create mode 100644 src/hid-tmff2.h create mode 160000 src/hid-tminit create mode 100644 src/hid-tmt248/hid-tmt248.c create mode 100644 src/hid-tmt300rs/hid-tmt300rs.c create mode 100644 src/hid-tmt300rs/hid-tmt300rs.h create mode 100644 src/hid-tmtx/hid-tmtx.c delete mode 100644 t248-effects.txt diff --git a/.gitmodules b/.gitmodules index 9415bdf..6038e0c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ -[submodule "hid-tminit"] - path = hid-tminit - url = https://github.com/scarburato/hid-tminit.git +[submodule "src/hid-tminit"] + path = src/hid-tminit + url = https://github.com/scarburato/hid-tminit diff --git a/Kbuild b/Kbuild deleted file mode 100644 index 619792c..0000000 --- a/Kbuild +++ /dev/null @@ -1,2 +0,0 @@ -obj-m := hid-tmff-new.o -hid-tmff-new-y := hid-tmff2.o hid-tmt300rs.o hid-tmt248.o hid-tmtx.o diff --git a/Makefile b/Makefile deleted file mode 100644 index 5e2ab40..0000000 --- a/Makefile +++ /dev/null @@ -1,16 +0,0 @@ -KDIR ?= /lib/modules/$(shell uname -r)/build - -all: hid-tminit - $(MAKE) -C $(KDIR) M=$(shell pwd) modules - -install: hid-tminit - $(MAKE) -C $(KDIR) M=$(shell pwd) modules_install - depmod -A - -clean: hid-tminit - $(MAKE) -C $(KDIR) M=$(shell pwd) clean - - -.PHONY: hid-tminit -hid-tminit: - $(MAKE) -C hid-tminit KDIR="$(KDIR)" $(MAKECMDGOALS) diff --git a/README.md b/README.md index bd06fc8..a0e075c 100644 --- a/README.md +++ b/README.md @@ -1,67 +1,57 @@ -# Linux kernel module for Thrustmaster T300RS, T248 and (experimental) TX wheels +# Linux kernel module for Thrustmaster T300RS, T248 and TX(experimental) wheels -## Current state -Playable. I've made some improvements to the dynamic updating of effects, and -while still far for perfect, the experience is slowly getting better and better. -Some drawbacks include possible effect inaccuracies in comparison with the Windows driver and in some -games inconsistent pedal mapping. Meaning that all pedals should be detected in games, but may be mapped incorrectly. +## Description +A Linux kernel module for Thrustmaster T300RS, T248, and TX wheels is a software component designed to enhance the compatibility and functionality of these popular racing wheel peripherals when used with Linux-based operating systems. This kernel module allows seamless integration of Thrustmaster wheels with the Linux kernel, providing users with a robust and feature-rich experience for gaming and other applications. -Anycase, **this version is usable in most force feedback games, supports -rangesetting as well as gain and autocentering along with most force feedback effects.** +I've made some improvements to the dynamic updating of effects, and while still far for perfect, the experience is slowly getting better and better. Some drawbacks include possible effect inaccuracies in comparison with the Windows driver and in some games inconsistent pedal mapping. Meaning that all pedals should be detected in games, but may be mapped incorrectly. -### Help wanted with adding more Thrustmaster wheels +> **Current version ``0.8`` is USABLE in most force feedback games, supports rangesetting as well as gain and autocentering along with most force feedback effects.** While I haven't personally come across any crashes or lockups with this version, I can't promise that they won't occur under any circumstances. +### Help wanted with adding more Thrustmaster wheels Currently open requests for wheels: - + [T500 RS](https://github.com/Kimplul/hid-tmff2/issues/18) + [T818](https://github.com/Kimplul/hid-tmff2/issues/58) + [T-GT II](https://github.com/Kimplul/hid-tmff2/issues/55) -If you would like to help add a wheel to this driver, please have a look through the -[wiki](https://github.com/Kimplul/hid-tmff2/wiki#how-to-add-in-support-for-a-new-t-series-wheel) for what might need to be done. -If you have a wheel that's not on the list, but suspect it might fit into the driver, please feel free to open up an issue about it. - -## Small note - -While I haven't personally come across any crashes or lockups with this -version, I can't promise that they won't occur under any circumstances. - -With that in mind, +If you would like to help add a wheel to this driver, please have a look through the [wiki](https://github.com/Kimplul/hid-tmff2/wiki#how-to-add-in-support-for-a-new-t-series-wheel) and/or [CONTRIBUTING.md](./docs/CONTRIBUTING.md) for what might need to be done. If you have a wheel that's not on the list, but suspect it might fit into the driver, please feel free to open up an issue about it. ## Installation - ### Dependencies - Kernel modules require kernel headers to be installed. -+ Debian-based: `apt install linux-headers-$(uname -r)` -+ Arch-based: `pacman -S linux-headers` -+ Fedora-based: `yum install kernel-devel kernel-headers` ++ Debian-based: ``sudo apt install linux-headers-$(uname -r)`` ++ Arch-based: ``sudo pacman -S linux-headers`` ++ Fedora-based: ``sudo yum install kernel-devel kernel-headers`` +> **NOTE:** See [here](https://github.com/Kimplul/hid-tmff2/wiki/Integrating-driver-into-distros) for install instructions for other linux distributions. ### Manual installation - + Unplug wheel from computer -+ `git clone --recurse-submodules https://github.com/Kimplul/hid-tmff2.git` -+ `make` -+ `sudo make install` +```shell +git clone --recurse-submodules https://github.com/Kimplul/hid-tmff2.git +cd hid-tmff2/src +make +sudo make install +``` + Plug wheel back in -+ reboot (not strictly necessary, but definitely recommended) ++ Reboot (not strictly necessary, but definitely recommended) Done! -> Note: On some systems, you will get an error/warning about SSL. This is normal for unsigned modules. For info on signing modules yourself (completely optional), see [here](https://www.kernel.org/doc/html/latest/admin-guide/module-signing.html?highlight=module%20signing). +> **NOTE:** On some systems, you will get an error/warning about SSL. This is normal for unsigned modules. For info on signing modules yourself (completely optional), see [here](https://www.kernel.org/doc/html/latest/admin-guide/module-signing.html?highlight=module%20signing). -> Note: Thrustmaster TX wheels aren't supported by `hid-tminit` as of yet, meaning that TX wheels have to be initialized with `tmrd`. Please see https://github.com/Kimplul/hid-tmff2/issues/48. +> **NOTE:** Thrustmaster TX wheels aren't supported by `hid-tminit` as of yet, meaning that TX wheels have to be initialized with `tmrd`. Please see https://github.com/Kimplul/hid-tmff2/issues/48. ### DKMS - + Unplug wheel from computer -+ `sudo ./dkms-install.sh` +```shell +sudo bash ./dkms/dkms-install.sh +``` + Plug wheel back in + reboot (not strictly necessary, but definitely recommended) Done! + > :warning: Warning: There have been reports that this driver does not work if the wheel's firmware version is older than v. 31. > To update the firmware, you will have to fire up a Windows installation and update the firmware using the official Thrustmaster tools. @@ -69,13 +59,10 @@ Done! > and you may have to uninstall the older version of the driver. ## Additional tidbits - -+ Reportedly some games running under Wine/Proton won't recognize wheels without the official Thrustmaster drivers installed within the prefix. See [#46](https://github.com/Kimplul/hid-tmff2/issues/46#issuecomment-1199080845). For installation instructions, see [wiki](https://github.com/Kimplul/hid-tmff2/wiki) - - Note that you will still need the Linux driver, the Windows driver just installs some files needed by games to correctly recognize the Linux driver. The Windows driver itself does not work under Wine/Proton. ++ Reportedly some games running under Wine/Proton won't recognize wheels without the official Thrustmaster drivers installed within the prefix. See [#46](https://github.com/Kimplul/hid-tmff2/issues/46#issuecomment-1199080845). For installation instructions, see [wiki](https://github.com/Kimplul/hid-tmff2/wiki). Note that you will still need the Linux driver, the Windows driver just installs some files needed by games to correctly recognize the Linux driver. The Windows driver itself does not work under Wine/Proton. + Until the updated `hid-tminit` is upstreamed, you might want to blacklist the kernel module `hid-thrustmaster`. Do this with - ``` + ```shell echo 'blacklist hid_thrustmaster' > /etc/modprobe.d/hid_thrustmaster.con ``` diff --git a/TODO b/TODO deleted file mode 100644 index 70d3652..0000000 --- a/TODO +++ /dev/null @@ -1,7 +0,0 @@ -+ Make code more general to allow for stuff like T150/T248/T500 to be relatively -easily included. - -+ Use workqueues instead of doing everything in t300rs_timer - -+ T248 open/close seems to crash the wheel and the kernel along with it, check how Windows handles it -+ T248 fix rdesc, 0x0a somewhere needs to be set to 0x60 diff --git a/dkms-install.sh b/dkms-install.sh deleted file mode 100755 index 6e77e80..0000000 --- a/dkms-install.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/sh - -if ! [ $(id -u) = 0 ]; then - echo "You must run this script as root!" - exit 1 -fi - -TM_NAME=hid-tmff2 -TM_VER=0.8 -DST="/usr/src/${TM_NAME}-${TM_VER}" - -mkdir -p "${DST}" -cp -r ./* "${DST}" - -dkms add -m "${TM_NAME}" -v "${TM_VER}" -dkms build -m "${TM_NAME}" -v "${TM_VER}" -dkms install -m "${TM_NAME}" -v "${TM_VER}" - -exit $? diff --git a/dkms.conf b/dkms.conf deleted file mode 100644 index 25ef2ca..0000000 --- a/dkms.conf +++ /dev/null @@ -1,18 +0,0 @@ -PACKAGE_NAME="hid-tmff2" -PACKAGE_VERSION="0.8" -AUTOINSTALL="yes" - -# By default, DKMS will add KERNELRELEASE to the make command line; however, -# this will cause the kernel module build to infer that it was invoked via -# Kbuild directly instead of DKMS. The dkms(8) manual page recommends quoting -# the 'make' command name to suppress this behavior. -MAKE[0]="'make' KDIR=\"$kernel_source_dir\"" -CLEAN[0]="'make' KDIR=\"$kernel_source_dir\" clean" - -# The list of kernel modules. -#__DKMS_MODULES -BUILT_MODULE_NAME[0]="hid-tminit" -BUILT_MODULE_LOCATION="hid-tminit/" -DEST_MODULE_LOCATION[0]="/kernel/drivers/hid" -BUILT_MODULE_NAME[1]="hid-tmff-new" -DEST_MODULE_LOCATION[1]="/kernel/drivers/hid" diff --git a/dkms/dkms-install.sh b/dkms/dkms-install.sh new file mode 100755 index 0000000..f77eec4 --- /dev/null +++ b/dkms/dkms-install.sh @@ -0,0 +1,19 @@ +#!/bin/sh + +if ! [ $(id -u) = 0 ]; then + echo "You must run this script as root!" + exit 1 +fi + +TM_NAME=hid-tmff2 +TM_VER=0.8 +DST="/usr/src/${TM_NAME}-${TM_VER}" + +mkdir -p "${DST}" +cp -r ../src/* "${DST}" + +dkms add -m "${TM_NAME}" -v "${TM_VER}" +dkms build -m "${TM_NAME}" -v "${TM_VER}" +dkms install -m "${TM_NAME}" -v "${TM_VER}" + +exit $? diff --git a/dkms/dkms.conf b/dkms/dkms.conf new file mode 100644 index 0000000..25ef2ca --- /dev/null +++ b/dkms/dkms.conf @@ -0,0 +1,18 @@ +PACKAGE_NAME="hid-tmff2" +PACKAGE_VERSION="0.8" +AUTOINSTALL="yes" + +# By default, DKMS will add KERNELRELEASE to the make command line; however, +# this will cause the kernel module build to infer that it was invoked via +# Kbuild directly instead of DKMS. The dkms(8) manual page recommends quoting +# the 'make' command name to suppress this behavior. +MAKE[0]="'make' KDIR=\"$kernel_source_dir\"" +CLEAN[0]="'make' KDIR=\"$kernel_source_dir\" clean" + +# The list of kernel modules. +#__DKMS_MODULES +BUILT_MODULE_NAME[0]="hid-tminit" +BUILT_MODULE_LOCATION="hid-tminit/" +DEST_MODULE_LOCATION[0]="/kernel/drivers/hid" +BUILT_MODULE_NAME[1]="hid-tmff-new" +DEST_MODULE_LOCATION[1]="/kernel/drivers/hid" diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md new file mode 100644 index 0000000..0e2c7b6 --- /dev/null +++ b/docs/CONTRIBUTING.md @@ -0,0 +1,41 @@ +# Contributing + +## Overview +This file contains general contributing information, but individual parts also may have more specific information to them, linked here: + - ``[FFBEffects-T300RS](./FFBEffects-T300RS.md)``: Force Feedback Effects example for T300RS + - ``[FFBEffects-T248](./FFBEffects-T248.md)``: Force Feedback Effects example for T300RS + - ``[To-Do](./TODO.md)``: TO-DO list for maintainers + - ``[Structure](https://github.com/Kimplul/hid-tmff2/wiki#structure-of-the-thrustmaster-device-stack)``: Structure of Thrustmaster device stack + +## How to add in support for a new T-series wheel? +Should probably not be too often that you need this info, but essentially use wireshark like in the previous example, but spin up a Windows virtual machine and install the Thrustmaster drivers on to it, and pass the device to the virtual machine. I prefer to use ``qemu`` with ``virt-manager`` as a frontend. With the wheel working under Windows, install [fedit.exe](https://gimx.fr/download/b882e209a0ac023d03abbf560dfc3f25fe6367ca/fedit.zip) and methodically go through all effects the device supports and compare the USB packets the driver sends out. You should be able to build up a table of what each value in the USB packet means, see [FFBEffects-T300RS.md](./FFBEffects-T300RS.md) for an example of what I found out about the T300. + +## How to capture what effects a game sends to the driver? +Use ffbwrap from ffbtools: [github:berarma/ffbtools](https://github.com/berarma/ffbtools) +The documentation gives good examples, but tl;dr; For steam, insert the following into a game's launch options: +```shell +$ ffbwrap --logger=/home/$USER/game.log /dev/input/by-id/usb-Thrustmaster_Thrustmaster_T300RS_Racing_wheel-event-joystick -- %command% +``` +This will create a file called ``game.log`` (with additional timestamp) in your home directory. Preferably change the name to suit the game, but you do you. + +> **NOTE:** Most fixes presented in the documentation are more or less obsolete by now, but the tool is still very useful for logging purposes. + +## How to capture what USB packets the driver sends to the device? +I'd recommend [wireshark](https://www.wireshark.org/) + +Usb capture setup is fairly straightforward: [wireshark/CaptureSetup](https://wiki.wireshark.org/CaptureSetup/USB#linux) + +Here's what I typically do when starting capturing: + +- Run ``sudo modprobe usbmon``. This will load a kernel module that allows Wireshark to read the USB packets. +- Open wireshark with root privileges. There are some ways to allow wireshark to access the packets with regular user privileges, I just haven't bothered with it. +- Select ``usbmon0`` from the view that opens up by default. +- The screen will quickly fill up with noise from other devices connected to the computer, so you will have to filter out the noise. + - Run ``lsusb`` in a terminal. Look for the T300 in the list, you should see something like ``Bus 001 Device 006: ID 044f:b66e ThrustMaster, Inc. Thrustmaster T300RS Racing wheel`` + - From the previous command, the Bus and Device fields can be used to filter out only packets from/to the device. To see all packets coming from the wheel, add in a filter ``usb.src ~ "1\.6\..*"``, where ``1`` is in this case from the ``Bus`` field and ``6`` from the ``Device`` field. To see all packets coming from the device, use ``usb.dst``. + - This data is probably also filled with a lot of cruft. Data to/from endpoint 2 is button state info, which is probably unnecessary. To see FFB data being sent to the device, use endpoint 1, i.e. ``usb.dst == "1.6.1"``. ``~`` is a Perl-compliant regex operator, whereas ``==`` just matches the string directly. To see both data coming from the device and going to it, use ``usb.dst == "..." || usb.src == "..."``. + - There is also endpoint 0, ``usb.src == "1.6.0"`` but it doesn't seem to be used for much. +- Use the three buttons in the top left of the screen to start, stop and restart captures. +- Do whatever you want with the device, packets should automatically be captured. When you want to save your capture to a file, stop the recording and go to ``File > Export Specified Packets`` and make sure ``Displayed`` is selected. This will apply the filter you've been using, and will only include the packets that are visible in Wireshark, i.e. it applies the filter you've specified. + +> **NOTE:** Every time you unplug and replug your wheel, its ``Device`` field will probably change. diff --git a/docs/FFBEffects-T248.md b/docs/FFBEffects-T248.md new file mode 100644 index 0000000..dfd5971 --- /dev/null +++ b/docs/FFBEffects-T248.md @@ -0,0 +1,7 @@ +# Force Feedback Effects - Thrustmaster T248 + +## Open: +Identical to T300RS + +## Close: +Identical to T300RS diff --git a/docs/FFBEffects-T300RS.md b/docs/FFBEffects-T300RS.md new file mode 100644 index 0000000..1770bdd --- /dev/null +++ b/docs/FFBEffects-T300RS.md @@ -0,0 +1,542 @@ +# Force Feedback Effects - Thrustmaster T300RS (incl. PS3/PS4 modes) +Here I will gather all info I can find out about the different force feedback effects and their USB commands. All values seem to be little-endian. Also, general note, it seems like some effects are affected by the direction of the force, denoted by ``***``. As far as I can tell, ``fade_level`` and ``attack_level`` values are calculated by mapping the ``u16`` into ``s16`` and taking the absolute value, and after that multiplying by the direction. + +> **NOTE:** All values are examples of actual commands that I captured on the USB interface, not the only available ones. + + +## GENERAL +### Playing +``` + 60 00 - standard header + 01 - ID + 89 - playing options + 01 - play + 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +``` + +### Stopping/removing (why are they rolled into one?) +``` + 60 00 - standard header + 01 - ID + 89 - playing options + 00 - stop + 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +``` + +## Open: +``` + 60 01 - standard header? + 04 - ? + 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + + (this command doesn't appear every time, odd) + 60 12 - ? + bf 04 00 00 03 b7 1e - ? /* wow, this is different on different wheels? */ + 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + + 60 01 - ? + 05 - ? + 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +``` + +## Close: +``` + 60 01 - standard header? + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +``` + +## Rotation angle: +``` + 60 08 - header plus settings? + 11 - set rotation angle + 55 d5 - angle (between ff ff and 7b 09, one degree is roughly 3c) + 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +``` + +## FF_AUTOCENTER: +``` + 60 08 - header plus settings? + 03 - set autocenter force + 8f 02 - force (between ff ff and 00 00) + 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +``` + +## FF_GAIN: +``` + 60 02 - header plus gain? + bf - gain (between ff and 00) + 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +``` + +## FF_CONSTANT +### Initialization +``` + 60 00 - standard header + 01 - ID + 6a - new constant effect(?) + fe ff - strength *** + 00 00 - attack_length + 00 00 - attack_level *** + 00 00 - fade_length + 00 00 - fade_level *** + 00 4f - ? + f7 17 - time in milliseconds, unsigned + 00 00 + 07 00 - offset from start(?) + 00 ff ff - end of new effect(?) + 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +``` + +### Modifying (For dynamic updating of effects) + +#### Direction: ``N/A`` + +#### Constant force: +``` +60 00 - standard header +01 - ID +0a - modify constant force +05 16 - constant force *** +00 00 00 00 00 00 00 00 00 00 +00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +``` + +#### Envelope: +``` +60 00 - standard header +01 - ID + 31 - modify envelope +84 - ID of envelope attribute ( attack_level 82, + attack_length 81, + fade_level 88, + fade_length 84 ) + 63 04 - value attribute should be set to ?***? + 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +``` + +#### Duration: +``` + 60 00 - standard header + 01 - ID + 49 00 41 - modify timing? + 6c 20 - length in milliseconds + 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +``` + +#### Offset: ``N/A ?`` + +## FF_RAMP +(Why on earth is it this difficult to figure out how a ramp works?) +### Init: +``` + 60 00 - standard header + 01 - ID + 6b - new ramp effect + f6 7f - difference *** + fe ff - level? *** (level = (if end_level > start_level, end_level, else + start_level) - difference?) (signed) + 00 00 + f7 17 - time + 00 80 - some kind of marker + 00 00 - attack_length + f6 7f - attack_level *** + 00 00 - fade_length + f6 7f - fade_level*** + 04 - invert (going "down", 05, going "up", 04) + 4f + f7 17 - time again? + 00 00 + 00 00 - offset + 00 ff ff - end of init + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +``` +### Modifying: +(Sweet baby jesus. I'll figure this out later ``Ramp(?)``) +``` + 60 00 - standard header + 01 - ID + 0e - ??? + 03 - ramp? + e3 04 - difference? + 7d 75 - "level"? + 05 - ???? + 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +``` +#### Envelope: +``` + 60 00 - standard header + 01 - ID + 31 - modify envelope + 84 - ID of envelope attribute ( attack_level 82, + attack_length 81, + fade_level 88, + fade_length 84 ) + 63 04 - value attribute should be set to + 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +``` +#### Duration: +``` + 60 00 + 01 - ID + 4e - ? + 08 - ? + 01 00 - time in milliseconds + 05 - ? + 41 - ? + 01 00 - time in milliseconds + 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +``` +#### Offset: ``N/A ?`` + +## FF_SPRING: +### Init: +``` + 60 00 - standard header + 01 - ID + 64 - new conditional effect + fc 7f - positive coefficient (right?) + fc 7f - negative coefficient (left?) + fe ff - deadband (left?) (deadband + offset) (fe ff means no deadband) + fe ff - deadband (right?) (deadband + offset) + a6 6a a6 6a fe ff fe ff fe ff fe ff df 58 a6 6a 06 - some weird hard-coded + values to do with springs? + + 4f + f7 17 - duration + 00 00 + 00 00 - offset + 00 ff ff - end of init + 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +``` + +### Modifying: +Same as below (FF_DAMPER + FF_FRICTION + FF_INERTIA) + +## FF_DAMPER + FF_FRICTION + FF_INERTIA: +### Init: +``` + 60 00 - standard header + 02 -ID + 64 - new conditional effect + fc 7f - positive coefficient (right?) + fc 7f - negative coefficient (left?) + fe ff - deadband (left?) (deadband + offset) (fe ff means no deadband) + fe ff - deadband (right?) (deadband + offset) + fc 7f fc 7f fe ff fe ff fe ff fe ff fc 7f fc 7f 07 - some weird hard-coded + values to do with friction? + 4f + f7 17 - duration + 00 00 + 00 00 - offset + 00 ff ff - end of init + 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +``` +### Modifying: +``` + positive coefficient: + 60 00 + 02 - ID + 0e 41 + 64 35 - value, signed (between 00 00 and 01 80?) + 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + + negative coefficient: + 60 00 + 02 - ID + 0e 42 + 64 35 - value, signed (between 00 00 and ff 7f?) + 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + + deadband/offset: + 60 00 + 02 - ID + 0e 4c + 64 35 - deadband (right?) (deadband + offset) (fe ff means no deadband) + 00 00 - deadband (left?) (deadband + offset) + 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +``` + +### Duration: +``` + 60 00 - standard header + 01 - ID + 49 06 41 - modify timing? + 6c 20 - length in milliseconds + 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +``` + +### Offset: ``N/A ?`` + +## FF_PERIODIC: +### Init +``` + 60 00 - standard header + 01 - ID + 6b - new periodic effect + 00 00 - magnitude *** + fe ff - offset (up/down) + 00 00 - phase (left/right) + e8 03 - period + 00 80 + 00 00 - attack_length + 00 00 - attack_level *** + 00 00 - fade_length + 00 00 - fade_level *** + 01 - type of periodic ( square 01, + sine 03, + triangle 02, + sawtooth down 05, + sawtooth up 04 ) + 4f + f7 17 - duration + 00 00 + 00 00 - offset + 00 ff ff - end of init + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +``` + +### Modifying: +``` + magnitude: + 60 00 + 02 - ID + 0e 01 + 64 35 - value, signed (between 00 00 and ff 7f?) + 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + + offset of effect: + 60 00 + 02 - ID + 0e 02 + 64 35 - value, signed (between 00 00 and ff 7f?) + 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + + phase: + 60 00 + 02 - ID + 0e 04 + 64 35 - value, signed (between 80 01 and ff 7f?) + 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + + period: + 60 00 + 02 - ID + 0e 08 + 64 35 - value, signed (between 00 00 and I guess ff 7f?) (max value in + wireshark is 0d 07, but I don't know if that's a driver or program issue) + 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + +``` + +### Envelope: +``` + 60 00 - standard header + 01 - ID + 31 - modify envelope + 84 - ID of envelope attribute ( attack_level 82, + attack_length 81, + fade_level 88, + fade_length 84 ) + 63 04 - value attribute should be set to + 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +``` + +### Duration: +``` + Square: + 60 00 + 01 - ID + 49 - ? + 01 - Effect type + 41 - ? + 4e 0c - Time in milliseconds + 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + + Sine: + 60 00 + 01 - ID + 49 - ? + 03 - Effect type + 41 - ? + ec 13 - Time in milliseconds + 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + + Triangle: + 60 00 + 01 - ID + 49 - ? + 02 - Effect type + 41 - ? + 4e 0c - Time in milliseconds + 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + + Sawtooth up: + 60 00 + 01 - ID + 49 - ? + 04 - Effect type + 41 - ? + 4e 0c - Time in milliseconds + 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + + Sawtooth down: + 60 00 + 01 - ID + 49 - ? + 05 - Effect type + 41 - ? + ec 13 - Time in milliseconds + 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +``` + +### Offset: ``N/A ?`` + +## PS4 Input +``` +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 226 /* wheel */ +ff00.0021 = 125 /* wheel */ +ff00.0021 = 255 /* gas */ +ff00.0021 = 255 /* gas */ +ff00.0021 = 255 /* brake */ +ff00.0021 = 255 /* brake */ +ff00.0021 = 255 /* clutch */ +ff00.0021 = 255 /* clutch */ +ff00.0021 = 0 +ff00.0021 = 255 +ff00.0021 = 255 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +``` diff --git a/docs/TODO.md b/docs/TODO.md new file mode 100644 index 0000000..bd73609 --- /dev/null +++ b/docs/TODO.md @@ -0,0 +1,10 @@ +# TO-DO list + +- Make code more general to allow for stuff like T150/T248/T500 to be relatively +easily included. + +- Use workqueues instead of doing everything in t300rs_timer + +### T248 + - open/close seems to crash the wheel and the kernel along with it, check how Windows handles it + - fix rdesc, 0x0a somewhere needs to be set to 0x60 diff --git a/force-effects.txt b/force-effects.txt deleted file mode 100644 index 0595611..0000000 --- a/force-effects.txt +++ /dev/null @@ -1,462 +0,0 @@ -Here I will gather all info I can find out about the different force effects and -their USB commands. All values seem to be little-endian. -Also, general note, it seems like some effects are affected by the direction of -the force, denoted by *** - -As far as I can tell, fade_level and attack_level values are calculated by -mapping the u16 into s16 and taking the absolute value, and after that -multiplying by the direction. - -All values are examples of actual commands that I captured on the USB interface, -not the only ones available. - -GENERAL: - - Playing: - 60 00 - standard header - 01 - ID - 89 - playing options - 01 - play - 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - - - Stopping/removing: (why are they rolled into one?) - 60 00 - standard header - 01 - ID - 89 - playing options - 00 - stop - 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - -FF_CONSTANT: - Initialization: - 60 00 - standard header - 01 - ID - 6a - new constant effect(?) - fe ff - strength *** - 00 00 - attack_length - 00 00 - attack_level *** - 00 00 - fade_length - 00 00 - fade_level *** - 00 4f - ? - f7 17 - time in milliseconds, unsigned - 00 00 - 07 00 - offset from start(?) - 00 ff ff - end of new effect(?) - 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - - - Modifying: (For dynamic updating of effects) - Direction: n/a - - Constant force: - 60 00 - standard header - 01 - ID - 0a - modify constant force - 05 16 - constant force *** - 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - - Envelope: - 60 00 - standard header - 01 - ID - 31 - modify envelope - 84 - ID of envelope attribute ( attack_level 82, - attack_length 81, - fade_level 88, - fade_length 84 ) - 63 04 - value attribute should be set to ?***? - 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - - Duration: - 60 00 - standard header - 01 - ID - 49 00 41 - modify timing? - 6c 20 - length in milliseconds - 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - - Offset: - Not supported? - - -FF_RAMP: - (Why on earth is it this difficult to figure out how a fucking ramp works?) - Init: - 60 00 - standard header - 01 - ID - 6b - new ramp effect - f6 7f - difference *** - fe ff - level? *** (level = (if end_level > start_level, end_level, else - start_level) - difference?) (signed) - 00 00 - f7 17 - time - 00 80 - some kind of marker - 00 00 - attack_length - f6 7f - attack_level *** - 00 00 - fade_length - f6 7f - fade_level*** - 04 - invert (going "down", 05, going "up", 04) - 4f - f7 17 - time again? - 00 00 - 00 00 - offset - 00 ff ff - end of init - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - - Modifying: - Sweet baby jesus - I'll figure this out later - Ramp(?): - 60 00 - standard header - 01 - ID - 0e - ??? - 03 - ramp? - e3 04 - difference? - 7d 75 - "level"? - 05 - ???? - 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - - - Envelope: - 60 00 - standard header - 01 - ID - 31 - modify envelope - 84 - ID of envelope attribute ( attack_level 82, - attack_length 81, - fade_level 88, - fade_length 84 ) - 63 04 - value attribute should be set to - 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - - Duration: - - 60 00 - 01 - ID - 4e - ? - 08 - ? - 01 00 - time in milliseconds - 05 - ? - 41 - ? - 01 00 - time in milliseconds - 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - - Offset: - Not supported? - -FF_SPRING: - Init: - 60 00 - standard header - 01 - ID - 64 - new conditional effect - fc 7f - positive coefficient (right?) - fc 7f - negative coefficient (left?) - fe ff - deadband (left?) (deadband + offset) (fe ff means no deadband) - fe ff - deadband (right?) (deadband + offset) - a6 6a a6 6a fe ff fe ff fe ff fe ff df 58 a6 6a 06 - some weird hard-coded - values to do with springs? - - 4f - f7 17 - duration - 00 00 - 00 00 - offset - 00 ff ff - end of init - 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - - Modifying: - Same as under - -FF_DAMPER + FF_FRICTION + FF_INERTIA: - Init: - 60 00 - standard header - 02 -ID - 64 - new conditional effect - fc 7f - positive coefficient (right?) - fc 7f - negative coefficient (left?) - fe ff - deadband (left?) (deadband + offset) (fe ff means no deadband) - fe ff - deadband (right?) (deadband + offset) - fc 7f fc 7f fe ff fe ff fe ff fe ff fc 7f fc 7f 07 - some weird hard-coded - values to do with friction? - 4f - f7 17 - duration - 00 00 - 00 00 - offset - 00 ff ff - end of init - 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - - Modifying: - positive coefficient: - 60 00 - 02 - ID - 0e 41 - 64 35 - value, signed (between 00 00 and 01 80?) - 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - - negative coefficient: - 60 00 - 02 - ID - 0e 42 - 64 35 - value, signed (between 00 00 and ff 7f?) - 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - - deadband/offset: - 60 00 - 02 - ID - 0e 4c - 64 35 - deadband (right?) (deadband + offset) (fe ff means no deadband) - 00 00 - deadband (left?) (deadband + offset) - 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - - Duration: - 60 00 - standard header - 01 - ID - 49 06 41 - modify timing? - 6c 20 - length in milliseconds - 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - - Offset: - Not supported? - -FF_PERIODIC: - 60 00 - standard header - 01 - ID - 6b - new periodic effect - 00 00 - magnitude *** - fe ff - offset (up/down) - 00 00 - phase (left/right) - e8 03 - period - 00 80 - 00 00 - attack_length - 00 00 - attack_level *** - 00 00 - fade_length - 00 00 - fade_level *** - 01 - type of periodic ( square 01, - sine 03, - triangle 02, - sawtooth down 05, - sawtooth up 04 ) - 4f - f7 17 - duration - 00 00 - 00 00 - offset - 00 ff ff - end of init - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - - Modifying: - magnitude: - 60 00 - 02 - ID - 0e 01 - 64 35 - value, signed (between 00 00 and ff 7f?) - 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - - offset of effect: - 60 00 - 02 - ID - 0e 02 - 64 35 - value, signed (between 00 00 and ff 7f?) - 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - - phase: - 60 00 - 02 - ID - 0e 04 - 64 35 - value, signed (between 80 01 and ff 7f?) - 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - - period: - 60 00 - 02 - ID - 0e 08 - 64 35 - value, signed (between 00 00 and I guess ff 7f?) (max value in - wireshark is 0d 07, but I don't know if that's a driver or program issue) - 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - - - Envelope: - 60 00 - standard header - 01 - ID - 31 - modify envelope - 84 - ID of envelope attribute ( attack_level 82, - attack_length 81, - fade_level 88, - fade_length 84 ) - 63 04 - value attribute should be set to - 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - - Duration: - - Square: - 60 00 - 01 - ID - 49 - ? - 01 - Effect type - 41 - ? - 4e 0c - Time in milliseconds - 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - - Sine: - 60 00 - 01 - ID - 49 - ? - 03 - Effect type - 41 - ? - ec 13 - Time in milliseconds - 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - - Triangle: - 60 00 - 01 - ID - 49 - ? - 02 - Effect type - 41 - ? - 4e 0c - Time in milliseconds - 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - - Sawtooth up: - 60 00 - 01 - ID - 49 - ? - 04 - Effect type - 41 - ? - 4e 0c - Time in milliseconds - 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - - Sawtooth down: - 60 00 - 01 - ID - 49 - ? - 05 - Effect type - 41 - ? - ec 13 - Time in milliseconds - 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - - Offset: - Not supported? - - -FF_AUTOCENTER: - 60 08 - header plus settings? - 03 - set autocenter force - 8f 02 - force (between ff ff and 00 00) - 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - -FF_GAIN: - 60 02 - header plus gain? - bf - gain (between ff and 00) - 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - -Rotation angle: - 60 08 - header plus settings? - 11 - set rotation angle - 55 d5 - angle (between ff ff and 7b 09, one degree is roughly 3c) - 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - -Open: - 60 01 - standard header? - 04 - ? - 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - - (this command doesn't appear every time, odd) - 60 12 - ? - bf 04 00 00 03 b7 1e - ? /* wow, this is different on different wheels? */ - 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - - 60 01 - ? - 05 - ? - 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - -Close: - 60 01 - standard header? - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - diff --git a/hid-tmff2.c b/hid-tmff2.c deleted file mode 100644 index 44df1e3..0000000 --- a/hid-tmff2.c +++ /dev/null @@ -1,749 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -#include -#include -#include -#include "hid-tmff2.h" - - -int open_mode = 1; -module_param(open_mode, int, 0660); -MODULE_PARM_DESC(open_mode, - "Whether to send mode change commands on open/close"); - -int timer_msecs = DEFAULT_TIMER_PERIOD; -module_param(timer_msecs, int, 0660); -MODULE_PARM_DESC(timer_msecs, - "Timer resolution in msecs"); - -/* should these be removed and just rely on /sys? */ -int spring_level = 30; -module_param(spring_level, int, 0); -MODULE_PARM_DESC(spring_level, - "Level of spring force (0-100), as per Oversteer standards"); - -int damper_level = 30; -module_param(damper_level, int, 0); -MODULE_PARM_DESC(damper_level, - "Level of damper force (0-100), as per Oversteer standards"); - -int friction_level = 30; -module_param(friction_level, int, 0); -MODULE_PARM_DESC(friction_level, - "Level of friction force (0-100), as per Oversteer standards"); - -int range = 900; -module_param(range, int, 0); -MODULE_PARM_DESC(range, - "Range of wheel, depends on the wheel. Invalid values are ignored"); - -int alt_mode = 0; -module_param(alt_mode, int, 0); -MODULE_PARM_DESC(alt_mode, - "Alternate mode, eg. F1 mode"); - -#define GAIN_MAX 65535 -int gain = 40000; -module_param(gain, int, 0); -MODULE_PARM_DESC(gain, - "Level of gain (0-65535)"); - -static spinlock_t lock; -static unsigned long lock_flags; - -static struct tmff2_device_entry *tmff2_from_hdev(struct hid_device *hdev) -{ - struct tmff2_device_entry *tmff2; - spin_lock_irqsave(&lock, lock_flags); - - if (!(tmff2 = hid_get_drvdata(hdev))) - dev_err(&hdev->dev, "hdev private data not found\n"); - - spin_unlock_irqrestore(&lock, lock_flags); - - return tmff2; -} - -static struct tmff2_device_entry *tmff2_from_input(struct input_dev *input_dev) -{ - struct hid_device *hdev; - spin_lock_irqsave(&lock, lock_flags); - - if (!(hdev = input_get_drvdata(input_dev))) - dev_err(&input_dev->dev, "input_dev private data not found\n"); - - spin_unlock_irqrestore(&lock, lock_flags); - - return tmff2_from_hdev(hdev); -} - -static ssize_t spring_level_store(struct device *dev, - struct device_attribute *attr, const char *buf, size_t count) -{ - unsigned int value; - int ret; - - ret = kstrtouint(buf, 0, &value); - if (ret) { - dev_err(dev, "kstrtouint failed at spring_level_store: %i", ret); - return ret; - } - - if (value > 100) { - dev_info(dev, "value %i larger than max 100, clamping to 100.\n", value); - value = 100; - } - - spring_level = value; - - return count; -} - -static ssize_t spring_level_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - - return scnprintf(buf, PAGE_SIZE, "%u\n", spring_level); -} -static DEVICE_ATTR_RW(spring_level); - -static ssize_t damper_level_store(struct device *dev, - struct device_attribute *attr, const char *buf, size_t count) -{ - unsigned int value; - int ret; - - - ret = kstrtouint(buf, 0, &value); - if (ret) { - dev_err(dev, "kstrtouint failed at damper_level_store: %i", ret); - return ret; - } - - if (value > 100) { - dev_info(dev, "value %i larger than max 100, clamping to 100.\n", value); - value = 100; - } - - damper_level = value; - - return count; -} - -static ssize_t damper_level_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - - return scnprintf(buf, PAGE_SIZE, "%u\n", damper_level); -} -static DEVICE_ATTR_RW(damper_level); - -static ssize_t friction_level_store(struct device *dev, - struct device_attribute *attr, const char *buf, size_t count) -{ - unsigned int value; - int ret; - - - ret = kstrtouint(buf, 0, &value); - if (ret) { - dev_err(dev, "kstrtouint failed at friction_level_store: %i", ret); - return ret; - } - - if (value > 100) { - dev_info(dev, "value %i larger than max 100, clamping to 100.\n", value); - value = 100; - } - - friction_level = value; - - return count; -} - -static ssize_t friction_level_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - size_t count; - - - count = scnprintf(buf, PAGE_SIZE, "%u\n", friction_level); - - return count; -} -static DEVICE_ATTR_RW(friction_level); - -static ssize_t range_store(struct device *dev, - struct device_attribute *attr, const char *buf, size_t count) -{ - struct tmff2_device_entry *tmff2 = tmff2_from_hdev(to_hid_device(dev)); - unsigned int value; - int ret; - - - if (!tmff2) - return -ENODEV; - - if ((ret = kstrtouint(buf, 0, &value))) { - hid_err(tmff2->hdev, "kstrtouint failed at range_store: %i", ret); - return ret; - } - - if (tmff2->set_range) { - if ((ret = tmff2->set_range(tmff2->data, value))) - return ret; - } - - return count; -} - -static ssize_t range_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - - return scnprintf(buf, PAGE_SIZE, "%u\n", range); -} -static DEVICE_ATTR_RW(range); - -static ssize_t alternate_modes_store(struct device *dev, - struct device_attribute *attr, const char *buf, size_t count) -{ - struct tmff2_device_entry *tmff2 = tmff2_from_hdev(to_hid_device(dev)); - - if (!tmff2) - return -ENODEV; - - if (tmff2->alt_mode_store) - return tmff2->alt_mode_store(tmff2->data, buf, count); - - return 0; -} - -static ssize_t alternate_modes_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct tmff2_device_entry *tmff2 = tmff2_from_hdev(to_hid_device(dev)); - - if (!tmff2) - return -ENODEV; - - if (tmff2->alt_mode_show) - return tmff2->alt_mode_show(tmff2->data, buf); - - return 0; -} -static DEVICE_ATTR_RW(alternate_modes); - -static ssize_t gain_store(struct device *dev, - struct device_attribute *attr, const char *buf, size_t count) -{ - struct tmff2_device_entry *tmff2 = tmff2_from_hdev(to_hid_device(dev)); - unsigned int value; - int ret; - - if (!tmff2) - return -ENODEV; - - if ((ret = kstrtouint(buf, 0, &value))) { - dev_err(dev, "kstrtouint failed at gain_store: %i", ret); - return ret; - } - - gain = value; - if (tmff2->set_gain) /* if we can, update gain immediately */ - tmff2->set_gain(tmff2->data, (GAIN_MAX * gain) / GAIN_MAX); - - return count; -} - -static ssize_t gain_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - return scnprintf(buf, PAGE_SIZE, "%i\n", gain); -} -static DEVICE_ATTR_RW(gain); - -static void tmff2_set_gain(struct input_dev *dev, uint16_t value) -{ - struct tmff2_device_entry *tmff2 = tmff2_from_input(dev); - - if (!tmff2) - return; - - if (!tmff2->set_gain) { - hid_err(tmff2->hdev, "missing set_gain\n"); - return; - } - - if (tmff2->set_gain(tmff2->data, (value * gain) / GAIN_MAX)) - hid_warn(tmff2->hdev, "unable to set gain\n"); -} - -static void tmff2_set_autocenter(struct input_dev *dev, uint16_t value) -{ - struct tmff2_device_entry *tmff2 = tmff2_from_input(dev); - - if (!tmff2) - return; - - if (!tmff2->set_autocenter) { - hid_err(tmff2->hdev, "missing set_autocenter\n"); - return; - } - - if (tmff2->set_autocenter(tmff2->data, value)) - hid_warn(tmff2->hdev, "unable to set autocenter\n"); -} - -static void tmff2_work_handler(struct work_struct *w) -{ - struct delayed_work *dw = container_of(w, struct delayed_work, work); - struct tmff2_device_entry *tmff2 = container_of(dw, struct tmff2_device_entry, work); - struct tmff2_effect_state *state; - int max_count = 0, effect_id; - unsigned long time_now; - __u16 effect_length; - - - if (!tmff2) - return; - - for (effect_id = 0; effect_id < tmff2->max_effects; ++effect_id) { - spin_lock(&tmff2->lock); - - time_now = JIFFIES2MS(jiffies); - state = &tmff2->states[effect_id]; - - effect_length = state->effect.replay.length; - if (test_bit(FF_EFFECT_PLAYING, &state->flags) && effect_length) { - if ((time_now - state->start_time) >= effect_length) { - __clear_bit(FF_EFFECT_PLAYING, &state->flags); - __clear_bit(FF_EFFECT_QUEUE_UPDATE, &state->flags); - - if (state->count) - state->count--; - - if (state->count) - __set_bit(FF_EFFECT_QUEUE_START, &state->flags); - } - } - - if (test_bit(FF_EFFECT_QUEUE_UPLOAD, &state->flags)) { - if (tmff2->upload_effect(tmff2->data, state)) { - hid_warn(tmff2->hdev, "failed uploading effect\n"); - } else { - __clear_bit(FF_EFFECT_QUEUE_UPLOAD, &state->flags); - /* if we're uploading an effect, it's bound to be the up - * to date available */ - __clear_bit(FF_EFFECT_QUEUE_UPDATE, &state->flags); - } - } - - if (test_bit(FF_EFFECT_QUEUE_UPDATE, &state->flags)) { - if (tmff2->update_effect(tmff2->data, state)) - hid_warn(tmff2->hdev, "failed updating effect\n"); - else - __clear_bit(FF_EFFECT_QUEUE_UPDATE, &state->flags); - } - - if (test_bit(FF_EFFECT_QUEUE_START, &state->flags)) { - if (tmff2->play_effect(tmff2->data, state)) { - hid_warn(tmff2->hdev, "failed starting effect\n"); - } else { - __clear_bit(FF_EFFECT_QUEUE_START, &state->flags); - __set_bit(FF_EFFECT_PLAYING, &state->flags); - } - - } - - if (test_bit(FF_EFFECT_QUEUE_STOP, &state->flags)) { - if (tmff2->stop_effect(tmff2->data, state)) { - hid_warn(tmff2->hdev, "failed stopping effect\n"); - } else { - __clear_bit(FF_EFFECT_PLAYING, &state->flags); - __clear_bit(FF_EFFECT_QUEUE_STOP, &state->flags); - } - } - - if (state->count > max_count) - max_count = state->count; - - spin_unlock(&tmff2->lock); - } - - if (max_count && tmff2->allow_scheduling) - schedule_delayed_work(&tmff2->work, msecs_to_jiffies(timer_msecs)); -} - -static int tmff2_upload(struct input_dev *dev, - struct ff_effect *effect, struct ff_effect *old) -{ - struct tmff2_effect_state *state; - struct tmff2_device_entry *tmff2 = tmff2_from_input(dev); - - if (!tmff2) - return -ENODEV; - - if (effect->type == FF_PERIODIC && effect->u.periodic.period == 0) - return -EINVAL; - - state = &tmff2->states[effect->id]; - - spin_lock(&tmff2->lock); - - state->effect = *effect; - - if (old) { - if (!test_bit(FF_EFFECT_QUEUE_UPDATE, &state->flags)) - state->old = *old; - - __set_bit(FF_EFFECT_QUEUE_UPDATE, &state->flags); - } else { - __set_bit(FF_EFFECT_QUEUE_UPLOAD, &state->flags); - } - - spin_unlock(&tmff2->lock); - return 0; -} - -static int tmff2_play(struct input_dev *dev, int effect_id, int value) -{ - struct tmff2_effect_state *state; - struct tmff2_device_entry *tmff2 = tmff2_from_input(dev); - - if (!tmff2) - return -ENODEV; - - state = &tmff2->states[effect_id]; - if (!state) - return 0; - - spin_lock(&tmff2->lock); - if (value > 0) { - state->count = value; - state->start_time = JIFFIES2MS(jiffies); - __set_bit(FF_EFFECT_QUEUE_START, &state->flags); - __clear_bit(FF_EFFECT_QUEUE_STOP, &state->flags); - } else { - __set_bit(FF_EFFECT_QUEUE_STOP, &state->flags); - __clear_bit(FF_EFFECT_QUEUE_START, &state->flags); - } - - spin_unlock(&tmff2->lock); - - if (!delayed_work_pending(&tmff2->work) && tmff2->allow_scheduling) - schedule_delayed_work(&tmff2->work, 0); - - return 0; -} - -static int tmff2_open(struct input_dev *dev) -{ - struct tmff2_device_entry *tmff2 = tmff2_from_input(dev); - - if (!tmff2) - return -ENODEV; - - if (tmff2->open) - return tmff2->open(tmff2->data, open_mode); - - hid_err(tmff2->hdev, "no open callback set\n"); - return -EINVAL; -} - -static void tmff2_close(struct input_dev *dev) -{ - struct tmff2_device_entry *tmff2 = tmff2_from_input(dev); - - if (!tmff2) - return; - - /* since we're closing the device, no need to continue feeding it new data */ - cancel_delayed_work_sync(&tmff2->work); - - if (tmff2->close) { - tmff2->close(tmff2->data, open_mode); - return; - } - - hid_err(tmff2->hdev, "no close callback set\n"); -} - -static int tmff2_create_files(struct tmff2_device_entry *tmff2) -{ - struct device *dev = &tmff2->hdev->dev; - int ret; - - - /* could use short circuiting but this is more explicit */ - if (tmff2->params & PARAM_GAIN) { - if ((ret = device_create_file(dev, &dev_attr_gain))) { - hid_err(tmff2->hdev, "unable to create sysfs for gain\n"); - goto gain_err; - } - } - - if (tmff2->params & PARAM_ALT_MODE) { - if ((ret = device_create_file(dev, &dev_attr_alternate_modes))) { - hid_err(tmff2->hdev, "unable to create sysfs for alternate_modes\n"); - goto alt_err; - } - } - - if (tmff2->params & PARAM_RANGE) { - if ((ret = device_create_file(dev, &dev_attr_range))) { - hid_warn(tmff2->hdev, "unable to create sysfs for range\n"); - goto range_err; - } - } - - if (tmff2->params & PARAM_SPRING_LEVEL) { - if ((ret = device_create_file(dev, &dev_attr_spring_level))) { - hid_warn(tmff2->hdev, "unable to create sysfs for spring_level\n"); - goto spring_err; - } - } - - if (tmff2->params & PARAM_DAMPER_LEVEL) { - if ((ret = device_create_file(dev, &dev_attr_damper_level))) { - hid_warn(tmff2->hdev, "unable to create sysfs for damper_level\n"); - goto damper_err; - } - } - - if (tmff2->params & PARAM_FRICTION_LEVEL) { - if ((ret = device_create_file(dev, &dev_attr_friction_level))) { - hid_warn(tmff2->hdev, "unable to create sysfs for friction_level\n"); - goto friction_err; - } - } - - return 0; - -friction_err: - device_remove_file(dev, &dev_attr_damper_level); -damper_err: - device_remove_file(dev, &dev_attr_spring_level); -spring_err: - device_remove_file(dev, &dev_attr_range); -range_err: - device_remove_file(dev, &dev_attr_alternate_modes); -alt_err: - device_remove_file(dev, &dev_attr_gain); -gain_err: - return ret; -} - -static int tmff2_wheel_init(struct tmff2_device_entry *tmff2) -{ - int ret, i; - struct ff_device *ff; - - spin_lock_init(&lock); - spin_lock_init(&tmff2->lock); - INIT_DELAYED_WORK(&tmff2->work, tmff2_work_handler); - - /* get parameters etc from backend */ - if ((ret = tmff2->wheel_init(tmff2, open_mode))) - goto err; - - - tmff2->states = kzalloc(sizeof(struct tmff2_effect_state) * tmff2->max_effects, - GFP_KERNEL); - - if (!tmff2->states) { - ret = -ENOMEM; - goto err; - } - - /* set supported effects into input_dev->ffbit */ - for (i = 0; tmff2->supported_effects[i] >= 0; ++i) - __set_bit(tmff2->supported_effects[i], tmff2->input_dev->ffbit); - - /* create actual ff device*/ - if ((ret = input_ff_create(tmff2->input_dev, tmff2->max_effects))) { - hid_err(tmff2->hdev, "could not create input_ff\n"); - goto err; - } - - /* set ff callbacks */ - ff = tmff2->input_dev->ff; - ff->upload = tmff2_upload; - ff->playback = tmff2_play; - - if (tmff2->open) - tmff2->input_dev->open = tmff2_open; - - if (tmff2->close) - tmff2->input_dev->close = tmff2_close; - - /* set defaults wherever possible */ - if (tmff2->set_gain) { - ff->set_gain = tmff2_set_gain; - tmff2->set_gain(tmff2->data, (GAIN_MAX * gain) / GAIN_MAX); - } - - if (tmff2->set_autocenter) - ff->set_autocenter = tmff2_set_autocenter; - - if (tmff2->set_range) - tmff2->set_range(tmff2->data, range); - - if (tmff2->switch_mode) - tmff2->switch_mode(tmff2->data, alt_mode); - - /* create files */ - if ((ret = tmff2_create_files(tmff2))) - goto err; - - tmff2->allow_scheduling = 1; - return 0; - - input_ff_destroy(tmff2->input_dev); -err: - return ret; -} - -static int tmff2_probe(struct hid_device *hdev, const struct hid_device_id *id) -{ - struct tmff2_device_entry *tmff2 = - kzalloc(sizeof(struct tmff2_device_entry), GFP_KERNEL); - - int ret; - - - if (!tmff2) { - ret = -ENOMEM; - goto oom_err; - } - - tmff2->hdev = hdev; - hid_set_drvdata(tmff2->hdev, tmff2); - - switch (tmff2->hdev->product) { - /* t300rs */ - case TMT300RS_PS3_NORM_ID: - case TMT300RS_PS3_ADV_ID: - case TMT300RS_PS4_NORM_ID: - if ((ret = t300rs_populate_api(tmff2))) - goto wheel_err; - break; - - case TMT248_PC_ID: - if ((ret = t248_populate_api(tmff2))) - goto wheel_err; - break; - - case TX_ACTIVE: - if ((ret = tx_populate_api(tmff2))) - goto wheel_err; - break; - - default: - ret = -ENODEV; - goto wheel_err; - } - - if ((ret = hid_parse(tmff2->hdev))) { - hid_err(hdev, "parse failed\n"); - goto hid_err; - } - - if ((ret = hid_hw_start(tmff2->hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF))) { - hid_err(hdev, "hw start failed\n"); - goto hid_err; - } - - tmff2->input_dev = list_entry(hdev->inputs.next, struct hid_input, list)->input; - - if ((ret = tmff2_wheel_init(tmff2))) { - hid_err(hdev, "init failed\n"); - goto init_err; - } - - return 0; - -init_err: - hid_hw_stop(hdev); -hid_err: - tmff2->wheel_destroy(tmff2->data); -wheel_err: - kfree(tmff2); -oom_err: - return ret; -} - -static __u8 *tmff2_report_fixup(struct hid_device *hdev, __u8 *rdesc, - unsigned int *rsize) -{ - struct tmff2_device_entry *tmff2 = tmff2_from_hdev(hdev); - - if (!tmff2) /* not entirely sure what the best course of action would be here */ - return rdesc; - - if (tmff2->wheel_fixup) - return tmff2->wheel_fixup(hdev, rdesc, rsize); - - return rdesc; -} - -static void tmff2_remove(struct hid_device *hdev) -{ - struct tmff2_device_entry *tmff2 = tmff2_from_hdev(hdev); - struct device *dev; - - if (!tmff2) - return; - - tmff2->allow_scheduling = 0; - cancel_delayed_work_sync(&tmff2->work); - - dev = &tmff2->hdev->dev; - if (tmff2->params & PARAM_FRICTION_LEVEL) - device_remove_file(dev, &dev_attr_friction_level); - - if (tmff2->params & PARAM_DAMPER_LEVEL) - device_remove_file(dev, &dev_attr_damper_level); - - if (tmff2->params & PARAM_SPRING_LEVEL) - device_remove_file(dev, &dev_attr_spring_level); - - if (tmff2->params & PARAM_RANGE) - device_remove_file(dev, &dev_attr_range); - - if (tmff2->params & PARAM_ALT_MODE) - device_remove_file(dev, &dev_attr_alternate_modes); - - if (tmff2->params & PARAM_GAIN) - device_remove_file(dev, &dev_attr_gain); - - hid_hw_stop(hdev); - tmff2->wheel_destroy(tmff2->data); - - kfree(tmff2->states); - kfree(tmff2); -} - -static const struct hid_device_id tmff2_devices[] = { - /* t300rs and variations */ - {HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, TMT300RS_PS3_NORM_ID)}, - {HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, TMT300RS_PS3_ADV_ID)}, - {HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, TMT300RS_PS4_NORM_ID)}, - /* t248 PC*/ - {HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, TMT248_PC_ID)}, - /* tx */ - {HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, TX_ACTIVE)}, - - {} -}; -MODULE_DEVICE_TABLE(hid, tmff2_devices); - -static struct hid_driver tmff2_driver = { - .name = "tmff2", - .id_table = tmff2_devices, - .probe = tmff2_probe, - .remove = tmff2_remove, - .report_fixup = tmff2_report_fixup, -}; -module_hid_driver(tmff2_driver); - -MODULE_LICENSE("GPL"); diff --git a/hid-tmff2.h b/hid-tmff2.h deleted file mode 100644 index 97d52a1..0000000 --- a/hid-tmff2.h +++ /dev/null @@ -1,147 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -#ifndef __HID_TMFF2_H -#define __HID_TMFF2_H - -#include -#include -#include - -extern int timer_msecs; -extern int spring_level; -extern int damper_level; -extern int friction_level; -extern int range; -extern int gain; -extern int alt_mode; - -#define USB_VENDOR_ID_THRUSTMASTER 0x044f - -/* the wheel seems to only be capable of processing a certain number of - * interrupts per second, and if this value is too low the kernel urb buffer(or - * some buffer at least) fills up. Optimally I would figure out some way to - * space out the interrupts so that they all leave at regular intervals, but - * for now this is good enough, go slow enough that everything works. - */ -#define DEFAULT_TIMER_PERIOD 8 - -#define FF_EFFECT_QUEUE_UPLOAD 0 -#define FF_EFFECT_QUEUE_START 1 -#define FF_EFFECT_QUEUE_STOP 2 -#define FF_EFFECT_QUEUE_UPDATE 3 -#define FF_EFFECT_PLAYING 4 - -#define PARAM_SPRING_LEVEL (1 << 0) -#define PARAM_DAMPER_LEVEL (1 << 1) -#define PARAM_FRICTION_LEVEL (1 << 2) -#define PARAM_RANGE (1 << 3) -#define PARAM_ALT_MODE (1 << 4) -#define PARAM_GAIN (1 << 5) - -#undef fixp_sin16 -#define fixp_sin16(v) (((v % 360) > 180) ?\ - -(fixp_sin32((v % 360) - 180) >> 16)\ - : fixp_sin32(v) >> 16) - -#define JIFFIES2MS(jiffies) ((jiffies) * 1000 / HZ) - -struct tmff2_effect_state { - struct ff_effect effect; - struct ff_effect old; - - unsigned long flags; - unsigned long count; - unsigned long start_time; -}; - -struct tmff2_device_entry { - struct hid_device *hdev; - struct input_dev *input_dev; - - /* pointer to array */ - struct tmff2_effect_state *states; - - struct delayed_work work; - - spinlock_t lock; - - int allow_scheduling; - - /* fields relevant to each actual device (T300, T150...) */ - void *data; - unsigned long params; - unsigned long max_effects; - signed short supported_effects[FF_CNT]; - - /* obligatory callbacks */ - int (*play_effect)(void *data, struct tmff2_effect_state *state); - int (*upload_effect)(void *data, struct tmff2_effect_state *state); - int (*update_effect)(void *data, struct tmff2_effect_state *state); - int (*stop_effect)(void *data, struct tmff2_effect_state *state); - - int (*wheel_init)(struct tmff2_device_entry *tmff2, int open_mode); - int (*wheel_destroy)(void *data); - - /* optional callbacks */ - int (*open)(void *data, int); - int (*close)(void *data, int); - int (*set_gain)(void *data, uint16_t gain); - int (*set_range)(void *data, uint16_t range); - /* switch_mode has to not do anything if we're alredy in the specified - * mode */ - int (*switch_mode)(void *data, uint16_t mode); - ssize_t (*alt_mode_show)(void *data, char *buf); - ssize_t (*alt_mode_store)(void *data, const char *buf, size_t count); - int (*set_autocenter)(void *data, uint16_t autocenter); - __u8 *(*wheel_fixup)(struct hid_device *hdev, __u8 *rdesc, unsigned int *rsize); - - /* void pointers are dangerous, I know, but in this case likely the best option... */ -}; - -/* external */ -int t300rs_populate_api(struct tmff2_device_entry *tmff2); -int t248_populate_api(struct tmff2_device_entry *tmff2); -int tx_populate_api(struct tmff2_device_entry *tmff2); - -#define TMT300RS_PS3_NORM_ID 0xb66e -#define TMT300RS_PS3_ADV_ID 0xb66f -#define TMT300RS_PS4_NORM_ID 0xb66d - -#define TMT248_PC_ID 0xb696 - -#define TX_ACTIVE 0xb669 - -/* apis to different wheel families */ -/* T248 at least uses the T300RS api, not sure if there are other wheels but that's - * why these functions are given global linkage */ - -struct t300rs_device_entry { - struct hid_device *hdev; - struct input_dev *input_dev; - struct hid_report *report; - struct hid_field *ff_field; - struct usb_device *usbdev; - - int (*open)(struct input_dev *dev); - void (*close)(struct input_dev *dev); - - int mode; - int attachment; - u8 buffer_length; - u8 *send_buffer; -}; - -int t300rs_play_effect(void *, struct tmff2_effect_state *); -int t300rs_upload_effect(void *, struct tmff2_effect_state *); -int t300rs_update_effect(void *, struct tmff2_effect_state *); -int t300rs_stop_effect(void *, struct tmff2_effect_state *); - -int t300rs_open(void *, int); -int t300rs_close(void *, int); -int t300rs_set_gain(void *, uint16_t); -int t300rs_set_range(void *, uint16_t); -int t300rs_set_autocenter(void *, uint16_t); - -int t300rs_send_buf(struct t300rs_device_entry *t300rs, u8 *send_buffer, size_t len); -int t300rs_send_int(struct t300rs_device_entry *t300rs); - -#endif /* __HID_TMFF2_H */ diff --git a/hid-tminit b/hid-tminit deleted file mode 160000 index 53fde05..0000000 --- a/hid-tminit +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 53fde059e88a4314d987ab75518edb8ce86cf8a3 diff --git a/hid-tmt248.c b/hid-tmt248.c deleted file mode 100644 index 386f6d8..0000000 --- a/hid-tmt248.c +++ /dev/null @@ -1,335 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -#include -#include -#include "hid-tmff2.h" - -#define T248_MAX_EFFECTS 16 -#define T248_BUFFER_LENGTH 63 - -static const u8 setup_0[64] = { 0x42, 0x01 }; -static const u8 setup_1[64] = { 0x0a, 0x04, 0x90, 0x03 }; -static const u8 setup_2[64] = { 0x0a, 0x04, 0x00, 0x0c }; -static const u8 setup_3[64] = { 0x0a, 0x04, 0x12, 0x10 }; -static const u8 setup_4[64] = { 0x0a, 0x04, 0x00, 0x06 }; -static const u8 setup_5[64] = { 0x0a, 0x04, 0x00, 0x0e }; -static const u8 setup_6[64] = { 0x0a, 0x04, 0x00, 0x0e, 0x01 }; -static const u8 *const setup_arr[] = { setup_0, setup_1, setup_2, setup_3, setup_4, setup_5, setup_6 }; -static const unsigned int setup_arr_sizes[] = { - ARRAY_SIZE(setup_0), - ARRAY_SIZE(setup_1), - ARRAY_SIZE(setup_2), - ARRAY_SIZE(setup_3), - ARRAY_SIZE(setup_4), - ARRAY_SIZE(setup_5), - ARRAY_SIZE(setup_6) -}; - -static const unsigned long t248_params = - PARAM_SPRING_LEVEL - | PARAM_DAMPER_LEVEL - | PARAM_FRICTION_LEVEL - | PARAM_RANGE - | PARAM_GAIN - ; - -static const signed short t248_effects[] = { - FF_CONSTANT, - FF_RAMP, - FF_SPRING, - FF_DAMPER, - FF_FRICTION, - FF_INERTIA, - FF_PERIODIC, - FF_SINE, - FF_TRIANGLE, - FF_SQUARE, - FF_SAW_UP, - FF_SAW_DOWN, - FF_AUTOCENTER, - FF_GAIN, - -1 -}; - -/* TODO: sort through this stuff */ -static u8 t248_pc_rdesc_fixed[] = { - 0x05, 0x01, /* Usage page (Generic Desktop) */ - 0x09, 0x04, /* Usage (Joystick) */ - 0xa1, 0x01, /* Collection (Application) */ - 0x09, 0x01, /* Usage (Pointer) */ - 0xa1, 0x00, /* Collection (Physical) */ - 0x85, 0x07, /* Report ID (7) */ - 0x09, 0x30, /* Usage (X) */ - 0x15, 0x00, /* Logical minimum (0) */ - 0x27, 0xff, 0xff, 0x00, 0x00, /* Logical maximum (65535) */ - 0x35, 0x00, /* Physical minimum (0) */ - 0x47, 0xff, 0xff, 0x00, 0x00, /* Physical maximum (65535) */ - 0x75, 0x10, /* Report size (16) */ - 0x95, 0x01, /* Report count (1) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - 0x09, 0x31, /* Usage (Y) TODO: clutch? */ - 0x26, 0xff, 0x03, /* Logical maximum (1023) */ - 0x46, 0xff, 0x03, /* Physical maximum (1023) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - 0x09, 0x35, /* Usage (Rz) TODO: brake? */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - 0x09, 0x36, /* Usage (Slider) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - 0x75, 0x08, /* Report size (8) */ - 0x26, 0xff, 0x00, /* Logical maximum (255) */ - 0x46, 0xff, 0x00, /* Physical maximum (255) */ - 0x09, 0x40, /* Usage (Vx) TODO: what is this? */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - 0x09, 0x41, /* Usage (Vy) TODO: --||-- */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - 0x09, 0x33, /* Usage (Rx) TODO: --||-- */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - 0x09, 0x34, /* Usage (Ry) TODO: --||-- */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - 0x09, 0x32, /* Usage (Z) TODO: --||-- (gas?) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - 0x09, 0x37, /* Usage (Dial) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - 0x05, 0x09, /* Usage page (Button) */ - 0x19, 0x01, /* Usage minimum (1) */ - 0x29, 0x1a, /* Usage maximum (13) */ - 0x25, 0x01, /* Logical maximum (1) */ - 0x45, 0x01, /* Physical maximum (1) */ - 0x75, 0x01, /* Report size (1) */ - 0x95, 0x1a, /* Report count (26) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - 0x75, 0x06, /* Report size (6) */ - 0x95, 0x01, /* Report count (1) */ - 0x81, 0x03, /* Usage (Variable, Absolute, Constant) */ - 0x05, 0x01, /* Usage page (Generic Desktop) */ - 0x09, 0x39, /* Usage (Hat Switch) */ - 0x25, 0x07, /* Logical maximum (7) */ - 0x46, 0x3b, 0x01, /* Physical maximum (315) */ - 0x55, 0x00, /* Unit exponent (0) */ - 0x65, 0x14, /* Unit (Eng rot, Angular Pos) */ - 0x75, 0x04, /* Report size (4) */ - 0x81, 0x42, /* Input (Variable, Absolute, NullState) */ - 0x65, 0x00, /* Input (None) */ - 0x81, 0x03, /* Input (Variable, Absolute, Constant) */ - 0x85, 0x60, /* Report ID (96), prev 10 */ - 0x06, 0x00, 0xff, /* Usage page (Vendor 1) */ - 0x09, 0x60, /* Usage (96), prev 10 */ - 0x75, 0x08, /* Report size (8) */ - 0x95, 0x3f, /* Report count (63) */ - 0x26, 0xff, 0x00, /* Logical maximum (256) */ - 0x46, 0xff, 0x00, /* Physical maximum (256) */ - 0x91, 0x02, /* Output (Variable, Absolute) */ - 0x85, 0x02, /* Report ID (2) */ - 0x09, 0x02, /* Usage (2) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - 0x09, 0x14, /* Usage (20) */ - 0x85, 0x14, /* Report ID (20) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - 0xc0, /* End collection */ - 0xc0, /* End collection */ -}; - -static int t248_interrupts(struct t300rs_device_entry *t248) -{ - u8 *send_buf = kmalloc(256, GFP_KERNEL); - struct usb_interface *usbif = to_usb_interface(t248->hdev->dev.parent); - struct usb_host_endpoint *ep; - int ret, trans, b_ep, i; - - if (!send_buf) { - hid_err(t248->hdev, "failed allocating send buffer\n"); - return -ENOMEM; - } - - ep = &usbif->cur_altsetting->endpoint[1]; - b_ep = ep->desc.bEndpointAddress; - - for (i = 0; i < ARRAY_SIZE(setup_arr); ++i) { - memcpy(send_buf, setup_arr[i], setup_arr_sizes[i]); - - ret = usb_interrupt_msg(t248->usbdev, - usb_sndintpipe(t248->usbdev, b_ep), - send_buf, setup_arr_sizes[i], - &trans, - USB_CTRL_SET_TIMEOUT); - - if (ret) { - hid_err(t248->hdev, "setup data couldn't be sent\n"); - goto err; - } - } - -err: - kfree(send_buf); - return ret; -} - -int t248_wheel_destroy(void *data) -{ - struct t300rs_device_entry *t300rs = data; - - if (!t300rs) - return -ENODEV; - - kfree(t300rs->send_buffer); - kfree(t300rs); - return 0; -} - -int t248_set_range(void *data, uint16_t value) -{ - struct t300rs_device_entry *t248 = data; - - if (value < 140) { - hid_info(t248->hdev, "value %i too small, clamping to 140\n", value); - value = 140; - } - - if (value > 900) { - hid_info(t248->hdev, "value %i too large, clamping to 900\n", value); - value = 900; - } - - return t300rs_set_range(data, value); -} - -static int t248_send_open(struct t300rs_device_entry *t248) -{ - int r1, r2; - t248->send_buffer[0] = 0x01; - t248->send_buffer[1] = 0x04; - if ((r1 = t300rs_send_int(t248))) - return r1; - - t248->send_buffer[0] = 0x01; - t248->send_buffer[1] = 0x05; - if ((r2 = t300rs_send_int(t248))) - return r2; - - return 0; -} - -static int t248_open(void *data, int open_mode) -{ - struct t300rs_device_entry *t248 = data; - - if (!t248) - return -ENODEV; - - if (open_mode) - t248_send_open(t248); - - return t248->open(t248->input_dev); -} - -static int t248_send_close(struct t300rs_device_entry *t248) -{ - int r1, r2; - t248->send_buffer[0] = 0x01; - t248->send_buffer[1] = 0x05; - if ((r1 = t300rs_send_int(t248))) - return r1; - - t248->send_buffer[0] = 0x01; - t248->send_buffer[1] = 0x00; - if ((r2 = t300rs_send_int(t248))) - return r2; - - return 0; -} - -static int t248_close(void *data, int open_mode) -{ - struct t300rs_device_entry *t248 = data; - - if (!t248) - return -ENODEV; - - if (open_mode) - t248_send_close(t248); - - t248->close(t248->input_dev); - return 0; -} - -int t248_wheel_init(struct tmff2_device_entry *tmff2, int open_mode) -{ - struct t300rs_device_entry *t248 = kzalloc(sizeof(struct t300rs_device_entry), GFP_KERNEL); - struct list_head *report_list; - int ret; - - - if (!t248) { - ret = -ENOMEM; - goto t248_err; - } - - t248->hdev = tmff2->hdev; - t248->input_dev = tmff2->input_dev; - t248->usbdev = to_usb_device(tmff2->hdev->dev.parent->parent); - t248->buffer_length = T248_BUFFER_LENGTH; - - t248->send_buffer = kzalloc(t248->buffer_length, GFP_KERNEL); - if (!t248->send_buffer) { - ret = -ENOMEM; - goto send_err; - } - - report_list = &t248->hdev->report_enum[HID_OUTPUT_REPORT].report_list; - t248->report = list_entry(report_list->next, struct hid_report, list); - t248->ff_field = t248->report->field[0]; - - t248->open = t248->input_dev->open; - t248->close = t248->input_dev->close; - - if ((ret = t248_interrupts(t248))) - goto interrupt_err; - - /* everything went OK */ - tmff2->data = t248; - tmff2->params = t248_params; - tmff2->max_effects = T248_MAX_EFFECTS; - memcpy(tmff2->supported_effects, t248_effects, sizeof(t248_effects)); - - if (!open_mode) - t248_send_open(t248); - - hid_info(t248->hdev, "force feedback for T248\n"); - return 0; - -interrupt_err: -send_err: - kfree(t248); -t248_err: - hid_err(tmff2->hdev, "failed initializing T248\n"); - return ret; -} - -static __u8 *t248_wheel_fixup(struct hid_device *hdev, __u8 *rdesc, - unsigned int *rsize) -{ - rdesc = t248_pc_rdesc_fixed; - *rsize = sizeof(t248_pc_rdesc_fixed); - return rdesc; -} - -int t248_populate_api(struct tmff2_device_entry *tmff2) -{ - tmff2->play_effect = t300rs_play_effect; - tmff2->upload_effect = t300rs_upload_effect; - tmff2->update_effect = t300rs_update_effect; - tmff2->stop_effect = t300rs_stop_effect; - - tmff2->set_gain = t300rs_set_gain; - tmff2->set_autocenter = t300rs_set_autocenter; - /* T248 only has 900 degree range, instead of T300RS 1080 */ - tmff2->set_range = t248_set_range; - tmff2->wheel_fixup = t248_wheel_fixup; - - tmff2->open = t248_open; - tmff2->close = t248_close; - - tmff2->wheel_init = t248_wheel_init; - tmff2->wheel_destroy = t248_wheel_destroy; - - return 0; -} diff --git a/hid-tmt300rs.c b/hid-tmt300rs.c deleted file mode 100644 index 4499687..0000000 --- a/hid-tmt300rs.c +++ /dev/null @@ -1,1714 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -#include -#include -#include "hid-tmff2.h" - -#define T300RS_MAX_EFFECTS 16 -#define T300RS_NORM_BUFFER_LENGTH 63 -#define T300RS_PS4_BUFFER_LENGTH 31 - -#define T300RS_DEFAULT_ATTACHMENT 0x06 -#define T300RS_F1_ATTACHMENT 0x03 - -static const unsigned long t300rs_params = - PARAM_SPRING_LEVEL - | PARAM_DAMPER_LEVEL - | PARAM_FRICTION_LEVEL - | PARAM_GAIN - | PARAM_RANGE - | PARAM_ALT_MODE - ; - -static const signed short t300rs_effects[] = { - FF_CONSTANT, - FF_RAMP, - FF_SPRING, - FF_DAMPER, - FF_FRICTION, - FF_INERTIA, - FF_PERIODIC, - FF_SINE, - FF_TRIANGLE, - FF_SQUARE, - FF_SAW_UP, - FF_SAW_DOWN, - FF_AUTOCENTER, - FF_GAIN, - -1 -}; - -struct __packed t300rs_fw_response { - uint8_t unused1[2]; - uint8_t fw_version; - uint8_t unused2; -}; - - -struct __packed t300rs_packet_header { - uint8_t zero1; - uint8_t id; - uint8_t code; -}; - -struct __packed t300rs_setup_header { - uint8_t cmd; - uint8_t code; -}; - -struct __packed t300rs_packet_envelope { - uint16_t attack_length; - uint16_t attack_level; - uint16_t fade_length; - uint16_t fade_level; -}; - -struct __packed t300rs_packet_timing { - uint8_t start_marker; - uint16_t duration; - uint8_t zero1[2]; - uint16_t offset; - uint8_t zero2; - uint16_t end_marker; -}; - -struct usb_ctrlrequest t300rs_fw_request = { - .bRequestType = 0xc1, - .bRequest = 86, - .wValue = 0, - .wIndex = 0, - .wLength = 8 -}; - -struct t300rs_data { - unsigned long quirks; - void *device_props; -}; - -static u8 t300rs_rdesc_nrm_fixed[] = { - 0x05, 0x01, /* Usage page (Generic Desktop) */ - 0x09, 0x04, /* Usage (Joystick) */ - 0xa1, 0x01, /* Collection (Application) */ - 0x09, 0x01, /* Usage (Pointer) */ - 0xa1, 0x00, /* Collection (Physical) */ - 0x85, 0x07, /* Report ID (7) */ - 0x09, 0x30, /* Usage (X) */ - 0x15, 0x00, /* Logical minimum (0) */ - 0x27, 0xff, 0xff, 0x00, 0x00, /* Logical maximum (65535) */ - 0x35, 0x00, /* Physical minimum (0) */ - 0x47, 0xff, 0xff, 0x00, 0x00, /* Physical maximum (65535) */ - 0x75, 0x10, /* Report size (16) */ - 0x95, 0x01, /* Report count (1) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - 0x09, 0x35, /* Usage (Rz) (Brake) */ - 0x26, 0xff, 0x03, /* Logical maximum (1023) */ - 0x46, 0xff, 0x03, /* Physical maximum (1023) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - 0x09, 0x32, /* Usage (Z) (Gas) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - 0x09, 0x31, /* Usage (Y) (Clutch) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - 0x81, 0x03, /* Input (Variable, Absolute, Constant) */ - 0x05, 0x09, /* Usage page (Button) */ - 0x19, 0x01, /* Usage minimum (1) */ - 0x29, 0x0d, /* Usage maximum (13) */ - 0x25, 0x01, /* Logical maximum (1) */ - 0x45, 0x01, /* Physical maximum (1) */ - 0x75, 0x01, /* Report size (1) */ - 0x95, 0x0d, /* Report count (13) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - 0x75, 0x0b, /* Report size (13) */ - 0x95, 0x01, /* Report count (1) */ - 0x81, 0x03, /* Usage (Variable, Absolute, Constant) */ - 0x05, 0x01, /* Usage page (Generic Desktop) */ - 0x09, 0x39, /* Usage (Hat Switch) */ - 0x25, 0x07, /* Logical maximum (7) */ - 0x46, 0x3b, 0x01, /* Physical maximum (315) */ - 0x55, 0x00, /* Unit exponent (0) */ - 0x65, 0x14, /* Unit (Eng Rot, Angular Pos) */ - 0x75, 0x04, /* Report size (4) */ - 0x81, 0x42, /* Input (Variable, Absolute, NullState) */ - 0x65, 0x00, /* Unit (None) */ - 0x81, 0x03, /* Input (Variable, Absolute, Constant) */ - 0x85, 0x60, /* Report ID (96), prev 10 */ - 0x06, 0x00, 0xff, /* Usage page (Vendor 1) */ - 0x09, 0x60, /* Usage (96), prev 10 */ - 0x75, 0x08, /* Report size (8) */ - 0x95, 0x3f, /* Report count (63) */ - 0x26, 0xff, 0x7f, /* Logical maximum (32767) */ - 0x15, 0x00, /* Logical minimum (0) */ - 0x46, 0xff, 0x7f, /* Physical maximum (32767) */ - 0x36, 0x00, 0x80, /* Physical minimum (-32768) */ - 0x91, 0x02, /* Output (Variable, Absolute) */ - 0x85, 0x02, /* Report ID (2) */ - 0x09, 0x02, /* Usage (2) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - 0x09, 0x14, /* Usage (20) */ - 0x85, 0x14, /* Report ID (20) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - 0xc0, /* End collection */ - 0xc0, /* End collection */ -}; - -static u8 t300rs_rdesc_adv_fixed[] = { - 0x05, 0x01, /* Usage page (Generic Desktop) */ - 0x09, 0x04, /* Usage (Joystick) */ - 0xa1, 0x01, /* Collection (Application) */ - 0x09, 0x01, /* Usage (Pointer) */ - 0xa1, 0x00, /* Collection (Physical) */ - 0x85, 0x07, /* Report ID (7) */ - 0x09, 0x30, /* Usage (X) */ - 0x15, 0x00, /* Logical minimum (0) */ - 0x27, 0xff, 0xff, 0x00, 0x00, /* Logical maximum (65535) */ - 0x35, 0x00, /* Physical minimum (0) */ - 0x47, 0xff, 0xff, 0x00, 0x00, /* Physical maximum (65535) */ - 0x75, 0x10, /* Report size (16) */ - 0x95, 0x01, /* Report count (1) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - 0x09, 0x31, /* Usage (Y) */ - 0x26, 0xff, 0x03, /* Logical maximum (1023) */ - 0x46, 0xff, 0x03, /* Physical maximum (1023) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - 0x09, 0x35, /* Usage (Rz) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - 0x09, 0x36, /* Usage (Slider) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - 0x81, 0x03, /* Input (Variable, Absolute, Constant) ? */ - 0x05, 0x09, /* Usage page (Button) */ - 0x19, 0x01, /* Usage minimum (1) */ - 0x29, 0x19, /* Usage maximum (25) */ - 0x25, 0x01, /* Logical maximum (1) */ - 0x45, 0x01, /* Physical maximum (1) */ - 0x75, 0x01, /* Report size (1) */ - 0x95, 0x19, /* Report count (25) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - 0x75, 0x03, /* Report size (3) */ - 0x95, 0x01, /* Report count (1) */ - 0x81, 0x03, /* Input (Variable, Absolute, Constant) */ - 0x05, 0x01, /* Usage page (Generic Desktop) */ - 0x09, 0x39, /* Usage (Hat Switch) */ - 0x25, 0x07, /* Logical maximum (7) */ - 0x46, 0x3b, 0x01, /* Physical maximum (315) */ - 0x55, 0x00, /* Unit exponent (0) */ - 0x65, 0x14, /* Unit (Eng Rot, Angular Pos) */ - 0x75, 0x04, /* Report size (4) */ - 0x81, 0x42, /* Input (Variable, Absolute, NullState) */ - 0x65, 0x00, /* Unit (None) */ - 0x85, 0x60, /* Report ID (96), prev 10 */ - 0x06, 0x00, 0xff, /* Usage page (Vendor 1) */ - 0x09, 0x60, /* Usage (96), prev 10 (why?) */ - 0x75, 0x08, /* Report size (8) */ - 0x95, 0x3f, /* Report count (63) */ - 0x26, 0xff, 0x00, /* Logical maximum (255) */ - 0x46, 0xff, 0x00, /* Physical maximum (255) */ - 0x91, 0x02, /* Output (Variable, Absolute) */ - 0x85, 0x02, /* Report ID (2) */ - 0x09, 0x02, /* Usage (Mouse) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - 0x09, 0x14, /* Usage (20) */ - 0x85, 0x14, /* Report ID (20) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - 0xc0, /* End collection */ - 0xc0, /* End collection */ -}; - -static u8 t300rs_rdesc_ps4_fixed[] = { - 0x05, 0x01, /* Usage page (Generic Desktop) */ - 0x09, 0x05, /* Usage (GamePad) */ - 0xa1, 0x01, /* Collection (Application) */ - 0x85, 0x01, /* Report ID (1) */ - 0x09, 0x00, /* Usage (U) (was X) */ - 0x09, 0x00, /* Usage (U) (was Y) */ - 0x09, 0x00, /* Usage (U) (was Z) */ - 0x09, 0x00, /* Usage (U) (was Rz)*/ - 0x15, 0x00, /* Logical minimum (0) */ - 0x26, 0xff, 0x00, /* Logical maximum (255) */ - 0x75, 0x08, /* Report size (8) */ - 0x95, 0x04, /* Report count (4) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - 0x09, 0x39, /* Usage (Hat Switch) */ - 0x15, 0x00, /* Logical minimum (0) */ - 0x25, 0x07, /* Logical maximum (7)*/ - 0x35, 0x00, /* Physical minimum (0) */ - 0x46, 0x3b, 0x01, /* Physical maximum (315) */ - 0x65, 0x14, /* Unit (Eng Rot, Angular Pos) */ - 0x75, 0x04, /* Report size (4) */ - 0x95, 0x01, /* Report count (1) */ - 0x81, 0x42, /* Input (Variable, Absolute, NullState) */ - 0x65, 0x00, /* Input (None) */ - 0x05, 0x09, /* Usage page (Button) */ - 0x19, 0x01, /* Usage minimum (1) */ - 0x29, 0x0e, /* Usage maximum (14) */ - 0x15, 0x00, /* Logical minimum (0) */ - 0x25, 0x01, /* Logical maximum (1) */ - 0x75, 0x01, /* Report size (1) */ - 0x95, 0x0e, /* Report size (14) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - 0x06, 0x00, 0xff, /* Usage page (Vendor 1) */ - 0x09, 0x20, /* Usage (32) */ - 0x75, 0x06, /* Report size (6) */ - 0x95, 0x01, /* Report count (1) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - 0x05, 0x01, /* Usage page (Generic Desktop) */ - 0x09, 0x00, /* Usage (U) (was Rx)*/ - 0x09, 0x00, /* Usage (U) (was Ry) */ - 0x15, 0x00, /* Logical minimum (0) */ - 0x26, 0xff, 0x00, /* Logical maximum (255) */ - 0x75, 0x08, /* Report size (8) */ - 0x95, 0x02, /* Report count (2) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - 0x05, 0x01, /* Usage page (Vendor 1) */ - /* constant zero? */ - 0x09, 0x00, /* Usage (33) */ - 0x95, 0x21, /* Report count (33) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - /* wheel */ - 0x09, 0x30, /* Usage (X) */ - 0x15, 0x00, /* Logical minimum (0) */ - 0x27, 0xff, 0xff, 0x00, 0x00, /* Logical maximum (65535) */ - 0x35, 0x00, /* Physical minimum (0) */ - 0x47, 0xff, 0xff, 0x00, 0x00, /* Physical maximum (65535) */ - 0x75, 0x10, /* Report size (16) */ - 0x95, 0x01, /* Report count (1) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - /* gas */ - 0x09, 0x31, /* Usage (Y) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - /* brake */ - 0x09, 0x32, /* Usage (Z) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - /* clutch */ - 0x09, 0x35, /* Usage (Rz) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - /* stick shifter (check model no) */ - 0x05, 0x09, /* Usage page (Button) */ - 0x19, 0x0f, /* Usage minimum (15) */ - 0x29, 0x17, /* Usage maximum (23) */ - 0x15, 0x00, /* Logical minimum (1) */ - 0x25, 0x01, /* Logical maximum (1) */ - 0x75, 0x01, /* Report size (1) */ - 0x95, 0x08, /* Report count (8) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - /* no clue */ - 0x05, 0x01, /* Usage page (Generic Desktop) */ - 0x09, 0x00, /* Usage (U) */ - 0x75, 0x08, /* Report size (8) */ - 0x95, 0x0c, /* Report count (12) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - /* continue unmodified */ - 0x06, 0x00, 0xff, /* Usage page (Vendor defined 1) */ - 0x85, 0x60, /* Report ID (5) (change to 0x60?) */ - 0x09, 0x60, /* Usage (34) (change to 0x60?) */ - 0x95, 0x1f, /* Report count (31) () */ - 0x91, 0x02, /* Output (Variable, Absolute) */ - 0x85, 0x03, /* Report ID (3) */ - 0x0a, 0x21, 0x27, /* ??? */ - 0x95, 0x2f, /* Report count (47) */ - 0xb1, 0x02, /* Feature (Data, Var, Abs) */ - 0xc0, /* End collection */ - - /* From here on out no clue */ - 0x06, 0xf0, - 0xff, 0x09, - 0x40, 0xa1, - 0x01, 0x85, - 0xf0, 0x09, - 0x47, 0x95, - 0x3f, 0xb1, - 0x02, 0x85, - 0xf1, 0x09, - 0x48, 0x95, - 0x3f, 0xb1, - 0x02, 0x85, - 0xf2, 0x09, - 0x49, 0x95, - 0x0f, 0xb1, - 0x02, 0x85, - 0xf3, 0x0a, - 0x01, 0x47, - 0x95, 0x07, - 0xb1, 0x02, - 0xc0, -}; - -static u8 spring_values[] = { - 0xa6, 0x6a, 0xa6, 0x6a, 0xfe, - 0xff, 0xfe, 0xff, 0xfe, 0xff, - 0xfe, 0xff, 0xdf, 0x58, 0xa6, - 0x6a, 0x06 -}; - -static u8 damper_values[] = { - 0xfc, 0x7f, 0xfc, 0x7f, 0xfe, - 0xff, 0xfe, 0xff, 0xfe, 0xff, - 0xfe, 0xff, 0xfc, 0x7f, 0xfc, - 0x7f, 0x07 -}; - -int t300rs_send_buf(struct t300rs_device_entry *t300rs, u8 *send_buffer, size_t len) -{ - int i; - /* check that send_buffer fits into our report */ - if (len > t300rs->buffer_length) - return -EINVAL; - - /* fill with actual data */ - for (i = 0; i < len; ++i) - t300rs->ff_field->value[i] = send_buffer[i]; - - /* fill the rest with zeroes */ - for (i = len; i < t300rs->buffer_length; ++i) - t300rs->ff_field->value[i] = 0; - - hid_hw_request(t300rs->hdev, t300rs->report, HID_REQ_SET_REPORT); - return 0; -} - -int t300rs_send_int(struct t300rs_device_entry *t300rs) -{ - t300rs_send_buf(t300rs, t300rs->send_buffer, t300rs->buffer_length); - memset(t300rs->send_buffer, 0, t300rs->buffer_length); - - return 0; -} - -static void t300rs_fill_header(struct t300rs_packet_header *packet_header, - uint8_t id, uint8_t code) -{ - packet_header->id = id + 1; - packet_header->code = code; -} - -int t300rs_play_effect(void *data, struct tmff2_effect_state *state) -{ - struct t300rs_device_entry *t300rs = data; - struct __packed t300rs_packet_play { - struct t300rs_packet_header header; - uint8_t value; - } *play_packet = (struct t300rs_packet_play *)t300rs->send_buffer; - - int ret; - - - t300rs_fill_header(&play_packet->header, state->effect.id, 0x89); - play_packet->value = 0x01; - - ret = t300rs_send_int(t300rs); - if (ret) - hid_err(t300rs->hdev, "failed starting effect play\n"); - - return ret; -} - -int t300rs_stop_effect(void *data, struct tmff2_effect_state *state) -{ - struct t300rs_device_entry *t300rs = data; - struct __packed t300rs_packet_stop { - struct t300rs_packet_header header; - uint8_t value; - } *stop_packet = (struct t300rs_packet_stop *)t300rs->send_buffer; - - int ret; - - - t300rs_fill_header(&stop_packet->header, state->effect.id, 0x89); - - ret = t300rs_send_int(t300rs); - if (ret) - hid_err(t300rs->hdev, "failed stopping effect play\n"); - - return ret; -} - -static void t300rs_fill_envelope(struct t300rs_packet_envelope *packet_envelope, - int16_t level, uint16_t duration, struct ff_envelope *envelope) -{ - uint16_t attack_length = (duration * envelope->attack_length) / 0x7fff; - uint16_t attack_level = (level * envelope->attack_level) / 0x7fff; - uint16_t fade_length = (duration * envelope->fade_length) / 0x7fff; - uint16_t fade_level = (level * envelope->fade_level) / 0x7fff; - - packet_envelope->attack_length = cpu_to_le16(attack_length); - packet_envelope->attack_level = cpu_to_le16(attack_level); - packet_envelope->fade_length = cpu_to_le16(fade_length); - packet_envelope->fade_level = cpu_to_le16(fade_level); -} - -static void t300rs_fill_timing(struct t300rs_packet_timing *packet_timing, - uint16_t duration, uint16_t offset){ - packet_timing->start_marker = 0x4f; - - packet_timing->duration = cpu_to_le16(duration); - packet_timing->offset = cpu_to_le16(offset); - - packet_timing->end_marker = 0xffff; -} - -static int t300rs_update_envelope(struct t300rs_device_entry *t300rs, - struct tmff2_effect_state *state, - int16_t level, - uint16_t duration, - uint8_t id, - struct ff_envelope envelope, - struct ff_envelope envelope_old - ) -{ - struct __packed t300rs_packet_mod_envelope { - struct t300rs_packet_header header; - uint8_t attribute; - uint16_t value; - } *packet_mod_envelope = (struct t300rs_packet_mod_envelope *)t300rs->send_buffer; - - uint16_t attack_length, attack_level, fade_length, fade_level; - int ret = 0; - - duration = duration - 1; - - attack_length = (duration * envelope.attack_length) / 0x7fff; - attack_level = (level * envelope.attack_level) / 0x7fff; - fade_length = (duration * envelope.fade_length) / 0x7fff; - fade_level = (level * envelope.fade_level) / 0x7fff; - - if (envelope.attack_length != envelope_old.attack_length) { - t300rs_fill_header(&packet_mod_envelope->header, id, 0x31); - packet_mod_envelope->attribute = 0x81; - packet_mod_envelope->value = cpu_to_le16(attack_length); - - ret = t300rs_send_int(t300rs); - if (ret) { - hid_err(t300rs->hdev, "failed modifying effect envelope\n"); - goto error; - } - } - - if (envelope.attack_level != envelope_old.attack_level) { - t300rs_fill_header(&packet_mod_envelope->header, id, 0x31); - packet_mod_envelope->attribute = 0x82; - packet_mod_envelope->value = cpu_to_le16(attack_level); - - ret = t300rs_send_int(t300rs); - if (ret) { - hid_err(t300rs->hdev, "failed modifying effect envelope\n"); - goto error; - } - } - - if (envelope.fade_length != envelope_old.fade_length) { - t300rs_fill_header(&packet_mod_envelope->header, id, 0x31); - packet_mod_envelope->attribute = 0x84; - packet_mod_envelope->value = cpu_to_le16(fade_length); - - ret = t300rs_send_int(t300rs); - if (ret) { - hid_err(t300rs->hdev, "failed modifying effect envelope\n"); - goto error; - } - } - - if (envelope.fade_level != envelope_old.fade_level) { - t300rs_fill_header(&packet_mod_envelope->header, id, 0x31); - packet_mod_envelope->attribute = 0x88; - packet_mod_envelope->value = cpu_to_le16(fade_level); - - ret = t300rs_send_int(t300rs); - if (ret) { - hid_err(t300rs->hdev, "failed modifying effect envelope\n"); - goto error; - } - } - -error: - return ret; -} - -static int t300rs_update_simple_duration(struct t300rs_device_entry *t300rs, - struct tmff2_effect_state *state, unsigned type) -{ - struct ff_effect effect = state->effect; - struct __packed t300rs_packet_mod_duration { - struct t300rs_packet_header header; - uint16_t marker; - uint16_t duration; - } *packet_mod_duration = (struct t300rs_packet_mod_duration *)t300rs->send_buffer; - - uint16_t duration; - int ret = 0; - - duration = effect.replay.length - 1; - - t300rs_fill_header(&packet_mod_duration->header, effect.id, 0x49); - packet_mod_duration->marker = cpu_to_le16(0x4100 + type); - packet_mod_duration->duration = cpu_to_le16(duration); - - ret = t300rs_send_int(t300rs); - if (ret) { - hid_err(t300rs->hdev, "failed modifying duration\n"); - goto error; - } - -error: - return ret; -} - -static int t300rs_update_periodic_duration(struct t300rs_device_entry *t300rs, - struct tmff2_effect_state *state, unsigned type) -{ - struct ff_effect effect = state->effect; - struct __packed t300rs_packet_mod_duration { - struct t300rs_packet_header header; - uint8_t type; - uint8_t marker; - uint16_t duration; - } *packet_mod_duration = (struct t300rs_packet_mod_duration *)t300rs->send_buffer; - - uint16_t duration; - int ret = 0; - - duration = effect.replay.length - 1; - - t300rs_fill_header(&packet_mod_duration->header, effect.id, 0x49); - packet_mod_duration->type = type; - packet_mod_duration->marker = 0x41; - packet_mod_duration->duration = cpu_to_le16(duration); - - ret = t300rs_send_int(t300rs); - if (ret) { - hid_err(t300rs->hdev, "failed modifying duration\n"); - goto error; - } - -error: - return ret; -} - -static int t300rs_update_ramp_duration(struct t300rs_device_entry *t300rs, - struct tmff2_effect_state *state) -{ - struct ff_effect effect = state->effect; - struct __packed t300rs_packet_mod_duration { - struct t300rs_packet_header header; - uint8_t marker0; - uint16_t duration0; - uint8_t marker1; - uint8_t marker2; - uint16_t duration1; - } *packet_mod_duration = (struct t300rs_packet_mod_duration *)t300rs->send_buffer; - - uint16_t duration; - int ret = 0; - - duration = effect.replay.length - 1; - - t300rs_fill_header(&packet_mod_duration->header, effect.id, 0x4e); - packet_mod_duration->marker0 = 0x08; - packet_mod_duration->duration0 = cpu_to_le16(duration); - packet_mod_duration->marker1 = 0x05; - packet_mod_duration->marker2 = 0x41; - packet_mod_duration->duration1 = cpu_to_le16(duration); - - ret = t300rs_send_int(t300rs); - if (ret) { - hid_err(t300rs->hdev, "failed modifying duration\n"); - goto error; - } - -error: - return ret; -} - -static int t300rs_update_constant(struct t300rs_device_entry *t300rs, - struct tmff2_effect_state *state) -{ - struct ff_effect effect = state->effect; - struct ff_effect old = state->old; - struct ff_constant_effect constant = effect.u.constant; - struct ff_constant_effect constant_old = old.u.constant; - struct __packed t300rs_packet_mod_constant { - struct t300rs_packet_header header; - uint16_t level; - } *packet_mod_constant = (struct t300rs_packet_mod_constant *)t300rs->send_buffer; - int ret; - int16_t level; - - level = (constant.level * fixp_sin16(effect.direction * 360 / 0x10000)) / 0x7fff; - - if ((constant.level != constant_old.level) || (effect.direction != old.direction)) { - - t300rs_fill_header(&packet_mod_constant->header, effect.id, 0x0a); - packet_mod_constant->level = cpu_to_le16(level); - - ret = t300rs_send_int(t300rs); - if (ret) { - hid_err(t300rs->hdev, "failed modifying constant effect\n"); - goto error; - } - - } - - ret = t300rs_update_envelope(t300rs, - state, - level, - effect.replay.length, - effect.id, - constant.envelope, - constant_old.envelope - ); - - if (ret) { - hid_err(t300rs->hdev, "failed modifying constant envelope\n"); - goto error; - } - - ret = t300rs_update_simple_duration(t300rs, state, 0x00); - if (ret) { - hid_err(t300rs->hdev, "failed modifying constant duration\n"); - goto error; - } - -error: - return ret; -} - -static int t300rs_update_ramp(struct t300rs_device_entry *t300rs, - struct tmff2_effect_state *state) -{ - struct ff_effect effect = state->effect; - struct ff_effect old = state->old; - struct ff_ramp_effect ramp = effect.u.ramp; - struct ff_ramp_effect ramp_old = old.u.ramp; - struct __packed t300rs_packet_mod_ramp { - struct t300rs_packet_header header; - uint8_t attribute; - uint16_t difference; - uint16_t level; - } *packet_mod_ramp = (struct t300rs_packet_mod_ramp *)t300rs->send_buffer; - - int ret; - - uint16_t difference, top, bottom; - int16_t level; - - top = ramp.end_level > ramp.start_level ? ramp.end_level : ramp.start_level; - bottom = ramp.end_level > ramp.start_level ? ramp.start_level : ramp.end_level; - - - difference = ((top - bottom) * fixp_sin16(effect.direction * 360 / 0x10000)) / 0x7fff; - - - level = (top * fixp_sin16(effect.direction * 360 / 0x10000)) / 0x7fff; - - if ((ramp.start_level != ramp_old.start_level) - || (ramp.end_level != ramp_old.end_level) - || (effect.direction != old.direction)) { - - t300rs_fill_header(&packet_mod_ramp->header, effect.id, 0x0e); - packet_mod_ramp->attribute = 0x03; - packet_mod_ramp->difference = cpu_to_le16(difference); - packet_mod_ramp->level = cpu_to_le16(level); - - ret = t300rs_send_int(t300rs); - if (ret) { - hid_err(t300rs->hdev, "failed modifying ramp effect\n"); - goto error; - } - - } - - ret = t300rs_update_envelope(t300rs, - state, - level, - effect.replay.length, - effect.id, - ramp.envelope, - ramp_old.envelope - ); - - - if (ret) { - hid_err(t300rs->hdev, "failed modifying ramp envelope\n"); - goto error; - } - - ret = t300rs_update_ramp_duration(t300rs, state); - if (ret) { - hid_err(t300rs->hdev, "failed modifying ramp duration\n"); - goto error; - } - -error: - return ret; -} - -static int t300rs_update_damper(struct t300rs_device_entry *t300rs, - struct tmff2_effect_state *state) -{ - struct ff_effect effect = state->effect; - struct ff_effect old = state->old; - struct ff_condition_effect damper = effect.u.condition[0]; - struct ff_condition_effect damper_old = old.u.condition[0]; - struct __packed t300rs_packet_mod_damper { - struct t300rs_packet_header header; - uint8_t attribute; - uint16_t value0; - uint16_t value1; - } *packet_mod_damper = (struct t300rs_packet_mod_damper *)t300rs->send_buffer; - - int ret, input_level; - - input_level = damper_level; - if (state->effect.type == FF_FRICTION) - input_level = friction_level; - - if (state->effect.type == FF_SPRING) - input_level = spring_level; - - if (damper.right_coeff != damper_old.right_coeff) { - int16_t coeff = damper.right_coeff * input_level / 100; - - t300rs_fill_header(&packet_mod_damper->header, effect.id, 0x0e); - packet_mod_damper->attribute = 0x41; - packet_mod_damper->value0 = cpu_to_le16(coeff); - - ret = t300rs_send_int(t300rs); - if (ret) { - hid_err(t300rs->hdev, "failed modifying damper rc\n"); - goto error; - } - - } - - if (damper.left_coeff != damper_old.left_coeff) { - int16_t coeff = damper.left_coeff * input_level / 100; - - t300rs_fill_header(&packet_mod_damper->header, effect.id, 0x0e); - packet_mod_damper->attribute = 0x42; - packet_mod_damper->value0 = cpu_to_le16(coeff); - - ret = t300rs_send_int(t300rs); - if (ret) { - hid_err(t300rs->hdev, "failed modifying damper lc\n"); - goto error; - } - - } - - if ((damper.deadband != damper_old.deadband) - || (damper.center != damper_old.center)) { - - uint16_t right_deadband = 0xfffe - damper.deadband - damper.center; - uint16_t left_deadband = 0xfffe - damper.deadband + damper.center; - - t300rs_fill_header(&packet_mod_damper->header, effect.id, 0x0e); - packet_mod_damper->attribute = 0x4c; - packet_mod_damper->value0 = cpu_to_le16(right_deadband); - packet_mod_damper->value1 = cpu_to_le16(left_deadband); - - ret = t300rs_send_int(t300rs); - if (ret) { - hid_err(t300rs->hdev, "failed modifying damper deadband\n"); - goto error; - } - - } - - ret = t300rs_update_simple_duration(t300rs, state, 0x06); - if (ret) { - hid_err(t300rs->hdev, "failed modifying damper duration\n"); - goto error; - } - -error: - return ret; -} - -static int t300rs_update_spring(struct t300rs_device_entry *t300rs, - struct tmff2_effect_state *state) -{ - return t300rs_update_damper(t300rs, state); -} - - -static int t300rs_update_periodic(struct t300rs_device_entry *t300rs, - struct tmff2_effect_state *state) -{ - struct ff_effect effect = state->effect; - struct ff_effect old = state->old; - struct ff_periodic_effect periodic = effect.u.periodic; - struct ff_periodic_effect periodic_old = old.u.periodic; - struct __packed t300rs_packet_mod_periodic { - struct t300rs_packet_header header; - uint8_t attribute; - uint16_t value; - } *packet_mod_periodic = (struct t300rs_packet_mod_periodic *)t300rs->send_buffer; - - int ret; - int16_t magnitude, phase; - - magnitude = (periodic.magnitude * fixp_sin16(effect.direction * 360 / 0x10000)) / 0x7fff; - phase = periodic.phase; - - if ((periodic.magnitude != periodic_old.magnitude) - || (effect.direction != old.direction)) { - - t300rs_fill_header(&packet_mod_periodic->header, effect.id, 0x0e); - packet_mod_periodic->attribute = 0x01; - packet_mod_periodic->value = cpu_to_le16(magnitude); - - ret = t300rs_send_int(t300rs); - if (ret) { - hid_err(t300rs->hdev, "failed modifying periodic magnitude\n"); - goto error; - } - - } - - if (periodic.offset != periodic_old.offset) { - uint16_t offset = periodic.offset; - - t300rs_fill_header(&packet_mod_periodic->header, effect.id, 0x0e); - packet_mod_periodic->attribute = 0x02; - packet_mod_periodic->value = cpu_to_le16(offset); - - ret = t300rs_send_int(t300rs); - if (ret) { - hid_err(t300rs->hdev, "failed modifying periodic offset\n"); - goto error; - } - - } - - if (periodic.phase != periodic_old.phase) { - - t300rs_fill_header(&packet_mod_periodic->header, effect.id, 0x0e); - packet_mod_periodic->attribute = 0x04; - packet_mod_periodic->value = cpu_to_le16(phase); - - ret = t300rs_send_int(t300rs); - if (ret) { - hid_err(t300rs->hdev, "failed modifying periodic phase\n"); - goto error; - } - - } - - if (periodic.period != periodic_old.period) { - int16_t period = periodic.period; - - t300rs_fill_header(&packet_mod_periodic->header, effect.id, 0x0e); - packet_mod_periodic->attribute = 0x08; - packet_mod_periodic->value = cpu_to_le16(period); - - ret = t300rs_send_int(t300rs); - if (ret) { - hid_err(t300rs->hdev, "failed modifying periodic period\n"); - goto error; - } - - } - - ret = t300rs_update_envelope(t300rs, - state, - magnitude, - effect.replay.length, - effect.id, - periodic.envelope, - periodic_old.envelope - ); - - if (ret) { - hid_err(t300rs->hdev, "failed modifying periodic envelope\n"); - goto error; - } - - ret = t300rs_update_periodic_duration(t300rs, state, periodic.waveform - 0x57); - if (ret) { - hid_err(t300rs->hdev, "failed modifying periodic duration\n"); - goto error; - } - -error: - return ret; -} - -static int t300rs_upload_constant(struct t300rs_device_entry *t300rs, - struct tmff2_effect_state *state) -{ - struct ff_effect effect = state->effect; - struct ff_constant_effect constant = state->effect.u.constant; - struct __packed t300rs_packet_constant { - struct t300rs_packet_header header; - uint16_t level; - struct t300rs_packet_envelope envelope; - uint8_t zero; - struct t300rs_packet_timing timing; - } *packet_constant = (struct t300rs_packet_constant *)t300rs->send_buffer; - - int16_t level; - uint16_t duration, offset; - - int ret; - - level = (constant.level * fixp_sin16(effect.direction * 360 / 0x10000)) / 0x7fff; - duration = effect.replay.length - 1; - - offset = effect.replay.delay; - - t300rs_fill_header(&packet_constant->header, effect.id, 0x6a); - - packet_constant->level = cpu_to_le16(level); - - t300rs_fill_envelope(&packet_constant->envelope, level, duration, - &constant.envelope); - t300rs_fill_timing(&packet_constant->timing, duration, offset); - - ret = t300rs_send_int(t300rs); - if (ret) - hid_err(t300rs->hdev, "failed uploading constant effect\n"); - - return ret; -} - -static int t300rs_upload_ramp(struct t300rs_device_entry *t300rs, - struct tmff2_effect_state *state) -{ - struct ff_effect effect = state->effect; - struct ff_ramp_effect ramp = state->effect.u.ramp; - struct __packed t300rs_packet_ramp { - struct t300rs_packet_header header; - uint16_t difference; - uint16_t level; - uint8_t zero1[2]; - uint16_t duration; - uint16_t marker; - struct t300rs_packet_envelope envelope; - uint8_t direction; - struct t300rs_packet_timing timing; - } *packet_ramp = (struct t300rs_packet_ramp *)t300rs->send_buffer; - - int ret; - uint16_t difference, offset, top, bottom, duration; - int16_t level; - - duration = effect.replay.length - 1; - - top = ramp.end_level > ramp.start_level ? ramp.end_level : ramp.start_level; - bottom = ramp.end_level > ramp.start_level ? ramp.start_level : ramp.end_level; - - - difference = ((top - bottom) * fixp_sin16(effect.direction * 360 / 0x10000)) / 0x7fff; - level = (top * fixp_sin16(effect.direction * 360 / 0x10000)) / 0x7fff; - offset = effect.replay.delay; - - - t300rs_fill_header(&packet_ramp->header, effect.id, 0x6b); - - packet_ramp->difference = cpu_to_le16(difference); - packet_ramp->level = cpu_to_le16(level); - packet_ramp->duration = cpu_to_le16(duration); - - packet_ramp->marker = cpu_to_le16(0x8000); - - t300rs_fill_envelope(&packet_ramp->envelope, level, duration, - &ramp.envelope); - - packet_ramp->direction = ramp.end_level > ramp.start_level ? 0x04 : 0x05; - t300rs_fill_timing(&packet_ramp->timing, duration, offset); - - ret = t300rs_send_int(t300rs); - if (ret) - hid_err(t300rs->hdev, "failed uploading ramp"); - - return ret; -} - -static int t300rs_upload_spring(struct t300rs_device_entry *t300rs, - struct tmff2_effect_state *state) -{ - struct ff_effect effect = state->effect; - /* we only care about the first axis */ - struct ff_condition_effect spring = state->effect.u.condition[0]; - struct __packed t300rs_packet_spring { - struct t300rs_packet_header header; - uint16_t right_coeff; - uint16_t left_coeff; - uint16_t right_deadband; - uint16_t left_deadband; - uint8_t spring_start[17]; - struct t300rs_packet_timing timing; - } *packet_spring = (struct t300rs_packet_spring *)t300rs->send_buffer; - - int ret; - uint16_t duration, right_coeff, left_coeff, right_deadband, left_deadband, offset; - - duration = effect.replay.length - 1; - - right_coeff = spring.right_coeff * spring_level / 100; - left_coeff = spring.left_coeff * spring_level / 100; - - right_deadband = 0xfffe - spring.deadband - spring.center; - left_deadband = 0xfffe - spring.deadband + spring.center; - - offset = effect.replay.delay; - - t300rs_fill_header(&packet_spring->header, effect.id, 0x64); - - packet_spring->right_coeff = cpu_to_le16(right_coeff); - packet_spring->left_coeff = cpu_to_le16(left_coeff); - packet_spring->right_deadband = cpu_to_le16(right_deadband); - packet_spring->left_deadband = cpu_to_le16(left_deadband); - - memcpy(&packet_spring->spring_start, spring_values, ARRAY_SIZE(spring_values)); - t300rs_fill_timing(&packet_spring->timing, duration, offset); - - ret = t300rs_send_int(t300rs); - if (ret) - hid_err(t300rs->hdev, "failed uploading spring\n"); - - return ret; -} - -static int t300rs_upload_damper(struct t300rs_device_entry *t300rs, - struct tmff2_effect_state *state) -{ - - struct ff_effect effect = state->effect; - /* we only care about the first axis */ - struct ff_condition_effect spring = state->effect.u.condition[0]; - struct __packed t300rs_packet_damper { - struct t300rs_packet_header header; - uint16_t right_coeff; - uint16_t left_coeff; - uint16_t right_deadband; - uint16_t left_deadband; - uint8_t damper_start[17]; - struct t300rs_packet_timing timing; - } *packet_damper = (struct t300rs_packet_damper *)t300rs->send_buffer; - - int ret, input_level; - uint16_t duration, right_coeff, left_coeff, right_deadband, left_deadband, offset; - - duration = effect.replay.length - 1; - - input_level = damper_level; - if (state->effect.type == FF_FRICTION) - input_level = friction_level; - - right_coeff = spring.right_coeff * input_level / 100; - left_coeff = spring.left_coeff * input_level / 100; - - right_deadband = 0xfffe - spring.deadband - spring.center; - left_deadband = 0xfffe - spring.deadband + spring.center; - - offset = effect.replay.delay; - - t300rs_fill_header(&packet_damper->header, effect.id, 0x64); - - packet_damper->right_coeff = cpu_to_le16(right_coeff); - packet_damper->left_coeff = cpu_to_le16(left_coeff); - packet_damper->right_deadband = cpu_to_le16(right_deadband); - packet_damper->left_deadband = cpu_to_le16(left_deadband); - - memcpy(&packet_damper->damper_start, damper_values, ARRAY_SIZE(damper_values)); - t300rs_fill_timing(&packet_damper->timing, duration, offset); - - ret = t300rs_send_int(t300rs); - if (ret) - hid_err(t300rs->hdev, "failed uploading spring\n"); - - return ret; -} - -static int t300rs_upload_periodic(struct t300rs_device_entry *t300rs, - struct tmff2_effect_state *state) -{ - struct ff_effect effect = state->effect; - struct ff_periodic_effect periodic = state->effect.u.periodic; - struct __packed t300rs_packet_periodic { - struct t300rs_packet_header header; - uint16_t magnitude; - uint16_t periodic_offset; - uint16_t phase; - uint16_t period; - uint16_t marker; - struct t300rs_packet_envelope envelope; - uint8_t waveform; - struct t300rs_packet_timing timing; - } *packet_periodic = (struct t300rs_packet_periodic *)t300rs->send_buffer; - - int ret; - uint16_t duration, period, offset, periodic_offset; - int16_t phase, magnitude; - - duration = effect.replay.length - 1; - magnitude = (periodic.magnitude * fixp_sin16(effect.direction * 360 / 0x10000)) / 0x7fff; - - phase = periodic.phase; - periodic_offset = periodic.offset; - - period = periodic.period; - offset = effect.replay.delay; - - t300rs_fill_header(&packet_periodic->header, effect.id, 0x6b); - - packet_periodic->magnitude = cpu_to_le16(magnitude); - packet_periodic->periodic_offset = cpu_to_le16(periodic_offset); - packet_periodic->phase = cpu_to_le16(phase); - packet_periodic->period = cpu_to_le16(period); - - packet_periodic->marker = cpu_to_le16(0x8000); - - t300rs_fill_envelope(&packet_periodic->envelope, magnitude, duration, - &periodic.envelope); - - packet_periodic->waveform = periodic.waveform - 0x57; - - t300rs_fill_timing(&packet_periodic->timing, duration, offset); - - ret = t300rs_send_int(t300rs); - if (ret) - hid_err(t300rs->hdev, "failed uploading periodic effect"); - - return ret; -} - -int t300rs_update_effect(void *data, struct tmff2_effect_state *state) -{ - struct t300rs_device_entry *t300rs = data; - switch (state->effect.type) { - case FF_CONSTANT: - return t300rs_update_constant(t300rs, state); - case FF_RAMP: - return t300rs_update_ramp(t300rs, state); - case FF_SPRING: - return t300rs_update_spring(t300rs, state); - case FF_DAMPER: - case FF_FRICTION: - case FF_INERTIA: - return t300rs_update_damper(t300rs, state); - case FF_PERIODIC: - return t300rs_update_periodic(t300rs, state); - default: - hid_err(t300rs->hdev, "invalid effect type: %x", state->effect.type); - return -1; - } -} - -int t300rs_upload_effect(void *data, struct tmff2_effect_state *state) -{ - struct t300rs_device_entry *t300rs = data; - switch (state->effect.type) { - case FF_CONSTANT: - return t300rs_upload_constant(t300rs, state); - case FF_RAMP: - return t300rs_upload_ramp(t300rs, state); - case FF_SPRING: - return t300rs_upload_spring(t300rs, state); - case FF_DAMPER: - case FF_FRICTION: - case FF_INERTIA: - return t300rs_upload_damper(t300rs, state); - case FF_PERIODIC: - return t300rs_upload_periodic(t300rs, state); - default: - hid_err(t300rs->hdev, "invalid effect type: %x", state->effect.type); - return -1; - } -} - -static int t300rs_switch_mode(void *data, uint16_t mode) -{ - struct t300rs_device_entry *t300rs = data; - if (!t300rs) - return -ENODEV; - - if(t300rs->mode == mode) /* already in specified mode */ - return 0; - - if (mode == 0) - /* go to normal mode */ - usb_control_msg(t300rs->usbdev, - usb_sndctrlpipe(t300rs->usbdev, 0), - 83, 0x41, 5, 0, 0, 0, - USB_CTRL_SET_TIMEOUT - ); - else if (mode == 1) - /* go to advanced mode */ - usb_control_msg(t300rs->usbdev, - usb_sndctrlpipe(t300rs->usbdev, 0), - 83, 0x41, 3, 0, 0, 0, - USB_CTRL_SET_TIMEOUT - ); - else - hid_warn(t300rs->hdev, "mode %i not supported\n", mode); - - - return 0; -} - -static struct t300rs_alt_modes { - char *id; - char *label; - uint16_t mode; -} t300rs_modes[] = { - {"base", "T300RS base", 0}, - {"F1", "T300RS with F1 wheel attachment", 1} -}; - -static ssize_t t300rs_alt_mode_show(void *data, char *buf) -{ - struct t300rs_device_entry *t300rs = data; - ssize_t count = 0; - int i; - if (!t300rs) - return -ENODEV; - - if (t300rs->attachment != T300RS_F1_ATTACHMENT) - /* we only support one base mode */ - return scnprintf(buf, PAGE_SIZE, "%s: %s *\n", - t300rs_modes[0].id, t300rs_modes[0].label); - - for (i = 0; i < ARRAY_SIZE(t300rs_modes); ++i) { - count += scnprintf(buf + count, PAGE_SIZE - count, "%s: %s", - t300rs_modes[i].id, t300rs_modes[i].label); - - if (count >= PAGE_SIZE - 1) - return count; - - if (t300rs_modes[i].mode == t300rs->mode) - count += scnprintf(buf + count, PAGE_SIZE - count, " *\n"); - else - count += scnprintf(buf + count, PAGE_SIZE - count, "\n"); - - if (count >= PAGE_SIZE - 1) - return count; - } - - return count; -} - -static ssize_t t300rs_alt_mode_store(void *data, const char *buf, size_t count) -{ - struct t300rs_device_entry *t300rs = data; - int i, len, mode_len; - char *lbuf; - if (!t300rs) - return -ENODEV; - - if (t300rs->attachment != T300RS_F1_ATTACHMENT) - return count; /* don't do anything */ - - lbuf = kasprintf(GFP_KERNEL, "%s", buf); - if (!lbuf) - return -ENOMEM; - - len = strlen(buf); - for (i = 0; i < ARRAY_SIZE(t300rs_modes); ++i) { - mode_len = strlen(t300rs_modes[i].id); - if (mode_len > len) - continue; - - if (strncmp(lbuf, t300rs_modes[i].id, mode_len) == 0) { - t300rs_switch_mode(data, t300rs_modes[i].mode); - break; - } - } - - kfree(lbuf); - return count; -} - -int t300rs_set_autocenter(void *data, uint16_t value) -{ - struct t300rs_device_entry *t300rs = data; - struct __packed t300rs_packet_autocenter { - struct t300rs_setup_header header; - uint16_t value; - } *autocenter_packet; - int ret; - - if (!t300rs) - return -ENODEV; - - /* TODO: this should probably also use a separately allocated buffer? */ - autocenter_packet = (struct t300rs_packet_autocenter *)t300rs->send_buffer; - - autocenter_packet->header.cmd = 0x08; - autocenter_packet->header.code = 0x04; - autocenter_packet->value = cpu_to_le16(0x01); - - if ((ret = t300rs_send_int(t300rs))) { - hid_err(t300rs->hdev, "failed setting autocenter"); - return ret; - } - - autocenter_packet->header.cmd = 0x08; - autocenter_packet->header.code = 0x03; - - autocenter_packet->value = cpu_to_le16(value); - - if ((ret = t300rs_send_int(t300rs))) - hid_err(t300rs->hdev, "failed setting autocenter"); - - return ret; -} - -int t300rs_set_gain(void *data, uint16_t gain) -{ - struct t300rs_device_entry *t300rs = data; - struct __packed t300rs_packet_gain { - struct t300rs_setup_header header; - } *gain_packet; - int ret; - - if (!t300rs) - return -ENODEV; - - gain_packet = (struct t300rs_packet_gain *)t300rs->send_buffer; - gain_packet->header.cmd = 0x02; - gain_packet->header.code = (gain >> 8) & 0xff; - - if ((ret = t300rs_send_int(t300rs))) - hid_err(t300rs->hdev, "failed setting gain: %i\n", ret); - - return ret; -} - -int t300rs_set_range(void *data, uint16_t value) -{ - struct t300rs_device_entry *t300rs = data; - /* it's important that we don't use t300rs->send_buffer, as range can be - * set from outside of the FFB environment, and we don't want to - * accidentally overwrite any data. */ - u8 *send_buffer = kzalloc(t300rs->buffer_length, GFP_KERNEL); - uint16_t scaled_value; - int ret; - - if (value < 40) { - hid_info(t300rs->hdev, "value %i too small, clamping to 40\n", value); - value = 40; - } - - if (value > 1080) { - hid_info(t300rs->hdev, "value %i too large, clamping to 1080\n", value); - value = 1080; - } - - if (!send_buffer) { - ret = -EINVAL; - hid_err(t300rs->hdev, "could not allocate send_buffer\n"); - goto err; - } - - scaled_value = value * 0x3c; - send_buffer[0] = 0x08; - send_buffer[1] = 0x11; - send_buffer[2] = scaled_value & 0xff; - send_buffer[3] = scaled_value >> 8; - - if ((ret = t300rs_send_buf(t300rs, send_buffer, t300rs->buffer_length))) - hid_warn(t300rs->hdev, "failed setting range\n"); - - /* since everythin went OK, update the current range */ - range = value; -err: - kfree(send_buffer); - return ret; -} - -static int t300rs_send_open(struct t300rs_device_entry *t300rs) -{ - struct __packed t300rs_packet_open { - struct t300rs_setup_header header; - } *open_packet; - - open_packet = (struct t300rs_packet_open *)t300rs->send_buffer; - open_packet->header.cmd = 0x01; - open_packet->header.code = 0x05; - - return t300rs_send_int(t300rs); -} - -static int t300rs_send_close(struct t300rs_device_entry *t300rs) -{ - struct __packed t300rs_packet_open { - struct t300rs_setup_header header; - } *open_packet; - - open_packet = (struct t300rs_packet_open *)t300rs->send_buffer; - open_packet->header.cmd = 0x01; - - return t300rs_send_int(t300rs); -} - -int t300rs_open(void *data, int open_mode) -{ - struct t300rs_device_entry *t300rs = data; - if (!t300rs) - return -ENODEV; - - if (open_mode && t300rs_send_open(t300rs)) - hid_warn(t300rs->hdev, "failed sending open command\n"); - - return t300rs->open(t300rs->input_dev); -} - -int t300rs_close(void *data, int open_mode) -{ - struct t300rs_device_entry *t300rs = data; - int ret; - - if (!t300rs) - return -ENODEV; - - if (open_mode && (ret = t300rs_send_close(t300rs))) - hid_warn(t300rs->hdev, "failed sending close command\n"); - - t300rs->close(t300rs->input_dev); - return ret; -} - -static int t300rs_check_firmware(struct t300rs_device_entry *t300rs) -{ - int ret; - struct t300rs_fw_response *fw_response = - kzalloc(sizeof(struct t300rs_fw_response), GFP_KERNEL); - - if (!fw_response) { - hid_err(t300rs->hdev, "could not allocate fw_response\n"); - return -ENOMEM; - } - - /* Fetch firmware version */ - ret = usb_control_msg(t300rs->usbdev, - usb_rcvctrlpipe(t300rs->usbdev, 0), - t300rs_fw_request.bRequest, - t300rs_fw_request.bRequestType, - t300rs_fw_request.wValue, - t300rs_fw_request.wIndex, - fw_response, - t300rs_fw_request.wLength, - USB_CTRL_SET_TIMEOUT - ); - - if (ret < 0) { - hid_err(t300rs->hdev, "could not fetch firmware version: %i\n", ret); - goto out; - } - - /* Educated guess */ - if (fw_response->fw_version < 31 && ret >= 0) { - hid_err(t300rs->hdev, - "firmware version %i is too old, please update.\n", - fw_response->fw_version - ); - - hid_info(t300rs->hdev, "note: this has to be done through Windows.\n"); - - ret = -EINVAL; - goto out; - } - - /* everything OK */ - ret = 0; - -out: - kfree(fw_response); - return ret; -} - -static int t300rs_get_attachment(struct t300rs_device_entry *t300rs) -{ - /* taken directly from hid_tminit */ - struct __packed t300rs_attachment_response - { - uint16_t type; - - union { - struct __packed { - uint16_t field0; - uint16_t field1; - uint8_t attachment; - uint8_t model; - uint16_t field2; - uint16_t field3; - uint16_t field4; - uint16_t field5; - } a; - - struct __packed { - uint16_t field0; - uint16_t field1; - uint8_t attachment; - uint8_t model; - } b; - }; - } *response = kzalloc(GFP_KERNEL, sizeof(struct t300rs_attachment_response)); - struct usb_ctrlrequest t300rs_attachment_rq = { - .bRequestType = 0xc1, - .bRequest = 73, - .wValue = 0, - .wIndex = 0, - .wLength = sizeof(struct t300rs_attachment_response) - }; - int ret, attachment; - if (!response) - return -ENODEV; - - ret = usb_control_msg(t300rs->usbdev, - usb_rcvctrlpipe(t300rs->usbdev, 0), - t300rs_attachment_rq.bRequest, - t300rs_attachment_rq.bRequestType, - t300rs_attachment_rq.wValue, - t300rs_attachment_rq.wIndex, - response, - sizeof(struct t300rs_attachment_response), - USB_CTRL_SET_TIMEOUT - ); - - if (ret < 0) { - hid_err(t300rs->hdev, "could not fetch attachment: %i\n", ret); - goto out; - } - - if (response->type == cpu_to_le16(0x49)) { - attachment = response->a.attachment; - } else if (response->type == cpu_to_le16(0x47)) { - attachment = response->b.attachment; - } else { - hid_err(t300rs->hdev, "unknown packet type %hx\n, please contact a maintainer", - response->type); - ret = -EINVAL; - goto out; - } - - kfree(response); - return attachment; - -out: - kfree(response); - return ret; -} - -static int t300rs_wheel_init(struct tmff2_device_entry *tmff2, int open_mode) -{ - struct t300rs_device_entry *t300rs = kzalloc(sizeof(struct t300rs_device_entry), GFP_KERNEL); - struct list_head *report_list; - int ret; - - if (!t300rs) { - ret = -ENOMEM; - goto t300rs_err; - } - - t300rs->hdev = tmff2->hdev; - t300rs->input_dev = tmff2->input_dev; - t300rs->usbdev = to_usb_device(tmff2->hdev->dev.parent->parent); - - if(t300rs->hdev->product == TMT300RS_PS4_NORM_ID) - t300rs->buffer_length = T300RS_PS4_BUFFER_LENGTH; - else - t300rs->buffer_length = T300RS_NORM_BUFFER_LENGTH; - - t300rs->send_buffer = kzalloc(t300rs->buffer_length, GFP_KERNEL); - if (!t300rs->send_buffer) { - ret = -ENOMEM; - goto send_err; - } - - if ((ret = t300rs_check_firmware(t300rs))) - goto firmware_err; - - report_list = &t300rs->hdev->report_enum[HID_OUTPUT_REPORT].report_list; - - /* because we set the rdesc, we know exactly which report and field to use */ - t300rs->report = list_entry(report_list->next, struct hid_report, list); - t300rs->ff_field = t300rs->report->field[0]; - - t300rs->open = t300rs->input_dev->open; - t300rs->close = t300rs->input_dev->close; - - /* TODO: PS4 advanced mode? */ - alt_mode = (t300rs->mode = (t300rs->hdev->product == TMT300RS_PS3_ADV_ID)); - if ((t300rs->attachment = t300rs_get_attachment(t300rs)) < 0) - t300rs->attachment = T300RS_DEFAULT_ATTACHMENT; - - /* everythin went OK */ - tmff2->data = t300rs; - tmff2->params = t300rs_params; - tmff2->max_effects = T300RS_MAX_EFFECTS; - memcpy(tmff2->supported_effects, t300rs_effects, sizeof(t300rs_effects)); - - if (!open_mode) - t300rs_send_open(t300rs); - - hid_info(t300rs->hdev, "force feedback for T300RS\n"); - return 0; - -firmware_err: - kfree(t300rs->send_buffer); -send_err: - kfree(t300rs); -t300rs_err: - hid_err(tmff2->hdev, "failed initializing T300RS\n"); - return ret; -} - -static int t300rs_wheel_destroy(void *data) -{ - struct t300rs_device_entry *t300rs = data; - if (!t300rs) - return -ENODEV; - - kfree(t300rs->send_buffer); - kfree(t300rs); - return 0; -} - -static __u8 *t300rs_wheel_fixup(struct hid_device *hdev, __u8 *rdesc, - unsigned int *rsize) -{ - switch (hdev->product) { - case TMT300RS_PS3_NORM_ID: - /* normal PS3 mode */ - rdesc = t300rs_rdesc_nrm_fixed; - *rsize = sizeof(t300rs_rdesc_nrm_fixed); - break; - - case TMT300RS_PS4_NORM_ID: - /* PS4 normal mode */ - rdesc = t300rs_rdesc_ps4_fixed; - *rsize = sizeof(t300rs_rdesc_ps4_fixed); - break; - - case TMT300RS_PS3_ADV_ID: - /* PS3 advanced mode */ - rdesc = t300rs_rdesc_adv_fixed; - *rsize = sizeof(t300rs_rdesc_adv_fixed); - break; - } - - return rdesc; -} - -int t300rs_populate_api(struct tmff2_device_entry *tmff2) -{ - /* set callbacks */ - tmff2->play_effect = t300rs_play_effect; - tmff2->upload_effect = t300rs_upload_effect; - tmff2->update_effect = t300rs_update_effect; - tmff2->stop_effect = t300rs_stop_effect; - - tmff2->wheel_init = t300rs_wheel_init; - tmff2->wheel_destroy = t300rs_wheel_destroy; - - tmff2->open = t300rs_open; - tmff2->close = t300rs_close; - tmff2->set_gain = t300rs_set_gain; - tmff2->set_range = t300rs_set_range; - tmff2->switch_mode = t300rs_switch_mode; - tmff2->alt_mode_show = t300rs_alt_mode_show; - tmff2->alt_mode_store = t300rs_alt_mode_store; - tmff2->set_autocenter = t300rs_set_autocenter; - tmff2->wheel_fixup = t300rs_wheel_fixup; - - return 0; -} diff --git a/hid-tmt300rs.h b/hid-tmt300rs.h deleted file mode 100644 index 9045341..0000000 --- a/hid-tmt300rs.h +++ /dev/null @@ -1,9 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -#ifndef __HID_TMT300RS_H -#define __HID_TMT300RS_H - -#include "hid-tmff2.h" - -int t300rs_populate_api(struct tmff2_device_entry *tmff2); - -#endif /* __HID_TM300RS_H */ diff --git a/hid-tmtx.c b/hid-tmtx.c deleted file mode 100644 index 47d59ad..0000000 --- a/hid-tmtx.c +++ /dev/null @@ -1,323 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -#include -#include -#include "hid-tmff2.h" - -#define T248_MAX_EFFECTS 16 -#define T248_BUFFER_LENGTH 63 - -static const u8 setup_0[64] = { 0x42, 0x01 }; -static const u8 setup_1[64] = { 0x0a, 0x04, 0x90, 0x03 }; -static const u8 setup_2[64] = { 0x0a, 0x04, 0x00, 0x0c }; -static const u8 setup_3[64] = { 0x0a, 0x04, 0x12, 0x10 }; -static const u8 setup_4[64] = { 0x0a, 0x04, 0x00, 0x06 }; -static const u8 setup_5[64] = { 0x0a, 0x04, 0x00, 0x0e }; -static const u8 setup_6[64] = { 0x0a, 0x04, 0x00, 0x0e, 0x01 }; -static const u8 *const setup_arr[] = { setup_0, setup_1, setup_2, setup_3, setup_4, setup_5, setup_6 }; -static const unsigned int setup_arr_sizes[] = { - ARRAY_SIZE(setup_0), - ARRAY_SIZE(setup_1), - ARRAY_SIZE(setup_2), - ARRAY_SIZE(setup_3), - ARRAY_SIZE(setup_4), - ARRAY_SIZE(setup_5), - ARRAY_SIZE(setup_6) -}; - -static const unsigned long tx_params = - PARAM_SPRING_LEVEL - | PARAM_DAMPER_LEVEL - | PARAM_FRICTION_LEVEL - | PARAM_RANGE - | PARAM_GAIN - ; - -static const signed short tx_effects[] = { - FF_CONSTANT, - FF_RAMP, - FF_SPRING, - FF_DAMPER, - FF_FRICTION, - FF_INERTIA, - FF_PERIODIC, - FF_SINE, - FF_TRIANGLE, - FF_SQUARE, - FF_SAW_UP, - FF_SAW_DOWN, - FF_AUTOCENTER, - FF_GAIN, - -1 -}; - -/* TODO: sort through this stuff */ -static u8 tx_pc_rdesc_fixed[] = { - 0x05, 0x01, /* Usage page (Generic Desktop) */ - 0x09, 0x04, /* Usage (Joystick) */ - 0xa1, 0x01, /* Collection (Application) */ - 0x09, 0x01, /* Usage (Pointer) */ - 0xa1, 0x00, /* Collection (Physical) */ - 0x85, 0x07, /* Report ID (7) */ - 0x09, 0x30, /* Usage (X) */ - 0x15, 0x00, /* Logical minimum (0) */ - 0x27, 0xff, 0xff, 0x00, 0x00, /* Logical maximum (65535) */ - 0x35, 0x00, /* Physical minimum (0) */ - 0x47, 0xff, 0xff, 0x00, 0x00, /* Physical maximum (65535) */ - 0x75, 0x10, /* Report size (16) */ - 0x95, 0x01, /* Report count (1) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - 0x09, 0x35, /* Usage (Rz) (Brake) */ - 0x26, 0xff, 0x03, /* Logical maximum (1023) */ - 0x46, 0xff, 0x03, /* Physical maximum (1023) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - 0x09, 0x32, /* Usage (Z) (Gas) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - 0x09, 0x31, /* Usage (Y) (Clutch) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - 0x81, 0x03, /* Input (Variable, Absolute, Constant) */ - 0x05, 0x09, /* Usage page (Button) */ - 0x19, 0x01, /* Usage minimum (1) */ - 0x29, 0x0d, /* Usage maximum (13) */ - 0x25, 0x01, /* Logical maximum (1) */ - 0x45, 0x01, /* Physical maximum (1) */ - 0x75, 0x01, /* Report size (1) */ - 0x95, 0x0d, /* Report count (13) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - 0x75, 0x0b, /* Report size (13) */ - 0x95, 0x01, /* Report count (1) */ - 0x81, 0x03, /* Usage (Variable, Absolute, Constant) */ - 0x05, 0x01, /* Usage page (Generic Desktop) */ - 0x09, 0x39, /* Usage (Hat Switch) */ - 0x25, 0x07, /* Logical maximum (7) */ - 0x46, 0x3b, 0x01, /* Physical maximum (315) */ - 0x55, 0x00, /* Unit exponent (0) */ - 0x65, 0x14, /* Unit (Eng Rot, Angular Pos) */ - 0x75, 0x04, /* Report size (4) */ - 0x81, 0x42, /* Input (Variable, Absolute, NullState) */ - 0x65, 0x00, /* Unit (None) */ - 0x81, 0x03, /* Input (Variable, Absolute, Constant) */ - 0x85, 0x60, /* Report ID (96), prev 10 */ - 0x06, 0x00, 0xff, /* Usage page (Vendor 1) */ - 0x09, 0x60, /* Usage (96), prev 10 */ - 0x75, 0x08, /* Report size (8) */ - 0x95, 0x3f, /* Report count (63) */ - 0x26, 0xff, 0x7f, /* Logical maximum (32767) */ - 0x15, 0x00, /* Logical minimum (0) */ - 0x46, 0xff, 0x7f, /* Physical maximum (32767) */ - 0x36, 0x00, 0x80, /* Physical minimum (-32768) */ - 0x91, 0x02, /* Output (Variable, Absolute) */ - 0x85, 0x02, /* Report ID (2) */ - 0x09, 0x02, /* Usage (2) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - 0x09, 0x14, /* Usage (20) */ - 0x85, 0x14, /* Report ID (20) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - 0xc0, /* End collection */ - 0xc0, /* End collection */ -}; - -static int tx_interrupts(struct t300rs_device_entry *tx) -{ - u8 *send_buf = kmalloc(256, GFP_KERNEL); - struct usb_interface *usbif = to_usb_interface(tx->hdev->dev.parent); - struct usb_host_endpoint *ep; - int ret, trans, b_ep, i; - - if (!send_buf) { - hid_err(tx->hdev, "failed allocating send buffer\n"); - return -ENOMEM; - } - - ep = &usbif->cur_altsetting->endpoint[1]; - b_ep = ep->desc.bEndpointAddress; - - for (i = 0; i < ARRAY_SIZE(setup_arr); ++i) { - memcpy(send_buf, setup_arr[i], setup_arr_sizes[i]); - - ret = usb_interrupt_msg(tx->usbdev, - usb_sndintpipe(tx->usbdev, b_ep), - send_buf, setup_arr_sizes[i], - &trans, - USB_CTRL_SET_TIMEOUT); - - if (ret) { - hid_err(tx->hdev, "setup data couldn't be sent\n"); - goto err; - } - } - -err: - kfree(send_buf); - return ret; -} - -int tx_wheel_destroy(void *data) -{ - struct t300rs_device_entry *t300rs = data; - - if (!t300rs) - return -ENODEV; - - kfree(t300rs->send_buffer); - kfree(t300rs); - return 0; -} - -int tx_set_range(void *data, uint16_t value) -{ - struct t300rs_device_entry *tx = data; - - if (value < 140) { - hid_info(tx->hdev, "value %i too small, clamping to 140\n", value); - value = 140; - } - - if (value > 900) { - hid_info(tx->hdev, "value %i too large, clamping to 900\n", value); - value = 900; - } - - return t300rs_set_range(data, value); -} - -static int tx_send_open(struct t300rs_device_entry *tx) -{ - int r1, r2; - tx->send_buffer[0] = 0x01; - tx->send_buffer[1] = 0x04; - if ((r1 = t300rs_send_int(tx))) - return r1; - - tx->send_buffer[0] = 0x01; - tx->send_buffer[1] = 0x05; - if ((r2 = t300rs_send_int(tx))) - return r2; - - return 0; -} - -static int tx_open(void *data, int open_mode) -{ - struct t300rs_device_entry *tx = data; - - if (!tx) - return -ENODEV; - - if (open_mode) - tx_send_open(tx); - - return tx->open(tx->input_dev); -} - -static int tx_send_close(struct t300rs_device_entry *tx) -{ - int r1, r2; - tx->send_buffer[0] = 0x01; - tx->send_buffer[1] = 0x05; - if ((r1 = t300rs_send_int(tx))) - return r1; - - tx->send_buffer[0] = 0x01; - tx->send_buffer[1] = 0x00; - if ((r2 = t300rs_send_int(tx))) - return r2; - - return 0; -} - -static int tx_close(void *data, int open_mode) -{ - struct t300rs_device_entry *tx = data; - - if (!tx) - return -ENODEV; - - if (open_mode) - tx_send_close(tx); - - tx->close(tx->input_dev); - return 0; -} - -int tx_wheel_init(struct tmff2_device_entry *tmff2, int open_mode) -{ - struct t300rs_device_entry *tx = kzalloc(sizeof(struct t300rs_device_entry), GFP_KERNEL); - struct list_head *report_list; - int ret; - - - if (!tx) { - ret = -ENOMEM; - goto tx_err; - } - - tx->hdev = tmff2->hdev; - tx->input_dev = tmff2->input_dev; - tx->usbdev = to_usb_device(tmff2->hdev->dev.parent->parent); - tx->buffer_length = T248_BUFFER_LENGTH; - - tx->send_buffer = kzalloc(tx->buffer_length, GFP_KERNEL); - if (!tx->send_buffer) { - ret = -ENOMEM; - goto send_err; - } - - report_list = &tx->hdev->report_enum[HID_OUTPUT_REPORT].report_list; - tx->report = list_entry(report_list->next, struct hid_report, list); - tx->ff_field = tx->report->field[0]; - - tx->open = tx->input_dev->open; - tx->close = tx->input_dev->close; - - if ((ret = tx_interrupts(tx))) - goto interrupt_err; - - /* everything went OK */ - tmff2->data = tx; - tmff2->params = tx_params; - tmff2->max_effects = T248_MAX_EFFECTS; - memcpy(tmff2->supported_effects, tx_effects, sizeof(tx_effects)); - - if (!open_mode) - tx_send_open(tx); - - hid_info(tx->hdev, "force feedback for TX\n"); - return 0; - -interrupt_err: -send_err: - kfree(tx); -tx_err: - hid_err(tmff2->hdev, "failed initializing TX\n"); - return ret; -} - -static __u8 *tx_wheel_fixup(struct hid_device *hdev, __u8 *rdesc, - unsigned int *rsize) -{ - rdesc = tx_pc_rdesc_fixed; - *rsize = sizeof(tx_pc_rdesc_fixed); - return rdesc; -} - -int tx_populate_api(struct tmff2_device_entry *tmff2) -{ - tmff2->play_effect = t300rs_play_effect; - tmff2->upload_effect = t300rs_upload_effect; - tmff2->update_effect = t300rs_update_effect; - tmff2->stop_effect = t300rs_stop_effect; - - tmff2->set_gain = t300rs_set_gain; - tmff2->set_autocenter = t300rs_set_autocenter; - /* TX only has 900 degree range, instead of T300RS 1080 */ - tmff2->set_range = tx_set_range; - tmff2->wheel_fixup = tx_wheel_fixup; - - tmff2->open = tx_open; - tmff2->close = tx_close; - - tmff2->wheel_init = tx_wheel_init; - tmff2->wheel_destroy = tx_wheel_destroy; - - return 0; -} diff --git a/ps4-input.txt b/ps4-input.txt deleted file mode 100644 index 9286c7e..0000000 --- a/ps4-input.txt +++ /dev/null @@ -1,54 +0,0 @@ -1 ff00.0021 = 0 -2 ff00.0021 = 0 -3 ff00.0021 = 0 -4 ff00.0021 = 0 -5 ff00.0021 = 0 -6 ff00.0021 = 0 -7 ff00.0021 = 0 -8 ff00.0021 = 0 -9 ff00.0021 = 0 -10 ff00.0021 = 0 -11 ff00.0021 = 0 -12 ff00.0021 = 0 -13 ff00.0021 = 0 -14 ff00.0021 = 0 -15 ff00.0021 = 0 -16 ff00.0021 = 0 -17 ff00.0021 = 0 -18 ff00.0021 = 0 -19 ff00.0021 = 0 -20 ff00.0021 = 0 -21 ff00.0021 = 0 -22 ff00.0021 = 0 -23 ff00.0021 = 0 -24 ff00.0021 = 0 -25 ff00.0021 = 0 -26 ff00.0021 = 0 -27 ff00.0021 = 0 -28 ff00.0021 = 0 -29 ff00.0021 = 0 -30 ff00.0021 = 0 -31 ff00.0021 = 0 -32 ff00.0021 = 0 -33 ff00.0021 = 0 -34 ff00.0021 = 226 /* wheel */ -35 ff00.0021 = 125 /* wheel */ -36 ff00.0021 = 255 /* gas */ -37 ff00.0021 = 255 /* gas */ -38 ff00.0021 = 255 /* brake */ -39 ff00.0021 = 255 /* brake */ -40 ff00.0021 = 255 /* clutch */ -41 ff00.0021 = 255 /* clutch */ -42 ff00.0021 = 0 -43 ff00.0021 = 255 -44 ff00.0021 = 255 -45 ff00.0021 = 0 -46 ff00.0021 = 0 -47 ff00.0021 = 0 -48 ff00.0021 = 0 -49 ff00.0021 = 0 -50 ff00.0021 = 0 -51 ff00.0021 = 0 -52 ff00.0021 = 0 -53 ff00.0021 = 0 -54 ff00.0021 = 0 diff --git a/src/Kbuild b/src/Kbuild new file mode 100644 index 0000000..b8541e0 --- /dev/null +++ b/src/Kbuild @@ -0,0 +1,2 @@ +obj-m := hid-tmff-new.o +hid-tmff-new-y := hid-tmff2.o hid-tmt300rs/hid-tmt300rs.o hid-tmt248/hid-tmt248.o hid-tmtx/hid-tmtx.o diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 0000000..5e2ab40 --- /dev/null +++ b/src/Makefile @@ -0,0 +1,16 @@ +KDIR ?= /lib/modules/$(shell uname -r)/build + +all: hid-tminit + $(MAKE) -C $(KDIR) M=$(shell pwd) modules + +install: hid-tminit + $(MAKE) -C $(KDIR) M=$(shell pwd) modules_install + depmod -A + +clean: hid-tminit + $(MAKE) -C $(KDIR) M=$(shell pwd) clean + + +.PHONY: hid-tminit +hid-tminit: + $(MAKE) -C hid-tminit KDIR="$(KDIR)" $(MAKECMDGOALS) diff --git a/src/hid-tmff2.c b/src/hid-tmff2.c new file mode 100644 index 0000000..44df1e3 --- /dev/null +++ b/src/hid-tmff2.c @@ -0,0 +1,749 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#include +#include +#include +#include "hid-tmff2.h" + + +int open_mode = 1; +module_param(open_mode, int, 0660); +MODULE_PARM_DESC(open_mode, + "Whether to send mode change commands on open/close"); + +int timer_msecs = DEFAULT_TIMER_PERIOD; +module_param(timer_msecs, int, 0660); +MODULE_PARM_DESC(timer_msecs, + "Timer resolution in msecs"); + +/* should these be removed and just rely on /sys? */ +int spring_level = 30; +module_param(spring_level, int, 0); +MODULE_PARM_DESC(spring_level, + "Level of spring force (0-100), as per Oversteer standards"); + +int damper_level = 30; +module_param(damper_level, int, 0); +MODULE_PARM_DESC(damper_level, + "Level of damper force (0-100), as per Oversteer standards"); + +int friction_level = 30; +module_param(friction_level, int, 0); +MODULE_PARM_DESC(friction_level, + "Level of friction force (0-100), as per Oversteer standards"); + +int range = 900; +module_param(range, int, 0); +MODULE_PARM_DESC(range, + "Range of wheel, depends on the wheel. Invalid values are ignored"); + +int alt_mode = 0; +module_param(alt_mode, int, 0); +MODULE_PARM_DESC(alt_mode, + "Alternate mode, eg. F1 mode"); + +#define GAIN_MAX 65535 +int gain = 40000; +module_param(gain, int, 0); +MODULE_PARM_DESC(gain, + "Level of gain (0-65535)"); + +static spinlock_t lock; +static unsigned long lock_flags; + +static struct tmff2_device_entry *tmff2_from_hdev(struct hid_device *hdev) +{ + struct tmff2_device_entry *tmff2; + spin_lock_irqsave(&lock, lock_flags); + + if (!(tmff2 = hid_get_drvdata(hdev))) + dev_err(&hdev->dev, "hdev private data not found\n"); + + spin_unlock_irqrestore(&lock, lock_flags); + + return tmff2; +} + +static struct tmff2_device_entry *tmff2_from_input(struct input_dev *input_dev) +{ + struct hid_device *hdev; + spin_lock_irqsave(&lock, lock_flags); + + if (!(hdev = input_get_drvdata(input_dev))) + dev_err(&input_dev->dev, "input_dev private data not found\n"); + + spin_unlock_irqrestore(&lock, lock_flags); + + return tmff2_from_hdev(hdev); +} + +static ssize_t spring_level_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + unsigned int value; + int ret; + + ret = kstrtouint(buf, 0, &value); + if (ret) { + dev_err(dev, "kstrtouint failed at spring_level_store: %i", ret); + return ret; + } + + if (value > 100) { + dev_info(dev, "value %i larger than max 100, clamping to 100.\n", value); + value = 100; + } + + spring_level = value; + + return count; +} + +static ssize_t spring_level_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + + return scnprintf(buf, PAGE_SIZE, "%u\n", spring_level); +} +static DEVICE_ATTR_RW(spring_level); + +static ssize_t damper_level_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + unsigned int value; + int ret; + + + ret = kstrtouint(buf, 0, &value); + if (ret) { + dev_err(dev, "kstrtouint failed at damper_level_store: %i", ret); + return ret; + } + + if (value > 100) { + dev_info(dev, "value %i larger than max 100, clamping to 100.\n", value); + value = 100; + } + + damper_level = value; + + return count; +} + +static ssize_t damper_level_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + + return scnprintf(buf, PAGE_SIZE, "%u\n", damper_level); +} +static DEVICE_ATTR_RW(damper_level); + +static ssize_t friction_level_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + unsigned int value; + int ret; + + + ret = kstrtouint(buf, 0, &value); + if (ret) { + dev_err(dev, "kstrtouint failed at friction_level_store: %i", ret); + return ret; + } + + if (value > 100) { + dev_info(dev, "value %i larger than max 100, clamping to 100.\n", value); + value = 100; + } + + friction_level = value; + + return count; +} + +static ssize_t friction_level_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + size_t count; + + + count = scnprintf(buf, PAGE_SIZE, "%u\n", friction_level); + + return count; +} +static DEVICE_ATTR_RW(friction_level); + +static ssize_t range_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct tmff2_device_entry *tmff2 = tmff2_from_hdev(to_hid_device(dev)); + unsigned int value; + int ret; + + + if (!tmff2) + return -ENODEV; + + if ((ret = kstrtouint(buf, 0, &value))) { + hid_err(tmff2->hdev, "kstrtouint failed at range_store: %i", ret); + return ret; + } + + if (tmff2->set_range) { + if ((ret = tmff2->set_range(tmff2->data, value))) + return ret; + } + + return count; +} + +static ssize_t range_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + + return scnprintf(buf, PAGE_SIZE, "%u\n", range); +} +static DEVICE_ATTR_RW(range); + +static ssize_t alternate_modes_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct tmff2_device_entry *tmff2 = tmff2_from_hdev(to_hid_device(dev)); + + if (!tmff2) + return -ENODEV; + + if (tmff2->alt_mode_store) + return tmff2->alt_mode_store(tmff2->data, buf, count); + + return 0; +} + +static ssize_t alternate_modes_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct tmff2_device_entry *tmff2 = tmff2_from_hdev(to_hid_device(dev)); + + if (!tmff2) + return -ENODEV; + + if (tmff2->alt_mode_show) + return tmff2->alt_mode_show(tmff2->data, buf); + + return 0; +} +static DEVICE_ATTR_RW(alternate_modes); + +static ssize_t gain_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct tmff2_device_entry *tmff2 = tmff2_from_hdev(to_hid_device(dev)); + unsigned int value; + int ret; + + if (!tmff2) + return -ENODEV; + + if ((ret = kstrtouint(buf, 0, &value))) { + dev_err(dev, "kstrtouint failed at gain_store: %i", ret); + return ret; + } + + gain = value; + if (tmff2->set_gain) /* if we can, update gain immediately */ + tmff2->set_gain(tmff2->data, (GAIN_MAX * gain) / GAIN_MAX); + + return count; +} + +static ssize_t gain_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return scnprintf(buf, PAGE_SIZE, "%i\n", gain); +} +static DEVICE_ATTR_RW(gain); + +static void tmff2_set_gain(struct input_dev *dev, uint16_t value) +{ + struct tmff2_device_entry *tmff2 = tmff2_from_input(dev); + + if (!tmff2) + return; + + if (!tmff2->set_gain) { + hid_err(tmff2->hdev, "missing set_gain\n"); + return; + } + + if (tmff2->set_gain(tmff2->data, (value * gain) / GAIN_MAX)) + hid_warn(tmff2->hdev, "unable to set gain\n"); +} + +static void tmff2_set_autocenter(struct input_dev *dev, uint16_t value) +{ + struct tmff2_device_entry *tmff2 = tmff2_from_input(dev); + + if (!tmff2) + return; + + if (!tmff2->set_autocenter) { + hid_err(tmff2->hdev, "missing set_autocenter\n"); + return; + } + + if (tmff2->set_autocenter(tmff2->data, value)) + hid_warn(tmff2->hdev, "unable to set autocenter\n"); +} + +static void tmff2_work_handler(struct work_struct *w) +{ + struct delayed_work *dw = container_of(w, struct delayed_work, work); + struct tmff2_device_entry *tmff2 = container_of(dw, struct tmff2_device_entry, work); + struct tmff2_effect_state *state; + int max_count = 0, effect_id; + unsigned long time_now; + __u16 effect_length; + + + if (!tmff2) + return; + + for (effect_id = 0; effect_id < tmff2->max_effects; ++effect_id) { + spin_lock(&tmff2->lock); + + time_now = JIFFIES2MS(jiffies); + state = &tmff2->states[effect_id]; + + effect_length = state->effect.replay.length; + if (test_bit(FF_EFFECT_PLAYING, &state->flags) && effect_length) { + if ((time_now - state->start_time) >= effect_length) { + __clear_bit(FF_EFFECT_PLAYING, &state->flags); + __clear_bit(FF_EFFECT_QUEUE_UPDATE, &state->flags); + + if (state->count) + state->count--; + + if (state->count) + __set_bit(FF_EFFECT_QUEUE_START, &state->flags); + } + } + + if (test_bit(FF_EFFECT_QUEUE_UPLOAD, &state->flags)) { + if (tmff2->upload_effect(tmff2->data, state)) { + hid_warn(tmff2->hdev, "failed uploading effect\n"); + } else { + __clear_bit(FF_EFFECT_QUEUE_UPLOAD, &state->flags); + /* if we're uploading an effect, it's bound to be the up + * to date available */ + __clear_bit(FF_EFFECT_QUEUE_UPDATE, &state->flags); + } + } + + if (test_bit(FF_EFFECT_QUEUE_UPDATE, &state->flags)) { + if (tmff2->update_effect(tmff2->data, state)) + hid_warn(tmff2->hdev, "failed updating effect\n"); + else + __clear_bit(FF_EFFECT_QUEUE_UPDATE, &state->flags); + } + + if (test_bit(FF_EFFECT_QUEUE_START, &state->flags)) { + if (tmff2->play_effect(tmff2->data, state)) { + hid_warn(tmff2->hdev, "failed starting effect\n"); + } else { + __clear_bit(FF_EFFECT_QUEUE_START, &state->flags); + __set_bit(FF_EFFECT_PLAYING, &state->flags); + } + + } + + if (test_bit(FF_EFFECT_QUEUE_STOP, &state->flags)) { + if (tmff2->stop_effect(tmff2->data, state)) { + hid_warn(tmff2->hdev, "failed stopping effect\n"); + } else { + __clear_bit(FF_EFFECT_PLAYING, &state->flags); + __clear_bit(FF_EFFECT_QUEUE_STOP, &state->flags); + } + } + + if (state->count > max_count) + max_count = state->count; + + spin_unlock(&tmff2->lock); + } + + if (max_count && tmff2->allow_scheduling) + schedule_delayed_work(&tmff2->work, msecs_to_jiffies(timer_msecs)); +} + +static int tmff2_upload(struct input_dev *dev, + struct ff_effect *effect, struct ff_effect *old) +{ + struct tmff2_effect_state *state; + struct tmff2_device_entry *tmff2 = tmff2_from_input(dev); + + if (!tmff2) + return -ENODEV; + + if (effect->type == FF_PERIODIC && effect->u.periodic.period == 0) + return -EINVAL; + + state = &tmff2->states[effect->id]; + + spin_lock(&tmff2->lock); + + state->effect = *effect; + + if (old) { + if (!test_bit(FF_EFFECT_QUEUE_UPDATE, &state->flags)) + state->old = *old; + + __set_bit(FF_EFFECT_QUEUE_UPDATE, &state->flags); + } else { + __set_bit(FF_EFFECT_QUEUE_UPLOAD, &state->flags); + } + + spin_unlock(&tmff2->lock); + return 0; +} + +static int tmff2_play(struct input_dev *dev, int effect_id, int value) +{ + struct tmff2_effect_state *state; + struct tmff2_device_entry *tmff2 = tmff2_from_input(dev); + + if (!tmff2) + return -ENODEV; + + state = &tmff2->states[effect_id]; + if (!state) + return 0; + + spin_lock(&tmff2->lock); + if (value > 0) { + state->count = value; + state->start_time = JIFFIES2MS(jiffies); + __set_bit(FF_EFFECT_QUEUE_START, &state->flags); + __clear_bit(FF_EFFECT_QUEUE_STOP, &state->flags); + } else { + __set_bit(FF_EFFECT_QUEUE_STOP, &state->flags); + __clear_bit(FF_EFFECT_QUEUE_START, &state->flags); + } + + spin_unlock(&tmff2->lock); + + if (!delayed_work_pending(&tmff2->work) && tmff2->allow_scheduling) + schedule_delayed_work(&tmff2->work, 0); + + return 0; +} + +static int tmff2_open(struct input_dev *dev) +{ + struct tmff2_device_entry *tmff2 = tmff2_from_input(dev); + + if (!tmff2) + return -ENODEV; + + if (tmff2->open) + return tmff2->open(tmff2->data, open_mode); + + hid_err(tmff2->hdev, "no open callback set\n"); + return -EINVAL; +} + +static void tmff2_close(struct input_dev *dev) +{ + struct tmff2_device_entry *tmff2 = tmff2_from_input(dev); + + if (!tmff2) + return; + + /* since we're closing the device, no need to continue feeding it new data */ + cancel_delayed_work_sync(&tmff2->work); + + if (tmff2->close) { + tmff2->close(tmff2->data, open_mode); + return; + } + + hid_err(tmff2->hdev, "no close callback set\n"); +} + +static int tmff2_create_files(struct tmff2_device_entry *tmff2) +{ + struct device *dev = &tmff2->hdev->dev; + int ret; + + + /* could use short circuiting but this is more explicit */ + if (tmff2->params & PARAM_GAIN) { + if ((ret = device_create_file(dev, &dev_attr_gain))) { + hid_err(tmff2->hdev, "unable to create sysfs for gain\n"); + goto gain_err; + } + } + + if (tmff2->params & PARAM_ALT_MODE) { + if ((ret = device_create_file(dev, &dev_attr_alternate_modes))) { + hid_err(tmff2->hdev, "unable to create sysfs for alternate_modes\n"); + goto alt_err; + } + } + + if (tmff2->params & PARAM_RANGE) { + if ((ret = device_create_file(dev, &dev_attr_range))) { + hid_warn(tmff2->hdev, "unable to create sysfs for range\n"); + goto range_err; + } + } + + if (tmff2->params & PARAM_SPRING_LEVEL) { + if ((ret = device_create_file(dev, &dev_attr_spring_level))) { + hid_warn(tmff2->hdev, "unable to create sysfs for spring_level\n"); + goto spring_err; + } + } + + if (tmff2->params & PARAM_DAMPER_LEVEL) { + if ((ret = device_create_file(dev, &dev_attr_damper_level))) { + hid_warn(tmff2->hdev, "unable to create sysfs for damper_level\n"); + goto damper_err; + } + } + + if (tmff2->params & PARAM_FRICTION_LEVEL) { + if ((ret = device_create_file(dev, &dev_attr_friction_level))) { + hid_warn(tmff2->hdev, "unable to create sysfs for friction_level\n"); + goto friction_err; + } + } + + return 0; + +friction_err: + device_remove_file(dev, &dev_attr_damper_level); +damper_err: + device_remove_file(dev, &dev_attr_spring_level); +spring_err: + device_remove_file(dev, &dev_attr_range); +range_err: + device_remove_file(dev, &dev_attr_alternate_modes); +alt_err: + device_remove_file(dev, &dev_attr_gain); +gain_err: + return ret; +} + +static int tmff2_wheel_init(struct tmff2_device_entry *tmff2) +{ + int ret, i; + struct ff_device *ff; + + spin_lock_init(&lock); + spin_lock_init(&tmff2->lock); + INIT_DELAYED_WORK(&tmff2->work, tmff2_work_handler); + + /* get parameters etc from backend */ + if ((ret = tmff2->wheel_init(tmff2, open_mode))) + goto err; + + + tmff2->states = kzalloc(sizeof(struct tmff2_effect_state) * tmff2->max_effects, + GFP_KERNEL); + + if (!tmff2->states) { + ret = -ENOMEM; + goto err; + } + + /* set supported effects into input_dev->ffbit */ + for (i = 0; tmff2->supported_effects[i] >= 0; ++i) + __set_bit(tmff2->supported_effects[i], tmff2->input_dev->ffbit); + + /* create actual ff device*/ + if ((ret = input_ff_create(tmff2->input_dev, tmff2->max_effects))) { + hid_err(tmff2->hdev, "could not create input_ff\n"); + goto err; + } + + /* set ff callbacks */ + ff = tmff2->input_dev->ff; + ff->upload = tmff2_upload; + ff->playback = tmff2_play; + + if (tmff2->open) + tmff2->input_dev->open = tmff2_open; + + if (tmff2->close) + tmff2->input_dev->close = tmff2_close; + + /* set defaults wherever possible */ + if (tmff2->set_gain) { + ff->set_gain = tmff2_set_gain; + tmff2->set_gain(tmff2->data, (GAIN_MAX * gain) / GAIN_MAX); + } + + if (tmff2->set_autocenter) + ff->set_autocenter = tmff2_set_autocenter; + + if (tmff2->set_range) + tmff2->set_range(tmff2->data, range); + + if (tmff2->switch_mode) + tmff2->switch_mode(tmff2->data, alt_mode); + + /* create files */ + if ((ret = tmff2_create_files(tmff2))) + goto err; + + tmff2->allow_scheduling = 1; + return 0; + + input_ff_destroy(tmff2->input_dev); +err: + return ret; +} + +static int tmff2_probe(struct hid_device *hdev, const struct hid_device_id *id) +{ + struct tmff2_device_entry *tmff2 = + kzalloc(sizeof(struct tmff2_device_entry), GFP_KERNEL); + + int ret; + + + if (!tmff2) { + ret = -ENOMEM; + goto oom_err; + } + + tmff2->hdev = hdev; + hid_set_drvdata(tmff2->hdev, tmff2); + + switch (tmff2->hdev->product) { + /* t300rs */ + case TMT300RS_PS3_NORM_ID: + case TMT300RS_PS3_ADV_ID: + case TMT300RS_PS4_NORM_ID: + if ((ret = t300rs_populate_api(tmff2))) + goto wheel_err; + break; + + case TMT248_PC_ID: + if ((ret = t248_populate_api(tmff2))) + goto wheel_err; + break; + + case TX_ACTIVE: + if ((ret = tx_populate_api(tmff2))) + goto wheel_err; + break; + + default: + ret = -ENODEV; + goto wheel_err; + } + + if ((ret = hid_parse(tmff2->hdev))) { + hid_err(hdev, "parse failed\n"); + goto hid_err; + } + + if ((ret = hid_hw_start(tmff2->hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF))) { + hid_err(hdev, "hw start failed\n"); + goto hid_err; + } + + tmff2->input_dev = list_entry(hdev->inputs.next, struct hid_input, list)->input; + + if ((ret = tmff2_wheel_init(tmff2))) { + hid_err(hdev, "init failed\n"); + goto init_err; + } + + return 0; + +init_err: + hid_hw_stop(hdev); +hid_err: + tmff2->wheel_destroy(tmff2->data); +wheel_err: + kfree(tmff2); +oom_err: + return ret; +} + +static __u8 *tmff2_report_fixup(struct hid_device *hdev, __u8 *rdesc, + unsigned int *rsize) +{ + struct tmff2_device_entry *tmff2 = tmff2_from_hdev(hdev); + + if (!tmff2) /* not entirely sure what the best course of action would be here */ + return rdesc; + + if (tmff2->wheel_fixup) + return tmff2->wheel_fixup(hdev, rdesc, rsize); + + return rdesc; +} + +static void tmff2_remove(struct hid_device *hdev) +{ + struct tmff2_device_entry *tmff2 = tmff2_from_hdev(hdev); + struct device *dev; + + if (!tmff2) + return; + + tmff2->allow_scheduling = 0; + cancel_delayed_work_sync(&tmff2->work); + + dev = &tmff2->hdev->dev; + if (tmff2->params & PARAM_FRICTION_LEVEL) + device_remove_file(dev, &dev_attr_friction_level); + + if (tmff2->params & PARAM_DAMPER_LEVEL) + device_remove_file(dev, &dev_attr_damper_level); + + if (tmff2->params & PARAM_SPRING_LEVEL) + device_remove_file(dev, &dev_attr_spring_level); + + if (tmff2->params & PARAM_RANGE) + device_remove_file(dev, &dev_attr_range); + + if (tmff2->params & PARAM_ALT_MODE) + device_remove_file(dev, &dev_attr_alternate_modes); + + if (tmff2->params & PARAM_GAIN) + device_remove_file(dev, &dev_attr_gain); + + hid_hw_stop(hdev); + tmff2->wheel_destroy(tmff2->data); + + kfree(tmff2->states); + kfree(tmff2); +} + +static const struct hid_device_id tmff2_devices[] = { + /* t300rs and variations */ + {HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, TMT300RS_PS3_NORM_ID)}, + {HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, TMT300RS_PS3_ADV_ID)}, + {HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, TMT300RS_PS4_NORM_ID)}, + /* t248 PC*/ + {HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, TMT248_PC_ID)}, + /* tx */ + {HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, TX_ACTIVE)}, + + {} +}; +MODULE_DEVICE_TABLE(hid, tmff2_devices); + +static struct hid_driver tmff2_driver = { + .name = "tmff2", + .id_table = tmff2_devices, + .probe = tmff2_probe, + .remove = tmff2_remove, + .report_fixup = tmff2_report_fixup, +}; +module_hid_driver(tmff2_driver); + +MODULE_LICENSE("GPL"); diff --git a/src/hid-tmff2.h b/src/hid-tmff2.h new file mode 100644 index 0000000..c8019dd --- /dev/null +++ b/src/hid-tmff2.h @@ -0,0 +1,147 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#ifndef __HID_TMFF2_H +#define __HID_TMFF2_H + +#include +#include +#include + +extern int timer_msecs; +extern int spring_level; +extern int damper_level; +extern int friction_level; +extern int range; +extern int gain; +extern int alt_mode; + +#define USB_VENDOR_ID_THRUSTMASTER 0x044f + +/* the wheel seems to only be capable of processing a certain number of + * interrupts per second, and if this value is too low the kernel urb buffer(or + * some buffer at least) fills up. Optimally I would figure out some way to + * space out the interrupts so that they all leave at regular intervals, but + * for now this is good enough, go slow enough that everything works. + */ +#define DEFAULT_TIMER_PERIOD 8 + +#define FF_EFFECT_QUEUE_UPLOAD 0 +#define FF_EFFECT_QUEUE_START 1 +#define FF_EFFECT_QUEUE_STOP 2 +#define FF_EFFECT_QUEUE_UPDATE 3 +#define FF_EFFECT_PLAYING 4 + +#define PARAM_SPRING_LEVEL (1 << 0) +#define PARAM_DAMPER_LEVEL (1 << 1) +#define PARAM_FRICTION_LEVEL (1 << 2) +#define PARAM_RANGE (1 << 3) +#define PARAM_ALT_MODE (1 << 4) +#define PARAM_GAIN (1 << 5) + +#undef fixp_sin16 +#define fixp_sin16(v) (((v % 360) > 180) ?\ + -(fixp_sin32((v % 360) - 180) >> 16)\ + : fixp_sin32(v) >> 16) + +#define JIFFIES2MS(jiffies) ((jiffies) * 1000 / HZ) + +struct tmff2_effect_state { + struct ff_effect effect; + struct ff_effect old; + + unsigned long flags; + unsigned long count; + unsigned long start_time; +}; + +struct tmff2_device_entry { + struct hid_device *hdev; + struct input_dev *input_dev; + + /* pointer to array */ + struct tmff2_effect_state *states; + + struct delayed_work work; + + spinlock_t lock; + + int allow_scheduling; + + /* fields relevant to each actual device (T300, T150...) */ + void *data; + unsigned long params; + unsigned long max_effects; + signed short supported_effects[FF_CNT]; + + /* obligatory callbacks */ + int (*play_effect)(void *data, struct tmff2_effect_state *state); + int (*upload_effect)(void *data, struct tmff2_effect_state *state); + int (*update_effect)(void *data, struct tmff2_effect_state *state); + int (*stop_effect)(void *data, struct tmff2_effect_state *state); + + int (*wheel_init)(struct tmff2_device_entry *tmff2, int open_mode); + int (*wheel_destroy)(void *data); + + /* optional callbacks */ + int (*open)(void *data, int); + int (*close)(void *data, int); + int (*set_gain)(void *data, uint16_t gain); + int (*set_range)(void *data, uint16_t range); + /* switch_mode has to not do anything if we're alredy in the specified + * mode */ + int (*switch_mode)(void *data, uint16_t mode); + ssize_t (*alt_mode_show)(void *data, char *buf); + ssize_t (*alt_mode_store)(void *data, const char *buf, size_t count); + int (*set_autocenter)(void *data, uint16_t autocenter); + __u8 *(*wheel_fixup)(struct hid_device *hdev, __u8 *rdesc, unsigned int *rsize); + + /* void pointers are dangerous, I know, but in this case likely the best option... */ +}; + +/* external */ +int t300rs_populate_api(struct tmff2_device_entry *tmff2); +int t248_populate_api(struct tmff2_device_entry *tmff2); +int tx_populate_api(struct tmff2_device_entry *tmff2); + +#define TMT300RS_PS3_NORM_ID 0xb66e +#define TMT300RS_PS3_ADV_ID 0xb66f +#define TMT300RS_PS4_NORM_ID 0xb66d + +#define TMT248_PC_ID 0xb696 + +#define TX_ACTIVE 0xb669 + +/* APIs to different wheel families */ +/* T248 at least uses the T300RS api, not sure if there are other wheels but that's + * why these functions are given global linkage */ + +struct t300rs_device_entry { + struct hid_device *hdev; + struct input_dev *input_dev; + struct hid_report *report; + struct hid_field *ff_field; + struct usb_device *usbdev; + + int (*open)(struct input_dev *dev); + void (*close)(struct input_dev *dev); + + int mode; + int attachment; + u8 buffer_length; + u8 *send_buffer; +}; + +int t300rs_play_effect(void *, struct tmff2_effect_state *); +int t300rs_upload_effect(void *, struct tmff2_effect_state *); +int t300rs_update_effect(void *, struct tmff2_effect_state *); +int t300rs_stop_effect(void *, struct tmff2_effect_state *); + +int t300rs_open(void *, int); +int t300rs_close(void *, int); +int t300rs_set_gain(void *, uint16_t); +int t300rs_set_range(void *, uint16_t); +int t300rs_set_autocenter(void *, uint16_t); + +int t300rs_send_buf(struct t300rs_device_entry *t300rs, u8 *send_buffer, size_t len); +int t300rs_send_int(struct t300rs_device_entry *t300rs); + +#endif diff --git a/src/hid-tminit b/src/hid-tminit new file mode 160000 index 0000000..9375f6c --- /dev/null +++ b/src/hid-tminit @@ -0,0 +1 @@ +Subproject commit 9375f6c7d83af5dd6c8b8fe30351d0f36043b20a diff --git a/src/hid-tmt248/hid-tmt248.c b/src/hid-tmt248/hid-tmt248.c new file mode 100644 index 0000000..af3aaa9 --- /dev/null +++ b/src/hid-tmt248/hid-tmt248.c @@ -0,0 +1,335 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#include +#include +#include "../hid-tmff2.h" + +#define T248_MAX_EFFECTS 16 +#define T248_BUFFER_LENGTH 63 + +static const u8 setup_0[64] = { 0x42, 0x01 }; +static const u8 setup_1[64] = { 0x0a, 0x04, 0x90, 0x03 }; +static const u8 setup_2[64] = { 0x0a, 0x04, 0x00, 0x0c }; +static const u8 setup_3[64] = { 0x0a, 0x04, 0x12, 0x10 }; +static const u8 setup_4[64] = { 0x0a, 0x04, 0x00, 0x06 }; +static const u8 setup_5[64] = { 0x0a, 0x04, 0x00, 0x0e }; +static const u8 setup_6[64] = { 0x0a, 0x04, 0x00, 0x0e, 0x01 }; +static const u8 *const setup_arr[] = { setup_0, setup_1, setup_2, setup_3, setup_4, setup_5, setup_6 }; +static const unsigned int setup_arr_sizes[] = { + ARRAY_SIZE(setup_0), + ARRAY_SIZE(setup_1), + ARRAY_SIZE(setup_2), + ARRAY_SIZE(setup_3), + ARRAY_SIZE(setup_4), + ARRAY_SIZE(setup_5), + ARRAY_SIZE(setup_6) +}; + +static const unsigned long t248_params = + PARAM_SPRING_LEVEL + | PARAM_DAMPER_LEVEL + | PARAM_FRICTION_LEVEL + | PARAM_RANGE + | PARAM_GAIN + ; + +static const signed short t248_effects[] = { + FF_CONSTANT, + FF_RAMP, + FF_SPRING, + FF_DAMPER, + FF_FRICTION, + FF_INERTIA, + FF_PERIODIC, + FF_SINE, + FF_TRIANGLE, + FF_SQUARE, + FF_SAW_UP, + FF_SAW_DOWN, + FF_AUTOCENTER, + FF_GAIN, + -1 +}; + +/* TODO: sort through this stuff */ +static u8 t248_pc_rdesc_fixed[] = { + 0x05, 0x01, /* Usage page (Generic Desktop) */ + 0x09, 0x04, /* Usage (Joystick) */ + 0xa1, 0x01, /* Collection (Application) */ + 0x09, 0x01, /* Usage (Pointer) */ + 0xa1, 0x00, /* Collection (Physical) */ + 0x85, 0x07, /* Report ID (7) */ + 0x09, 0x30, /* Usage (X) */ + 0x15, 0x00, /* Logical minimum (0) */ + 0x27, 0xff, 0xff, 0x00, 0x00, /* Logical maximum (65535) */ + 0x35, 0x00, /* Physical minimum (0) */ + 0x47, 0xff, 0xff, 0x00, 0x00, /* Physical maximum (65535) */ + 0x75, 0x10, /* Report size (16) */ + 0x95, 0x01, /* Report count (1) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x09, 0x31, /* Usage (Y) TODO: clutch? */ + 0x26, 0xff, 0x03, /* Logical maximum (1023) */ + 0x46, 0xff, 0x03, /* Physical maximum (1023) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x09, 0x35, /* Usage (Rz) TODO: brake? */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x09, 0x36, /* Usage (Slider) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x75, 0x08, /* Report size (8) */ + 0x26, 0xff, 0x00, /* Logical maximum (255) */ + 0x46, 0xff, 0x00, /* Physical maximum (255) */ + 0x09, 0x40, /* Usage (Vx) TODO: what is this? */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x09, 0x41, /* Usage (Vy) TODO: --||-- */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x09, 0x33, /* Usage (Rx) TODO: --||-- */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x09, 0x34, /* Usage (Ry) TODO: --||-- */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x09, 0x32, /* Usage (Z) TODO: --||-- (gas?) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x09, 0x37, /* Usage (Dial) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x05, 0x09, /* Usage page (Button) */ + 0x19, 0x01, /* Usage minimum (1) */ + 0x29, 0x1a, /* Usage maximum (13) */ + 0x25, 0x01, /* Logical maximum (1) */ + 0x45, 0x01, /* Physical maximum (1) */ + 0x75, 0x01, /* Report size (1) */ + 0x95, 0x1a, /* Report count (26) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x75, 0x06, /* Report size (6) */ + 0x95, 0x01, /* Report count (1) */ + 0x81, 0x03, /* Usage (Variable, Absolute, Constant) */ + 0x05, 0x01, /* Usage page (Generic Desktop) */ + 0x09, 0x39, /* Usage (Hat Switch) */ + 0x25, 0x07, /* Logical maximum (7) */ + 0x46, 0x3b, 0x01, /* Physical maximum (315) */ + 0x55, 0x00, /* Unit exponent (0) */ + 0x65, 0x14, /* Unit (Eng rot, Angular Pos) */ + 0x75, 0x04, /* Report size (4) */ + 0x81, 0x42, /* Input (Variable, Absolute, NullState) */ + 0x65, 0x00, /* Input (None) */ + 0x81, 0x03, /* Input (Variable, Absolute, Constant) */ + 0x85, 0x60, /* Report ID (96), prev 10 */ + 0x06, 0x00, 0xff, /* Usage page (Vendor 1) */ + 0x09, 0x60, /* Usage (96), prev 10 */ + 0x75, 0x08, /* Report size (8) */ + 0x95, 0x3f, /* Report count (63) */ + 0x26, 0xff, 0x00, /* Logical maximum (256) */ + 0x46, 0xff, 0x00, /* Physical maximum (256) */ + 0x91, 0x02, /* Output (Variable, Absolute) */ + 0x85, 0x02, /* Report ID (2) */ + 0x09, 0x02, /* Usage (2) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x09, 0x14, /* Usage (20) */ + 0x85, 0x14, /* Report ID (20) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0xc0, /* End collection */ + 0xc0, /* End collection */ +}; + +static int t248_interrupts(struct t300rs_device_entry *t248) +{ + u8 *send_buf = kmalloc(256, GFP_KERNEL); + struct usb_interface *usbif = to_usb_interface(t248->hdev->dev.parent); + struct usb_host_endpoint *ep; + int ret, trans, b_ep, i; + + if (!send_buf) { + hid_err(t248->hdev, "failed allocating send buffer\n"); + return -ENOMEM; + } + + ep = &usbif->cur_altsetting->endpoint[1]; + b_ep = ep->desc.bEndpointAddress; + + for (i = 0; i < ARRAY_SIZE(setup_arr); ++i) { + memcpy(send_buf, setup_arr[i], setup_arr_sizes[i]); + + ret = usb_interrupt_msg(t248->usbdev, + usb_sndintpipe(t248->usbdev, b_ep), + send_buf, setup_arr_sizes[i], + &trans, + USB_CTRL_SET_TIMEOUT); + + if (ret) { + hid_err(t248->hdev, "setup data couldn't be sent\n"); + goto err; + } + } + +err: + kfree(send_buf); + return ret; +} + +int t248_wheel_destroy(void *data) +{ + struct t300rs_device_entry *t300rs = data; + + if (!t300rs) + return -ENODEV; + + kfree(t300rs->send_buffer); + kfree(t300rs); + return 0; +} + +int t248_set_range(void *data, uint16_t value) +{ + struct t300rs_device_entry *t248 = data; + + if (value < 140) { + hid_info(t248->hdev, "value %i too small, clamping to 140\n", value); + value = 140; + } + + if (value > 900) { + hid_info(t248->hdev, "value %i too large, clamping to 900\n", value); + value = 900; + } + + return t300rs_set_range(data, value); +} + +static int t248_send_open(struct t300rs_device_entry *t248) +{ + int r1, r2; + t248->send_buffer[0] = 0x01; + t248->send_buffer[1] = 0x04; + if ((r1 = t300rs_send_int(t248))) + return r1; + + t248->send_buffer[0] = 0x01; + t248->send_buffer[1] = 0x05; + if ((r2 = t300rs_send_int(t248))) + return r2; + + return 0; +} + +static int t248_open(void *data, int open_mode) +{ + struct t300rs_device_entry *t248 = data; + + if (!t248) + return -ENODEV; + + if (open_mode) + t248_send_open(t248); + + return t248->open(t248->input_dev); +} + +static int t248_send_close(struct t300rs_device_entry *t248) +{ + int r1, r2; + t248->send_buffer[0] = 0x01; + t248->send_buffer[1] = 0x05; + if ((r1 = t300rs_send_int(t248))) + return r1; + + t248->send_buffer[0] = 0x01; + t248->send_buffer[1] = 0x00; + if ((r2 = t300rs_send_int(t248))) + return r2; + + return 0; +} + +static int t248_close(void *data, int open_mode) +{ + struct t300rs_device_entry *t248 = data; + + if (!t248) + return -ENODEV; + + if (open_mode) + t248_send_close(t248); + + t248->close(t248->input_dev); + return 0; +} + +int t248_wheel_init(struct tmff2_device_entry *tmff2, int open_mode) +{ + struct t300rs_device_entry *t248 = kzalloc(sizeof(struct t300rs_device_entry), GFP_KERNEL); + struct list_head *report_list; + int ret; + + + if (!t248) { + ret = -ENOMEM; + goto t248_err; + } + + t248->hdev = tmff2->hdev; + t248->input_dev = tmff2->input_dev; + t248->usbdev = to_usb_device(tmff2->hdev->dev.parent->parent); + t248->buffer_length = T248_BUFFER_LENGTH; + + t248->send_buffer = kzalloc(t248->buffer_length, GFP_KERNEL); + if (!t248->send_buffer) { + ret = -ENOMEM; + goto send_err; + } + + report_list = &t248->hdev->report_enum[HID_OUTPUT_REPORT].report_list; + t248->report = list_entry(report_list->next, struct hid_report, list); + t248->ff_field = t248->report->field[0]; + + t248->open = t248->input_dev->open; + t248->close = t248->input_dev->close; + + if ((ret = t248_interrupts(t248))) + goto interrupt_err; + + /* everything went OK */ + tmff2->data = t248; + tmff2->params = t248_params; + tmff2->max_effects = T248_MAX_EFFECTS; + memcpy(tmff2->supported_effects, t248_effects, sizeof(t248_effects)); + + if (!open_mode) + t248_send_open(t248); + + hid_info(t248->hdev, "force feedback for T248\n"); + return 0; + +interrupt_err: +send_err: + kfree(t248); +t248_err: + hid_err(tmff2->hdev, "failed initializing T248\n"); + return ret; +} + +static __u8 *t248_wheel_fixup(struct hid_device *hdev, __u8 *rdesc, + unsigned int *rsize) +{ + rdesc = t248_pc_rdesc_fixed; + *rsize = sizeof(t248_pc_rdesc_fixed); + return rdesc; +} + +int t248_populate_api(struct tmff2_device_entry *tmff2) +{ + tmff2->play_effect = t300rs_play_effect; + tmff2->upload_effect = t300rs_upload_effect; + tmff2->update_effect = t300rs_update_effect; + tmff2->stop_effect = t300rs_stop_effect; + + tmff2->set_gain = t300rs_set_gain; + tmff2->set_autocenter = t300rs_set_autocenter; + /* T248 only has 900 degree range, instead of T300RS 1080 */ + tmff2->set_range = t248_set_range; + tmff2->wheel_fixup = t248_wheel_fixup; + + tmff2->open = t248_open; + tmff2->close = t248_close; + + tmff2->wheel_init = t248_wheel_init; + tmff2->wheel_destroy = t248_wheel_destroy; + + return 0; +} diff --git a/src/hid-tmt300rs/hid-tmt300rs.c b/src/hid-tmt300rs/hid-tmt300rs.c new file mode 100644 index 0000000..20bb239 --- /dev/null +++ b/src/hid-tmt300rs/hid-tmt300rs.c @@ -0,0 +1,1714 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#include +#include +#include "../hid-tmff2.h" + +#define T300RS_MAX_EFFECTS 16 +#define T300RS_NORM_BUFFER_LENGTH 63 +#define T300RS_PS4_BUFFER_LENGTH 31 + +#define T300RS_DEFAULT_ATTACHMENT 0x06 +#define T300RS_F1_ATTACHMENT 0x03 + +static const unsigned long t300rs_params = + PARAM_SPRING_LEVEL + | PARAM_DAMPER_LEVEL + | PARAM_FRICTION_LEVEL + | PARAM_GAIN + | PARAM_RANGE + | PARAM_ALT_MODE + ; + +static const signed short t300rs_effects[] = { + FF_CONSTANT, + FF_RAMP, + FF_SPRING, + FF_DAMPER, + FF_FRICTION, + FF_INERTIA, + FF_PERIODIC, + FF_SINE, + FF_TRIANGLE, + FF_SQUARE, + FF_SAW_UP, + FF_SAW_DOWN, + FF_AUTOCENTER, + FF_GAIN, + -1 +}; + +struct __packed t300rs_fw_response { + uint8_t unused1[2]; + uint8_t fw_version; + uint8_t unused2; +}; + + +struct __packed t300rs_packet_header { + uint8_t zero1; + uint8_t id; + uint8_t code; +}; + +struct __packed t300rs_setup_header { + uint8_t cmd; + uint8_t code; +}; + +struct __packed t300rs_packet_envelope { + uint16_t attack_length; + uint16_t attack_level; + uint16_t fade_length; + uint16_t fade_level; +}; + +struct __packed t300rs_packet_timing { + uint8_t start_marker; + uint16_t duration; + uint8_t zero1[2]; + uint16_t offset; + uint8_t zero2; + uint16_t end_marker; +}; + +struct usb_ctrlrequest t300rs_fw_request = { + .bRequestType = 0xc1, + .bRequest = 86, + .wValue = 0, + .wIndex = 0, + .wLength = 8 +}; + +struct t300rs_data { + unsigned long quirks; + void *device_props; +}; + +static u8 t300rs_rdesc_nrm_fixed[] = { + 0x05, 0x01, /* Usage page (Generic Desktop) */ + 0x09, 0x04, /* Usage (Joystick) */ + 0xa1, 0x01, /* Collection (Application) */ + 0x09, 0x01, /* Usage (Pointer) */ + 0xa1, 0x00, /* Collection (Physical) */ + 0x85, 0x07, /* Report ID (7) */ + 0x09, 0x30, /* Usage (X) */ + 0x15, 0x00, /* Logical minimum (0) */ + 0x27, 0xff, 0xff, 0x00, 0x00, /* Logical maximum (65535) */ + 0x35, 0x00, /* Physical minimum (0) */ + 0x47, 0xff, 0xff, 0x00, 0x00, /* Physical maximum (65535) */ + 0x75, 0x10, /* Report size (16) */ + 0x95, 0x01, /* Report count (1) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x09, 0x35, /* Usage (Rz) (Brake) */ + 0x26, 0xff, 0x03, /* Logical maximum (1023) */ + 0x46, 0xff, 0x03, /* Physical maximum (1023) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x09, 0x32, /* Usage (Z) (Gas) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x09, 0x31, /* Usage (Y) (Clutch) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x81, 0x03, /* Input (Variable, Absolute, Constant) */ + 0x05, 0x09, /* Usage page (Button) */ + 0x19, 0x01, /* Usage minimum (1) */ + 0x29, 0x0d, /* Usage maximum (13) */ + 0x25, 0x01, /* Logical maximum (1) */ + 0x45, 0x01, /* Physical maximum (1) */ + 0x75, 0x01, /* Report size (1) */ + 0x95, 0x0d, /* Report count (13) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x75, 0x0b, /* Report size (13) */ + 0x95, 0x01, /* Report count (1) */ + 0x81, 0x03, /* Usage (Variable, Absolute, Constant) */ + 0x05, 0x01, /* Usage page (Generic Desktop) */ + 0x09, 0x39, /* Usage (Hat Switch) */ + 0x25, 0x07, /* Logical maximum (7) */ + 0x46, 0x3b, 0x01, /* Physical maximum (315) */ + 0x55, 0x00, /* Unit exponent (0) */ + 0x65, 0x14, /* Unit (Eng Rot, Angular Pos) */ + 0x75, 0x04, /* Report size (4) */ + 0x81, 0x42, /* Input (Variable, Absolute, NullState) */ + 0x65, 0x00, /* Unit (None) */ + 0x81, 0x03, /* Input (Variable, Absolute, Constant) */ + 0x85, 0x60, /* Report ID (96), prev 10 */ + 0x06, 0x00, 0xff, /* Usage page (Vendor 1) */ + 0x09, 0x60, /* Usage (96), prev 10 */ + 0x75, 0x08, /* Report size (8) */ + 0x95, 0x3f, /* Report count (63) */ + 0x26, 0xff, 0x7f, /* Logical maximum (32767) */ + 0x15, 0x00, /* Logical minimum (0) */ + 0x46, 0xff, 0x7f, /* Physical maximum (32767) */ + 0x36, 0x00, 0x80, /* Physical minimum (-32768) */ + 0x91, 0x02, /* Output (Variable, Absolute) */ + 0x85, 0x02, /* Report ID (2) */ + 0x09, 0x02, /* Usage (2) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x09, 0x14, /* Usage (20) */ + 0x85, 0x14, /* Report ID (20) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0xc0, /* End collection */ + 0xc0, /* End collection */ +}; + +static u8 t300rs_rdesc_adv_fixed[] = { + 0x05, 0x01, /* Usage page (Generic Desktop) */ + 0x09, 0x04, /* Usage (Joystick) */ + 0xa1, 0x01, /* Collection (Application) */ + 0x09, 0x01, /* Usage (Pointer) */ + 0xa1, 0x00, /* Collection (Physical) */ + 0x85, 0x07, /* Report ID (7) */ + 0x09, 0x30, /* Usage (X) */ + 0x15, 0x00, /* Logical minimum (0) */ + 0x27, 0xff, 0xff, 0x00, 0x00, /* Logical maximum (65535) */ + 0x35, 0x00, /* Physical minimum (0) */ + 0x47, 0xff, 0xff, 0x00, 0x00, /* Physical maximum (65535) */ + 0x75, 0x10, /* Report size (16) */ + 0x95, 0x01, /* Report count (1) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x09, 0x31, /* Usage (Y) */ + 0x26, 0xff, 0x03, /* Logical maximum (1023) */ + 0x46, 0xff, 0x03, /* Physical maximum (1023) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x09, 0x35, /* Usage (Rz) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x09, 0x36, /* Usage (Slider) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x81, 0x03, /* Input (Variable, Absolute, Constant) ? */ + 0x05, 0x09, /* Usage page (Button) */ + 0x19, 0x01, /* Usage minimum (1) */ + 0x29, 0x19, /* Usage maximum (25) */ + 0x25, 0x01, /* Logical maximum (1) */ + 0x45, 0x01, /* Physical maximum (1) */ + 0x75, 0x01, /* Report size (1) */ + 0x95, 0x19, /* Report count (25) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x75, 0x03, /* Report size (3) */ + 0x95, 0x01, /* Report count (1) */ + 0x81, 0x03, /* Input (Variable, Absolute, Constant) */ + 0x05, 0x01, /* Usage page (Generic Desktop) */ + 0x09, 0x39, /* Usage (Hat Switch) */ + 0x25, 0x07, /* Logical maximum (7) */ + 0x46, 0x3b, 0x01, /* Physical maximum (315) */ + 0x55, 0x00, /* Unit exponent (0) */ + 0x65, 0x14, /* Unit (Eng Rot, Angular Pos) */ + 0x75, 0x04, /* Report size (4) */ + 0x81, 0x42, /* Input (Variable, Absolute, NullState) */ + 0x65, 0x00, /* Unit (None) */ + 0x85, 0x60, /* Report ID (96), prev 10 */ + 0x06, 0x00, 0xff, /* Usage page (Vendor 1) */ + 0x09, 0x60, /* Usage (96), prev 10 (why?) */ + 0x75, 0x08, /* Report size (8) */ + 0x95, 0x3f, /* Report count (63) */ + 0x26, 0xff, 0x00, /* Logical maximum (255) */ + 0x46, 0xff, 0x00, /* Physical maximum (255) */ + 0x91, 0x02, /* Output (Variable, Absolute) */ + 0x85, 0x02, /* Report ID (2) */ + 0x09, 0x02, /* Usage (Mouse) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x09, 0x14, /* Usage (20) */ + 0x85, 0x14, /* Report ID (20) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0xc0, /* End collection */ + 0xc0, /* End collection */ +}; + +static u8 t300rs_rdesc_ps4_fixed[] = { + 0x05, 0x01, /* Usage page (Generic Desktop) */ + 0x09, 0x05, /* Usage (GamePad) */ + 0xa1, 0x01, /* Collection (Application) */ + 0x85, 0x01, /* Report ID (1) */ + 0x09, 0x00, /* Usage (U) (was X) */ + 0x09, 0x00, /* Usage (U) (was Y) */ + 0x09, 0x00, /* Usage (U) (was Z) */ + 0x09, 0x00, /* Usage (U) (was Rz)*/ + 0x15, 0x00, /* Logical minimum (0) */ + 0x26, 0xff, 0x00, /* Logical maximum (255) */ + 0x75, 0x08, /* Report size (8) */ + 0x95, 0x04, /* Report count (4) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x09, 0x39, /* Usage (Hat Switch) */ + 0x15, 0x00, /* Logical minimum (0) */ + 0x25, 0x07, /* Logical maximum (7)*/ + 0x35, 0x00, /* Physical minimum (0) */ + 0x46, 0x3b, 0x01, /* Physical maximum (315) */ + 0x65, 0x14, /* Unit (Eng Rot, Angular Pos) */ + 0x75, 0x04, /* Report size (4) */ + 0x95, 0x01, /* Report count (1) */ + 0x81, 0x42, /* Input (Variable, Absolute, NullState) */ + 0x65, 0x00, /* Input (None) */ + 0x05, 0x09, /* Usage page (Button) */ + 0x19, 0x01, /* Usage minimum (1) */ + 0x29, 0x0e, /* Usage maximum (14) */ + 0x15, 0x00, /* Logical minimum (0) */ + 0x25, 0x01, /* Logical maximum (1) */ + 0x75, 0x01, /* Report size (1) */ + 0x95, 0x0e, /* Report size (14) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x06, 0x00, 0xff, /* Usage page (Vendor 1) */ + 0x09, 0x20, /* Usage (32) */ + 0x75, 0x06, /* Report size (6) */ + 0x95, 0x01, /* Report count (1) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x05, 0x01, /* Usage page (Generic Desktop) */ + 0x09, 0x00, /* Usage (U) (was Rx)*/ + 0x09, 0x00, /* Usage (U) (was Ry) */ + 0x15, 0x00, /* Logical minimum (0) */ + 0x26, 0xff, 0x00, /* Logical maximum (255) */ + 0x75, 0x08, /* Report size (8) */ + 0x95, 0x02, /* Report count (2) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x05, 0x01, /* Usage page (Vendor 1) */ + /* constant zero? */ + 0x09, 0x00, /* Usage (33) */ + 0x95, 0x21, /* Report count (33) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + /* wheel */ + 0x09, 0x30, /* Usage (X) */ + 0x15, 0x00, /* Logical minimum (0) */ + 0x27, 0xff, 0xff, 0x00, 0x00, /* Logical maximum (65535) */ + 0x35, 0x00, /* Physical minimum (0) */ + 0x47, 0xff, 0xff, 0x00, 0x00, /* Physical maximum (65535) */ + 0x75, 0x10, /* Report size (16) */ + 0x95, 0x01, /* Report count (1) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + /* gas */ + 0x09, 0x31, /* Usage (Y) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + /* brake */ + 0x09, 0x32, /* Usage (Z) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + /* clutch */ + 0x09, 0x35, /* Usage (Rz) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + /* stick shifter (check model no) */ + 0x05, 0x09, /* Usage page (Button) */ + 0x19, 0x0f, /* Usage minimum (15) */ + 0x29, 0x17, /* Usage maximum (23) */ + 0x15, 0x00, /* Logical minimum (1) */ + 0x25, 0x01, /* Logical maximum (1) */ + 0x75, 0x01, /* Report size (1) */ + 0x95, 0x08, /* Report count (8) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + /* no clue */ + 0x05, 0x01, /* Usage page (Generic Desktop) */ + 0x09, 0x00, /* Usage (U) */ + 0x75, 0x08, /* Report size (8) */ + 0x95, 0x0c, /* Report count (12) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + /* continue unmodified */ + 0x06, 0x00, 0xff, /* Usage page (Vendor defined 1) */ + 0x85, 0x60, /* Report ID (5) (change to 0x60?) */ + 0x09, 0x60, /* Usage (34) (change to 0x60?) */ + 0x95, 0x1f, /* Report count (31) () */ + 0x91, 0x02, /* Output (Variable, Absolute) */ + 0x85, 0x03, /* Report ID (3) */ + 0x0a, 0x21, 0x27, /* ??? */ + 0x95, 0x2f, /* Report count (47) */ + 0xb1, 0x02, /* Feature (Data, Var, Abs) */ + 0xc0, /* End collection */ + + /* From here on out no clue */ + 0x06, 0xf0, + 0xff, 0x09, + 0x40, 0xa1, + 0x01, 0x85, + 0xf0, 0x09, + 0x47, 0x95, + 0x3f, 0xb1, + 0x02, 0x85, + 0xf1, 0x09, + 0x48, 0x95, + 0x3f, 0xb1, + 0x02, 0x85, + 0xf2, 0x09, + 0x49, 0x95, + 0x0f, 0xb1, + 0x02, 0x85, + 0xf3, 0x0a, + 0x01, 0x47, + 0x95, 0x07, + 0xb1, 0x02, + 0xc0, +}; + +static u8 spring_values[] = { + 0xa6, 0x6a, 0xa6, 0x6a, 0xfe, + 0xff, 0xfe, 0xff, 0xfe, 0xff, + 0xfe, 0xff, 0xdf, 0x58, 0xa6, + 0x6a, 0x06 +}; + +static u8 damper_values[] = { + 0xfc, 0x7f, 0xfc, 0x7f, 0xfe, + 0xff, 0xfe, 0xff, 0xfe, 0xff, + 0xfe, 0xff, 0xfc, 0x7f, 0xfc, + 0x7f, 0x07 +}; + +int t300rs_send_buf(struct t300rs_device_entry *t300rs, u8 *send_buffer, size_t len) +{ + int i; + /* check that send_buffer fits into our report */ + if (len > t300rs->buffer_length) + return -EINVAL; + + /* fill with actual data */ + for (i = 0; i < len; ++i) + t300rs->ff_field->value[i] = send_buffer[i]; + + /* fill the rest with zeroes */ + for (i = len; i < t300rs->buffer_length; ++i) + t300rs->ff_field->value[i] = 0; + + hid_hw_request(t300rs->hdev, t300rs->report, HID_REQ_SET_REPORT); + return 0; +} + +int t300rs_send_int(struct t300rs_device_entry *t300rs) +{ + t300rs_send_buf(t300rs, t300rs->send_buffer, t300rs->buffer_length); + memset(t300rs->send_buffer, 0, t300rs->buffer_length); + + return 0; +} + +static void t300rs_fill_header(struct t300rs_packet_header *packet_header, + uint8_t id, uint8_t code) +{ + packet_header->id = id + 1; + packet_header->code = code; +} + +int t300rs_play_effect(void *data, struct tmff2_effect_state *state) +{ + struct t300rs_device_entry *t300rs = data; + struct __packed t300rs_packet_play { + struct t300rs_packet_header header; + uint8_t value; + } *play_packet = (struct t300rs_packet_play *)t300rs->send_buffer; + + int ret; + + + t300rs_fill_header(&play_packet->header, state->effect.id, 0x89); + play_packet->value = 0x01; + + ret = t300rs_send_int(t300rs); + if (ret) + hid_err(t300rs->hdev, "failed starting effect play\n"); + + return ret; +} + +int t300rs_stop_effect(void *data, struct tmff2_effect_state *state) +{ + struct t300rs_device_entry *t300rs = data; + struct __packed t300rs_packet_stop { + struct t300rs_packet_header header; + uint8_t value; + } *stop_packet = (struct t300rs_packet_stop *)t300rs->send_buffer; + + int ret; + + + t300rs_fill_header(&stop_packet->header, state->effect.id, 0x89); + + ret = t300rs_send_int(t300rs); + if (ret) + hid_err(t300rs->hdev, "failed stopping effect play\n"); + + return ret; +} + +static void t300rs_fill_envelope(struct t300rs_packet_envelope *packet_envelope, + int16_t level, uint16_t duration, struct ff_envelope *envelope) +{ + uint16_t attack_length = (duration * envelope->attack_length) / 0x7fff; + uint16_t attack_level = (level * envelope->attack_level) / 0x7fff; + uint16_t fade_length = (duration * envelope->fade_length) / 0x7fff; + uint16_t fade_level = (level * envelope->fade_level) / 0x7fff; + + packet_envelope->attack_length = cpu_to_le16(attack_length); + packet_envelope->attack_level = cpu_to_le16(attack_level); + packet_envelope->fade_length = cpu_to_le16(fade_length); + packet_envelope->fade_level = cpu_to_le16(fade_level); +} + +static void t300rs_fill_timing(struct t300rs_packet_timing *packet_timing, + uint16_t duration, uint16_t offset){ + packet_timing->start_marker = 0x4f; + + packet_timing->duration = cpu_to_le16(duration); + packet_timing->offset = cpu_to_le16(offset); + + packet_timing->end_marker = 0xffff; +} + +static int t300rs_update_envelope(struct t300rs_device_entry *t300rs, + struct tmff2_effect_state *state, + int16_t level, + uint16_t duration, + uint8_t id, + struct ff_envelope envelope, + struct ff_envelope envelope_old + ) +{ + struct __packed t300rs_packet_mod_envelope { + struct t300rs_packet_header header; + uint8_t attribute; + uint16_t value; + } *packet_mod_envelope = (struct t300rs_packet_mod_envelope *)t300rs->send_buffer; + + uint16_t attack_length, attack_level, fade_length, fade_level; + int ret = 0; + + duration = duration - 1; + + attack_length = (duration * envelope.attack_length) / 0x7fff; + attack_level = (level * envelope.attack_level) / 0x7fff; + fade_length = (duration * envelope.fade_length) / 0x7fff; + fade_level = (level * envelope.fade_level) / 0x7fff; + + if (envelope.attack_length != envelope_old.attack_length) { + t300rs_fill_header(&packet_mod_envelope->header, id, 0x31); + packet_mod_envelope->attribute = 0x81; + packet_mod_envelope->value = cpu_to_le16(attack_length); + + ret = t300rs_send_int(t300rs); + if (ret) { + hid_err(t300rs->hdev, "failed modifying effect envelope\n"); + goto error; + } + } + + if (envelope.attack_level != envelope_old.attack_level) { + t300rs_fill_header(&packet_mod_envelope->header, id, 0x31); + packet_mod_envelope->attribute = 0x82; + packet_mod_envelope->value = cpu_to_le16(attack_level); + + ret = t300rs_send_int(t300rs); + if (ret) { + hid_err(t300rs->hdev, "failed modifying effect envelope\n"); + goto error; + } + } + + if (envelope.fade_length != envelope_old.fade_length) { + t300rs_fill_header(&packet_mod_envelope->header, id, 0x31); + packet_mod_envelope->attribute = 0x84; + packet_mod_envelope->value = cpu_to_le16(fade_length); + + ret = t300rs_send_int(t300rs); + if (ret) { + hid_err(t300rs->hdev, "failed modifying effect envelope\n"); + goto error; + } + } + + if (envelope.fade_level != envelope_old.fade_level) { + t300rs_fill_header(&packet_mod_envelope->header, id, 0x31); + packet_mod_envelope->attribute = 0x88; + packet_mod_envelope->value = cpu_to_le16(fade_level); + + ret = t300rs_send_int(t300rs); + if (ret) { + hid_err(t300rs->hdev, "failed modifying effect envelope\n"); + goto error; + } + } + +error: + return ret; +} + +static int t300rs_update_simple_duration(struct t300rs_device_entry *t300rs, + struct tmff2_effect_state *state, unsigned type) +{ + struct ff_effect effect = state->effect; + struct __packed t300rs_packet_mod_duration { + struct t300rs_packet_header header; + uint16_t marker; + uint16_t duration; + } *packet_mod_duration = (struct t300rs_packet_mod_duration *)t300rs->send_buffer; + + uint16_t duration; + int ret = 0; + + duration = effect.replay.length - 1; + + t300rs_fill_header(&packet_mod_duration->header, effect.id, 0x49); + packet_mod_duration->marker = cpu_to_le16(0x4100 + type); + packet_mod_duration->duration = cpu_to_le16(duration); + + ret = t300rs_send_int(t300rs); + if (ret) { + hid_err(t300rs->hdev, "failed modifying duration\n"); + goto error; + } + +error: + return ret; +} + +static int t300rs_update_periodic_duration(struct t300rs_device_entry *t300rs, + struct tmff2_effect_state *state, unsigned type) +{ + struct ff_effect effect = state->effect; + struct __packed t300rs_packet_mod_duration { + struct t300rs_packet_header header; + uint8_t type; + uint8_t marker; + uint16_t duration; + } *packet_mod_duration = (struct t300rs_packet_mod_duration *)t300rs->send_buffer; + + uint16_t duration; + int ret = 0; + + duration = effect.replay.length - 1; + + t300rs_fill_header(&packet_mod_duration->header, effect.id, 0x49); + packet_mod_duration->type = type; + packet_mod_duration->marker = 0x41; + packet_mod_duration->duration = cpu_to_le16(duration); + + ret = t300rs_send_int(t300rs); + if (ret) { + hid_err(t300rs->hdev, "failed modifying duration\n"); + goto error; + } + +error: + return ret; +} + +static int t300rs_update_ramp_duration(struct t300rs_device_entry *t300rs, + struct tmff2_effect_state *state) +{ + struct ff_effect effect = state->effect; + struct __packed t300rs_packet_mod_duration { + struct t300rs_packet_header header; + uint8_t marker0; + uint16_t duration0; + uint8_t marker1; + uint8_t marker2; + uint16_t duration1; + } *packet_mod_duration = (struct t300rs_packet_mod_duration *)t300rs->send_buffer; + + uint16_t duration; + int ret = 0; + + duration = effect.replay.length - 1; + + t300rs_fill_header(&packet_mod_duration->header, effect.id, 0x4e); + packet_mod_duration->marker0 = 0x08; + packet_mod_duration->duration0 = cpu_to_le16(duration); + packet_mod_duration->marker1 = 0x05; + packet_mod_duration->marker2 = 0x41; + packet_mod_duration->duration1 = cpu_to_le16(duration); + + ret = t300rs_send_int(t300rs); + if (ret) { + hid_err(t300rs->hdev, "failed modifying duration\n"); + goto error; + } + +error: + return ret; +} + +static int t300rs_update_constant(struct t300rs_device_entry *t300rs, + struct tmff2_effect_state *state) +{ + struct ff_effect effect = state->effect; + struct ff_effect old = state->old; + struct ff_constant_effect constant = effect.u.constant; + struct ff_constant_effect constant_old = old.u.constant; + struct __packed t300rs_packet_mod_constant { + struct t300rs_packet_header header; + uint16_t level; + } *packet_mod_constant = (struct t300rs_packet_mod_constant *)t300rs->send_buffer; + int ret; + int16_t level; + + level = (constant.level * fixp_sin16(effect.direction * 360 / 0x10000)) / 0x7fff; + + if ((constant.level != constant_old.level) || (effect.direction != old.direction)) { + + t300rs_fill_header(&packet_mod_constant->header, effect.id, 0x0a); + packet_mod_constant->level = cpu_to_le16(level); + + ret = t300rs_send_int(t300rs); + if (ret) { + hid_err(t300rs->hdev, "failed modifying constant effect\n"); + goto error; + } + + } + + ret = t300rs_update_envelope(t300rs, + state, + level, + effect.replay.length, + effect.id, + constant.envelope, + constant_old.envelope + ); + + if (ret) { + hid_err(t300rs->hdev, "failed modifying constant envelope\n"); + goto error; + } + + ret = t300rs_update_simple_duration(t300rs, state, 0x00); + if (ret) { + hid_err(t300rs->hdev, "failed modifying constant duration\n"); + goto error; + } + +error: + return ret; +} + +static int t300rs_update_ramp(struct t300rs_device_entry *t300rs, + struct tmff2_effect_state *state) +{ + struct ff_effect effect = state->effect; + struct ff_effect old = state->old; + struct ff_ramp_effect ramp = effect.u.ramp; + struct ff_ramp_effect ramp_old = old.u.ramp; + struct __packed t300rs_packet_mod_ramp { + struct t300rs_packet_header header; + uint8_t attribute; + uint16_t difference; + uint16_t level; + } *packet_mod_ramp = (struct t300rs_packet_mod_ramp *)t300rs->send_buffer; + + int ret; + + uint16_t difference, top, bottom; + int16_t level; + + top = ramp.end_level > ramp.start_level ? ramp.end_level : ramp.start_level; + bottom = ramp.end_level > ramp.start_level ? ramp.start_level : ramp.end_level; + + + difference = ((top - bottom) * fixp_sin16(effect.direction * 360 / 0x10000)) / 0x7fff; + + + level = (top * fixp_sin16(effect.direction * 360 / 0x10000)) / 0x7fff; + + if ((ramp.start_level != ramp_old.start_level) + || (ramp.end_level != ramp_old.end_level) + || (effect.direction != old.direction)) { + + t300rs_fill_header(&packet_mod_ramp->header, effect.id, 0x0e); + packet_mod_ramp->attribute = 0x03; + packet_mod_ramp->difference = cpu_to_le16(difference); + packet_mod_ramp->level = cpu_to_le16(level); + + ret = t300rs_send_int(t300rs); + if (ret) { + hid_err(t300rs->hdev, "failed modifying ramp effect\n"); + goto error; + } + + } + + ret = t300rs_update_envelope(t300rs, + state, + level, + effect.replay.length, + effect.id, + ramp.envelope, + ramp_old.envelope + ); + + + if (ret) { + hid_err(t300rs->hdev, "failed modifying ramp envelope\n"); + goto error; + } + + ret = t300rs_update_ramp_duration(t300rs, state); + if (ret) { + hid_err(t300rs->hdev, "failed modifying ramp duration\n"); + goto error; + } + +error: + return ret; +} + +static int t300rs_update_damper(struct t300rs_device_entry *t300rs, + struct tmff2_effect_state *state) +{ + struct ff_effect effect = state->effect; + struct ff_effect old = state->old; + struct ff_condition_effect damper = effect.u.condition[0]; + struct ff_condition_effect damper_old = old.u.condition[0]; + struct __packed t300rs_packet_mod_damper { + struct t300rs_packet_header header; + uint8_t attribute; + uint16_t value0; + uint16_t value1; + } *packet_mod_damper = (struct t300rs_packet_mod_damper *)t300rs->send_buffer; + + int ret, input_level; + + input_level = damper_level; + if (state->effect.type == FF_FRICTION) + input_level = friction_level; + + if (state->effect.type == FF_SPRING) + input_level = spring_level; + + if (damper.right_coeff != damper_old.right_coeff) { + int16_t coeff = damper.right_coeff * input_level / 100; + + t300rs_fill_header(&packet_mod_damper->header, effect.id, 0x0e); + packet_mod_damper->attribute = 0x41; + packet_mod_damper->value0 = cpu_to_le16(coeff); + + ret = t300rs_send_int(t300rs); + if (ret) { + hid_err(t300rs->hdev, "failed modifying damper rc\n"); + goto error; + } + + } + + if (damper.left_coeff != damper_old.left_coeff) { + int16_t coeff = damper.left_coeff * input_level / 100; + + t300rs_fill_header(&packet_mod_damper->header, effect.id, 0x0e); + packet_mod_damper->attribute = 0x42; + packet_mod_damper->value0 = cpu_to_le16(coeff); + + ret = t300rs_send_int(t300rs); + if (ret) { + hid_err(t300rs->hdev, "failed modifying damper lc\n"); + goto error; + } + + } + + if ((damper.deadband != damper_old.deadband) + || (damper.center != damper_old.center)) { + + uint16_t right_deadband = 0xfffe - damper.deadband - damper.center; + uint16_t left_deadband = 0xfffe - damper.deadband + damper.center; + + t300rs_fill_header(&packet_mod_damper->header, effect.id, 0x0e); + packet_mod_damper->attribute = 0x4c; + packet_mod_damper->value0 = cpu_to_le16(right_deadband); + packet_mod_damper->value1 = cpu_to_le16(left_deadband); + + ret = t300rs_send_int(t300rs); + if (ret) { + hid_err(t300rs->hdev, "failed modifying damper deadband\n"); + goto error; + } + + } + + ret = t300rs_update_simple_duration(t300rs, state, 0x06); + if (ret) { + hid_err(t300rs->hdev, "failed modifying damper duration\n"); + goto error; + } + +error: + return ret; +} + +static int t300rs_update_spring(struct t300rs_device_entry *t300rs, + struct tmff2_effect_state *state) +{ + return t300rs_update_damper(t300rs, state); +} + + +static int t300rs_update_periodic(struct t300rs_device_entry *t300rs, + struct tmff2_effect_state *state) +{ + struct ff_effect effect = state->effect; + struct ff_effect old = state->old; + struct ff_periodic_effect periodic = effect.u.periodic; + struct ff_periodic_effect periodic_old = old.u.periodic; + struct __packed t300rs_packet_mod_periodic { + struct t300rs_packet_header header; + uint8_t attribute; + uint16_t value; + } *packet_mod_periodic = (struct t300rs_packet_mod_periodic *)t300rs->send_buffer; + + int ret; + int16_t magnitude, phase; + + magnitude = (periodic.magnitude * fixp_sin16(effect.direction * 360 / 0x10000)) / 0x7fff; + phase = periodic.phase; + + if ((periodic.magnitude != periodic_old.magnitude) + || (effect.direction != old.direction)) { + + t300rs_fill_header(&packet_mod_periodic->header, effect.id, 0x0e); + packet_mod_periodic->attribute = 0x01; + packet_mod_periodic->value = cpu_to_le16(magnitude); + + ret = t300rs_send_int(t300rs); + if (ret) { + hid_err(t300rs->hdev, "failed modifying periodic magnitude\n"); + goto error; + } + + } + + if (periodic.offset != periodic_old.offset) { + uint16_t offset = periodic.offset; + + t300rs_fill_header(&packet_mod_periodic->header, effect.id, 0x0e); + packet_mod_periodic->attribute = 0x02; + packet_mod_periodic->value = cpu_to_le16(offset); + + ret = t300rs_send_int(t300rs); + if (ret) { + hid_err(t300rs->hdev, "failed modifying periodic offset\n"); + goto error; + } + + } + + if (periodic.phase != periodic_old.phase) { + + t300rs_fill_header(&packet_mod_periodic->header, effect.id, 0x0e); + packet_mod_periodic->attribute = 0x04; + packet_mod_periodic->value = cpu_to_le16(phase); + + ret = t300rs_send_int(t300rs); + if (ret) { + hid_err(t300rs->hdev, "failed modifying periodic phase\n"); + goto error; + } + + } + + if (periodic.period != periodic_old.period) { + int16_t period = periodic.period; + + t300rs_fill_header(&packet_mod_periodic->header, effect.id, 0x0e); + packet_mod_periodic->attribute = 0x08; + packet_mod_periodic->value = cpu_to_le16(period); + + ret = t300rs_send_int(t300rs); + if (ret) { + hid_err(t300rs->hdev, "failed modifying periodic period\n"); + goto error; + } + + } + + ret = t300rs_update_envelope(t300rs, + state, + magnitude, + effect.replay.length, + effect.id, + periodic.envelope, + periodic_old.envelope + ); + + if (ret) { + hid_err(t300rs->hdev, "failed modifying periodic envelope\n"); + goto error; + } + + ret = t300rs_update_periodic_duration(t300rs, state, periodic.waveform - 0x57); + if (ret) { + hid_err(t300rs->hdev, "failed modifying periodic duration\n"); + goto error; + } + +error: + return ret; +} + +static int t300rs_upload_constant(struct t300rs_device_entry *t300rs, + struct tmff2_effect_state *state) +{ + struct ff_effect effect = state->effect; + struct ff_constant_effect constant = state->effect.u.constant; + struct __packed t300rs_packet_constant { + struct t300rs_packet_header header; + uint16_t level; + struct t300rs_packet_envelope envelope; + uint8_t zero; + struct t300rs_packet_timing timing; + } *packet_constant = (struct t300rs_packet_constant *)t300rs->send_buffer; + + int16_t level; + uint16_t duration, offset; + + int ret; + + level = (constant.level * fixp_sin16(effect.direction * 360 / 0x10000)) / 0x7fff; + duration = effect.replay.length - 1; + + offset = effect.replay.delay; + + t300rs_fill_header(&packet_constant->header, effect.id, 0x6a); + + packet_constant->level = cpu_to_le16(level); + + t300rs_fill_envelope(&packet_constant->envelope, level, duration, + &constant.envelope); + t300rs_fill_timing(&packet_constant->timing, duration, offset); + + ret = t300rs_send_int(t300rs); + if (ret) + hid_err(t300rs->hdev, "failed uploading constant effect\n"); + + return ret; +} + +static int t300rs_upload_ramp(struct t300rs_device_entry *t300rs, + struct tmff2_effect_state *state) +{ + struct ff_effect effect = state->effect; + struct ff_ramp_effect ramp = state->effect.u.ramp; + struct __packed t300rs_packet_ramp { + struct t300rs_packet_header header; + uint16_t difference; + uint16_t level; + uint8_t zero1[2]; + uint16_t duration; + uint16_t marker; + struct t300rs_packet_envelope envelope; + uint8_t direction; + struct t300rs_packet_timing timing; + } *packet_ramp = (struct t300rs_packet_ramp *)t300rs->send_buffer; + + int ret; + uint16_t difference, offset, top, bottom, duration; + int16_t level; + + duration = effect.replay.length - 1; + + top = ramp.end_level > ramp.start_level ? ramp.end_level : ramp.start_level; + bottom = ramp.end_level > ramp.start_level ? ramp.start_level : ramp.end_level; + + + difference = ((top - bottom) * fixp_sin16(effect.direction * 360 / 0x10000)) / 0x7fff; + level = (top * fixp_sin16(effect.direction * 360 / 0x10000)) / 0x7fff; + offset = effect.replay.delay; + + + t300rs_fill_header(&packet_ramp->header, effect.id, 0x6b); + + packet_ramp->difference = cpu_to_le16(difference); + packet_ramp->level = cpu_to_le16(level); + packet_ramp->duration = cpu_to_le16(duration); + + packet_ramp->marker = cpu_to_le16(0x8000); + + t300rs_fill_envelope(&packet_ramp->envelope, level, duration, + &ramp.envelope); + + packet_ramp->direction = ramp.end_level > ramp.start_level ? 0x04 : 0x05; + t300rs_fill_timing(&packet_ramp->timing, duration, offset); + + ret = t300rs_send_int(t300rs); + if (ret) + hid_err(t300rs->hdev, "failed uploading ramp"); + + return ret; +} + +static int t300rs_upload_spring(struct t300rs_device_entry *t300rs, + struct tmff2_effect_state *state) +{ + struct ff_effect effect = state->effect; + /* we only care about the first axis */ + struct ff_condition_effect spring = state->effect.u.condition[0]; + struct __packed t300rs_packet_spring { + struct t300rs_packet_header header; + uint16_t right_coeff; + uint16_t left_coeff; + uint16_t right_deadband; + uint16_t left_deadband; + uint8_t spring_start[17]; + struct t300rs_packet_timing timing; + } *packet_spring = (struct t300rs_packet_spring *)t300rs->send_buffer; + + int ret; + uint16_t duration, right_coeff, left_coeff, right_deadband, left_deadband, offset; + + duration = effect.replay.length - 1; + + right_coeff = spring.right_coeff * spring_level / 100; + left_coeff = spring.left_coeff * spring_level / 100; + + right_deadband = 0xfffe - spring.deadband - spring.center; + left_deadband = 0xfffe - spring.deadband + spring.center; + + offset = effect.replay.delay; + + t300rs_fill_header(&packet_spring->header, effect.id, 0x64); + + packet_spring->right_coeff = cpu_to_le16(right_coeff); + packet_spring->left_coeff = cpu_to_le16(left_coeff); + packet_spring->right_deadband = cpu_to_le16(right_deadband); + packet_spring->left_deadband = cpu_to_le16(left_deadband); + + memcpy(&packet_spring->spring_start, spring_values, ARRAY_SIZE(spring_values)); + t300rs_fill_timing(&packet_spring->timing, duration, offset); + + ret = t300rs_send_int(t300rs); + if (ret) + hid_err(t300rs->hdev, "failed uploading spring\n"); + + return ret; +} + +static int t300rs_upload_damper(struct t300rs_device_entry *t300rs, + struct tmff2_effect_state *state) +{ + + struct ff_effect effect = state->effect; + /* we only care about the first axis */ + struct ff_condition_effect spring = state->effect.u.condition[0]; + struct __packed t300rs_packet_damper { + struct t300rs_packet_header header; + uint16_t right_coeff; + uint16_t left_coeff; + uint16_t right_deadband; + uint16_t left_deadband; + uint8_t damper_start[17]; + struct t300rs_packet_timing timing; + } *packet_damper = (struct t300rs_packet_damper *)t300rs->send_buffer; + + int ret, input_level; + uint16_t duration, right_coeff, left_coeff, right_deadband, left_deadband, offset; + + duration = effect.replay.length - 1; + + input_level = damper_level; + if (state->effect.type == FF_FRICTION) + input_level = friction_level; + + right_coeff = spring.right_coeff * input_level / 100; + left_coeff = spring.left_coeff * input_level / 100; + + right_deadband = 0xfffe - spring.deadband - spring.center; + left_deadband = 0xfffe - spring.deadband + spring.center; + + offset = effect.replay.delay; + + t300rs_fill_header(&packet_damper->header, effect.id, 0x64); + + packet_damper->right_coeff = cpu_to_le16(right_coeff); + packet_damper->left_coeff = cpu_to_le16(left_coeff); + packet_damper->right_deadband = cpu_to_le16(right_deadband); + packet_damper->left_deadband = cpu_to_le16(left_deadband); + + memcpy(&packet_damper->damper_start, damper_values, ARRAY_SIZE(damper_values)); + t300rs_fill_timing(&packet_damper->timing, duration, offset); + + ret = t300rs_send_int(t300rs); + if (ret) + hid_err(t300rs->hdev, "failed uploading spring\n"); + + return ret; +} + +static int t300rs_upload_periodic(struct t300rs_device_entry *t300rs, + struct tmff2_effect_state *state) +{ + struct ff_effect effect = state->effect; + struct ff_periodic_effect periodic = state->effect.u.periodic; + struct __packed t300rs_packet_periodic { + struct t300rs_packet_header header; + uint16_t magnitude; + uint16_t periodic_offset; + uint16_t phase; + uint16_t period; + uint16_t marker; + struct t300rs_packet_envelope envelope; + uint8_t waveform; + struct t300rs_packet_timing timing; + } *packet_periodic = (struct t300rs_packet_periodic *)t300rs->send_buffer; + + int ret; + uint16_t duration, period, offset, periodic_offset; + int16_t phase, magnitude; + + duration = effect.replay.length - 1; + magnitude = (periodic.magnitude * fixp_sin16(effect.direction * 360 / 0x10000)) / 0x7fff; + + phase = periodic.phase; + periodic_offset = periodic.offset; + + period = periodic.period; + offset = effect.replay.delay; + + t300rs_fill_header(&packet_periodic->header, effect.id, 0x6b); + + packet_periodic->magnitude = cpu_to_le16(magnitude); + packet_periodic->periodic_offset = cpu_to_le16(periodic_offset); + packet_periodic->phase = cpu_to_le16(phase); + packet_periodic->period = cpu_to_le16(period); + + packet_periodic->marker = cpu_to_le16(0x8000); + + t300rs_fill_envelope(&packet_periodic->envelope, magnitude, duration, + &periodic.envelope); + + packet_periodic->waveform = periodic.waveform - 0x57; + + t300rs_fill_timing(&packet_periodic->timing, duration, offset); + + ret = t300rs_send_int(t300rs); + if (ret) + hid_err(t300rs->hdev, "failed uploading periodic effect"); + + return ret; +} + +int t300rs_update_effect(void *data, struct tmff2_effect_state *state) +{ + struct t300rs_device_entry *t300rs = data; + switch (state->effect.type) { + case FF_CONSTANT: + return t300rs_update_constant(t300rs, state); + case FF_RAMP: + return t300rs_update_ramp(t300rs, state); + case FF_SPRING: + return t300rs_update_spring(t300rs, state); + case FF_DAMPER: + case FF_FRICTION: + case FF_INERTIA: + return t300rs_update_damper(t300rs, state); + case FF_PERIODIC: + return t300rs_update_periodic(t300rs, state); + default: + hid_err(t300rs->hdev, "invalid effect type: %x", state->effect.type); + return -1; + } +} + +int t300rs_upload_effect(void *data, struct tmff2_effect_state *state) +{ + struct t300rs_device_entry *t300rs = data; + switch (state->effect.type) { + case FF_CONSTANT: + return t300rs_upload_constant(t300rs, state); + case FF_RAMP: + return t300rs_upload_ramp(t300rs, state); + case FF_SPRING: + return t300rs_upload_spring(t300rs, state); + case FF_DAMPER: + case FF_FRICTION: + case FF_INERTIA: + return t300rs_upload_damper(t300rs, state); + case FF_PERIODIC: + return t300rs_upload_periodic(t300rs, state); + default: + hid_err(t300rs->hdev, "invalid effect type: %x", state->effect.type); + return -1; + } +} + +static int t300rs_switch_mode(void *data, uint16_t mode) +{ + struct t300rs_device_entry *t300rs = data; + if (!t300rs) + return -ENODEV; + + if(t300rs->mode == mode) /* already in specified mode */ + return 0; + + if (mode == 0) + /* go to normal mode */ + usb_control_msg(t300rs->usbdev, + usb_sndctrlpipe(t300rs->usbdev, 0), + 83, 0x41, 5, 0, 0, 0, + USB_CTRL_SET_TIMEOUT + ); + else if (mode == 1) + /* go to advanced mode */ + usb_control_msg(t300rs->usbdev, + usb_sndctrlpipe(t300rs->usbdev, 0), + 83, 0x41, 3, 0, 0, 0, + USB_CTRL_SET_TIMEOUT + ); + else + hid_warn(t300rs->hdev, "mode %i not supported\n", mode); + + + return 0; +} + +static struct t300rs_alt_modes { + char *id; + char *label; + uint16_t mode; +} t300rs_modes[] = { + {"base", "T300RS base", 0}, + {"F1", "T300RS with F1 wheel attachment", 1} +}; + +static ssize_t t300rs_alt_mode_show(void *data, char *buf) +{ + struct t300rs_device_entry *t300rs = data; + ssize_t count = 0; + int i; + if (!t300rs) + return -ENODEV; + + if (t300rs->attachment != T300RS_F1_ATTACHMENT) + /* we only support one base mode */ + return scnprintf(buf, PAGE_SIZE, "%s: %s *\n", + t300rs_modes[0].id, t300rs_modes[0].label); + + for (i = 0; i < ARRAY_SIZE(t300rs_modes); ++i) { + count += scnprintf(buf + count, PAGE_SIZE - count, "%s: %s", + t300rs_modes[i].id, t300rs_modes[i].label); + + if (count >= PAGE_SIZE - 1) + return count; + + if (t300rs_modes[i].mode == t300rs->mode) + count += scnprintf(buf + count, PAGE_SIZE - count, " *\n"); + else + count += scnprintf(buf + count, PAGE_SIZE - count, "\n"); + + if (count >= PAGE_SIZE - 1) + return count; + } + + return count; +} + +static ssize_t t300rs_alt_mode_store(void *data, const char *buf, size_t count) +{ + struct t300rs_device_entry *t300rs = data; + int i, len, mode_len; + char *lbuf; + if (!t300rs) + return -ENODEV; + + if (t300rs->attachment != T300RS_F1_ATTACHMENT) + return count; /* don't do anything */ + + lbuf = kasprintf(GFP_KERNEL, "%s", buf); + if (!lbuf) + return -ENOMEM; + + len = strlen(buf); + for (i = 0; i < ARRAY_SIZE(t300rs_modes); ++i) { + mode_len = strlen(t300rs_modes[i].id); + if (mode_len > len) + continue; + + if (strncmp(lbuf, t300rs_modes[i].id, mode_len) == 0) { + t300rs_switch_mode(data, t300rs_modes[i].mode); + break; + } + } + + kfree(lbuf); + return count; +} + +int t300rs_set_autocenter(void *data, uint16_t value) +{ + struct t300rs_device_entry *t300rs = data; + struct __packed t300rs_packet_autocenter { + struct t300rs_setup_header header; + uint16_t value; + } *autocenter_packet; + int ret; + + if (!t300rs) + return -ENODEV; + + /* TODO: this should probably also use a separately allocated buffer? */ + autocenter_packet = (struct t300rs_packet_autocenter *)t300rs->send_buffer; + + autocenter_packet->header.cmd = 0x08; + autocenter_packet->header.code = 0x04; + autocenter_packet->value = cpu_to_le16(0x01); + + if ((ret = t300rs_send_int(t300rs))) { + hid_err(t300rs->hdev, "failed setting autocenter"); + return ret; + } + + autocenter_packet->header.cmd = 0x08; + autocenter_packet->header.code = 0x03; + + autocenter_packet->value = cpu_to_le16(value); + + if ((ret = t300rs_send_int(t300rs))) + hid_err(t300rs->hdev, "failed setting autocenter"); + + return ret; +} + +int t300rs_set_gain(void *data, uint16_t gain) +{ + struct t300rs_device_entry *t300rs = data; + struct __packed t300rs_packet_gain { + struct t300rs_setup_header header; + } *gain_packet; + int ret; + + if (!t300rs) + return -ENODEV; + + gain_packet = (struct t300rs_packet_gain *)t300rs->send_buffer; + gain_packet->header.cmd = 0x02; + gain_packet->header.code = (gain >> 8) & 0xff; + + if ((ret = t300rs_send_int(t300rs))) + hid_err(t300rs->hdev, "failed setting gain: %i\n", ret); + + return ret; +} + +int t300rs_set_range(void *data, uint16_t value) +{ + struct t300rs_device_entry *t300rs = data; + /* it's important that we don't use t300rs->send_buffer, as range can be + * set from outside of the FFB environment, and we don't want to + * accidentally overwrite any data. */ + u8 *send_buffer = kzalloc(t300rs->buffer_length, GFP_KERNEL); + uint16_t scaled_value; + int ret; + + if (value < 40) { + hid_info(t300rs->hdev, "value %i too small, clamping to 40\n", value); + value = 40; + } + + if (value > 1080) { + hid_info(t300rs->hdev, "value %i too large, clamping to 1080\n", value); + value = 1080; + } + + if (!send_buffer) { + ret = -EINVAL; + hid_err(t300rs->hdev, "could not allocate send_buffer\n"); + goto err; + } + + scaled_value = value * 0x3c; + send_buffer[0] = 0x08; + send_buffer[1] = 0x11; + send_buffer[2] = scaled_value & 0xff; + send_buffer[3] = scaled_value >> 8; + + if ((ret = t300rs_send_buf(t300rs, send_buffer, t300rs->buffer_length))) + hid_warn(t300rs->hdev, "failed setting range\n"); + + /* since everythin went OK, update the current range */ + range = value; +err: + kfree(send_buffer); + return ret; +} + +static int t300rs_send_open(struct t300rs_device_entry *t300rs) +{ + struct __packed t300rs_packet_open { + struct t300rs_setup_header header; + } *open_packet; + + open_packet = (struct t300rs_packet_open *)t300rs->send_buffer; + open_packet->header.cmd = 0x01; + open_packet->header.code = 0x05; + + return t300rs_send_int(t300rs); +} + +static int t300rs_send_close(struct t300rs_device_entry *t300rs) +{ + struct __packed t300rs_packet_open { + struct t300rs_setup_header header; + } *open_packet; + + open_packet = (struct t300rs_packet_open *)t300rs->send_buffer; + open_packet->header.cmd = 0x01; + + return t300rs_send_int(t300rs); +} + +int t300rs_open(void *data, int open_mode) +{ + struct t300rs_device_entry *t300rs = data; + if (!t300rs) + return -ENODEV; + + if (open_mode && t300rs_send_open(t300rs)) + hid_warn(t300rs->hdev, "failed sending open command\n"); + + return t300rs->open(t300rs->input_dev); +} + +int t300rs_close(void *data, int open_mode) +{ + struct t300rs_device_entry *t300rs = data; + int ret; + + if (!t300rs) + return -ENODEV; + + if (open_mode && (ret = t300rs_send_close(t300rs))) + hid_warn(t300rs->hdev, "failed sending close command\n"); + + t300rs->close(t300rs->input_dev); + return ret; +} + +static int t300rs_check_firmware(struct t300rs_device_entry *t300rs) +{ + int ret; + struct t300rs_fw_response *fw_response = + kzalloc(sizeof(struct t300rs_fw_response), GFP_KERNEL); + + if (!fw_response) { + hid_err(t300rs->hdev, "could not allocate fw_response\n"); + return -ENOMEM; + } + + /* Fetch firmware version */ + ret = usb_control_msg(t300rs->usbdev, + usb_rcvctrlpipe(t300rs->usbdev, 0), + t300rs_fw_request.bRequest, + t300rs_fw_request.bRequestType, + t300rs_fw_request.wValue, + t300rs_fw_request.wIndex, + fw_response, + t300rs_fw_request.wLength, + USB_CTRL_SET_TIMEOUT + ); + + if (ret < 0) { + hid_err(t300rs->hdev, "could not fetch firmware version: %i\n", ret); + goto out; + } + + /* Educated guess */ + if (fw_response->fw_version < 31 && ret >= 0) { + hid_err(t300rs->hdev, + "firmware version %i is too old, please update.\n", + fw_response->fw_version + ); + + hid_info(t300rs->hdev, "note: this has to be done through Windows.\n"); + + ret = -EINVAL; + goto out; + } + + /* everything OK */ + ret = 0; + +out: + kfree(fw_response); + return ret; +} + +static int t300rs_get_attachment(struct t300rs_device_entry *t300rs) +{ + /* taken directly from hid_tminit */ + struct __packed t300rs_attachment_response + { + uint16_t type; + + union { + struct __packed { + uint16_t field0; + uint16_t field1; + uint8_t attachment; + uint8_t model; + uint16_t field2; + uint16_t field3; + uint16_t field4; + uint16_t field5; + } a; + + struct __packed { + uint16_t field0; + uint16_t field1; + uint8_t attachment; + uint8_t model; + } b; + }; + } *response = kzalloc(GFP_KERNEL, sizeof(struct t300rs_attachment_response)); + struct usb_ctrlrequest t300rs_attachment_rq = { + .bRequestType = 0xc1, + .bRequest = 73, + .wValue = 0, + .wIndex = 0, + .wLength = sizeof(struct t300rs_attachment_response) + }; + int ret, attachment; + if (!response) + return -ENODEV; + + ret = usb_control_msg(t300rs->usbdev, + usb_rcvctrlpipe(t300rs->usbdev, 0), + t300rs_attachment_rq.bRequest, + t300rs_attachment_rq.bRequestType, + t300rs_attachment_rq.wValue, + t300rs_attachment_rq.wIndex, + response, + sizeof(struct t300rs_attachment_response), + USB_CTRL_SET_TIMEOUT + ); + + if (ret < 0) { + hid_err(t300rs->hdev, "could not fetch attachment: %i\n", ret); + goto out; + } + + if (response->type == cpu_to_le16(0x49)) { + attachment = response->a.attachment; + } else if (response->type == cpu_to_le16(0x47)) { + attachment = response->b.attachment; + } else { + hid_err(t300rs->hdev, "unknown packet type %hx\n, please contact a maintainer", + response->type); + ret = -EINVAL; + goto out; + } + + kfree(response); + return attachment; + +out: + kfree(response); + return ret; +} + +static int t300rs_wheel_init(struct tmff2_device_entry *tmff2, int open_mode) +{ + struct t300rs_device_entry *t300rs = kzalloc(sizeof(struct t300rs_device_entry), GFP_KERNEL); + struct list_head *report_list; + int ret; + + if (!t300rs) { + ret = -ENOMEM; + goto t300rs_err; + } + + t300rs->hdev = tmff2->hdev; + t300rs->input_dev = tmff2->input_dev; + t300rs->usbdev = to_usb_device(tmff2->hdev->dev.parent->parent); + + if(t300rs->hdev->product == TMT300RS_PS4_NORM_ID) + t300rs->buffer_length = T300RS_PS4_BUFFER_LENGTH; + else + t300rs->buffer_length = T300RS_NORM_BUFFER_LENGTH; + + t300rs->send_buffer = kzalloc(t300rs->buffer_length, GFP_KERNEL); + if (!t300rs->send_buffer) { + ret = -ENOMEM; + goto send_err; + } + + if ((ret = t300rs_check_firmware(t300rs))) + goto firmware_err; + + report_list = &t300rs->hdev->report_enum[HID_OUTPUT_REPORT].report_list; + + /* because we set the rdesc, we know exactly which report and field to use */ + t300rs->report = list_entry(report_list->next, struct hid_report, list); + t300rs->ff_field = t300rs->report->field[0]; + + t300rs->open = t300rs->input_dev->open; + t300rs->close = t300rs->input_dev->close; + + /* TODO: PS4 advanced mode? */ + alt_mode = (t300rs->mode = (t300rs->hdev->product == TMT300RS_PS3_ADV_ID)); + if ((t300rs->attachment = t300rs_get_attachment(t300rs)) < 0) + t300rs->attachment = T300RS_DEFAULT_ATTACHMENT; + + /* everythin went OK */ + tmff2->data = t300rs; + tmff2->params = t300rs_params; + tmff2->max_effects = T300RS_MAX_EFFECTS; + memcpy(tmff2->supported_effects, t300rs_effects, sizeof(t300rs_effects)); + + if (!open_mode) + t300rs_send_open(t300rs); + + hid_info(t300rs->hdev, "force feedback for T300RS\n"); + return 0; + +firmware_err: + kfree(t300rs->send_buffer); +send_err: + kfree(t300rs); +t300rs_err: + hid_err(tmff2->hdev, "failed initializing T300RS\n"); + return ret; +} + +static int t300rs_wheel_destroy(void *data) +{ + struct t300rs_device_entry *t300rs = data; + if (!t300rs) + return -ENODEV; + + kfree(t300rs->send_buffer); + kfree(t300rs); + return 0; +} + +static __u8 *t300rs_wheel_fixup(struct hid_device *hdev, __u8 *rdesc, + unsigned int *rsize) +{ + switch (hdev->product) { + case TMT300RS_PS3_NORM_ID: + /* normal PS3 mode */ + rdesc = t300rs_rdesc_nrm_fixed; + *rsize = sizeof(t300rs_rdesc_nrm_fixed); + break; + + case TMT300RS_PS4_NORM_ID: + /* PS4 normal mode */ + rdesc = t300rs_rdesc_ps4_fixed; + *rsize = sizeof(t300rs_rdesc_ps4_fixed); + break; + + case TMT300RS_PS3_ADV_ID: + /* PS3 advanced mode */ + rdesc = t300rs_rdesc_adv_fixed; + *rsize = sizeof(t300rs_rdesc_adv_fixed); + break; + } + + return rdesc; +} + +int t300rs_populate_api(struct tmff2_device_entry *tmff2) +{ + /* set callbacks */ + tmff2->play_effect = t300rs_play_effect; + tmff2->upload_effect = t300rs_upload_effect; + tmff2->update_effect = t300rs_update_effect; + tmff2->stop_effect = t300rs_stop_effect; + + tmff2->wheel_init = t300rs_wheel_init; + tmff2->wheel_destroy = t300rs_wheel_destroy; + + tmff2->open = t300rs_open; + tmff2->close = t300rs_close; + tmff2->set_gain = t300rs_set_gain; + tmff2->set_range = t300rs_set_range; + tmff2->switch_mode = t300rs_switch_mode; + tmff2->alt_mode_show = t300rs_alt_mode_show; + tmff2->alt_mode_store = t300rs_alt_mode_store; + tmff2->set_autocenter = t300rs_set_autocenter; + tmff2->wheel_fixup = t300rs_wheel_fixup; + + return 0; +} diff --git a/src/hid-tmt300rs/hid-tmt300rs.h b/src/hid-tmt300rs/hid-tmt300rs.h new file mode 100644 index 0000000..2305f4b --- /dev/null +++ b/src/hid-tmt300rs/hid-tmt300rs.h @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#ifndef __HID_TMT300RS_H +#define __HID_TMT300RS_H + +#include "../hid-tmff2.h" + +int t300rs_populate_api(struct tmff2_device_entry *tmff2); + +#endif diff --git a/src/hid-tmtx/hid-tmtx.c b/src/hid-tmtx/hid-tmtx.c new file mode 100644 index 0000000..afca799 --- /dev/null +++ b/src/hid-tmtx/hid-tmtx.c @@ -0,0 +1,323 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#include +#include +#include "../hid-tmff2.h" + +#define TMTX_MAX_EFFECTS 16 +#define TMTX_BUFFER_LENGTH 63 + +static const u8 setup_0[64] = { 0x42, 0x01 }; +static const u8 setup_1[64] = { 0x0a, 0x04, 0x90, 0x03 }; +static const u8 setup_2[64] = { 0x0a, 0x04, 0x00, 0x0c }; +static const u8 setup_3[64] = { 0x0a, 0x04, 0x12, 0x10 }; +static const u8 setup_4[64] = { 0x0a, 0x04, 0x00, 0x06 }; +static const u8 setup_5[64] = { 0x0a, 0x04, 0x00, 0x0e }; +static const u8 setup_6[64] = { 0x0a, 0x04, 0x00, 0x0e, 0x01 }; +static const u8 *const setup_arr[] = { setup_0, setup_1, setup_2, setup_3, setup_4, setup_5, setup_6 }; +static const unsigned int setup_arr_sizes[] = { + ARRAY_SIZE(setup_0), + ARRAY_SIZE(setup_1), + ARRAY_SIZE(setup_2), + ARRAY_SIZE(setup_3), + ARRAY_SIZE(setup_4), + ARRAY_SIZE(setup_5), + ARRAY_SIZE(setup_6) +}; + +static const unsigned long tx_params = + PARAM_SPRING_LEVEL + | PARAM_DAMPER_LEVEL + | PARAM_FRICTION_LEVEL + | PARAM_RANGE + | PARAM_GAIN + ; + +static const signed short tx_effects[] = { + FF_CONSTANT, + FF_RAMP, + FF_SPRING, + FF_DAMPER, + FF_FRICTION, + FF_INERTIA, + FF_PERIODIC, + FF_SINE, + FF_TRIANGLE, + FF_SQUARE, + FF_SAW_UP, + FF_SAW_DOWN, + FF_AUTOCENTER, + FF_GAIN, + -1 +}; + +/* TODO: sort through this stuff */ +static u8 tx_pc_rdesc_fixed[] = { + 0x05, 0x01, /* Usage page (Generic Desktop) */ + 0x09, 0x04, /* Usage (Joystick) */ + 0xa1, 0x01, /* Collection (Application) */ + 0x09, 0x01, /* Usage (Pointer) */ + 0xa1, 0x00, /* Collection (Physical) */ + 0x85, 0x07, /* Report ID (7) */ + 0x09, 0x30, /* Usage (X) */ + 0x15, 0x00, /* Logical minimum (0) */ + 0x27, 0xff, 0xff, 0x00, 0x00, /* Logical maximum (65535) */ + 0x35, 0x00, /* Physical minimum (0) */ + 0x47, 0xff, 0xff, 0x00, 0x00, /* Physical maximum (65535) */ + 0x75, 0x10, /* Report size (16) */ + 0x95, 0x01, /* Report count (1) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x09, 0x35, /* Usage (Rz) (Brake) */ + 0x26, 0xff, 0x03, /* Logical maximum (1023) */ + 0x46, 0xff, 0x03, /* Physical maximum (1023) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x09, 0x32, /* Usage (Z) (Gas) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x09, 0x31, /* Usage (Y) (Clutch) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x81, 0x03, /* Input (Variable, Absolute, Constant) */ + 0x05, 0x09, /* Usage page (Button) */ + 0x19, 0x01, /* Usage minimum (1) */ + 0x29, 0x0d, /* Usage maximum (13) */ + 0x25, 0x01, /* Logical maximum (1) */ + 0x45, 0x01, /* Physical maximum (1) */ + 0x75, 0x01, /* Report size (1) */ + 0x95, 0x0d, /* Report count (13) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x75, 0x0b, /* Report size (13) */ + 0x95, 0x01, /* Report count (1) */ + 0x81, 0x03, /* Usage (Variable, Absolute, Constant) */ + 0x05, 0x01, /* Usage page (Generic Desktop) */ + 0x09, 0x39, /* Usage (Hat Switch) */ + 0x25, 0x07, /* Logical maximum (7) */ + 0x46, 0x3b, 0x01, /* Physical maximum (315) */ + 0x55, 0x00, /* Unit exponent (0) */ + 0x65, 0x14, /* Unit (Eng Rot, Angular Pos) */ + 0x75, 0x04, /* Report size (4) */ + 0x81, 0x42, /* Input (Variable, Absolute, NullState) */ + 0x65, 0x00, /* Unit (None) */ + 0x81, 0x03, /* Input (Variable, Absolute, Constant) */ + 0x85, 0x60, /* Report ID (96), prev 10 */ + 0x06, 0x00, 0xff, /* Usage page (Vendor 1) */ + 0x09, 0x60, /* Usage (96), prev 10 */ + 0x75, 0x08, /* Report size (8) */ + 0x95, 0x3f, /* Report count (63) */ + 0x26, 0xff, 0x7f, /* Logical maximum (32767) */ + 0x15, 0x00, /* Logical minimum (0) */ + 0x46, 0xff, 0x7f, /* Physical maximum (32767) */ + 0x36, 0x00, 0x80, /* Physical minimum (-32768) */ + 0x91, 0x02, /* Output (Variable, Absolute) */ + 0x85, 0x02, /* Report ID (2) */ + 0x09, 0x02, /* Usage (2) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x09, 0x14, /* Usage (20) */ + 0x85, 0x14, /* Report ID (20) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0xc0, /* End collection */ + 0xc0, /* End collection */ +}; + +static int tx_interrupts(struct t300rs_device_entry *tx) +{ + u8 *send_buf = kmalloc(256, GFP_KERNEL); + struct usb_interface *usbif = to_usb_interface(tx->hdev->dev.parent); + struct usb_host_endpoint *ep; + int ret, trans, b_ep, i; + + if (!send_buf) { + hid_err(tx->hdev, "failed allocating send buffer\n"); + return -ENOMEM; + } + + ep = &usbif->cur_altsetting->endpoint[1]; + b_ep = ep->desc.bEndpointAddress; + + for (i = 0; i < ARRAY_SIZE(setup_arr); ++i) { + memcpy(send_buf, setup_arr[i], setup_arr_sizes[i]); + + ret = usb_interrupt_msg(tx->usbdev, + usb_sndintpipe(tx->usbdev, b_ep), + send_buf, setup_arr_sizes[i], + &trans, + USB_CTRL_SET_TIMEOUT); + + if (ret) { + hid_err(tx->hdev, "setup data couldn't be sent\n"); + goto err; + } + } + +err: + kfree(send_buf); + return ret; +} + +int tx_wheel_destroy(void *data) +{ + struct t300rs_device_entry *t300rs = data; + + if (!t300rs) + return -ENODEV; + + kfree(t300rs->send_buffer); + kfree(t300rs); + return 0; +} + +int tx_set_range(void *data, uint16_t value) +{ + struct t300rs_device_entry *tx = data; + + if (value < 140) { + hid_info(tx->hdev, "value %i too small, clamping to 140\n", value); + value = 140; + } + + if (value > 900) { + hid_info(tx->hdev, "value %i too large, clamping to 900\n", value); + value = 900; + } + + return t300rs_set_range(data, value); +} + +static int tx_send_open(struct t300rs_device_entry *tx) +{ + int r1, r2; + tx->send_buffer[0] = 0x01; + tx->send_buffer[1] = 0x04; + if ((r1 = t300rs_send_int(tx))) + return r1; + + tx->send_buffer[0] = 0x01; + tx->send_buffer[1] = 0x05; + if ((r2 = t300rs_send_int(tx))) + return r2; + + return 0; +} + +static int tx_open(void *data, int open_mode) +{ + struct t300rs_device_entry *tx = data; + + if (!tx) + return -ENODEV; + + if (open_mode) + tx_send_open(tx); + + return tx->open(tx->input_dev); +} + +static int tx_send_close(struct t300rs_device_entry *tx) +{ + int r1, r2; + tx->send_buffer[0] = 0x01; + tx->send_buffer[1] = 0x05; + if ((r1 = t300rs_send_int(tx))) + return r1; + + tx->send_buffer[0] = 0x01; + tx->send_buffer[1] = 0x00; + if ((r2 = t300rs_send_int(tx))) + return r2; + + return 0; +} + +static int tx_close(void *data, int open_mode) +{ + struct t300rs_device_entry *tx = data; + + if (!tx) + return -ENODEV; + + if (open_mode) + tx_send_close(tx); + + tx->close(tx->input_dev); + return 0; +} + +int tx_wheel_init(struct tmff2_device_entry *tmff2, int open_mode) +{ + struct t300rs_device_entry *tx = kzalloc(sizeof(struct t300rs_device_entry), GFP_KERNEL); + struct list_head *report_list; + int ret; + + + if (!tx) { + ret = -ENOMEM; + goto tx_err; + } + + tx->hdev = tmff2->hdev; + tx->input_dev = tmff2->input_dev; + tx->usbdev = to_usb_device(tmff2->hdev->dev.parent->parent); + tx->buffer_length = TMTX_BUFFER_LENGTH; + + tx->send_buffer = kzalloc(tx->buffer_length, GFP_KERNEL); + if (!tx->send_buffer) { + ret = -ENOMEM; + goto send_err; + } + + report_list = &tx->hdev->report_enum[HID_OUTPUT_REPORT].report_list; + tx->report = list_entry(report_list->next, struct hid_report, list); + tx->ff_field = tx->report->field[0]; + + tx->open = tx->input_dev->open; + tx->close = tx->input_dev->close; + + if ((ret = tx_interrupts(tx))) + goto interrupt_err; + + /* everything went OK */ + tmff2->data = tx; + tmff2->params = tx_params; + tmff2->max_effects = TMTX_MAX_EFFECTS; + memcpy(tmff2->supported_effects, tx_effects, sizeof(tx_effects)); + + if (!open_mode) + tx_send_open(tx); + + hid_info(tx->hdev, "force feedback for TX\n"); + return 0; + +interrupt_err: +send_err: + kfree(tx); +tx_err: + hid_err(tmff2->hdev, "failed initializing TX\n"); + return ret; +} + +static __u8 *tx_wheel_fixup(struct hid_device *hdev, __u8 *rdesc, + unsigned int *rsize) +{ + rdesc = tx_pc_rdesc_fixed; + *rsize = sizeof(tx_pc_rdesc_fixed); + return rdesc; +} + +int tx_populate_api(struct tmff2_device_entry *tmff2) +{ + tmff2->play_effect = t300rs_play_effect; + tmff2->upload_effect = t300rs_upload_effect; + tmff2->update_effect = t300rs_update_effect; + tmff2->stop_effect = t300rs_stop_effect; + + tmff2->set_gain = t300rs_set_gain; + tmff2->set_autocenter = t300rs_set_autocenter; + /* TX only has 900 degree range, instead of T300RS 1080 */ + tmff2->set_range = tx_set_range; + tmff2->wheel_fixup = tx_wheel_fixup; + + tmff2->open = tx_open; + tmff2->close = tx_close; + + tmff2->wheel_init = tx_wheel_init; + tmff2->wheel_destroy = tx_wheel_destroy; + + return 0; +} diff --git a/t248-effects.txt b/t248-effects.txt deleted file mode 100644 index 821fc7a..0000000 --- a/t248-effects.txt +++ /dev/null @@ -1,2 +0,0 @@ -Open: Identical to T300 -Close: Identical to T300 -- cgit v1.3 From 6bf954ca59b4672601052fbfe212c0b07cb06ab1 Mon Sep 17 00:00:00 2001 From: RaySlash Date: Tue, 3 Oct 2023 12:38:51 +1000 Subject: docs: update TODO.md --- docs/TODO.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/TODO.md b/docs/TODO.md index bd73609..14b8d2e 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -1,10 +1,11 @@ # TO-DO list -- Make code more general to allow for stuff like T150/T248/T500 to be relatively -easily included. +- Make code more general to allow for stuff like T150/T248/T500 to be relatively easily included. - Use workqueues instead of doing everything in t300rs_timer +- Document changes to code in an organised manner. + ### T248 - - open/close seems to crash the wheel and the kernel along with it, check how Windows handles it - - fix rdesc, 0x0a somewhere needs to be set to 0x60 +- open/close seems to crash the wheel and the kernel along with it, check how Windows handles it +- fix rdesc, 0x0a somewhere needs to be set to 0x60 -- cgit v1.3 From 569a897ab2ae73509f11e5653401ff3e5dafa901 Mon Sep 17 00:00:00 2001 From: Steve Mathew Joy <45141270+RaySlash@users.noreply.github.com> Date: Tue, 3 Oct 2023 23:45:57 +1000 Subject: docs: update README.md --- README.md | 75 +++++++++++++++++++++++++++++++++------------------------------ 1 file changed, 39 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index a0e075c..6bad92b 100644 --- a/README.md +++ b/README.md @@ -1,32 +1,30 @@ # Linux kernel module for Thrustmaster T300RS, T248 and TX(experimental) wheels +> **DISCLAIMER:** The module is now ready for near-stable USE in most force feedback games, supports rangesetting as well as gain and autocentering along with most force feedback effects. While I haven't personally come across any crashes or lockups with this version, I can't promise that they won't occur under any circumstances. -## Description -A Linux kernel module for Thrustmaster T300RS, T248, and TX wheels is a software component designed to enhance the compatibility and functionality of these popular racing wheel peripherals when used with Linux-based operating systems. This kernel module allows seamless integration of Thrustmaster wheels with the Linux kernel, providing users with a robust and feature-rich experience for gaming and other applications. - -I've made some improvements to the dynamic updating of effects, and while still far for perfect, the experience is slowly getting better and better. Some drawbacks include possible effect inaccuracies in comparison with the Windows driver and in some games inconsistent pedal mapping. Meaning that all pedals should be detected in games, but may be mapped incorrectly. +![GitHub last commit (branch)](https://img.shields.io/github/last-commit/Kimplul/hid-tmff2/master) +![GitHub](https://img.shields.io/github/license/Kimplul/hid-tmff2) +![GitHub contributors](https://img.shields.io/github/contributors/Kimplul/hid-tmff2) -> **Current version ``0.8`` is USABLE in most force feedback games, supports rangesetting as well as gain and autocentering along with most force feedback effects.** While I haven't personally come across any crashes or lockups with this version, I can't promise that they won't occur under any circumstances. -### Help wanted with adding more Thrustmaster wheels -Currently open requests for wheels: -+ [T500 RS](https://github.com/Kimplul/hid-tmff2/issues/18) -+ [T818](https://github.com/Kimplul/hid-tmff2/issues/58) -+ [T-GT II](https://github.com/Kimplul/hid-tmff2/issues/55) +## Description +A Linux kernel module for Thrustmaster T300RS, T248, and TX (experimental support) wheels is a software component designed to enhance the compatibility and functionality of these popular racing wheel peripherals when used with Linux-based operating systems. This kernel module allows seamless integration of Thrustmaster wheels with the Linux kernel, providing users with a robust and feature-rich experience for gaming and other applications with working force feedback effects. -If you would like to help add a wheel to this driver, please have a look through the [wiki](https://github.com/Kimplul/hid-tmff2/wiki#how-to-add-in-support-for-a-new-t-series-wheel) and/or [CONTRIBUTING.md](./docs/CONTRIBUTING.md) for what might need to be done. If you have a wheel that's not on the list, but suspect it might fit into the driver, please feel free to open up an issue about it. +I've been working on enhancing the real-time updating of effects, and although it's not flawless yet, the overall experience is gradually improving. There are a couple of issues, though. First, there might be occasional inaccuracies in how the effects compare to the Windows driver. Second, in certain games, the mapping of pedal inputs can be inconsistent. This means that while all pedals should be recognized by the games, they might not be mapped correctly. ## Installation -### Dependencies -Kernel modules require kernel headers to be installed. - -+ Debian-based: ``sudo apt install linux-headers-$(uname -r)`` -+ Arch-based: ``sudo pacman -S linux-headers`` -+ Fedora-based: ``sudo yum install kernel-devel kernel-headers`` +You can either install this kernel module by using DKMS or manually building from source: -> **NOTE:** See [here](https://github.com/Kimplul/hid-tmff2/wiki/Integrating-driver-into-distros) for install instructions for other linux distributions. +### Dependencies +Kernel modules require kernel headers to be installed. Use any one of the right command for your distribution: +```shell +$ sudo apt install linux-headers-$(uname -r) #Debian-based +$ sudo pacman -S linux-headers #Arch-based +$ sudo yum install kernel-devel kernel-headers #Fedora-based +``` -### Manual installation +#### Manual installation + Unplug wheel from computer ++ Use the following commands in your terminal of choice: ```shell git clone --recurse-submodules https://github.com/Kimplul/hid-tmff2.git cd hid-tmff2/src @@ -34,34 +32,39 @@ make sudo make install ``` + Plug wheel back in -+ Reboot (not strictly necessary, but definitely recommended) - -Done! - -> **NOTE:** On some systems, you will get an error/warning about SSL. This is normal for unsigned modules. For info on signing modules yourself (completely optional), see [here](https://www.kernel.org/doc/html/latest/admin-guide/module-signing.html?highlight=module%20signing). - -> **NOTE:** Thrustmaster TX wheels aren't supported by `hid-tminit` as of yet, meaning that TX wheels have to be initialized with `tmrd`. Please see https://github.com/Kimplul/hid-tmff2/issues/48. ++ Reboot *(Optional, yet Recommended)* -### DKMS +#### DKMS (Dynamic Kernel Module Support) + Unplug wheel from computer -```shell -sudo bash ./dkms/dkms-install.sh -``` ++ Clone the repo `git clone --recurse-submodules https://github.com/Kimplul/hid-tmff2.git` ++ Use `sudo bash hid-tmff2/dkms/dkms-install.sh` on your terminal of choice + Plug wheel back in -+ reboot (not strictly necessary, but definitely recommended) ++ Reboot *(Optional, yet Recommended)* + +> **NOTE:** See [here](https://github.com/Kimplul/hid-tmff2/wiki/Integrating-driver-into-distros) for install instructions for other linux distributions. + +> **NOTE:** On some systems, you will get an error/warning about SSL. This is normal for unsigned modules. For info on signing modules yourself (completely optional), see [here](https://www.kernel.org/doc/html/latest/admin-guide/module-signing.html?highlight=module%20signing). -Done! +> **NOTE:** Thrustmaster TX wheels aren't supported by `hid-tminit` as of yet, meaning that TX wheels have to be initialized with `tmrd`. Please see https://github.com/Kimplul/hid-tmff2/issues/48. -> :warning: Warning: There have been reports that this driver does not work if the wheel's firmware version is older than v. 31. +> :warning: **NOTE:** There have been reports that this driver does not work if the wheel's firmware version is older than v. 31. > To update the firmware, you will have to fire up a Windows installation and update the firmware using the official Thrustmaster tools. -> :warning: Warning: There was a name change when adding support for the T248 from `hid-tmt300rs` to `hid-tmff-new`, +> :warning: **NOTE:** There was a name change when adding support for the T248 from `hid-tmt300rs` to `hid-tmff-new`, > and you may have to uninstall the older version of the driver. -## Additional tidbits +## Contribute to project +This project wants help from people who can contribute. If you would like to help add a wheel to this driver, please have a look through the [wiki](https://github.com/Kimplul/hid-tmff2/wiki#how-to-add-in-support-for-a-new-t-series-wheel) and/or [CONTRIBUTING.md](./docs/CONTRIBUTING.md) for what might need to be done. If you have a wheel that's not on the list, but suspect it might fit into the driver, please feel free to open up an issue about it. +Currently open requests for wheels: +- [T500 RS](https://github.com/Kimplul/hid-tmff2/issues/18) +- [T818](https://github.com/Kimplul/hid-tmff2/issues/58) +- [T-GT II](https://github.com/Kimplul/hid-tmff2/issues/55) +- [T128P](https://github.com/Kimplul/hid-tmff2/issues/67) + +## FAQ (Frequently Asked Questions) + Reportedly some games running under Wine/Proton won't recognize wheels without the official Thrustmaster drivers installed within the prefix. See [#46](https://github.com/Kimplul/hid-tmff2/issues/46#issuecomment-1199080845). For installation instructions, see [wiki](https://github.com/Kimplul/hid-tmff2/wiki). Note that you will still need the Linux driver, the Windows driver just installs some files needed by games to correctly recognize the Linux driver. The Windows driver itself does not work under Wine/Proton. -+ Until the updated `hid-tminit` is upstreamed, you might want to blacklist the kernel module `hid-thrustmaster`. Do this with ++ Until the updated `hid-tminit` is [upstreamed](https://github.com/scarburato/hid-tminit), you might want to blacklist the kernel module `hid-thrustmaster`. Do this with ```shell echo 'blacklist hid_thrustmaster' > /etc/modprobe.d/hid_thrustmaster.con ``` -- cgit v1.3 From fe7751d6c9eb7fcc318691636d08654a75736934 Mon Sep 17 00:00:00 2001 From: Steve Mathew Joy <45141270+RaySlash@users.noreply.github.com> Date: Wed, 4 Oct 2023 00:06:27 +1000 Subject: docs: update CONTRIBUTING.md --- docs/CONTRIBUTING.md | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 0e2c7b6..b1f289b 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -1,11 +1,20 @@ # Contributing ## Overview -This file contains general contributing information, but individual parts also may have more specific information to them, linked here: - - ``[FFBEffects-T300RS](./FFBEffects-T300RS.md)``: Force Feedback Effects example for T300RS - - ``[FFBEffects-T248](./FFBEffects-T248.md)``: Force Feedback Effects example for T300RS - - ``[To-Do](./TODO.md)``: TO-DO list for maintainers - - ``[Structure](https://github.com/Kimplul/hid-tmff2/wiki#structure-of-the-thrustmaster-device-stack)``: Structure of Thrustmaster device stack +This file contains general contributing information. This project is seeking for help from people who can contribute. If you have a wheel that's not on the list, but suspect it might fit into the driver, please feel free to open up an issue about it. + +Currently open requests for wheels: +- [T500 RS](https://github.com/Kimplul/hid-tmff2/issues/18) +- [T818](https://github.com/Kimplul/hid-tmff2/issues/58) +- [T-GT II](https://github.com/Kimplul/hid-tmff2/issues/55) +- [T128P](https://github.com/Kimplul/hid-tmff2/issues/67) + +Other documents available are linked here: + +- [FFBEffects-T300RS](./FFBEffects-T300RS.md): Force Feedback Effects example for T300RS +- [FFBEffects-T248](./FFBEffects-T248.md): Force Feedback Effects example for T248 +- [Structure](https://github.com/Kimplul/hid-tmff2/wiki#structure-of-the-thrustmaster-device-stack): Structure of Thrustmaster device stack +- [TO-DO](./TODO.md): TO-DO list for maintainers ## How to add in support for a new T-series wheel? Should probably not be too often that you need this info, but essentially use wireshark like in the previous example, but spin up a Windows virtual machine and install the Thrustmaster drivers on to it, and pass the device to the virtual machine. I prefer to use ``qemu`` with ``virt-manager`` as a frontend. With the wheel working under Windows, install [fedit.exe](https://gimx.fr/download/b882e209a0ac023d03abbf560dfc3f25fe6367ca/fedit.zip) and methodically go through all effects the device supports and compare the USB packets the driver sends out. You should be able to build up a table of what each value in the USB packet means, see [FFBEffects-T300RS.md](./FFBEffects-T300RS.md) for an example of what I found out about the T300. -- cgit v1.3 From c5e36a43b975177f37d9c3bf42ed657c44cbd5d5 Mon Sep 17 00:00:00 2001 From: RaySlash Date: Wed, 4 Oct 2023 13:10:48 +1000 Subject: docs: rename and update - Add dir `docs/effects/` - Rename existing `docs/FFBEffects-*.md` to `docs/effects/FFBEFFECTS_*.md` - Update double backticks with single to improve consistency --- docs/CONTRIBUTING.md | 20 +- docs/FFBEffects-T248.md | 7 - docs/FFBEffects-T300RS.md | 542 ---------------------------------------------- 3 files changed, 10 insertions(+), 559 deletions(-) delete mode 100644 docs/FFBEffects-T248.md delete mode 100644 docs/FFBEffects-T300RS.md diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index b1f289b..db937a1 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -17,7 +17,7 @@ Other documents available are linked here: - [TO-DO](./TODO.md): TO-DO list for maintainers ## How to add in support for a new T-series wheel? -Should probably not be too often that you need this info, but essentially use wireshark like in the previous example, but spin up a Windows virtual machine and install the Thrustmaster drivers on to it, and pass the device to the virtual machine. I prefer to use ``qemu`` with ``virt-manager`` as a frontend. With the wheel working under Windows, install [fedit.exe](https://gimx.fr/download/b882e209a0ac023d03abbf560dfc3f25fe6367ca/fedit.zip) and methodically go through all effects the device supports and compare the USB packets the driver sends out. You should be able to build up a table of what each value in the USB packet means, see [FFBEffects-T300RS.md](./FFBEffects-T300RS.md) for an example of what I found out about the T300. +Should probably not be too often that you need this info, but essentially use wireshark like in the previous example, but spin up a Windows virtual machine and install the Thrustmaster drivers on to it, and pass the device to the virtual machine. I prefer to use `qemu` with `virt-manager` as a frontend. With the wheel working under Windows, install [fedit.exe](https://gimx.fr/download/b882e209a0ac023d03abbf560dfc3f25fe6367ca/fedit.zip) and methodically go through all effects the device supports and compare the USB packets the driver sends out. You should be able to build up a table of what each value in the USB packet means, see [FFBEffects-T300RS.md](./FFBEffects-T300RS.md) for an example of what I found out about the T300. ## How to capture what effects a game sends to the driver? Use ffbwrap from ffbtools: [github:berarma/ffbtools](https://github.com/berarma/ffbtools) @@ -25,7 +25,7 @@ The documentation gives good examples, but tl;dr; For steam, insert the followin ```shell $ ffbwrap --logger=/home/$USER/game.log /dev/input/by-id/usb-Thrustmaster_Thrustmaster_T300RS_Racing_wheel-event-joystick -- %command% ``` -This will create a file called ``game.log`` (with additional timestamp) in your home directory. Preferably change the name to suit the game, but you do you. +This will create a file called `game.log` (with additional timestamp) in your home directory. Preferably change the name to suit the game, but you do you. > **NOTE:** Most fixes presented in the documentation are more or less obsolete by now, but the tool is still very useful for logging purposes. @@ -36,15 +36,15 @@ Usb capture setup is fairly straightforward: [wireshark/CaptureSetup](https://wi Here's what I typically do when starting capturing: -- Run ``sudo modprobe usbmon``. This will load a kernel module that allows Wireshark to read the USB packets. +- Run `sudo modprobe usbmon`. This will load a kernel module that allows Wireshark to read the USB packets. - Open wireshark with root privileges. There are some ways to allow wireshark to access the packets with regular user privileges, I just haven't bothered with it. -- Select ``usbmon0`` from the view that opens up by default. +- Select `usbmon0` from the view that opens up by default. - The screen will quickly fill up with noise from other devices connected to the computer, so you will have to filter out the noise. - - Run ``lsusb`` in a terminal. Look for the T300 in the list, you should see something like ``Bus 001 Device 006: ID 044f:b66e ThrustMaster, Inc. Thrustmaster T300RS Racing wheel`` - - From the previous command, the Bus and Device fields can be used to filter out only packets from/to the device. To see all packets coming from the wheel, add in a filter ``usb.src ~ "1\.6\..*"``, where ``1`` is in this case from the ``Bus`` field and ``6`` from the ``Device`` field. To see all packets coming from the device, use ``usb.dst``. - - This data is probably also filled with a lot of cruft. Data to/from endpoint 2 is button state info, which is probably unnecessary. To see FFB data being sent to the device, use endpoint 1, i.e. ``usb.dst == "1.6.1"``. ``~`` is a Perl-compliant regex operator, whereas ``==`` just matches the string directly. To see both data coming from the device and going to it, use ``usb.dst == "..." || usb.src == "..."``. - - There is also endpoint 0, ``usb.src == "1.6.0"`` but it doesn't seem to be used for much. + - Run `lsusb` in a terminal. Look for the T300 in the list, you should see something like `Bus 001 Device 006: ID 044f:b66e ThrustMaster, Inc. Thrustmaster T300RS Racing wheel` + - From the previous command, the Bus and Device fields can be used to filter out only packets from/to the device. To see all packets coming from the wheel, add in a filter `usb.src ~ "1\.6\..*"`, where `1` is in this case from the `Bus` field and `6` from the `Device` field. To see all packets coming from the device, use `usb.dst`. + - This data is probably also filled with a lot of cruft. Data to/from endpoint 2 is button state info, which is probably unnecessary. To see FFB data being sent to the device, use endpoint 1, i.e. `usb.dst == "1.6.1"`. `~` is a Perl-compliant regex operator, whereas `==` just matches the string directly. To see both data coming from the device and going to it, use `usb.dst == "..." || usb.src == "..."`. + - There is also endpoint 0, `usb.src == "1.6.0"` but it doesn't seem to be used for much. - Use the three buttons in the top left of the screen to start, stop and restart captures. -- Do whatever you want with the device, packets should automatically be captured. When you want to save your capture to a file, stop the recording and go to ``File > Export Specified Packets`` and make sure ``Displayed`` is selected. This will apply the filter you've been using, and will only include the packets that are visible in Wireshark, i.e. it applies the filter you've specified. +- Do whatever you want with the device, packets should automatically be captured. When you want to save your capture to a file, stop the recording and go to `File > Export Specified Packets` and make sure `Displayed` is selected. This will apply the filter you've been using, and will only include the packets that are visible in Wireshark, i.e. it applies the filter you've specified. -> **NOTE:** Every time you unplug and replug your wheel, its ``Device`` field will probably change. +> **NOTE:** Every time you unplug and replug your wheel, its `Device` field will probably change. diff --git a/docs/FFBEffects-T248.md b/docs/FFBEffects-T248.md deleted file mode 100644 index dfd5971..0000000 --- a/docs/FFBEffects-T248.md +++ /dev/null @@ -1,7 +0,0 @@ -# Force Feedback Effects - Thrustmaster T248 - -## Open: -Identical to T300RS - -## Close: -Identical to T300RS diff --git a/docs/FFBEffects-T300RS.md b/docs/FFBEffects-T300RS.md deleted file mode 100644 index 1770bdd..0000000 --- a/docs/FFBEffects-T300RS.md +++ /dev/null @@ -1,542 +0,0 @@ -# Force Feedback Effects - Thrustmaster T300RS (incl. PS3/PS4 modes) -Here I will gather all info I can find out about the different force feedback effects and their USB commands. All values seem to be little-endian. Also, general note, it seems like some effects are affected by the direction of the force, denoted by ``***``. As far as I can tell, ``fade_level`` and ``attack_level`` values are calculated by mapping the ``u16`` into ``s16`` and taking the absolute value, and after that multiplying by the direction. - -> **NOTE:** All values are examples of actual commands that I captured on the USB interface, not the only available ones. - - -## GENERAL -### Playing -``` - 60 00 - standard header - 01 - ID - 89 - playing options - 01 - play - 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -``` - -### Stopping/removing (why are they rolled into one?) -``` - 60 00 - standard header - 01 - ID - 89 - playing options - 00 - stop - 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -``` - -## Open: -``` - 60 01 - standard header? - 04 - ? - 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - - (this command doesn't appear every time, odd) - 60 12 - ? - bf 04 00 00 03 b7 1e - ? /* wow, this is different on different wheels? */ - 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - - 60 01 - ? - 05 - ? - 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -``` - -## Close: -``` - 60 01 - standard header? - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -``` - -## Rotation angle: -``` - 60 08 - header plus settings? - 11 - set rotation angle - 55 d5 - angle (between ff ff and 7b 09, one degree is roughly 3c) - 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -``` - -## FF_AUTOCENTER: -``` - 60 08 - header plus settings? - 03 - set autocenter force - 8f 02 - force (between ff ff and 00 00) - 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -``` - -## FF_GAIN: -``` - 60 02 - header plus gain? - bf - gain (between ff and 00) - 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -``` - -## FF_CONSTANT -### Initialization -``` - 60 00 - standard header - 01 - ID - 6a - new constant effect(?) - fe ff - strength *** - 00 00 - attack_length - 00 00 - attack_level *** - 00 00 - fade_length - 00 00 - fade_level *** - 00 4f - ? - f7 17 - time in milliseconds, unsigned - 00 00 - 07 00 - offset from start(?) - 00 ff ff - end of new effect(?) - 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -``` - -### Modifying (For dynamic updating of effects) - -#### Direction: ``N/A`` - -#### Constant force: -``` -60 00 - standard header -01 - ID -0a - modify constant force -05 16 - constant force *** -00 00 00 00 00 00 00 00 00 00 -00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -``` - -#### Envelope: -``` -60 00 - standard header -01 - ID - 31 - modify envelope -84 - ID of envelope attribute ( attack_level 82, - attack_length 81, - fade_level 88, - fade_length 84 ) - 63 04 - value attribute should be set to ?***? - 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -``` - -#### Duration: -``` - 60 00 - standard header - 01 - ID - 49 00 41 - modify timing? - 6c 20 - length in milliseconds - 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -``` - -#### Offset: ``N/A ?`` - -## FF_RAMP -(Why on earth is it this difficult to figure out how a ramp works?) -### Init: -``` - 60 00 - standard header - 01 - ID - 6b - new ramp effect - f6 7f - difference *** - fe ff - level? *** (level = (if end_level > start_level, end_level, else - start_level) - difference?) (signed) - 00 00 - f7 17 - time - 00 80 - some kind of marker - 00 00 - attack_length - f6 7f - attack_level *** - 00 00 - fade_length - f6 7f - fade_level*** - 04 - invert (going "down", 05, going "up", 04) - 4f - f7 17 - time again? - 00 00 - 00 00 - offset - 00 ff ff - end of init - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -``` -### Modifying: -(Sweet baby jesus. I'll figure this out later ``Ramp(?)``) -``` - 60 00 - standard header - 01 - ID - 0e - ??? - 03 - ramp? - e3 04 - difference? - 7d 75 - "level"? - 05 - ???? - 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -``` -#### Envelope: -``` - 60 00 - standard header - 01 - ID - 31 - modify envelope - 84 - ID of envelope attribute ( attack_level 82, - attack_length 81, - fade_level 88, - fade_length 84 ) - 63 04 - value attribute should be set to - 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -``` -#### Duration: -``` - 60 00 - 01 - ID - 4e - ? - 08 - ? - 01 00 - time in milliseconds - 05 - ? - 41 - ? - 01 00 - time in milliseconds - 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -``` -#### Offset: ``N/A ?`` - -## FF_SPRING: -### Init: -``` - 60 00 - standard header - 01 - ID - 64 - new conditional effect - fc 7f - positive coefficient (right?) - fc 7f - negative coefficient (left?) - fe ff - deadband (left?) (deadband + offset) (fe ff means no deadband) - fe ff - deadband (right?) (deadband + offset) - a6 6a a6 6a fe ff fe ff fe ff fe ff df 58 a6 6a 06 - some weird hard-coded - values to do with springs? - - 4f - f7 17 - duration - 00 00 - 00 00 - offset - 00 ff ff - end of init - 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -``` - -### Modifying: -Same as below (FF_DAMPER + FF_FRICTION + FF_INERTIA) - -## FF_DAMPER + FF_FRICTION + FF_INERTIA: -### Init: -``` - 60 00 - standard header - 02 -ID - 64 - new conditional effect - fc 7f - positive coefficient (right?) - fc 7f - negative coefficient (left?) - fe ff - deadband (left?) (deadband + offset) (fe ff means no deadband) - fe ff - deadband (right?) (deadband + offset) - fc 7f fc 7f fe ff fe ff fe ff fe ff fc 7f fc 7f 07 - some weird hard-coded - values to do with friction? - 4f - f7 17 - duration - 00 00 - 00 00 - offset - 00 ff ff - end of init - 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -``` -### Modifying: -``` - positive coefficient: - 60 00 - 02 - ID - 0e 41 - 64 35 - value, signed (between 00 00 and 01 80?) - 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - - negative coefficient: - 60 00 - 02 - ID - 0e 42 - 64 35 - value, signed (between 00 00 and ff 7f?) - 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - - deadband/offset: - 60 00 - 02 - ID - 0e 4c - 64 35 - deadband (right?) (deadband + offset) (fe ff means no deadband) - 00 00 - deadband (left?) (deadband + offset) - 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -``` - -### Duration: -``` - 60 00 - standard header - 01 - ID - 49 06 41 - modify timing? - 6c 20 - length in milliseconds - 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -``` - -### Offset: ``N/A ?`` - -## FF_PERIODIC: -### Init -``` - 60 00 - standard header - 01 - ID - 6b - new periodic effect - 00 00 - magnitude *** - fe ff - offset (up/down) - 00 00 - phase (left/right) - e8 03 - period - 00 80 - 00 00 - attack_length - 00 00 - attack_level *** - 00 00 - fade_length - 00 00 - fade_level *** - 01 - type of periodic ( square 01, - sine 03, - triangle 02, - sawtooth down 05, - sawtooth up 04 ) - 4f - f7 17 - duration - 00 00 - 00 00 - offset - 00 ff ff - end of init - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -``` - -### Modifying: -``` - magnitude: - 60 00 - 02 - ID - 0e 01 - 64 35 - value, signed (between 00 00 and ff 7f?) - 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - - offset of effect: - 60 00 - 02 - ID - 0e 02 - 64 35 - value, signed (between 00 00 and ff 7f?) - 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - - phase: - 60 00 - 02 - ID - 0e 04 - 64 35 - value, signed (between 80 01 and ff 7f?) - 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - - period: - 60 00 - 02 - ID - 0e 08 - 64 35 - value, signed (between 00 00 and I guess ff 7f?) (max value in - wireshark is 0d 07, but I don't know if that's a driver or program issue) - 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - -``` - -### Envelope: -``` - 60 00 - standard header - 01 - ID - 31 - modify envelope - 84 - ID of envelope attribute ( attack_level 82, - attack_length 81, - fade_level 88, - fade_length 84 ) - 63 04 - value attribute should be set to - 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -``` - -### Duration: -``` - Square: - 60 00 - 01 - ID - 49 - ? - 01 - Effect type - 41 - ? - 4e 0c - Time in milliseconds - 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - - Sine: - 60 00 - 01 - ID - 49 - ? - 03 - Effect type - 41 - ? - ec 13 - Time in milliseconds - 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - - Triangle: - 60 00 - 01 - ID - 49 - ? - 02 - Effect type - 41 - ? - 4e 0c - Time in milliseconds - 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - - Sawtooth up: - 60 00 - 01 - ID - 49 - ? - 04 - Effect type - 41 - ? - 4e 0c - Time in milliseconds - 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - - Sawtooth down: - 60 00 - 01 - ID - 49 - ? - 05 - Effect type - 41 - ? - ec 13 - Time in milliseconds - 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -``` - -### Offset: ``N/A ?`` - -## PS4 Input -``` -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 226 /* wheel */ -ff00.0021 = 125 /* wheel */ -ff00.0021 = 255 /* gas */ -ff00.0021 = 255 /* gas */ -ff00.0021 = 255 /* brake */ -ff00.0021 = 255 /* brake */ -ff00.0021 = 255 /* clutch */ -ff00.0021 = 255 /* clutch */ -ff00.0021 = 0 -ff00.0021 = 255 -ff00.0021 = 255 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -``` -- cgit v1.3 From e5a719b530418f26d957e2cae0c56a92539cb324 Mon Sep 17 00:00:00 2001 From: RaySlash Date: Wed, 4 Oct 2023 13:18:29 +1000 Subject: docs: update shields in README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6bad92b..49e4282 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # Linux kernel module for Thrustmaster T300RS, T248 and TX(experimental) wheels > **DISCLAIMER:** The module is now ready for near-stable USE in most force feedback games, supports rangesetting as well as gain and autocentering along with most force feedback effects. While I haven't personally come across any crashes or lockups with this version, I can't promise that they won't occur under any circumstances. -![GitHub last commit (branch)](https://img.shields.io/github/last-commit/Kimplul/hid-tmff2/master) -![GitHub](https://img.shields.io/github/license/Kimplul/hid-tmff2) +![GitHub last commit (master)](https://img.shields.io/github/last-commit/Kimplul/hid-tmff2/master) +![License](https://img.shields.io/github/license/Kimplul/hid-tmff2) ![GitHub contributors](https://img.shields.io/github/contributors/Kimplul/hid-tmff2) -- cgit v1.3 From fc6076449bd39a17fc8210af38c941ece9cf9cc0 Mon Sep 17 00:00:00 2001 From: RaySlash Date: Wed, 4 Oct 2023 14:11:29 +1000 Subject: src, deps, docs: update - Update the effects documents - Rename `hid-tm*` to `tm*` for folders - Move `Makefile` and `Kbuild` to root - Add `deps/` and move in [hid-tminit](https://github.com/scarburato/hid-tminit/commit/9375f6c7d83af5dd6c8b8fe30351d0f36043b20a) --- .gitmodules | 4 +- Kbuild | 2 + Makefile | 16 + deps/tminit | 1 + docs/effects/FFBEFFECTS_T248.md | 7 + docs/effects/FFBEFFECTS_T300RS.md | 541 ++++++++++++ src/Kbuild | 2 - src/Makefile | 16 - src/hid-tminit | 1 - src/hid-tmt248/hid-tmt248.c | 335 -------- src/hid-tmt300rs/hid-tmt300rs.c | 1714 ------------------------------------- src/hid-tmt300rs/hid-tmt300rs.h | 9 - src/hid-tmtx/hid-tmtx.c | 323 ------- src/tmt248/hid-tmt248.c | 335 ++++++++ src/tmt300rs/hid-tmt300rs.c | 1714 +++++++++++++++++++++++++++++++++++++ src/tmt300rs/hid-tmt300rs.h | 9 + src/tmtx/hid-tmtx.c | 323 +++++++ 17 files changed, 2950 insertions(+), 2402 deletions(-) create mode 100644 Kbuild create mode 100644 Makefile create mode 160000 deps/tminit create mode 100644 docs/effects/FFBEFFECTS_T248.md create mode 100644 docs/effects/FFBEFFECTS_T300RS.md delete mode 100644 src/Kbuild delete mode 100644 src/Makefile delete mode 160000 src/hid-tminit delete mode 100644 src/hid-tmt248/hid-tmt248.c delete mode 100644 src/hid-tmt300rs/hid-tmt300rs.c delete mode 100644 src/hid-tmt300rs/hid-tmt300rs.h delete mode 100644 src/hid-tmtx/hid-tmtx.c create mode 100644 src/tmt248/hid-tmt248.c create mode 100644 src/tmt300rs/hid-tmt300rs.c create mode 100644 src/tmt300rs/hid-tmt300rs.h create mode 100644 src/tmtx/hid-tmtx.c diff --git a/.gitmodules b/.gitmodules index 6038e0c..047c0f3 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ -[submodule "src/hid-tminit"] - path = src/hid-tminit +[submodule "deps/tminit"] + path = deps/tminit url = https://github.com/scarburato/hid-tminit diff --git a/Kbuild b/Kbuild new file mode 100644 index 0000000..e33e86f --- /dev/null +++ b/Kbuild @@ -0,0 +1,2 @@ +obj-m := hid-tmff-new.o +hid-tmff-new-y := src/hid-tmff2.o src/tmt300rs/hid-tmt300rs.o src/tmt248/hid-tmt248.o src/tmtx/hid-tmtx.o diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..5e2ab40 --- /dev/null +++ b/Makefile @@ -0,0 +1,16 @@ +KDIR ?= /lib/modules/$(shell uname -r)/build + +all: hid-tminit + $(MAKE) -C $(KDIR) M=$(shell pwd) modules + +install: hid-tminit + $(MAKE) -C $(KDIR) M=$(shell pwd) modules_install + depmod -A + +clean: hid-tminit + $(MAKE) -C $(KDIR) M=$(shell pwd) clean + + +.PHONY: hid-tminit +hid-tminit: + $(MAKE) -C hid-tminit KDIR="$(KDIR)" $(MAKECMDGOALS) diff --git a/deps/tminit b/deps/tminit new file mode 160000 index 0000000..9375f6c --- /dev/null +++ b/deps/tminit @@ -0,0 +1 @@ +Subproject commit 9375f6c7d83af5dd6c8b8fe30351d0f36043b20a diff --git a/docs/effects/FFBEFFECTS_T248.md b/docs/effects/FFBEFFECTS_T248.md new file mode 100644 index 0000000..f43fb55 --- /dev/null +++ b/docs/effects/FFBEFFECTS_T248.md @@ -0,0 +1,7 @@ +# Force Feedback Effects - Thrustmaster T248 + +## Open: +Identical to [T300RS](./FFBEFFECTST300RS) + +## Close: +Identical to [T300RS](./FFBEFFECTST300RS) diff --git a/docs/effects/FFBEFFECTS_T300RS.md b/docs/effects/FFBEFFECTS_T300RS.md new file mode 100644 index 0000000..f076ec7 --- /dev/null +++ b/docs/effects/FFBEFFECTS_T300RS.md @@ -0,0 +1,541 @@ +# Force Feedback Effects - Thrustmaster T300RS (incl. PS3/PS4 modes) +Here I will gather all info I can find out about the different force feedback effects and their USB commands. All values seem to be little-endian. Also, general note, it seems like some effects are affected by the direction of the force, denoted by `***`. As far as I can tell, `fade_level` and `attack_level` values are calculated by mapping the `u16` into `s16` and taking the absolute value, and after that multiplying by the direction. + +> **NOTE:** All values are examples of actual commands that I captured on the USB interface, not the only available ones. + + +## GENERAL +### Playing +``` + 60 00 - standard header + 01 - ID + 89 - playing options + 01 - play + 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +``` + +### Stopping/removing (why are they rolled into one?) +``` + 60 00 - standard header + 01 - ID + 89 - playing options + 00 - stop + 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +``` + +## Open: +``` + 60 01 - standard header? + 04 - ? + 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + + (this command doesn't appear every time, odd) + 60 12 - ? + bf 04 00 00 03 b7 1e - ? /* wow, this is different on different wheels? */ + 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + + 60 01 - ? + 05 - ? + 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +``` + +## Close: +``` + 60 01 - standard header? + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +``` + +## Rotation angle: +``` + 60 08 - header plus settings? + 11 - set rotation angle + 55 d5 - angle (between ff ff and 7b 09, one degree is roughly 3c) + 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +``` + +## FF_AUTOCENTER: +``` + 60 08 - header plus settings? + 03 - set autocenter force + 8f 02 - force (between ff ff and 00 00) + 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +``` + +## FF_GAIN: +``` + 60 02 - header plus gain? + bf - gain (between ff and 00) + 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +``` + +## FF_CONSTANT +### Initialization +``` + 60 00 - standard header + 01 - ID + 6a - new constant effect(?) + fe ff - strength *** + 00 00 - attack_length + 00 00 - attack_level *** + 00 00 - fade_length + 00 00 - fade_level *** + 00 4f - ? + f7 17 - time in milliseconds, unsigned + 00 00 + 07 00 - offset from start(?) + 00 ff ff - end of new effect(?) + 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +``` + +### Modifying (For dynamic updating of effects) + +#### Direction: ``N/A`` + +#### Constant force: +``` +60 00 - standard header +01 - ID +0a - modify constant force +05 16 - constant force *** +00 00 00 00 00 00 00 00 00 00 +00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +``` + +#### Envelope: +``` +60 00 - standard header +01 - ID + 31 - modify envelope +84 - ID of envelope attribute ( attack_level 82, + attack_length 81, + fade_level 88, + fade_length 84 ) + 63 04 - value attribute should be set to ?***? + 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +``` + +#### Duration: +``` + 60 00 - standard header + 01 - ID + 49 00 41 - modify timing? + 6c 20 - length in milliseconds + 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +``` + +#### Offset: `N/A` + +## FF_RAMP +(Why on earth is it this difficult to figure out how a ramp works?) +### Init: +``` + 60 00 - standard header + 01 - ID + 6b - new ramp effect + f6 7f - difference *** + fe ff - level? *** (level = (if end_level > start_level, end_level, else + start_level) - difference?) (signed) + 00 00 + f7 17 - time + 00 80 - some kind of marker + 00 00 - attack_length + f6 7f - attack_level *** + 00 00 - fade_length + f6 7f - fade_level*** + 04 - invert (going "down", 05, going "up", 04) + 4f + f7 17 - time again? + 00 00 + 00 00 - offset + 00 ff ff - end of init + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +``` +### Modifying: +(Sweet baby jesus. I'll figure this out later ``Ramp(?)``) +``` + 60 00 - standard header + 01 - ID + 0e - ??? + 03 - ramp? + e3 04 - difference? + 7d 75 - "level"? + 05 - ???? + 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +``` +#### Envelope: +``` + 60 00 - standard header + 01 - ID + 31 - modify envelope + 84 - ID of envelope attribute ( attack_level 82, + attack_length 81, + fade_level 88, + fade_length 84 ) + 63 04 - value attribute should be set to + 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +``` +#### Duration: +``` + 60 00 + 01 - ID + 4e - ? + 08 - ? + 01 00 - time in milliseconds + 05 - ? + 41 - ? + 01 00 - time in milliseconds + 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +``` +#### Offset: `N/A` + +## FF_SPRING: +### Init: +``` + 60 00 - standard header + 01 - ID + 64 - new conditional effect + fc 7f - positive coefficient (right?) + fc 7f - negative coefficient (left?) + fe ff - deadband (left?) (deadband + offset) (fe ff means no deadband) + fe ff - deadband (right?) (deadband + offset) + a6 6a a6 6a fe ff fe ff fe ff fe ff df 58 a6 6a 06 - some weird hard-coded + values to do with springs? + + 4f + f7 17 - duration + 00 00 + 00 00 - offset + 00 ff ff - end of init + 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +``` + +### Modifying: +Same as below (FF_DAMPER + FF_FRICTION + FF_INERTIA) + +## FF_DAMPER + FF_FRICTION + FF_INERTIA: +### Init: +``` + 60 00 - standard header + 02 -ID + 64 - new conditional effect + fc 7f - positive coefficient (right?) + fc 7f - negative coefficient (left?) + fe ff - deadband (left?) (deadband + offset) (fe ff means no deadband) + fe ff - deadband (right?) (deadband + offset) + fc 7f fc 7f fe ff fe ff fe ff fe ff fc 7f fc 7f 07 - some weird hard-coded + values to do with friction? + 4f + f7 17 - duration + 00 00 + 00 00 - offset + 00 ff ff - end of init + 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +``` +### Modifying: +``` + positive coefficient: + 60 00 + 02 - ID + 0e 41 + 64 35 - value, signed (between 00 00 and 01 80?) + 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + + negative coefficient: + 60 00 + 02 - ID + 0e 42 + 64 35 - value, signed (between 00 00 and ff 7f?) + 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + + deadband/offset: + 60 00 + 02 - ID + 0e 4c + 64 35 - deadband (right?) (deadband + offset) (fe ff means no deadband) + 00 00 - deadband (left?) (deadband + offset) + 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +``` + +### Duration: +``` + 60 00 - standard header + 01 - ID + 49 06 41 - modify timing? + 6c 20 - length in milliseconds + 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +``` + +### Offset: `N/A` + +## FF_PERIODIC: +### Init +``` + 60 00 - standard header + 01 - ID + 6b - new periodic effect + 00 00 - magnitude *** + fe ff - offset (up/down) + 00 00 - phase (left/right) + e8 03 - period + 00 80 + 00 00 - attack_length + 00 00 - attack_level *** + 00 00 - fade_length + 00 00 - fade_level *** + 01 - type of periodic ( square 01, + sine 03, + triangle 02, + sawtooth down 05, + sawtooth up 04 ) + 4f + f7 17 - duration + 00 00 + 00 00 - offset + 00 ff ff - end of init + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +``` + +### Modifying: +``` + magnitude: + 60 00 + 02 - ID + 0e 01 + 64 35 - value, signed (between 00 00 and ff 7f?) + 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + + offset of effect: + 60 00 + 02 - ID + 0e 02 + 64 35 - value, signed (between 00 00 and ff 7f?) + 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + + phase: + 60 00 + 02 - ID + 0e 04 + 64 35 - value, signed (between 80 01 and ff 7f?) + 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + + period: + 60 00 + 02 - ID + 0e 08 + 64 35 - value, signed (between 00 00 and I guess ff 7f?) (max value in + wireshark is 0d 07, but I don't know if that's a driver or program issue) + 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + +``` + +### Envelope: +``` + 60 00 - standard header + 01 - ID + 31 - modify envelope + 84 - ID of envelope attribute ( attack_level 82, + attack_length 81, + fade_level 88, + fade_length 84 ) + 63 04 - value attribute should be set to + 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +``` + +### Duration: +``` + Square: + 60 00 + 01 - ID + 49 - ? + 01 - Effect type + 41 - ? + 4e 0c - Time in milliseconds + 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + + Sine: + 60 00 + 01 - ID + 49 - ? + 03 - Effect type + 41 - ? + ec 13 - Time in milliseconds + 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + + Triangle: + 60 00 + 01 - ID + 49 - ? + 02 - Effect type + 41 - ? + 4e 0c - Time in milliseconds + 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + + Sawtooth up: + 60 00 + 01 - ID + 49 - ? + 04 - Effect type + 41 - ? + 4e 0c - Time in milliseconds + 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + + Sawtooth down: + 60 00 + 01 - ID + 49 - ? + 05 - Effect type + 41 - ? + ec 13 - Time in milliseconds + 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +``` +### Offset: `N/A ?` + +## PS4 Input +``` +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 226 /* wheel */ +ff00.0021 = 125 /* wheel */ +ff00.0021 = 255 /* gas */ +ff00.0021 = 255 /* gas */ +ff00.0021 = 255 /* brake */ +ff00.0021 = 255 /* brake */ +ff00.0021 = 255 /* clutch */ +ff00.0021 = 255 /* clutch */ +ff00.0021 = 0 +ff00.0021 = 255 +ff00.0021 = 255 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +ff00.0021 = 0 +``` diff --git a/src/Kbuild b/src/Kbuild deleted file mode 100644 index b8541e0..0000000 --- a/src/Kbuild +++ /dev/null @@ -1,2 +0,0 @@ -obj-m := hid-tmff-new.o -hid-tmff-new-y := hid-tmff2.o hid-tmt300rs/hid-tmt300rs.o hid-tmt248/hid-tmt248.o hid-tmtx/hid-tmtx.o diff --git a/src/Makefile b/src/Makefile deleted file mode 100644 index 5e2ab40..0000000 --- a/src/Makefile +++ /dev/null @@ -1,16 +0,0 @@ -KDIR ?= /lib/modules/$(shell uname -r)/build - -all: hid-tminit - $(MAKE) -C $(KDIR) M=$(shell pwd) modules - -install: hid-tminit - $(MAKE) -C $(KDIR) M=$(shell pwd) modules_install - depmod -A - -clean: hid-tminit - $(MAKE) -C $(KDIR) M=$(shell pwd) clean - - -.PHONY: hid-tminit -hid-tminit: - $(MAKE) -C hid-tminit KDIR="$(KDIR)" $(MAKECMDGOALS) diff --git a/src/hid-tminit b/src/hid-tminit deleted file mode 160000 index 9375f6c..0000000 --- a/src/hid-tminit +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 9375f6c7d83af5dd6c8b8fe30351d0f36043b20a diff --git a/src/hid-tmt248/hid-tmt248.c b/src/hid-tmt248/hid-tmt248.c deleted file mode 100644 index af3aaa9..0000000 --- a/src/hid-tmt248/hid-tmt248.c +++ /dev/null @@ -1,335 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -#include -#include -#include "../hid-tmff2.h" - -#define T248_MAX_EFFECTS 16 -#define T248_BUFFER_LENGTH 63 - -static const u8 setup_0[64] = { 0x42, 0x01 }; -static const u8 setup_1[64] = { 0x0a, 0x04, 0x90, 0x03 }; -static const u8 setup_2[64] = { 0x0a, 0x04, 0x00, 0x0c }; -static const u8 setup_3[64] = { 0x0a, 0x04, 0x12, 0x10 }; -static const u8 setup_4[64] = { 0x0a, 0x04, 0x00, 0x06 }; -static const u8 setup_5[64] = { 0x0a, 0x04, 0x00, 0x0e }; -static const u8 setup_6[64] = { 0x0a, 0x04, 0x00, 0x0e, 0x01 }; -static const u8 *const setup_arr[] = { setup_0, setup_1, setup_2, setup_3, setup_4, setup_5, setup_6 }; -static const unsigned int setup_arr_sizes[] = { - ARRAY_SIZE(setup_0), - ARRAY_SIZE(setup_1), - ARRAY_SIZE(setup_2), - ARRAY_SIZE(setup_3), - ARRAY_SIZE(setup_4), - ARRAY_SIZE(setup_5), - ARRAY_SIZE(setup_6) -}; - -static const unsigned long t248_params = - PARAM_SPRING_LEVEL - | PARAM_DAMPER_LEVEL - | PARAM_FRICTION_LEVEL - | PARAM_RANGE - | PARAM_GAIN - ; - -static const signed short t248_effects[] = { - FF_CONSTANT, - FF_RAMP, - FF_SPRING, - FF_DAMPER, - FF_FRICTION, - FF_INERTIA, - FF_PERIODIC, - FF_SINE, - FF_TRIANGLE, - FF_SQUARE, - FF_SAW_UP, - FF_SAW_DOWN, - FF_AUTOCENTER, - FF_GAIN, - -1 -}; - -/* TODO: sort through this stuff */ -static u8 t248_pc_rdesc_fixed[] = { - 0x05, 0x01, /* Usage page (Generic Desktop) */ - 0x09, 0x04, /* Usage (Joystick) */ - 0xa1, 0x01, /* Collection (Application) */ - 0x09, 0x01, /* Usage (Pointer) */ - 0xa1, 0x00, /* Collection (Physical) */ - 0x85, 0x07, /* Report ID (7) */ - 0x09, 0x30, /* Usage (X) */ - 0x15, 0x00, /* Logical minimum (0) */ - 0x27, 0xff, 0xff, 0x00, 0x00, /* Logical maximum (65535) */ - 0x35, 0x00, /* Physical minimum (0) */ - 0x47, 0xff, 0xff, 0x00, 0x00, /* Physical maximum (65535) */ - 0x75, 0x10, /* Report size (16) */ - 0x95, 0x01, /* Report count (1) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - 0x09, 0x31, /* Usage (Y) TODO: clutch? */ - 0x26, 0xff, 0x03, /* Logical maximum (1023) */ - 0x46, 0xff, 0x03, /* Physical maximum (1023) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - 0x09, 0x35, /* Usage (Rz) TODO: brake? */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - 0x09, 0x36, /* Usage (Slider) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - 0x75, 0x08, /* Report size (8) */ - 0x26, 0xff, 0x00, /* Logical maximum (255) */ - 0x46, 0xff, 0x00, /* Physical maximum (255) */ - 0x09, 0x40, /* Usage (Vx) TODO: what is this? */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - 0x09, 0x41, /* Usage (Vy) TODO: --||-- */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - 0x09, 0x33, /* Usage (Rx) TODO: --||-- */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - 0x09, 0x34, /* Usage (Ry) TODO: --||-- */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - 0x09, 0x32, /* Usage (Z) TODO: --||-- (gas?) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - 0x09, 0x37, /* Usage (Dial) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - 0x05, 0x09, /* Usage page (Button) */ - 0x19, 0x01, /* Usage minimum (1) */ - 0x29, 0x1a, /* Usage maximum (13) */ - 0x25, 0x01, /* Logical maximum (1) */ - 0x45, 0x01, /* Physical maximum (1) */ - 0x75, 0x01, /* Report size (1) */ - 0x95, 0x1a, /* Report count (26) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - 0x75, 0x06, /* Report size (6) */ - 0x95, 0x01, /* Report count (1) */ - 0x81, 0x03, /* Usage (Variable, Absolute, Constant) */ - 0x05, 0x01, /* Usage page (Generic Desktop) */ - 0x09, 0x39, /* Usage (Hat Switch) */ - 0x25, 0x07, /* Logical maximum (7) */ - 0x46, 0x3b, 0x01, /* Physical maximum (315) */ - 0x55, 0x00, /* Unit exponent (0) */ - 0x65, 0x14, /* Unit (Eng rot, Angular Pos) */ - 0x75, 0x04, /* Report size (4) */ - 0x81, 0x42, /* Input (Variable, Absolute, NullState) */ - 0x65, 0x00, /* Input (None) */ - 0x81, 0x03, /* Input (Variable, Absolute, Constant) */ - 0x85, 0x60, /* Report ID (96), prev 10 */ - 0x06, 0x00, 0xff, /* Usage page (Vendor 1) */ - 0x09, 0x60, /* Usage (96), prev 10 */ - 0x75, 0x08, /* Report size (8) */ - 0x95, 0x3f, /* Report count (63) */ - 0x26, 0xff, 0x00, /* Logical maximum (256) */ - 0x46, 0xff, 0x00, /* Physical maximum (256) */ - 0x91, 0x02, /* Output (Variable, Absolute) */ - 0x85, 0x02, /* Report ID (2) */ - 0x09, 0x02, /* Usage (2) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - 0x09, 0x14, /* Usage (20) */ - 0x85, 0x14, /* Report ID (20) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - 0xc0, /* End collection */ - 0xc0, /* End collection */ -}; - -static int t248_interrupts(struct t300rs_device_entry *t248) -{ - u8 *send_buf = kmalloc(256, GFP_KERNEL); - struct usb_interface *usbif = to_usb_interface(t248->hdev->dev.parent); - struct usb_host_endpoint *ep; - int ret, trans, b_ep, i; - - if (!send_buf) { - hid_err(t248->hdev, "failed allocating send buffer\n"); - return -ENOMEM; - } - - ep = &usbif->cur_altsetting->endpoint[1]; - b_ep = ep->desc.bEndpointAddress; - - for (i = 0; i < ARRAY_SIZE(setup_arr); ++i) { - memcpy(send_buf, setup_arr[i], setup_arr_sizes[i]); - - ret = usb_interrupt_msg(t248->usbdev, - usb_sndintpipe(t248->usbdev, b_ep), - send_buf, setup_arr_sizes[i], - &trans, - USB_CTRL_SET_TIMEOUT); - - if (ret) { - hid_err(t248->hdev, "setup data couldn't be sent\n"); - goto err; - } - } - -err: - kfree(send_buf); - return ret; -} - -int t248_wheel_destroy(void *data) -{ - struct t300rs_device_entry *t300rs = data; - - if (!t300rs) - return -ENODEV; - - kfree(t300rs->send_buffer); - kfree(t300rs); - return 0; -} - -int t248_set_range(void *data, uint16_t value) -{ - struct t300rs_device_entry *t248 = data; - - if (value < 140) { - hid_info(t248->hdev, "value %i too small, clamping to 140\n", value); - value = 140; - } - - if (value > 900) { - hid_info(t248->hdev, "value %i too large, clamping to 900\n", value); - value = 900; - } - - return t300rs_set_range(data, value); -} - -static int t248_send_open(struct t300rs_device_entry *t248) -{ - int r1, r2; - t248->send_buffer[0] = 0x01; - t248->send_buffer[1] = 0x04; - if ((r1 = t300rs_send_int(t248))) - return r1; - - t248->send_buffer[0] = 0x01; - t248->send_buffer[1] = 0x05; - if ((r2 = t300rs_send_int(t248))) - return r2; - - return 0; -} - -static int t248_open(void *data, int open_mode) -{ - struct t300rs_device_entry *t248 = data; - - if (!t248) - return -ENODEV; - - if (open_mode) - t248_send_open(t248); - - return t248->open(t248->input_dev); -} - -static int t248_send_close(struct t300rs_device_entry *t248) -{ - int r1, r2; - t248->send_buffer[0] = 0x01; - t248->send_buffer[1] = 0x05; - if ((r1 = t300rs_send_int(t248))) - return r1; - - t248->send_buffer[0] = 0x01; - t248->send_buffer[1] = 0x00; - if ((r2 = t300rs_send_int(t248))) - return r2; - - return 0; -} - -static int t248_close(void *data, int open_mode) -{ - struct t300rs_device_entry *t248 = data; - - if (!t248) - return -ENODEV; - - if (open_mode) - t248_send_close(t248); - - t248->close(t248->input_dev); - return 0; -} - -int t248_wheel_init(struct tmff2_device_entry *tmff2, int open_mode) -{ - struct t300rs_device_entry *t248 = kzalloc(sizeof(struct t300rs_device_entry), GFP_KERNEL); - struct list_head *report_list; - int ret; - - - if (!t248) { - ret = -ENOMEM; - goto t248_err; - } - - t248->hdev = tmff2->hdev; - t248->input_dev = tmff2->input_dev; - t248->usbdev = to_usb_device(tmff2->hdev->dev.parent->parent); - t248->buffer_length = T248_BUFFER_LENGTH; - - t248->send_buffer = kzalloc(t248->buffer_length, GFP_KERNEL); - if (!t248->send_buffer) { - ret = -ENOMEM; - goto send_err; - } - - report_list = &t248->hdev->report_enum[HID_OUTPUT_REPORT].report_list; - t248->report = list_entry(report_list->next, struct hid_report, list); - t248->ff_field = t248->report->field[0]; - - t248->open = t248->input_dev->open; - t248->close = t248->input_dev->close; - - if ((ret = t248_interrupts(t248))) - goto interrupt_err; - - /* everything went OK */ - tmff2->data = t248; - tmff2->params = t248_params; - tmff2->max_effects = T248_MAX_EFFECTS; - memcpy(tmff2->supported_effects, t248_effects, sizeof(t248_effects)); - - if (!open_mode) - t248_send_open(t248); - - hid_info(t248->hdev, "force feedback for T248\n"); - return 0; - -interrupt_err: -send_err: - kfree(t248); -t248_err: - hid_err(tmff2->hdev, "failed initializing T248\n"); - return ret; -} - -static __u8 *t248_wheel_fixup(struct hid_device *hdev, __u8 *rdesc, - unsigned int *rsize) -{ - rdesc = t248_pc_rdesc_fixed; - *rsize = sizeof(t248_pc_rdesc_fixed); - return rdesc; -} - -int t248_populate_api(struct tmff2_device_entry *tmff2) -{ - tmff2->play_effect = t300rs_play_effect; - tmff2->upload_effect = t300rs_upload_effect; - tmff2->update_effect = t300rs_update_effect; - tmff2->stop_effect = t300rs_stop_effect; - - tmff2->set_gain = t300rs_set_gain; - tmff2->set_autocenter = t300rs_set_autocenter; - /* T248 only has 900 degree range, instead of T300RS 1080 */ - tmff2->set_range = t248_set_range; - tmff2->wheel_fixup = t248_wheel_fixup; - - tmff2->open = t248_open; - tmff2->close = t248_close; - - tmff2->wheel_init = t248_wheel_init; - tmff2->wheel_destroy = t248_wheel_destroy; - - return 0; -} diff --git a/src/hid-tmt300rs/hid-tmt300rs.c b/src/hid-tmt300rs/hid-tmt300rs.c deleted file mode 100644 index 20bb239..0000000 --- a/src/hid-tmt300rs/hid-tmt300rs.c +++ /dev/null @@ -1,1714 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -#include -#include -#include "../hid-tmff2.h" - -#define T300RS_MAX_EFFECTS 16 -#define T300RS_NORM_BUFFER_LENGTH 63 -#define T300RS_PS4_BUFFER_LENGTH 31 - -#define T300RS_DEFAULT_ATTACHMENT 0x06 -#define T300RS_F1_ATTACHMENT 0x03 - -static const unsigned long t300rs_params = - PARAM_SPRING_LEVEL - | PARAM_DAMPER_LEVEL - | PARAM_FRICTION_LEVEL - | PARAM_GAIN - | PARAM_RANGE - | PARAM_ALT_MODE - ; - -static const signed short t300rs_effects[] = { - FF_CONSTANT, - FF_RAMP, - FF_SPRING, - FF_DAMPER, - FF_FRICTION, - FF_INERTIA, - FF_PERIODIC, - FF_SINE, - FF_TRIANGLE, - FF_SQUARE, - FF_SAW_UP, - FF_SAW_DOWN, - FF_AUTOCENTER, - FF_GAIN, - -1 -}; - -struct __packed t300rs_fw_response { - uint8_t unused1[2]; - uint8_t fw_version; - uint8_t unused2; -}; - - -struct __packed t300rs_packet_header { - uint8_t zero1; - uint8_t id; - uint8_t code; -}; - -struct __packed t300rs_setup_header { - uint8_t cmd; - uint8_t code; -}; - -struct __packed t300rs_packet_envelope { - uint16_t attack_length; - uint16_t attack_level; - uint16_t fade_length; - uint16_t fade_level; -}; - -struct __packed t300rs_packet_timing { - uint8_t start_marker; - uint16_t duration; - uint8_t zero1[2]; - uint16_t offset; - uint8_t zero2; - uint16_t end_marker; -}; - -struct usb_ctrlrequest t300rs_fw_request = { - .bRequestType = 0xc1, - .bRequest = 86, - .wValue = 0, - .wIndex = 0, - .wLength = 8 -}; - -struct t300rs_data { - unsigned long quirks; - void *device_props; -}; - -static u8 t300rs_rdesc_nrm_fixed[] = { - 0x05, 0x01, /* Usage page (Generic Desktop) */ - 0x09, 0x04, /* Usage (Joystick) */ - 0xa1, 0x01, /* Collection (Application) */ - 0x09, 0x01, /* Usage (Pointer) */ - 0xa1, 0x00, /* Collection (Physical) */ - 0x85, 0x07, /* Report ID (7) */ - 0x09, 0x30, /* Usage (X) */ - 0x15, 0x00, /* Logical minimum (0) */ - 0x27, 0xff, 0xff, 0x00, 0x00, /* Logical maximum (65535) */ - 0x35, 0x00, /* Physical minimum (0) */ - 0x47, 0xff, 0xff, 0x00, 0x00, /* Physical maximum (65535) */ - 0x75, 0x10, /* Report size (16) */ - 0x95, 0x01, /* Report count (1) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - 0x09, 0x35, /* Usage (Rz) (Brake) */ - 0x26, 0xff, 0x03, /* Logical maximum (1023) */ - 0x46, 0xff, 0x03, /* Physical maximum (1023) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - 0x09, 0x32, /* Usage (Z) (Gas) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - 0x09, 0x31, /* Usage (Y) (Clutch) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - 0x81, 0x03, /* Input (Variable, Absolute, Constant) */ - 0x05, 0x09, /* Usage page (Button) */ - 0x19, 0x01, /* Usage minimum (1) */ - 0x29, 0x0d, /* Usage maximum (13) */ - 0x25, 0x01, /* Logical maximum (1) */ - 0x45, 0x01, /* Physical maximum (1) */ - 0x75, 0x01, /* Report size (1) */ - 0x95, 0x0d, /* Report count (13) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - 0x75, 0x0b, /* Report size (13) */ - 0x95, 0x01, /* Report count (1) */ - 0x81, 0x03, /* Usage (Variable, Absolute, Constant) */ - 0x05, 0x01, /* Usage page (Generic Desktop) */ - 0x09, 0x39, /* Usage (Hat Switch) */ - 0x25, 0x07, /* Logical maximum (7) */ - 0x46, 0x3b, 0x01, /* Physical maximum (315) */ - 0x55, 0x00, /* Unit exponent (0) */ - 0x65, 0x14, /* Unit (Eng Rot, Angular Pos) */ - 0x75, 0x04, /* Report size (4) */ - 0x81, 0x42, /* Input (Variable, Absolute, NullState) */ - 0x65, 0x00, /* Unit (None) */ - 0x81, 0x03, /* Input (Variable, Absolute, Constant) */ - 0x85, 0x60, /* Report ID (96), prev 10 */ - 0x06, 0x00, 0xff, /* Usage page (Vendor 1) */ - 0x09, 0x60, /* Usage (96), prev 10 */ - 0x75, 0x08, /* Report size (8) */ - 0x95, 0x3f, /* Report count (63) */ - 0x26, 0xff, 0x7f, /* Logical maximum (32767) */ - 0x15, 0x00, /* Logical minimum (0) */ - 0x46, 0xff, 0x7f, /* Physical maximum (32767) */ - 0x36, 0x00, 0x80, /* Physical minimum (-32768) */ - 0x91, 0x02, /* Output (Variable, Absolute) */ - 0x85, 0x02, /* Report ID (2) */ - 0x09, 0x02, /* Usage (2) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - 0x09, 0x14, /* Usage (20) */ - 0x85, 0x14, /* Report ID (20) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - 0xc0, /* End collection */ - 0xc0, /* End collection */ -}; - -static u8 t300rs_rdesc_adv_fixed[] = { - 0x05, 0x01, /* Usage page (Generic Desktop) */ - 0x09, 0x04, /* Usage (Joystick) */ - 0xa1, 0x01, /* Collection (Application) */ - 0x09, 0x01, /* Usage (Pointer) */ - 0xa1, 0x00, /* Collection (Physical) */ - 0x85, 0x07, /* Report ID (7) */ - 0x09, 0x30, /* Usage (X) */ - 0x15, 0x00, /* Logical minimum (0) */ - 0x27, 0xff, 0xff, 0x00, 0x00, /* Logical maximum (65535) */ - 0x35, 0x00, /* Physical minimum (0) */ - 0x47, 0xff, 0xff, 0x00, 0x00, /* Physical maximum (65535) */ - 0x75, 0x10, /* Report size (16) */ - 0x95, 0x01, /* Report count (1) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - 0x09, 0x31, /* Usage (Y) */ - 0x26, 0xff, 0x03, /* Logical maximum (1023) */ - 0x46, 0xff, 0x03, /* Physical maximum (1023) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - 0x09, 0x35, /* Usage (Rz) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - 0x09, 0x36, /* Usage (Slider) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - 0x81, 0x03, /* Input (Variable, Absolute, Constant) ? */ - 0x05, 0x09, /* Usage page (Button) */ - 0x19, 0x01, /* Usage minimum (1) */ - 0x29, 0x19, /* Usage maximum (25) */ - 0x25, 0x01, /* Logical maximum (1) */ - 0x45, 0x01, /* Physical maximum (1) */ - 0x75, 0x01, /* Report size (1) */ - 0x95, 0x19, /* Report count (25) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - 0x75, 0x03, /* Report size (3) */ - 0x95, 0x01, /* Report count (1) */ - 0x81, 0x03, /* Input (Variable, Absolute, Constant) */ - 0x05, 0x01, /* Usage page (Generic Desktop) */ - 0x09, 0x39, /* Usage (Hat Switch) */ - 0x25, 0x07, /* Logical maximum (7) */ - 0x46, 0x3b, 0x01, /* Physical maximum (315) */ - 0x55, 0x00, /* Unit exponent (0) */ - 0x65, 0x14, /* Unit (Eng Rot, Angular Pos) */ - 0x75, 0x04, /* Report size (4) */ - 0x81, 0x42, /* Input (Variable, Absolute, NullState) */ - 0x65, 0x00, /* Unit (None) */ - 0x85, 0x60, /* Report ID (96), prev 10 */ - 0x06, 0x00, 0xff, /* Usage page (Vendor 1) */ - 0x09, 0x60, /* Usage (96), prev 10 (why?) */ - 0x75, 0x08, /* Report size (8) */ - 0x95, 0x3f, /* Report count (63) */ - 0x26, 0xff, 0x00, /* Logical maximum (255) */ - 0x46, 0xff, 0x00, /* Physical maximum (255) */ - 0x91, 0x02, /* Output (Variable, Absolute) */ - 0x85, 0x02, /* Report ID (2) */ - 0x09, 0x02, /* Usage (Mouse) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - 0x09, 0x14, /* Usage (20) */ - 0x85, 0x14, /* Report ID (20) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - 0xc0, /* End collection */ - 0xc0, /* End collection */ -}; - -static u8 t300rs_rdesc_ps4_fixed[] = { - 0x05, 0x01, /* Usage page (Generic Desktop) */ - 0x09, 0x05, /* Usage (GamePad) */ - 0xa1, 0x01, /* Collection (Application) */ - 0x85, 0x01, /* Report ID (1) */ - 0x09, 0x00, /* Usage (U) (was X) */ - 0x09, 0x00, /* Usage (U) (was Y) */ - 0x09, 0x00, /* Usage (U) (was Z) */ - 0x09, 0x00, /* Usage (U) (was Rz)*/ - 0x15, 0x00, /* Logical minimum (0) */ - 0x26, 0xff, 0x00, /* Logical maximum (255) */ - 0x75, 0x08, /* Report size (8) */ - 0x95, 0x04, /* Report count (4) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - 0x09, 0x39, /* Usage (Hat Switch) */ - 0x15, 0x00, /* Logical minimum (0) */ - 0x25, 0x07, /* Logical maximum (7)*/ - 0x35, 0x00, /* Physical minimum (0) */ - 0x46, 0x3b, 0x01, /* Physical maximum (315) */ - 0x65, 0x14, /* Unit (Eng Rot, Angular Pos) */ - 0x75, 0x04, /* Report size (4) */ - 0x95, 0x01, /* Report count (1) */ - 0x81, 0x42, /* Input (Variable, Absolute, NullState) */ - 0x65, 0x00, /* Input (None) */ - 0x05, 0x09, /* Usage page (Button) */ - 0x19, 0x01, /* Usage minimum (1) */ - 0x29, 0x0e, /* Usage maximum (14) */ - 0x15, 0x00, /* Logical minimum (0) */ - 0x25, 0x01, /* Logical maximum (1) */ - 0x75, 0x01, /* Report size (1) */ - 0x95, 0x0e, /* Report size (14) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - 0x06, 0x00, 0xff, /* Usage page (Vendor 1) */ - 0x09, 0x20, /* Usage (32) */ - 0x75, 0x06, /* Report size (6) */ - 0x95, 0x01, /* Report count (1) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - 0x05, 0x01, /* Usage page (Generic Desktop) */ - 0x09, 0x00, /* Usage (U) (was Rx)*/ - 0x09, 0x00, /* Usage (U) (was Ry) */ - 0x15, 0x00, /* Logical minimum (0) */ - 0x26, 0xff, 0x00, /* Logical maximum (255) */ - 0x75, 0x08, /* Report size (8) */ - 0x95, 0x02, /* Report count (2) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - 0x05, 0x01, /* Usage page (Vendor 1) */ - /* constant zero? */ - 0x09, 0x00, /* Usage (33) */ - 0x95, 0x21, /* Report count (33) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - /* wheel */ - 0x09, 0x30, /* Usage (X) */ - 0x15, 0x00, /* Logical minimum (0) */ - 0x27, 0xff, 0xff, 0x00, 0x00, /* Logical maximum (65535) */ - 0x35, 0x00, /* Physical minimum (0) */ - 0x47, 0xff, 0xff, 0x00, 0x00, /* Physical maximum (65535) */ - 0x75, 0x10, /* Report size (16) */ - 0x95, 0x01, /* Report count (1) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - /* gas */ - 0x09, 0x31, /* Usage (Y) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - /* brake */ - 0x09, 0x32, /* Usage (Z) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - /* clutch */ - 0x09, 0x35, /* Usage (Rz) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - /* stick shifter (check model no) */ - 0x05, 0x09, /* Usage page (Button) */ - 0x19, 0x0f, /* Usage minimum (15) */ - 0x29, 0x17, /* Usage maximum (23) */ - 0x15, 0x00, /* Logical minimum (1) */ - 0x25, 0x01, /* Logical maximum (1) */ - 0x75, 0x01, /* Report size (1) */ - 0x95, 0x08, /* Report count (8) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - /* no clue */ - 0x05, 0x01, /* Usage page (Generic Desktop) */ - 0x09, 0x00, /* Usage (U) */ - 0x75, 0x08, /* Report size (8) */ - 0x95, 0x0c, /* Report count (12) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - /* continue unmodified */ - 0x06, 0x00, 0xff, /* Usage page (Vendor defined 1) */ - 0x85, 0x60, /* Report ID (5) (change to 0x60?) */ - 0x09, 0x60, /* Usage (34) (change to 0x60?) */ - 0x95, 0x1f, /* Report count (31) () */ - 0x91, 0x02, /* Output (Variable, Absolute) */ - 0x85, 0x03, /* Report ID (3) */ - 0x0a, 0x21, 0x27, /* ??? */ - 0x95, 0x2f, /* Report count (47) */ - 0xb1, 0x02, /* Feature (Data, Var, Abs) */ - 0xc0, /* End collection */ - - /* From here on out no clue */ - 0x06, 0xf0, - 0xff, 0x09, - 0x40, 0xa1, - 0x01, 0x85, - 0xf0, 0x09, - 0x47, 0x95, - 0x3f, 0xb1, - 0x02, 0x85, - 0xf1, 0x09, - 0x48, 0x95, - 0x3f, 0xb1, - 0x02, 0x85, - 0xf2, 0x09, - 0x49, 0x95, - 0x0f, 0xb1, - 0x02, 0x85, - 0xf3, 0x0a, - 0x01, 0x47, - 0x95, 0x07, - 0xb1, 0x02, - 0xc0, -}; - -static u8 spring_values[] = { - 0xa6, 0x6a, 0xa6, 0x6a, 0xfe, - 0xff, 0xfe, 0xff, 0xfe, 0xff, - 0xfe, 0xff, 0xdf, 0x58, 0xa6, - 0x6a, 0x06 -}; - -static u8 damper_values[] = { - 0xfc, 0x7f, 0xfc, 0x7f, 0xfe, - 0xff, 0xfe, 0xff, 0xfe, 0xff, - 0xfe, 0xff, 0xfc, 0x7f, 0xfc, - 0x7f, 0x07 -}; - -int t300rs_send_buf(struct t300rs_device_entry *t300rs, u8 *send_buffer, size_t len) -{ - int i; - /* check that send_buffer fits into our report */ - if (len > t300rs->buffer_length) - return -EINVAL; - - /* fill with actual data */ - for (i = 0; i < len; ++i) - t300rs->ff_field->value[i] = send_buffer[i]; - - /* fill the rest with zeroes */ - for (i = len; i < t300rs->buffer_length; ++i) - t300rs->ff_field->value[i] = 0; - - hid_hw_request(t300rs->hdev, t300rs->report, HID_REQ_SET_REPORT); - return 0; -} - -int t300rs_send_int(struct t300rs_device_entry *t300rs) -{ - t300rs_send_buf(t300rs, t300rs->send_buffer, t300rs->buffer_length); - memset(t300rs->send_buffer, 0, t300rs->buffer_length); - - return 0; -} - -static void t300rs_fill_header(struct t300rs_packet_header *packet_header, - uint8_t id, uint8_t code) -{ - packet_header->id = id + 1; - packet_header->code = code; -} - -int t300rs_play_effect(void *data, struct tmff2_effect_state *state) -{ - struct t300rs_device_entry *t300rs = data; - struct __packed t300rs_packet_play { - struct t300rs_packet_header header; - uint8_t value; - } *play_packet = (struct t300rs_packet_play *)t300rs->send_buffer; - - int ret; - - - t300rs_fill_header(&play_packet->header, state->effect.id, 0x89); - play_packet->value = 0x01; - - ret = t300rs_send_int(t300rs); - if (ret) - hid_err(t300rs->hdev, "failed starting effect play\n"); - - return ret; -} - -int t300rs_stop_effect(void *data, struct tmff2_effect_state *state) -{ - struct t300rs_device_entry *t300rs = data; - struct __packed t300rs_packet_stop { - struct t300rs_packet_header header; - uint8_t value; - } *stop_packet = (struct t300rs_packet_stop *)t300rs->send_buffer; - - int ret; - - - t300rs_fill_header(&stop_packet->header, state->effect.id, 0x89); - - ret = t300rs_send_int(t300rs); - if (ret) - hid_err(t300rs->hdev, "failed stopping effect play\n"); - - return ret; -} - -static void t300rs_fill_envelope(struct t300rs_packet_envelope *packet_envelope, - int16_t level, uint16_t duration, struct ff_envelope *envelope) -{ - uint16_t attack_length = (duration * envelope->attack_length) / 0x7fff; - uint16_t attack_level = (level * envelope->attack_level) / 0x7fff; - uint16_t fade_length = (duration * envelope->fade_length) / 0x7fff; - uint16_t fade_level = (level * envelope->fade_level) / 0x7fff; - - packet_envelope->attack_length = cpu_to_le16(attack_length); - packet_envelope->attack_level = cpu_to_le16(attack_level); - packet_envelope->fade_length = cpu_to_le16(fade_length); - packet_envelope->fade_level = cpu_to_le16(fade_level); -} - -static void t300rs_fill_timing(struct t300rs_packet_timing *packet_timing, - uint16_t duration, uint16_t offset){ - packet_timing->start_marker = 0x4f; - - packet_timing->duration = cpu_to_le16(duration); - packet_timing->offset = cpu_to_le16(offset); - - packet_timing->end_marker = 0xffff; -} - -static int t300rs_update_envelope(struct t300rs_device_entry *t300rs, - struct tmff2_effect_state *state, - int16_t level, - uint16_t duration, - uint8_t id, - struct ff_envelope envelope, - struct ff_envelope envelope_old - ) -{ - struct __packed t300rs_packet_mod_envelope { - struct t300rs_packet_header header; - uint8_t attribute; - uint16_t value; - } *packet_mod_envelope = (struct t300rs_packet_mod_envelope *)t300rs->send_buffer; - - uint16_t attack_length, attack_level, fade_length, fade_level; - int ret = 0; - - duration = duration - 1; - - attack_length = (duration * envelope.attack_length) / 0x7fff; - attack_level = (level * envelope.attack_level) / 0x7fff; - fade_length = (duration * envelope.fade_length) / 0x7fff; - fade_level = (level * envelope.fade_level) / 0x7fff; - - if (envelope.attack_length != envelope_old.attack_length) { - t300rs_fill_header(&packet_mod_envelope->header, id, 0x31); - packet_mod_envelope->attribute = 0x81; - packet_mod_envelope->value = cpu_to_le16(attack_length); - - ret = t300rs_send_int(t300rs); - if (ret) { - hid_err(t300rs->hdev, "failed modifying effect envelope\n"); - goto error; - } - } - - if (envelope.attack_level != envelope_old.attack_level) { - t300rs_fill_header(&packet_mod_envelope->header, id, 0x31); - packet_mod_envelope->attribute = 0x82; - packet_mod_envelope->value = cpu_to_le16(attack_level); - - ret = t300rs_send_int(t300rs); - if (ret) { - hid_err(t300rs->hdev, "failed modifying effect envelope\n"); - goto error; - } - } - - if (envelope.fade_length != envelope_old.fade_length) { - t300rs_fill_header(&packet_mod_envelope->header, id, 0x31); - packet_mod_envelope->attribute = 0x84; - packet_mod_envelope->value = cpu_to_le16(fade_length); - - ret = t300rs_send_int(t300rs); - if (ret) { - hid_err(t300rs->hdev, "failed modifying effect envelope\n"); - goto error; - } - } - - if (envelope.fade_level != envelope_old.fade_level) { - t300rs_fill_header(&packet_mod_envelope->header, id, 0x31); - packet_mod_envelope->attribute = 0x88; - packet_mod_envelope->value = cpu_to_le16(fade_level); - - ret = t300rs_send_int(t300rs); - if (ret) { - hid_err(t300rs->hdev, "failed modifying effect envelope\n"); - goto error; - } - } - -error: - return ret; -} - -static int t300rs_update_simple_duration(struct t300rs_device_entry *t300rs, - struct tmff2_effect_state *state, unsigned type) -{ - struct ff_effect effect = state->effect; - struct __packed t300rs_packet_mod_duration { - struct t300rs_packet_header header; - uint16_t marker; - uint16_t duration; - } *packet_mod_duration = (struct t300rs_packet_mod_duration *)t300rs->send_buffer; - - uint16_t duration; - int ret = 0; - - duration = effect.replay.length - 1; - - t300rs_fill_header(&packet_mod_duration->header, effect.id, 0x49); - packet_mod_duration->marker = cpu_to_le16(0x4100 + type); - packet_mod_duration->duration = cpu_to_le16(duration); - - ret = t300rs_send_int(t300rs); - if (ret) { - hid_err(t300rs->hdev, "failed modifying duration\n"); - goto error; - } - -error: - return ret; -} - -static int t300rs_update_periodic_duration(struct t300rs_device_entry *t300rs, - struct tmff2_effect_state *state, unsigned type) -{ - struct ff_effect effect = state->effect; - struct __packed t300rs_packet_mod_duration { - struct t300rs_packet_header header; - uint8_t type; - uint8_t marker; - uint16_t duration; - } *packet_mod_duration = (struct t300rs_packet_mod_duration *)t300rs->send_buffer; - - uint16_t duration; - int ret = 0; - - duration = effect.replay.length - 1; - - t300rs_fill_header(&packet_mod_duration->header, effect.id, 0x49); - packet_mod_duration->type = type; - packet_mod_duration->marker = 0x41; - packet_mod_duration->duration = cpu_to_le16(duration); - - ret = t300rs_send_int(t300rs); - if (ret) { - hid_err(t300rs->hdev, "failed modifying duration\n"); - goto error; - } - -error: - return ret; -} - -static int t300rs_update_ramp_duration(struct t300rs_device_entry *t300rs, - struct tmff2_effect_state *state) -{ - struct ff_effect effect = state->effect; - struct __packed t300rs_packet_mod_duration { - struct t300rs_packet_header header; - uint8_t marker0; - uint16_t duration0; - uint8_t marker1; - uint8_t marker2; - uint16_t duration1; - } *packet_mod_duration = (struct t300rs_packet_mod_duration *)t300rs->send_buffer; - - uint16_t duration; - int ret = 0; - - duration = effect.replay.length - 1; - - t300rs_fill_header(&packet_mod_duration->header, effect.id, 0x4e); - packet_mod_duration->marker0 = 0x08; - packet_mod_duration->duration0 = cpu_to_le16(duration); - packet_mod_duration->marker1 = 0x05; - packet_mod_duration->marker2 = 0x41; - packet_mod_duration->duration1 = cpu_to_le16(duration); - - ret = t300rs_send_int(t300rs); - if (ret) { - hid_err(t300rs->hdev, "failed modifying duration\n"); - goto error; - } - -error: - return ret; -} - -static int t300rs_update_constant(struct t300rs_device_entry *t300rs, - struct tmff2_effect_state *state) -{ - struct ff_effect effect = state->effect; - struct ff_effect old = state->old; - struct ff_constant_effect constant = effect.u.constant; - struct ff_constant_effect constant_old = old.u.constant; - struct __packed t300rs_packet_mod_constant { - struct t300rs_packet_header header; - uint16_t level; - } *packet_mod_constant = (struct t300rs_packet_mod_constant *)t300rs->send_buffer; - int ret; - int16_t level; - - level = (constant.level * fixp_sin16(effect.direction * 360 / 0x10000)) / 0x7fff; - - if ((constant.level != constant_old.level) || (effect.direction != old.direction)) { - - t300rs_fill_header(&packet_mod_constant->header, effect.id, 0x0a); - packet_mod_constant->level = cpu_to_le16(level); - - ret = t300rs_send_int(t300rs); - if (ret) { - hid_err(t300rs->hdev, "failed modifying constant effect\n"); - goto error; - } - - } - - ret = t300rs_update_envelope(t300rs, - state, - level, - effect.replay.length, - effect.id, - constant.envelope, - constant_old.envelope - ); - - if (ret) { - hid_err(t300rs->hdev, "failed modifying constant envelope\n"); - goto error; - } - - ret = t300rs_update_simple_duration(t300rs, state, 0x00); - if (ret) { - hid_err(t300rs->hdev, "failed modifying constant duration\n"); - goto error; - } - -error: - return ret; -} - -static int t300rs_update_ramp(struct t300rs_device_entry *t300rs, - struct tmff2_effect_state *state) -{ - struct ff_effect effect = state->effect; - struct ff_effect old = state->old; - struct ff_ramp_effect ramp = effect.u.ramp; - struct ff_ramp_effect ramp_old = old.u.ramp; - struct __packed t300rs_packet_mod_ramp { - struct t300rs_packet_header header; - uint8_t attribute; - uint16_t difference; - uint16_t level; - } *packet_mod_ramp = (struct t300rs_packet_mod_ramp *)t300rs->send_buffer; - - int ret; - - uint16_t difference, top, bottom; - int16_t level; - - top = ramp.end_level > ramp.start_level ? ramp.end_level : ramp.start_level; - bottom = ramp.end_level > ramp.start_level ? ramp.start_level : ramp.end_level; - - - difference = ((top - bottom) * fixp_sin16(effect.direction * 360 / 0x10000)) / 0x7fff; - - - level = (top * fixp_sin16(effect.direction * 360 / 0x10000)) / 0x7fff; - - if ((ramp.start_level != ramp_old.start_level) - || (ramp.end_level != ramp_old.end_level) - || (effect.direction != old.direction)) { - - t300rs_fill_header(&packet_mod_ramp->header, effect.id, 0x0e); - packet_mod_ramp->attribute = 0x03; - packet_mod_ramp->difference = cpu_to_le16(difference); - packet_mod_ramp->level = cpu_to_le16(level); - - ret = t300rs_send_int(t300rs); - if (ret) { - hid_err(t300rs->hdev, "failed modifying ramp effect\n"); - goto error; - } - - } - - ret = t300rs_update_envelope(t300rs, - state, - level, - effect.replay.length, - effect.id, - ramp.envelope, - ramp_old.envelope - ); - - - if (ret) { - hid_err(t300rs->hdev, "failed modifying ramp envelope\n"); - goto error; - } - - ret = t300rs_update_ramp_duration(t300rs, state); - if (ret) { - hid_err(t300rs->hdev, "failed modifying ramp duration\n"); - goto error; - } - -error: - return ret; -} - -static int t300rs_update_damper(struct t300rs_device_entry *t300rs, - struct tmff2_effect_state *state) -{ - struct ff_effect effect = state->effect; - struct ff_effect old = state->old; - struct ff_condition_effect damper = effect.u.condition[0]; - struct ff_condition_effect damper_old = old.u.condition[0]; - struct __packed t300rs_packet_mod_damper { - struct t300rs_packet_header header; - uint8_t attribute; - uint16_t value0; - uint16_t value1; - } *packet_mod_damper = (struct t300rs_packet_mod_damper *)t300rs->send_buffer; - - int ret, input_level; - - input_level = damper_level; - if (state->effect.type == FF_FRICTION) - input_level = friction_level; - - if (state->effect.type == FF_SPRING) - input_level = spring_level; - - if (damper.right_coeff != damper_old.right_coeff) { - int16_t coeff = damper.right_coeff * input_level / 100; - - t300rs_fill_header(&packet_mod_damper->header, effect.id, 0x0e); - packet_mod_damper->attribute = 0x41; - packet_mod_damper->value0 = cpu_to_le16(coeff); - - ret = t300rs_send_int(t300rs); - if (ret) { - hid_err(t300rs->hdev, "failed modifying damper rc\n"); - goto error; - } - - } - - if (damper.left_coeff != damper_old.left_coeff) { - int16_t coeff = damper.left_coeff * input_level / 100; - - t300rs_fill_header(&packet_mod_damper->header, effect.id, 0x0e); - packet_mod_damper->attribute = 0x42; - packet_mod_damper->value0 = cpu_to_le16(coeff); - - ret = t300rs_send_int(t300rs); - if (ret) { - hid_err(t300rs->hdev, "failed modifying damper lc\n"); - goto error; - } - - } - - if ((damper.deadband != damper_old.deadband) - || (damper.center != damper_old.center)) { - - uint16_t right_deadband = 0xfffe - damper.deadband - damper.center; - uint16_t left_deadband = 0xfffe - damper.deadband + damper.center; - - t300rs_fill_header(&packet_mod_damper->header, effect.id, 0x0e); - packet_mod_damper->attribute = 0x4c; - packet_mod_damper->value0 = cpu_to_le16(right_deadband); - packet_mod_damper->value1 = cpu_to_le16(left_deadband); - - ret = t300rs_send_int(t300rs); - if (ret) { - hid_err(t300rs->hdev, "failed modifying damper deadband\n"); - goto error; - } - - } - - ret = t300rs_update_simple_duration(t300rs, state, 0x06); - if (ret) { - hid_err(t300rs->hdev, "failed modifying damper duration\n"); - goto error; - } - -error: - return ret; -} - -static int t300rs_update_spring(struct t300rs_device_entry *t300rs, - struct tmff2_effect_state *state) -{ - return t300rs_update_damper(t300rs, state); -} - - -static int t300rs_update_periodic(struct t300rs_device_entry *t300rs, - struct tmff2_effect_state *state) -{ - struct ff_effect effect = state->effect; - struct ff_effect old = state->old; - struct ff_periodic_effect periodic = effect.u.periodic; - struct ff_periodic_effect periodic_old = old.u.periodic; - struct __packed t300rs_packet_mod_periodic { - struct t300rs_packet_header header; - uint8_t attribute; - uint16_t value; - } *packet_mod_periodic = (struct t300rs_packet_mod_periodic *)t300rs->send_buffer; - - int ret; - int16_t magnitude, phase; - - magnitude = (periodic.magnitude * fixp_sin16(effect.direction * 360 / 0x10000)) / 0x7fff; - phase = periodic.phase; - - if ((periodic.magnitude != periodic_old.magnitude) - || (effect.direction != old.direction)) { - - t300rs_fill_header(&packet_mod_periodic->header, effect.id, 0x0e); - packet_mod_periodic->attribute = 0x01; - packet_mod_periodic->value = cpu_to_le16(magnitude); - - ret = t300rs_send_int(t300rs); - if (ret) { - hid_err(t300rs->hdev, "failed modifying periodic magnitude\n"); - goto error; - } - - } - - if (periodic.offset != periodic_old.offset) { - uint16_t offset = periodic.offset; - - t300rs_fill_header(&packet_mod_periodic->header, effect.id, 0x0e); - packet_mod_periodic->attribute = 0x02; - packet_mod_periodic->value = cpu_to_le16(offset); - - ret = t300rs_send_int(t300rs); - if (ret) { - hid_err(t300rs->hdev, "failed modifying periodic offset\n"); - goto error; - } - - } - - if (periodic.phase != periodic_old.phase) { - - t300rs_fill_header(&packet_mod_periodic->header, effect.id, 0x0e); - packet_mod_periodic->attribute = 0x04; - packet_mod_periodic->value = cpu_to_le16(phase); - - ret = t300rs_send_int(t300rs); - if (ret) { - hid_err(t300rs->hdev, "failed modifying periodic phase\n"); - goto error; - } - - } - - if (periodic.period != periodic_old.period) { - int16_t period = periodic.period; - - t300rs_fill_header(&packet_mod_periodic->header, effect.id, 0x0e); - packet_mod_periodic->attribute = 0x08; - packet_mod_periodic->value = cpu_to_le16(period); - - ret = t300rs_send_int(t300rs); - if (ret) { - hid_err(t300rs->hdev, "failed modifying periodic period\n"); - goto error; - } - - } - - ret = t300rs_update_envelope(t300rs, - state, - magnitude, - effect.replay.length, - effect.id, - periodic.envelope, - periodic_old.envelope - ); - - if (ret) { - hid_err(t300rs->hdev, "failed modifying periodic envelope\n"); - goto error; - } - - ret = t300rs_update_periodic_duration(t300rs, state, periodic.waveform - 0x57); - if (ret) { - hid_err(t300rs->hdev, "failed modifying periodic duration\n"); - goto error; - } - -error: - return ret; -} - -static int t300rs_upload_constant(struct t300rs_device_entry *t300rs, - struct tmff2_effect_state *state) -{ - struct ff_effect effect = state->effect; - struct ff_constant_effect constant = state->effect.u.constant; - struct __packed t300rs_packet_constant { - struct t300rs_packet_header header; - uint16_t level; - struct t300rs_packet_envelope envelope; - uint8_t zero; - struct t300rs_packet_timing timing; - } *packet_constant = (struct t300rs_packet_constant *)t300rs->send_buffer; - - int16_t level; - uint16_t duration, offset; - - int ret; - - level = (constant.level * fixp_sin16(effect.direction * 360 / 0x10000)) / 0x7fff; - duration = effect.replay.length - 1; - - offset = effect.replay.delay; - - t300rs_fill_header(&packet_constant->header, effect.id, 0x6a); - - packet_constant->level = cpu_to_le16(level); - - t300rs_fill_envelope(&packet_constant->envelope, level, duration, - &constant.envelope); - t300rs_fill_timing(&packet_constant->timing, duration, offset); - - ret = t300rs_send_int(t300rs); - if (ret) - hid_err(t300rs->hdev, "failed uploading constant effect\n"); - - return ret; -} - -static int t300rs_upload_ramp(struct t300rs_device_entry *t300rs, - struct tmff2_effect_state *state) -{ - struct ff_effect effect = state->effect; - struct ff_ramp_effect ramp = state->effect.u.ramp; - struct __packed t300rs_packet_ramp { - struct t300rs_packet_header header; - uint16_t difference; - uint16_t level; - uint8_t zero1[2]; - uint16_t duration; - uint16_t marker; - struct t300rs_packet_envelope envelope; - uint8_t direction; - struct t300rs_packet_timing timing; - } *packet_ramp = (struct t300rs_packet_ramp *)t300rs->send_buffer; - - int ret; - uint16_t difference, offset, top, bottom, duration; - int16_t level; - - duration = effect.replay.length - 1; - - top = ramp.end_level > ramp.start_level ? ramp.end_level : ramp.start_level; - bottom = ramp.end_level > ramp.start_level ? ramp.start_level : ramp.end_level; - - - difference = ((top - bottom) * fixp_sin16(effect.direction * 360 / 0x10000)) / 0x7fff; - level = (top * fixp_sin16(effect.direction * 360 / 0x10000)) / 0x7fff; - offset = effect.replay.delay; - - - t300rs_fill_header(&packet_ramp->header, effect.id, 0x6b); - - packet_ramp->difference = cpu_to_le16(difference); - packet_ramp->level = cpu_to_le16(level); - packet_ramp->duration = cpu_to_le16(duration); - - packet_ramp->marker = cpu_to_le16(0x8000); - - t300rs_fill_envelope(&packet_ramp->envelope, level, duration, - &ramp.envelope); - - packet_ramp->direction = ramp.end_level > ramp.start_level ? 0x04 : 0x05; - t300rs_fill_timing(&packet_ramp->timing, duration, offset); - - ret = t300rs_send_int(t300rs); - if (ret) - hid_err(t300rs->hdev, "failed uploading ramp"); - - return ret; -} - -static int t300rs_upload_spring(struct t300rs_device_entry *t300rs, - struct tmff2_effect_state *state) -{ - struct ff_effect effect = state->effect; - /* we only care about the first axis */ - struct ff_condition_effect spring = state->effect.u.condition[0]; - struct __packed t300rs_packet_spring { - struct t300rs_packet_header header; - uint16_t right_coeff; - uint16_t left_coeff; - uint16_t right_deadband; - uint16_t left_deadband; - uint8_t spring_start[17]; - struct t300rs_packet_timing timing; - } *packet_spring = (struct t300rs_packet_spring *)t300rs->send_buffer; - - int ret; - uint16_t duration, right_coeff, left_coeff, right_deadband, left_deadband, offset; - - duration = effect.replay.length - 1; - - right_coeff = spring.right_coeff * spring_level / 100; - left_coeff = spring.left_coeff * spring_level / 100; - - right_deadband = 0xfffe - spring.deadband - spring.center; - left_deadband = 0xfffe - spring.deadband + spring.center; - - offset = effect.replay.delay; - - t300rs_fill_header(&packet_spring->header, effect.id, 0x64); - - packet_spring->right_coeff = cpu_to_le16(right_coeff); - packet_spring->left_coeff = cpu_to_le16(left_coeff); - packet_spring->right_deadband = cpu_to_le16(right_deadband); - packet_spring->left_deadband = cpu_to_le16(left_deadband); - - memcpy(&packet_spring->spring_start, spring_values, ARRAY_SIZE(spring_values)); - t300rs_fill_timing(&packet_spring->timing, duration, offset); - - ret = t300rs_send_int(t300rs); - if (ret) - hid_err(t300rs->hdev, "failed uploading spring\n"); - - return ret; -} - -static int t300rs_upload_damper(struct t300rs_device_entry *t300rs, - struct tmff2_effect_state *state) -{ - - struct ff_effect effect = state->effect; - /* we only care about the first axis */ - struct ff_condition_effect spring = state->effect.u.condition[0]; - struct __packed t300rs_packet_damper { - struct t300rs_packet_header header; - uint16_t right_coeff; - uint16_t left_coeff; - uint16_t right_deadband; - uint16_t left_deadband; - uint8_t damper_start[17]; - struct t300rs_packet_timing timing; - } *packet_damper = (struct t300rs_packet_damper *)t300rs->send_buffer; - - int ret, input_level; - uint16_t duration, right_coeff, left_coeff, right_deadband, left_deadband, offset; - - duration = effect.replay.length - 1; - - input_level = damper_level; - if (state->effect.type == FF_FRICTION) - input_level = friction_level; - - right_coeff = spring.right_coeff * input_level / 100; - left_coeff = spring.left_coeff * input_level / 100; - - right_deadband = 0xfffe - spring.deadband - spring.center; - left_deadband = 0xfffe - spring.deadband + spring.center; - - offset = effect.replay.delay; - - t300rs_fill_header(&packet_damper->header, effect.id, 0x64); - - packet_damper->right_coeff = cpu_to_le16(right_coeff); - packet_damper->left_coeff = cpu_to_le16(left_coeff); - packet_damper->right_deadband = cpu_to_le16(right_deadband); - packet_damper->left_deadband = cpu_to_le16(left_deadband); - - memcpy(&packet_damper->damper_start, damper_values, ARRAY_SIZE(damper_values)); - t300rs_fill_timing(&packet_damper->timing, duration, offset); - - ret = t300rs_send_int(t300rs); - if (ret) - hid_err(t300rs->hdev, "failed uploading spring\n"); - - return ret; -} - -static int t300rs_upload_periodic(struct t300rs_device_entry *t300rs, - struct tmff2_effect_state *state) -{ - struct ff_effect effect = state->effect; - struct ff_periodic_effect periodic = state->effect.u.periodic; - struct __packed t300rs_packet_periodic { - struct t300rs_packet_header header; - uint16_t magnitude; - uint16_t periodic_offset; - uint16_t phase; - uint16_t period; - uint16_t marker; - struct t300rs_packet_envelope envelope; - uint8_t waveform; - struct t300rs_packet_timing timing; - } *packet_periodic = (struct t300rs_packet_periodic *)t300rs->send_buffer; - - int ret; - uint16_t duration, period, offset, periodic_offset; - int16_t phase, magnitude; - - duration = effect.replay.length - 1; - magnitude = (periodic.magnitude * fixp_sin16(effect.direction * 360 / 0x10000)) / 0x7fff; - - phase = periodic.phase; - periodic_offset = periodic.offset; - - period = periodic.period; - offset = effect.replay.delay; - - t300rs_fill_header(&packet_periodic->header, effect.id, 0x6b); - - packet_periodic->magnitude = cpu_to_le16(magnitude); - packet_periodic->periodic_offset = cpu_to_le16(periodic_offset); - packet_periodic->phase = cpu_to_le16(phase); - packet_periodic->period = cpu_to_le16(period); - - packet_periodic->marker = cpu_to_le16(0x8000); - - t300rs_fill_envelope(&packet_periodic->envelope, magnitude, duration, - &periodic.envelope); - - packet_periodic->waveform = periodic.waveform - 0x57; - - t300rs_fill_timing(&packet_periodic->timing, duration, offset); - - ret = t300rs_send_int(t300rs); - if (ret) - hid_err(t300rs->hdev, "failed uploading periodic effect"); - - return ret; -} - -int t300rs_update_effect(void *data, struct tmff2_effect_state *state) -{ - struct t300rs_device_entry *t300rs = data; - switch (state->effect.type) { - case FF_CONSTANT: - return t300rs_update_constant(t300rs, state); - case FF_RAMP: - return t300rs_update_ramp(t300rs, state); - case FF_SPRING: - return t300rs_update_spring(t300rs, state); - case FF_DAMPER: - case FF_FRICTION: - case FF_INERTIA: - return t300rs_update_damper(t300rs, state); - case FF_PERIODIC: - return t300rs_update_periodic(t300rs, state); - default: - hid_err(t300rs->hdev, "invalid effect type: %x", state->effect.type); - return -1; - } -} - -int t300rs_upload_effect(void *data, struct tmff2_effect_state *state) -{ - struct t300rs_device_entry *t300rs = data; - switch (state->effect.type) { - case FF_CONSTANT: - return t300rs_upload_constant(t300rs, state); - case FF_RAMP: - return t300rs_upload_ramp(t300rs, state); - case FF_SPRING: - return t300rs_upload_spring(t300rs, state); - case FF_DAMPER: - case FF_FRICTION: - case FF_INERTIA: - return t300rs_upload_damper(t300rs, state); - case FF_PERIODIC: - return t300rs_upload_periodic(t300rs, state); - default: - hid_err(t300rs->hdev, "invalid effect type: %x", state->effect.type); - return -1; - } -} - -static int t300rs_switch_mode(void *data, uint16_t mode) -{ - struct t300rs_device_entry *t300rs = data; - if (!t300rs) - return -ENODEV; - - if(t300rs->mode == mode) /* already in specified mode */ - return 0; - - if (mode == 0) - /* go to normal mode */ - usb_control_msg(t300rs->usbdev, - usb_sndctrlpipe(t300rs->usbdev, 0), - 83, 0x41, 5, 0, 0, 0, - USB_CTRL_SET_TIMEOUT - ); - else if (mode == 1) - /* go to advanced mode */ - usb_control_msg(t300rs->usbdev, - usb_sndctrlpipe(t300rs->usbdev, 0), - 83, 0x41, 3, 0, 0, 0, - USB_CTRL_SET_TIMEOUT - ); - else - hid_warn(t300rs->hdev, "mode %i not supported\n", mode); - - - return 0; -} - -static struct t300rs_alt_modes { - char *id; - char *label; - uint16_t mode; -} t300rs_modes[] = { - {"base", "T300RS base", 0}, - {"F1", "T300RS with F1 wheel attachment", 1} -}; - -static ssize_t t300rs_alt_mode_show(void *data, char *buf) -{ - struct t300rs_device_entry *t300rs = data; - ssize_t count = 0; - int i; - if (!t300rs) - return -ENODEV; - - if (t300rs->attachment != T300RS_F1_ATTACHMENT) - /* we only support one base mode */ - return scnprintf(buf, PAGE_SIZE, "%s: %s *\n", - t300rs_modes[0].id, t300rs_modes[0].label); - - for (i = 0; i < ARRAY_SIZE(t300rs_modes); ++i) { - count += scnprintf(buf + count, PAGE_SIZE - count, "%s: %s", - t300rs_modes[i].id, t300rs_modes[i].label); - - if (count >= PAGE_SIZE - 1) - return count; - - if (t300rs_modes[i].mode == t300rs->mode) - count += scnprintf(buf + count, PAGE_SIZE - count, " *\n"); - else - count += scnprintf(buf + count, PAGE_SIZE - count, "\n"); - - if (count >= PAGE_SIZE - 1) - return count; - } - - return count; -} - -static ssize_t t300rs_alt_mode_store(void *data, const char *buf, size_t count) -{ - struct t300rs_device_entry *t300rs = data; - int i, len, mode_len; - char *lbuf; - if (!t300rs) - return -ENODEV; - - if (t300rs->attachment != T300RS_F1_ATTACHMENT) - return count; /* don't do anything */ - - lbuf = kasprintf(GFP_KERNEL, "%s", buf); - if (!lbuf) - return -ENOMEM; - - len = strlen(buf); - for (i = 0; i < ARRAY_SIZE(t300rs_modes); ++i) { - mode_len = strlen(t300rs_modes[i].id); - if (mode_len > len) - continue; - - if (strncmp(lbuf, t300rs_modes[i].id, mode_len) == 0) { - t300rs_switch_mode(data, t300rs_modes[i].mode); - break; - } - } - - kfree(lbuf); - return count; -} - -int t300rs_set_autocenter(void *data, uint16_t value) -{ - struct t300rs_device_entry *t300rs = data; - struct __packed t300rs_packet_autocenter { - struct t300rs_setup_header header; - uint16_t value; - } *autocenter_packet; - int ret; - - if (!t300rs) - return -ENODEV; - - /* TODO: this should probably also use a separately allocated buffer? */ - autocenter_packet = (struct t300rs_packet_autocenter *)t300rs->send_buffer; - - autocenter_packet->header.cmd = 0x08; - autocenter_packet->header.code = 0x04; - autocenter_packet->value = cpu_to_le16(0x01); - - if ((ret = t300rs_send_int(t300rs))) { - hid_err(t300rs->hdev, "failed setting autocenter"); - return ret; - } - - autocenter_packet->header.cmd = 0x08; - autocenter_packet->header.code = 0x03; - - autocenter_packet->value = cpu_to_le16(value); - - if ((ret = t300rs_send_int(t300rs))) - hid_err(t300rs->hdev, "failed setting autocenter"); - - return ret; -} - -int t300rs_set_gain(void *data, uint16_t gain) -{ - struct t300rs_device_entry *t300rs = data; - struct __packed t300rs_packet_gain { - struct t300rs_setup_header header; - } *gain_packet; - int ret; - - if (!t300rs) - return -ENODEV; - - gain_packet = (struct t300rs_packet_gain *)t300rs->send_buffer; - gain_packet->header.cmd = 0x02; - gain_packet->header.code = (gain >> 8) & 0xff; - - if ((ret = t300rs_send_int(t300rs))) - hid_err(t300rs->hdev, "failed setting gain: %i\n", ret); - - return ret; -} - -int t300rs_set_range(void *data, uint16_t value) -{ - struct t300rs_device_entry *t300rs = data; - /* it's important that we don't use t300rs->send_buffer, as range can be - * set from outside of the FFB environment, and we don't want to - * accidentally overwrite any data. */ - u8 *send_buffer = kzalloc(t300rs->buffer_length, GFP_KERNEL); - uint16_t scaled_value; - int ret; - - if (value < 40) { - hid_info(t300rs->hdev, "value %i too small, clamping to 40\n", value); - value = 40; - } - - if (value > 1080) { - hid_info(t300rs->hdev, "value %i too large, clamping to 1080\n", value); - value = 1080; - } - - if (!send_buffer) { - ret = -EINVAL; - hid_err(t300rs->hdev, "could not allocate send_buffer\n"); - goto err; - } - - scaled_value = value * 0x3c; - send_buffer[0] = 0x08; - send_buffer[1] = 0x11; - send_buffer[2] = scaled_value & 0xff; - send_buffer[3] = scaled_value >> 8; - - if ((ret = t300rs_send_buf(t300rs, send_buffer, t300rs->buffer_length))) - hid_warn(t300rs->hdev, "failed setting range\n"); - - /* since everythin went OK, update the current range */ - range = value; -err: - kfree(send_buffer); - return ret; -} - -static int t300rs_send_open(struct t300rs_device_entry *t300rs) -{ - struct __packed t300rs_packet_open { - struct t300rs_setup_header header; - } *open_packet; - - open_packet = (struct t300rs_packet_open *)t300rs->send_buffer; - open_packet->header.cmd = 0x01; - open_packet->header.code = 0x05; - - return t300rs_send_int(t300rs); -} - -static int t300rs_send_close(struct t300rs_device_entry *t300rs) -{ - struct __packed t300rs_packet_open { - struct t300rs_setup_header header; - } *open_packet; - - open_packet = (struct t300rs_packet_open *)t300rs->send_buffer; - open_packet->header.cmd = 0x01; - - return t300rs_send_int(t300rs); -} - -int t300rs_open(void *data, int open_mode) -{ - struct t300rs_device_entry *t300rs = data; - if (!t300rs) - return -ENODEV; - - if (open_mode && t300rs_send_open(t300rs)) - hid_warn(t300rs->hdev, "failed sending open command\n"); - - return t300rs->open(t300rs->input_dev); -} - -int t300rs_close(void *data, int open_mode) -{ - struct t300rs_device_entry *t300rs = data; - int ret; - - if (!t300rs) - return -ENODEV; - - if (open_mode && (ret = t300rs_send_close(t300rs))) - hid_warn(t300rs->hdev, "failed sending close command\n"); - - t300rs->close(t300rs->input_dev); - return ret; -} - -static int t300rs_check_firmware(struct t300rs_device_entry *t300rs) -{ - int ret; - struct t300rs_fw_response *fw_response = - kzalloc(sizeof(struct t300rs_fw_response), GFP_KERNEL); - - if (!fw_response) { - hid_err(t300rs->hdev, "could not allocate fw_response\n"); - return -ENOMEM; - } - - /* Fetch firmware version */ - ret = usb_control_msg(t300rs->usbdev, - usb_rcvctrlpipe(t300rs->usbdev, 0), - t300rs_fw_request.bRequest, - t300rs_fw_request.bRequestType, - t300rs_fw_request.wValue, - t300rs_fw_request.wIndex, - fw_response, - t300rs_fw_request.wLength, - USB_CTRL_SET_TIMEOUT - ); - - if (ret < 0) { - hid_err(t300rs->hdev, "could not fetch firmware version: %i\n", ret); - goto out; - } - - /* Educated guess */ - if (fw_response->fw_version < 31 && ret >= 0) { - hid_err(t300rs->hdev, - "firmware version %i is too old, please update.\n", - fw_response->fw_version - ); - - hid_info(t300rs->hdev, "note: this has to be done through Windows.\n"); - - ret = -EINVAL; - goto out; - } - - /* everything OK */ - ret = 0; - -out: - kfree(fw_response); - return ret; -} - -static int t300rs_get_attachment(struct t300rs_device_entry *t300rs) -{ - /* taken directly from hid_tminit */ - struct __packed t300rs_attachment_response - { - uint16_t type; - - union { - struct __packed { - uint16_t field0; - uint16_t field1; - uint8_t attachment; - uint8_t model; - uint16_t field2; - uint16_t field3; - uint16_t field4; - uint16_t field5; - } a; - - struct __packed { - uint16_t field0; - uint16_t field1; - uint8_t attachment; - uint8_t model; - } b; - }; - } *response = kzalloc(GFP_KERNEL, sizeof(struct t300rs_attachment_response)); - struct usb_ctrlrequest t300rs_attachment_rq = { - .bRequestType = 0xc1, - .bRequest = 73, - .wValue = 0, - .wIndex = 0, - .wLength = sizeof(struct t300rs_attachment_response) - }; - int ret, attachment; - if (!response) - return -ENODEV; - - ret = usb_control_msg(t300rs->usbdev, - usb_rcvctrlpipe(t300rs->usbdev, 0), - t300rs_attachment_rq.bRequest, - t300rs_attachment_rq.bRequestType, - t300rs_attachment_rq.wValue, - t300rs_attachment_rq.wIndex, - response, - sizeof(struct t300rs_attachment_response), - USB_CTRL_SET_TIMEOUT - ); - - if (ret < 0) { - hid_err(t300rs->hdev, "could not fetch attachment: %i\n", ret); - goto out; - } - - if (response->type == cpu_to_le16(0x49)) { - attachment = response->a.attachment; - } else if (response->type == cpu_to_le16(0x47)) { - attachment = response->b.attachment; - } else { - hid_err(t300rs->hdev, "unknown packet type %hx\n, please contact a maintainer", - response->type); - ret = -EINVAL; - goto out; - } - - kfree(response); - return attachment; - -out: - kfree(response); - return ret; -} - -static int t300rs_wheel_init(struct tmff2_device_entry *tmff2, int open_mode) -{ - struct t300rs_device_entry *t300rs = kzalloc(sizeof(struct t300rs_device_entry), GFP_KERNEL); - struct list_head *report_list; - int ret; - - if (!t300rs) { - ret = -ENOMEM; - goto t300rs_err; - } - - t300rs->hdev = tmff2->hdev; - t300rs->input_dev = tmff2->input_dev; - t300rs->usbdev = to_usb_device(tmff2->hdev->dev.parent->parent); - - if(t300rs->hdev->product == TMT300RS_PS4_NORM_ID) - t300rs->buffer_length = T300RS_PS4_BUFFER_LENGTH; - else - t300rs->buffer_length = T300RS_NORM_BUFFER_LENGTH; - - t300rs->send_buffer = kzalloc(t300rs->buffer_length, GFP_KERNEL); - if (!t300rs->send_buffer) { - ret = -ENOMEM; - goto send_err; - } - - if ((ret = t300rs_check_firmware(t300rs))) - goto firmware_err; - - report_list = &t300rs->hdev->report_enum[HID_OUTPUT_REPORT].report_list; - - /* because we set the rdesc, we know exactly which report and field to use */ - t300rs->report = list_entry(report_list->next, struct hid_report, list); - t300rs->ff_field = t300rs->report->field[0]; - - t300rs->open = t300rs->input_dev->open; - t300rs->close = t300rs->input_dev->close; - - /* TODO: PS4 advanced mode? */ - alt_mode = (t300rs->mode = (t300rs->hdev->product == TMT300RS_PS3_ADV_ID)); - if ((t300rs->attachment = t300rs_get_attachment(t300rs)) < 0) - t300rs->attachment = T300RS_DEFAULT_ATTACHMENT; - - /* everythin went OK */ - tmff2->data = t300rs; - tmff2->params = t300rs_params; - tmff2->max_effects = T300RS_MAX_EFFECTS; - memcpy(tmff2->supported_effects, t300rs_effects, sizeof(t300rs_effects)); - - if (!open_mode) - t300rs_send_open(t300rs); - - hid_info(t300rs->hdev, "force feedback for T300RS\n"); - return 0; - -firmware_err: - kfree(t300rs->send_buffer); -send_err: - kfree(t300rs); -t300rs_err: - hid_err(tmff2->hdev, "failed initializing T300RS\n"); - return ret; -} - -static int t300rs_wheel_destroy(void *data) -{ - struct t300rs_device_entry *t300rs = data; - if (!t300rs) - return -ENODEV; - - kfree(t300rs->send_buffer); - kfree(t300rs); - return 0; -} - -static __u8 *t300rs_wheel_fixup(struct hid_device *hdev, __u8 *rdesc, - unsigned int *rsize) -{ - switch (hdev->product) { - case TMT300RS_PS3_NORM_ID: - /* normal PS3 mode */ - rdesc = t300rs_rdesc_nrm_fixed; - *rsize = sizeof(t300rs_rdesc_nrm_fixed); - break; - - case TMT300RS_PS4_NORM_ID: - /* PS4 normal mode */ - rdesc = t300rs_rdesc_ps4_fixed; - *rsize = sizeof(t300rs_rdesc_ps4_fixed); - break; - - case TMT300RS_PS3_ADV_ID: - /* PS3 advanced mode */ - rdesc = t300rs_rdesc_adv_fixed; - *rsize = sizeof(t300rs_rdesc_adv_fixed); - break; - } - - return rdesc; -} - -int t300rs_populate_api(struct tmff2_device_entry *tmff2) -{ - /* set callbacks */ - tmff2->play_effect = t300rs_play_effect; - tmff2->upload_effect = t300rs_upload_effect; - tmff2->update_effect = t300rs_update_effect; - tmff2->stop_effect = t300rs_stop_effect; - - tmff2->wheel_init = t300rs_wheel_init; - tmff2->wheel_destroy = t300rs_wheel_destroy; - - tmff2->open = t300rs_open; - tmff2->close = t300rs_close; - tmff2->set_gain = t300rs_set_gain; - tmff2->set_range = t300rs_set_range; - tmff2->switch_mode = t300rs_switch_mode; - tmff2->alt_mode_show = t300rs_alt_mode_show; - tmff2->alt_mode_store = t300rs_alt_mode_store; - tmff2->set_autocenter = t300rs_set_autocenter; - tmff2->wheel_fixup = t300rs_wheel_fixup; - - return 0; -} diff --git a/src/hid-tmt300rs/hid-tmt300rs.h b/src/hid-tmt300rs/hid-tmt300rs.h deleted file mode 100644 index 2305f4b..0000000 --- a/src/hid-tmt300rs/hid-tmt300rs.h +++ /dev/null @@ -1,9 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -#ifndef __HID_TMT300RS_H -#define __HID_TMT300RS_H - -#include "../hid-tmff2.h" - -int t300rs_populate_api(struct tmff2_device_entry *tmff2); - -#endif diff --git a/src/hid-tmtx/hid-tmtx.c b/src/hid-tmtx/hid-tmtx.c deleted file mode 100644 index afca799..0000000 --- a/src/hid-tmtx/hid-tmtx.c +++ /dev/null @@ -1,323 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -#include -#include -#include "../hid-tmff2.h" - -#define TMTX_MAX_EFFECTS 16 -#define TMTX_BUFFER_LENGTH 63 - -static const u8 setup_0[64] = { 0x42, 0x01 }; -static const u8 setup_1[64] = { 0x0a, 0x04, 0x90, 0x03 }; -static const u8 setup_2[64] = { 0x0a, 0x04, 0x00, 0x0c }; -static const u8 setup_3[64] = { 0x0a, 0x04, 0x12, 0x10 }; -static const u8 setup_4[64] = { 0x0a, 0x04, 0x00, 0x06 }; -static const u8 setup_5[64] = { 0x0a, 0x04, 0x00, 0x0e }; -static const u8 setup_6[64] = { 0x0a, 0x04, 0x00, 0x0e, 0x01 }; -static const u8 *const setup_arr[] = { setup_0, setup_1, setup_2, setup_3, setup_4, setup_5, setup_6 }; -static const unsigned int setup_arr_sizes[] = { - ARRAY_SIZE(setup_0), - ARRAY_SIZE(setup_1), - ARRAY_SIZE(setup_2), - ARRAY_SIZE(setup_3), - ARRAY_SIZE(setup_4), - ARRAY_SIZE(setup_5), - ARRAY_SIZE(setup_6) -}; - -static const unsigned long tx_params = - PARAM_SPRING_LEVEL - | PARAM_DAMPER_LEVEL - | PARAM_FRICTION_LEVEL - | PARAM_RANGE - | PARAM_GAIN - ; - -static const signed short tx_effects[] = { - FF_CONSTANT, - FF_RAMP, - FF_SPRING, - FF_DAMPER, - FF_FRICTION, - FF_INERTIA, - FF_PERIODIC, - FF_SINE, - FF_TRIANGLE, - FF_SQUARE, - FF_SAW_UP, - FF_SAW_DOWN, - FF_AUTOCENTER, - FF_GAIN, - -1 -}; - -/* TODO: sort through this stuff */ -static u8 tx_pc_rdesc_fixed[] = { - 0x05, 0x01, /* Usage page (Generic Desktop) */ - 0x09, 0x04, /* Usage (Joystick) */ - 0xa1, 0x01, /* Collection (Application) */ - 0x09, 0x01, /* Usage (Pointer) */ - 0xa1, 0x00, /* Collection (Physical) */ - 0x85, 0x07, /* Report ID (7) */ - 0x09, 0x30, /* Usage (X) */ - 0x15, 0x00, /* Logical minimum (0) */ - 0x27, 0xff, 0xff, 0x00, 0x00, /* Logical maximum (65535) */ - 0x35, 0x00, /* Physical minimum (0) */ - 0x47, 0xff, 0xff, 0x00, 0x00, /* Physical maximum (65535) */ - 0x75, 0x10, /* Report size (16) */ - 0x95, 0x01, /* Report count (1) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - 0x09, 0x35, /* Usage (Rz) (Brake) */ - 0x26, 0xff, 0x03, /* Logical maximum (1023) */ - 0x46, 0xff, 0x03, /* Physical maximum (1023) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - 0x09, 0x32, /* Usage (Z) (Gas) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - 0x09, 0x31, /* Usage (Y) (Clutch) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - 0x81, 0x03, /* Input (Variable, Absolute, Constant) */ - 0x05, 0x09, /* Usage page (Button) */ - 0x19, 0x01, /* Usage minimum (1) */ - 0x29, 0x0d, /* Usage maximum (13) */ - 0x25, 0x01, /* Logical maximum (1) */ - 0x45, 0x01, /* Physical maximum (1) */ - 0x75, 0x01, /* Report size (1) */ - 0x95, 0x0d, /* Report count (13) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - 0x75, 0x0b, /* Report size (13) */ - 0x95, 0x01, /* Report count (1) */ - 0x81, 0x03, /* Usage (Variable, Absolute, Constant) */ - 0x05, 0x01, /* Usage page (Generic Desktop) */ - 0x09, 0x39, /* Usage (Hat Switch) */ - 0x25, 0x07, /* Logical maximum (7) */ - 0x46, 0x3b, 0x01, /* Physical maximum (315) */ - 0x55, 0x00, /* Unit exponent (0) */ - 0x65, 0x14, /* Unit (Eng Rot, Angular Pos) */ - 0x75, 0x04, /* Report size (4) */ - 0x81, 0x42, /* Input (Variable, Absolute, NullState) */ - 0x65, 0x00, /* Unit (None) */ - 0x81, 0x03, /* Input (Variable, Absolute, Constant) */ - 0x85, 0x60, /* Report ID (96), prev 10 */ - 0x06, 0x00, 0xff, /* Usage page (Vendor 1) */ - 0x09, 0x60, /* Usage (96), prev 10 */ - 0x75, 0x08, /* Report size (8) */ - 0x95, 0x3f, /* Report count (63) */ - 0x26, 0xff, 0x7f, /* Logical maximum (32767) */ - 0x15, 0x00, /* Logical minimum (0) */ - 0x46, 0xff, 0x7f, /* Physical maximum (32767) */ - 0x36, 0x00, 0x80, /* Physical minimum (-32768) */ - 0x91, 0x02, /* Output (Variable, Absolute) */ - 0x85, 0x02, /* Report ID (2) */ - 0x09, 0x02, /* Usage (2) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - 0x09, 0x14, /* Usage (20) */ - 0x85, 0x14, /* Report ID (20) */ - 0x81, 0x02, /* Input (Variable, Absolute) */ - 0xc0, /* End collection */ - 0xc0, /* End collection */ -}; - -static int tx_interrupts(struct t300rs_device_entry *tx) -{ - u8 *send_buf = kmalloc(256, GFP_KERNEL); - struct usb_interface *usbif = to_usb_interface(tx->hdev->dev.parent); - struct usb_host_endpoint *ep; - int ret, trans, b_ep, i; - - if (!send_buf) { - hid_err(tx->hdev, "failed allocating send buffer\n"); - return -ENOMEM; - } - - ep = &usbif->cur_altsetting->endpoint[1]; - b_ep = ep->desc.bEndpointAddress; - - for (i = 0; i < ARRAY_SIZE(setup_arr); ++i) { - memcpy(send_buf, setup_arr[i], setup_arr_sizes[i]); - - ret = usb_interrupt_msg(tx->usbdev, - usb_sndintpipe(tx->usbdev, b_ep), - send_buf, setup_arr_sizes[i], - &trans, - USB_CTRL_SET_TIMEOUT); - - if (ret) { - hid_err(tx->hdev, "setup data couldn't be sent\n"); - goto err; - } - } - -err: - kfree(send_buf); - return ret; -} - -int tx_wheel_destroy(void *data) -{ - struct t300rs_device_entry *t300rs = data; - - if (!t300rs) - return -ENODEV; - - kfree(t300rs->send_buffer); - kfree(t300rs); - return 0; -} - -int tx_set_range(void *data, uint16_t value) -{ - struct t300rs_device_entry *tx = data; - - if (value < 140) { - hid_info(tx->hdev, "value %i too small, clamping to 140\n", value); - value = 140; - } - - if (value > 900) { - hid_info(tx->hdev, "value %i too large, clamping to 900\n", value); - value = 900; - } - - return t300rs_set_range(data, value); -} - -static int tx_send_open(struct t300rs_device_entry *tx) -{ - int r1, r2; - tx->send_buffer[0] = 0x01; - tx->send_buffer[1] = 0x04; - if ((r1 = t300rs_send_int(tx))) - return r1; - - tx->send_buffer[0] = 0x01; - tx->send_buffer[1] = 0x05; - if ((r2 = t300rs_send_int(tx))) - return r2; - - return 0; -} - -static int tx_open(void *data, int open_mode) -{ - struct t300rs_device_entry *tx = data; - - if (!tx) - return -ENODEV; - - if (open_mode) - tx_send_open(tx); - - return tx->open(tx->input_dev); -} - -static int tx_send_close(struct t300rs_device_entry *tx) -{ - int r1, r2; - tx->send_buffer[0] = 0x01; - tx->send_buffer[1] = 0x05; - if ((r1 = t300rs_send_int(tx))) - return r1; - - tx->send_buffer[0] = 0x01; - tx->send_buffer[1] = 0x00; - if ((r2 = t300rs_send_int(tx))) - return r2; - - return 0; -} - -static int tx_close(void *data, int open_mode) -{ - struct t300rs_device_entry *tx = data; - - if (!tx) - return -ENODEV; - - if (open_mode) - tx_send_close(tx); - - tx->close(tx->input_dev); - return 0; -} - -int tx_wheel_init(struct tmff2_device_entry *tmff2, int open_mode) -{ - struct t300rs_device_entry *tx = kzalloc(sizeof(struct t300rs_device_entry), GFP_KERNEL); - struct list_head *report_list; - int ret; - - - if (!tx) { - ret = -ENOMEM; - goto tx_err; - } - - tx->hdev = tmff2->hdev; - tx->input_dev = tmff2->input_dev; - tx->usbdev = to_usb_device(tmff2->hdev->dev.parent->parent); - tx->buffer_length = TMTX_BUFFER_LENGTH; - - tx->send_buffer = kzalloc(tx->buffer_length, GFP_KERNEL); - if (!tx->send_buffer) { - ret = -ENOMEM; - goto send_err; - } - - report_list = &tx->hdev->report_enum[HID_OUTPUT_REPORT].report_list; - tx->report = list_entry(report_list->next, struct hid_report, list); - tx->ff_field = tx->report->field[0]; - - tx->open = tx->input_dev->open; - tx->close = tx->input_dev->close; - - if ((ret = tx_interrupts(tx))) - goto interrupt_err; - - /* everything went OK */ - tmff2->data = tx; - tmff2->params = tx_params; - tmff2->max_effects = TMTX_MAX_EFFECTS; - memcpy(tmff2->supported_effects, tx_effects, sizeof(tx_effects)); - - if (!open_mode) - tx_send_open(tx); - - hid_info(tx->hdev, "force feedback for TX\n"); - return 0; - -interrupt_err: -send_err: - kfree(tx); -tx_err: - hid_err(tmff2->hdev, "failed initializing TX\n"); - return ret; -} - -static __u8 *tx_wheel_fixup(struct hid_device *hdev, __u8 *rdesc, - unsigned int *rsize) -{ - rdesc = tx_pc_rdesc_fixed; - *rsize = sizeof(tx_pc_rdesc_fixed); - return rdesc; -} - -int tx_populate_api(struct tmff2_device_entry *tmff2) -{ - tmff2->play_effect = t300rs_play_effect; - tmff2->upload_effect = t300rs_upload_effect; - tmff2->update_effect = t300rs_update_effect; - tmff2->stop_effect = t300rs_stop_effect; - - tmff2->set_gain = t300rs_set_gain; - tmff2->set_autocenter = t300rs_set_autocenter; - /* TX only has 900 degree range, instead of T300RS 1080 */ - tmff2->set_range = tx_set_range; - tmff2->wheel_fixup = tx_wheel_fixup; - - tmff2->open = tx_open; - tmff2->close = tx_close; - - tmff2->wheel_init = tx_wheel_init; - tmff2->wheel_destroy = tx_wheel_destroy; - - return 0; -} diff --git a/src/tmt248/hid-tmt248.c b/src/tmt248/hid-tmt248.c new file mode 100644 index 0000000..af3aaa9 --- /dev/null +++ b/src/tmt248/hid-tmt248.c @@ -0,0 +1,335 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#include +#include +#include "../hid-tmff2.h" + +#define T248_MAX_EFFECTS 16 +#define T248_BUFFER_LENGTH 63 + +static const u8 setup_0[64] = { 0x42, 0x01 }; +static const u8 setup_1[64] = { 0x0a, 0x04, 0x90, 0x03 }; +static const u8 setup_2[64] = { 0x0a, 0x04, 0x00, 0x0c }; +static const u8 setup_3[64] = { 0x0a, 0x04, 0x12, 0x10 }; +static const u8 setup_4[64] = { 0x0a, 0x04, 0x00, 0x06 }; +static const u8 setup_5[64] = { 0x0a, 0x04, 0x00, 0x0e }; +static const u8 setup_6[64] = { 0x0a, 0x04, 0x00, 0x0e, 0x01 }; +static const u8 *const setup_arr[] = { setup_0, setup_1, setup_2, setup_3, setup_4, setup_5, setup_6 }; +static const unsigned int setup_arr_sizes[] = { + ARRAY_SIZE(setup_0), + ARRAY_SIZE(setup_1), + ARRAY_SIZE(setup_2), + ARRAY_SIZE(setup_3), + ARRAY_SIZE(setup_4), + ARRAY_SIZE(setup_5), + ARRAY_SIZE(setup_6) +}; + +static const unsigned long t248_params = + PARAM_SPRING_LEVEL + | PARAM_DAMPER_LEVEL + | PARAM_FRICTION_LEVEL + | PARAM_RANGE + | PARAM_GAIN + ; + +static const signed short t248_effects[] = { + FF_CONSTANT, + FF_RAMP, + FF_SPRING, + FF_DAMPER, + FF_FRICTION, + FF_INERTIA, + FF_PERIODIC, + FF_SINE, + FF_TRIANGLE, + FF_SQUARE, + FF_SAW_UP, + FF_SAW_DOWN, + FF_AUTOCENTER, + FF_GAIN, + -1 +}; + +/* TODO: sort through this stuff */ +static u8 t248_pc_rdesc_fixed[] = { + 0x05, 0x01, /* Usage page (Generic Desktop) */ + 0x09, 0x04, /* Usage (Joystick) */ + 0xa1, 0x01, /* Collection (Application) */ + 0x09, 0x01, /* Usage (Pointer) */ + 0xa1, 0x00, /* Collection (Physical) */ + 0x85, 0x07, /* Report ID (7) */ + 0x09, 0x30, /* Usage (X) */ + 0x15, 0x00, /* Logical minimum (0) */ + 0x27, 0xff, 0xff, 0x00, 0x00, /* Logical maximum (65535) */ + 0x35, 0x00, /* Physical minimum (0) */ + 0x47, 0xff, 0xff, 0x00, 0x00, /* Physical maximum (65535) */ + 0x75, 0x10, /* Report size (16) */ + 0x95, 0x01, /* Report count (1) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x09, 0x31, /* Usage (Y) TODO: clutch? */ + 0x26, 0xff, 0x03, /* Logical maximum (1023) */ + 0x46, 0xff, 0x03, /* Physical maximum (1023) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x09, 0x35, /* Usage (Rz) TODO: brake? */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x09, 0x36, /* Usage (Slider) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x75, 0x08, /* Report size (8) */ + 0x26, 0xff, 0x00, /* Logical maximum (255) */ + 0x46, 0xff, 0x00, /* Physical maximum (255) */ + 0x09, 0x40, /* Usage (Vx) TODO: what is this? */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x09, 0x41, /* Usage (Vy) TODO: --||-- */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x09, 0x33, /* Usage (Rx) TODO: --||-- */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x09, 0x34, /* Usage (Ry) TODO: --||-- */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x09, 0x32, /* Usage (Z) TODO: --||-- (gas?) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x09, 0x37, /* Usage (Dial) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x05, 0x09, /* Usage page (Button) */ + 0x19, 0x01, /* Usage minimum (1) */ + 0x29, 0x1a, /* Usage maximum (13) */ + 0x25, 0x01, /* Logical maximum (1) */ + 0x45, 0x01, /* Physical maximum (1) */ + 0x75, 0x01, /* Report size (1) */ + 0x95, 0x1a, /* Report count (26) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x75, 0x06, /* Report size (6) */ + 0x95, 0x01, /* Report count (1) */ + 0x81, 0x03, /* Usage (Variable, Absolute, Constant) */ + 0x05, 0x01, /* Usage page (Generic Desktop) */ + 0x09, 0x39, /* Usage (Hat Switch) */ + 0x25, 0x07, /* Logical maximum (7) */ + 0x46, 0x3b, 0x01, /* Physical maximum (315) */ + 0x55, 0x00, /* Unit exponent (0) */ + 0x65, 0x14, /* Unit (Eng rot, Angular Pos) */ + 0x75, 0x04, /* Report size (4) */ + 0x81, 0x42, /* Input (Variable, Absolute, NullState) */ + 0x65, 0x00, /* Input (None) */ + 0x81, 0x03, /* Input (Variable, Absolute, Constant) */ + 0x85, 0x60, /* Report ID (96), prev 10 */ + 0x06, 0x00, 0xff, /* Usage page (Vendor 1) */ + 0x09, 0x60, /* Usage (96), prev 10 */ + 0x75, 0x08, /* Report size (8) */ + 0x95, 0x3f, /* Report count (63) */ + 0x26, 0xff, 0x00, /* Logical maximum (256) */ + 0x46, 0xff, 0x00, /* Physical maximum (256) */ + 0x91, 0x02, /* Output (Variable, Absolute) */ + 0x85, 0x02, /* Report ID (2) */ + 0x09, 0x02, /* Usage (2) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x09, 0x14, /* Usage (20) */ + 0x85, 0x14, /* Report ID (20) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0xc0, /* End collection */ + 0xc0, /* End collection */ +}; + +static int t248_interrupts(struct t300rs_device_entry *t248) +{ + u8 *send_buf = kmalloc(256, GFP_KERNEL); + struct usb_interface *usbif = to_usb_interface(t248->hdev->dev.parent); + struct usb_host_endpoint *ep; + int ret, trans, b_ep, i; + + if (!send_buf) { + hid_err(t248->hdev, "failed allocating send buffer\n"); + return -ENOMEM; + } + + ep = &usbif->cur_altsetting->endpoint[1]; + b_ep = ep->desc.bEndpointAddress; + + for (i = 0; i < ARRAY_SIZE(setup_arr); ++i) { + memcpy(send_buf, setup_arr[i], setup_arr_sizes[i]); + + ret = usb_interrupt_msg(t248->usbdev, + usb_sndintpipe(t248->usbdev, b_ep), + send_buf, setup_arr_sizes[i], + &trans, + USB_CTRL_SET_TIMEOUT); + + if (ret) { + hid_err(t248->hdev, "setup data couldn't be sent\n"); + goto err; + } + } + +err: + kfree(send_buf); + return ret; +} + +int t248_wheel_destroy(void *data) +{ + struct t300rs_device_entry *t300rs = data; + + if (!t300rs) + return -ENODEV; + + kfree(t300rs->send_buffer); + kfree(t300rs); + return 0; +} + +int t248_set_range(void *data, uint16_t value) +{ + struct t300rs_device_entry *t248 = data; + + if (value < 140) { + hid_info(t248->hdev, "value %i too small, clamping to 140\n", value); + value = 140; + } + + if (value > 900) { + hid_info(t248->hdev, "value %i too large, clamping to 900\n", value); + value = 900; + } + + return t300rs_set_range(data, value); +} + +static int t248_send_open(struct t300rs_device_entry *t248) +{ + int r1, r2; + t248->send_buffer[0] = 0x01; + t248->send_buffer[1] = 0x04; + if ((r1 = t300rs_send_int(t248))) + return r1; + + t248->send_buffer[0] = 0x01; + t248->send_buffer[1] = 0x05; + if ((r2 = t300rs_send_int(t248))) + return r2; + + return 0; +} + +static int t248_open(void *data, int open_mode) +{ + struct t300rs_device_entry *t248 = data; + + if (!t248) + return -ENODEV; + + if (open_mode) + t248_send_open(t248); + + return t248->open(t248->input_dev); +} + +static int t248_send_close(struct t300rs_device_entry *t248) +{ + int r1, r2; + t248->send_buffer[0] = 0x01; + t248->send_buffer[1] = 0x05; + if ((r1 = t300rs_send_int(t248))) + return r1; + + t248->send_buffer[0] = 0x01; + t248->send_buffer[1] = 0x00; + if ((r2 = t300rs_send_int(t248))) + return r2; + + return 0; +} + +static int t248_close(void *data, int open_mode) +{ + struct t300rs_device_entry *t248 = data; + + if (!t248) + return -ENODEV; + + if (open_mode) + t248_send_close(t248); + + t248->close(t248->input_dev); + return 0; +} + +int t248_wheel_init(struct tmff2_device_entry *tmff2, int open_mode) +{ + struct t300rs_device_entry *t248 = kzalloc(sizeof(struct t300rs_device_entry), GFP_KERNEL); + struct list_head *report_list; + int ret; + + + if (!t248) { + ret = -ENOMEM; + goto t248_err; + } + + t248->hdev = tmff2->hdev; + t248->input_dev = tmff2->input_dev; + t248->usbdev = to_usb_device(tmff2->hdev->dev.parent->parent); + t248->buffer_length = T248_BUFFER_LENGTH; + + t248->send_buffer = kzalloc(t248->buffer_length, GFP_KERNEL); + if (!t248->send_buffer) { + ret = -ENOMEM; + goto send_err; + } + + report_list = &t248->hdev->report_enum[HID_OUTPUT_REPORT].report_list; + t248->report = list_entry(report_list->next, struct hid_report, list); + t248->ff_field = t248->report->field[0]; + + t248->open = t248->input_dev->open; + t248->close = t248->input_dev->close; + + if ((ret = t248_interrupts(t248))) + goto interrupt_err; + + /* everything went OK */ + tmff2->data = t248; + tmff2->params = t248_params; + tmff2->max_effects = T248_MAX_EFFECTS; + memcpy(tmff2->supported_effects, t248_effects, sizeof(t248_effects)); + + if (!open_mode) + t248_send_open(t248); + + hid_info(t248->hdev, "force feedback for T248\n"); + return 0; + +interrupt_err: +send_err: + kfree(t248); +t248_err: + hid_err(tmff2->hdev, "failed initializing T248\n"); + return ret; +} + +static __u8 *t248_wheel_fixup(struct hid_device *hdev, __u8 *rdesc, + unsigned int *rsize) +{ + rdesc = t248_pc_rdesc_fixed; + *rsize = sizeof(t248_pc_rdesc_fixed); + return rdesc; +} + +int t248_populate_api(struct tmff2_device_entry *tmff2) +{ + tmff2->play_effect = t300rs_play_effect; + tmff2->upload_effect = t300rs_upload_effect; + tmff2->update_effect = t300rs_update_effect; + tmff2->stop_effect = t300rs_stop_effect; + + tmff2->set_gain = t300rs_set_gain; + tmff2->set_autocenter = t300rs_set_autocenter; + /* T248 only has 900 degree range, instead of T300RS 1080 */ + tmff2->set_range = t248_set_range; + tmff2->wheel_fixup = t248_wheel_fixup; + + tmff2->open = t248_open; + tmff2->close = t248_close; + + tmff2->wheel_init = t248_wheel_init; + tmff2->wheel_destroy = t248_wheel_destroy; + + return 0; +} diff --git a/src/tmt300rs/hid-tmt300rs.c b/src/tmt300rs/hid-tmt300rs.c new file mode 100644 index 0000000..20bb239 --- /dev/null +++ b/src/tmt300rs/hid-tmt300rs.c @@ -0,0 +1,1714 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#include +#include +#include "../hid-tmff2.h" + +#define T300RS_MAX_EFFECTS 16 +#define T300RS_NORM_BUFFER_LENGTH 63 +#define T300RS_PS4_BUFFER_LENGTH 31 + +#define T300RS_DEFAULT_ATTACHMENT 0x06 +#define T300RS_F1_ATTACHMENT 0x03 + +static const unsigned long t300rs_params = + PARAM_SPRING_LEVEL + | PARAM_DAMPER_LEVEL + | PARAM_FRICTION_LEVEL + | PARAM_GAIN + | PARAM_RANGE + | PARAM_ALT_MODE + ; + +static const signed short t300rs_effects[] = { + FF_CONSTANT, + FF_RAMP, + FF_SPRING, + FF_DAMPER, + FF_FRICTION, + FF_INERTIA, + FF_PERIODIC, + FF_SINE, + FF_TRIANGLE, + FF_SQUARE, + FF_SAW_UP, + FF_SAW_DOWN, + FF_AUTOCENTER, + FF_GAIN, + -1 +}; + +struct __packed t300rs_fw_response { + uint8_t unused1[2]; + uint8_t fw_version; + uint8_t unused2; +}; + + +struct __packed t300rs_packet_header { + uint8_t zero1; + uint8_t id; + uint8_t code; +}; + +struct __packed t300rs_setup_header { + uint8_t cmd; + uint8_t code; +}; + +struct __packed t300rs_packet_envelope { + uint16_t attack_length; + uint16_t attack_level; + uint16_t fade_length; + uint16_t fade_level; +}; + +struct __packed t300rs_packet_timing { + uint8_t start_marker; + uint16_t duration; + uint8_t zero1[2]; + uint16_t offset; + uint8_t zero2; + uint16_t end_marker; +}; + +struct usb_ctrlrequest t300rs_fw_request = { + .bRequestType = 0xc1, + .bRequest = 86, + .wValue = 0, + .wIndex = 0, + .wLength = 8 +}; + +struct t300rs_data { + unsigned long quirks; + void *device_props; +}; + +static u8 t300rs_rdesc_nrm_fixed[] = { + 0x05, 0x01, /* Usage page (Generic Desktop) */ + 0x09, 0x04, /* Usage (Joystick) */ + 0xa1, 0x01, /* Collection (Application) */ + 0x09, 0x01, /* Usage (Pointer) */ + 0xa1, 0x00, /* Collection (Physical) */ + 0x85, 0x07, /* Report ID (7) */ + 0x09, 0x30, /* Usage (X) */ + 0x15, 0x00, /* Logical minimum (0) */ + 0x27, 0xff, 0xff, 0x00, 0x00, /* Logical maximum (65535) */ + 0x35, 0x00, /* Physical minimum (0) */ + 0x47, 0xff, 0xff, 0x00, 0x00, /* Physical maximum (65535) */ + 0x75, 0x10, /* Report size (16) */ + 0x95, 0x01, /* Report count (1) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x09, 0x35, /* Usage (Rz) (Brake) */ + 0x26, 0xff, 0x03, /* Logical maximum (1023) */ + 0x46, 0xff, 0x03, /* Physical maximum (1023) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x09, 0x32, /* Usage (Z) (Gas) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x09, 0x31, /* Usage (Y) (Clutch) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x81, 0x03, /* Input (Variable, Absolute, Constant) */ + 0x05, 0x09, /* Usage page (Button) */ + 0x19, 0x01, /* Usage minimum (1) */ + 0x29, 0x0d, /* Usage maximum (13) */ + 0x25, 0x01, /* Logical maximum (1) */ + 0x45, 0x01, /* Physical maximum (1) */ + 0x75, 0x01, /* Report size (1) */ + 0x95, 0x0d, /* Report count (13) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x75, 0x0b, /* Report size (13) */ + 0x95, 0x01, /* Report count (1) */ + 0x81, 0x03, /* Usage (Variable, Absolute, Constant) */ + 0x05, 0x01, /* Usage page (Generic Desktop) */ + 0x09, 0x39, /* Usage (Hat Switch) */ + 0x25, 0x07, /* Logical maximum (7) */ + 0x46, 0x3b, 0x01, /* Physical maximum (315) */ + 0x55, 0x00, /* Unit exponent (0) */ + 0x65, 0x14, /* Unit (Eng Rot, Angular Pos) */ + 0x75, 0x04, /* Report size (4) */ + 0x81, 0x42, /* Input (Variable, Absolute, NullState) */ + 0x65, 0x00, /* Unit (None) */ + 0x81, 0x03, /* Input (Variable, Absolute, Constant) */ + 0x85, 0x60, /* Report ID (96), prev 10 */ + 0x06, 0x00, 0xff, /* Usage page (Vendor 1) */ + 0x09, 0x60, /* Usage (96), prev 10 */ + 0x75, 0x08, /* Report size (8) */ + 0x95, 0x3f, /* Report count (63) */ + 0x26, 0xff, 0x7f, /* Logical maximum (32767) */ + 0x15, 0x00, /* Logical minimum (0) */ + 0x46, 0xff, 0x7f, /* Physical maximum (32767) */ + 0x36, 0x00, 0x80, /* Physical minimum (-32768) */ + 0x91, 0x02, /* Output (Variable, Absolute) */ + 0x85, 0x02, /* Report ID (2) */ + 0x09, 0x02, /* Usage (2) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x09, 0x14, /* Usage (20) */ + 0x85, 0x14, /* Report ID (20) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0xc0, /* End collection */ + 0xc0, /* End collection */ +}; + +static u8 t300rs_rdesc_adv_fixed[] = { + 0x05, 0x01, /* Usage page (Generic Desktop) */ + 0x09, 0x04, /* Usage (Joystick) */ + 0xa1, 0x01, /* Collection (Application) */ + 0x09, 0x01, /* Usage (Pointer) */ + 0xa1, 0x00, /* Collection (Physical) */ + 0x85, 0x07, /* Report ID (7) */ + 0x09, 0x30, /* Usage (X) */ + 0x15, 0x00, /* Logical minimum (0) */ + 0x27, 0xff, 0xff, 0x00, 0x00, /* Logical maximum (65535) */ + 0x35, 0x00, /* Physical minimum (0) */ + 0x47, 0xff, 0xff, 0x00, 0x00, /* Physical maximum (65535) */ + 0x75, 0x10, /* Report size (16) */ + 0x95, 0x01, /* Report count (1) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x09, 0x31, /* Usage (Y) */ + 0x26, 0xff, 0x03, /* Logical maximum (1023) */ + 0x46, 0xff, 0x03, /* Physical maximum (1023) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x09, 0x35, /* Usage (Rz) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x09, 0x36, /* Usage (Slider) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x81, 0x03, /* Input (Variable, Absolute, Constant) ? */ + 0x05, 0x09, /* Usage page (Button) */ + 0x19, 0x01, /* Usage minimum (1) */ + 0x29, 0x19, /* Usage maximum (25) */ + 0x25, 0x01, /* Logical maximum (1) */ + 0x45, 0x01, /* Physical maximum (1) */ + 0x75, 0x01, /* Report size (1) */ + 0x95, 0x19, /* Report count (25) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x75, 0x03, /* Report size (3) */ + 0x95, 0x01, /* Report count (1) */ + 0x81, 0x03, /* Input (Variable, Absolute, Constant) */ + 0x05, 0x01, /* Usage page (Generic Desktop) */ + 0x09, 0x39, /* Usage (Hat Switch) */ + 0x25, 0x07, /* Logical maximum (7) */ + 0x46, 0x3b, 0x01, /* Physical maximum (315) */ + 0x55, 0x00, /* Unit exponent (0) */ + 0x65, 0x14, /* Unit (Eng Rot, Angular Pos) */ + 0x75, 0x04, /* Report size (4) */ + 0x81, 0x42, /* Input (Variable, Absolute, NullState) */ + 0x65, 0x00, /* Unit (None) */ + 0x85, 0x60, /* Report ID (96), prev 10 */ + 0x06, 0x00, 0xff, /* Usage page (Vendor 1) */ + 0x09, 0x60, /* Usage (96), prev 10 (why?) */ + 0x75, 0x08, /* Report size (8) */ + 0x95, 0x3f, /* Report count (63) */ + 0x26, 0xff, 0x00, /* Logical maximum (255) */ + 0x46, 0xff, 0x00, /* Physical maximum (255) */ + 0x91, 0x02, /* Output (Variable, Absolute) */ + 0x85, 0x02, /* Report ID (2) */ + 0x09, 0x02, /* Usage (Mouse) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x09, 0x14, /* Usage (20) */ + 0x85, 0x14, /* Report ID (20) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0xc0, /* End collection */ + 0xc0, /* End collection */ +}; + +static u8 t300rs_rdesc_ps4_fixed[] = { + 0x05, 0x01, /* Usage page (Generic Desktop) */ + 0x09, 0x05, /* Usage (GamePad) */ + 0xa1, 0x01, /* Collection (Application) */ + 0x85, 0x01, /* Report ID (1) */ + 0x09, 0x00, /* Usage (U) (was X) */ + 0x09, 0x00, /* Usage (U) (was Y) */ + 0x09, 0x00, /* Usage (U) (was Z) */ + 0x09, 0x00, /* Usage (U) (was Rz)*/ + 0x15, 0x00, /* Logical minimum (0) */ + 0x26, 0xff, 0x00, /* Logical maximum (255) */ + 0x75, 0x08, /* Report size (8) */ + 0x95, 0x04, /* Report count (4) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x09, 0x39, /* Usage (Hat Switch) */ + 0x15, 0x00, /* Logical minimum (0) */ + 0x25, 0x07, /* Logical maximum (7)*/ + 0x35, 0x00, /* Physical minimum (0) */ + 0x46, 0x3b, 0x01, /* Physical maximum (315) */ + 0x65, 0x14, /* Unit (Eng Rot, Angular Pos) */ + 0x75, 0x04, /* Report size (4) */ + 0x95, 0x01, /* Report count (1) */ + 0x81, 0x42, /* Input (Variable, Absolute, NullState) */ + 0x65, 0x00, /* Input (None) */ + 0x05, 0x09, /* Usage page (Button) */ + 0x19, 0x01, /* Usage minimum (1) */ + 0x29, 0x0e, /* Usage maximum (14) */ + 0x15, 0x00, /* Logical minimum (0) */ + 0x25, 0x01, /* Logical maximum (1) */ + 0x75, 0x01, /* Report size (1) */ + 0x95, 0x0e, /* Report size (14) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x06, 0x00, 0xff, /* Usage page (Vendor 1) */ + 0x09, 0x20, /* Usage (32) */ + 0x75, 0x06, /* Report size (6) */ + 0x95, 0x01, /* Report count (1) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x05, 0x01, /* Usage page (Generic Desktop) */ + 0x09, 0x00, /* Usage (U) (was Rx)*/ + 0x09, 0x00, /* Usage (U) (was Ry) */ + 0x15, 0x00, /* Logical minimum (0) */ + 0x26, 0xff, 0x00, /* Logical maximum (255) */ + 0x75, 0x08, /* Report size (8) */ + 0x95, 0x02, /* Report count (2) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x05, 0x01, /* Usage page (Vendor 1) */ + /* constant zero? */ + 0x09, 0x00, /* Usage (33) */ + 0x95, 0x21, /* Report count (33) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + /* wheel */ + 0x09, 0x30, /* Usage (X) */ + 0x15, 0x00, /* Logical minimum (0) */ + 0x27, 0xff, 0xff, 0x00, 0x00, /* Logical maximum (65535) */ + 0x35, 0x00, /* Physical minimum (0) */ + 0x47, 0xff, 0xff, 0x00, 0x00, /* Physical maximum (65535) */ + 0x75, 0x10, /* Report size (16) */ + 0x95, 0x01, /* Report count (1) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + /* gas */ + 0x09, 0x31, /* Usage (Y) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + /* brake */ + 0x09, 0x32, /* Usage (Z) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + /* clutch */ + 0x09, 0x35, /* Usage (Rz) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + /* stick shifter (check model no) */ + 0x05, 0x09, /* Usage page (Button) */ + 0x19, 0x0f, /* Usage minimum (15) */ + 0x29, 0x17, /* Usage maximum (23) */ + 0x15, 0x00, /* Logical minimum (1) */ + 0x25, 0x01, /* Logical maximum (1) */ + 0x75, 0x01, /* Report size (1) */ + 0x95, 0x08, /* Report count (8) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + /* no clue */ + 0x05, 0x01, /* Usage page (Generic Desktop) */ + 0x09, 0x00, /* Usage (U) */ + 0x75, 0x08, /* Report size (8) */ + 0x95, 0x0c, /* Report count (12) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + /* continue unmodified */ + 0x06, 0x00, 0xff, /* Usage page (Vendor defined 1) */ + 0x85, 0x60, /* Report ID (5) (change to 0x60?) */ + 0x09, 0x60, /* Usage (34) (change to 0x60?) */ + 0x95, 0x1f, /* Report count (31) () */ + 0x91, 0x02, /* Output (Variable, Absolute) */ + 0x85, 0x03, /* Report ID (3) */ + 0x0a, 0x21, 0x27, /* ??? */ + 0x95, 0x2f, /* Report count (47) */ + 0xb1, 0x02, /* Feature (Data, Var, Abs) */ + 0xc0, /* End collection */ + + /* From here on out no clue */ + 0x06, 0xf0, + 0xff, 0x09, + 0x40, 0xa1, + 0x01, 0x85, + 0xf0, 0x09, + 0x47, 0x95, + 0x3f, 0xb1, + 0x02, 0x85, + 0xf1, 0x09, + 0x48, 0x95, + 0x3f, 0xb1, + 0x02, 0x85, + 0xf2, 0x09, + 0x49, 0x95, + 0x0f, 0xb1, + 0x02, 0x85, + 0xf3, 0x0a, + 0x01, 0x47, + 0x95, 0x07, + 0xb1, 0x02, + 0xc0, +}; + +static u8 spring_values[] = { + 0xa6, 0x6a, 0xa6, 0x6a, 0xfe, + 0xff, 0xfe, 0xff, 0xfe, 0xff, + 0xfe, 0xff, 0xdf, 0x58, 0xa6, + 0x6a, 0x06 +}; + +static u8 damper_values[] = { + 0xfc, 0x7f, 0xfc, 0x7f, 0xfe, + 0xff, 0xfe, 0xff, 0xfe, 0xff, + 0xfe, 0xff, 0xfc, 0x7f, 0xfc, + 0x7f, 0x07 +}; + +int t300rs_send_buf(struct t300rs_device_entry *t300rs, u8 *send_buffer, size_t len) +{ + int i; + /* check that send_buffer fits into our report */ + if (len > t300rs->buffer_length) + return -EINVAL; + + /* fill with actual data */ + for (i = 0; i < len; ++i) + t300rs->ff_field->value[i] = send_buffer[i]; + + /* fill the rest with zeroes */ + for (i = len; i < t300rs->buffer_length; ++i) + t300rs->ff_field->value[i] = 0; + + hid_hw_request(t300rs->hdev, t300rs->report, HID_REQ_SET_REPORT); + return 0; +} + +int t300rs_send_int(struct t300rs_device_entry *t300rs) +{ + t300rs_send_buf(t300rs, t300rs->send_buffer, t300rs->buffer_length); + memset(t300rs->send_buffer, 0, t300rs->buffer_length); + + return 0; +} + +static void t300rs_fill_header(struct t300rs_packet_header *packet_header, + uint8_t id, uint8_t code) +{ + packet_header->id = id + 1; + packet_header->code = code; +} + +int t300rs_play_effect(void *data, struct tmff2_effect_state *state) +{ + struct t300rs_device_entry *t300rs = data; + struct __packed t300rs_packet_play { + struct t300rs_packet_header header; + uint8_t value; + } *play_packet = (struct t300rs_packet_play *)t300rs->send_buffer; + + int ret; + + + t300rs_fill_header(&play_packet->header, state->effect.id, 0x89); + play_packet->value = 0x01; + + ret = t300rs_send_int(t300rs); + if (ret) + hid_err(t300rs->hdev, "failed starting effect play\n"); + + return ret; +} + +int t300rs_stop_effect(void *data, struct tmff2_effect_state *state) +{ + struct t300rs_device_entry *t300rs = data; + struct __packed t300rs_packet_stop { + struct t300rs_packet_header header; + uint8_t value; + } *stop_packet = (struct t300rs_packet_stop *)t300rs->send_buffer; + + int ret; + + + t300rs_fill_header(&stop_packet->header, state->effect.id, 0x89); + + ret = t300rs_send_int(t300rs); + if (ret) + hid_err(t300rs->hdev, "failed stopping effect play\n"); + + return ret; +} + +static void t300rs_fill_envelope(struct t300rs_packet_envelope *packet_envelope, + int16_t level, uint16_t duration, struct ff_envelope *envelope) +{ + uint16_t attack_length = (duration * envelope->attack_length) / 0x7fff; + uint16_t attack_level = (level * envelope->attack_level) / 0x7fff; + uint16_t fade_length = (duration * envelope->fade_length) / 0x7fff; + uint16_t fade_level = (level * envelope->fade_level) / 0x7fff; + + packet_envelope->attack_length = cpu_to_le16(attack_length); + packet_envelope->attack_level = cpu_to_le16(attack_level); + packet_envelope->fade_length = cpu_to_le16(fade_length); + packet_envelope->fade_level = cpu_to_le16(fade_level); +} + +static void t300rs_fill_timing(struct t300rs_packet_timing *packet_timing, + uint16_t duration, uint16_t offset){ + packet_timing->start_marker = 0x4f; + + packet_timing->duration = cpu_to_le16(duration); + packet_timing->offset = cpu_to_le16(offset); + + packet_timing->end_marker = 0xffff; +} + +static int t300rs_update_envelope(struct t300rs_device_entry *t300rs, + struct tmff2_effect_state *state, + int16_t level, + uint16_t duration, + uint8_t id, + struct ff_envelope envelope, + struct ff_envelope envelope_old + ) +{ + struct __packed t300rs_packet_mod_envelope { + struct t300rs_packet_header header; + uint8_t attribute; + uint16_t value; + } *packet_mod_envelope = (struct t300rs_packet_mod_envelope *)t300rs->send_buffer; + + uint16_t attack_length, attack_level, fade_length, fade_level; + int ret = 0; + + duration = duration - 1; + + attack_length = (duration * envelope.attack_length) / 0x7fff; + attack_level = (level * envelope.attack_level) / 0x7fff; + fade_length = (duration * envelope.fade_length) / 0x7fff; + fade_level = (level * envelope.fade_level) / 0x7fff; + + if (envelope.attack_length != envelope_old.attack_length) { + t300rs_fill_header(&packet_mod_envelope->header, id, 0x31); + packet_mod_envelope->attribute = 0x81; + packet_mod_envelope->value = cpu_to_le16(attack_length); + + ret = t300rs_send_int(t300rs); + if (ret) { + hid_err(t300rs->hdev, "failed modifying effect envelope\n"); + goto error; + } + } + + if (envelope.attack_level != envelope_old.attack_level) { + t300rs_fill_header(&packet_mod_envelope->header, id, 0x31); + packet_mod_envelope->attribute = 0x82; + packet_mod_envelope->value = cpu_to_le16(attack_level); + + ret = t300rs_send_int(t300rs); + if (ret) { + hid_err(t300rs->hdev, "failed modifying effect envelope\n"); + goto error; + } + } + + if (envelope.fade_length != envelope_old.fade_length) { + t300rs_fill_header(&packet_mod_envelope->header, id, 0x31); + packet_mod_envelope->attribute = 0x84; + packet_mod_envelope->value = cpu_to_le16(fade_length); + + ret = t300rs_send_int(t300rs); + if (ret) { + hid_err(t300rs->hdev, "failed modifying effect envelope\n"); + goto error; + } + } + + if (envelope.fade_level != envelope_old.fade_level) { + t300rs_fill_header(&packet_mod_envelope->header, id, 0x31); + packet_mod_envelope->attribute = 0x88; + packet_mod_envelope->value = cpu_to_le16(fade_level); + + ret = t300rs_send_int(t300rs); + if (ret) { + hid_err(t300rs->hdev, "failed modifying effect envelope\n"); + goto error; + } + } + +error: + return ret; +} + +static int t300rs_update_simple_duration(struct t300rs_device_entry *t300rs, + struct tmff2_effect_state *state, unsigned type) +{ + struct ff_effect effect = state->effect; + struct __packed t300rs_packet_mod_duration { + struct t300rs_packet_header header; + uint16_t marker; + uint16_t duration; + } *packet_mod_duration = (struct t300rs_packet_mod_duration *)t300rs->send_buffer; + + uint16_t duration; + int ret = 0; + + duration = effect.replay.length - 1; + + t300rs_fill_header(&packet_mod_duration->header, effect.id, 0x49); + packet_mod_duration->marker = cpu_to_le16(0x4100 + type); + packet_mod_duration->duration = cpu_to_le16(duration); + + ret = t300rs_send_int(t300rs); + if (ret) { + hid_err(t300rs->hdev, "failed modifying duration\n"); + goto error; + } + +error: + return ret; +} + +static int t300rs_update_periodic_duration(struct t300rs_device_entry *t300rs, + struct tmff2_effect_state *state, unsigned type) +{ + struct ff_effect effect = state->effect; + struct __packed t300rs_packet_mod_duration { + struct t300rs_packet_header header; + uint8_t type; + uint8_t marker; + uint16_t duration; + } *packet_mod_duration = (struct t300rs_packet_mod_duration *)t300rs->send_buffer; + + uint16_t duration; + int ret = 0; + + duration = effect.replay.length - 1; + + t300rs_fill_header(&packet_mod_duration->header, effect.id, 0x49); + packet_mod_duration->type = type; + packet_mod_duration->marker = 0x41; + packet_mod_duration->duration = cpu_to_le16(duration); + + ret = t300rs_send_int(t300rs); + if (ret) { + hid_err(t300rs->hdev, "failed modifying duration\n"); + goto error; + } + +error: + return ret; +} + +static int t300rs_update_ramp_duration(struct t300rs_device_entry *t300rs, + struct tmff2_effect_state *state) +{ + struct ff_effect effect = state->effect; + struct __packed t300rs_packet_mod_duration { + struct t300rs_packet_header header; + uint8_t marker0; + uint16_t duration0; + uint8_t marker1; + uint8_t marker2; + uint16_t duration1; + } *packet_mod_duration = (struct t300rs_packet_mod_duration *)t300rs->send_buffer; + + uint16_t duration; + int ret = 0; + + duration = effect.replay.length - 1; + + t300rs_fill_header(&packet_mod_duration->header, effect.id, 0x4e); + packet_mod_duration->marker0 = 0x08; + packet_mod_duration->duration0 = cpu_to_le16(duration); + packet_mod_duration->marker1 = 0x05; + packet_mod_duration->marker2 = 0x41; + packet_mod_duration->duration1 = cpu_to_le16(duration); + + ret = t300rs_send_int(t300rs); + if (ret) { + hid_err(t300rs->hdev, "failed modifying duration\n"); + goto error; + } + +error: + return ret; +} + +static int t300rs_update_constant(struct t300rs_device_entry *t300rs, + struct tmff2_effect_state *state) +{ + struct ff_effect effect = state->effect; + struct ff_effect old = state->old; + struct ff_constant_effect constant = effect.u.constant; + struct ff_constant_effect constant_old = old.u.constant; + struct __packed t300rs_packet_mod_constant { + struct t300rs_packet_header header; + uint16_t level; + } *packet_mod_constant = (struct t300rs_packet_mod_constant *)t300rs->send_buffer; + int ret; + int16_t level; + + level = (constant.level * fixp_sin16(effect.direction * 360 / 0x10000)) / 0x7fff; + + if ((constant.level != constant_old.level) || (effect.direction != old.direction)) { + + t300rs_fill_header(&packet_mod_constant->header, effect.id, 0x0a); + packet_mod_constant->level = cpu_to_le16(level); + + ret = t300rs_send_int(t300rs); + if (ret) { + hid_err(t300rs->hdev, "failed modifying constant effect\n"); + goto error; + } + + } + + ret = t300rs_update_envelope(t300rs, + state, + level, + effect.replay.length, + effect.id, + constant.envelope, + constant_old.envelope + ); + + if (ret) { + hid_err(t300rs->hdev, "failed modifying constant envelope\n"); + goto error; + } + + ret = t300rs_update_simple_duration(t300rs, state, 0x00); + if (ret) { + hid_err(t300rs->hdev, "failed modifying constant duration\n"); + goto error; + } + +error: + return ret; +} + +static int t300rs_update_ramp(struct t300rs_device_entry *t300rs, + struct tmff2_effect_state *state) +{ + struct ff_effect effect = state->effect; + struct ff_effect old = state->old; + struct ff_ramp_effect ramp = effect.u.ramp; + struct ff_ramp_effect ramp_old = old.u.ramp; + struct __packed t300rs_packet_mod_ramp { + struct t300rs_packet_header header; + uint8_t attribute; + uint16_t difference; + uint16_t level; + } *packet_mod_ramp = (struct t300rs_packet_mod_ramp *)t300rs->send_buffer; + + int ret; + + uint16_t difference, top, bottom; + int16_t level; + + top = ramp.end_level > ramp.start_level ? ramp.end_level : ramp.start_level; + bottom = ramp.end_level > ramp.start_level ? ramp.start_level : ramp.end_level; + + + difference = ((top - bottom) * fixp_sin16(effect.direction * 360 / 0x10000)) / 0x7fff; + + + level = (top * fixp_sin16(effect.direction * 360 / 0x10000)) / 0x7fff; + + if ((ramp.start_level != ramp_old.start_level) + || (ramp.end_level != ramp_old.end_level) + || (effect.direction != old.direction)) { + + t300rs_fill_header(&packet_mod_ramp->header, effect.id, 0x0e); + packet_mod_ramp->attribute = 0x03; + packet_mod_ramp->difference = cpu_to_le16(difference); + packet_mod_ramp->level = cpu_to_le16(level); + + ret = t300rs_send_int(t300rs); + if (ret) { + hid_err(t300rs->hdev, "failed modifying ramp effect\n"); + goto error; + } + + } + + ret = t300rs_update_envelope(t300rs, + state, + level, + effect.replay.length, + effect.id, + ramp.envelope, + ramp_old.envelope + ); + + + if (ret) { + hid_err(t300rs->hdev, "failed modifying ramp envelope\n"); + goto error; + } + + ret = t300rs_update_ramp_duration(t300rs, state); + if (ret) { + hid_err(t300rs->hdev, "failed modifying ramp duration\n"); + goto error; + } + +error: + return ret; +} + +static int t300rs_update_damper(struct t300rs_device_entry *t300rs, + struct tmff2_effect_state *state) +{ + struct ff_effect effect = state->effect; + struct ff_effect old = state->old; + struct ff_condition_effect damper = effect.u.condition[0]; + struct ff_condition_effect damper_old = old.u.condition[0]; + struct __packed t300rs_packet_mod_damper { + struct t300rs_packet_header header; + uint8_t attribute; + uint16_t value0; + uint16_t value1; + } *packet_mod_damper = (struct t300rs_packet_mod_damper *)t300rs->send_buffer; + + int ret, input_level; + + input_level = damper_level; + if (state->effect.type == FF_FRICTION) + input_level = friction_level; + + if (state->effect.type == FF_SPRING) + input_level = spring_level; + + if (damper.right_coeff != damper_old.right_coeff) { + int16_t coeff = damper.right_coeff * input_level / 100; + + t300rs_fill_header(&packet_mod_damper->header, effect.id, 0x0e); + packet_mod_damper->attribute = 0x41; + packet_mod_damper->value0 = cpu_to_le16(coeff); + + ret = t300rs_send_int(t300rs); + if (ret) { + hid_err(t300rs->hdev, "failed modifying damper rc\n"); + goto error; + } + + } + + if (damper.left_coeff != damper_old.left_coeff) { + int16_t coeff = damper.left_coeff * input_level / 100; + + t300rs_fill_header(&packet_mod_damper->header, effect.id, 0x0e); + packet_mod_damper->attribute = 0x42; + packet_mod_damper->value0 = cpu_to_le16(coeff); + + ret = t300rs_send_int(t300rs); + if (ret) { + hid_err(t300rs->hdev, "failed modifying damper lc\n"); + goto error; + } + + } + + if ((damper.deadband != damper_old.deadband) + || (damper.center != damper_old.center)) { + + uint16_t right_deadband = 0xfffe - damper.deadband - damper.center; + uint16_t left_deadband = 0xfffe - damper.deadband + damper.center; + + t300rs_fill_header(&packet_mod_damper->header, effect.id, 0x0e); + packet_mod_damper->attribute = 0x4c; + packet_mod_damper->value0 = cpu_to_le16(right_deadband); + packet_mod_damper->value1 = cpu_to_le16(left_deadband); + + ret = t300rs_send_int(t300rs); + if (ret) { + hid_err(t300rs->hdev, "failed modifying damper deadband\n"); + goto error; + } + + } + + ret = t300rs_update_simple_duration(t300rs, state, 0x06); + if (ret) { + hid_err(t300rs->hdev, "failed modifying damper duration\n"); + goto error; + } + +error: + return ret; +} + +static int t300rs_update_spring(struct t300rs_device_entry *t300rs, + struct tmff2_effect_state *state) +{ + return t300rs_update_damper(t300rs, state); +} + + +static int t300rs_update_periodic(struct t300rs_device_entry *t300rs, + struct tmff2_effect_state *state) +{ + struct ff_effect effect = state->effect; + struct ff_effect old = state->old; + struct ff_periodic_effect periodic = effect.u.periodic; + struct ff_periodic_effect periodic_old = old.u.periodic; + struct __packed t300rs_packet_mod_periodic { + struct t300rs_packet_header header; + uint8_t attribute; + uint16_t value; + } *packet_mod_periodic = (struct t300rs_packet_mod_periodic *)t300rs->send_buffer; + + int ret; + int16_t magnitude, phase; + + magnitude = (periodic.magnitude * fixp_sin16(effect.direction * 360 / 0x10000)) / 0x7fff; + phase = periodic.phase; + + if ((periodic.magnitude != periodic_old.magnitude) + || (effect.direction != old.direction)) { + + t300rs_fill_header(&packet_mod_periodic->header, effect.id, 0x0e); + packet_mod_periodic->attribute = 0x01; + packet_mod_periodic->value = cpu_to_le16(magnitude); + + ret = t300rs_send_int(t300rs); + if (ret) { + hid_err(t300rs->hdev, "failed modifying periodic magnitude\n"); + goto error; + } + + } + + if (periodic.offset != periodic_old.offset) { + uint16_t offset = periodic.offset; + + t300rs_fill_header(&packet_mod_periodic->header, effect.id, 0x0e); + packet_mod_periodic->attribute = 0x02; + packet_mod_periodic->value = cpu_to_le16(offset); + + ret = t300rs_send_int(t300rs); + if (ret) { + hid_err(t300rs->hdev, "failed modifying periodic offset\n"); + goto error; + } + + } + + if (periodic.phase != periodic_old.phase) { + + t300rs_fill_header(&packet_mod_periodic->header, effect.id, 0x0e); + packet_mod_periodic->attribute = 0x04; + packet_mod_periodic->value = cpu_to_le16(phase); + + ret = t300rs_send_int(t300rs); + if (ret) { + hid_err(t300rs->hdev, "failed modifying periodic phase\n"); + goto error; + } + + } + + if (periodic.period != periodic_old.period) { + int16_t period = periodic.period; + + t300rs_fill_header(&packet_mod_periodic->header, effect.id, 0x0e); + packet_mod_periodic->attribute = 0x08; + packet_mod_periodic->value = cpu_to_le16(period); + + ret = t300rs_send_int(t300rs); + if (ret) { + hid_err(t300rs->hdev, "failed modifying periodic period\n"); + goto error; + } + + } + + ret = t300rs_update_envelope(t300rs, + state, + magnitude, + effect.replay.length, + effect.id, + periodic.envelope, + periodic_old.envelope + ); + + if (ret) { + hid_err(t300rs->hdev, "failed modifying periodic envelope\n"); + goto error; + } + + ret = t300rs_update_periodic_duration(t300rs, state, periodic.waveform - 0x57); + if (ret) { + hid_err(t300rs->hdev, "failed modifying periodic duration\n"); + goto error; + } + +error: + return ret; +} + +static int t300rs_upload_constant(struct t300rs_device_entry *t300rs, + struct tmff2_effect_state *state) +{ + struct ff_effect effect = state->effect; + struct ff_constant_effect constant = state->effect.u.constant; + struct __packed t300rs_packet_constant { + struct t300rs_packet_header header; + uint16_t level; + struct t300rs_packet_envelope envelope; + uint8_t zero; + struct t300rs_packet_timing timing; + } *packet_constant = (struct t300rs_packet_constant *)t300rs->send_buffer; + + int16_t level; + uint16_t duration, offset; + + int ret; + + level = (constant.level * fixp_sin16(effect.direction * 360 / 0x10000)) / 0x7fff; + duration = effect.replay.length - 1; + + offset = effect.replay.delay; + + t300rs_fill_header(&packet_constant->header, effect.id, 0x6a); + + packet_constant->level = cpu_to_le16(level); + + t300rs_fill_envelope(&packet_constant->envelope, level, duration, + &constant.envelope); + t300rs_fill_timing(&packet_constant->timing, duration, offset); + + ret = t300rs_send_int(t300rs); + if (ret) + hid_err(t300rs->hdev, "failed uploading constant effect\n"); + + return ret; +} + +static int t300rs_upload_ramp(struct t300rs_device_entry *t300rs, + struct tmff2_effect_state *state) +{ + struct ff_effect effect = state->effect; + struct ff_ramp_effect ramp = state->effect.u.ramp; + struct __packed t300rs_packet_ramp { + struct t300rs_packet_header header; + uint16_t difference; + uint16_t level; + uint8_t zero1[2]; + uint16_t duration; + uint16_t marker; + struct t300rs_packet_envelope envelope; + uint8_t direction; + struct t300rs_packet_timing timing; + } *packet_ramp = (struct t300rs_packet_ramp *)t300rs->send_buffer; + + int ret; + uint16_t difference, offset, top, bottom, duration; + int16_t level; + + duration = effect.replay.length - 1; + + top = ramp.end_level > ramp.start_level ? ramp.end_level : ramp.start_level; + bottom = ramp.end_level > ramp.start_level ? ramp.start_level : ramp.end_level; + + + difference = ((top - bottom) * fixp_sin16(effect.direction * 360 / 0x10000)) / 0x7fff; + level = (top * fixp_sin16(effect.direction * 360 / 0x10000)) / 0x7fff; + offset = effect.replay.delay; + + + t300rs_fill_header(&packet_ramp->header, effect.id, 0x6b); + + packet_ramp->difference = cpu_to_le16(difference); + packet_ramp->level = cpu_to_le16(level); + packet_ramp->duration = cpu_to_le16(duration); + + packet_ramp->marker = cpu_to_le16(0x8000); + + t300rs_fill_envelope(&packet_ramp->envelope, level, duration, + &ramp.envelope); + + packet_ramp->direction = ramp.end_level > ramp.start_level ? 0x04 : 0x05; + t300rs_fill_timing(&packet_ramp->timing, duration, offset); + + ret = t300rs_send_int(t300rs); + if (ret) + hid_err(t300rs->hdev, "failed uploading ramp"); + + return ret; +} + +static int t300rs_upload_spring(struct t300rs_device_entry *t300rs, + struct tmff2_effect_state *state) +{ + struct ff_effect effect = state->effect; + /* we only care about the first axis */ + struct ff_condition_effect spring = state->effect.u.condition[0]; + struct __packed t300rs_packet_spring { + struct t300rs_packet_header header; + uint16_t right_coeff; + uint16_t left_coeff; + uint16_t right_deadband; + uint16_t left_deadband; + uint8_t spring_start[17]; + struct t300rs_packet_timing timing; + } *packet_spring = (struct t300rs_packet_spring *)t300rs->send_buffer; + + int ret; + uint16_t duration, right_coeff, left_coeff, right_deadband, left_deadband, offset; + + duration = effect.replay.length - 1; + + right_coeff = spring.right_coeff * spring_level / 100; + left_coeff = spring.left_coeff * spring_level / 100; + + right_deadband = 0xfffe - spring.deadband - spring.center; + left_deadband = 0xfffe - spring.deadband + spring.center; + + offset = effect.replay.delay; + + t300rs_fill_header(&packet_spring->header, effect.id, 0x64); + + packet_spring->right_coeff = cpu_to_le16(right_coeff); + packet_spring->left_coeff = cpu_to_le16(left_coeff); + packet_spring->right_deadband = cpu_to_le16(right_deadband); + packet_spring->left_deadband = cpu_to_le16(left_deadband); + + memcpy(&packet_spring->spring_start, spring_values, ARRAY_SIZE(spring_values)); + t300rs_fill_timing(&packet_spring->timing, duration, offset); + + ret = t300rs_send_int(t300rs); + if (ret) + hid_err(t300rs->hdev, "failed uploading spring\n"); + + return ret; +} + +static int t300rs_upload_damper(struct t300rs_device_entry *t300rs, + struct tmff2_effect_state *state) +{ + + struct ff_effect effect = state->effect; + /* we only care about the first axis */ + struct ff_condition_effect spring = state->effect.u.condition[0]; + struct __packed t300rs_packet_damper { + struct t300rs_packet_header header; + uint16_t right_coeff; + uint16_t left_coeff; + uint16_t right_deadband; + uint16_t left_deadband; + uint8_t damper_start[17]; + struct t300rs_packet_timing timing; + } *packet_damper = (struct t300rs_packet_damper *)t300rs->send_buffer; + + int ret, input_level; + uint16_t duration, right_coeff, left_coeff, right_deadband, left_deadband, offset; + + duration = effect.replay.length - 1; + + input_level = damper_level; + if (state->effect.type == FF_FRICTION) + input_level = friction_level; + + right_coeff = spring.right_coeff * input_level / 100; + left_coeff = spring.left_coeff * input_level / 100; + + right_deadband = 0xfffe - spring.deadband - spring.center; + left_deadband = 0xfffe - spring.deadband + spring.center; + + offset = effect.replay.delay; + + t300rs_fill_header(&packet_damper->header, effect.id, 0x64); + + packet_damper->right_coeff = cpu_to_le16(right_coeff); + packet_damper->left_coeff = cpu_to_le16(left_coeff); + packet_damper->right_deadband = cpu_to_le16(right_deadband); + packet_damper->left_deadband = cpu_to_le16(left_deadband); + + memcpy(&packet_damper->damper_start, damper_values, ARRAY_SIZE(damper_values)); + t300rs_fill_timing(&packet_damper->timing, duration, offset); + + ret = t300rs_send_int(t300rs); + if (ret) + hid_err(t300rs->hdev, "failed uploading spring\n"); + + return ret; +} + +static int t300rs_upload_periodic(struct t300rs_device_entry *t300rs, + struct tmff2_effect_state *state) +{ + struct ff_effect effect = state->effect; + struct ff_periodic_effect periodic = state->effect.u.periodic; + struct __packed t300rs_packet_periodic { + struct t300rs_packet_header header; + uint16_t magnitude; + uint16_t periodic_offset; + uint16_t phase; + uint16_t period; + uint16_t marker; + struct t300rs_packet_envelope envelope; + uint8_t waveform; + struct t300rs_packet_timing timing; + } *packet_periodic = (struct t300rs_packet_periodic *)t300rs->send_buffer; + + int ret; + uint16_t duration, period, offset, periodic_offset; + int16_t phase, magnitude; + + duration = effect.replay.length - 1; + magnitude = (periodic.magnitude * fixp_sin16(effect.direction * 360 / 0x10000)) / 0x7fff; + + phase = periodic.phase; + periodic_offset = periodic.offset; + + period = periodic.period; + offset = effect.replay.delay; + + t300rs_fill_header(&packet_periodic->header, effect.id, 0x6b); + + packet_periodic->magnitude = cpu_to_le16(magnitude); + packet_periodic->periodic_offset = cpu_to_le16(periodic_offset); + packet_periodic->phase = cpu_to_le16(phase); + packet_periodic->period = cpu_to_le16(period); + + packet_periodic->marker = cpu_to_le16(0x8000); + + t300rs_fill_envelope(&packet_periodic->envelope, magnitude, duration, + &periodic.envelope); + + packet_periodic->waveform = periodic.waveform - 0x57; + + t300rs_fill_timing(&packet_periodic->timing, duration, offset); + + ret = t300rs_send_int(t300rs); + if (ret) + hid_err(t300rs->hdev, "failed uploading periodic effect"); + + return ret; +} + +int t300rs_update_effect(void *data, struct tmff2_effect_state *state) +{ + struct t300rs_device_entry *t300rs = data; + switch (state->effect.type) { + case FF_CONSTANT: + return t300rs_update_constant(t300rs, state); + case FF_RAMP: + return t300rs_update_ramp(t300rs, state); + case FF_SPRING: + return t300rs_update_spring(t300rs, state); + case FF_DAMPER: + case FF_FRICTION: + case FF_INERTIA: + return t300rs_update_damper(t300rs, state); + case FF_PERIODIC: + return t300rs_update_periodic(t300rs, state); + default: + hid_err(t300rs->hdev, "invalid effect type: %x", state->effect.type); + return -1; + } +} + +int t300rs_upload_effect(void *data, struct tmff2_effect_state *state) +{ + struct t300rs_device_entry *t300rs = data; + switch (state->effect.type) { + case FF_CONSTANT: + return t300rs_upload_constant(t300rs, state); + case FF_RAMP: + return t300rs_upload_ramp(t300rs, state); + case FF_SPRING: + return t300rs_upload_spring(t300rs, state); + case FF_DAMPER: + case FF_FRICTION: + case FF_INERTIA: + return t300rs_upload_damper(t300rs, state); + case FF_PERIODIC: + return t300rs_upload_periodic(t300rs, state); + default: + hid_err(t300rs->hdev, "invalid effect type: %x", state->effect.type); + return -1; + } +} + +static int t300rs_switch_mode(void *data, uint16_t mode) +{ + struct t300rs_device_entry *t300rs = data; + if (!t300rs) + return -ENODEV; + + if(t300rs->mode == mode) /* already in specified mode */ + return 0; + + if (mode == 0) + /* go to normal mode */ + usb_control_msg(t300rs->usbdev, + usb_sndctrlpipe(t300rs->usbdev, 0), + 83, 0x41, 5, 0, 0, 0, + USB_CTRL_SET_TIMEOUT + ); + else if (mode == 1) + /* go to advanced mode */ + usb_control_msg(t300rs->usbdev, + usb_sndctrlpipe(t300rs->usbdev, 0), + 83, 0x41, 3, 0, 0, 0, + USB_CTRL_SET_TIMEOUT + ); + else + hid_warn(t300rs->hdev, "mode %i not supported\n", mode); + + + return 0; +} + +static struct t300rs_alt_modes { + char *id; + char *label; + uint16_t mode; +} t300rs_modes[] = { + {"base", "T300RS base", 0}, + {"F1", "T300RS with F1 wheel attachment", 1} +}; + +static ssize_t t300rs_alt_mode_show(void *data, char *buf) +{ + struct t300rs_device_entry *t300rs = data; + ssize_t count = 0; + int i; + if (!t300rs) + return -ENODEV; + + if (t300rs->attachment != T300RS_F1_ATTACHMENT) + /* we only support one base mode */ + return scnprintf(buf, PAGE_SIZE, "%s: %s *\n", + t300rs_modes[0].id, t300rs_modes[0].label); + + for (i = 0; i < ARRAY_SIZE(t300rs_modes); ++i) { + count += scnprintf(buf + count, PAGE_SIZE - count, "%s: %s", + t300rs_modes[i].id, t300rs_modes[i].label); + + if (count >= PAGE_SIZE - 1) + return count; + + if (t300rs_modes[i].mode == t300rs->mode) + count += scnprintf(buf + count, PAGE_SIZE - count, " *\n"); + else + count += scnprintf(buf + count, PAGE_SIZE - count, "\n"); + + if (count >= PAGE_SIZE - 1) + return count; + } + + return count; +} + +static ssize_t t300rs_alt_mode_store(void *data, const char *buf, size_t count) +{ + struct t300rs_device_entry *t300rs = data; + int i, len, mode_len; + char *lbuf; + if (!t300rs) + return -ENODEV; + + if (t300rs->attachment != T300RS_F1_ATTACHMENT) + return count; /* don't do anything */ + + lbuf = kasprintf(GFP_KERNEL, "%s", buf); + if (!lbuf) + return -ENOMEM; + + len = strlen(buf); + for (i = 0; i < ARRAY_SIZE(t300rs_modes); ++i) { + mode_len = strlen(t300rs_modes[i].id); + if (mode_len > len) + continue; + + if (strncmp(lbuf, t300rs_modes[i].id, mode_len) == 0) { + t300rs_switch_mode(data, t300rs_modes[i].mode); + break; + } + } + + kfree(lbuf); + return count; +} + +int t300rs_set_autocenter(void *data, uint16_t value) +{ + struct t300rs_device_entry *t300rs = data; + struct __packed t300rs_packet_autocenter { + struct t300rs_setup_header header; + uint16_t value; + } *autocenter_packet; + int ret; + + if (!t300rs) + return -ENODEV; + + /* TODO: this should probably also use a separately allocated buffer? */ + autocenter_packet = (struct t300rs_packet_autocenter *)t300rs->send_buffer; + + autocenter_packet->header.cmd = 0x08; + autocenter_packet->header.code = 0x04; + autocenter_packet->value = cpu_to_le16(0x01); + + if ((ret = t300rs_send_int(t300rs))) { + hid_err(t300rs->hdev, "failed setting autocenter"); + return ret; + } + + autocenter_packet->header.cmd = 0x08; + autocenter_packet->header.code = 0x03; + + autocenter_packet->value = cpu_to_le16(value); + + if ((ret = t300rs_send_int(t300rs))) + hid_err(t300rs->hdev, "failed setting autocenter"); + + return ret; +} + +int t300rs_set_gain(void *data, uint16_t gain) +{ + struct t300rs_device_entry *t300rs = data; + struct __packed t300rs_packet_gain { + struct t300rs_setup_header header; + } *gain_packet; + int ret; + + if (!t300rs) + return -ENODEV; + + gain_packet = (struct t300rs_packet_gain *)t300rs->send_buffer; + gain_packet->header.cmd = 0x02; + gain_packet->header.code = (gain >> 8) & 0xff; + + if ((ret = t300rs_send_int(t300rs))) + hid_err(t300rs->hdev, "failed setting gain: %i\n", ret); + + return ret; +} + +int t300rs_set_range(void *data, uint16_t value) +{ + struct t300rs_device_entry *t300rs = data; + /* it's important that we don't use t300rs->send_buffer, as range can be + * set from outside of the FFB environment, and we don't want to + * accidentally overwrite any data. */ + u8 *send_buffer = kzalloc(t300rs->buffer_length, GFP_KERNEL); + uint16_t scaled_value; + int ret; + + if (value < 40) { + hid_info(t300rs->hdev, "value %i too small, clamping to 40\n", value); + value = 40; + } + + if (value > 1080) { + hid_info(t300rs->hdev, "value %i too large, clamping to 1080\n", value); + value = 1080; + } + + if (!send_buffer) { + ret = -EINVAL; + hid_err(t300rs->hdev, "could not allocate send_buffer\n"); + goto err; + } + + scaled_value = value * 0x3c; + send_buffer[0] = 0x08; + send_buffer[1] = 0x11; + send_buffer[2] = scaled_value & 0xff; + send_buffer[3] = scaled_value >> 8; + + if ((ret = t300rs_send_buf(t300rs, send_buffer, t300rs->buffer_length))) + hid_warn(t300rs->hdev, "failed setting range\n"); + + /* since everythin went OK, update the current range */ + range = value; +err: + kfree(send_buffer); + return ret; +} + +static int t300rs_send_open(struct t300rs_device_entry *t300rs) +{ + struct __packed t300rs_packet_open { + struct t300rs_setup_header header; + } *open_packet; + + open_packet = (struct t300rs_packet_open *)t300rs->send_buffer; + open_packet->header.cmd = 0x01; + open_packet->header.code = 0x05; + + return t300rs_send_int(t300rs); +} + +static int t300rs_send_close(struct t300rs_device_entry *t300rs) +{ + struct __packed t300rs_packet_open { + struct t300rs_setup_header header; + } *open_packet; + + open_packet = (struct t300rs_packet_open *)t300rs->send_buffer; + open_packet->header.cmd = 0x01; + + return t300rs_send_int(t300rs); +} + +int t300rs_open(void *data, int open_mode) +{ + struct t300rs_device_entry *t300rs = data; + if (!t300rs) + return -ENODEV; + + if (open_mode && t300rs_send_open(t300rs)) + hid_warn(t300rs->hdev, "failed sending open command\n"); + + return t300rs->open(t300rs->input_dev); +} + +int t300rs_close(void *data, int open_mode) +{ + struct t300rs_device_entry *t300rs = data; + int ret; + + if (!t300rs) + return -ENODEV; + + if (open_mode && (ret = t300rs_send_close(t300rs))) + hid_warn(t300rs->hdev, "failed sending close command\n"); + + t300rs->close(t300rs->input_dev); + return ret; +} + +static int t300rs_check_firmware(struct t300rs_device_entry *t300rs) +{ + int ret; + struct t300rs_fw_response *fw_response = + kzalloc(sizeof(struct t300rs_fw_response), GFP_KERNEL); + + if (!fw_response) { + hid_err(t300rs->hdev, "could not allocate fw_response\n"); + return -ENOMEM; + } + + /* Fetch firmware version */ + ret = usb_control_msg(t300rs->usbdev, + usb_rcvctrlpipe(t300rs->usbdev, 0), + t300rs_fw_request.bRequest, + t300rs_fw_request.bRequestType, + t300rs_fw_request.wValue, + t300rs_fw_request.wIndex, + fw_response, + t300rs_fw_request.wLength, + USB_CTRL_SET_TIMEOUT + ); + + if (ret < 0) { + hid_err(t300rs->hdev, "could not fetch firmware version: %i\n", ret); + goto out; + } + + /* Educated guess */ + if (fw_response->fw_version < 31 && ret >= 0) { + hid_err(t300rs->hdev, + "firmware version %i is too old, please update.\n", + fw_response->fw_version + ); + + hid_info(t300rs->hdev, "note: this has to be done through Windows.\n"); + + ret = -EINVAL; + goto out; + } + + /* everything OK */ + ret = 0; + +out: + kfree(fw_response); + return ret; +} + +static int t300rs_get_attachment(struct t300rs_device_entry *t300rs) +{ + /* taken directly from hid_tminit */ + struct __packed t300rs_attachment_response + { + uint16_t type; + + union { + struct __packed { + uint16_t field0; + uint16_t field1; + uint8_t attachment; + uint8_t model; + uint16_t field2; + uint16_t field3; + uint16_t field4; + uint16_t field5; + } a; + + struct __packed { + uint16_t field0; + uint16_t field1; + uint8_t attachment; + uint8_t model; + } b; + }; + } *response = kzalloc(GFP_KERNEL, sizeof(struct t300rs_attachment_response)); + struct usb_ctrlrequest t300rs_attachment_rq = { + .bRequestType = 0xc1, + .bRequest = 73, + .wValue = 0, + .wIndex = 0, + .wLength = sizeof(struct t300rs_attachment_response) + }; + int ret, attachment; + if (!response) + return -ENODEV; + + ret = usb_control_msg(t300rs->usbdev, + usb_rcvctrlpipe(t300rs->usbdev, 0), + t300rs_attachment_rq.bRequest, + t300rs_attachment_rq.bRequestType, + t300rs_attachment_rq.wValue, + t300rs_attachment_rq.wIndex, + response, + sizeof(struct t300rs_attachment_response), + USB_CTRL_SET_TIMEOUT + ); + + if (ret < 0) { + hid_err(t300rs->hdev, "could not fetch attachment: %i\n", ret); + goto out; + } + + if (response->type == cpu_to_le16(0x49)) { + attachment = response->a.attachment; + } else if (response->type == cpu_to_le16(0x47)) { + attachment = response->b.attachment; + } else { + hid_err(t300rs->hdev, "unknown packet type %hx\n, please contact a maintainer", + response->type); + ret = -EINVAL; + goto out; + } + + kfree(response); + return attachment; + +out: + kfree(response); + return ret; +} + +static int t300rs_wheel_init(struct tmff2_device_entry *tmff2, int open_mode) +{ + struct t300rs_device_entry *t300rs = kzalloc(sizeof(struct t300rs_device_entry), GFP_KERNEL); + struct list_head *report_list; + int ret; + + if (!t300rs) { + ret = -ENOMEM; + goto t300rs_err; + } + + t300rs->hdev = tmff2->hdev; + t300rs->input_dev = tmff2->input_dev; + t300rs->usbdev = to_usb_device(tmff2->hdev->dev.parent->parent); + + if(t300rs->hdev->product == TMT300RS_PS4_NORM_ID) + t300rs->buffer_length = T300RS_PS4_BUFFER_LENGTH; + else + t300rs->buffer_length = T300RS_NORM_BUFFER_LENGTH; + + t300rs->send_buffer = kzalloc(t300rs->buffer_length, GFP_KERNEL); + if (!t300rs->send_buffer) { + ret = -ENOMEM; + goto send_err; + } + + if ((ret = t300rs_check_firmware(t300rs))) + goto firmware_err; + + report_list = &t300rs->hdev->report_enum[HID_OUTPUT_REPORT].report_list; + + /* because we set the rdesc, we know exactly which report and field to use */ + t300rs->report = list_entry(report_list->next, struct hid_report, list); + t300rs->ff_field = t300rs->report->field[0]; + + t300rs->open = t300rs->input_dev->open; + t300rs->close = t300rs->input_dev->close; + + /* TODO: PS4 advanced mode? */ + alt_mode = (t300rs->mode = (t300rs->hdev->product == TMT300RS_PS3_ADV_ID)); + if ((t300rs->attachment = t300rs_get_attachment(t300rs)) < 0) + t300rs->attachment = T300RS_DEFAULT_ATTACHMENT; + + /* everythin went OK */ + tmff2->data = t300rs; + tmff2->params = t300rs_params; + tmff2->max_effects = T300RS_MAX_EFFECTS; + memcpy(tmff2->supported_effects, t300rs_effects, sizeof(t300rs_effects)); + + if (!open_mode) + t300rs_send_open(t300rs); + + hid_info(t300rs->hdev, "force feedback for T300RS\n"); + return 0; + +firmware_err: + kfree(t300rs->send_buffer); +send_err: + kfree(t300rs); +t300rs_err: + hid_err(tmff2->hdev, "failed initializing T300RS\n"); + return ret; +} + +static int t300rs_wheel_destroy(void *data) +{ + struct t300rs_device_entry *t300rs = data; + if (!t300rs) + return -ENODEV; + + kfree(t300rs->send_buffer); + kfree(t300rs); + return 0; +} + +static __u8 *t300rs_wheel_fixup(struct hid_device *hdev, __u8 *rdesc, + unsigned int *rsize) +{ + switch (hdev->product) { + case TMT300RS_PS3_NORM_ID: + /* normal PS3 mode */ + rdesc = t300rs_rdesc_nrm_fixed; + *rsize = sizeof(t300rs_rdesc_nrm_fixed); + break; + + case TMT300RS_PS4_NORM_ID: + /* PS4 normal mode */ + rdesc = t300rs_rdesc_ps4_fixed; + *rsize = sizeof(t300rs_rdesc_ps4_fixed); + break; + + case TMT300RS_PS3_ADV_ID: + /* PS3 advanced mode */ + rdesc = t300rs_rdesc_adv_fixed; + *rsize = sizeof(t300rs_rdesc_adv_fixed); + break; + } + + return rdesc; +} + +int t300rs_populate_api(struct tmff2_device_entry *tmff2) +{ + /* set callbacks */ + tmff2->play_effect = t300rs_play_effect; + tmff2->upload_effect = t300rs_upload_effect; + tmff2->update_effect = t300rs_update_effect; + tmff2->stop_effect = t300rs_stop_effect; + + tmff2->wheel_init = t300rs_wheel_init; + tmff2->wheel_destroy = t300rs_wheel_destroy; + + tmff2->open = t300rs_open; + tmff2->close = t300rs_close; + tmff2->set_gain = t300rs_set_gain; + tmff2->set_range = t300rs_set_range; + tmff2->switch_mode = t300rs_switch_mode; + tmff2->alt_mode_show = t300rs_alt_mode_show; + tmff2->alt_mode_store = t300rs_alt_mode_store; + tmff2->set_autocenter = t300rs_set_autocenter; + tmff2->wheel_fixup = t300rs_wheel_fixup; + + return 0; +} diff --git a/src/tmt300rs/hid-tmt300rs.h b/src/tmt300rs/hid-tmt300rs.h new file mode 100644 index 0000000..2305f4b --- /dev/null +++ b/src/tmt300rs/hid-tmt300rs.h @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#ifndef __HID_TMT300RS_H +#define __HID_TMT300RS_H + +#include "../hid-tmff2.h" + +int t300rs_populate_api(struct tmff2_device_entry *tmff2); + +#endif diff --git a/src/tmtx/hid-tmtx.c b/src/tmtx/hid-tmtx.c new file mode 100644 index 0000000..afca799 --- /dev/null +++ b/src/tmtx/hid-tmtx.c @@ -0,0 +1,323 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#include +#include +#include "../hid-tmff2.h" + +#define TMTX_MAX_EFFECTS 16 +#define TMTX_BUFFER_LENGTH 63 + +static const u8 setup_0[64] = { 0x42, 0x01 }; +static const u8 setup_1[64] = { 0x0a, 0x04, 0x90, 0x03 }; +static const u8 setup_2[64] = { 0x0a, 0x04, 0x00, 0x0c }; +static const u8 setup_3[64] = { 0x0a, 0x04, 0x12, 0x10 }; +static const u8 setup_4[64] = { 0x0a, 0x04, 0x00, 0x06 }; +static const u8 setup_5[64] = { 0x0a, 0x04, 0x00, 0x0e }; +static const u8 setup_6[64] = { 0x0a, 0x04, 0x00, 0x0e, 0x01 }; +static const u8 *const setup_arr[] = { setup_0, setup_1, setup_2, setup_3, setup_4, setup_5, setup_6 }; +static const unsigned int setup_arr_sizes[] = { + ARRAY_SIZE(setup_0), + ARRAY_SIZE(setup_1), + ARRAY_SIZE(setup_2), + ARRAY_SIZE(setup_3), + ARRAY_SIZE(setup_4), + ARRAY_SIZE(setup_5), + ARRAY_SIZE(setup_6) +}; + +static const unsigned long tx_params = + PARAM_SPRING_LEVEL + | PARAM_DAMPER_LEVEL + | PARAM_FRICTION_LEVEL + | PARAM_RANGE + | PARAM_GAIN + ; + +static const signed short tx_effects[] = { + FF_CONSTANT, + FF_RAMP, + FF_SPRING, + FF_DAMPER, + FF_FRICTION, + FF_INERTIA, + FF_PERIODIC, + FF_SINE, + FF_TRIANGLE, + FF_SQUARE, + FF_SAW_UP, + FF_SAW_DOWN, + FF_AUTOCENTER, + FF_GAIN, + -1 +}; + +/* TODO: sort through this stuff */ +static u8 tx_pc_rdesc_fixed[] = { + 0x05, 0x01, /* Usage page (Generic Desktop) */ + 0x09, 0x04, /* Usage (Joystick) */ + 0xa1, 0x01, /* Collection (Application) */ + 0x09, 0x01, /* Usage (Pointer) */ + 0xa1, 0x00, /* Collection (Physical) */ + 0x85, 0x07, /* Report ID (7) */ + 0x09, 0x30, /* Usage (X) */ + 0x15, 0x00, /* Logical minimum (0) */ + 0x27, 0xff, 0xff, 0x00, 0x00, /* Logical maximum (65535) */ + 0x35, 0x00, /* Physical minimum (0) */ + 0x47, 0xff, 0xff, 0x00, 0x00, /* Physical maximum (65535) */ + 0x75, 0x10, /* Report size (16) */ + 0x95, 0x01, /* Report count (1) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x09, 0x35, /* Usage (Rz) (Brake) */ + 0x26, 0xff, 0x03, /* Logical maximum (1023) */ + 0x46, 0xff, 0x03, /* Physical maximum (1023) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x09, 0x32, /* Usage (Z) (Gas) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x09, 0x31, /* Usage (Y) (Clutch) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x81, 0x03, /* Input (Variable, Absolute, Constant) */ + 0x05, 0x09, /* Usage page (Button) */ + 0x19, 0x01, /* Usage minimum (1) */ + 0x29, 0x0d, /* Usage maximum (13) */ + 0x25, 0x01, /* Logical maximum (1) */ + 0x45, 0x01, /* Physical maximum (1) */ + 0x75, 0x01, /* Report size (1) */ + 0x95, 0x0d, /* Report count (13) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x75, 0x0b, /* Report size (13) */ + 0x95, 0x01, /* Report count (1) */ + 0x81, 0x03, /* Usage (Variable, Absolute, Constant) */ + 0x05, 0x01, /* Usage page (Generic Desktop) */ + 0x09, 0x39, /* Usage (Hat Switch) */ + 0x25, 0x07, /* Logical maximum (7) */ + 0x46, 0x3b, 0x01, /* Physical maximum (315) */ + 0x55, 0x00, /* Unit exponent (0) */ + 0x65, 0x14, /* Unit (Eng Rot, Angular Pos) */ + 0x75, 0x04, /* Report size (4) */ + 0x81, 0x42, /* Input (Variable, Absolute, NullState) */ + 0x65, 0x00, /* Unit (None) */ + 0x81, 0x03, /* Input (Variable, Absolute, Constant) */ + 0x85, 0x60, /* Report ID (96), prev 10 */ + 0x06, 0x00, 0xff, /* Usage page (Vendor 1) */ + 0x09, 0x60, /* Usage (96), prev 10 */ + 0x75, 0x08, /* Report size (8) */ + 0x95, 0x3f, /* Report count (63) */ + 0x26, 0xff, 0x7f, /* Logical maximum (32767) */ + 0x15, 0x00, /* Logical minimum (0) */ + 0x46, 0xff, 0x7f, /* Physical maximum (32767) */ + 0x36, 0x00, 0x80, /* Physical minimum (-32768) */ + 0x91, 0x02, /* Output (Variable, Absolute) */ + 0x85, 0x02, /* Report ID (2) */ + 0x09, 0x02, /* Usage (2) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x09, 0x14, /* Usage (20) */ + 0x85, 0x14, /* Report ID (20) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0xc0, /* End collection */ + 0xc0, /* End collection */ +}; + +static int tx_interrupts(struct t300rs_device_entry *tx) +{ + u8 *send_buf = kmalloc(256, GFP_KERNEL); + struct usb_interface *usbif = to_usb_interface(tx->hdev->dev.parent); + struct usb_host_endpoint *ep; + int ret, trans, b_ep, i; + + if (!send_buf) { + hid_err(tx->hdev, "failed allocating send buffer\n"); + return -ENOMEM; + } + + ep = &usbif->cur_altsetting->endpoint[1]; + b_ep = ep->desc.bEndpointAddress; + + for (i = 0; i < ARRAY_SIZE(setup_arr); ++i) { + memcpy(send_buf, setup_arr[i], setup_arr_sizes[i]); + + ret = usb_interrupt_msg(tx->usbdev, + usb_sndintpipe(tx->usbdev, b_ep), + send_buf, setup_arr_sizes[i], + &trans, + USB_CTRL_SET_TIMEOUT); + + if (ret) { + hid_err(tx->hdev, "setup data couldn't be sent\n"); + goto err; + } + } + +err: + kfree(send_buf); + return ret; +} + +int tx_wheel_destroy(void *data) +{ + struct t300rs_device_entry *t300rs = data; + + if (!t300rs) + return -ENODEV; + + kfree(t300rs->send_buffer); + kfree(t300rs); + return 0; +} + +int tx_set_range(void *data, uint16_t value) +{ + struct t300rs_device_entry *tx = data; + + if (value < 140) { + hid_info(tx->hdev, "value %i too small, clamping to 140\n", value); + value = 140; + } + + if (value > 900) { + hid_info(tx->hdev, "value %i too large, clamping to 900\n", value); + value = 900; + } + + return t300rs_set_range(data, value); +} + +static int tx_send_open(struct t300rs_device_entry *tx) +{ + int r1, r2; + tx->send_buffer[0] = 0x01; + tx->send_buffer[1] = 0x04; + if ((r1 = t300rs_send_int(tx))) + return r1; + + tx->send_buffer[0] = 0x01; + tx->send_buffer[1] = 0x05; + if ((r2 = t300rs_send_int(tx))) + return r2; + + return 0; +} + +static int tx_open(void *data, int open_mode) +{ + struct t300rs_device_entry *tx = data; + + if (!tx) + return -ENODEV; + + if (open_mode) + tx_send_open(tx); + + return tx->open(tx->input_dev); +} + +static int tx_send_close(struct t300rs_device_entry *tx) +{ + int r1, r2; + tx->send_buffer[0] = 0x01; + tx->send_buffer[1] = 0x05; + if ((r1 = t300rs_send_int(tx))) + return r1; + + tx->send_buffer[0] = 0x01; + tx->send_buffer[1] = 0x00; + if ((r2 = t300rs_send_int(tx))) + return r2; + + return 0; +} + +static int tx_close(void *data, int open_mode) +{ + struct t300rs_device_entry *tx = data; + + if (!tx) + return -ENODEV; + + if (open_mode) + tx_send_close(tx); + + tx->close(tx->input_dev); + return 0; +} + +int tx_wheel_init(struct tmff2_device_entry *tmff2, int open_mode) +{ + struct t300rs_device_entry *tx = kzalloc(sizeof(struct t300rs_device_entry), GFP_KERNEL); + struct list_head *report_list; + int ret; + + + if (!tx) { + ret = -ENOMEM; + goto tx_err; + } + + tx->hdev = tmff2->hdev; + tx->input_dev = tmff2->input_dev; + tx->usbdev = to_usb_device(tmff2->hdev->dev.parent->parent); + tx->buffer_length = TMTX_BUFFER_LENGTH; + + tx->send_buffer = kzalloc(tx->buffer_length, GFP_KERNEL); + if (!tx->send_buffer) { + ret = -ENOMEM; + goto send_err; + } + + report_list = &tx->hdev->report_enum[HID_OUTPUT_REPORT].report_list; + tx->report = list_entry(report_list->next, struct hid_report, list); + tx->ff_field = tx->report->field[0]; + + tx->open = tx->input_dev->open; + tx->close = tx->input_dev->close; + + if ((ret = tx_interrupts(tx))) + goto interrupt_err; + + /* everything went OK */ + tmff2->data = tx; + tmff2->params = tx_params; + tmff2->max_effects = TMTX_MAX_EFFECTS; + memcpy(tmff2->supported_effects, tx_effects, sizeof(tx_effects)); + + if (!open_mode) + tx_send_open(tx); + + hid_info(tx->hdev, "force feedback for TX\n"); + return 0; + +interrupt_err: +send_err: + kfree(tx); +tx_err: + hid_err(tmff2->hdev, "failed initializing TX\n"); + return ret; +} + +static __u8 *tx_wheel_fixup(struct hid_device *hdev, __u8 *rdesc, + unsigned int *rsize) +{ + rdesc = tx_pc_rdesc_fixed; + *rsize = sizeof(tx_pc_rdesc_fixed); + return rdesc; +} + +int tx_populate_api(struct tmff2_device_entry *tmff2) +{ + tmff2->play_effect = t300rs_play_effect; + tmff2->upload_effect = t300rs_upload_effect; + tmff2->update_effect = t300rs_update_effect; + tmff2->stop_effect = t300rs_stop_effect; + + tmff2->set_gain = t300rs_set_gain; + tmff2->set_autocenter = t300rs_set_autocenter; + /* TX only has 900 degree range, instead of T300RS 1080 */ + tmff2->set_range = tx_set_range; + tmff2->wheel_fixup = tx_wheel_fixup; + + tmff2->open = tx_open; + tmff2->close = tx_close; + + tmff2->wheel_init = tx_wheel_init; + tmff2->wheel_destroy = tx_wheel_destroy; + + return 0; +} -- cgit v1.3 From 8036ced205934b71717a1814b8053b473de11a7c Mon Sep 17 00:00:00 2001 From: RaySlash Date: Wed, 4 Oct 2023 14:23:14 +1000 Subject: deps: update Makefile --- Makefile | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index 5e2ab40..2cac153 100644 --- a/Makefile +++ b/Makefile @@ -1,16 +1,16 @@ KDIR ?= /lib/modules/$(shell uname -r)/build -all: hid-tminit +all: deps/tminit $(MAKE) -C $(KDIR) M=$(shell pwd) modules -install: hid-tminit +install: deps/tminit $(MAKE) -C $(KDIR) M=$(shell pwd) modules_install depmod -A -clean: hid-tminit +clean: deps/tminit $(MAKE) -C $(KDIR) M=$(shell pwd) clean -.PHONY: hid-tminit -hid-tminit: - $(MAKE) -C hid-tminit KDIR="$(KDIR)" $(MAKECMDGOALS) +.PHONY: deps/tminit +deps/tminit: + $(MAKE) -C tminit KDIR="$(KDIR)" $(MAKECMDGOALS) -- cgit v1.3 From 647e972e49133f0cc96566c5053d6c3a491a244b Mon Sep 17 00:00:00 2001 From: RaySlash Date: Wed, 4 Oct 2023 14:31:14 +1000 Subject: dkms: update script --- dkms/dkms-install.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/dkms/dkms-install.sh b/dkms/dkms-install.sh index f77eec4..16e3125 100755 --- a/dkms/dkms-install.sh +++ b/dkms/dkms-install.sh @@ -11,6 +11,7 @@ DST="/usr/src/${TM_NAME}-${TM_VER}" mkdir -p "${DST}" cp -r ../src/* "${DST}" +cp -r ../deps/* "${DST}" dkms add -m "${TM_NAME}" -v "${TM_VER}" dkms build -m "${TM_NAME}" -v "${TM_VER}" -- cgit v1.3 From e0422e1b84272c14790a172e76c340caebd5a4fd Mon Sep 17 00:00:00 2001 From: RaySlash Date: Wed, 4 Oct 2023 14:34:31 +1000 Subject: deps: fix path and pass build --- Makefile | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index 2cac153..ddd0091 100644 --- a/Makefile +++ b/Makefile @@ -1,16 +1,16 @@ KDIR ?= /lib/modules/$(shell uname -r)/build -all: deps/tminit +all: hid-tminit $(MAKE) -C $(KDIR) M=$(shell pwd) modules -install: deps/tminit +install: hid-tminit $(MAKE) -C $(KDIR) M=$(shell pwd) modules_install depmod -A -clean: deps/tminit +clean: hid-tminit $(MAKE) -C $(KDIR) M=$(shell pwd) clean -.PHONY: deps/tminit -deps/tminit: - $(MAKE) -C tminit KDIR="$(KDIR)" $(MAKECMDGOALS) +.PHONY: hid-tminit +hid-tminit: + $(MAKE) -C deps/tminit KDIR="$(KDIR)" $(MAKECMDGOALS) -- cgit v1.3 From 0c1382901e189557037343029fa86a7d249892b7 Mon Sep 17 00:00:00 2001 From: RaySlash Date: Wed, 4 Oct 2023 14:47:20 +1000 Subject: deps: update tminit to hid-tminit --- .gitmodules | 4 ++-- Makefile | 2 +- deps/hid-tminit | 1 + deps/tminit | 1 - 4 files changed, 4 insertions(+), 4 deletions(-) create mode 160000 deps/hid-tminit delete mode 160000 deps/tminit diff --git a/.gitmodules b/.gitmodules index 047c0f3..998e8c6 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ -[submodule "deps/tminit"] - path = deps/tminit +[submodule "deps/hid-tminit"] + path = deps/hid-tminit url = https://github.com/scarburato/hid-tminit diff --git a/Makefile b/Makefile index ddd0091..63f9b60 100644 --- a/Makefile +++ b/Makefile @@ -13,4 +13,4 @@ clean: hid-tminit .PHONY: hid-tminit hid-tminit: - $(MAKE) -C deps/tminit KDIR="$(KDIR)" $(MAKECMDGOALS) + $(MAKE) -C deps/hid-tminit KDIR="$(KDIR)" $(MAKECMDGOALS) diff --git a/deps/hid-tminit b/deps/hid-tminit new file mode 160000 index 0000000..9375f6c --- /dev/null +++ b/deps/hid-tminit @@ -0,0 +1 @@ +Subproject commit 9375f6c7d83af5dd6c8b8fe30351d0f36043b20a diff --git a/deps/tminit b/deps/tminit deleted file mode 160000 index 9375f6c..0000000 --- a/deps/tminit +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 9375f6c7d83af5dd6c8b8fe30351d0f36043b20a -- cgit v1.3 From 35b2ee8412d03172613ab12dd452fcd5660f934b Mon Sep 17 00:00:00 2001 From: RaySlash Date: Wed, 4 Oct 2023 14:59:24 +1000 Subject: deps: update paths --- Makefile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 63f9b60..747a48e 100644 --- a/Makefile +++ b/Makefile @@ -1,16 +1,16 @@ KDIR ?= /lib/modules/$(shell uname -r)/build -all: hid-tminit +all: deps/hid-tminit $(MAKE) -C $(KDIR) M=$(shell pwd) modules -install: hid-tminit +install: deps/hid-tminit $(MAKE) -C $(KDIR) M=$(shell pwd) modules_install depmod -A -clean: hid-tminit +clean: deps/hid-tminit $(MAKE) -C $(KDIR) M=$(shell pwd) clean -.PHONY: hid-tminit +.PHONY: deps/hid-tminit hid-tminit: $(MAKE) -C deps/hid-tminit KDIR="$(KDIR)" $(MAKECMDGOALS) -- cgit v1.3 From 179a41e5047c05b24c2c751251b4e1c2e1c7597a Mon Sep 17 00:00:00 2001 From: Kimplul Date: Sun, 8 Oct 2023 20:26:23 +0300 Subject: deps: fix missing deps/ --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 747a48e..86c2a51 100644 --- a/Makefile +++ b/Makefile @@ -12,5 +12,5 @@ clean: deps/hid-tminit .PHONY: deps/hid-tminit -hid-tminit: +deps/hid-tminit: $(MAKE) -C deps/hid-tminit KDIR="$(KDIR)" $(MAKECMDGOALS) -- cgit v1.3 From 4445da64240e9d5867dfdfefed6052586fb1148b Mon Sep 17 00:00:00 2001 From: Kimplul Date: Sun, 8 Oct 2023 20:36:41 +0300 Subject: dkms: fix file paths --- dkms/dkms-install.sh | 11 +++++++++-- dkms/dkms.conf | 2 +- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/dkms/dkms-install.sh b/dkms/dkms-install.sh index 16e3125..261bbe7 100755 --- a/dkms/dkms-install.sh +++ b/dkms/dkms-install.sh @@ -1,5 +1,8 @@ #!/bin/sh +# script assumes it is being run from root, i.e. +# sudo ./dkms/dkms-install.sh + if ! [ $(id -u) = 0 ]; then echo "You must run this script as root!" exit 1 @@ -10,8 +13,12 @@ TM_VER=0.8 DST="/usr/src/${TM_NAME}-${TM_VER}" mkdir -p "${DST}" -cp -r ../src/* "${DST}" -cp -r ../deps/* "${DST}" +cp -r * "${DST}" + +# copy dkms.conf specifically to be in root dir or build area +cp dkms/dkms.conf "${DST}" + +cd dkms dkms add -m "${TM_NAME}" -v "${TM_VER}" dkms build -m "${TM_NAME}" -v "${TM_VER}" diff --git a/dkms/dkms.conf b/dkms/dkms.conf index 25ef2ca..3863d31 100644 --- a/dkms/dkms.conf +++ b/dkms/dkms.conf @@ -12,7 +12,7 @@ CLEAN[0]="'make' KDIR=\"$kernel_source_dir\" clean" # The list of kernel modules. #__DKMS_MODULES BUILT_MODULE_NAME[0]="hid-tminit" -BUILT_MODULE_LOCATION="hid-tminit/" +BUILT_MODULE_LOCATION="deps/hid-tminit/" DEST_MODULE_LOCATION[0]="/kernel/drivers/hid" BUILT_MODULE_NAME[1]="hid-tmff-new" DEST_MODULE_LOCATION[1]="/kernel/drivers/hid" -- cgit v1.3 From 3027994d340f09892efa08e22c1f2e1ae6d7010d Mon Sep 17 00:00:00 2001 From: Kimplul Date: Sun, 8 Oct 2023 21:21:52 +0300 Subject: docs: mostly tweak structure and formatting + Still have to check that everything renders properly on GitHub. --- README.md | 186 ++++++++----- docs/CONTRIBUTING.md | 122 ++++++--- docs/DRIVER.md | 49 ++++ docs/FFBEFFECTS.md | 558 ++++++++++++++++++++++++++++++++++++++ docs/INTEGRATION.md | 6 + docs/STRUCTURE.md | 42 +++ docs/effects/FFBEFFECTS_T248.md | 7 - docs/effects/FFBEFFECTS_T300RS.md | 541 ------------------------------------ 8 files changed, 870 insertions(+), 641 deletions(-) create mode 100644 docs/DRIVER.md create mode 100644 docs/FFBEFFECTS.md create mode 100644 docs/INTEGRATION.md create mode 100644 docs/STRUCTURE.md delete mode 100644 docs/effects/FFBEFFECTS_T248.md delete mode 100644 docs/effects/FFBEFFECTS_T300RS.md diff --git a/README.md b/README.md index 49e4282..683fb79 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,10 @@ -# Linux kernel module for Thrustmaster T300RS, T248 and TX(experimental) wheels -> **DISCLAIMER:** The module is now ready for near-stable USE in most force feedback games, supports rangesetting as well as gain and autocentering along with most force feedback effects. While I haven't personally come across any crashes or lockups with this version, I can't promise that they won't occur under any circumstances. +# Linux kernel module for Thrustmaster T300RS, T248 and TX (experimental) wheels + +> **DISCLAIMER:** The module is now ready for near-stable USE in most force +> feedback games, supports rangesetting as well as gain and autocentering along +> with most force feedback effects. While I haven't personally come across any +> crashes or lockups with this version, I can't promise that they won't occur +> under any circumstances. ![GitHub last commit (master)](https://img.shields.io/github/last-commit/Kimplul/hid-tmff2/master) ![License](https://img.shields.io/github/license/Kimplul/hid-tmff2) @@ -7,98 +12,155 @@ ## Description -A Linux kernel module for Thrustmaster T300RS, T248, and TX (experimental support) wheels is a software component designed to enhance the compatibility and functionality of these popular racing wheel peripherals when used with Linux-based operating systems. This kernel module allows seamless integration of Thrustmaster wheels with the Linux kernel, providing users with a robust and feature-rich experience for gaming and other applications with working force feedback effects. -I've been working on enhancing the real-time updating of effects, and although it's not flawless yet, the overall experience is gradually improving. There are a couple of issues, though. First, there might be occasional inaccuracies in how the effects compare to the Windows driver. Second, in certain games, the mapping of pedal inputs can be inconsistent. This means that while all pedals should be recognized by the games, they might not be mapped correctly. +A Linux kernel module for Thrustmaster T300RS, T248, and TX +(experimental support) wheels. + +I've been working on enhancing the real-time updating of effects, and although +it's not flawless yet, the overall experience is gradually improving. There are +a couple of issues, though. First, there might be occasional inaccuracies in how +the effects compare to the Windows driver. Second, in certain games, the mapping +of pedal inputs can be inconsistent. This means that while all pedals should be +recognized by the games, they might not be mapped correctly. ## Installation -You can either install this kernel module by using DKMS or manually building from source: + +You can either install this kernel module by using DKMS or +manually building from source: ### Dependencies -Kernel modules require kernel headers to be installed. Use any one of the right command for your distribution: + +Kernel modules require kernel headers to be installed. Use any +one of the right command for your distribution: + ```shell -$ sudo apt install linux-headers-$(uname -r) #Debian-based -$ sudo pacman -S linux-headers #Arch-based -$ sudo yum install kernel-devel kernel-headers #Fedora-based +sudo apt install linux-headers-$(uname -r) # Debian-based +sudo pacman -S linux-headers # Arch-based +sudo yum install kernel-devel kernel-headers # Fedora-based ``` #### Manual installation + Unplug wheel from computer -+ Use the following commands in your terminal of choice: -```shell -git clone --recurse-submodules https://github.com/Kimplul/hid-tmff2.git -cd hid-tmff2/src -make -sudo make install -``` ++ Run + ```shell + git clone --recurse-submodules https://github.com/Kimplul/hid-tmff2.git + cd hid-tmff2 + make + sudo make install + ``` + Plug wheel back in + Reboot *(Optional, yet Recommended)* #### DKMS (Dynamic Kernel Module Support) + + Unplug wheel from computer -+ Clone the repo `git clone --recurse-submodules https://github.com/Kimplul/hid-tmff2.git` -+ Use `sudo bash hid-tmff2/dkms/dkms-install.sh` on your terminal of choice ++ Run + ```shell + git clone --recurse-submodules https://github.com/Kimplul/hid-tmff2.git + cd hid-tmff2 + sudo ./dkms/dkms-install.sh + ``` + Plug wheel back in + Reboot *(Optional, yet Recommended)* -> **NOTE:** See [here](https://github.com/Kimplul/hid-tmff2/wiki/Integrating-driver-into-distros) for install instructions for other linux distributions. +> **NOTE:** See [INTEGRATION](./docs/INTEGRATION.md) +> for install instructions for other linux distributions. -> **NOTE:** On some systems, you will get an error/warning about SSL. This is normal for unsigned modules. For info on signing modules yourself (completely optional), see [here](https://www.kernel.org/doc/html/latest/admin-guide/module-signing.html?highlight=module%20signing). +> **NOTE:** On some systems, you will get an error/warning about SSL. This is +> normal for unsigned modules. For info on signing modules yourself +> (completely optional), see +> [here](https://www.kernel.org/doc/html/latest/admin-guide/module-signing.html?highlight=module%20signing). -> **NOTE:** Thrustmaster TX wheels aren't supported by `hid-tminit` as of yet, meaning that TX wheels have to be initialized with `tmrd`. Please see https://github.com/Kimplul/hid-tmff2/issues/48. +> **NOTE:** Thrustmaster TX wheels aren't supported by `hid-tminit` as of yet, +> meaning that TX wheels have to be initialized with `tmdrv`. Please see +> https://github.com/Kimplul/hid-tmff2/issues/48. -> :warning: **NOTE:** There have been reports that this driver does not work if the wheel's firmware version is older than v. 31. -> To update the firmware, you will have to fire up a Windows installation and update the firmware using the official Thrustmaster tools. +> **WARNING:** There have been reports that this driver does not work if +> the wheel's firmware version is older than v. 31. To update the firmware, you +> will have to fire up a Windows installation and update the firmware using the +> official Thrustmaster tools. -> :warning: **NOTE:** There was a name change when adding support for the T248 from `hid-tmt300rs` to `hid-tmff-new`, -> and you may have to uninstall the older version of the driver. +> **WARNING:** There was a name change when adding support for the T248 +> from `hid-tmt300rs` to `hid-tmff-new`, and you may have to uninstall the older +> version of the driver. ## Contribute to project -This project wants help from people who can contribute. If you would like to help add a wheel to this driver, please have a look through the [wiki](https://github.com/Kimplul/hid-tmff2/wiki#how-to-add-in-support-for-a-new-t-series-wheel) and/or [CONTRIBUTING.md](./docs/CONTRIBUTING.md) for what might need to be done. If you have a wheel that's not on the list, but suspect it might fit into the driver, please feel free to open up an issue about it. -Currently open requests for wheels: -- [T500 RS](https://github.com/Kimplul/hid-tmff2/issues/18) -- [T818](https://github.com/Kimplul/hid-tmff2/issues/58) -- [T-GT II](https://github.com/Kimplul/hid-tmff2/issues/55) -- [T128P](https://github.com/Kimplul/hid-tmff2/issues/67) -## FAQ (Frequently Asked Questions) -+ Reportedly some games running under Wine/Proton won't recognize wheels without the official Thrustmaster drivers installed within the prefix. See [#46](https://github.com/Kimplul/hid-tmff2/issues/46#issuecomment-1199080845). For installation instructions, see [wiki](https://github.com/Kimplul/hid-tmff2/wiki). Note that you will still need the Linux driver, the Windows driver just installs some files needed by games to correctly recognize the Linux driver. The Windows driver itself does not work under Wine/Proton. +This project wants help from people who can contribute. +If you would like to help add a wheel to this driver, +please have a look through `docs` and/or [CONTRIBUTING.md](./docs/CONTRIBUTING.md) +for what might need to be done. + +If you have a wheel that's not not supported, but suspect it might fit into the +driver, please feel free to open up an issue about it. Currently open requests +for wheels: -+ Until the updated `hid-tminit` is [upstreamed](https://github.com/scarburato/hid-tminit), you might want to blacklist the kernel module `hid-thrustmaster`. Do this with - ```shell - echo 'blacklist hid_thrustmaster' > /etc/modprobe.d/hid_thrustmaster.con - ``` ++ [T500 RS](https://github.com/Kimplul/hid-tmff2/issues/18) ++ [T818](https://github.com/Kimplul/hid-tmff2/issues/58) ++ [T-GT II](https://github.com/Kimplul/hid-tmff2/issues/55) ++ [T128P](https://github.com/Kimplul/hid-tmff2/issues/67) -+ If you've bought a new wheel, you will most likely have to update the firmware through Windows before it will work with this driver. +## FAQ (Frequently Asked Questions) ++ Reportedly some games running under Wine/Proton won't recognize wheels without + the official Thrustmaster drivers installed within the prefix. See + [#46](https://github.com/Kimplul/hid-tmff2/issues/46#issuecomment-1199080845). + For installation instructions, see + [DRIVER](./docs/DRIVER.md). + + Note that you will still need + the Linux driver, the Windows driver just installs some files needed by games to + correctly recognize the Linux driver. The Windows driver itself does not work + under Wine/Proton. + ++ Until the updated `hid-tminit` is + [upstreamed](https://github.com/scarburato/hid-tminit), you might want to + blacklist the kernel module `hid-thrustmaster`. Do this with + ```shell + echo 'blacklist hid_thrustmaster' > /etc/modprobe.d/hid_thrustmaster.con + ``` + ++ If you've bought a new wheel, you will most likely have to update the firmware + through Windows before it will work with this driver. + ++ T300 RS has an advanced F1 mode that can be activated with an F1 attachment + when in PS3 mode. The base wheel will also work in PS4 mode, but it's less + tested and if you encounter issues with this mode, please feel free to open up + an issue about it. + ++ T248 isn't as extensively tested as T300 RS, please see issues and open new + ones if you encounter problems. There is currently no support for the built-in + screen. + ++ TX support is considered experimental, please see issues + (especially https://github.com/Kimplul/hid-tmff2/issues/48) + and open new ones if you encounter any problems. + ++ To change gain, autocentering etc. use + [Oversteer](https://github.com/berarma/oversteer). -+ T300 RS has an advanced F1 mode that can be activated with an F1 attachment when in PS3 mode. The base wheel will also work in PS4 mode, - but it's less tested and if you encounter issues with this mode, please feel free to open up an issue about it. - -+ T248 isn't as extensively tested as T300 RS, please see issues and open new ones if you encounter problems. - There is currently no support for the built-in screen. ++ If a wheel has a deadzone in games, you can try setting up a udev rule: + `/etc/udev/rules.d/99-joydev.rules` -+ TX support is considered experimental, please see issues (especially https://github.com/Kimplul/hid-tmff2/issues/48) and open new ones - if you encounter issues. + ``` SUBSYSTEM=="input", ATTRS{idVendor}=="044f", + ATTRS{idProduct}=="WHEEL_ID", RUN+="/usr/bin/evdev-joystick --evdev + %E{DEVNAME} --deadzone 0" ``` -+ To change gain, autocentering etc. use [Oversteer](https://github.com/berarma/oversteer). + where `WHEEL_ID` is -+ If a wheel has a deadzone in games, you can try setting up a udev rule: - - `/etc/udev/rules.d/99-joydev.rules` + | Wheel | WHEEL_ID | + |----------------------------|------------| + | T300 RS, PS3 normal mode | b66e | + | T300 RS, PS3 advanced mode | b66f | + | T300 RS, PS4 normal mode | b66d | + | T248 | b696 | - ``` - SUBSYSTEM=="input", ATTRS{idVendor}=="044f", ATTRS{idProduct}=="WHEEL_ID", RUN+="/usr/bin/evdev-joystick --evdev %E{DEVNAME} --deadzone 0" - ``` - - where `WHEEL_ID` is - | Wheel | WHEEL_ID | - |----------------------------|------| - | T300 RS, PS3 normal mode | b66e | - | T300 RS, PS3 advanced mode | b66f | - | T300 RS, PS4 normal mode | b66d | - | T248 | b696 | + This should make sure that the wheel behaves like you'd want from a wheel. - This should make sure that the wheel behaves like you'd want from a wheel. ++ There have been reports that some games work better with a different timer + period (see [#11](https://github.com/Kimplul/hid-tmff2/issues/11) and + [#10](https://github.com/Kimplul/hid-tmff2/issues/10)). -+ There have been reports that some games work better with a different timer period (see [#11](https://github.com/Kimplul/hid-tmff2/issues/11) and [#10](https://github.com/Kimplul/hid-tmff2/issues/10)). To change the timer period, create `/etc/modprobe.d/hid-tmff-new.conf` and add `options hid-tmff-new timer_msecs=NUMBER` into it. The default timer period is 8, but numbers as low as 2 should work alright. + To change the timer period, create `/etc/modprobe.d/hid-tmff-new.conf` + and add `options hid-tmff-new timer_msecs=NUMBER` into it. + The default timer period is 8, but numbers as low as 2 should work alright. diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index db937a1..46b8b26 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -1,50 +1,110 @@ # Contributing -## Overview -This file contains general contributing information. This project is seeking for help from people who can contribute. If you have a wheel that's not on the list, but suspect it might fit into the driver, please feel free to open up an issue about it. +## Overview This file contains general contributing information. + +This project is seeking for help from people who can contribute. If you have a +wheel that's not on the list, but suspect it might fit into the driver, please +feel free to open up an issue about it. Currently open requests for wheels: -- [T500 RS](https://github.com/Kimplul/hid-tmff2/issues/18) -- [T818](https://github.com/Kimplul/hid-tmff2/issues/58) -- [T-GT II](https://github.com/Kimplul/hid-tmff2/issues/55) -- [T128P](https://github.com/Kimplul/hid-tmff2/issues/67) ++ [T500 RS](https://github.com/Kimplul/hid-tmff2/issues/18) ++ [T818](https://github.com/Kimplul/hid-tmff2/issues/58) ++ [T-GT II](https://github.com/Kimplul/hid-tmff2/issues/55) ++ [T128P](https://github.com/Kimplul/hid-tmff2/issues/67) Other documents available are linked here: -- [FFBEffects-T300RS](./FFBEffects-T300RS.md): Force Feedback Effects example for T300RS -- [FFBEffects-T248](./FFBEffects-T248.md): Force Feedback Effects example for T248 -- [Structure](https://github.com/Kimplul/hid-tmff2/wiki#structure-of-the-thrustmaster-device-stack): Structure of Thrustmaster device stack -- [TO-DO](./TODO.md): TO-DO list for maintainers ++ [FFBEEFFECTS](./FFBEFFECTS.md): + Force feedback effects example for T300RS and compatible wheels -## How to add in support for a new T-series wheel? -Should probably not be too often that you need this info, but essentially use wireshark like in the previous example, but spin up a Windows virtual machine and install the Thrustmaster drivers on to it, and pass the device to the virtual machine. I prefer to use `qemu` with `virt-manager` as a frontend. With the wheel working under Windows, install [fedit.exe](https://gimx.fr/download/b882e209a0ac023d03abbf560dfc3f25fe6367ca/fedit.zip) and methodically go through all effects the device supports and compare the USB packets the driver sends out. You should be able to build up a table of what each value in the USB packet means, see [FFBEffects-T300RS.md](./FFBEffects-T300RS.md) for an example of what I found out about the T300. ++ [STRUCTURE](./STRUCTURE.md): + Structure of Thrustmaster device stack + ++ [TODO](./TODO.md): + TO-DO list for maintainers ## How to capture what effects a game sends to the driver? -Use ffbwrap from ffbtools: [github:berarma/ffbtools](https://github.com/berarma/ffbtools) -The documentation gives good examples, but tl;dr; For steam, insert the following into a game's launch options: + +Use ffbwrap from ffbtools: +[github:berarma/ffbtools](https://github.com/berarma/ffbtools) + +The documentation gives good examples, but tl;dr; For steam, insert the following +into a game's launch options: + ```shell -$ ffbwrap --logger=/home/$USER/game.log /dev/input/by-id/usb-Thrustmaster_Thrustmaster_T300RS_Racing_wheel-event-joystick -- %command% +ffbwrap --logger=/home/$USER/game.log /dev/input/by-id/usb-Thrustmaster_Thrustmaster_T300RS_Racing_wheel-event-joystick -- %command% ``` -This will create a file called `game.log` (with additional timestamp) in your home directory. Preferably change the name to suit the game, but you do you. -> **NOTE:** Most fixes presented in the documentation are more or less obsolete by now, but the tool is still very useful for logging purposes. +This will create a file called `game.log` (with additional timestamp) in your +home directory. Preferably change the name to suit the game, but you do you. + +> **NOTE:** Most fixes presented in the documentation are more or less obsolete +> by now, but the tool is still very useful for logging purposes. ## How to capture what USB packets the driver sends to the device? -I'd recommend [wireshark](https://www.wireshark.org/) -Usb capture setup is fairly straightforward: [wireshark/CaptureSetup](https://wiki.wireshark.org/CaptureSetup/USB#linux) +I'd recommend +[wireshark](https://www.wireshark.org/) + +Usb capture setup is fairly straightforward: +[wireshark/CaptureSetup](https://wiki.wireshark.org/CaptureSetup/USB#linux) + +Here's what I typically do when starting a capture: + ++ Run `sudo modprobe usbmon`. This will load a kernel module that allows + Wireshark to read the USB packets. + ++ Open wireshark with root privileges. There are some ways to allow wireshark to + access the packets with regular user privileges, I just haven't bothered with + it. -Here's what I typically do when starting capturing: ++ Select `usbmon0` from the view that opens up by default. + ++ The screen will quickly fill up with noise from other devices connected to the + computer, so you will have to filter out the noise. + ++ Run `lsusb` in a terminal. Look for the T300 in the list, you should see + something like `Bus 001 Device 006: ID 044f:b66e ThrustMaster, Inc. + Thrustmaster T300RS Racing wheel` + ++ From the previous command, the Bus and Device fields can be used to filter + out only packets from/to the device. To see all packets coming from the + wheel, add in a filter `usb.src ~ "1\.6\..*"`, where `1` is in this case + from the `Bus` field and `6` from the `Device` field. To see all packets + being sent to the device, use `usb.dst`. + ++ This data is probably also filled with a lot of cruft. Data to/from + endpoint 2 is button state info, which is probably unnecessary. To see FFB + data being sent to the device, use endpoint 1, i.e. `usb.dst == "1.6.1"`. + `~` is a Perl-compliant regex operator, whereas `==` just matches the string + directly. To see both data coming from the device and going to it, use + `usb.dst == "..." || usb.src == "..."`. + ++ There is also endpoint 0, `usb.src == "1.6.0"` but it doesn't seem to be + used for much. + ++ Use the three buttons in the top left of the screen to start, stop and restart + captures. + ++ Do whatever you want with the device, packets should automatically be + captured. When you want to save your capture to a file, stop the recording and + go to `File > Export Specified Packets` and make sure `Displayed` is selected. + This will apply the filter you've been using, and will only include the packets + that are visible in Wireshark, i.e. it applies the filter you've specified. + +> **NOTE:** Every time you unplug and replug your wheel, its `Device` field will +> probably change. + +## How to add in support for a new T-series wheel? -- Run `sudo modprobe usbmon`. This will load a kernel module that allows Wireshark to read the USB packets. -- Open wireshark with root privileges. There are some ways to allow wireshark to access the packets with regular user privileges, I just haven't bothered with it. -- Select `usbmon0` from the view that opens up by default. -- The screen will quickly fill up with noise from other devices connected to the computer, so you will have to filter out the noise. - - Run `lsusb` in a terminal. Look for the T300 in the list, you should see something like `Bus 001 Device 006: ID 044f:b66e ThrustMaster, Inc. Thrustmaster T300RS Racing wheel` - - From the previous command, the Bus and Device fields can be used to filter out only packets from/to the device. To see all packets coming from the wheel, add in a filter `usb.src ~ "1\.6\..*"`, where `1` is in this case from the `Bus` field and `6` from the `Device` field. To see all packets coming from the device, use `usb.dst`. - - This data is probably also filled with a lot of cruft. Data to/from endpoint 2 is button state info, which is probably unnecessary. To see FFB data being sent to the device, use endpoint 1, i.e. `usb.dst == "1.6.1"`. `~` is a Perl-compliant regex operator, whereas `==` just matches the string directly. To see both data coming from the device and going to it, use `usb.dst == "..." || usb.src == "..."`. - - There is also endpoint 0, `usb.src == "1.6.0"` but it doesn't seem to be used for much. -- Use the three buttons in the top left of the screen to start, stop and restart captures. -- Do whatever you want with the device, packets should automatically be captured. When you want to save your capture to a file, stop the recording and go to `File > Export Specified Packets` and make sure `Displayed` is selected. This will apply the filter you've been using, and will only include the packets that are visible in Wireshark, i.e. it applies the filter you've specified. +Should probably not be too often that you need this info, but essentially use +wireshark like in the previous example, but spin up a Windows virtual machine +and install the Thrustmaster drivers on to it, and pass the device to the +virtual machine. I prefer to use `qemu` with `virt-manager` as a frontend. -> **NOTE:** Every time you unplug and replug your wheel, its `Device` field will probably change. +With the wheel working under Windows, install +[fedit.exe](https://gimx.fr/download/b882e209a0ac023d03abbf560dfc3f25fe6367ca/fedit.zip) +and methodically go through all effects the device supports and compare the USB +packets the driver sends out. You should be able to build up a table of what +each value in the USB packet means, see [FFBEFFECTS.md](./FFBEFFECTS.md) +for an example of what I found out about the T300. diff --git a/docs/DRIVER.md b/docs/DRIVER.md new file mode 100644 index 0000000..053ee41 --- /dev/null +++ b/docs/DRIVER.md @@ -0,0 +1,49 @@ +There are some reports that games will work better with the Thrustmaster Windows +drivers installed. + +# Installing the Windows drivers inside a proton prefix + +1. Install [protontricks](https://github.com/Matoking/protontricks). + +2. Download the + [Thrustmaster drivers](https://support.thrustmaster.com/en/product/t300rs-en/). + +3. Look up the ID of your game with `protontricks -s NAME`. For example: + ```shell + $ protontricks -s Dirt + Found the following games: + DiRT 3 Complete Edition (321040) + DiRT 4 (421020) + DiRT Rally (310560) + DiRT Rally 2.0 (690790) + + To run Protontricks for the chosen game, run: $ protontricks APPID COMMAND + + NOTE: A game must be launched at least once before Protontricks can find the + game. + ``` + +4. Run the driver installer with protontricks. + ```shell + protontricks -c 'wine ~/Downloads/DRIVER_INSTALL_EXE' APPID + ``` + + Dirt 4 for example: + ```shell + protontricks -c 'wine ~/Downloads/2022_TTRS_1.exe' 421020 + ``` + +Note that `protontricks` may change the working directory, so relative paths may +end up not working as expected. + +# Installing the Windows drivers manually + +Assuming you've already downloaded the installer: + +```shell +WINE_PREFIX=/path/to/prefix wine ./DRIVER_INSTALL_EXE +``` + +[Lutris](https://lutris.net/) is an excellent way to keep track of which +non-steam games exist in which prefixes, and allows you to run executables in +prefixes from a GUI if that's more your thing. diff --git a/docs/FFBEFFECTS.md b/docs/FFBEFFECTS.md new file mode 100644 index 0000000..e2b3edc --- /dev/null +++ b/docs/FFBEFFECTS.md @@ -0,0 +1,558 @@ +# Force Feedback Effects - Thrustmaster T300RS and compatible + +Here I will gather all info I can find out about the different force feedback +effects and their USB commands. All values seem to be little-endian. + +Also, general note, it seems like some effects are affected by the direction of +the force, denoted by `***`. As far as I can tell, `fade_level` and +`attack_level` values are calculated by mapping the `u16` into `s16` and taking +the absolute value, and after that multiplying by the direction. + +A full list of wheels that use USB packets in the format documented here is yet +to be determined, but at least T300, T248 and TX seem to use it. Each wheel may +have model specific peculiarities, though. + +> **NOTE:** All values are examples of actual commands that I captured on the +> USB interface, not the only available ones. + +## GENERAL +### Playing +``` + 60 00 - standard header + 01 - ID + 89 - playing options + 01 - play + 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +``` + +### Stopping/removing (why are they rolled into one?) +``` + 60 00 - standard header + 01 - ID + 89 - playing options + 00 - stop + 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +``` + +## Open: +``` + 60 01 - standard header? + 04 - ? + 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + + (this command doesn't appear every time, odd) + 60 12 - ? + bf 04 00 00 03 b7 1e - ? /* wow, this is different on different wheels? */ + 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + + 60 01 - ? + 05 - ? + 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +``` + +## Close: +``` + 60 01 - standard header? + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +``` + +## Rotation angle: +``` + 60 08 - header plus settings? + 11 - set rotation angle + 55 d5 - angle (between ff ff and 7b 09, one degree is roughly 3c) + 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +``` + +## FF_AUTOCENTER: +``` + 60 08 - header plus settings? + 03 - set autocenter force + 8f 02 - force (between ff ff and 00 00) + 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +``` + +## FF_GAIN: +``` + 60 02 - header plus gain? + bf - gain (between ff and 00) + 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +``` + +## FF_CONSTANT +### Initialization +``` + 60 00 - standard header + 01 - ID + 6a - new constant effect(?) + fe ff - strength *** + 00 00 - attack_length + 00 00 - attack_level *** + 00 00 - fade_length + 00 00 - fade_level *** + 00 4f - ? + f7 17 - time in milliseconds, unsigned + 00 00 + 07 00 - offset from start(?) + 00 ff ff - end of new effect(?) + 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +``` + +### Modifying (For dynamic updating of effects) + +#### Direction: ``N/A`` + +#### Constant force: +``` + 60 00 - standard header + 01 - ID + 0a - modify constant force + 05 16 - constant force *** + 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +``` + +#### Envelope: +``` + 60 00 - standard header + 01 - ID + 31 - modify envelope + 84 - ID of envelope attribute ( attack_level 82, + attack_length 81, + fade_level 88, + fade_length 84 ) + 63 04 - value attribute should be set to ?***? + 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +``` + +#### Duration: +``` + 60 00 - standard header + 01 - ID + 49 00 41 - modify timing? + 6c 20 - length in milliseconds + 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +``` + +#### Offset: `N/A` + +## FF_RAMP + +Ramps seem to follow triangle wave parameters. + +### Init: +``` + 60 00 - standard header + 01 - ID + 6b - new ramp effect + f6 7f - difference *** + fe ff - level? *** (level = (if end_level > start_level, end_level, else + start_level) - difference?) (signed) + 00 00 + f7 17 - time + 00 80 - some kind of marker + 00 00 - attack_length + f6 7f - attack_level *** + 00 00 - fade_length + f6 7f - fade_level*** + 04 - invert (going "down", 05, going "up", 04) + 4f + f7 17 - time again? + 00 00 + 00 00 - offset + 00 ff ff - end of init + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +``` + +### Modifying: +``` + 60 00 - standard header + 01 - ID + 0e - ??? + 03 - ramp? + e3 04 - difference? + 7d 75 - "level"? + 05 - ???? + 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +``` + +#### Envelope: +``` + 60 00 - standard header + 01 - ID + 31 - modify envelope + 84 - ID of envelope attribute ( attack_level 82, + attack_length 81, + fade_level 88, + fade_length 84 ) + 63 04 - value attribute should be set to + 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +``` + +#### Duration: +``` + 60 00 + 01 - ID + 4e - ? + 08 - ? + 01 00 - time in milliseconds + 05 - ? + 41 - ? + 01 00 - time in milliseconds + 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +``` + +#### Offset: `N/A` + +## FF_SPRING: +### Init: +``` + 60 00 - standard header + 01 - ID + 64 - new conditional effect + fc 7f - positive coefficient (right?) + fc 7f - negative coefficient (left?) + fe ff - deadband (left?) (deadband + offset) (fe ff means no deadband) + fe ff - deadband (right?) (deadband + offset) + a6 6a a6 6a fe ff fe ff fe ff fe ff df 58 a6 6a 06 - some weird hard-coded + values to do with springs? + + 4f + f7 17 - duration + 00 00 + 00 00 - offset + 00 ff ff - end of init + 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +``` + +### Modifying: +Same as below (FF_DAMPER + FF_FRICTION + FF_INERTIA) + +## FF_DAMPER + FF_FRICTION + FF_INERTIA: +### Init: +``` + 60 00 - standard header + 02 -ID + 64 - new conditional effect + fc 7f - positive coefficient (right?) + fc 7f - negative coefficient (left?) + fe ff - deadband (left?) (deadband + offset) (fe ff means no deadband) + fe ff - deadband (right?) (deadband + offset) + fc 7f fc 7f fe ff fe ff fe ff fe ff fc 7f fc 7f 07 - some weird hard-coded + values to do with friction? + 4f + f7 17 - duration + 00 00 + 00 00 - offset + 00 ff ff - end of init + 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +``` + +### Modifying: +``` + positive coefficient: + 60 00 + 02 - ID + 0e 41 + 64 35 - value, signed (between 00 00 and 01 80?) + 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + + negative coefficient: + 60 00 + 02 - ID + 0e 42 + 64 35 - value, signed (between 00 00 and ff 7f?) + 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + + deadband/offset: + 60 00 + 02 - ID + 0e 4c + 64 35 - deadband (right?) (deadband + offset) (fe ff means no deadband) + 00 00 - deadband (left?) (deadband + offset) + 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +``` + +### Duration: +``` + 60 00 - standard header + 01 - ID + 49 06 41 - modify timing? + 6c 20 - length in milliseconds + 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +``` + +### Offset: `N/A` + +## FF_PERIODIC: +### Init +``` + 60 00 - standard header + 01 - ID + 6b - new periodic effect + 00 00 - magnitude *** + fe ff - offset (up/down) + 00 00 - phase (left/right) + e8 03 - period + 00 80 + 00 00 - attack_length + 00 00 - attack_level *** + 00 00 - fade_length + 00 00 - fade_level *** + 01 - type of periodic ( square 01, + sine 03, + triangle 02, + sawtooth down 05, + sawtooth up 04 ) + 4f + f7 17 - duration + 00 00 + 00 00 - offset + 00 ff ff - end of init + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +``` + +### Modifying: +``` + magnitude: + 60 00 + 02 - ID + 0e 01 + 64 35 - value, signed (between 00 00 and ff 7f?) + 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + + offset of effect: + 60 00 + 02 - ID + 0e 02 + 64 35 - value, signed (between 00 00 and ff 7f?) + 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + + phase: + 60 00 + 02 - ID + 0e 04 + 64 35 - value, signed (between 80 01 and ff 7f?) + 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + + period: + 60 00 + 02 - ID + 0e 08 + 64 35 - value, signed (between 00 00 and I guess ff 7f?) (max value in + wireshark is 0d 07, but I don't know if that's a driver or program issue) + 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + +``` + +### Envelope: +``` + 60 00 - standard header + 01 - ID + 31 - modify envelope + 84 - ID of envelope attribute ( attack_level 82, + attack_length 81, + fade_level 88, + fade_length 84 ) + 63 04 - value attribute should be set to + 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +``` + +### Duration: +``` + Square: + 60 00 + 01 - ID + 49 - ? + 01 - Effect type + 41 - ? + 4e 0c - Time in milliseconds + 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + + Sine: + 60 00 + 01 - ID + 49 - ? + 03 - Effect type + 41 - ? + ec 13 - Time in milliseconds + 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + + Triangle: + 60 00 + 01 - ID + 49 - ? + 02 - Effect type + 41 - ? + 4e 0c - Time in milliseconds + 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + + Sawtooth up: + 60 00 + 01 - ID + 49 - ? + 04 - Effect type + 41 - ? + 4e 0c - Time in milliseconds + 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + + Sawtooth down: + 60 00 + 01 - ID + 49 - ? + 05 - Effect type + 41 - ? + ec 13 - Time in milliseconds + 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +``` +### Offset: `N/A ?` + +## PS4 Input `rdesc` +``` + ff00.0021 = 0 + ff00.0021 = 0 + ff00.0021 = 0 + ff00.0021 = 0 + ff00.0021 = 0 + ff00.0021 = 0 + ff00.0021 = 0 + ff00.0021 = 0 + ff00.0021 = 0 + ff00.0021 = 0 + ff00.0021 = 0 + ff00.0021 = 0 + ff00.0021 = 0 + ff00.0021 = 0 + ff00.0021 = 0 + ff00.0021 = 0 + ff00.0021 = 0 + ff00.0021 = 0 + ff00.0021 = 0 + ff00.0021 = 0 + ff00.0021 = 0 + ff00.0021 = 0 + ff00.0021 = 0 + ff00.0021 = 0 + ff00.0021 = 0 + ff00.0021 = 0 + ff00.0021 = 0 + ff00.0021 = 0 + ff00.0021 = 0 + ff00.0021 = 0 + ff00.0021 = 0 + ff00.0021 = 0 + ff00.0021 = 0 + ff00.0021 = 226 /* wheel */ + ff00.0021 = 125 /* wheel */ + ff00.0021 = 255 /* gas */ + ff00.0021 = 255 /* gas */ + ff00.0021 = 255 /* brake */ + ff00.0021 = 255 /* brake */ + ff00.0021 = 255 /* clutch */ + ff00.0021 = 255 /* clutch */ + ff00.0021 = 0 + ff00.0021 = 255 + ff00.0021 = 255 + ff00.0021 = 0 + ff00.0021 = 0 + ff00.0021 = 0 + ff00.0021 = 0 + ff00.0021 = 0 + ff00.0021 = 0 + ff00.0021 = 0 + ff00.0021 = 0 + ff00.0021 = 0 + ff00.0021 = 0 +``` diff --git a/docs/INTEGRATION.md b/docs/INTEGRATION.md new file mode 100644 index 0000000..bcdb9f4 --- /dev/null +++ b/docs/INTEGRATION.md @@ -0,0 +1,6 @@ +Some distros require more complicated installation methods than what's outlined +in the README. This page is intended to collect information about the topic. + +# NixOS + +See https://github.com/Kimplul/hid-tmff2/issues/71. diff --git a/docs/STRUCTURE.md b/docs/STRUCTURE.md new file mode 100644 index 0000000..e7778ba --- /dev/null +++ b/docs/STRUCTURE.md @@ -0,0 +1,42 @@ +# Structure of the Thrustmaster device 'stack' + ++ `hid-tmff` + +Driver for older Thrustmaster wheels, has existed in the Kernel for a long time +and I have nothing to with it. + ++ `hid-thrustmaster` + +Driver for initializing newer Thrustmaster wheels, initially added in version +5.13. Essentially `hid-tminit`, with some modifications to better fit into the +kernel. + ++ `hid-tminit` + +Driver for initializing newer Thrustmaster wheels, currently maintained over at +https://github.com/scarburato/hid-tminit. + +All newer Thrustmaster wheels initially identify themselves as `Thrustmaster FFB +Wheel`, and it is up to the `hid-tminit` driver to boot the wheel into its +correct designation, after which the corresponding driver can take over. No FFB +takes place while this driver is active. + ++ `hid-tmff-new` + +Active driver module for different wheels, responsible for handling FFB. +At the moment only supports T300 RS and T248. Note that the T300 RS has a number +of different modes, of which PS3, PS4, and PS3 F1 (advanced) mode are known to +work (at least on some systems, please open up any issues you encounter.) + ++ `hid-tmff2` + +Name of this projects, initially I intended to write `hid-tmff` but for newer +wheels, hence the name. Because of some slight weirdness in the Kernel build +configuration, I couldn't use `hid-tmff2` as the module name, so I chose +`hid-tmff-new`. + +I don't know exactly which wheels are 'newer' and which are 'older', but at +least T150/T248/T300/T500 are in the 'newer' category. + +Note that the T150 already has a driver in a separate module +(https://github.com/scarburato/t150_driver). diff --git a/docs/effects/FFBEFFECTS_T248.md b/docs/effects/FFBEFFECTS_T248.md deleted file mode 100644 index f43fb55..0000000 --- a/docs/effects/FFBEFFECTS_T248.md +++ /dev/null @@ -1,7 +0,0 @@ -# Force Feedback Effects - Thrustmaster T248 - -## Open: -Identical to [T300RS](./FFBEFFECTST300RS) - -## Close: -Identical to [T300RS](./FFBEFFECTST300RS) diff --git a/docs/effects/FFBEFFECTS_T300RS.md b/docs/effects/FFBEFFECTS_T300RS.md deleted file mode 100644 index f076ec7..0000000 --- a/docs/effects/FFBEFFECTS_T300RS.md +++ /dev/null @@ -1,541 +0,0 @@ -# Force Feedback Effects - Thrustmaster T300RS (incl. PS3/PS4 modes) -Here I will gather all info I can find out about the different force feedback effects and their USB commands. All values seem to be little-endian. Also, general note, it seems like some effects are affected by the direction of the force, denoted by `***`. As far as I can tell, `fade_level` and `attack_level` values are calculated by mapping the `u16` into `s16` and taking the absolute value, and after that multiplying by the direction. - -> **NOTE:** All values are examples of actual commands that I captured on the USB interface, not the only available ones. - - -## GENERAL -### Playing -``` - 60 00 - standard header - 01 - ID - 89 - playing options - 01 - play - 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -``` - -### Stopping/removing (why are they rolled into one?) -``` - 60 00 - standard header - 01 - ID - 89 - playing options - 00 - stop - 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -``` - -## Open: -``` - 60 01 - standard header? - 04 - ? - 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - - (this command doesn't appear every time, odd) - 60 12 - ? - bf 04 00 00 03 b7 1e - ? /* wow, this is different on different wheels? */ - 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - - 60 01 - ? - 05 - ? - 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -``` - -## Close: -``` - 60 01 - standard header? - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -``` - -## Rotation angle: -``` - 60 08 - header plus settings? - 11 - set rotation angle - 55 d5 - angle (between ff ff and 7b 09, one degree is roughly 3c) - 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -``` - -## FF_AUTOCENTER: -``` - 60 08 - header plus settings? - 03 - set autocenter force - 8f 02 - force (between ff ff and 00 00) - 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -``` - -## FF_GAIN: -``` - 60 02 - header plus gain? - bf - gain (between ff and 00) - 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -``` - -## FF_CONSTANT -### Initialization -``` - 60 00 - standard header - 01 - ID - 6a - new constant effect(?) - fe ff - strength *** - 00 00 - attack_length - 00 00 - attack_level *** - 00 00 - fade_length - 00 00 - fade_level *** - 00 4f - ? - f7 17 - time in milliseconds, unsigned - 00 00 - 07 00 - offset from start(?) - 00 ff ff - end of new effect(?) - 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -``` - -### Modifying (For dynamic updating of effects) - -#### Direction: ``N/A`` - -#### Constant force: -``` -60 00 - standard header -01 - ID -0a - modify constant force -05 16 - constant force *** -00 00 00 00 00 00 00 00 00 00 -00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -``` - -#### Envelope: -``` -60 00 - standard header -01 - ID - 31 - modify envelope -84 - ID of envelope attribute ( attack_level 82, - attack_length 81, - fade_level 88, - fade_length 84 ) - 63 04 - value attribute should be set to ?***? - 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -``` - -#### Duration: -``` - 60 00 - standard header - 01 - ID - 49 00 41 - modify timing? - 6c 20 - length in milliseconds - 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -``` - -#### Offset: `N/A` - -## FF_RAMP -(Why on earth is it this difficult to figure out how a ramp works?) -### Init: -``` - 60 00 - standard header - 01 - ID - 6b - new ramp effect - f6 7f - difference *** - fe ff - level? *** (level = (if end_level > start_level, end_level, else - start_level) - difference?) (signed) - 00 00 - f7 17 - time - 00 80 - some kind of marker - 00 00 - attack_length - f6 7f - attack_level *** - 00 00 - fade_length - f6 7f - fade_level*** - 04 - invert (going "down", 05, going "up", 04) - 4f - f7 17 - time again? - 00 00 - 00 00 - offset - 00 ff ff - end of init - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -``` -### Modifying: -(Sweet baby jesus. I'll figure this out later ``Ramp(?)``) -``` - 60 00 - standard header - 01 - ID - 0e - ??? - 03 - ramp? - e3 04 - difference? - 7d 75 - "level"? - 05 - ???? - 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -``` -#### Envelope: -``` - 60 00 - standard header - 01 - ID - 31 - modify envelope - 84 - ID of envelope attribute ( attack_level 82, - attack_length 81, - fade_level 88, - fade_length 84 ) - 63 04 - value attribute should be set to - 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -``` -#### Duration: -``` - 60 00 - 01 - ID - 4e - ? - 08 - ? - 01 00 - time in milliseconds - 05 - ? - 41 - ? - 01 00 - time in milliseconds - 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -``` -#### Offset: `N/A` - -## FF_SPRING: -### Init: -``` - 60 00 - standard header - 01 - ID - 64 - new conditional effect - fc 7f - positive coefficient (right?) - fc 7f - negative coefficient (left?) - fe ff - deadband (left?) (deadband + offset) (fe ff means no deadband) - fe ff - deadband (right?) (deadband + offset) - a6 6a a6 6a fe ff fe ff fe ff fe ff df 58 a6 6a 06 - some weird hard-coded - values to do with springs? - - 4f - f7 17 - duration - 00 00 - 00 00 - offset - 00 ff ff - end of init - 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -``` - -### Modifying: -Same as below (FF_DAMPER + FF_FRICTION + FF_INERTIA) - -## FF_DAMPER + FF_FRICTION + FF_INERTIA: -### Init: -``` - 60 00 - standard header - 02 -ID - 64 - new conditional effect - fc 7f - positive coefficient (right?) - fc 7f - negative coefficient (left?) - fe ff - deadband (left?) (deadband + offset) (fe ff means no deadband) - fe ff - deadband (right?) (deadband + offset) - fc 7f fc 7f fe ff fe ff fe ff fe ff fc 7f fc 7f 07 - some weird hard-coded - values to do with friction? - 4f - f7 17 - duration - 00 00 - 00 00 - offset - 00 ff ff - end of init - 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -``` -### Modifying: -``` - positive coefficient: - 60 00 - 02 - ID - 0e 41 - 64 35 - value, signed (between 00 00 and 01 80?) - 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - - negative coefficient: - 60 00 - 02 - ID - 0e 42 - 64 35 - value, signed (between 00 00 and ff 7f?) - 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - - deadband/offset: - 60 00 - 02 - ID - 0e 4c - 64 35 - deadband (right?) (deadband + offset) (fe ff means no deadband) - 00 00 - deadband (left?) (deadband + offset) - 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -``` - -### Duration: -``` - 60 00 - standard header - 01 - ID - 49 06 41 - modify timing? - 6c 20 - length in milliseconds - 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -``` - -### Offset: `N/A` - -## FF_PERIODIC: -### Init -``` - 60 00 - standard header - 01 - ID - 6b - new periodic effect - 00 00 - magnitude *** - fe ff - offset (up/down) - 00 00 - phase (left/right) - e8 03 - period - 00 80 - 00 00 - attack_length - 00 00 - attack_level *** - 00 00 - fade_length - 00 00 - fade_level *** - 01 - type of periodic ( square 01, - sine 03, - triangle 02, - sawtooth down 05, - sawtooth up 04 ) - 4f - f7 17 - duration - 00 00 - 00 00 - offset - 00 ff ff - end of init - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -``` - -### Modifying: -``` - magnitude: - 60 00 - 02 - ID - 0e 01 - 64 35 - value, signed (between 00 00 and ff 7f?) - 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - - offset of effect: - 60 00 - 02 - ID - 0e 02 - 64 35 - value, signed (between 00 00 and ff 7f?) - 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - - phase: - 60 00 - 02 - ID - 0e 04 - 64 35 - value, signed (between 80 01 and ff 7f?) - 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - - period: - 60 00 - 02 - ID - 0e 08 - 64 35 - value, signed (between 00 00 and I guess ff 7f?) (max value in - wireshark is 0d 07, but I don't know if that's a driver or program issue) - 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - -``` - -### Envelope: -``` - 60 00 - standard header - 01 - ID - 31 - modify envelope - 84 - ID of envelope attribute ( attack_level 82, - attack_length 81, - fade_level 88, - fade_length 84 ) - 63 04 - value attribute should be set to - 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -``` - -### Duration: -``` - Square: - 60 00 - 01 - ID - 49 - ? - 01 - Effect type - 41 - ? - 4e 0c - Time in milliseconds - 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - - Sine: - 60 00 - 01 - ID - 49 - ? - 03 - Effect type - 41 - ? - ec 13 - Time in milliseconds - 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - - Triangle: - 60 00 - 01 - ID - 49 - ? - 02 - Effect type - 41 - ? - 4e 0c - Time in milliseconds - 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - - Sawtooth up: - 60 00 - 01 - ID - 49 - ? - 04 - Effect type - 41 - ? - 4e 0c - Time in milliseconds - 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - - Sawtooth down: - 60 00 - 01 - ID - 49 - ? - 05 - Effect type - 41 - ? - ec 13 - Time in milliseconds - 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -``` -### Offset: `N/A ?` - -## PS4 Input -``` -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 226 /* wheel */ -ff00.0021 = 125 /* wheel */ -ff00.0021 = 255 /* gas */ -ff00.0021 = 255 /* gas */ -ff00.0021 = 255 /* brake */ -ff00.0021 = 255 /* brake */ -ff00.0021 = 255 /* clutch */ -ff00.0021 = 255 /* clutch */ -ff00.0021 = 0 -ff00.0021 = 255 -ff00.0021 = 255 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -ff00.0021 = 0 -``` -- cgit v1.3 From 29400ff75b47ef4320c3fccef5860e44fd8f3a62 Mon Sep 17 00:00:00 2001 From: Kimplul Date: Sun, 8 Oct 2023 21:46:49 +0300 Subject: docs: general tidbits --- README.md | 9 +++++---- docs/CONTRIBUTING.md | 9 ++++++--- docs/TODO.md | 12 ++---------- src/hid-tmff2.c | 16 ++++------------ src/hid-tmff2.h | 17 +++++++++-------- src/tmt300rs/hid-tmt300rs.c | 17 +++++++++++------ 6 files changed, 37 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index 683fb79..56520b9 100644 --- a/README.md +++ b/README.md @@ -116,7 +116,7 @@ for wheels: [upstreamed](https://github.com/scarburato/hid-tminit), you might want to blacklist the kernel module `hid-thrustmaster`. Do this with ```shell - echo 'blacklist hid_thrustmaster' > /etc/modprobe.d/hid_thrustmaster.con + echo 'blacklist hid_thrustmaster' > /etc/modprobe.d/hid_thrustmaster.conf ``` + If you've bought a new wheel, you will most likely have to update the firmware @@ -141,9 +141,9 @@ for wheels: + If a wheel has a deadzone in games, you can try setting up a udev rule: `/etc/udev/rules.d/99-joydev.rules` - ``` SUBSYSTEM=="input", ATTRS{idVendor}=="044f", - ATTRS{idProduct}=="WHEEL_ID", RUN+="/usr/bin/evdev-joystick --evdev - %E{DEVNAME} --deadzone 0" ``` + ``` + SUBSYSTEM=="input", ATTRS{idVendor}=="044f", ATTRS{idProduct}=="WHEEL_ID", RUN+="/usr/bin/evdev-joystick --evdev %E{DEVNAME} --deadzone 0" + ``` where `WHEEL_ID` is @@ -153,6 +153,7 @@ for wheels: | T300 RS, PS3 advanced mode | b66f | | T300 RS, PS4 normal mode | b66d | | T248 | b696 | + | TX | b669 | This should make sure that the wheel behaves like you'd want from a wheel. diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 46b8b26..7e4b0dd 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -15,13 +15,16 @@ Currently open requests for wheels: Other documents available are linked here: + [FFBEEFFECTS](./FFBEFFECTS.md): - Force feedback effects example for T300RS and compatible wheels + Force feedback effects example for T300RS and compatible wheels + [STRUCTURE](./STRUCTURE.md): - Structure of Thrustmaster device stack + Structure of Thrustmaster device stack + ++ [DRIVER.md](./DRIVER.md): + Info on installing Thrustmaster drivers under Wine + [TODO](./TODO.md): - TO-DO list for maintainers + TODO list for maintainers ## How to capture what effects a game sends to the driver? diff --git a/docs/TODO.md b/docs/TODO.md index 14b8d2e..632a8a8 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -1,11 +1,3 @@ -# TO-DO list +# TODO list -- Make code more general to allow for stuff like T150/T248/T500 to be relatively easily included. - -- Use workqueues instead of doing everything in t300rs_timer - -- Document changes to code in an organised manner. - -### T248 -- open/close seems to crash the wheel and the kernel along with it, check how Windows handles it -- fix rdesc, 0x0a somewhere needs to be set to 0x60 ++ Try to figure out how/if RRRE (#39) determines FFB from wheel input diff --git a/src/hid-tmff2.c b/src/hid-tmff2.c index 44df1e3..013039f 100644 --- a/src/hid-tmff2.c +++ b/src/hid-tmff2.c @@ -101,7 +101,6 @@ static ssize_t spring_level_store(struct device *dev, static ssize_t spring_level_show(struct device *dev, struct device_attribute *attr, char *buf) { - return scnprintf(buf, PAGE_SIZE, "%u\n", spring_level); } static DEVICE_ATTR_RW(spring_level); @@ -132,7 +131,6 @@ static ssize_t damper_level_store(struct device *dev, static ssize_t damper_level_show(struct device *dev, struct device_attribute *attr, char *buf) { - return scnprintf(buf, PAGE_SIZE, "%u\n", damper_level); } static DEVICE_ATTR_RW(damper_level); @@ -163,12 +161,7 @@ static ssize_t friction_level_store(struct device *dev, static ssize_t friction_level_show(struct device *dev, struct device_attribute *attr, char *buf) { - size_t count; - - - count = scnprintf(buf, PAGE_SIZE, "%u\n", friction_level); - - return count; + return scnprintf(buf, PAGE_SIZE, "%u\n", friction_level); } static DEVICE_ATTR_RW(friction_level); @@ -179,7 +172,6 @@ static ssize_t range_store(struct device *dev, unsigned int value; int ret; - if (!tmff2) return -ENODEV; @@ -199,7 +191,6 @@ static ssize_t range_store(struct device *dev, static ssize_t range_show(struct device *dev, struct device_attribute *attr, char *buf) { - return scnprintf(buf, PAGE_SIZE, "%u\n", range); } static DEVICE_ATTR_RW(range); @@ -333,7 +324,7 @@ static void tmff2_work_handler(struct work_struct *w) } else { __clear_bit(FF_EFFECT_QUEUE_UPLOAD, &state->flags); /* if we're uploading an effect, it's bound to be the up - * to date available */ + * to date */ __clear_bit(FF_EFFECT_QUEUE_UPDATE, &state->flags); } } @@ -458,6 +449,8 @@ static void tmff2_close(struct input_dev *dev) return; /* since we're closing the device, no need to continue feeding it new data */ + /* TODO: check somewhere that multiple users can't open us at the same + * time */ cancel_delayed_work_sync(&tmff2->work); if (tmff2->close) { @@ -620,7 +613,6 @@ static int tmff2_probe(struct hid_device *hdev, const struct hid_device_id *id) hid_set_drvdata(tmff2->hdev, tmff2); switch (tmff2->hdev->product) { - /* t300rs */ case TMT300RS_PS3_NORM_ID: case TMT300RS_PS3_ADV_ID: case TMT300RS_PS4_NORM_ID: diff --git a/src/hid-tmff2.h b/src/hid-tmff2.h index c8019dd..1eb3546 100644 --- a/src/hid-tmff2.h +++ b/src/hid-tmff2.h @@ -17,7 +17,7 @@ extern int alt_mode; #define USB_VENDOR_ID_THRUSTMASTER 0x044f /* the wheel seems to only be capable of processing a certain number of - * interrupts per second, and if this value is too low the kernel urb buffer(or + * interrupts per second, and if this value is too low the kernel urb buffer (or * some buffer at least) fills up. Optimally I would figure out some way to * space out the interrupts so that they all leave at regular intervals, but * for now this is good enough, go slow enough that everything works. @@ -66,7 +66,7 @@ struct tmff2_device_entry { int allow_scheduling; - /* fields relevant to each actual device (T300, T150...) */ + /* fields relevant to each actual device (T300, T248...) */ void *data; unsigned long params; unsigned long max_effects; @@ -86,15 +86,16 @@ struct tmff2_device_entry { int (*close)(void *data, int); int (*set_gain)(void *data, uint16_t gain); int (*set_range)(void *data, uint16_t range); - /* switch_mode has to not do anything if we're alredy in the specified - * mode */ + /* switch_mode is required to not do anything if we're alredy in the + * specified mode */ int (*switch_mode)(void *data, uint16_t mode); ssize_t (*alt_mode_show)(void *data, char *buf); ssize_t (*alt_mode_store)(void *data, const char *buf, size_t count); int (*set_autocenter)(void *data, uint16_t autocenter); __u8 *(*wheel_fixup)(struct hid_device *hdev, __u8 *rdesc, unsigned int *rsize); - /* void pointers are dangerous, I know, but in this case likely the best option... */ + /* void pointers are dangerous, I know, but in this case likely the + * best option... */ }; /* external */ @@ -108,11 +109,11 @@ int tx_populate_api(struct tmff2_device_entry *tmff2); #define TMT248_PC_ID 0xb696 -#define TX_ACTIVE 0xb669 +#define TX_ACTIVE 0xb669 /* APIs to different wheel families */ -/* T248 at least uses the T300RS api, not sure if there are other wheels but that's - * why these functions are given global linkage */ +/* T248 and TX at least uses the T300RS api, not sure if there are other wheels + * but that's why these functions are given global linkage */ struct t300rs_device_entry { struct hid_device *hdev; diff --git a/src/tmt300rs/hid-tmt300rs.c b/src/tmt300rs/hid-tmt300rs.c index 20bb239..0191620 100644 --- a/src/tmt300rs/hid-tmt300rs.c +++ b/src/tmt300rs/hid-tmt300rs.c @@ -1187,7 +1187,8 @@ int t300rs_update_effect(void *data, struct tmff2_effect_state *state) case FF_PERIODIC: return t300rs_update_periodic(t300rs, state); default: - hid_err(t300rs->hdev, "invalid effect type: %x", state->effect.type); + hid_err(t300rs->hdev, "invalid effect type: %x", + state->effect.type); return -1; } } @@ -1209,7 +1210,8 @@ int t300rs_upload_effect(void *data, struct tmff2_effect_state *state) case FF_PERIODIC: return t300rs_upload_periodic(t300rs, state); default: - hid_err(t300rs->hdev, "invalid effect type: %x", state->effect.type); + hid_err(t300rs->hdev, "invalid effect type: %x", + state->effect.type); return -1; } } @@ -1328,7 +1330,9 @@ int t300rs_set_autocenter(void *data, uint16_t value) if (!t300rs) return -ENODEV; - /* TODO: this should probably also use a separately allocated buffer? */ + /* TODO: this should probably also use a separately allocated buffer? + * someone might change autocentering while we're updating the buffer + * which would cause corruption */ autocenter_packet = (struct t300rs_packet_autocenter *)t300rs->send_buffer; autocenter_packet->header.cmd = 0x08; @@ -1477,7 +1481,7 @@ static int t300rs_check_firmware(struct t300rs_device_entry *t300rs) return -ENOMEM; } - /* Fetch firmware version */ + /* fetch firmware version */ ret = usb_control_msg(t300rs->usbdev, usb_rcvctrlpipe(t300rs->usbdev, 0), t300rs_fw_request.bRequest, @@ -1494,7 +1498,7 @@ static int t300rs_check_firmware(struct t300rs_device_entry *t300rs) goto out; } - /* Educated guess */ + /* educated guess */ if (fw_response->fw_version < 31 && ret >= 0) { hid_err(t300rs->hdev, "firmware version %i is too old, please update.\n", @@ -1574,7 +1578,8 @@ static int t300rs_get_attachment(struct t300rs_device_entry *t300rs) } else if (response->type == cpu_to_le16(0x47)) { attachment = response->b.attachment; } else { - hid_err(t300rs->hdev, "unknown packet type %hx\n, please contact a maintainer", + hid_err(t300rs->hdev, + "unknown packet type %hx\n, please contact a maintainer", response->type); ret = -EINVAL; goto out; -- cgit v1.3 From 5fb8d85c99a61ec79baeb5dcda82302972499b46 Mon Sep 17 00:00:00 2001 From: Kimplul Date: Sun, 8 Oct 2023 21:48:34 +0300 Subject: docs: add TS-PC racer to list of open requests + Move list from CONTRIBUTING to README --- README.md | 1 + docs/CONTRIBUTING.md | 6 ------ 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/README.md b/README.md index 56520b9..afb32fa 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,7 @@ for wheels: + [T818](https://github.com/Kimplul/hid-tmff2/issues/58) + [T-GT II](https://github.com/Kimplul/hid-tmff2/issues/55) + [T128P](https://github.com/Kimplul/hid-tmff2/issues/67) ++ [TS-PC](https://github.com/Kimplul/hid-tmff2/issues/65) ## FAQ (Frequently Asked Questions) + Reportedly some games running under Wine/Proton won't recognize wheels without diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 7e4b0dd..51b405e 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -6,12 +6,6 @@ This project is seeking for help from people who can contribute. If you have a wheel that's not on the list, but suspect it might fit into the driver, please feel free to open up an issue about it. -Currently open requests for wheels: -+ [T500 RS](https://github.com/Kimplul/hid-tmff2/issues/18) -+ [T818](https://github.com/Kimplul/hid-tmff2/issues/58) -+ [T-GT II](https://github.com/Kimplul/hid-tmff2/issues/55) -+ [T128P](https://github.com/Kimplul/hid-tmff2/issues/67) - Other documents available are linked here: + [FFBEEFFECTS](./FFBEFFECTS.md): -- cgit v1.3 From 1ac3f52cfd972787e4684350f7593614837b6b19 Mon Sep 17 00:00:00 2001 From: Kimplul Date: Sun, 8 Oct 2023 21:53:41 +0300 Subject: docs: remove highlighting from url --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index afb32fa..856ed27 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,7 @@ sudo yum install kernel-devel kernel-headers # Fedora-based > **NOTE:** On some systems, you will get an error/warning about SSL. This is > normal for unsigned modules. For info on signing modules yourself > (completely optional), see -> [here](https://www.kernel.org/doc/html/latest/admin-guide/module-signing.html?highlight=module%20signing). +> [here](https://www.kernel.org/doc/html/latest/admin-guide/module-signing.html). > **NOTE:** Thrustmaster TX wheels aren't supported by `hid-tminit` as of yet, > meaning that TX wheels have to be initialized with `tmdrv`. Please see -- cgit v1.3