SDL: Added microsecond timestamp to sensor values for PS4 and PS5 controllers using the HIDAPI driver

From 2c518747b9cf45c1b94c570c19912e04476e8eed Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Tue, 27 Sep 2022 09:56:02 -0700
Subject: [PATCH] Added microsecond timestamp to sensor values for PS4 and PS5
 controllers using the HIDAPI driver

---
 include/SDL_events.h                         |  2 +
 include/SDL_gamecontroller.h                 | 17 +++++
 include/SDL_sensor.h                         | 19 +++++-
 src/dynapi/SDL2.exports                      |  2 +
 src/dynapi/SDL_dynapi_overrides.h            |  2 +
 src/dynapi/SDL_dynapi_procs.h                |  2 +
 src/joystick/SDL_gamecontroller.c            | 12 ++++
 src/joystick/SDL_joystick.c                  |  4 +-
 src/joystick/SDL_joystick_c.h                |  2 +-
 src/joystick/SDL_sysjoystick.h               |  1 +
 src/joystick/hidapi/SDL_hidapi_ps3.c         |  2 +-
 src/joystick/hidapi/SDL_hidapi_ps4.c         | 41 +++++++++---
 src/joystick/hidapi/SDL_hidapi_ps5.c         | 67 +++++++++++++++-----
 src/joystick/hidapi/SDL_hidapi_steam.c       |  4 +-
 src/joystick/hidapi/SDL_hidapi_switch.c      |  2 +-
 src/joystick/hidapi/SDL_hidapi_wii.c         |  6 +-
 src/joystick/iphoneos/SDL_mfijoystick.m      |  4 +-
 src/sensor/SDL_sensor.c                      | 30 ++++++---
 src/sensor/SDL_sensor_c.h                    |  2 +-
 src/sensor/SDL_syssensor.h                   |  1 +
 src/sensor/android/SDL_androidsensor.c       |  2 +-
 src/sensor/coremotion/SDL_coremotionsensor.m |  4 +-
 src/sensor/vita/SDL_vitasensor.c             | 27 +++++---
 src/sensor/vita/SDL_vitasensor.h             |  3 +-
 src/sensor/windows/SDL_windowssensor.c       |  4 +-
 test/testgamecontroller.c                    |  5 +-
 26 files changed, 204 insertions(+), 63 deletions(-)

diff --git a/include/SDL_events.h b/include/SDL_events.h
index 1836fc06c378..240a8a8bad85 100644
--- a/include/SDL_events.h
+++ b/include/SDL_events.h
@@ -474,6 +474,7 @@ typedef struct SDL_ControllerSensorEvent
     SDL_JoystickID which; /**< The joystick instance id */
     Sint32 sensor;      /**< The type of the sensor, one of the values of ::SDL_SensorType */
     float data[3];      /**< Up to 3 values from the sensor, as defined in SDL_sensor.h */
+    Uint64 timestamp_us; /**< The timestamp of the sensor reading in microseconds, if the hardware provides this information. */
 } SDL_ControllerSensorEvent;
 
 /**
@@ -565,6 +566,7 @@ typedef struct SDL_SensorEvent
     Uint32 timestamp;   /**< In milliseconds, populated using SDL_GetTicks() */
     Sint32 which;       /**< The instance ID of the sensor */
     float data[6];      /**< Up to 6 values from the sensor - additional values can be queried using SDL_SensorGetData() */
+    Uint64 timestamp_us; /**< The timestamp of the sensor reading in microseconds, if the hardware provides this information. */
 } SDL_SensorEvent;
 
 /**
diff --git a/include/SDL_gamecontroller.h b/include/SDL_gamecontroller.h
index ace1c163f1ca..e32d3e8f717a 100644
--- a/include/SDL_gamecontroller.h
+++ b/include/SDL_gamecontroller.h
@@ -895,6 +895,23 @@ extern DECLSPEC float SDLCALL SDL_GameControllerGetSensorDataRate(SDL_GameContro
  */
 extern DECLSPEC int SDLCALL SDL_GameControllerGetSensorData(SDL_GameController *gamecontroller, SDL_SensorType type, float *data, int num_values);
 
+/**
+ * Get the current state of a game controller sensor with the timestamp of the last update.
+ *
+ * The number of values and interpretation of the data is sensor dependent.
+ * See SDL_sensor.h for the details for each type of sensor.
+ *
+ * \param gamecontroller The controller to query
+ * \param type The type of sensor to query
+ * \param timestamp A pointer filled with the timestamp in microseconds of the current sensor reading if available, or 0 if not
+ * \param data A pointer filled with the current sensor state
+ * \param num_values The number of values to write to data
+ * \return 0 or -1 if an error occurred.
+ *
+ * \since This function is available since SDL 2.26.0.
+ */
+extern DECLSPEC int SDLCALL SDL_GameControllerGetSensorDataWithTimestamp(SDL_GameController *gamecontroller, SDL_SensorType type, Uint64 *timestamp, float *data, int num_values);
+
 /**
  * Start a rumble effect on a game controller.
  *
diff --git a/include/SDL_sensor.h b/include/SDL_sensor.h
index 6b07183eadfd..823cbbb70ec7 100644
--- a/include/SDL_sensor.h
+++ b/include/SDL_sensor.h
@@ -267,7 +267,22 @@ extern DECLSPEC SDL_SensorID SDLCALL SDL_SensorGetInstanceID(SDL_Sensor *sensor)
  *
  * \since This function is available since SDL 2.0.9.
  */
