Compare commits
99 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
31b4aa55b8 | ||
|
|
c29bb95c40 | ||
|
|
b62cc67397 | ||
|
|
f3648cc3c5 | ||
|
|
3afa74502e | ||
|
|
99b4ebb4ce | ||
|
|
823a950f07 | ||
|
|
4326430856 | ||
|
|
3932a51126 | ||
|
|
561a50299b | ||
|
|
164ad09ad8 | ||
|
|
baf35a36f0 | ||
|
|
51982fd1e2 | ||
|
|
d7633c82fd | ||
|
|
56fdaa11f8 | ||
|
|
c6649f3349 | ||
|
|
092bdbd661 | ||
|
|
9e27df12cb | ||
|
|
b4a1a47284 | ||
|
|
985182baf2 | ||
|
|
55d456c9e1 | ||
|
|
3b0f38483f | ||
|
|
3d19599175 | ||
|
|
6d0ac791b1 | ||
|
|
bc87313267 | ||
|
|
8f42ca6774 | ||
|
|
2ee89cb9a4 | ||
|
|
f8e9545f39 | ||
|
|
241a25cee9 | ||
|
|
8220bbbfb0 | ||
|
|
96e036acda | ||
|
|
bfbdb9620e | ||
|
|
38c629829f | ||
|
|
b58aee4403 | ||
|
|
34d6d6b294 | ||
|
|
3ff83db322 | ||
|
|
d2585bcf17 | ||
|
|
c092780bf3 | ||
|
|
e1c8ae6e54 | ||
|
|
dcdd8c1e6a | ||
|
|
67f3a0be6a | ||
|
|
49b38bcefb | ||
|
|
860794b6bb | ||
|
|
2e5efb2243 | ||
|
|
6d25c361d2 | ||
|
|
5416f537bf | ||
|
|
3db0eef19a | ||
|
|
d132ca633f | ||
|
|
86b81767b6 | ||
|
|
02a07521ae | ||
|
|
30ebc05078 | ||
|
|
ab76f16c32 | ||
|
|
a0b20c7055 | ||
|
|
11ed3dd91e | ||
|
|
776dccf8f9 | ||
|
|
96088a6c56 | ||
|
|
eb3766ac30 | ||
|
|
70fa7742f7 | ||
|
|
41c5bf3fd4 | ||
|
|
45360c7c34 | ||
|
|
6e39b84de7 | ||
|
|
d11220c261 | ||
|
|
9057ffeb6f | ||
|
|
365e585336 | ||
|
|
e9a0c9ac6b | ||
|
|
d9ac2dbbb1 | ||
|
|
b3ab9e9989 | ||
|
|
94547c9bfc | ||
|
|
d449f45904 | ||
|
|
79f47bd686 | ||
|
|
03d7fe30c7 | ||
|
|
b0867e2e6c | ||
|
|
55cc69064d | ||
|
|
dea4e51be5 | ||
|
|
83d1374b63 | ||
|
|
1da641aee4 | ||
|
|
ffa4a05a1b | ||
|
|
33a99c7458 | ||
|
|
9d9e4c47bd | ||
|
|
a6ec74f90d | ||
|
|
db0eb52449 | ||
|
|
93a696d1c8 | ||
|
|
4826a9d4ef | ||
|
|
fe6100696e | ||
|
|
50a4b4fcd5 | ||
|
|
ceaf164adf | ||
|
|
e5c6f7079c | ||
|
|
2a63e469ea | ||
|
|
b5f6015304 | ||
|
|
23255595f7 | ||
|
|
377eb942bf | ||
|
|
fb5c25ccf5 | ||
|
|
1ac6938ed4 | ||
|
|
d449d1c4e0 | ||
|
|
70c5d3fa88 | ||
|
|
615c31a9f7 | ||
|
|
32a55b6f99 | ||
|
|
0864cb67ef | ||
|
|
e365eba0dd |
4
Kbuild
4
Kbuild
@@ -1,3 +1,3 @@
|
||||
obj-m := hid-moza-ff.o
|
||||
hid-moza-ff-y := hid-moza.o hid-pidff.o
|
||||
obj-m := hid-universal-pidff.o
|
||||
hid-universal-pidff-y := hid-pidff-wrapper.o hid-pidff.o
|
||||
ccflags-y := -Idrivers/hid
|
||||
|
||||
4
Makefile
4
Makefile
@@ -4,6 +4,10 @@ KDIR := /lib/modules/${KVERSION}/build
|
||||
default:
|
||||
$(MAKE) -C $(KDIR) M=$$PWD
|
||||
|
||||
debug:
|
||||
$(MAKE) -C $(KDIR) M=$$PWD EXTRA_CFLAGS="-g -DDEBUG"
|
||||
|
||||
|
||||
install: default
|
||||
$(MAKE) -C $(KDIR) M=$$PWD modules_install
|
||||
depmod -A
|
||||
|
||||
95
README.md
95
README.md
@@ -1,74 +1,96 @@
|
||||
# Force feedback support for MOZA steering wheels
|
||||
# Universal Force Feedback driver for Linux
|
||||
|
||||
Linux module driver for MOZA driving wheels.
|
||||
Linux PIDFF driver with useful patches for initialization of FFB devices. Primarily targeting Direct Drive wheelbases.
|
||||
|
||||
MOZA wheels are basically DirectInput wheels.
|
||||
In that repository - copy of pidff driver from 6.6 kernel with some changes around infinite length effects (like that https://github.com/berarma/ffbtools/issues/26)
|
||||
## What's different between this and native pidff driver?
|
||||
That driver allows most DirectDrive wheelbases to initialize and work.
|
||||
Most of the DirectDrive wheelbases are basically DirectInput wheels, but with some caveats, which Windows allows, but pidff doesn't.
|
||||
In that repository - pidff driver with some changes, which allows most of the DirectDrive wheelbases to work.
|
||||
|
||||
1. Added multiple quirks for better initialization rules for different wheelbases (MOZA, VRS, Cammus)
|
||||
2. Fixes for infinite-length effects
|
||||
3. Fixes for out-of-bounds values (no more spam in kernel logs)
|
||||
|
||||
And that's basically it
|
||||
|
||||
## What devices are supported?
|
||||
### Bases:
|
||||
1. MOZA R3
|
||||
1. MOZA R5
|
||||
1. MOZA R9
|
||||
1. MOZA R12
|
||||
1. MOZA R16
|
||||
1. MOZA R21
|
||||
|
||||
### Wheel rims:
|
||||
Not tested
|
||||
1. MOZA R3, R5, R9, R12, R16, R21
|
||||
2. Cammus C5, C12
|
||||
3. VRS DirectForce Pro
|
||||
4. ...
|
||||
|
||||
## What works?
|
||||
1. FFB (all effects from device descriptor)
|
||||
2. All inputs (wheel axis, buttons)
|
||||
3. Setup through MOZA PitHouse with [some tweaking](#how-to-set-up-a-base-parameters))
|
||||
|
||||
|
||||
## What does not work?
|
||||
1. Telemetry functions (Shift lights, etc), mostly because telemetry works only with proprietary soft, which can't get access to shared memory chunks from games.
|
||||
1. Telemetry functions. They are handeled by [Monocoque](https://github.com/Spacefreak18/monocoque)
|
||||
2. `Firmware Update` function. Use Windows PC or Windows VM at the moment.
|
||||
3. Setup through proprietary software. May require [some tweaking](#how-to-set-up-a-base-parameters)
|
||||
|
||||
## How to install this driver?
|
||||
You can install it through AUR package, through DKMS or manually.
|
||||
|
||||
On SecureBoot enabled systems you will need additional steps for load this driver into the system. See [Signing](docs/SIGNING.md#signing) section.
|
||||
|
||||
### AUR package
|
||||
There's an [AUR package](https://aur.archlinux.org/packages/universal-pidff-dkms-git) for Arch Linux maintained by [@Lawstorant](https://github.com/Lawstorant).
|
||||
|
||||
## How to use that driver?
|
||||
You can install it through DKMS or manually.
|
||||
### DKMS
|
||||
1. Install `dkms`
|
||||
2. Clone repository to `/usr/src/moza-ff`
|
||||
DKMS will install module into system, and will update it every time you update your kernel. Module will persist after reboots. It's the preferrable way to install it on the most distros.
|
||||
|
||||
1. Install `dkms` package from your distro package manager
|
||||
2. Clone repository to `/usr/src/universal-pidff`
|
||||
3. Install the module:
|
||||
`sudo dkms install /usr/src/moza-ff`
|
||||
`sudo dkms install /usr/src/universal-pidff`
|
||||
4. Update initramfs:
|
||||
`sudo update-initramfs -u`
|
||||
5. Reboot
|
||||
|
||||
To remove module:
|
||||
`sudo dkms remove moza-ff/<version> --all`
|
||||
### Manually
|
||||
`sudo dkms remove universal-pidff/<version> --all`
|
||||
|
||||
1. Install `linux-headers-$(uname -r)`
|
||||
2. Clone repository
|
||||
3. `make`
|
||||
4. `sudo insmod hid-moza-ff.ko`
|
||||
### Manually
|
||||
Best for debugging purposes, where you need frequently change codebase/branches
|
||||
1. Install `linux-headers-$(uname -r)` and `build-essential` packages from your distro package manager
|
||||
2. Clone repository anywhere you want and `cd` into that directory
|
||||
3. `make`. Alternatively, you can enable debug logs from the driver with `make debug`
|
||||
4. Load module into system with `sudo insmod hid-universal-pidff.ko`
|
||||
|
||||
To unload module:
|
||||
`sudo rmmod hid_moza_ff`
|
||||
`sudo rmmod hid_universal_pidff`
|
||||
|
||||
## How to set up a base parameters?
|
||||
### Testing
|
||||
To test the supported effects, use ffbplay from [ffbtools](https://github.com/berarma/ffbtools) and play the included [effect-test.ffb](./effect-test.ffb) file
|
||||
|
||||
You can do it through MOZA PitHouse with Wine. You need to tweak Wine prefix for them.
|
||||
## How to set up a base parameters (max rotation degree, max power, filters, etc)?
|
||||
### MOZA
|
||||
**[Boxflat](https://github.com/Lawstorant/boxflat)** is a Linux Pit House alternative made by [@Lawstorant](https://github.com/Lawstorant)
|
||||
|
||||
**[Android App](https://play.google.com/store/apps/details?id=com.gudsen.mozapithouse)**
|
||||
|
||||
### Cammus
|
||||
**[Android App](https://play.google.com/store/apps/details?id=com.cammus.simulator)**
|
||||
|
||||
### VRS DirectForce Pro
|
||||
You can do it through VRS with Wine. You need to tweak Wine prefix for them.
|
||||
|
||||
That soft uses hidraw to set up a base. You need to create `udev` rule for allow access to hidraw device:
|
||||
```
|
||||
echo 'KERNEL=="hidraw*", ATTRS{idVendor}=="346e", MODE="0660", TAG+="uaccess"' | sudo tee /etc/udev/rules.d/11-moza.rules
|
||||
echo 'KERNEL=="hidraw*", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="a355", MODE="0666", TAG+="uaccess"' | sudo tee /etc/udev/rules.d/11-vrs.rules
|
||||
|
||||
udevadm control --reload-rules && udevadm trigger
|
||||
```
|
||||
|
||||
Then you need to force MOZA soft to use hidraw, not SDL, to find devices:
|
||||
Then you need to force VRS soft to use hidraw, not SDL, to find devices:
|
||||
1. Create new Wine prefix for them:
|
||||
|
||||
`mkdir ~/moza-wine`
|
||||
`mkdir ~/vrs-wine`
|
||||
2. Launch regedit in prefix:
|
||||
|
||||
`WINEPREFIX=$HOME/moza-wine wine regedit`
|
||||
`WINEPREFIX=$HOME/vrs-wine wine regedit`
|
||||
3. Create two keys in
|
||||
`HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\winebus`:
|
||||
|
||||
@@ -76,10 +98,13 @@ Then you need to force MOZA soft to use hidraw, not SDL, to find devices:
|
||||
* `Enable SDL` (DWORD) - set to `0`; (yes, variable name contains space)
|
||||
4. Now you can launch soft through that WINEPREFIX:
|
||||
|
||||
`WINEPREFIX=$HOME/moza-wine wine MOZA\ Pit\ House.exe` - launch your PitHouse from installation directory.
|
||||
`WINEPREFIX=$HOME/vrs-wine wine VRS.exe` - launch your soft from installation directory.
|
||||
|
||||
|
||||
## Known issues with the driver
|
||||
1. Firmware update does not work. Please use Windows machine or Windows VM for any firmware updates
|
||||
|
||||
### MOZA
|
||||
- Current limit of usable buttons is 160 (up from the Linux default of 80). Create an issue if you want this increased further.
|
||||
|
||||
## Known issues with the firmware
|
||||
You tell me please
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
PACKAGE_NAME="moza-ff"
|
||||
PACKAGE_VERSION="0.0.1"
|
||||
PACKAGE_NAME="universal-pidff"
|
||||
PACKAGE_VERSION="0.0.8"
|
||||
MAKE[0]="make KVERSION=$kernelver"
|
||||
CLEAN="make clean"
|
||||
BUILT_MODULE_NAME[0]="hid-moza-ff"
|
||||
DEST_MODULE_NAME[0]="hid-moza-ff"
|
||||
BUILT_MODULE_NAME[0]="hid-universal-pidff"
|
||||
DEST_MODULE_NAME[0]="hid-universal-pidff"
|
||||
DEST_MODULE_LOCATION[0]="/kernel/drivers/hid"
|
||||
AUTOINSTALL="yes"
|
||||
59
docs/SIGNING.md
Normal file
59
docs/SIGNING.md
Normal file
@@ -0,0 +1,59 @@
|
||||
|
||||
# Signing
|
||||
|
||||
## Signing module for SecureBoot
|
||||
Latest kernels forbid loading unsigned custom kernel modules into the system with SecureBoot enabled.
|
||||
|
||||
For SecureBoot enabled system you have a choice:
|
||||
1. Disable SecureBoot in your UEFI/BIOS
|
||||
2. Use generated Machine Owner Key from DKMS (supports automatic signing)
|
||||
3. Create Machine Owner Key and load it into your UEFI/BIOS, and sign kernel module with it.
|
||||
|
||||
### Using DKMS MOK key
|
||||
MOK private key and certificates are generated the first time DKMS is run. You just need to import it to your system.
|
||||
|
||||
The location as well can be changed by setting the appropriate variables in /etc/dkms/framework.conf. For example, to allow usage of the system default Ubuntu update-secureboot-policy set the configuration file as follows:
|
||||
```
|
||||
mok_signing_key="/var/lib/shim-signed/mok/MOK.priv"
|
||||
mok_certificate="/var/lib/shim-signed/mok/MOK.der"
|
||||
```
|
||||
|
||||
```
|
||||
# Find where keys are on your distro
|
||||
ls -al /var/lib/dkms/mok*
|
||||
# OR (on Ubuntu)
|
||||
ls -al /var/lib/shim-signed/mok/MOK*
|
||||
|
||||
# Enroll keys into system
|
||||
sudo mokutil --import /var/lib/dkms/mok.pub
|
||||
# OR (on Ubuntu)
|
||||
sudo mokutil --import /var/lib/shim-signed/mok/MOK.der
|
||||
```
|
||||
You need to reboot your PC after that, you will be greeted with blue screen dialog.
|
||||
Choose "Enroll MOK", then "Continue" and "Yes". After that choose "Reboot system".
|
||||
|
||||
Now DKMS should sign updated modules automatically as they updated.
|
||||
|
||||
[Reference](https://github.com/dell/dkms/blob/master/README.md#module-signing)
|
||||
|
||||
### Manually create MOK key and manually sign kernel module
|
||||
```
|
||||
# This creates Machine Owner Key
|
||||
openssl req -new -x509 -newkey rsa:2048 -keyout mok.key -outform DER -out mok.pub -nodes -days 36500 -subj "/CN=$hostname kernel module signing key/"
|
||||
|
||||
# This loads it into UEFI
|
||||
sudo mokutil --import mok.pub
|
||||
```
|
||||
|
||||
You need to reboot your PC after that, you will be greeted with blue screen dialog
|
||||
Choose "Enroll MOK", enter your MOK password if exists, then "Continue", "Yes", and then reboot your system.
|
||||
|
||||
After that you can manually sign your built kernel module like so (feel free to adjust paths to keys/certificate/modules):
|
||||
```
|
||||
sudo /usr/src/linux-headers-$(uname -r)/scripts/sign-file sha256 mok.key mok.pub hid-universal-pidff.ko
|
||||
```
|
||||
|
||||
Then you should be able to load driver like so:
|
||||
```
|
||||
sudo insmod hid-universal-pidff.ko
|
||||
```
|
||||
91
effect-test.ffb
Normal file
91
effect-test.ffb
Normal file
@@ -0,0 +1,91 @@
|
||||
00000000 # Constant force left
|
||||
00000000 > UPLOAD id:-1 dir:16384 type:CONSTANT level:5000
|
||||
00000000 < 0 id:0
|
||||
00000000 > PLAY 0 1
|
||||
|
||||
03000000 # Constant force right
|
||||
03000000 > UPLOAD id:0 dir:16384 type:CONSTANT level:-5000
|
||||
|
||||
06000000 # Spring
|
||||
06000000 > REMOVE 0
|
||||
06000000 > UPLOAD id:-1 dir:16384 type:SPRING left_coeff:2000 right_coeff:2000
|
||||
06000000 < 0 id:0
|
||||
06000000 > PLAY 0 1
|
||||
|
||||
07000000 # Move the wheel yourself to test these next 3 effects
|
||||
09000000 > STOP 0
|
||||
|
||||
09000000 # Damper
|
||||
09000000 > REMOVE 0
|
||||
09000000 > UPLOAD id:-1 dir:16384 type:DAMPER left_coeff:32767 right_coeff:32767
|
||||
09000000 < 0 id:0
|
||||
09000000 > PLAY 0 1
|
||||
|
||||
12000000 # Friction
|
||||
12000000 > STOP 0
|
||||
12000000 > REMOVE 0
|
||||
12000000 > UPLOAD id:-1 dir:16384 type:FRICTION left_coeff:32767 right_coeff:32767
|
||||
12000000 < 0 id:0
|
||||
12000000 > PLAY 0 1
|
||||
|
||||
15000000 # Inertia
|
||||
15000000 > STOP 0
|
||||
15000000 > REMOVE 0
|
||||
15000000 > UPLOAD id:-1 dir:16384 type:INERTIA left_coeff:32767 right_coeff:32767
|
||||
15000000 < 0 id:0
|
||||
15000000 > PLAY 0 1
|
||||
|
||||
18000000 # Periodic sine
|
||||
18000000 > REMOVE 0
|
||||
18000000 > UPLOAD id:-1 dir:16384 type:PERIODIC waveform:SINE period:100 magnitude:9000
|
||||
18000000 < 0 id:0
|
||||
18000000 > PLAY 0 1
|
||||
|
||||
21000000 # Periodic square
|
||||
21000000 > REMOVE 0
|
||||
21000000 > UPLOAD id:-1 dir:16384 type:PERIODIC waveform:SQUARE period:100 magnitude:4000
|
||||
21000000 < 0 id:0
|
||||
21000000 > PLAY 0 1
|
||||
|
||||
24000000 # Periodic triangle
|
||||
24000000 > REMOVE 0
|
||||
24000000 > UPLOAD id:-1 dir:16384 type:PERIODIC waveform:TRIANGLE period:100 magnitude:9000
|
||||
24000000 < 0 id:0
|
||||
24000000 > PLAY 0 1
|
||||
|
||||
27000000 # Periodic saw up
|
||||
27000000 > REMOVE 0
|
||||
27000000 > UPLOAD id:-1 dir:16384 type:PERIODIC waveform:SAW_UP period:100 magnitude:4000
|
||||
27000000 < 0 id:0
|
||||
27000000 > PLAY 0 1
|
||||
|
||||
30000000 # Periodic saw down
|
||||
30000000 > REMOVE 0
|
||||
30000000 > UPLOAD id:-1 dir:16384 type:PERIODIC waveform:SAW_DOWN period:100 magnitude:4000
|
||||
30000000 < 0 id:0
|
||||
30000000 > PLAY 0 1
|
||||
|
||||
33000000 # Sine constant force emulation left
|
||||
33000000 > REMOVE 0
|
||||
33000000 > UPLOAD id:-1 dir:16384 type:PERIODIC waveform:SINE period:1000 magnitude:0 offset:4000
|
||||
33000000 < 0 id:0
|
||||
33000000 > PLAY 0 1
|
||||
|
||||
36000000 # Sine constant force emulation right
|
||||
36000000 > UPLOAD id:0 dir:16384 type:PERIODIC waveform:SINE period:1000 magnitude:0 offset:-4000
|
||||
39000000 > STOP 0
|
||||
39000000 > REMOVE 0
|
||||
|
||||
40000000 # Back to center
|
||||
40000000 > UPLOAD id:-1 dir:16384 type:FRICTION left_coeff:16000 right_coeff:16000
|
||||
40000000 > UPLOAD id:-1 dir:16384 type:SPRING left_coeff:3000 right_coeff:3000
|
||||
40000000 < 0 id:0
|
||||
40000000 < 1 id:1
|
||||
40100000 > PLAY 0 1
|
||||
40100000 > PLAY 1 1
|
||||
42000000 > STOP 1
|
||||
42000000 > STOP 0
|
||||
42000000 > REMOVE 1
|
||||
42000000 > REMOVE 0
|
||||
|
||||
42000000 # Test end
|
||||
29
hid-ids.h
Normal file
29
hid-ids.h
Normal file
@@ -0,0 +1,29 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
#ifndef __HID_IDS_H
|
||||
#define __HID_IDS_H
|
||||
|
||||
// Moza Racing
|
||||
#define USB_VENDOR_ID_MOZA 0x346e
|
||||
#define USB_DEVICE_ID_MOZA_R3 0x0005
|
||||
#define USB_DEVICE_ID_MOZA_R5 0x0004
|
||||
#define USB_DEVICE_ID_MOZA_R9 0x0002
|
||||
#define USB_DEVICE_ID_MOZA_R12 0x0006
|
||||
#define USB_DEVICE_ID_MOZA_R16_R21 0x0000
|
||||
|
||||
// VRS DrivingForce Pro
|
||||
#define USB_VENDOR_ID_VRS 0x0483
|
||||
#define USB_DEVICE_ID_VRS_DFP 0xa355
|
||||
|
||||
// Moza Racing FH5 mode
|
||||
#define USB_DEVICE_ID_MOZA_R3_FH5 0x0015
|
||||
#define USB_DEVICE_ID_MOZA_R5_FH5 0x0014
|
||||
#define USB_DEVICE_ID_MOZA_R9_FH5 0x0012
|
||||
#define USB_DEVICE_ID_MOZA_R12_FH5 0x0016
|
||||
#define USB_DEVICE_ID_MOZA_R16_R21_FH5 0x0010
|
||||
|
||||
// Cammus
|
||||
#define USB_VENDOR_ID_CAMMUS 0x3416
|
||||
#define USB_DEVICE_ID_CAMMUS_C5 0x0301
|
||||
#define USB_DEVICE_ID_CAMMUS_C12 0x0302
|
||||
|
||||
#endif
|
||||
71
hid-moza.c
71
hid-moza.c
@@ -1,71 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* HID driver for Moza Steering Wheels
|
||||
*
|
||||
* Copyright (c) 2024 Makarenko Oleg
|
||||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/hid.h>
|
||||
#include <linux/module.h>
|
||||
#include "hid-moza.h"
|
||||
|
||||
static const struct hid_device_id moza_devices[] = {
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R3) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R5) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R9) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R12) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R16) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R21) },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(hid, moza_devices);
|
||||
|
||||
static int moza_probe(struct hid_device *hdev,
|
||||
const struct hid_device_id *id)
|
||||
{
|
||||
int ret;
|
||||
ret = hid_parse(hdev);
|
||||
if (ret) {
|
||||
hid_err(hdev, "parse failed\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
|
||||
if (ret) {
|
||||
hid_err(hdev, "hw start failed\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
ret = hid_pidff_init_moza(hdev);
|
||||
if (ret) {
|
||||
hid_warn(hdev, "Force feedback not supported\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int moza_input_configured(struct hid_device *hdev,
|
||||
struct hid_input *hidinput)
|
||||
{
|
||||
struct input_dev *input = hidinput->input;
|
||||
input_set_abs_params(input, ABS_X,
|
||||
input->absinfo[ABS_X].minimum, input->absinfo[ABS_X].maximum, 0, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct hid_driver moza_ff = {
|
||||
.name = "moza-ff",
|
||||
.id_table = moza_devices,
|
||||
.probe = moza_probe,
|
||||
.input_configured = moza_input_configured,
|
||||
};
|
||||
module_hid_driver(moza_ff);
|
||||
|
||||
MODULE_AUTHOR("Oleg Makarenko <oleg@makarenk.ooo>");
|
||||
MODULE_DESCRIPTION("MOZA HID FF Driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
15
hid-moza.h
15
hid-moza.h
@@ -1,15 +0,0 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
#ifndef __HID_MOZA_H
|
||||
#define __HID_MOZA_H
|
||||
|
||||
#define USB_VENDOR_ID_MOZA 0x346e
|
||||
#define USB_DEVICE_ID_MOZA_R3 0x0005
|
||||
#define USB_DEVICE_ID_MOZA_R5 0x0004
|
||||
#define USB_DEVICE_ID_MOZA_R9 0x0002
|
||||
#define USB_DEVICE_ID_MOZA_R12 0x0006
|
||||
#define USB_DEVICE_ID_MOZA_R16 0x0000
|
||||
#define USB_DEVICE_ID_MOZA_R21 0x0000
|
||||
|
||||
int hid_pidff_init_moza(struct hid_device *hdev);
|
||||
|
||||
#endif
|
||||
171
hid-pidff-wrapper.c
Normal file
171
hid-pidff-wrapper.c
Normal file
@@ -0,0 +1,171 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* HID PIDFF wrapper
|
||||
* First of all targeting steering wheels and wheelbases
|
||||
*
|
||||
* Copyright (c) 2024 Makarenko Oleg
|
||||
* Copyright (c) 2024 Tomasz Pakuła
|
||||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/hid.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/input-event-codes.h>
|
||||
#include "hid-ids.h"
|
||||
#include "hid-pidff.h"
|
||||
|
||||
#define JOY_RANGE (BTN_DEAD - BTN_JOYSTICK + 1)
|
||||
|
||||
static const struct hid_device_id pidff_wheel_devices[] = {
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R3),
|
||||
.driver_data = PIDFF_QUIRK_FIX_WHEEL_DIRECTION },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R5),
|
||||
.driver_data = PIDFF_QUIRK_FIX_WHEEL_DIRECTION },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R9),
|
||||
.driver_data = PIDFF_QUIRK_FIX_WHEEL_DIRECTION },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R12),
|
||||
.driver_data = PIDFF_QUIRK_FIX_WHEEL_DIRECTION },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R16_R21),
|
||||
.driver_data = PIDFF_QUIRK_FIX_WHEEL_DIRECTION },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R3_FH5),
|
||||
.driver_data = PIDFF_QUIRK_FIX_WHEEL_DIRECTION },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R5_FH5),
|
||||
.driver_data = PIDFF_QUIRK_FIX_WHEEL_DIRECTION },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R9_FH5),
|
||||
.driver_data = PIDFF_QUIRK_FIX_WHEEL_DIRECTION },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R12_FH5),
|
||||
.driver_data = PIDFF_QUIRK_FIX_WHEEL_DIRECTION },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R16_R21_FH5),
|
||||
.driver_data = PIDFF_QUIRK_FIX_WHEEL_DIRECTION },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_CAMMUS, USB_DEVICE_ID_CAMMUS_C5),
|
||||
.driver_data = PIDFF_QUIRK_NO_DELAY_EFFECT },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_CAMMUS, USB_DEVICE_ID_CAMMUS_C12),
|
||||
.driver_data = PIDFF_QUIRK_NO_DELAY_EFFECT },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_VRS, USB_DEVICE_ID_VRS_DFP),
|
||||
.driver_data = PIDFF_QUIRK_NO_DELAY_EFFECT
|
||||
| PIDFF_QUIRK_NO_STRICT_PID_CONTROL
|
||||
| PIDFF_QUIRK_NO_PID_PARAM_BLOCK_OFFSET },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(hid, pidff_wheel_devices);
|
||||
|
||||
|
||||
static u8 *moza_report_fixup(struct hid_device *hdev, __u8 *rdesc,
|
||||
unsigned int *rsize)
|
||||
{
|
||||
// Fix data type on PID Device Control
|
||||
if (rdesc[1002] == 0x91 && rdesc[1003] == 0x02) {
|
||||
rdesc[1003] = 0x00; // Fix header, it needs to be Array.
|
||||
}
|
||||
return rdesc;
|
||||
}
|
||||
|
||||
|
||||
static u8 *universal_pidff_report_fixup(struct hid_device *hdev, __u8 *rdesc,
|
||||
unsigned int *rsize)
|
||||
{
|
||||
if (hdev->vendor == USB_VENDOR_ID_MOZA) {
|
||||
return moza_report_fixup(hdev, rdesc, rsize);
|
||||
}
|
||||
return rdesc;
|
||||
}
|
||||
|
||||
/*
|
||||
* Map buttons manually to extend the default joystick buttn limit
|
||||
*/
|
||||
static int universal_pidff_input_mapping(struct hid_device *hdev, struct hid_input *hi,
|
||||
struct hid_field *field, struct hid_usage *usage,
|
||||
unsigned long **bit, int *max)
|
||||
{
|
||||
// Let the default behavior handle mapping if usage is not a button
|
||||
if ((usage->hid & HID_USAGE_PAGE) != HID_UP_BUTTON)
|
||||
return 0;
|
||||
|
||||
int button = ((usage->hid - 1) & HID_USAGE);
|
||||
int code = button + BTN_JOYSTICK;
|
||||
|
||||
// Detect the end of JOYSTICK buttons range
|
||||
// KEY_NEXT_FAVORITE = 0x270
|
||||
if (code > BTN_DEAD)
|
||||
code = button + KEY_NEXT_FAVORITE - JOY_RANGE;
|
||||
|
||||
// Map overflowing buttons to KEY_RESERVED for the upcoming new input event
|
||||
// It will handle button presses differently and won't depend on defined
|
||||
// ranges. KEY_RESERVED usage is needed for the button to not be ignored.
|
||||
if (code > KEY_MAX)
|
||||
code = KEY_RESERVED;
|
||||
|
||||
hid_map_usage(hi, usage, bit, max, EV_KEY, code);
|
||||
hid_dbg(hdev, "Button %d: usage %d", button, code);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Check if the device is PID and initialize it
|
||||
* Add quirks after initialisation
|
||||
*/
|
||||
static int universal_pidff_probe(struct hid_device *hdev,
|
||||
const struct hid_device_id *id)
|
||||
{
|
||||
int ret;
|
||||
ret = hid_parse(hdev);
|
||||
if (ret) {
|
||||
hid_err(hdev, "parse failed\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
|
||||
if (ret) {
|
||||
hid_err(hdev, "hw start failed\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
ret = hid_pidff_init_quirks(hdev, id);
|
||||
if (ret) {
|
||||
hid_warn(hdev, "Force feedback is not supported\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int universal_pidff_input_configured(struct hid_device *hdev,
|
||||
struct hid_input *hidinput)
|
||||
{
|
||||
// Remove fuzz and deadzone from the wheel axis
|
||||
struct input_dev *input = hidinput->input;
|
||||
input_set_abs_params(input, ABS_X,
|
||||
input->absinfo[ABS_X].minimum, input->absinfo[ABS_X].maximum, 0, 0);
|
||||
|
||||
// Decrease fuzz and deadzone on additional axes
|
||||
// Default Linux values are 255 for fuzz and 4096 for flat (deadzone)
|
||||
int axis;
|
||||
for (axis = ABS_Y; axis <= ABS_BRAKE; axis++) {
|
||||
if (!test_bit(axis, input->absbit))
|
||||
continue;
|
||||
|
||||
input_set_abs_params(input, axis,
|
||||
input->absinfo[axis].minimum,
|
||||
input->absinfo[axis].maximum, 8, 0);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct hid_driver universal_pidff = {
|
||||
.name = "hid-universal-pidff",
|
||||
.id_table = pidff_wheel_devices,
|
||||
.input_mapping = universal_pidff_input_mapping,
|
||||
.probe = universal_pidff_probe,
|
||||
.input_configured = universal_pidff_input_configured,
|
||||
.report_fixup = universal_pidff_report_fixup
|
||||
};
|
||||
module_hid_driver(universal_pidff);
|
||||
|
||||
MODULE_AUTHOR("Oleg Makarenko <oleg@makarenk.ooo>");
|
||||
MODULE_AUTHOR("Tomasz Pakuła <tomasz.pakula.oficjalny@gmail.com>");
|
||||
MODULE_DESCRIPTION("Universal HID PIDFF Driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
165
hid-pidff.c
165
hid-pidff.c
@@ -3,6 +3,8 @@
|
||||
* Force feedback driver for USB HID PID compliant devices
|
||||
*
|
||||
* Copyright (c) 2005, 2006 Anssi Hannula <anssi.hannula@gmail.com>
|
||||
* Copyright (c) 2024 Makarenko Oleg <oleg@makarenk.ooo>
|
||||
* Copyright (c) 2024 Tomasz Pakuła <tomasz@pakula.org>
|
||||
*/
|
||||
|
||||
/*
|
||||
@@ -19,9 +21,18 @@
|
||||
#include <linux/hid.h>
|
||||
|
||||
//#include "usbhid.h"
|
||||
#include "hid-pidff.h"
|
||||
|
||||
#define PID_EFFECTS_MAX 64
|
||||
|
||||
/*
|
||||
* This is 16384 or 90 degrees in polar coordinates (up)
|
||||
* Racing games exclusively use polar coordinates but sometimes
|
||||
* SDL/Proton mess with this value as they try to do
|
||||
* some conversions to fix FFB for flight sticks
|
||||
*/
|
||||
#define PIDFF_FIXED_DIRECTION 0x4000
|
||||
|
||||
/* Report usage table used to put reports into an array */
|
||||
|
||||
#define PID_SET_EFFECT 0
|
||||
@@ -62,6 +73,10 @@ static const u8 pidff_set_effect[] = {
|
||||
0x22, 0x50, 0x52, 0x53, 0x54, 0x56, 0xa7
|
||||
};
|
||||
|
||||
static const u8 pidff_set_effect_without_delay[] = {
|
||||
0x22, 0x50, 0x52, 0x53, 0x54, 0x56
|
||||
};
|
||||
|
||||
#define PID_ATTACK_LEVEL 1
|
||||
#define PID_ATTACK_TIME 2
|
||||
#define PID_FADE_LEVEL 3
|
||||
@@ -79,6 +94,11 @@ static const u8 pidff_set_condition[] = {
|
||||
0x22, 0x23, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65
|
||||
};
|
||||
|
||||
static const u8 pidff_set_condition_without_pbo[] = {
|
||||
0x22, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65
|
||||
};
|
||||
|
||||
|
||||
#define PID_MAGNITUDE 1
|
||||
#define PID_OFFSET 2
|
||||
#define PID_PHASE 3
|
||||
@@ -184,6 +204,7 @@ struct pidff_device {
|
||||
int operation_id[sizeof(pidff_effect_operation_status)];
|
||||
|
||||
int pid_id[PID_EFFECTS_MAX];
|
||||
unsigned quirks;
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -205,6 +226,36 @@ static int pidff_rescale_signed(int i, struct hid_field *field)
|
||||
field->logical_minimum / -0x8000;
|
||||
}
|
||||
|
||||
/*
|
||||
* Clamp minimum value for the given field
|
||||
*/
|
||||
static int pidff_clamp_min(int i, struct hid_field *field)
|
||||
{
|
||||
int ret = i < field->logical_minimum ? field->logical_minimum : i;
|
||||
pr_debug("clamped min value from %d to %d\n", i, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Clamp maximum value for the given field
|
||||
*/
|
||||
static int pidff_clamp_max(int i, struct hid_field *field)
|
||||
{
|
||||
int ret = i > field->logical_maximum ? field->logical_maximum : i;
|
||||
pr_debug("clamped max value from %d to %d\n", i, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Clamp value for the given field
|
||||
*/
|
||||
static int pidff_clamp(int i, struct hid_field *field)
|
||||
{
|
||||
i = pidff_clamp_min(i, field);
|
||||
i = pidff_clamp_max(i, field);
|
||||
return i;
|
||||
}
|
||||
|
||||
static void pidff_set(struct pidff_usage *usage, u16 value)
|
||||
{
|
||||
usage->value[0] = pidff_rescale(value, 0xffff, usage->field);
|
||||
@@ -247,7 +298,11 @@ static void pidff_set_envelope_report(struct pidff_device *pidff,
|
||||
pidff->set_envelope[PID_ATTACK_TIME].value[0] = envelope->attack_length;
|
||||
pidff->set_envelope[PID_FADE_TIME].value[0] = envelope->fade_length;
|
||||
|
||||
hid_dbg(pidff->hid, "attack %u => %d\n",
|
||||
hid_dbg(pidff->hid, "attack level %u => %d\n",
|
||||
envelope->attack_level,
|
||||
pidff->set_envelope[PID_ATTACK_LEVEL].value[0]);
|
||||
|
||||
hid_dbg(pidff->hid, "fade level %u => %d\n",
|
||||
envelope->attack_level,
|
||||
pidff->set_envelope[PID_ATTACK_LEVEL].value[0]);
|
||||
|
||||
@@ -261,6 +316,14 @@ static void pidff_set_envelope_report(struct pidff_device *pidff,
|
||||
static int pidff_needs_set_envelope(struct ff_envelope *envelope,
|
||||
struct ff_envelope *old)
|
||||
{
|
||||
if (!old) {
|
||||
return envelope->attack_level != 0 ||
|
||||
envelope->fade_level != 0 ||
|
||||
envelope->attack_length != 0 ||
|
||||
envelope->fade_length != 0;
|
||||
|
||||
}
|
||||
|
||||
return envelope->attack_level != old->attack_level ||
|
||||
envelope->fade_level != old->fade_level ||
|
||||
envelope->attack_length != old->attack_length ||
|
||||
@@ -297,22 +360,31 @@ static int pidff_needs_set_constant(struct ff_effect *effect,
|
||||
static void pidff_set_effect_report(struct pidff_device *pidff,
|
||||
struct ff_effect *effect)
|
||||
{
|
||||
/* check for device quirks */
|
||||
unsigned short direction = effect->direction;
|
||||
|
||||
if (pidff->quirks & PIDFF_QUIRK_FIX_WHEEL_DIRECTION)
|
||||
direction = PIDFF_FIXED_DIRECTION;
|
||||
|
||||
pidff->set_effect[PID_EFFECT_BLOCK_INDEX].value[0] =
|
||||
pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0];
|
||||
pidff->set_effect_type->value[0] =
|
||||
pidff->create_new_effect_type->value[0];
|
||||
pidff->set_effect[PID_DURATION].value[0] =
|
||||
effect->replay.length == 0 ? 0xffff : effect->replay.length;
|
||||
pidff->set_effect[PID_TRIGGER_BUTTON].value[0] = effect->trigger.button;
|
||||
pidff->set_effect[PID_TRIGGER_BUTTON].value[0] =
|
||||
effect->trigger.button;
|
||||
pidff->set_effect[PID_TRIGGER_REPEAT_INT].value[0] =
|
||||
effect->trigger.interval;
|
||||
pidff->set_effect[PID_GAIN].value[0] =
|
||||
pidff->set_effect[PID_GAIN].field->logical_maximum;
|
||||
pidff->set_effect[PID_DIRECTION_ENABLE].value[0] = 1;
|
||||
pidff->effect_direction->value[0] =
|
||||
pidff_rescale(effect->direction, 0xffff,
|
||||
pidff->effect_direction);
|
||||
pidff_rescale(direction, 0xffff, pidff->effect_direction);
|
||||
|
||||
if (!(pidff->quirks & PIDFF_QUIRK_NO_DELAY_EFFECT)) {
|
||||
pidff->set_effect[PID_START_DELAY].value[0] = effect->replay.delay;
|
||||
}
|
||||
|
||||
hid_hw_request(pidff->hid, pidff->reports[PID_SET_EFFECT],
|
||||
HID_REQ_SET_REPORT);
|
||||
@@ -344,7 +416,12 @@ static void pidff_set_periodic_report(struct pidff_device *pidff,
|
||||
pidff_set_signed(&pidff->set_periodic[PID_OFFSET],
|
||||
effect->u.periodic.offset);
|
||||
pidff_set(&pidff->set_periodic[PID_PHASE], effect->u.periodic.phase);
|
||||
pidff->set_periodic[PID_PERIOD].value[0] = effect->u.periodic.period;
|
||||
// Actually we just can use clamp macro
|
||||
// from include/linux/kernel.h#L59
|
||||
// But for the debug purposes we're leaving it as is
|
||||
pidff->set_periodic[PID_PERIOD].value[0] =
|
||||
pidff_clamp(effect->u.periodic.period,
|
||||
pidff->set_periodic[PID_PERIOD].field);
|
||||
|
||||
hid_hw_request(pidff->hid, pidff->reports[PID_SET_PERIODIC],
|
||||
HID_REQ_SET_REPORT);
|
||||
@@ -370,11 +447,18 @@ static void pidff_set_condition_report(struct pidff_device *pidff,
|
||||
struct ff_effect *effect)
|
||||
{
|
||||
int i;
|
||||
// Later we should take axis number out of the device.
|
||||
// Our driver must work with MOZA AB9 FFB Base
|
||||
int max_axis = 2;
|
||||
|
||||
pidff->set_condition[PID_EFFECT_BLOCK_INDEX].value[0] =
|
||||
pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0];
|
||||
|
||||
for (i = 0; i < 2; i++) {
|
||||
if (pidff->quirks & PIDFF_QUIRK_NO_PID_PARAM_BLOCK_OFFSET)
|
||||
max_axis = 1;
|
||||
|
||||
for (i = 0; i < max_axis; i++) {
|
||||
if (! (pidff->quirks & PIDFF_QUIRK_NO_PID_PARAM_BLOCK_OFFSET) )
|
||||
pidff->set_condition[PID_PARAM_BLOCK_OFFSET].value[0] = i;
|
||||
pidff_set_signed(&pidff->set_condition[PID_CP_OFFSET],
|
||||
effect->u.condition[i].center);
|
||||
@@ -505,6 +589,7 @@ static void pidff_playback_pid(struct pidff_device *pidff, int pid_id, int n)
|
||||
pidff->effect_operation[PID_LOOP_COUNT].value[0] = n;
|
||||
}
|
||||
|
||||
hid_dbg(pidff->hid, "Playing effect %d %d times\n", pid_id, n);
|
||||
hid_hw_request(pidff->hid, pidff->reports[PID_EFFECT_OPERATION],
|
||||
HID_REQ_SET_REPORT);
|
||||
}
|
||||
@@ -565,7 +650,7 @@ static int pidff_upload_effect(struct input_dev *dev, struct ff_effect *effect,
|
||||
pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0] =
|
||||
pidff->pid_id[effect->id];
|
||||
}
|
||||
|
||||
hid_dbg(pidff->hid, "Upload effect 0x%02x,", effect->type);
|
||||
switch (effect->type) {
|
||||
case FF_CONSTANT:
|
||||
if (!old) {
|
||||
@@ -578,9 +663,8 @@ static int pidff_upload_effect(struct input_dev *dev, struct ff_effect *effect,
|
||||
pidff_set_effect_report(pidff, effect);
|
||||
if (!old || pidff_needs_set_constant(effect, old))
|
||||
pidff_set_constant_force_report(pidff, effect);
|
||||
if (!old ||
|
||||
pidff_needs_set_envelope(&effect->u.constant.envelope,
|
||||
&old->u.constant.envelope))
|
||||
if (pidff_needs_set_envelope(&effect->u.constant.envelope,
|
||||
old ? &old->u.periodic.envelope : NULL))
|
||||
pidff_set_envelope_report(pidff,
|
||||
&effect->u.constant.envelope);
|
||||
break;
|
||||
@@ -617,9 +701,9 @@ static int pidff_upload_effect(struct input_dev *dev, struct ff_effect *effect,
|
||||
pidff_set_effect_report(pidff, effect);
|
||||
if (!old || pidff_needs_set_periodic(effect, old))
|
||||
pidff_set_periodic_report(pidff, effect);
|
||||
if (!old ||
|
||||
pidff_needs_set_envelope(&effect->u.periodic.envelope,
|
||||
&old->u.periodic.envelope))
|
||||
|
||||
if (pidff_needs_set_envelope(&effect->u.periodic.envelope,
|
||||
old ? &old->u.periodic.envelope : NULL))
|
||||
pidff_set_envelope_report(pidff,
|
||||
&effect->u.periodic.envelope);
|
||||
break;
|
||||
@@ -635,9 +719,9 @@ static int pidff_upload_effect(struct input_dev *dev, struct ff_effect *effect,
|
||||
pidff_set_effect_report(pidff, effect);
|
||||
if (!old || pidff_needs_set_ramp(effect, old))
|
||||
pidff_set_ramp_force_report(pidff, effect);
|
||||
if (!old ||
|
||||
pidff_needs_set_envelope(&effect->u.ramp.envelope,
|
||||
&old->u.ramp.envelope))
|
||||
|
||||
if (pidff_needs_set_envelope(&effect->u.ramp.envelope,
|
||||
old ? &old->u.periodic.envelope : NULL))
|
||||
pidff_set_envelope_report(pidff,
|
||||
&effect->u.ramp.envelope);
|
||||
break;
|
||||
@@ -740,7 +824,9 @@ static void pidff_autocenter(struct pidff_device *pidff, u16 magnitude)
|
||||
pidff->set_effect[PID_TRIGGER_REPEAT_INT].value[0] = 0;
|
||||
pidff_set(&pidff->set_effect[PID_GAIN], magnitude);
|
||||
pidff->set_effect[PID_DIRECTION_ENABLE].value[0] = 1;
|
||||
if (!(pidff->quirks & PIDFF_QUIRK_NO_DELAY_EFFECT)) {
|
||||
pidff->set_effect[PID_START_DELAY].value[0] = 0;
|
||||
}
|
||||
|
||||
hid_hw_request(pidff->hid, pidff->reports[PID_SET_EFFECT],
|
||||
HID_REQ_SET_REPORT);
|
||||
@@ -925,6 +1011,10 @@ static int pidff_find_special_fields(struct pidff_device *pidff)
|
||||
{
|
||||
hid_dbg(pidff->hid, "finding special fields\n");
|
||||
|
||||
int strict_pid_device_control = 1;
|
||||
|
||||
if (pidff->quirks & PIDFF_QUIRK_NO_STRICT_PID_CONTROL)
|
||||
strict_pid_device_control = 0;
|
||||
pidff->create_new_effect_type =
|
||||
pidff_find_special_field(pidff->reports[PID_CREATE_NEW_EFFECT],
|
||||
0x25, 1);
|
||||
@@ -936,7 +1026,7 @@ static int pidff_find_special_fields(struct pidff_device *pidff)
|
||||
0x57, 0);
|
||||
pidff->device_control =
|
||||
pidff_find_special_field(pidff->reports[PID_DEVICE_CONTROL],
|
||||
0x96, 1);
|
||||
0x96, strict_pid_device_control);
|
||||
pidff->block_load_status =
|
||||
pidff_find_special_field(pidff->reports[PID_BLOCK_LOAD],
|
||||
0x8b, 1);
|
||||
@@ -1068,10 +1158,23 @@ static int pidff_init_fields(struct pidff_device *pidff, struct input_dev *dev)
|
||||
{
|
||||
int envelope_ok = 0;
|
||||
|
||||
if (pidff->quirks & PIDFF_QUIRK_NO_DELAY_EFFECT) {
|
||||
hid_dbg(pidff->hid, "Find fields for set_effect without delay\n");
|
||||
if (pidff_find_fields(pidff->set_effect,
|
||||
pidff_set_effect_without_delay,
|
||||
pidff->reports[PID_SET_EFFECT], \
|
||||
sizeof(pidff_set_effect_without_delay), 1)) {
|
||||
hid_err(pidff->hid, "unknown set_effect report layout\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (PIDFF_FIND_FIELDS(set_effect, PID_SET_EFFECT, 1)) {
|
||||
hid_err(pidff->hid, "unknown set_effect report layout\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
PIDFF_FIND_FIELDS(block_load, PID_BLOCK_LOAD, 0);
|
||||
if (!pidff->block_load[PID_EFFECT_BLOCK_INDEX].value) {
|
||||
@@ -1120,10 +1223,26 @@ static int pidff_init_fields(struct pidff_device *pidff, struct input_dev *dev)
|
||||
clear_bit(FF_RAMP, dev->ffbit);
|
||||
}
|
||||
|
||||
if (pidff->quirks & PIDFF_QUIRK_NO_PID_PARAM_BLOCK_OFFSET) {
|
||||
if ((test_bit(FF_SPRING, dev->ffbit) ||
|
||||
test_bit(FF_DAMPER, dev->ffbit) ||
|
||||
test_bit(FF_FRICTION, dev->ffbit) ||
|
||||
test_bit(FF_INERTIA, dev->ffbit)) &&
|
||||
pidff_find_fields(pidff->set_condition,
|
||||
pidff_set_condition_without_pbo,
|
||||
pidff->reports[PID_SET_CONDITION], \
|
||||
sizeof(pidff_set_condition_without_pbo), 1)) {
|
||||
hid_warn(pidff->hid, "unknown condition effect layout (w/o PBO)\n");
|
||||
clear_bit(FF_SPRING, dev->ffbit);
|
||||
clear_bit(FF_DAMPER, dev->ffbit);
|
||||
clear_bit(FF_FRICTION, dev->ffbit);
|
||||
clear_bit(FF_INERTIA, dev->ffbit);
|
||||
}
|
||||
}
|
||||
else if ((test_bit(FF_SPRING, dev->ffbit) ||
|
||||
test_bit(FF_DAMPER, dev->ffbit) ||
|
||||
test_bit(FF_FRICTION, dev->ffbit) ||
|
||||
test_bit(FF_INERTIA, dev->ffbit)) &&
|
||||
PIDFF_FIND_FIELDS(set_condition, PID_SET_CONDITION, 1)) {
|
||||
hid_warn(pidff->hid, "unknown condition effect layout\n");
|
||||
clear_bit(FF_SPRING, dev->ffbit);
|
||||
@@ -1154,7 +1273,9 @@ static void pidff_reset(struct pidff_device *pidff)
|
||||
struct hid_device *hid = pidff->hid;
|
||||
int i = 0;
|
||||
|
||||
hid_dbg(hid, "%s: Resetting the device...", __func__);
|
||||
pidff->device_control->value[0] = pidff->control_id[PID_RESET];
|
||||
hid_dbg(hid, "%s: PID_RESET control_id: %02x", __func__, pidff->device_control->value[0]);
|
||||
/* We reset twice as sometimes hid_wait_io isn't waiting long enough */
|
||||
hid_hw_request(hid, pidff->reports[PID_DEVICE_CONTROL], HID_REQ_SET_REPORT);
|
||||
hid_hw_wait(hid);
|
||||
@@ -1163,6 +1284,7 @@ static void pidff_reset(struct pidff_device *pidff)
|
||||
|
||||
pidff->device_control->value[0] =
|
||||
pidff->control_id[PID_ENABLE_ACTUATORS];
|
||||
hid_dbg(hid, "%s: PID_ENABLE_ACTUATORS control_id: %02x", __func__, pidff->device_control->value[0]);
|
||||
hid_hw_request(hid, pidff->reports[PID_DEVICE_CONTROL], HID_REQ_SET_REPORT);
|
||||
hid_hw_wait(hid);
|
||||
|
||||
@@ -1184,6 +1306,7 @@ static void pidff_reset(struct pidff_device *pidff)
|
||||
hid_hw_wait(hid);
|
||||
}
|
||||
}
|
||||
hid_dbg(hid, "%s: Resetting device complete", __func__);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1225,9 +1348,9 @@ static int pidff_check_autocenter(struct pidff_device *pidff,
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if the device is PID and initialize it
|
||||
* Check if the device is PID and initialize it, with quirks
|
||||
*/
|
||||
int hid_pidff_init_moza(struct hid_device *hid)
|
||||
int hid_pidff_init_quirks(struct hid_device *hid, const struct hid_device_id *id)
|
||||
{
|
||||
struct pidff_device *pidff;
|
||||
struct hid_input *hidinput = list_entry(hid->inputs.next,
|
||||
@@ -1249,6 +1372,8 @@ int hid_pidff_init_moza(struct hid_device *hid)
|
||||
return -ENOMEM;
|
||||
|
||||
pidff->hid = hid;
|
||||
pidff->quirks |= id->driver_data;
|
||||
hid_dbg(dev, "Device quirks: %d\n", pidff->quirks);
|
||||
|
||||
hid_device_io_start(hid);
|
||||
|
||||
@@ -1314,7 +1439,7 @@ int hid_pidff_init_moza(struct hid_device *hid)
|
||||
ff->set_autocenter = pidff_set_autocenter;
|
||||
ff->playback = pidff_playback;
|
||||
|
||||
hid_info(dev, "Force feedback for Moza wheel\n");
|
||||
hid_info(dev, "Force feedback for USB HID PID devices\n");
|
||||
|
||||
hid_device_io_stop(hid);
|
||||
|
||||
|
||||
35
hid-pidff.h
Normal file
35
hid-pidff.h
Normal file
@@ -0,0 +1,35 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
#ifndef __HID_PIDFF_H
|
||||
#define __HID_PIDFF_H
|
||||
|
||||
/* PIDFF Quirks to solve issues with certain devices */
|
||||
|
||||
/*
|
||||
* Ignore direction and always set 16384 (0x4000)
|
||||
*/
|
||||
#define PIDFF_QUIRK_FIX_WHEEL_DIRECTION BIT(0)
|
||||
|
||||
/*
|
||||
* Skip initialization of 0xA7 descriptor (Delay effect)
|
||||
* Fixes VRS DFP, Cammus, old Simagic wheelbases
|
||||
*/
|
||||
#define PIDFF_QUIRK_NO_DELAY_EFFECT BIT(1)
|
||||
|
||||
/*
|
||||
* Ignore PARAM_BLOCK_OFFSET (Axis number).
|
||||
* Most of the wheelbases have only one Axis anyway
|
||||
* Fixes VRS DFP
|
||||
*/
|
||||
#define PIDFF_QUIRK_NO_PID_PARAM_BLOCK_OFFSET BIT(2)
|
||||
|
||||
/*
|
||||
* Some wheelbases don't have some PID_CONTROL fields.
|
||||
* PID standard does not define fields that MUST exist, but
|
||||
* that driver was strict about them. This quirk disables it.
|
||||
* Fixes VRS DFP
|
||||
*/
|
||||
#define PIDFF_QUIRK_NO_STRICT_PID_CONTROL BIT(3)
|
||||
|
||||
int hid_pidff_init_quirks(struct hid_device *hid, const struct hid_device_id *id);
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user