Re: [PATCH v12 14/14] HID: nintendo: add IMU support

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi Daniel

One comment below.

"Daniel J. Ogorchock" <djogorchock@xxxxxxxxx> wrote:
> This patch adds support for the controller's IMU. The accelerometer and
> gyro data are both provided to userspace using a second input device.
> The devices can be associated using their uniq value (set to the
> controller's MAC address).
> 
> A large part of this patch's functionality was provided by Carl Mueller.
> 
> The IMU device is blacklisted from the joydev input handler.
> 
> Signed-off-by: Daniel J. Ogorchock <djogorchock@xxxxxxxxx>
> ---
>  drivers/hid/hid-nintendo.c | 503 ++++++++++++++++++++++++++++++++++++-
>  drivers/input/joydev.c     |  10 +
>  2 files changed, 501 insertions(+), 12 deletions(-)
> 
> diff --git a/drivers/hid/hid-nintendo.c b/drivers/hid/hid-nintendo.c
> index 80f33ab58a5e..9bbab3bbeaf1 100644
> --- a/drivers/hid/hid-nintendo.c
> +++ b/drivers/hid/hid-nintendo.c
> @@ -2,7 +2,7 @@
>  /*
>   * HID driver for Nintendo Switch Joy-Cons and Pro Controllers
>   *
> - * Copyright (c) 2019 Daniel J. Ogorchock <djogorchock@xxxxxxxxx>
> + * Copyright (c) 2019-2020 Daniel J. Ogorchock <djogorchock@xxxxxxxxx>
>   *
>   * The following resources/projects were referenced for this driver:
>   *   https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
> @@ -26,6 +26,7 @@
>  #include <asm/unaligned.h>
>  #include <linux/delay.h>
>  #include <linux/device.h>
> +#include <linux/kernel.h>
>  #include <linux/hid.h>
>  #include <linux/input.h>
>  #include <linux/jiffies.h>
> @@ -115,6 +116,15 @@ static const u16 JC_CAL_USR_RIGHT_DATA_ADDR	= 0x801D;
>  static const u16 JC_CAL_FCT_DATA_LEFT_ADDR	= 0x603d;
>  static const u16 JC_CAL_FCT_DATA_RIGHT_ADDR	= 0x6046;
>  
> +/* SPI storage addresses of IMU factory calibration data */
> +static const u16 JC_IMU_CAL_FCT_DATA_ADDR	= 0x6020;
> +static const u16 JC_IMU_CAL_FCT_DATA_END	= 0x6037;
> +#define JC_IMU_CAL_DATA_SIZE \
> +	(JC_IMU_CAL_FCT_DATA_END - JC_IMU_CAL_FCT_DATA_ADDR + 1)
> +/* SPI storage addresses of IMU user calibration data */
> +static const u16 JC_IMU_CAL_USR_MAGIC_ADDR	= 0x8026;
> +static const u16 JC_IMU_CAL_USR_DATA_ADDR	= 0x8028;
> +
>  /* The raw analog joystick values will be mapped in terms of this magnitude */
>  static const u16 JC_MAX_STICK_MAG		= 32767;
>  static const u16 JC_STICK_FUZZ			= 250;
> @@ -125,6 +135,47 @@ static const u16 JC_MAX_DPAD_MAG		= 1;
>  static const u16 JC_DPAD_FUZZ			/*= 0*/;
>  static const u16 JC_DPAD_FLAT			/*= 0*/;
>  
> +/* Under most circumstances IMU reports are pushed every 15ms; use as default */
> +static const u16 JC_IMU_DFLT_AVG_DELTA_MS	= 15;
> +/* How many samples to sum before calculating average IMU report delta */
> +static const u16 JC_IMU_SAMPLES_PER_DELTA_AVG	= 300;
> +/* Controls how many dropped IMU packets at once trigger a warning message */
> +static const u16 JC_IMU_DROPPED_PKT_WARNING	= 3;
> +
> +/*
> + * The controller's accelerometer has a sensor resolution of 16bits and is
> + * configured with a range of +-8000 milliGs. Therefore, the resolution can be
> + * calculated thus: (2^16-1)/(8000 * 2) = 4.096 digits per milliG
> + * Resolution per G (rather than per millliG): 4.096 * 1000 = 4096 digits per G
> + * Alternatively: 1/4096 = .0002441 Gs per digit
> + */
> +static const s32 JC_IMU_MAX_ACCEL_MAG		= 32767;
> +static const u16 JC_IMU_ACCEL_RES_PER_G		= 4096;
> +static const u16 JC_IMU_ACCEL_FUZZ		= 10;
> +static const u16 JC_IMU_ACCEL_FLAT		/*= 0*/;
> +
> +/*
> + * The controller's gyroscope has a sensor resolution of 16bits and is
> + * configured with a range of +-2000 degrees/second.
> + * Digits per dps: (2^16 -1)/(2000*2) = 16.38375
> + * dps per digit: 16.38375E-1 = .0610
> + *
> + * STMicro recommends in the datasheet to add 15% to the dps/digit. This allows
> + * the full sensitivity range to be saturated without clipping. This yields more
> + * accurate results, so it's the technique this driver uses.
> + * dps per digit (corrected): .0610 * 1.15 = .0702
> + * digits per dps (corrected): .0702E-1 = 14.247
> + *
> + * Now, 14.247 truncating to 14 loses a lot of precision, so we rescale the
> + * min/max range by 1000.
> + */
> +static const s32 JC_IMU_PREC_RANGE_SCALE	= 1000;
> +/* Note: change mag and res_per_dps if prec_range_scale is ever altered */
> +static const s32 JC_IMU_MAX_GYRO_MAG		= 32767000; /* (2^16-1)*1000 */
> +static const u16 JC_IMU_GYRO_RES_PER_DPS	= 14247; /* (14.247*1000) */
> +static const u16 JC_IMU_GYRO_FUZZ		= 10;
> +static const u16 JC_IMU_GYRO_FLAT		/*= 0*/;
> +
>  /* frequency/amplitude tables for rumble */
>  struct joycon_rumble_freq_data {
>  	u16 high;
> @@ -259,6 +310,11 @@ struct joycon_stick_cal {
>  	s32 center;
>  };
>  
> +struct joycon_imu_cal {
> +	s16 offset[3];
> +	s16 scale[3];
> +};
> +
>  /*
>   * All the controller's button values are stored in a u32.
>   * They can be accessed with bitwise ANDs.
> @@ -306,6 +362,15 @@ struct joycon_subcmd_reply {
>  	u8 data[0]; /* will be at most 35 bytes */
>  } __packed;
>  
> +struct joycon_imu_data {
> +	s16 accel_x;
> +	s16 accel_y;
> +	s16 accel_z;
> +	s16 gyro_x;
> +	s16 gyro_y;
> +	s16 gyro_z;
> +} __packed;
> +
>  struct joycon_input_report {
>  	u8 id;
>  	u8 timer;
> @@ -315,11 +380,11 @@ struct joycon_input_report {
>  	u8 right_stick[3];
>  	u8 vibrator_report;
>  
> -	/*
> -	 * If support for firmware updates, gyroscope data, and/or NFC/IR
> -	 * are added in the future, this can be swapped for a union.
> -	 */
> -	struct joycon_subcmd_reply reply;
> +	union {
> +		struct joycon_subcmd_reply subcmd_reply;
> +		/* IMU input reports contain 3 samples */
> +		u8 imu_raw_bytes[sizeof(struct joycon_imu_data) * 3];
> +	};
>  } __packed;
>  
>  #define JC_MAX_RESP_SIZE	(sizeof(struct joycon_input_report) + 35)
> @@ -360,6 +425,13 @@ struct joycon_ctlr {
>  	struct joycon_stick_cal right_stick_cal_x;
>  	struct joycon_stick_cal right_stick_cal_y;
>  
> +	struct joycon_imu_cal accel_cal;
> +	struct joycon_imu_cal gyro_cal;
> +
> +	/* prevents needlessly recalculating these divisors every sample */
> +	s32 imu_cal_accel_divisor[3];
> +	s32 imu_cal_gyro_divisor[3];
> +
>  	/* power supply data */
>  	struct power_supply *battery;
>  	struct power_supply_desc battery_desc;
> @@ -379,6 +451,16 @@ struct joycon_ctlr {
>  	u16 rumble_rl_freq;
>  	u16 rumble_rh_freq;
>  	bool rumble_zero_amp;
> +
> +	/* imu */
> +	struct input_dev *imu_input;
> +	bool imu_first_packet_received; /* helps in initiating timestamp */
> +	unsigned int imu_timestamp_us; /* timestamp we report to userspace */
> +	unsigned int imu_last_pkt_ms; /* used to calc imu report delta */
> +	/* the following are used to track the average imu report time delta */
> +	unsigned int imu_delta_samples_count;
> +	unsigned int imu_delta_samples_sum;
> +	unsigned int imu_avg_delta_ms;
>  };
>  
>  /* Helper macros for checking controller type */
> @@ -563,7 +645,7 @@ static int joycon_request_spi_flash_read(struct joycon_ctlr *ctlr,
>  	} else {
>  		report = (struct joycon_input_report *)ctlr->input_buf;
>  		/* The read data starts at the 6th byte */
> -		*reply = &report->reply.data[5];
> +		*reply = &report->subcmd_reply.data[5];
>  	}
>  	return ret;
>  }
> @@ -723,6 +805,94 @@ static int joycon_request_calibration(struct joycon_ctlr *ctlr)
>  	return 0;
>  }
>  
> +/*
> + * These divisors are calculated once rather than for each sample. They are only
> + * dependent on the IMU calibration values. They are used when processing the
> + * IMU input reports.
> + */
> +static void joycon_calc_imu_cal_divisors(struct joycon_ctlr *ctlr)
> +{
> +	int i;
> +
> +	for (i = 0; i < 3; i++) {
> +		ctlr->imu_cal_accel_divisor[i] = ctlr->accel_cal.scale[i] -
> +						ctlr->accel_cal.offset[i];
> +		ctlr->imu_cal_gyro_divisor[i] = ctlr->gyro_cal.scale[i] -
> +						ctlr->gyro_cal.offset[i];
> +	}
> +}
> +
> +static const s16 DFLT_ACCEL_OFFSET /*= 0*/;
> +static const s16 DFLT_ACCEL_SCALE = 16384;
> +static const s16 DFLT_GYRO_OFFSET /*= 0*/;
> +static const s16 DFLT_GYRO_SCALE  = 13371;
> +static int joycon_request_imu_calibration(struct joycon_ctlr *ctlr)
> +{
> +	u16 imu_cal_addr = JC_IMU_CAL_FCT_DATA_ADDR;
> +	u8 *raw_cal;
> +	int ret;
> +	int i;
> +
> +	/* check if user calibration exists */
> +	if (!joycon_check_for_cal_magic(ctlr, JC_IMU_CAL_USR_MAGIC_ADDR)) {
> +		imu_cal_addr = JC_IMU_CAL_USR_DATA_ADDR;
> +		hid_info(ctlr->hdev, "using user cal for IMU\n");
> +	} else {
> +		hid_info(ctlr->hdev, "using factory cal for IMU\n");
> +	}
> +
> +	/* request IMU calibration data */
> +	hid_dbg(ctlr->hdev, "requesting IMU cal data\n");
> +	ret = joycon_request_spi_flash_read(ctlr, imu_cal_addr,
> +					    JC_IMU_CAL_DATA_SIZE, &raw_cal);
> +	if (ret) {
> +		hid_warn(ctlr->hdev,
> +			 "Failed to read IMU cal, using defaults; ret=%d\n",
> +			 ret);
> +
> +		for (i = 0; i < 3; i++) {
> +			ctlr->accel_cal.offset[i] = DFLT_ACCEL_OFFSET;
> +			ctlr->accel_cal.scale[i] = DFLT_ACCEL_SCALE;
> +			ctlr->gyro_cal.offset[i] = DFLT_GYRO_OFFSET;
> +			ctlr->gyro_cal.scale[i] = DFLT_GYRO_SCALE;
> +		}
> +		joycon_calc_imu_cal_divisors(ctlr);
> +		return ret;
> +	}
> +
> +	/* IMU calibration parsing */
> +	for (i = 0; i < 3; i++) {
> +		int j = i * 2;
> +
> +		ctlr->accel_cal.offset[i] = get_unaligned_le16(raw_cal + j);
> +		ctlr->accel_cal.scale[i] = get_unaligned_le16(raw_cal + j + 6);
> +		ctlr->gyro_cal.offset[i] = get_unaligned_le16(raw_cal + j + 12);
> +		ctlr->gyro_cal.scale[i] = get_unaligned_le16(raw_cal + j + 18);
> +	}
> +
> +	joycon_calc_imu_cal_divisors(ctlr);
> +
> +	hid_dbg(ctlr->hdev, "IMU calibration:\n"
> +			    "a_o[0]=%d a_o[1]=%d a_o[2]=%d\n"
> +			    "a_s[0]=%d a_s[1]=%d a_s[2]=%d\n"
> +			    "g_o[0]=%d g_o[1]=%d g_o[2]=%d\n"
> +			    "g_s[0]=%d g_s[1]=%d g_s[2]=%d\n",
> +			    ctlr->accel_cal.offset[0],
> +			    ctlr->accel_cal.offset[1],
> +			    ctlr->accel_cal.offset[2],
> +			    ctlr->accel_cal.scale[0],
> +			    ctlr->accel_cal.scale[1],
> +			    ctlr->accel_cal.scale[2],
> +			    ctlr->gyro_cal.offset[0],
> +			    ctlr->gyro_cal.offset[1],
> +			    ctlr->gyro_cal.offset[2],
> +			    ctlr->gyro_cal.scale[0],
> +			    ctlr->gyro_cal.scale[1],
> +			    ctlr->gyro_cal.scale[2]);
> +
> +	return 0;
> +}
> +
>  static int joycon_set_report_mode(struct joycon_ctlr *ctlr)
>  {
>  	struct joycon_subcmd_request *req;
> @@ -749,6 +919,19 @@ static int joycon_enable_rumble(struct joycon_ctlr *ctlr, bool enable)
>  	return joycon_send_subcmd(ctlr, req, 1, HZ/4);
>  }
>  
> +static int joycon_enable_imu(struct joycon_ctlr *ctlr, bool enable)

The 'enable' parameter is always true when the function is called. Maybe
it would be better to get rid of it?


Cheers,

Silvan




[Index of Archives]     [Linux Media Devel]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Linux Wireless Networking]     [Linux Omap]

  Powered by Linux