-extern DECLSPEC int SDLCALL SDL_SensorGetData(SDL_Sensor * sensor, float *data, int num_values);
+extern DECLSPEC int SDLCALL SDL_SensorGetData(SDL_Sensor *sensor, float *data, int num_values);
+
+/**
+ * Get the current state of an opened sensor with the timestamp of the last update.
+ *
+ * The number of values and interpretation of the data is sensor dependent.
+ *
+ * \param sensor The SDL_Sensor object to query
+ * \param timestamp A pointer filled with the timestamp in microseconds of the current sensor reading if available, or 0 if not
+ * \param data A pointer filled with the current sensor state
+ * \param num_values The number of values to write to data
+ * \returns 0 or -1 if an error occurred.
+ *
+ * \since This function is available since SDL 2.0.9.
+ */
+extern DECLSPEC int SDLCALL SDL_SensorGetDataWithTimestamp(SDL_Sensor *sensor, Uint64 *timestamp, float *data, int num_values);
 
 /**
  * Close a sensor previously opened with SDL_SensorOpen().
@@ -276,7 +291,7 @@ extern DECLSPEC int SDLCALL SDL_SensorGetData(SDL_Sensor * sensor, float *data,
  *
  * \since This function is available since SDL 2.0.9.
  */
-extern DECLSPEC void SDLCALL SDL_SensorClose(SDL_Sensor * sensor);
+extern DECLSPEC void SDLCALL SDL_SensorClose(SDL_Sensor *sensor);
 
 /**
  * Update the current state of the open sensors.
diff --git a/src/dynapi/SDL2.exports b/src/dynapi/SDL2.exports
index d9e3545c3d35..c2e903ecbeb3 100644
--- a/src/dynapi/SDL2.exports
+++ b/src/dynapi/SDL2.exports
@@ -863,3 +863,5 @@
 ++'_SDL_SetPrimarySelectionText'.'SDL2.dll'.'SDL_SetPrimarySelectionText'
 ++'_SDL_GetPrimarySelectionText'.'SDL2.dll'.'SDL_GetPrimarySelectionText'
 ++'_SDL_HasPrimarySelectionText'.'SDL2.dll'.'SDL_HasPrimarySelectionText'
+++'_SDL_GameControllerGetSensorDataWithTimestamp'.'SDL2.dll'.'SDL_GameControllerGetSensorDataWithTimestamp'
+++'_SDL_SensorGetDataWithTimestamp'.'SDL2.dll'.'SDL_SensorGetDataWithTimestamp'
diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h
index 2e982b81b123..fc9255cdd767 100644
--- a/src/dynapi/SDL_dynapi_overrides.h
+++ b/src/dynapi/SDL_dynapi_overrides.h
@@ -889,3 +889,5 @@
 #define SDL_SetPrimarySelectionText SDL_SetPrimarySelectionText_REAL
 #define SDL_GetPrimarySelectionText SDL_GetPrimarySelectionText_REAL
 #define SDL_HasPrimarySelectionText SDL_HasPrimarySelectionText_REAL
+#define SDL_GameControllerGetSensorDataWithTimestamp SDL_GameControllerGetSensorDataWithTimestamp_REAL
+#define SDL_SensorGetDataWithTimestamp SDL_SensorGetDataWithTimestamp_REAL
diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h
index b40effffc2f3..f8d858ddc8f9 100644
--- a/src/dynapi/SDL_dynapi_procs.h
+++ b/src/dynapi/SDL_dynapi_procs.h
@@ -972,3 +972,5 @@ SDL_DYNAPI_PROC(void,SDL_GetJoystickGUIDInfo,(SDL_JoystickGUID a, Uint16 *b, Uin
 SDL_DYNAPI_PROC(int,SDL_SetPrimarySelectionText,(const char *a),(a),return)
 SDL_DYNAPI_PROC(char*,SDL_GetPrimarySelectionText,(void),(),return)
 SDL_DYNAPI_PROC(SDL_bool,SDL_HasPrimarySelectionText,(void),(),return)
+SDL_DYNAPI_PROC(int,SDL_GameControllerGetSensorDataWithTimestamp,(SDL_GameController *a, SDL_SensorType b, Uint64 *c, float *d, int e),(a,b,c,d,e),return)
+SDL_DYNAPI_PROC(int,SDL_SensorGetDataWithTimestamp,(SDL_Sensor *a, Uint64 *b, float *c, int d),(a,b,c,d),return)
diff --git a/src/joystick/SDL_gamecontroller.c b/src/joystick/SDL_gamecontroller.c
index 204ae965b965..b764980e9859 100644
--- a/src/joystick/SDL_gamecontroller.c
+++ b/src/joystick/SDL_gamecontroller.c
@@ -2440,6 +2440,15 @@ SDL_GameControllerGetSensorDataRate(SDL_GameController *gamecontroller, SDL_Sens
  */
 int
 SDL_GameControllerGetSensorData(SDL_GameController *gamecontroller, SDL_SensorType type, float *data, int num_values)
