HID: playstation: add DualShock4 accelerometer and gyroscope support.
authorRoderick Colenbrander <roderick@gaikai.com>
Sat, 29 Oct 2022 18:48:43 +0000 (11:48 -0700)
committerJiri Kosina <jkosina@suse.cz>
Fri, 11 Nov 2022 10:07:09 +0000 (11:07 +0100)
Support accelerometer and gyroscope as separate input devices similar
how DualSense and hid-sony do it.

Signed-off-by: Roderick Colenbrander <roderick.colenbrander@sony.com>
Signed-off-by: Jiri Kosina <jkosina@suse.cz>
drivers/hid/hid-playstation.c

index 63fc3a6..03f33de 100644 (file)
@@ -286,6 +286,8 @@ struct dualsense_output_report {
 #define DS4_INPUT_REPORT_USB                   0x01
 #define DS4_INPUT_REPORT_USB_SIZE              64
 
+#define DS4_FEATURE_REPORT_CALIBRATION         0x02
+#define DS4_FEATURE_REPORT_CALIBRATION_SIZE    37
 #define DS4_FEATURE_REPORT_FIRMWARE_INFO       0xa3
 #define DS4_FEATURE_REPORT_FIRMWARE_INFO_SIZE  49
 #define DS4_FEATURE_REPORT_PAIRING_INFO                0x12
@@ -305,13 +307,27 @@ struct dualsense_output_report {
 #define DS4_BATTERY_STATUS_FULL                11
 
 /* DualShock4 hardware limits */
+#define DS4_ACC_RES_PER_G      8192
+#define DS4_ACC_RANGE          (4*DS_ACC_RES_PER_G)
+#define DS4_GYRO_RES_PER_DEG_S 1024
+#define DS4_GYRO_RANGE         (2048*DS_GYRO_RES_PER_DEG_S)
 #define DS4_TOUCHPAD_WIDTH     1920
 #define DS4_TOUCHPAD_HEIGHT    942
 
 struct dualshock4 {
        struct ps_device base;
        struct input_dev *gamepad;
+       struct input_dev *sensors;
        struct input_dev *touchpad;
+
+       /* Calibration data for accelerometer and gyroscope. */
+       struct ps_calibration_data accel_calib_data[3];
+       struct ps_calibration_data gyro_calib_data[3];
+
+       /* Timestamp for sensor data */
+       bool sensor_timestamp_initialized;
+       uint32_t prev_sensor_timestamp;
+       uint32_t sensor_timestamp_us;
 };
 
 struct dualshock4_touch_point {
@@ -334,9 +350,16 @@ struct dualshock4_input_report_common {
        uint8_t rx, ry;
        uint8_t buttons[3];
        uint8_t z, rz;
-       uint8_t reserved[20];
+
+       /* Motion sensors */
+       __le16 sensor_timestamp;
+       uint8_t sensor_temperature;
+       __le16 gyro[3]; /* x, y, z */
+       __le16 accel[3]; /* x, y, z */
+       uint8_t reserved2[5];
+
        uint8_t status[2];
-       uint8_t reserved2;
+       uint8_t reserved3;
 } __packed;
 static_assert(sizeof(struct dualshock4_input_report_common) == 32);
 
@@ -1531,6 +1554,97 @@ err:
        return ERR_PTR(ret);
 }
 
+static int dualshock4_get_calibration_data(struct dualshock4 *ds4)
+{
+       struct hid_device *hdev = ds4->base.hdev;
+       short gyro_pitch_bias, gyro_pitch_plus, gyro_pitch_minus;
+       short gyro_yaw_bias, gyro_yaw_plus, gyro_yaw_minus;
+       short gyro_roll_bias, gyro_roll_plus, gyro_roll_minus;
+       short gyro_speed_plus, gyro_speed_minus;
+       short acc_x_plus, acc_x_minus;
+       short acc_y_plus, acc_y_minus;
+       short acc_z_plus, acc_z_minus;
+       int speed_2x;
+       int range_2g;
+       int ret = 0;
+       uint8_t *buf;
+
+       buf = kzalloc(DS4_FEATURE_REPORT_CALIBRATION_SIZE, GFP_KERNEL);
+       if (!buf)
+               return -ENOMEM;
+
+       ret = ps_get_report(hdev, DS4_FEATURE_REPORT_CALIBRATION, buf,
+                       DS4_FEATURE_REPORT_CALIBRATION_SIZE);
+       if (ret) {
+               hid_err(hdev, "Failed to retrieve DualShock4 calibration info: %d\n", ret);
+               goto err_free;
+       }
+
+       gyro_pitch_bias  = get_unaligned_le16(&buf[1]);
+       gyro_yaw_bias    = get_unaligned_le16(&buf[3]);
+       gyro_roll_bias   = get_unaligned_le16(&buf[5]);
+       gyro_pitch_plus  = get_unaligned_le16(&buf[7]);
+       gyro_pitch_minus = get_unaligned_le16(&buf[9]);
+       gyro_yaw_plus    = get_unaligned_le16(&buf[11]);
+       gyro_yaw_minus   = get_unaligned_le16(&buf[13]);
+       gyro_roll_plus   = get_unaligned_le16(&buf[15]);
+       gyro_roll_minus  = get_unaligned_le16(&buf[17]);
+       gyro_speed_plus  = get_unaligned_le16(&buf[19]);
+       gyro_speed_minus = get_unaligned_le16(&buf[21]);
+       acc_x_plus       = get_unaligned_le16(&buf[23]);
+       acc_x_minus      = get_unaligned_le16(&buf[25]);
+       acc_y_plus       = get_unaligned_le16(&buf[27]);
+       acc_y_minus      = get_unaligned_le16(&buf[29]);
+       acc_z_plus       = get_unaligned_le16(&buf[31]);
+       acc_z_minus      = get_unaligned_le16(&buf[33]);
+
+       /*
+        * Set gyroscope calibration and normalization parameters.
+        * Data values will be normalized to 1/DS4_GYRO_RES_PER_DEG_S degree/s.
+        */
+       speed_2x = (gyro_speed_plus + gyro_speed_minus);
+       ds4->gyro_calib_data[0].abs_code = ABS_RX;
+       ds4->gyro_calib_data[0].bias = gyro_pitch_bias;
+       ds4->gyro_calib_data[0].sens_numer = speed_2x*DS4_GYRO_RES_PER_DEG_S;
+       ds4->gyro_calib_data[0].sens_denom = gyro_pitch_plus - gyro_pitch_minus;
+
+       ds4->gyro_calib_data[1].abs_code = ABS_RY;
+       ds4->gyro_calib_data[1].bias = gyro_yaw_bias;
+       ds4->gyro_calib_data[1].sens_numer = speed_2x*DS4_GYRO_RES_PER_DEG_S;
+       ds4->gyro_calib_data[1].sens_denom = gyro_yaw_plus - gyro_yaw_minus;
+
+       ds4->gyro_calib_data[2].abs_code = ABS_RZ;
+       ds4->gyro_calib_data[2].bias = gyro_roll_bias;
+       ds4->gyro_calib_data[2].sens_numer = speed_2x*DS4_GYRO_RES_PER_DEG_S;
+       ds4->gyro_calib_data[2].sens_denom = gyro_roll_plus - gyro_roll_minus;
+
+       /*
+        * Set accelerometer calibration and normalization parameters.
+        * Data values will be normalized to 1/DS4_ACC_RES_PER_G g.
+        */
+       range_2g = acc_x_plus - acc_x_minus;
+       ds4->accel_calib_data[0].abs_code = ABS_X;
+       ds4->accel_calib_data[0].bias = acc_x_plus - range_2g / 2;
+       ds4->accel_calib_data[0].sens_numer = 2*DS4_ACC_RES_PER_G;
+       ds4->accel_calib_data[0].sens_denom = range_2g;
+
+       range_2g = acc_y_plus - acc_y_minus;
+       ds4->accel_calib_data[1].abs_code = ABS_Y;
+       ds4->accel_calib_data[1].bias = acc_y_plus - range_2g / 2;
+       ds4->accel_calib_data[1].sens_numer = 2*DS4_ACC_RES_PER_G;
+       ds4->accel_calib_data[1].sens_denom = range_2g;
+
+       range_2g = acc_z_plus - acc_z_minus;
+       ds4->accel_calib_data[2].abs_code = ABS_Z;
+       ds4->accel_calib_data[2].bias = acc_z_plus - range_2g / 2;
+       ds4->accel_calib_data[2].sens_numer = 2*DS4_ACC_RES_PER_G;
+       ds4->accel_calib_data[2].sens_denom = range_2g;
+
+err_free:
+       kfree(buf);
+       return ret;
+}
+
 static int dualshock4_get_firmware_info(struct dualshock4 *ds4)
 {
        uint8_t *buf;
@@ -1587,6 +1701,7 @@ static int dualshock4_parse_report(struct ps_device *ps_dev, struct hid_report *
        struct dualshock4_touch_report *touch_reports;
        uint8_t battery_capacity, num_touch_reports, value;
        int battery_status, i, j;
+       uint16_t sensor_timestamp;
        unsigned long flags;
 
        /*
@@ -1634,6 +1749,44 @@ static int dualshock4_parse_report(struct ps_device *ps_dev, struct hid_report *
        input_report_key(ds4->gamepad, BTN_MODE,   ds4_report->buttons[2] & DS_BUTTONS2_PS_HOME);
        input_sync(ds4->gamepad);
 
+       /* Parse and calibrate gyroscope data. */
+       for (i = 0; i < ARRAY_SIZE(ds4_report->gyro); i++) {
+               int raw_data = (short)le16_to_cpu(ds4_report->gyro[i]);
+               int calib_data = mult_frac(ds4->gyro_calib_data[i].sens_numer,
+                                          raw_data - ds4->gyro_calib_data[i].bias,
+                                          ds4->gyro_calib_data[i].sens_denom);
+
+               input_report_abs(ds4->sensors, ds4->gyro_calib_data[i].abs_code, calib_data);
+       }
+
+       /* Parse and calibrate accelerometer data. */
+       for (i = 0; i < ARRAY_SIZE(ds4_report->accel); i++) {
+               int raw_data = (short)le16_to_cpu(ds4_report->accel[i]);
+               int calib_data = mult_frac(ds4->accel_calib_data[i].sens_numer,
+                                          raw_data - ds4->accel_calib_data[i].bias,
+                                          ds4->accel_calib_data[i].sens_denom);
+
+               input_report_abs(ds4->sensors, ds4->accel_calib_data[i].abs_code, calib_data);
+       }
+
+       /* Convert timestamp (in 5.33us unit) to timestamp_us */
+       sensor_timestamp = le16_to_cpu(ds4_report->sensor_timestamp);
+       if (!ds4->sensor_timestamp_initialized) {
+               ds4->sensor_timestamp_us = DIV_ROUND_CLOSEST(sensor_timestamp*16, 3);
+               ds4->sensor_timestamp_initialized = true;
+       } else {
+               uint16_t delta;
+
+               if (ds4->prev_sensor_timestamp > sensor_timestamp)
+                       delta = (U16_MAX - ds4->prev_sensor_timestamp + sensor_timestamp + 1);
+               else
+                       delta = sensor_timestamp - ds4->prev_sensor_timestamp;
+               ds4->sensor_timestamp_us += DIV_ROUND_CLOSEST(delta*16, 3);
+       }
+       ds4->prev_sensor_timestamp = sensor_timestamp;
+       input_event(ds4->sensors, EV_MSC, MSC_TIMESTAMP, ds4->sensor_timestamp_us);
+       input_sync(ds4->sensors);
+
        for (i = 0; i < num_touch_reports; i++) {
                struct dualshock4_touch_report *touch_report = &touch_reports[i];
 
@@ -1748,12 +1901,25 @@ static struct ps_device *dualshock4_create(struct hid_device *hdev)
        if (ret)
                return ERR_PTR(ret);
 
+       ret = dualshock4_get_calibration_data(ds4);
+       if (ret) {
+               hid_err(hdev, "Failed to get calibration data from DualShock4\n");
+               goto err;
+       }
+
        ds4->gamepad = ps_gamepad_create(hdev, NULL);
        if (IS_ERR(ds4->gamepad)) {
                ret = PTR_ERR(ds4->gamepad);
                goto err;
        }
 
+       ds4->sensors = ps_sensors_create(hdev, DS4_ACC_RANGE, DS4_ACC_RES_PER_G,
+                       DS4_GYRO_RANGE, DS4_GYRO_RES_PER_DEG_S);
+       if (IS_ERR(ds4->sensors)) {
+               ret = PTR_ERR(ds4->sensors);
+               goto err;
+       }
+
        ds4->touchpad = ps_touchpad_create(hdev, DS4_TOUCHPAD_WIDTH, DS4_TOUCHPAD_HEIGHT, 2);
        if (IS_ERR(ds4->touchpad)) {
                ret = PTR_ERR(ds4->touchpad);