in hid-nintendo.c [1017:1212]
static void joycon_parse_imu_report(struct joycon_ctlr *ctlr,
struct joycon_input_report *rep)
{
struct joycon_imu_data imu_data[3] = {0}; /* 3 reports per packet */
struct input_dev *idev = ctlr->imu_input;
unsigned int msecs = jiffies_to_msecs(jiffies);
unsigned int last_msecs = ctlr->imu_last_pkt_ms;
int i;
int value[6];
joycon_input_report_parse_imu_data(ctlr, rep, imu_data);
/*
* There are complexities surrounding how we determine the timestamps we
* associate with the samples we pass to userspace. The IMU input
* reports do not provide us with a good timestamp. There's a quickly
* incrementing 8-bit counter per input report, but it is not very
* useful for this purpose (it is not entirely clear what rate it
* increments at or if it varies based on packet push rate - more on
* the push rate below...).
*
* The reverse engineering work done on the joy-cons and pro controllers
* by the community seems to indicate the following:
* - The controller samples the IMU every 1.35ms. It then does some of
* its own processing, probably averaging the samples out.
* - Each imu input report contains 3 IMU samples, (usually 5ms apart).
* - In the standard reporting mode (which this driver uses exclusively)
* input reports are pushed from the controller as follows:
* * joy-con (bluetooth): every 15 ms
* * joy-cons (in charging grip via USB): every 15 ms
* * pro controller (USB): every 15 ms
* * pro controller (bluetooth): every 8 ms (this is the wildcard)
*
* Further complicating matters is that some bluetooth stacks are known
* to alter the controller's packet rate by hardcoding the bluetooth
* SSR for the switch controllers (android's stack currently sets the
* SSR to 11ms for both the joy-cons and pro controllers).
*
* In my own testing, I've discovered that my pro controller either
* reports IMU sample batches every 11ms or every 15ms. This rate is
* stable after connecting. It isn't 100% clear what determines this
* rate. Importantly, even when sending every 11ms, none of the samples
* are duplicates. This seems to indicate that the time deltas between
* reported samples can vary based on the input report rate.
*
* The solution employed in this driver is to keep track of the average
* time delta between IMU input reports. In testing, this value has
* proven to be stable, staying at 15ms or 11ms, though other hardware
* configurations and bluetooth stacks could potentially see other rates
* (hopefully this will become more clear as more people use the
* driver).
*
* Keeping track of the average report delta allows us to submit our
* timestamps to userspace based on that. Each report contains 3
* samples, so the IMU sampling rate should be avg_time_delta/3. We can
* also use this average to detect events where we have dropped a
* packet. The userspace timestamp for the samples will be adjusted
* accordingly to prevent unwanted behvaior.
*/
if (!ctlr->imu_first_packet_received) {
ctlr->imu_timestamp_us = 0;
ctlr->imu_delta_samples_count = 0;
ctlr->imu_delta_samples_sum = 0;
ctlr->imu_avg_delta_ms = JC_IMU_DFLT_AVG_DELTA_MS;
ctlr->imu_first_packet_received = true;
} else {
unsigned int delta = msecs - last_msecs;
unsigned int dropped_pkts;
unsigned int dropped_threshold;
/* avg imu report delta housekeeping */
ctlr->imu_delta_samples_sum += delta;
ctlr->imu_delta_samples_count++;
if (ctlr->imu_delta_samples_count >=
JC_IMU_SAMPLES_PER_DELTA_AVG) {
ctlr->imu_avg_delta_ms = ctlr->imu_delta_samples_sum /
ctlr->imu_delta_samples_count;
/* don't ever want divide by zero shenanigans */
if (ctlr->imu_avg_delta_ms == 0) {
ctlr->imu_avg_delta_ms = 1;
hid_warn(ctlr->hdev,
"calculated avg imu delta of 0\n");
}
ctlr->imu_delta_samples_count = 0;
ctlr->imu_delta_samples_sum = 0;
}
/* useful for debugging IMU sample rate */
hid_dbg(ctlr->hdev,
"imu_report: ms=%u last_ms=%u delta=%u avg_delta=%u\n",
msecs, last_msecs, delta, ctlr->imu_avg_delta_ms);
/* check if any packets have been dropped */
dropped_threshold = ctlr->imu_avg_delta_ms * 3 / 2;
dropped_pkts = (delta - min(delta, dropped_threshold)) /
ctlr->imu_avg_delta_ms;
ctlr->imu_timestamp_us += 1000 * ctlr->imu_avg_delta_ms;
if (dropped_pkts > JC_IMU_DROPPED_PKT_WARNING) {
hid_warn(ctlr->hdev,
"compensating for %u dropped IMU reports\n",
dropped_pkts);
hid_warn(ctlr->hdev,
"delta=%u avg_delta=%u\n",
delta, ctlr->imu_avg_delta_ms);
}
}
ctlr->imu_last_pkt_ms = msecs;
/* Each IMU input report contains three samples */
for (i = 0; i < 3; i++) {
input_event(idev, EV_MSC, MSC_TIMESTAMP,
ctlr->imu_timestamp_us);
/*
* These calculations (which use the controller's calibration
* settings to improve the final values) are based on those
* found in the community's reverse-engineering repo (linked at
* top of driver). For hid-nintendo, we make sure that the final
* value given to userspace is always in terms of the axis
* resolution we provided.
*
* Currently only the gyro calculations subtract the calibration
* offsets from the raw value itself. In testing, doing the same
* for the accelerometer raw values decreased accuracy.
*
* Note that the gyro values are multiplied by the
* precision-saving scaling factor to prevent large inaccuracies
* due to truncation of the resolution value which would
* otherwise occur. To prevent overflow (without resorting to 64
* bit integer math), the mult_frac macro is used.
*/
value[0] = mult_frac((JC_IMU_PREC_RANGE_SCALE *
(imu_data[i].gyro_x -
ctlr->gyro_cal.offset[0])),
ctlr->gyro_cal.scale[0],
ctlr->imu_cal_gyro_divisor[0]);
value[1] = mult_frac((JC_IMU_PREC_RANGE_SCALE *
(imu_data[i].gyro_y -
ctlr->gyro_cal.offset[1])),
ctlr->gyro_cal.scale[1],
ctlr->imu_cal_gyro_divisor[1]);
value[2] = mult_frac((JC_IMU_PREC_RANGE_SCALE *
(imu_data[i].gyro_z -
ctlr->gyro_cal.offset[2])),
ctlr->gyro_cal.scale[2],
ctlr->imu_cal_gyro_divisor[2]);
value[3] = ((s32)imu_data[i].accel_x *
ctlr->accel_cal.scale[0]) /
ctlr->imu_cal_accel_divisor[0];
value[4] = ((s32)imu_data[i].accel_y *
ctlr->accel_cal.scale[1]) /
ctlr->imu_cal_accel_divisor[1];
value[5] = ((s32)imu_data[i].accel_z *
ctlr->accel_cal.scale[2]) /
ctlr->imu_cal_accel_divisor[2];
hid_dbg(ctlr->hdev, "raw_gyro: g_x=%d g_y=%d g_z=%d\n",
imu_data[i].gyro_x, imu_data[i].gyro_y,
imu_data[i].gyro_z);
hid_dbg(ctlr->hdev, "raw_accel: a_x=%d a_y=%d a_z=%d\n",
imu_data[i].accel_x, imu_data[i].accel_y,
imu_data[i].accel_z);
/*
* The right joy-con has 2 axes negated, Y and Z. This is due to
* the orientation of the IMU in the controller. We negate those
* axes' values in order to be consistent with the left joy-con
* and the pro controller:
* X: positive is pointing toward the triggers
* Y: positive is pointing to the left
* Z: positive is pointing up (out of the buttons/sticks)
* The axes follow the right-hand rule.
*/
if (jc_type_is_joycon(ctlr) && jc_type_has_right(ctlr)) {
int j;
/* negate all but x axis */
for (j = 1; j < 6; ++j) {
if (j == 3)
continue;
value[j] *= -1;
}
}
input_report_abs(idev, ABS_RX, value[0]);
input_report_abs(idev, ABS_RY, value[1]);
input_report_abs(idev, ABS_RZ, value[2]);
input_report_abs(idev, ABS_X, value[3]);
input_report_abs(idev, ABS_Y, value[4]);
input_report_abs(idev, ABS_Z, value[5]);
input_sync(idev);
/* convert to micros and divide by 3 (3 samples per report). */
ctlr->imu_timestamp_us += ctlr->imu_avg_delta_ms * 1000 / 3;
}
}