+{
+    return SDL_GameControllerGetSensorDataWithTimestamp(gamecontroller, type, NULL, data, num_values);
+}
+
+/*
+ *  Get the current state of a game controller sensor.
+ */
+int
+SDL_GameControllerGetSensorDataWithTimestamp(SDL_GameController *gamecontroller, SDL_SensorType type, Uint64 *timestamp, float *data, int num_values)
 {
     SDL_Joystick *joystick = SDL_GameControllerGetJoystick(gamecontroller);
     int i;
@@ -2454,6 +2463,9 @@ SDL_GameControllerGetSensorData(SDL_GameController *gamecontroller, SDL_SensorTy
         if (sensor->type == type) {
             num_values = SDL_min(num_values, SDL_arraysize(sensor->data));
             SDL_memcpy(data, sensor->data, num_values*sizeof(*data));
+            if (timestamp) {
+                *timestamp = sensor->timestamp_us;
+            }
             return 0;
         }
     }
diff --git a/src/joystick/SDL_joystick.c b/src/joystick/SDL_joystick.c
index dc890266c81a..a42951ce1835 100644
--- a/src/joystick/SDL_joystick.c
+++ b/src/joystick/SDL_joystick.c
@@ -2983,7 +2983,7 @@ int SDL_PrivateJoystickTouchpad(SDL_Joystick *joystick, int touchpad, int finger
     return posted;
 }
 
-int SDL_PrivateJoystickSensor(SDL_Joystick *joystick, SDL_SensorType type, const float *data, int num_values)
+int SDL_PrivateJoystickSensor(SDL_Joystick *joystick, SDL_SensorType type, Uint64 timestamp_us, const float *data, int num_values)
 {
     int i;
     int posted = 0;
@@ -3004,6 +3004,7 @@ int SDL_PrivateJoystickSensor(SDL_Joystick *joystick, SDL_SensorType type, const
 
                 /* Update internal sensor state */
                 SDL_memcpy(sensor->data, data, num_values*sizeof(*data));
+                sensor->timestamp_us = timestamp_us;
 
                 /* Post the event, if desired */
 #if !SDL_EVENTS_DISABLED
@@ -3015,6 +3016,7 @@ int SDL_PrivateJoystickSensor(SDL_Joystick *joystick, SDL_SensorType type, const
                     num_values = SDL_min(num_values, SDL_arraysize(event.csensor.data));
                     SDL_memset(event.csensor.data, 0, sizeof(event.csensor.data));
                     SDL_memcpy(event.csensor.data, data, num_values*sizeof(*data));
+                    event.csensor.timestamp_us = timestamp_us;
                     posted = SDL_PushEvent(&event) == 1;
                 }
 #endif /* !SDL_EVENTS_DISABLED */
diff --git a/src/joystick/SDL_joystick_c.h b/src/joystick/SDL_joystick_c.h
index 82311a078523..7ed3e63180c2 100644
--- a/src/joystick/SDL_joystick_c.h
+++ b/src/joystick/SDL_joystick_c.h
@@ -160,7 +160,7 @@ extern int SDL_PrivateJoystickButton(SDL_Joystick *joystick,
 extern int SDL_PrivateJoystickTouchpad(SDL_Joystick *joystick,
                                        int touchpad, int finger, Uint8 state, float x, float y, float pressure);
 extern int SDL_PrivateJoystickSensor(SDL_Joystick *joystick,
-                                     SDL_SensorType type, const float *data, int num_values);
+                                     SDL_SensorType type, Uint64 timestamp_us, const float *data, int num_values);
 extern void SDL_PrivateJoystickBatteryLevel(SDL_Joystick *joystick,
                                             SDL_JoystickPowerLevel ePowerLevel);
 
diff --git a/src/joystick/SDL_sysjoystick.h b/src/joystick/SDL_sysjoystick.h
index 34a17429ff2e..a0400c6ffe6d 100644
--- a/src/joystick/SDL_sysjoystick.h
+++ b/src/joystick/SDL_sysjoystick.h
@@ -64,6 +64,7 @@ typedef struct _SDL_JoystickSensorInfo
     SDL_bool enabled;
     float rate;
     float data[3];      /* If this needs to expand, update SDL_ControllerSensorEvent */
+    Uint64 timestamp_us;
 } SDL_JoystickSensorInfo;
 
 struct _SDL_Joystick
diff --git a/src/joystick/hidapi/SDL_hidapi_ps3.c b/src/joystick/hidapi/SDL_hidapi_ps3.c
index a90d4a3bfc50..49913d9dff05 100644
--- a/src/joystick/hidapi/SDL_hidapi_ps3.c
+++ b/src/joystick/hidapi/SDL_hidapi_ps3.c
@@ -487,7 +487,7 @@ HIDAPI_DriverPS3_HandleStatePacket(SDL_Joystick *joystick, SDL_DriverPS3_Context
         sensor_data[0] = HIDAPI_DriverPS3_ScaleAccel(LOAD16(data[41], data[42]));
         sensor_data[1] = -HIDAPI_DriverPS3_ScaleAccel(LOAD16(data[45], data[46]));
         sensor_data[2] = -HIDAPI_DriverPS3_ScaleAccel(LOAD16(data[43], data[44]));
-        SDL_PrivateJoystickSensor(joystick, SDL_SENSOR_ACCEL, sensor_data, SDL_arraysize(sensor_data));
+        SDL_PrivateJoystickSensor(joystick, SDL_SENSOR_ACCEL, 0, sensor_data, SDL_arraysize(sensor_data));
     }
 
     SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state)));
