Summary
Update: the calibration described below is temporary. It does NOT survive a power cycle (unplugging the USB cable / battery removal reverts the drift). I'm keeping this report because the command sequence itself is real, reproducible, and may still be useful as a "soft reset" applied at every boot/connect (e.g. via a udev rule running a short script) — but it is not a permanent firmware fix, and I want to be upfront about that so nobody expects otherwise.
Also relevant: my controller's stock potentiometer/Hall sticks have been replaced with aftermarket Hall-effect stick modules. I can't rule out that this replacement is the actual root cause of the drift I'm seeing, or that it affects how well/how long the recalibration command holds. I have not tested this sequence on a controller with factory-original sticks, so I don't know if the "doesn't survive power cycle" behavior is specific to aftermarket Hall sticks or is just how this GIP command works in general.
Windows' "Xbox Accessories" app can correct this same severe analog-stick drift (right stick reporting values near the axis extreme at rest, e.g. RY = -30544 out of a possible ±32767) on an Xbox Series S/X Controller (VID 045e, PID 0b12).
By capturing USB traffic between Windows and the controller during a Xbox Accessories calibration run, I found and reproduced (on real Linux hardware, controller connected via USB cable) a 3-step GIP command sequence under command byte 0x1e, subcommand 0x10, that triggers what appears to be a runtime re-centering of the stick ADC baseline, not a host-side software offset. I confirmed the raw USB input reports change immediately after sending subcommand 0x02, before the user physically touches the stick. However, since it does not survive a power cycle, this re-centering is most likely stored in volatile RAM on the controller's MCU rather than written to persistent flash/EEPROM.
I don't maintain a GIP driver myself and don't have deep familiarity with this codebase, so I'm sharing the raw evidence and a minimal reproduction script in case it's useful for xone/xpadneo or anyone else working on this. Happy to capture more data if it helps (e.g. isolating left-stick-only calibration, testing on factory-original sticks, or dumping the full Windows USBPcap trace rather than just the 0x1e10 frames).
Hardware / environment
Controller: Xbox Series S/X Controller, VID 045e PID 0b12
Capture side (reference behavior): Windows 11, Xbox Accessories app, captured with Wireshark + USBPcap
Reproduction side: Linux (CachyOS, Arch-based), kernel's stock xpad driver temporarily unbound during the test, raw USB access via pyusb, controller connected by USB cable (not tested over Bluetooth or the wireless dongle)
Endpoints: interface 0, EP OUT 0x02 / EP IN 0x82 (interrupt)
Sticks are aftermarket Hall-effect replacements, not the original factory potentiometer/Hall modules. Not tested on stock hardware.
Symptom (before calibration)
Input report axis values at rest (stick physically untouched):
RX ≈ -790 (small, within normal noise)
RY ≈ -30544 (severe drift, near the negative extreme of int16 range)
Command sequence (as sent by Xbox Accessories on Windows, byte-for-byte)
Captured with Wireshark/USBPcap. Timestamps are relative to capture start.
t=118.224282 OUT 1e 10 01 02 0f 02
t=118.236239 IN 1e 10 01 0c 0f 00 00 02 67 00 4f 0f 22 00 b6 0f
t=118.239690 OUT 1e 10 02 02 10 02
t=118.280233 IN 1e 10 02 03 10 00 02
[ ~19 s window: user moves both analog sticks through full range ]
t=137.628523 OUT 1e 10 03 02 0f 02
t=137.636688 IN 1e 10 03 0c 0f 00 10 02 5e 00 57 0f 32 00 b5 0f
No other traffic to the controller during the 19 s window resembles a calibration write — only rumble (0x09 ...) and what looks like an unrelated periodic status/heartbeat command (0x0d 0x20 ...). This suggests the host is only reading stick extremes during that window (presumably to compute range/gain), while the actual re-centering trigger is the 0x02 subcommand sent beforehand.
Reproduction on Linux (this is the interesting part)
I resent the exact same 3 OUT payloads to the controller from Linux via raw USB (interface 0, EP 0x02 OUT / EP 0x82 IN), with xpad unbound from the device beforehand. Results, read from the raw GIP input report (0x2000..., sticks at offset 10/12/14/16 as int16 LE = LX/LY/RX/RY):
Before sequence (stick at rest): RX=-790 RY=-30544
Immediately after sending 1e1002 (subcmd 02),
stick still untouched: RX=-790 RY=248
After user moves both sticks through range,
then sending 1e1003 (subcmd 03), at rest: RX=33 RY=-17
The key observation: RY jumps from -30544 to near-zero the moment 1e1002 is sent, before the user has touched the stick. This rules out a purely host-side software correction (there's no "reading" step that could explain a change in the raw device-reported value without user input) and strongly suggests the controller firmware re-samples its own ADC/Hall-sensor baseline and treats the current physical position as the new center point at that moment.
The corrected value also survives being read again after the full sequence completes, with no further host-side arithmetic applied — i.e. it's not xpad/xone computing an offset and subtracting it in the driver; the device itself is reporting different raw numbers for the same physical resting position, within that session.
However, this correction does not survive a power cycle. Unplugging the USB cable (or removing the battery, on a wireless unit) and reconnecting reverts the controller to the original drifted values. This strongly suggests the new center point is held in volatile MCU RAM and recomputed/reset at boot, rather than written to flash/EEPROM. I have not yet tried re-running the sequence immediately after each reconnect as a practical workaround (e.g. via a udev rule), but that seems like the realistic use case for this finding rather than a one-time fix.
Response payload contents (unconfirmed interpretation)
The 10-byte payload following the 1e 10 01/03 0c 0f 00 header in both read/confirm responses:
Response to 1e1001 (before): 00 02 67 00 4f 0f 22 00 b6 0f
Response to 1e1003 (after): 10 02 5e 00 57 0f 32 00 b5 0f
Interpreted as 5×int16 LE:
field0 field1 field2 field3 field4
before: 512 103 3919 34 4022
after: 528 94 3927 50 4021
delta: +16 -9 +8 +16 -1
I have not confirmed what each field represents. The deltas here are small (tens of units) and don't obviously correlate with the ~30000-unit jump seen in the raw stick value, so my current guess is that these fields are secondary (range/gain trim?) rather than the center offset itself — the actual re-centering seems to happen internally in firmware as a side effect of the 0x02 subcommand, without the new center value necessarily being exposed in this particular response. I'd appreciate anyone with more GIP protocol knowledge sanity-checking this.
Minimal repro script
python
import usb.core, usb.util, struct, time
VID, PID = 0x045e, 0x0b12
EP_OUT, EP_IN = 0x02, 0x82
CMD_READ = bytes.fromhex("1e1001020f02")
CMD_START = bytes.fromhex("1e1002021002")
CMD_CONFIRM = bytes.fromhex("1e1003020f02")
dev = usb.core.find(idVendor=VID, idProduct=PID)
usb.util.claim_interface(dev, 0)
def send(payload):
dev.write(EP_OUT, payload, timeout=1000)
return bytes(dev.read(EP_IN, 64, timeout=1000))
print(send(CMD_READ).hex())
print(send(CMD_START).hex())
input("Move both sticks through full range now, then press Enter...")
print(send(CMD_CONFIRM).hex())
(Requires unbinding xpad/xone from the device first so the raw interrupt endpoints are free for userspace access; rebind afterwards.)
Open questions / things I haven't verified
Whether this works the same way (or at all) on a controller with factory-original sticks rather than aftermarket Hall-effect replacements. Given my hardware, I can't currently separate "this is how the GIP command behaves in general" from "this is how it behaves with my specific stick mod."
Whether Xbox Accessories on Windows achieves a persistent fix on this same (modified) hardware, or whether it would also revert after a power cycle if tested there — I only verified the Windows side qualitatively (the app reports it as fixed) and didn't power-cycle the controller while still on Windows to check.
Whether moving both sticks is required, or whether the controller calibrates each stick independently based on which one moved.
Whether re-sending the sequence automatically on every connect (e.g. via udev + a small script) is a viable practical workaround, given it does not persist on its own.
The exact meaning of the 10-byte payload fields above.
Whether the same subcommand sequence works over Bluetooth or the wireless dongle (only tested wired USB).
Whether trigger (LT/RT) calibration uses a related or entirely different command.
Why I'm posting this here
xpadneo's own docs state drift can't be fixed in the driver and recommend increasing deadzone instead. I also found a recent Arch Linux forum thread from someone with this exact symptom, asking whether true recentering (not just a larger deadzone) is possible at the evdev level, with no solution found at the time. If this sequence is confirmed and safe, it seems like a genuinely useful addition for xone (or a standalone udev-triggered utility) rather than something users have to reverse-engineer individually.
I'm not confident enough in my understanding of the full GIP command space to submit this as a driver patch myself, so I'm sharing it as a bug report / research note instead.
Summary
Update: the calibration described below is temporary. It does NOT survive a power cycle (unplugging the USB cable / battery removal reverts the drift). I'm keeping this report because the command sequence itself is real, reproducible, and may still be useful as a "soft reset" applied at every boot/connect (e.g. via a udev rule running a short script) — but it is not a permanent firmware fix, and I want to be upfront about that so nobody expects otherwise.
Also relevant: my controller's stock potentiometer/Hall sticks have been replaced with aftermarket Hall-effect stick modules. I can't rule out that this replacement is the actual root cause of the drift I'm seeing, or that it affects how well/how long the recalibration command holds. I have not tested this sequence on a controller with factory-original sticks, so I don't know if the "doesn't survive power cycle" behavior is specific to aftermarket Hall sticks or is just how this GIP command works in general.
Windows' "Xbox Accessories" app can correct this same severe analog-stick drift (right stick reporting values near the axis extreme at rest, e.g. RY = -30544 out of a possible ±32767) on an Xbox Series S/X Controller (VID 045e, PID 0b12).
By capturing USB traffic between Windows and the controller during a Xbox Accessories calibration run, I found and reproduced (on real Linux hardware, controller connected via USB cable) a 3-step GIP command sequence under command byte 0x1e, subcommand 0x10, that triggers what appears to be a runtime re-centering of the stick ADC baseline, not a host-side software offset. I confirmed the raw USB input reports change immediately after sending subcommand 0x02, before the user physically touches the stick. However, since it does not survive a power cycle, this re-centering is most likely stored in volatile RAM on the controller's MCU rather than written to persistent flash/EEPROM.
I don't maintain a GIP driver myself and don't have deep familiarity with this codebase, so I'm sharing the raw evidence and a minimal reproduction script in case it's useful for xone/xpadneo or anyone else working on this. Happy to capture more data if it helps (e.g. isolating left-stick-only calibration, testing on factory-original sticks, or dumping the full Windows USBPcap trace rather than just the 0x1e10 frames).
Hardware / environment
Controller: Xbox Series S/X Controller, VID 045e PID 0b12
Capture side (reference behavior): Windows 11, Xbox Accessories app, captured with Wireshark + USBPcap
Reproduction side: Linux (CachyOS, Arch-based), kernel's stock xpad driver temporarily unbound during the test, raw USB access via pyusb, controller connected by USB cable (not tested over Bluetooth or the wireless dongle)
Endpoints: interface 0, EP OUT 0x02 / EP IN 0x82 (interrupt)
Sticks are aftermarket Hall-effect replacements, not the original factory potentiometer/Hall modules. Not tested on stock hardware.
Symptom (before calibration)
Input report axis values at rest (stick physically untouched):
RX ≈ -790 (small, within normal noise)
RY ≈ -30544 (severe drift, near the negative extreme of int16 range)
Command sequence (as sent by Xbox Accessories on Windows, byte-for-byte)
Captured with Wireshark/USBPcap. Timestamps are relative to capture start.
t=118.224282 OUT 1e 10 01 02 0f 02
t=118.236239 IN 1e 10 01 0c 0f 00 00 02 67 00 4f 0f 22 00 b6 0f
t=118.239690 OUT 1e 10 02 02 10 02
t=118.280233 IN 1e 10 02 03 10 00 02
[ ~19 s window: user moves both analog sticks through full range ]
t=137.628523 OUT 1e 10 03 02 0f 02
t=137.636688 IN 1e 10 03 0c 0f 00 10 02 5e 00 57 0f 32 00 b5 0f
No other traffic to the controller during the 19 s window resembles a calibration write — only rumble (0x09 ...) and what looks like an unrelated periodic status/heartbeat command (0x0d 0x20 ...). This suggests the host is only reading stick extremes during that window (presumably to compute range/gain), while the actual re-centering trigger is the 0x02 subcommand sent beforehand.
Reproduction on Linux (this is the interesting part)
I resent the exact same 3 OUT payloads to the controller from Linux via raw USB (interface 0, EP 0x02 OUT / EP 0x82 IN), with xpad unbound from the device beforehand. Results, read from the raw GIP input report (0x2000..., sticks at offset 10/12/14/16 as int16 LE = LX/LY/RX/RY):
Before sequence (stick at rest): RX=-790 RY=-30544
Immediately after sending 1e1002 (subcmd 02),
stick still untouched: RX=-790 RY=248
After user moves both sticks through range,
then sending 1e1003 (subcmd 03), at rest: RX=33 RY=-17
The key observation: RY jumps from -30544 to near-zero the moment 1e1002 is sent, before the user has touched the stick. This rules out a purely host-side software correction (there's no "reading" step that could explain a change in the raw device-reported value without user input) and strongly suggests the controller firmware re-samples its own ADC/Hall-sensor baseline and treats the current physical position as the new center point at that moment.
The corrected value also survives being read again after the full sequence completes, with no further host-side arithmetic applied — i.e. it's not xpad/xone computing an offset and subtracting it in the driver; the device itself is reporting different raw numbers for the same physical resting position, within that session.
However, this correction does not survive a power cycle. Unplugging the USB cable (or removing the battery, on a wireless unit) and reconnecting reverts the controller to the original drifted values. This strongly suggests the new center point is held in volatile MCU RAM and recomputed/reset at boot, rather than written to flash/EEPROM. I have not yet tried re-running the sequence immediately after each reconnect as a practical workaround (e.g. via a udev rule), but that seems like the realistic use case for this finding rather than a one-time fix.
Response payload contents (unconfirmed interpretation)
The 10-byte payload following the 1e 10 01/03 0c 0f 00 header in both read/confirm responses:
Response to 1e1001 (before): 00 02 67 00 4f 0f 22 00 b6 0f
Response to 1e1003 (after): 10 02 5e 00 57 0f 32 00 b5 0f
Interpreted as 5×int16 LE:
before: 512 103 3919 34 4022
after: 528 94 3927 50 4021
delta: +16 -9 +8 +16 -1
I have not confirmed what each field represents. The deltas here are small (tens of units) and don't obviously correlate with the ~30000-unit jump seen in the raw stick value, so my current guess is that these fields are secondary (range/gain trim?) rather than the center offset itself — the actual re-centering seems to happen internally in firmware as a side effect of the 0x02 subcommand, without the new center value necessarily being exposed in this particular response. I'd appreciate anyone with more GIP protocol knowledge sanity-checking this.
Minimal repro script
python
import usb.core, usb.util, struct, time
VID, PID = 0x045e, 0x0b12
EP_OUT, EP_IN = 0x02, 0x82
CMD_READ = bytes.fromhex("1e1001020f02")
CMD_START = bytes.fromhex("1e1002021002")
CMD_CONFIRM = bytes.fromhex("1e1003020f02")
dev = usb.core.find(idVendor=VID, idProduct=PID)
usb.util.claim_interface(dev, 0)
def send(payload):
dev.write(EP_OUT, payload, timeout=1000)
return bytes(dev.read(EP_IN, 64, timeout=1000))
print(send(CMD_READ).hex())
print(send(CMD_START).hex())
input("Move both sticks through full range now, then press Enter...")
print(send(CMD_CONFIRM).hex())
(Requires unbinding xpad/xone from the device first so the raw interrupt endpoints are free for userspace access; rebind afterwards.)
Open questions / things I haven't verified
Whether this works the same way (or at all) on a controller with factory-original sticks rather than aftermarket Hall-effect replacements. Given my hardware, I can't currently separate "this is how the GIP command behaves in general" from "this is how it behaves with my specific stick mod."
Whether Xbox Accessories on Windows achieves a persistent fix on this same (modified) hardware, or whether it would also revert after a power cycle if tested there — I only verified the Windows side qualitatively (the app reports it as fixed) and didn't power-cycle the controller while still on Windows to check.
Whether moving both sticks is required, or whether the controller calibrates each stick independently based on which one moved.
Whether re-sending the sequence automatically on every connect (e.g. via udev + a small script) is a viable practical workaround, given it does not persist on its own.
The exact meaning of the 10-byte payload fields above.
Whether the same subcommand sequence works over Bluetooth or the wireless dongle (only tested wired USB).
Whether trigger (LT/RT) calibration uses a related or entirely different command.
Why I'm posting this here
xpadneo's own docs state drift can't be fixed in the driver and recommend increasing deadzone instead. I also found a recent Arch Linux forum thread from someone with this exact symptom, asking whether true recentering (not just a larger deadzone) is possible at the evdev level, with no solution found at the time. If this sequence is confirmed and safe, it seems like a genuinely useful addition for xone (or a standalone udev-triggered utility) rather than something users have to reverse-engineer individually.
I'm not confident enough in my understanding of the full GIP command space to submit this as a driver patch myself, so I'm sharing it as a bug report / research note instead.