aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Kbuild3
-rw-r--r--README.md30
-rw-r--r--docs/FFBEFFECTS.md215
-rw-r--r--docs/INTEGRATION.md5
-rw-r--r--src/hid-tmff2.c69
-rw-r--r--src/hid-tmff2.h3
-rw-r--r--src/tmt248/hid-tmt248.c9
-rw-r--r--src/tmt300rs/hid-tmt300rs.c381
-rw-r--r--src/tmtsxw/hid-tmtsxw.c324
-rw-r--r--src/tmtx/hid-tmtx.c9
10 files changed, 800 insertions, 248 deletions
diff --git a/Kbuild b/Kbuild
index 7ecc58c..3f4d740 100644
--- a/Kbuild
+++ b/Kbuild
@@ -3,4 +3,5 @@ hid-tmff-new-y := \
src/hid-tmff2.o \
src/tmt300rs/hid-tmt300rs.o \
src/tmt248/hid-tmt248.o \
- src/tmtx/hid-tmtx.o
+ src/tmtx/hid-tmtx.o \
+ src/tmtsxw/hid-tmtsxw.o
diff --git a/README.md b/README.md
index f9d5ba6..c7094c0 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# Linux kernel module for Thrustmaster T300RS, T248 and TX (experimental) wheels
+# Linux kernel module for Thrustmaster T300RS, T248 and (experimental) TX and TS-XW 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
@@ -13,8 +13,8 @@
## Description
-A Linux kernel module for Thrustmaster T300RS, T248, and TX
-(experimental support) wheels.
+A Linux kernel module for Thrustmaster T300RS, T248, and (experimental support)
+TX and TS-XV 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
@@ -25,13 +25,22 @@ recognized by the games, they might not be mapped correctly.
I only have access to the base editions of T300RS and T248 wheels to test with, but
from reports it seems that other editions (F1, GT, Alcantara, etc.) should also work
-with this driver. TX support was contributed by
-[@davidedmundson](https://github.com/davidedmundson).
+with this driver.
+
+TX support was contributed by
+[@davidedmundson](https://github.com/davidedmundson),
+TS-XW support was contributed by
+[@yassineimounachen](https://github.com/yassineimounachen).
## Installation
You can either install this kernel module by using DKMS or
-manually building from source:
+manually building from source. If you're unsure which to pick,
+go with DKMS, it will automatically recompile the driver
+whenever needed.
+
+An AUR package is also available:
+[hid-tmff2-dkms-git](https://aur.archlinux.org/packages/hid-tmff2-dkms-git)
### Dependencies
@@ -41,6 +50,7 @@ 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 pacman -S linux-neptune-61-headers # For SteamDeck specifically
sudo yum install kernel-devel kernel-headers # Fedora-based
```
@@ -76,8 +86,8 @@ sudo yum install kernel-devel kernel-headers # Fedora-based
> (completely optional), see
> [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
+> **NOTE:** Thrustmaster TX and TS-XW wheels aren't supported by `hid-tminit` as of yet,
+> meaning that the wheels have to be initialized with `tmdrv`. Please see
> https://github.com/Kimplul/hid-tmff2/issues/48.
> **WARNING:** There have been reports that this driver does not work if
@@ -103,7 +113,7 @@ 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)
++ [T128P](https://github.com/Kimplul/hid-tmff2/issues/67) (initial alpha support, needs `hid-tminit` tweaking)
+ [TS-PC](https://github.com/Kimplul/hid-tmff2/issues/65)
## Common issues and notes
@@ -123,7 +133,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.conf
+ echo 'blacklist hid_thrustmaster' | sudo tee /etc/modprobe.d/hid_thrustmaster.conf
```
+ If you've bought a new wheel, you will most likely have to update the firmware
diff --git a/docs/FFBEFFECTS.md b/docs/FFBEFFECTS.md
index e2b3edc..93dbb80 100644
--- a/docs/FFBEFFECTS.md
+++ b/docs/FFBEFFECTS.md
@@ -16,7 +16,7 @@ have model specific peculiarities, though.
> USB interface, not the only available ones.
## GENERAL
-### Playing
+### Playing a single effect once
```
60 00 - standard header
01 - ID
@@ -28,6 +28,19 @@ have model specific peculiarities, though.
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
```
+### Playing a single effect multiple or infinite times
+```
+ 60 00 - standard header
+ 01 - ID
+ 89 - playing options
+ 41 - play with count
+ 00 00 00 00 - play count, 0 for infinite, seems to be u16
+ 00 00 00 00 00 00 00
+ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ 00 00 00 00 00 00 00 00 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
@@ -112,7 +125,7 @@ have model specific peculiarities, though.
60 00 - standard header
01 - ID
6a - new constant effect(?)
- fe ff - strength ***
+ fe ff - strength between ff bf (-16385) and fd 3f (16381) ***
00 00 - attack_length
00 00 - attack_level ***
00 00 - fade_length
@@ -136,7 +149,7 @@ have model specific peculiarities, though.
60 00 - standard header
01 - ID
0a - modify constant force
- 05 16 - constant force ***
+ 05 16 - constant force between ff bf (-16385) and fd 3f (16381) ***
00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
@@ -249,21 +262,34 @@ Ramps seem to follow triangle wave parameters.
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
```
-#### Offset: `N/A`
+#### Offset: `N/A`
-## FF_SPRING:
+## FF_DAMPER + FF_FRICTION + FF_INERTIA + 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?
-
+ 02 -ID
+ 64 - new conditional effect (sometimes e4)
+ fc 7f - positive coefficient (right)
+ between 01 80 (-32767) and fc 7f (32764)
+ fc 7f - negative coefficient (left)
+ between 01 80 (-32767) and fc 7f (32764)
+ fe ff - deadband right (offset + deadband)
+ between 01 80 (-32767) and ff 7f (32767)
+ fe ff - deadband left (offset - deadband)
+ between 01 80 (-32767) and ff 7f (32767)
+ fc 7f - positive saturation (right)
+ between 00 00 and fc 7f (32764) or a6 6a (27302)
+ fc 7f - negative saturation (left)
+ between 00 00 and fc 7f (32764) or a6 6a (27302)
+ fe ff fe ff fe ff fe ff - some weird hard-coded
+ values to do with friction?
+ fc 7f - max positive saturation (type 07 - fc 7f
+ type 06 - a6 6a)
+ fc 7f - max negative saturation (type 07 - fc 7f
+ type 06 - a6 6a)
+ 07 - type (damper, friction, inertia - 07
+ spring - 06)
4f
f7 17 - duration
00 00
@@ -274,36 +300,36 @@ Ramps seem to follow triangle wave parameters.
```
### 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
+4th byte 5th byte hex
+00001110 01000001 0e 41 positive coefficient
+00001110 01000010 0e 42 negative coefficient
+00001110 01000011 0e 43 both coefficint
+00001110 01001100 0e 4c right and left deadband
+00001110 01010000 0e 50 positive saturation
+00001110 01100000 0e 60 negative saturation
+00001110 01110000 0e 70 both saturation
+00001110 01001111 0e 4f coefficient + deadband
+00001110 01111100 0e 7c deadband + saturation
+00001110 01110011 0e 73 coefficient + saturation
+00001110 01101101 0e 6d positive coefficient + deadband + negative saturation
+01001110 01000001 4e 41 positive coefficient + duration
+00001100 0c all effect specific parameters
+01001001 49 duration
+01001100 4c all effect specific parameters + duration
```
-### Modifying:
+Any combination that makes sense is possible, not restricted to the list above.
+
+The order of the fields are defined in the `all effect specific parameters + duration` packet example below.
+
```
positive coefficient:
60 00
02 - ID
0e 41
- 64 35 - value, signed (between 00 00 and 01 80?)
+ 64 35 - value, signed, between 01 80 (-32767) and fc 7f (32764)
00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
@@ -313,31 +339,121 @@ Same as below (FF_DAMPER + FF_FRICTION + FF_INERTIA)
60 00
02 - ID
0e 42
- 64 35 - value, signed (between 00 00 and ff 7f?)
+ 64 35 - value, signed, between 01 80 (-32767) and fc 7f (32764)
00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ positive and negative coefficient:
+ 60 00
+ 01 - ID
+ 0e 43
+ ab 67 - right coefficient, signed, between 01 80 (-32767) and fc 7f (32764)
+ 50 18 - left coefficient, signed, between 01 80 (-32767) and fc 7f (32764)
+ 00 00 00 00 00 00 00
+ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ 00 00 00 00 00 00 00 00 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)
+ 64 35 - deadband right (offset + deadband)
+ between 01 80 (-32767) and ff 7f (32767)
+ 00 00 - deadband left (offset - deadband)
+ between 01 80 (-32767) and ff 7f (32767)
+ 00 00 00 00 00 00 00
+ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+
+ positive saturation
+ 60 00
+ 01 - ID
+ 0e 50
+ fc 7f - value between 00 00 and fc 7f or a6 6a (defined in 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 00 00 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 saturation
+ 60 00
+ 01 - ID
+ 0e 60
+ fc 7f - value between 00 00 and fc 7f or a6 6a (defined in 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 00 00 00 00 00 00 00 00 00 00
+ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+
+ positive and negative saturation
+ 60 00
+ 01 - ID
+ 0e 70
+ da 5d - right saturation between 00 00 and fc 7f or a6 6a (defined in init)
+ b9 0b - left saturation between 00 00 and fc 7f or a6 6a (defined in 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 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+
+ all effect specific parameters
+ 60 00
+ 01 - ID
+ 0c
+ fe 1f - positive coefficient
+ 98 19 - negative coefficient
+ 96 59 - right deadband
+ fe 3f - left deadband
+ 65 26 - positive saturation
+ 98 19 - negative saturation
+ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+
+ all effect specific parameters + duration
+ 60 00
+ 01 - ID
+ 4c
+ 30 73 - positive coefficient
+ 30 73 - negative coefficient
+ ff 7f - right deadband
+ fc ff - left deadband
+ fd 5f - positive saturation
+ fd 5f - negative saturation
+ 06 - effect type (condition)?
+ 45 - update type, see below
+ 10 27 - length in milliseconds
+ 00 00 - offset 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
+
```
### Duration:
+
+Update type:
+
+```
+6th byte hex
+01000001 41 length
+01000100 44 offset
+01000101 45 length + offset
+```
+
```
60 00 - standard header
01 - ID
- 49 06 41 - modify timing?
+ 49
+ 06 - effect type (condition)?
+ 45 - update type
6c 20 - length in milliseconds
- 00 00 00 00 00 00 00 00
+ 00 00 - offset 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
@@ -351,10 +467,11 @@ Same as below (FF_DAMPER + FF_FRICTION + FF_INERTIA)
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 00 - magnitude between 00 00 and fc 7f (32764) ***
+ fe ff - offset (up/down) between ff bf (-16385) and fd 3f (16381)
+ 00 00 - phase (left/right) between 00 00 and 4a f7 (32586)
+ meaning is 0 to ~359 deg phase shift in 5b steps
+ e8 03 - period between 00 00 and ff ff (in milliseconds)
00 80
00 00 - attack_length
00 00 - attack_level ***
@@ -380,7 +497,7 @@ Same as below (FF_DAMPER + FF_FRICTION + FF_INERTIA)
60 00
02 - ID
0e 01
- 64 35 - value, signed (between 00 00 and ff 7f?)
+ 64 35 - value, between 00 00 and fc 7f (32764)
00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
@@ -390,7 +507,7 @@ Same as below (FF_DAMPER + FF_FRICTION + FF_INERTIA)
60 00
02 - ID
0e 02
- 64 35 - value, signed (between 00 00 and ff 7f?)
+ 64 35 - value, between ff bf (-16385) and fd 3f (16381)
00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
@@ -400,7 +517,8 @@ Same as below (FF_DAMPER + FF_FRICTION + FF_INERTIA)
60 00
02 - ID
0e 04
- 64 35 - value, signed (between 80 01 and ff 7f?)
+ 64 35 - value, between 00 00 and 4a f7 (32586) in 5b steps
+ meaning is 0 to 359 deg phase shift
00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
@@ -410,8 +528,7 @@ Same as below (FF_DAMPER + FF_FRICTION + FF_INERTIA)
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)
+ 64 35 - value, between 00 00 and ff ff (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
diff --git a/docs/INTEGRATION.md b/docs/INTEGRATION.md
index bcdb9f4..3d7c1aa 100644
--- a/docs/INTEGRATION.md
+++ b/docs/INTEGRATION.md
@@ -3,4 +3,7 @@ in the README. This page is intended to collect information about the topic.
# NixOS
-See https://github.com/Kimplul/hid-tmff2/issues/71.
+The previous discussion was in https://github.com/Kimplul/hid-tmff2/issues/71,
+but it seems to have been removed by GitHub. For now, please refer to
+
+https://search.nixos.org/packages?query=hid-tmff2
diff --git a/src/hid-tmff2.c b/src/hid-tmff2.c
index ee4232a..3b51445 100644
--- a/src/hid-tmff2.c
+++ b/src/hid-tmff2.c
@@ -292,7 +292,7 @@ static void tmff2_work_handler(struct work_struct *w)
struct tmff2_effect_state *state;
int max_count = 0, effect_id;
unsigned long time_now;
- __u16 effect_length;
+ __u16 effect_delay, effect_length;
if (!tmff2)
@@ -304,17 +304,14 @@ static void tmff2_work_handler(struct work_struct *w)
time_now = JIFFIES2MS(jiffies);
state = &tmff2->states[effect_id];
+ effect_delay = state->effect.replay.delay;
effect_length = state->effect.replay.length;
if (test_bit(FF_EFFECT_PLAYING, &state->flags) && effect_length) {
- if ((time_now - state->start_time) >= effect_length) {
+ if ((time_now - state->start_time) >= (effect_delay + effect_length) * state->count) {
__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);
+ state->count = 0;
}
}
@@ -359,6 +356,7 @@ static void tmff2_work_handler(struct work_struct *w)
max_count = state->count;
spin_unlock(&tmff2->lock);
+
/* wait for each effect update to actually be sent out to avoid
* filling up usb output queue */
hid_hw_wait(tmff2->hdev);
@@ -368,6 +366,32 @@ static void tmff2_work_handler(struct work_struct *w)
schedule_delayed_work(&tmff2->work, msecs_to_jiffies(timer_msecs));
}
+static void tmff2_rewrite_rumble(struct ff_effect *effect)
+{
+ /* this is more or less directly copied from
+ * https://elixir.bootlin.com/linux/latest/source/drivers/input/ff-core.c#L48
+ * except we set the direction to east, to make sure the direction
+ * scaling doesn't do any funny business */
+ int magnitude = 0;
+ if (effect->type != FF_RUMBLE)
+ return;
+
+ magnitude = effect->u.rumble.strong_magnitude / 3
+ + effect->u.rumble.weak_magnitude / 6;
+
+ effect->type = FF_PERIODIC;
+ effect->direction = 16384;
+ effect->u.periodic.waveform = FF_SINE;
+ effect->u.periodic.period = 50;
+ effect->u.periodic.magnitude = magnitude;
+ effect->u.periodic.offset = 0;
+ effect->u.periodic.phase = 0;
+ effect->u.periodic.envelope.attack_length = 0;
+ effect->u.periodic.envelope.attack_level = 0;
+ effect->u.periodic.envelope.fade_length = 0;
+ effect->u.periodic.envelope.fade_level = 0;
+}
+
static int tmff2_upload(struct input_dev *dev,
struct ff_effect *effect, struct ff_effect *old)
{
@@ -385,10 +409,13 @@ static int tmff2_upload(struct input_dev *dev,
spin_lock(&tmff2->lock);
state->effect = *effect;
+ tmff2_rewrite_rumble(&state->effect);
if (old) {
- if (!test_bit(FF_EFFECT_QUEUE_UPDATE, &state->flags))
+ if (!test_bit(FF_EFFECT_QUEUE_UPDATE, &state->flags)) {
state->old = *old;
+ tmff2_rewrite_rumble(&state->old);
+ }
__set_bit(FF_EFFECT_QUEUE_UPDATE, &state->flags);
} else {
@@ -555,10 +582,22 @@ static int tmff2_wheel_init(struct tmff2_device_entry *tmff2)
for (i = 0; tmff2->supported_effects[i] >= 0; ++i)
__set_bit(tmff2->supported_effects[i], tmff2->input_dev->ffbit);
+ /* tell ffb subsystem that we want to handle our own rumble effects,
+ * this is a (maybe kind of silly) workaround to the ffb subsystem
+ * setting the direction of rumble to 0, which in our case means 0
+ * force. Instead, do exactly what the subsystem would do, but update
+ * the direction.
+ *
+ * It *might* be a good idea to change this in the subsystem proper, but
+ * I don't know if anyone relies on the rumble to be 0 degrees, consider
+ * this just a proof of concept for now */
+ if (test_bit(FF_PERIODIC, tmff2->input_dev->ffbit))
+ __set_bit(FF_RUMBLE, 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;
+ goto ff_err;
}
/* set ff callbacks */
@@ -589,12 +628,15 @@ static int tmff2_wheel_init(struct tmff2_device_entry *tmff2)
/* create files */
if ((ret = tmff2_create_files(tmff2)))
- goto err;
+ goto file_err;
tmff2->allow_scheduling = 1;
return 0;
+file_err:
input_ff_destroy(tmff2->input_dev);
+ff_err:
+ kfree(tmff2->states);
err:
return ret;
}
@@ -632,7 +674,10 @@ static int tmff2_probe(struct hid_device *hdev, const struct hid_device_id *id)
if ((ret = tx_populate_api(tmff2)))
goto wheel_err;
break;
-
+ case TSXW_ACTIVE:
+ if ((ret = tsxw_populate_api(tmff2)))
+ goto wheel_err;
+ break;
default:
ret = -ENODEV;
goto wheel_err;
@@ -727,6 +772,8 @@ static const struct hid_device_id tmff2_devices[] = {
{HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, TMT248_PC_ID)},
/* tx */
{HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, TX_ACTIVE)},
+ /* tsxw */
+ {HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, TSXW_ACTIVE)},
{}
};
diff --git a/src/hid-tmff2.h b/src/hid-tmff2.h
index 1eb3546..666d249 100644
--- a/src/hid-tmff2.h
+++ b/src/hid-tmff2.h
@@ -102,6 +102,7 @@ struct tmff2_device_entry {
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);
+int tsxw_populate_api(struct tmff2_device_entry *tmff2);
#define TMT300RS_PS3_NORM_ID 0xb66e
#define TMT300RS_PS3_ADV_ID 0xb66f
@@ -111,6 +112,8 @@ int tx_populate_api(struct tmff2_device_entry *tmff2);
#define TX_ACTIVE 0xb669
+#define TSXW_ACTIVE 0xb692
+
/* APIs to different wheel families */
/* 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 */
diff --git a/src/tmt248/hid-tmt248.c b/src/tmt248/hid-tmt248.c
index af3aaa9..9ac43d9 100644
--- a/src/tmt248/hid-tmt248.c
+++ b/src/tmt248/hid-tmt248.c
@@ -163,7 +163,7 @@ err:
return ret;
}
-int t248_wheel_destroy(void *data)
+static int t248_wheel_destroy(void *data)
{
struct t300rs_device_entry *t300rs = data;
@@ -175,7 +175,7 @@ int t248_wheel_destroy(void *data)
return 0;
}
-int t248_set_range(void *data, uint16_t value)
+static int t248_set_range(void *data, uint16_t value)
{
struct t300rs_device_entry *t248 = data;
@@ -251,9 +251,10 @@ static int t248_close(void *data, int open_mode)
return 0;
}
-int t248_wheel_init(struct tmff2_device_entry *tmff2, int open_mode)
+static 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 t300rs_device_entry *t248 = kzalloc(sizeof(struct t300rs_device_entry),
+ GFP_KERNEL);
struct list_head *report_list;
int ret;
diff --git a/src/tmt300rs/hid-tmt300rs.c b/src/tmt300rs/hid-tmt300rs.c
index 0191620..40be801 100644
--- a/src/tmt300rs/hid-tmt300rs.c
+++ b/src/tmt300rs/hid-tmt300rs.c
@@ -330,19 +330,94 @@ static u8 t300rs_rdesc_ps4_fixed[] = {
0xc0,
};
-static u8 spring_values[] = {
- 0xa6, 0x6a, 0xa6, 0x6a, 0xfe,
- 0xff, 0xfe, 0xff, 0xfe, 0xff,
- 0xfe, 0xff, 0xdf, 0x58, 0xa6,
- 0x6a, 0x06
+static u8 condition_values[] = {
+ 0xfe, 0xff, 0xfe, 0xff, 0xfe,
+ 0xff, 0xfe, 0xff
};
-static u8 damper_values[] = {
- 0xfc, 0x7f, 0xfc, 0x7f, 0xfe,
- 0xff, 0xfe, 0xff, 0xfe, 0xff,
- 0xfe, 0xff, 0xfc, 0x7f, 0xfc,
- 0x7f, 0x07
-};
+static void t300rs_calculate_periodic_values(struct ff_effect *effect)
+{
+ struct ff_periodic_effect *periodic = &effect->u.periodic;
+ int16_t headroom;
+
+ effect->replay.length -= 1;
+
+ periodic->magnitude = (periodic->magnitude * fixp_sin16(effect->direction * 360 / 0x10000)) / 0x7fff;
+
+ if (periodic->magnitude < 0){
+ /* the wheel handles positive magnitudes only */
+ periodic->magnitude = -periodic->magnitude;
+
+ /* to give the expected result 180 deg is added to the phase */
+ periodic->phase = (periodic->phase + (0x10000 / 2)) % 0x10000;
+ }
+
+ /* the interval [0; 32677[ is used by the wheel for the [0; 360[ degree phase shift */
+ periodic->phase = periodic->phase * 32677 / 0x10000;
+
+ headroom = 0x7fff - periodic->magnitude;
+ /* magnitude + offset cannot be outside the valid magnitude range, */
+ /* otherwise the wheel behaves incorrectly */
+ periodic->offset = clamp(periodic->offset, -headroom, headroom);
+}
+
+static uint16_t t300rs_condition_max_saturation(uint16_t effect_type)
+{
+ if(effect_type == FF_SPRING)
+ return 0x6aa6;
+
+ return 0x7ffc;
+}
+
+static uint16_t t300rs_condition_effect_type(uint16_t effect_type)
+{
+ if(effect_type == FF_SPRING)
+ return 0x06;
+
+ return 0x07;
+}
+
+static int16_t t300rs_calculate_coefficient(int16_t coeff, uint16_t effect_type)
+{
+ uint8_t input_level;
+
+ switch (effect_type)
+ {
+ case FF_SPRING:
+ input_level = spring_level;
+ break;
+ case FF_DAMPER:
+ input_level = damper_level;
+ break;
+ case FF_FRICTION:
+ input_level = friction_level;
+ break;
+ default:
+ input_level = 100;
+ break;
+ }
+
+ return coeff * input_level / 100;
+}
+
+static uint16_t t300rs_calculate_saturation(uint16_t sat, uint16_t effect_type)
+{
+ uint16_t max = t300rs_condition_max_saturation(effect_type);
+
+ if(sat == 0)
+ return max;
+
+ return sat * max / 0xffff;
+}
+
+static void t300rs_calculate_deadband(int16_t *out_rband, int16_t *out_lband,
+ uint16_t deadband, int16_t offset)
+{
+ /* max deadband value is 0x7fff in either direction */
+ /* deadband is the width of the deadzone, one direction is half of it */
+ *out_rband = clamp(offset + (deadband / 2), -0x7fff, 0x7fff);
+ *out_lband = clamp(offset - (deadband / 2), -0x7fff, 0x7fff);
+}
int t300rs_send_buf(struct t300rs_device_entry *t300rs, u8 *send_buffer, size_t len)
{
@@ -383,14 +458,20 @@ 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;
+ uint8_t code;
+ uint16_t count;
} *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;
+ play_packet->code = 0x41;
+
+ if (state->count == 0 || state->count >= 65535)
+ play_packet->count = 0;
+ else
+ play_packet->count = cpu_to_le16(state->count);
ret = t300rs_send_int(t300rs);
if (ret)
@@ -630,6 +711,8 @@ static int t300rs_update_constant(struct t300rs_device_entry *t300rs,
int16_t level;
level = (constant.level * fixp_sin16(effect.direction * 360 / 0x10000)) / 0x7fff;
+ /* the Windows driver uses the range [-16385;16381] */
+ level = level / 2;
if ((constant.level != constant_old.level) || (effect.direction != old.direction)) {
@@ -738,114 +821,108 @@ error:
return ret;
}
-static int t300rs_update_damper(struct t300rs_device_entry *t300rs,
+static int t300rs_update_condition(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 ff_condition_effect cond = effect.u.condition[0];
+ struct ff_condition_effect cond_old = old.u.condition[0];
+ struct __packed t300rs_packet_mod_condition
+ {
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;
+ uint16_t right_coeff;
+ uint16_t left_coeff;
+ uint16_t right_deadband;
+ uint16_t left_deadband;
+ uint16_t right_saturation;
+ uint16_t left_saturation;
+ uint8_t effect_type;
+ uint8_t update_type;
+ uint16_t duration;
+ uint16_t delay;
+ } *packet_mod_condition = (struct t300rs_packet_mod_condition *)t300rs->send_buffer;
- if (state->effect.type == FF_SPRING)
- input_level = spring_level;
+ int ret = 0;
+ uint16_t duration, duration_old;
+ uint16_t right_sat, right_sat_old, left_sat, left_sat_old;
+ uint16_t right_coeff, right_coeff_old, left_coeff, left_coeff_old;
+ int16_t right_deadband, right_deadband_old, left_deadband, left_deadband_old;
- if (damper.right_coeff != damper_old.right_coeff) {
- int16_t coeff = damper.right_coeff * input_level / 100;
+ right_coeff = t300rs_calculate_coefficient(cond.right_coeff, effect.type);
+ right_coeff_old = t300rs_calculate_coefficient(cond_old.right_coeff, old.type);
- t300rs_fill_header(&packet_mod_damper->header, effect.id, 0x0e);
- packet_mod_damper->attribute = 0x41;
- packet_mod_damper->value0 = cpu_to_le16(coeff);
+ left_coeff = t300rs_calculate_coefficient(cond.left_coeff, effect.type);
+ left_coeff_old = t300rs_calculate_coefficient(cond_old.left_coeff, old.type);
- ret = t300rs_send_int(t300rs);
- if (ret) {
- hid_err(t300rs->hdev, "failed modifying damper rc\n");
- goto error;
- }
+ t300rs_calculate_deadband(&right_deadband, &left_deadband,
+ cond.deadband, cond.center);
+ t300rs_calculate_deadband(&right_deadband_old, &left_deadband_old,
+ cond_old.deadband, cond_old.center);
- }
+ right_sat = t300rs_calculate_saturation(cond.right_saturation, effect.type);
+ right_sat_old = t300rs_calculate_saturation(cond_old.right_saturation, old.type);
- if (damper.left_coeff != damper_old.left_coeff) {
- int16_t coeff = damper.left_coeff * input_level / 100;
+ left_sat = t300rs_calculate_saturation(cond.left_saturation, effect.type);
+ left_sat_old = t300rs_calculate_saturation(cond_old.left_saturation, old.type);
- 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;
- }
-
- }
+ duration = effect.replay.length - 1;
+ duration_old = old.replay.length - 1;
- if ((damper.deadband != damper_old.deadband)
- || (damper.center != damper_old.center)) {
+ if (right_coeff != right_coeff_old
+ || left_coeff != left_coeff_old
+ || right_deadband != right_deadband_old
+ || left_deadband != left_deadband_old
+ || right_sat != right_sat_old
+ || left_sat != left_sat_old
+ || duration != duration_old
+ || effect.replay.delay != old.replay.delay) {
- uint16_t right_deadband = 0xfffe - damper.deadband - damper.center;
- uint16_t left_deadband = 0xfffe - damper.deadband + damper.center;
+ packet_mod_condition->right_coeff = cpu_to_le16(right_coeff);
+ packet_mod_condition->left_coeff = cpu_to_le16(left_coeff);
+ packet_mod_condition->right_deadband = cpu_to_le16(right_deadband);
+ packet_mod_condition->left_deadband = cpu_to_le16(left_deadband);
+ packet_mod_condition->right_saturation = cpu_to_le16(right_sat);
+ packet_mod_condition->left_saturation = cpu_to_le16(left_sat);
+ packet_mod_condition->effect_type = 0x06;
+ packet_mod_condition->update_type = 0x45;
+ packet_mod_condition->duration = cpu_to_le16(duration);
+ packet_mod_condition->delay = cpu_to_le16(effect.replay.delay);
- 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);
+ t300rs_fill_header(&packet_mod_condition->header, effect.id, 0x4c);
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;
+ if (ret)
+ hid_err(t300rs->hdev, "failed modifying condition effect\n");
}
-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;
+ struct ff_periodic_effect periodic, periodic_old;
int ret;
- int16_t magnitude, phase;
+ uint16_t phase;
+ int16_t magnitude;
- magnitude = (periodic.magnitude * fixp_sin16(effect.direction * 360 / 0x10000)) / 0x7fff;
+ t300rs_calculate_periodic_values(&effect);
+ periodic = effect.u.periodic;
+ magnitude = periodic.magnitude;
phase = periodic.phase;
+ t300rs_calculate_periodic_values(&old);
+ periodic_old = old.u.periodic;
+
if ((periodic.magnitude != periodic_old.magnitude)
|| (effect.direction != old.direction)) {
@@ -948,6 +1025,8 @@ static int t300rs_upload_constant(struct t300rs_device_entry *t300rs,
int ret;
level = (constant.level * fixp_sin16(effect.direction * 360 / 0x10000)) / 0x7fff;
+ /* the Windows driver uses the range [-16385;16381] */
+ level = level / 2;
duration = effect.replay.length - 1;
offset = effect.replay.delay;
@@ -1020,99 +1099,67 @@ static int t300rs_upload_ramp(struct t300rs_device_entry *t300rs,
return ret;
}
-static int t300rs_upload_spring(struct t300rs_device_entry *t300rs,
+static int t300rs_upload_condition(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 ff_condition_effect cond = state->effect.u.condition[0];
+ struct __packed t300rs_packet_condition {
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];
+ int16_t right_coeff;
+ int16_t left_coeff;
+ int16_t right_deadband;
+ int16_t left_deadband;
+ uint16_t right_saturation;
+ uint16_t left_saturation;
+ uint8_t hardcoded[ARRAY_SIZE(condition_values)];
+ uint16_t max_right_saturation;
+ uint16_t max_left_saturation;
+ uint8_t type;
struct t300rs_packet_timing timing;
- } *packet_spring = (struct t300rs_packet_spring *)t300rs->send_buffer;
+ } *packet_condition = (struct t300rs_packet_condition *)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;
+ uint16_t duration, right_sat, left_sat, right_coeff, left_coeff, max_sat, offset;
+ int16_t right_deadband, left_deadband;
- t300rs_fill_header(&packet_spring->header, effect.id, 0x64);
+ right_coeff = t300rs_calculate_coefficient(cond.right_coeff, effect.type);
+ left_coeff = t300rs_calculate_coefficient(cond.left_coeff, effect.type);
- 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);
+ t300rs_calculate_deadband(&right_deadband, &left_deadband,
+ cond.deadband, cond.center);
- 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;
+ right_sat = t300rs_calculate_saturation(cond.right_saturation, effect.type);
+ left_sat = t300rs_calculate_saturation(cond.left_saturation, effect.type);
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;
+ offset = effect.replay.delay;
- right_deadband = 0xfffe - spring.deadband - spring.center;
- left_deadband = 0xfffe - spring.deadband + spring.center;
+ t300rs_fill_header(&packet_condition->header, effect.id, 0x64);
- offset = effect.replay.delay;
+ packet_condition->right_coeff = cpu_to_le16(right_coeff);
+ packet_condition->left_coeff = cpu_to_le16(left_coeff);
+ packet_condition->right_deadband = cpu_to_le16(right_deadband);
+ packet_condition->left_deadband = cpu_to_le16(left_deadband);
+ packet_condition->right_saturation = cpu_to_le16(right_sat);
+ packet_condition->left_saturation = cpu_to_le16(left_sat);
- t300rs_fill_header(&packet_damper->header, effect.id, 0x64);
+ memcpy(&packet_condition->hardcoded, condition_values,
+ ARRAY_SIZE(condition_values));
- 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);
+ max_sat = t300rs_condition_max_saturation(effect.type);
+ /* it seems that the maximum values do not affect the wheel. */
+ packet_condition->max_right_saturation = cpu_to_le16(max_sat);
+ packet_condition->max_left_saturation = cpu_to_le16(max_sat);
+ packet_condition->type = t300rs_condition_effect_type(effect.type);
- memcpy(&packet_damper->damper_start, damper_values, ARRAY_SIZE(damper_values));
- t300rs_fill_timing(&packet_damper->timing, duration, offset);
+ t300rs_fill_timing(&packet_condition->timing, duration, offset);
ret = t300rs_send_int(t300rs);
if (ret)
- hid_err(t300rs->hdev, "failed uploading spring\n");
+ hid_err(t300rs->hdev, "failed uploading condition\n");
return ret;
}
@@ -1121,7 +1168,6 @@ 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;
@@ -1134,19 +1180,20 @@ static int t300rs_upload_periodic(struct t300rs_device_entry *t300rs,
struct t300rs_packet_timing timing;
} *packet_periodic = (struct t300rs_packet_periodic *)t300rs->send_buffer;
+ struct ff_periodic_effect periodic;
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;
+ uint16_t duration, period, phase, offset, periodic_offset;
+ int16_t magnitude;
+ t300rs_calculate_periodic_values(&effect);
+ periodic = effect.u.periodic;
+ duration = effect.replay.length;
+ offset = effect.replay.delay;
+ magnitude = periodic.magnitude;
+ period = periodic.period;
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);
@@ -1179,11 +1226,10 @@ int t300rs_update_effect(void *data, struct tmff2_effect_state *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);
+ return t300rs_update_condition(t300rs, state);
case FF_PERIODIC:
return t300rs_update_periodic(t300rs, state);
default:
@@ -1202,11 +1248,10 @@ int t300rs_upload_effect(void *data, struct tmff2_effect_state *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);
+ return t300rs_upload_condition(t300rs, state);
case FF_PERIODIC:
return t300rs_upload_periodic(t300rs, state);
default:
diff --git a/src/tmtsxw/hid-tmtsxw.c b/src/tmtsxw/hid-tmtsxw.c
new file mode 100644
index 0000000..b8ee1fd
--- /dev/null
+++ b/src/tmtsxw/hid-tmtsxw.c
@@ -0,0 +1,324 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#include <linux/usb.h>
+#include <linux/hid.h>
+#include "../hid-tmff2.h"
+
+#define TMTSXW_MAX_EFFECTS 16
+#define TMTSXW_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 tsxw_params =
+ PARAM_SPRING_LEVEL
+ | PARAM_DAMPER_LEVEL
+ | PARAM_FRICTION_LEVEL
+ | PARAM_RANGE
+ | PARAM_GAIN
+ ;
+
+static const signed short tsxw_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 tsxw_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 tsxw_interrupts(struct t300rs_device_entry *tsxw)
+{
+ u8 *send_buf = kmalloc(256, GFP_KERNEL);
+ struct usb_interface *usbif = to_usb_interface(tsxw->hdev->dev.parent);
+ struct usb_host_endpoint *ep;
+ int ret, trans, b_ep, i;
+
+ if (!send_buf) {
+ hid_err(tsxw->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(tsxw->usbdev,
+ usb_sndintpipe(tsxw->usbdev, b_ep),
+ send_buf, setup_arr_sizes[i],
+ &trans,
+ USB_CTRL_SET_TIMEOUT);
+
+ if (ret) {
+ hid_err(tsxw->hdev, "setup data couldn't be sent\n");
+ goto err;
+ }
+ }
+
+err:
+ kfree(send_buf);
+ return ret;
+}
+
+static int tsxw_wheel_destroy(void *data)
+{
+ struct t300rs_device_entry *t300rs = data;
+
+ if (!t300rs)
+ return -ENODEV;
+
+ kfree(t300rs->send_buffer);
+ kfree(t300rs);
+ return 0;
+}
+
+static int tsxw_set_range(void *data, uint16_t value)
+{
+ struct t300rs_device_entry *tsxw = data;
+
+ if (value < 140) {
+ hid_info(tsxw->hdev, "value %i too small, clamping to 140\n", value);
+ value = 140;
+ }
+
+ if (value > 1080) {
+ hid_info(tsxw->hdev, "value %i too large, clamping to 1080\n", value);
+ value = 1080;
+ }
+
+ return t300rs_set_range(data, value);
+}
+
+static int tsxw_send_open(struct t300rs_device_entry *tsxw)
+{
+ int r1, r2;
+ tsxw->send_buffer[0] = 0x01;
+ tsxw->send_buffer[1] = 0x04;
+ if ((r1 = t300rs_send_int(tsxw)))
+ return r1;
+
+ tsxw->send_buffer[0] = 0x01;
+ tsxw->send_buffer[1] = 0x05;
+ if ((r2 = t300rs_send_int(tsxw)))
+ return r2;
+
+ return 0;
+}
+
+static int tsxw_open(void *data, int open_mode)
+{
+ struct t300rs_device_entry *tsxw = data;
+
+ if (!tsxw)
+ return -ENODEV;
+
+ if (open_mode)
+ tsxw_send_open(tsxw);
+
+ return tsxw->open(tsxw->input_dev);
+}
+
+static int tsxw_send_close(struct t300rs_device_entry *tsxw)
+{
+ int r1, r2;
+ tsxw->send_buffer[0] = 0x01;
+ tsxw->send_buffer[1] = 0x05;
+ if ((r1 = t300rs_send_int(tsxw)))
+ return r1;
+
+ tsxw->send_buffer[0] = 0x01;
+ tsxw->send_buffer[1] = 0x00;
+ if ((r2 = t300rs_send_int(tsxw)))
+ return r2;
+
+ return 0;
+}
+
+static int tsxw_close(void *data, int open_mode)
+{
+ struct t300rs_device_entry *tsxw = data;
+
+ if (!tsxw)
+ return -ENODEV;
+
+ if (open_mode)
+ tsxw_send_close(tsxw);
+
+ tsxw->close(tsxw->input_dev);
+ return 0;
+}
+
+static int tsxw_wheel_init(struct tmff2_device_entry *tmff2, int open_mode)
+{
+ struct t300rs_device_entry *tsxw = kzalloc(sizeof(struct t300rs_device_entry),
+ GFP_KERNEL);
+ struct list_head *report_list;
+ int ret;
+
+
+ if (!tsxw) {
+ ret = -ENOMEM;
+ goto tsxw_err;
+ }
+
+ tsxw->hdev = tmff2->hdev;
+ tsxw->input_dev = tmff2->input_dev;
+ tsxw->usbdev = to_usb_device(tmff2->hdev->dev.parent->parent);
+ tsxw->buffer_length = TMTSXW_BUFFER_LENGTH;
+
+ tsxw->send_buffer = kzalloc(tsxw->buffer_length, GFP_KERNEL);
+ if (!tsxw->send_buffer) {
+ ret = -ENOMEM;
+ goto send_err;
+ }
+
+ report_list = &tsxw->hdev->report_enum[HID_OUTPUT_REPORT].report_list;
+ tsxw->report = list_entry(report_list->next, struct hid_report, list);
+ tsxw->ff_field = tsxw->report->field[0];
+
+ tsxw->open = tsxw->input_dev->open;
+ tsxw->close = tsxw->input_dev->close;
+
+ if ((ret = tsxw_interrupts(tsxw)))
+ goto interrupt_err;
+
+ /* everything went OK */
+ tmff2->data = tsxw;
+ tmff2->params = tsxw_params;
+ tmff2->max_effects = TMTSXW_MAX_EFFECTS;
+ memcpy(tmff2->supported_effects, tsxw_effects, sizeof(tsxw_effects));
+
+ if (!open_mode)
+ tsxw_send_open(tsxw);
+
+ hid_info(tsxw->hdev, "force feedback for TS-XW\n");
+ return 0;
+
+interrupt_err:
+send_err:
+ kfree(tsxw);
+tsxw_err:
+ hid_err(tmff2->hdev, "failed initializing TS-XW\n");
+ return ret;
+}
+
+static __u8 *tsxw_wheel_fixup(struct hid_device *hdev, __u8 *rdesc,
+ unsigned int *rsize)
+{
+ rdesc = tsxw_pc_rdesc_fixed;
+ *rsize = sizeof(tsxw_pc_rdesc_fixed);
+ return rdesc;
+}
+
+int tsxw_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;
+ /* TS-XW has 1080 degree range, just like T300RS 1080 */
+ tmff2->set_range = tsxw_set_range;
+ tmff2->wheel_fixup = tsxw_wheel_fixup;
+
+ tmff2->open = tsxw_open;
+ tmff2->close = tsxw_close;
+
+ tmff2->wheel_init = tsxw_wheel_init;
+ tmff2->wheel_destroy = tsxw_wheel_destroy;
+
+ return 0;
+}
diff --git a/src/tmtx/hid-tmtx.c b/src/tmtx/hid-tmtx.c
index afca799..857886c 100644
--- a/src/tmtx/hid-tmtx.c
+++ b/src/tmtx/hid-tmtx.c
@@ -151,7 +151,7 @@ err:
return ret;
}
-int tx_wheel_destroy(void *data)
+static int tx_wheel_destroy(void *data)
{
struct t300rs_device_entry *t300rs = data;
@@ -163,7 +163,7 @@ int tx_wheel_destroy(void *data)
return 0;
}
-int tx_set_range(void *data, uint16_t value)
+static int tx_set_range(void *data, uint16_t value)
{
struct t300rs_device_entry *tx = data;
@@ -239,9 +239,10 @@ static int tx_close(void *data, int open_mode)
return 0;
}
-int tx_wheel_init(struct tmff2_device_entry *tmff2, int open_mode)
+static 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 t300rs_device_entry *tx = kzalloc(sizeof(struct t300rs_device_entry),
+ GFP_KERNEL);
struct list_head *report_list;
int ret;