diff --git a/src/joystick/hidapi/SDL_hidapi_ps4.c b/src/joystick/hidapi/SDL_hidapi_ps4.c
index 6b4fee3ab298..40884dade0f9 100644
--- a/src/joystick/hidapi/SDL_hidapi_ps4.c
+++ b/src/joystick/hidapi/SDL_hidapi_ps4.c
@@ -84,23 +84,24 @@ typedef struct
     Uint8 ucLeftJoystickY;
     Uint8 ucRightJoystickX;
     Uint8 ucRightJoystickY;
-    Uint8 rgucButtonsHatAndCounter[ 3 ];
+    Uint8 rgucButtonsHatAndCounter[3];
     Uint8 ucTriggerLeft;
     Uint8 ucTriggerRight;
-    Uint8 _rgucPad0[ 3 ];
+    Uint8 rgucTimestamp[2];
+    Uint8 _rgucPad0[1];
     Uint8 rgucGyroX[2];
     Uint8 rgucGyroY[2];
     Uint8 rgucGyroZ[2];
     Uint8 rgucAccelX[2];
     Uint8 rgucAccelY[2];
     Uint8 rgucAccelZ[2];
-    Uint8 _rgucPad1[ 5 ];
+    Uint8 _rgucPad1[5];
     Uint8 ucBatteryLevel;
-    Uint8 _rgucPad2[ 4 ];
+    Uint8 _rgucPad2[4];
     Uint8 ucTouchpadCounter1;
-    Uint8 rgucTouchpadData1[ 3 ];
+    Uint8 rgucTouchpadData1[3];
     Uint8 ucTouchpadCounter2;
-    Uint8 rgucTouchpadData2[ 3 ];
+    Uint8 rgucTouchpadData2[3];
 } PS4StatePacket_t;
 
 typedef struct
@@ -147,6 +148,8 @@ typedef struct {
     Uint8 led_red;
     Uint8 led_green;
     Uint8 led_blue;
+    Uint16 last_timestamp;
+    Uint64 timestamp;
     PS4StatePacket_t last_state;
 } SDL_DriverPS4_Context;
 
@@ -807,6 +810,7 @@ HIDAPI_DriverPS4_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joysti
         HIDAPI_DriverPS4_LoadCalibrationData(device);
     }
     ctx->report_sensors = enabled;
+    ctx->timestamp = 0;
 
     return 0;
 }
@@ -944,17 +948,38 @@ HIDAPI_DriverPS4_HandleStatePacket(SDL_Joystick *joystick, SDL_hid_device *dev,
     }
 
     if (ctx->report_sensors) {
+        Uint16 timestamp;
+        Uint64 timestamp_us;
         float data[3];
 
+        timestamp = LOAD16(packet->rgucTimestamp[0], packet->rgucTimestamp[1]);
+        if (ctx->timestamp) {
+            Uint16 delta;
+
+            if (ctx->last_timestamp > timestamp) {
+                delta = (SDL_MAX_UINT16 - ctx->last_timestamp + timestamp + 1);
+            } else {
+                delta = (timestamp - ctx->last_timestamp);
+            }
+            ctx->timestamp += delta;
+        } else {
+            ctx->timestamp = timestamp;
+        }
+        ctx->last_timestamp = timestamp;
+
+        /* Sensor timestamp is in 5.33us units */
+        timestamp_us = (ctx->timestamp * 16) / 3;
+
+
         data[0] = HIDAPI_DriverPS4_ApplyCalibrationData(ctx, 0, LOAD16(packet->rgucGyroX[0], packet->rgucGyroX[1]));
         data[1] = HIDAPI_DriverPS4_ApplyCalibrationData(ctx, 1, LOAD16(packet->rgucGyroY[0], packet->rgucGyroY[1]));
         data[2] = HIDAPI_DriverPS4_ApplyCalibrationData(ctx, 2, LOAD16(packet->rgucGyroZ[0], packet->rgucGyroZ[1]));
-        SDL_PrivateJoystickSensor(joystick, SDL_SENSOR_GYRO, data, 3);
+        SDL_PrivateJoystickSensor(joystick, SDL_SENSOR_GYRO, timestamp_us, data, 3);
 
         data[0] = HIDAPI_DriverPS4_ApplyCalibrationData(ctx, 3, LOAD16(packet->rgucAccelX[0], packet->rgucAccelX[1]));
         data[1] = HIDAPI_DriverPS4_ApplyCalibrationData(ctx, 4, LOAD16(packet->rgucAccelY[0], packet->rgucAccelY[1]));
         data[2] = HIDAPI_DriverPS4_ApplyCalibrationData(ctx, 5, LOAD16(packet->rgucAccelZ[0], packet->rgucAccelZ[1]));
-        SDL_PrivateJoystickSensor(joystick, SDL_SENSOR_ACCEL, data, 3);
+        SDL_PrivateJoystickSensor(joystick, SDL_SENSOR_ACCEL, timestamp_us, data, 3);
     }
 
     SDL_memcpy(&ctx->last_state, packet, sizeof(ctx->last_state));
diff --git a/src/joystick/hidapi/SDL_hidapi_ps5.c b/src/joystick/hidapi/SDL_hidapi_ps5.c
index 3892764a1df0..0e41d2e5aa83 100644
--- a/src/joystick/hidapi/SDL_hidapi_ps5.c
+++ b/src/joystick/hidapi/SDL_hidapi_ps5.c
@@ -94,6 +94,7 @@ typedef struct
     Uint8 rgucAccelX[2];                /* 21 */
     Uint8 rgucAccelY[2];                /* 23 */
     Uint8 rgucAccelZ[2];                /* 25 */
+    Uint8 rgucSensorTimestamp[4];       /* 27 - 32 bit little endian */
 
 } PS5StatePacketCommon_t;
 
@@ -114,8 +115,8 @@ typedef struct
     Uint8 rgucAccelX[2];                /* 21 */
     Uint8 rgucAccelY[2];                /* 23 */
     Uint8 rgucAccelZ[2];                /* 25 */
-    Uint8 rgucTimer1[4];                /* 27 - 32 bit little endian */
-    Uint8 ucBatteryTemp;                /* 31 */
+    Uint8 rgucSensorTimestamp[4];       /* 27 - 32 bit little endian */
+    Uint8 ucSensorTemp;                 /* 31 */
     Uint8 ucTouchpadCounter1;           /* 32 - high bit clear + counter */
     Uint8 rgucTouchpadData1[3];         /* 33 - X/Y, 12 bits per axis */
     Uint8 ucTouchpadCounter2;           /* 36 - high bit clear + counter */
@@ -145,7 +146,7 @@ typedef struct
     Uint8 rgucAccelX[2];                /* 21 */
     Uint8 rgucAccelY[2];                /* 23 */
     Uint8 rgucAccelZ[2];                /* 25 */
-    Uint8 rgucUnknown1[4];              /* 27 */
+    Uint8 rgucSensorTimestamp[4];       /* 27 - 32 bit little endian */
     Uint8 ucTouchpadCounter1;           /* 31 - high bit clear + counter */
     Uint8 rgucTouchpadData1[3];         /* 32 - X/Y, 12 bits per axis */
     Uint8 ucTouchpadCounter2;           /* 35 - high bit clear + counter */
@@ -225,6 +226,8 @@ typedef struct {
     Uint8 led_green;
     Uint8 led_blue;
     EDS5LEDResetState led_reset_state;
+    Uint32 last_timestamp;
+    Uint64 timestamp;
     union
     {
         PS5SimpleStatePacket_t simple;
@@ -706,21 +709,21 @@ HIDAPI_DriverPS5_CheckPendingLEDReset(SDL_HIDAPI_Device *device)
     SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context;
     SDL_bool led_reset_complete = SDL_FALSE;
 
-    if (ctx->use_alternate_report) {
-        /* We don't know how to check the timer, just assume it's complete for now */
-        led_reset_complete = SDL_TRUE;
-    } else {
-        const PS5StatePacket_t *packet = &ctx->last_state.full_state;
+    if (ctx->sensors_supported) {
+        const PS5StatePacketCommon_t *packet = &ctx->last_state.state;
 
         /* Check the timer to make sure the Bluetooth connection LED animation is complete */
         const Uint32 connection_complete = 10200000;
-        Uint32 timer = ((Uint32)packet->rgucTimer1[0] <<  0) |
-                       ((Uint32)packet->rgucTimer1[1] <<  8) |
-                       ((Uint32)packet->rgucTimer1[2] << 16) |
-                       ((Uint32)packet->rgucTimer1[3] << 24);
-        if (SDL_TICKS_PASSED(timer, connection_complete)) {
+        Uint32 timestamp = LOAD32(packet->rgucSensorTimestamp[0],
+                                  packet->rgucSensorTimestamp[1],
+                                  packet->rgucSensorTimestamp[2],
+                                  packet->rgucSensorTimestamp[3]);
+        if (SDL_TICKS_PASSED(timestamp, connection_complete)) {
             led_reset_complete = SDL_TRUE;
         }
+    } else {
+        /* We don't know how to check the timer, just assume it's complete for now */
+        led_reset_complete = SDL_TRUE;
     }
 
     if (led_reset_complete) {
@@ -759,8 +762,14 @@ HIDAPI_DriverPS5_SetEnhancedMode(SDL_HIDAPI_Device *device, SDL_Joystick *joysti
             ctx->report_touchpad = SDL_TRUE;
         }
         if (ctx->sensors_supported) {
-            SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, 250.0f);
-            SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 250.0f);
+            if (device->is_bluetooth) {
+                /* Bluetooth sensor update rate appears to be 1000 Hz */
+                SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, 1000.0f);
+                SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 1000.0f);
+            } else {
+                SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, 250.0f);
+                SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 250.0f);
+            }
         }
 
         /* Switch into enhanced report mode */
@@ -989,6 +998,7 @@ HIDAPI_DriverPS5_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joysti
         HIDAPI_DriverPS5_LoadCalibrationData(device);
     }
     ctx->report_sensors = enabled;
+    ctx->timestamp = 0;
 
     return 0;
 }
@@ -1180,17 +1190,40 @@ HIDAPI_DriverPS5_HandleStatePacketCommon(SDL_Joystick *joystick, SDL_hid_device
     SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_RIGHTY, axis);
 
     if (ctx->report_sensors) {
+        Uint32 timestamp;
+        Uint64 timestamp_us;
         float data[3];
 
+        timestamp = LOAD32(packet->rgucSensorTimestamp[0],
+                           packet->rgucSensorTimestamp[1],
+                           packet->rgucSensorTimestamp[2],
+                           packet->rgucSensorTimestamp[3]);
+        if (ctx->timestamp) {
+            Uint32 delta;
+
+            if (ctx->last_timestamp > timestamp) {
+                delta = (SDL_MAX_UINT32 - ctx->last_timestamp + timestamp + 1);
+            } else {
+                delta = (timestamp - ctx->last_timestamp);
+            }
+            ctx->timestamp += delta;
+        } else {
+            ctx->timestamp = timestamp;
+        }
+        ctx->last_timestamp = timestamp;
+
+        /* Sensor timestamp is in 0.33us units */
+        timestamp_us = ctx->timestamp / 3;
+
         data[0] = HIDAPI_DriverPS5_ApplyCalibrationData(ctx, 0, LOAD16(packet->rgucGyroX[0], packet->rgucGyroX[1]));
         data[1] = HIDAPI_DriverPS5_ApplyCalibrationData(ctx, 1, LOAD16(packet->rgucGyroY[0], packet->rgucGyroY[1]));
         data[2] = HIDAPI_DriverPS5_ApplyCalibrationData(ctx, 2, LOAD16(packet->rgucGyroZ[0], packet->rgucGyroZ[1]));
-        SDL_PrivateJoystickSensor(joystick, SDL_SENSOR_GYRO, data, 3);
+        SDL_PrivateJoystickSensor(joystick, SDL_SENSOR_GYRO, timestamp_us, data, 3);
 
         data[0] = HIDAPI_DriverPS5_ApplyCalibrationData(ctx, 3, LOAD16(packet->rgucAccelX[0], packet->rgucAccelX[1]));
         data[1] = HIDAPI_DriverPS5_ApplyCalibrationData(ctx, 4, LOAD16(packet->rgucAccelY[0], packet->rgucAccelY[1]));
         data[2] = HIDAPI_DriverPS5_ApplyCalibrationData(ctx, 5, LOAD16(packet->rgucAccelZ[0], packet->rgucAccelZ[1]));
-        SDL_PrivateJoystickSensor(joystick, SDL_SENSOR_ACCEL, data, 3);
+        SDL_PrivateJoystickSensor(joystick, SDL_SENSOR_ACCEL, timestamp_us, data, 3);
     }
 }
 
diff --git a/src/joystick/hidapi/SDL_hidapi_steam.c b/src/joystick/hidapi/SDL_hidapi_steam.c
index 6df6512b3573..9f4bb72ba010 100644
--- a/src/joystick/hidapi/SDL_hidapi_steam.c
+++ b/src/joystick/hidapi/SDL_hidapi_steam.c
@@ -1241,12 +1241,12 @@ HIDAPI_DriverSteam_UpdateDevice(SDL_HIDAPI_Device *device)
                 values[0] = (ctx->m_state.sGyroX / 32768.0f) * (2000.0f * (M_PI / 180.0f));
                 values[1] = (ctx->m_state.sGyroZ / 32768.0f) * (2000.0f * (M_PI / 180.0f));
                 values[2] = (ctx->m_state.sGyroY / 32768.0f) * (2000.0f * (M_PI / 180.0f));
-                SDL_PrivateJoystickSensor(joystick, SDL_SENSOR_GYRO, values, 3);
+                SDL_PrivateJoystickSensor(joystick, SDL_SENSOR_GYRO, 0, values, 3);
 
                 values[0] = (ctx->m_state.sAccelX / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY;
                 values[1] = (ctx->m_state.sAccelZ / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY;
                 values[2] = (-ctx->m_state.sAccelY / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY;
-                SDL_PrivateJoystickSensor(joystick, SDL_SENSOR_ACCEL, values, 3);
+                SDL_PrivateJoystickSensor(joystick, SDL_SENSOR_ACCEL, 0, values, 3);
             }
 
             ctx->m_last_state = ctx->m_state;
diff --git a/src/joystick/hidapi/SDL_hidapi_switch.c b/src/joystick/hidapi/SDL_hidapi_switch.c
index 8a7c9f0cb7fb..55641d7e2c15 100644
--- a/src/joystick/hidapi/SDL_hidapi_switch.c
+++ b/src/joystick/hidapi/SDL_hidapi_switch.c
@@ -1797,7 +1797,7 @@ static void SendSensorUpdate(SDL_Joystick *joystick, SDL_DriverSwitch_Context *c
         data[0] = -tmp;
     }
 
-    SDL_PrivateJoystickSensor(joystick, type, data, 3);
+    SDL_PrivateJoystickSensor(joystick, type, 0, data, 3);
 }
 
 static void HandleCombinedControllerStateL(SDL_Joystick *joystick, SDL_DriverSwitch_Context *ctx, SwitchStatePacket_t *packet)
diff --git a/src/joystick/hidapi/SDL_hidapi_wii.c b/src/joystick/hidapi/SDL_hidapi_wii.c
index fbf8a83c480b..3f40fcc44898 100644
--- a/src/joystick/hidapi/SDL_hidapi_wii.c
+++ b/src/joystick/hidapi/SDL_hidapi_wii.c
@@ -1151,7 +1151,7 @@ static void HandleNunchuckButtonData(SDL_DriverWii_Context *ctx, SDL_Joystick *j
         values[0] = -((float)x / ACCEL_RES_PER_G) * SDL_STANDARD_GRAVITY;
         values[1] = ((float)z / ACCEL_RES_PER_G) * SDL_STANDARD_GRAVITY;
         values[2] = ((float)y / ACCEL_RES_PER_G) * SDL_STANDARD_GRAVITY;
-        SDL_PrivateJoystickSensor(joystick, SDL_SENSOR_ACCEL_L, values, 3);
+        SDL_PrivateJoystickSensor(joystick, SDL_SENSOR_ACCEL_L, 0, values, 3);
     }
 }
 
@@ -1191,7 +1191,7 @@ static void HandleMotionPlusData(SDL_DriverWii_Context *ctx, SDL_Joystick *joyst
         values[0] = -((float)z / GYRO_RES_PER_DEGREE) * (float)M_PI / 180.0f;
         values[1] = ((float)x / GYRO_RES_PER_DEGREE) * (float)M_PI / 180.0f;
         values[2] = ((float)y / GYRO_RES_PER_DEGREE) * (float)M_PI / 180.0f;
-        SDL_PrivateJoystickSensor(joystick, SDL_SENSOR_GYRO, values, 3);
+        SDL_PrivateJoystickSensor(joystick, SDL_SENSOR_GYRO, 0, values, 3);
     }
 }
 
@@ -1212,7 +1212,7 @@ static void HandleWiiRemoteAccelData(SDL_DriverWii_Context *ctx, SDL_Joystick *j
     values[0] = -((float)x / ACCEL_RES_PER_G) * SDL_STANDARD_GRAVITY;
     values[1] = ((float)z / ACCEL_RES_PER_G) * SDL_STANDARD_GRAVITY;
     values[2] = ((float)y / ACCEL_RES_PER_G) * SDL_STANDARD_GRAVITY;
-    SDL_PrivateJoystickSensor(joystick, SDL_SENSOR_ACCEL, values, 3);
+    SDL_PrivateJoystickSensor(joystick, SDL_SENSOR_ACCEL, 0, values, 3);
 }
 
 static void HandleButtonData(SDL_DriverWii_Context *ctx, SDL_Joystick *joystick, WiiButtonData *data)
diff --git a/src/joystick/iphoneos/SDL_mfijoystick.m b/src/joystick/iphoneos/SDL_mfijoystick.m
index dbd1d8c09d04..72f2ffb63804 100644
--- a/src/joystick/iphoneos/SDL_mfijoystick.m
+++ b/src/joystick/iphoneos/SDL_mfijoystick.m
@@ -1064,14 +1064,14 @@ @interface GCMicroGamepad (SDL)
                         data[0] = rate.x;
                         data[1] = rate.z;
                         data[2] = -rate.y;
-                        SDL_PrivateJoystickSensor(joystick, SDL_SENSOR_GYRO, data, 3);
+                        SDL_PrivateJoystickSensor(joystick, SDL_SENSOR_GYRO, 0, data, 3);
                     }
                     if (motion.hasGravityAndUserAcceleration) {
                         GCAcceleration accel = motion.acceleration;
                         data[0] = -accel.x * SDL_STANDARD_GRAVITY;
                         data[1] = -accel.y * SDL_STANDARD_GRAVITY;
                         data[2] = -accel.z * SDL_STANDARD_GRAVITY;
-                        SDL_PrivateJoystickSensor(joystick, SDL_SENSOR_ACCEL, data, 3);
+                        SDL_PrivateJoystickSensor(joystick, SDL_SENSOR_ACCEL, 0, data, 3);
                     }
                 }
             }
diff --git a/src/sensor/SDL_sensor.c b/src/sensor/SDL_sensor.c
index 161026bd2bd8..ba86528e4bc2 100644
--- a/src/sensor/SDL_sensor.c
+++ b/src/sensor/SDL_sensor.c
@@ -307,7 +307,7 @@ SDL_SensorFromInstanceID(SDL_SensorID instance_id)
  * Checks to make sure the sensor is valid.
  */
 static int
-SDL_PrivateSensorValid(SDL_Sensor * sensor)
+SDL_PrivateSensorValid(SDL_Sensor *sensor)
 {
     int valid;
 
@@ -325,7 +325,7 @@ SDL_PrivateSensorValid(SDL_Sensor * sensor)
  * Get the friendly name of this sensor
  */
 const char *
-SDL_SensorGetName(SDL_Sensor * sensor)
+SDL_SensorGetName(SDL_Sensor *sensor)
 {
     if (!SDL_PrivateSensorValid(sensor)) {
         return NULL;
@@ -338,7 +338,7 @@ SDL_SensorGetName(SDL_Sensor * sensor)
  * Get the type of this sensor
  */
 SDL_SensorType
-SDL_SensorGetType(SDL_Sensor * sensor)
+SDL_SensorGetType(SDL_Sensor *sensor)
 {
     if (!SDL_PrivateSensorValid(sensor)) {
         return SDL_SENSOR_INVALID;
@@ -351,7 +351,7 @@ SDL_SensorGetType(SDL_Sensor * sensor)
  * Get the platform dependent type of this sensor
  */
 int
-SDL_SensorGetNonPortableType(SDL_Sensor * sensor)
+SDL_SensorGetNonPortableType(SDL_Sensor *sensor)
 {
     if (!SDL_PrivateSensorValid(sensor)) {
         return -1;
@@ -364,7 +364,7 @@ SDL_SensorGetNonPortableType(SDL_Sensor * sensor)
  * Get the instance id for this opened sensor
  */
 SDL_SensorID
-SDL_SensorGetInstanceID(SDL_Sensor * sensor)
+SDL_SensorGetInstanceID(SDL_Sensor *sensor)
 {
     if (!SDL_PrivateSensorValid(sensor)) {
         return -1;
@@ -377,7 +377,16 @@ SDL_SensorGetInstanceID(SDL_Sensor * sensor)
  * Get the current state of this sensor
  */
 int
-SDL_SensorGetData(SDL_Sensor * sensor, float *data, int num_values)
+SDL_SensorGetData(SDL_Sensor *sensor, float *data, int num_values)
+{
+    return SDL_SensorGetDataWithTimestamp(sensor, NULL, data, num_values);
+}
+
+/*
+ * Get the current state of this sensor
+ */
+int
+SDL_SensorGetDataWithTimestamp(SDL_Sensor *sensor, Uint64 *timestamp, float *data, int num_values)
 {
     if (!SDL_PrivateSensorValid(sensor)) {
         return -1;
@@ -385,6 +394,9 @@ SDL_SensorGetData(SDL_Sensor * sensor, float *data, int num_values)
 
     num_values = SDL_min(num_values, SDL_arraysize(sensor->data));
     SDL_memcpy(data, sensor->data, num_values*sizeof(*data));
+    if (timestamp) {
+        *timestamp = sensor->timestamp_us;
+    }
     return 0;
 }
 
@@ -392,7 +404,7 @@ SDL_SensorGetData(SDL_Sensor * sensor, float *data, int num_values)
  * Close a sensor previously opened with SDL_SensorOpen()
  */
 void
-SDL_SensorClose(SDL_Sensor * sensor)
+SDL_SensorClose(SDL_Sensor *sensor)
 {
     SDL_Sensor *sensorlist;
     SDL_Sensor *sensorlistprev;
@@ -478,7 +490,7 @@ SDL_SensorQuit(void)
 /* These are global for SDL_syssensor.c and SDL_events.c */
 
 int
-SDL_PrivateSensorUpdate(SDL_Sensor *sensor, float *data, int num_values)
+SDL_PrivateSensorUpdate(SDL_Sensor *sensor, Uint64 timestamp_us, float *data, int num_values)
 {
     int posted;
 
@@ -487,6 +499,7 @@ SDL_PrivateSensorUpdate(SDL_Sensor *sensor, float *data, int num_values)
     /* Update internal sensor state */
     num_values = SDL_min(num_values, SDL_arraysize(sensor->data));
     SDL_memcpy(sensor->data, data, num_values*sizeof(*data));
+    sensor->timestamp_us = timestamp_us;
 
     /* Post the event, if desired */
     posted = 0;
@@ -498,6 +511,7 @@ SDL_PrivateSensorUpdate(SDL_Sensor *sensor, float *data, int num_values)
         num_values = SDL_min(num_values, SDL_arraysize(event.sensor.data));
         SDL_memset(event.sensor.data, 0, sizeof(event.sensor.data));
         SDL_memcpy(event.sensor.data, data, num_values*sizeof(*data));
+        event.sensor.timestamp_us = timestamp_us;
         p

(Patch may be truncated, please check the link at the top of this post.)