diff mbox series

[v3,6/7] iio: imu: inv_mpu6050: add MPU925x magnetometer support

Message ID 20190916094128.30122-7-jmaneyrol@invensense.com (mailing list archive)
State New, archived
Headers show
Series add magnetometer support for MPU925x | expand

Commit Message

Jean-Baptiste Maneyrol Sept. 16, 2019, 9:42 a.m. UTC
Add support of driving MPU9250 magnetometer connected on i2c
auxiliary bus using the MPU i2c master.

Signed-off-by: Jean-Baptiste Maneyrol <jmaneyrol@invensense.com>
---
 drivers/iio/imu/inv_mpu6050/Makefile       |   2 +-
 drivers/iio/imu/inv_mpu6050/inv_mpu_core.c | 144 ++++++++-
 drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h  |   4 +
 drivers/iio/imu/inv_mpu6050/inv_mpu_magn.c | 355 +++++++++++++++++++++
 drivers/iio/imu/inv_mpu6050/inv_mpu_magn.h |  36 +++
 5 files changed, 537 insertions(+), 4 deletions(-)
 create mode 100644 drivers/iio/imu/inv_mpu6050/inv_mpu_magn.c
 create mode 100644 drivers/iio/imu/inv_mpu6050/inv_mpu_magn.h

Comments

Jonathan Cameron Oct. 5, 2019, 11:13 a.m. UTC | #1
On Mon, 16 Sep 2019 09:42:07 +0000
Jean-Baptiste Maneyrol <JManeyrol@invensense.com> wrote:

> Add support of driving MPU9250 magnetometer connected on i2c
> auxiliary bus using the MPU i2c master.
> 
> Signed-off-by: Jean-Baptiste Maneyrol <jmaneyrol@invensense.com>
Same issue of the c file not including it's own header. Fixed up.

Applied to the togreg branch of iio.git and pushed out as testing
for the autobuilders to play with it.

Thanks,

Jonathan

> ---
>  drivers/iio/imu/inv_mpu6050/Makefile       |   2 +-
>  drivers/iio/imu/inv_mpu6050/inv_mpu_core.c | 144 ++++++++-
>  drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h  |   4 +
>  drivers/iio/imu/inv_mpu6050/inv_mpu_magn.c | 355 +++++++++++++++++++++
>  drivers/iio/imu/inv_mpu6050/inv_mpu_magn.h |  36 +++
>  5 files changed, 537 insertions(+), 4 deletions(-)
>  create mode 100644 drivers/iio/imu/inv_mpu6050/inv_mpu_magn.c
>  create mode 100644 drivers/iio/imu/inv_mpu6050/inv_mpu_magn.h
> 
> diff --git a/drivers/iio/imu/inv_mpu6050/Makefile b/drivers/iio/imu/inv_mpu6050/Makefile
> index 2cfbd926522f..c103441a906b 100644
> --- a/drivers/iio/imu/inv_mpu6050/Makefile
> +++ b/drivers/iio/imu/inv_mpu6050/Makefile
> @@ -5,7 +5,7 @@
>  
>  obj-$(CONFIG_INV_MPU6050_IIO) += inv-mpu6050.o
>  inv-mpu6050-y := inv_mpu_core.o inv_mpu_ring.o inv_mpu_trigger.o \
> -		 inv_mpu_aux.o
> +		 inv_mpu_aux.o inv_mpu_magn.o
>  
>  obj-$(CONFIG_INV_MPU6050_I2C) += inv-mpu6050-i2c.o
>  inv-mpu6050-i2c-y := inv_mpu_i2c.o inv_mpu_acpi.o
> diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c b/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c
> index 7b2e4d81bbba..f1c65e0dd1a5 100644
> --- a/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c
> +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c
> @@ -17,6 +17,7 @@
>  #include <linux/platform_device.h>
>  #include <linux/regulator/consumer.h>
>  #include "inv_mpu_iio.h"
> +#include "inv_mpu_magn.h"
>  
>  /*
>   * this is the gyro scale translated from dynamic range plus/minus
> @@ -332,6 +333,11 @@ static int inv_mpu6050_init_config(struct iio_dev *indio_dev)
>  	 */
>  	st->chip_period = NSEC_PER_MSEC;
>  
> +	/* magn chip init, noop if not present in the chip */
> +	result = inv_mpu_magn_probe(st);
> +	if (result)
> +		goto error_power_off;
> +
>  	return inv_mpu6050_set_power_itg(st, false);
>  
>  error_power_off:
> @@ -411,6 +417,9 @@ static int inv_mpu6050_read_channel_data(struct iio_dev *indio_dev,
>  		ret = inv_mpu6050_sensor_show(st, st->reg->temperature,
>  					      IIO_MOD_X, val);
>  		break;
> +	case IIO_MAGN:
> +		ret = inv_mpu_magn_read(st, chan->channel2, val);
> +		break;
>  	default:
>  		ret = -EINVAL;
>  		break;
> @@ -469,6 +478,8 @@ inv_mpu6050_read_raw(struct iio_dev *indio_dev,
>  				*val2 = INV_MPU6050_TEMP_SCALE;
>  
>  			return IIO_VAL_INT_PLUS_MICRO;
> +		case IIO_MAGN:
> +			return inv_mpu_magn_get_scale(st, chan, val, val2);
>  		default:
>  			return -EINVAL;
>  		}
> @@ -710,6 +721,11 @@ inv_mpu6050_fifo_rate_store(struct device *dev, struct device_attribute *attr,
>  	if (result)
>  		goto fifo_rate_fail_power_off;
>  
> +	/* update rate for magn, noop if not present in chip */
> +	result = inv_mpu_magn_set_rate(st, fifo_rate);
> +	if (result)
> +		goto fifo_rate_fail_power_off;
> +
>  fifo_rate_fail_power_off:
>  	result |= inv_mpu6050_set_power_itg(st, false);
>  fifo_rate_fail_unlock:
> @@ -795,8 +811,14 @@ inv_get_mount_matrix(const struct iio_dev *indio_dev,
>  		     const struct iio_chan_spec *chan)
>  {
>  	struct inv_mpu6050_state *data = iio_priv(indio_dev);
> +	const struct iio_mount_matrix *matrix;
> +
> +	if (chan->type == IIO_MAGN)
> +		matrix = &data->magn_orient;
> +	else
> +		matrix = &data->orientation;
>  
> -	return &data->orientation;
> +	return matrix;
>  }
>  
>  static const struct iio_chan_spec_ext_info inv_ext_info[] = {
> @@ -864,6 +886,98 @@ static const unsigned long inv_mpu_scan_masks[] = {
>  	0,
>  };
>  
> +#define INV_MPU9X50_MAGN_CHAN(_chan2, _bits, _index)			\
> +	{								\
> +		.type = IIO_MAGN,					\
> +		.modified = 1,						\
> +		.channel2 = _chan2,					\
> +		.info_mask_separate = BIT(IIO_CHAN_INFO_SCALE) |	\
> +				      BIT(IIO_CHAN_INFO_RAW),		\
> +		.scan_index = _index,					\
> +		.scan_type = {						\
> +			.sign = 's',					\
> +			.realbits = _bits,				\
> +			.storagebits = 16,				\
> +			.shift = 0,					\

This doesn't need to be set as 0 is the 'obvious' default.

> +			.endianness = IIO_BE,				\
> +		},							\
> +		.ext_info = inv_ext_info,				\
> +	}
> +
> +static const struct iio_chan_spec inv_mpu9250_channels[] = {
> +	IIO_CHAN_SOFT_TIMESTAMP(INV_MPU9X50_SCAN_TIMESTAMP),
> +	/*
> +	 * Note that temperature should only be via polled reading only,
> +	 * not the final scan elements output.
> +	 */
> +	{
> +		.type = IIO_TEMP,
> +		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW)
> +				| BIT(IIO_CHAN_INFO_OFFSET)
> +				| BIT(IIO_CHAN_INFO_SCALE),
> +		.scan_index = -1,
> +	},
> +	INV_MPU6050_CHAN(IIO_ANGL_VEL, IIO_MOD_X, INV_MPU6050_SCAN_GYRO_X),
> +	INV_MPU6050_CHAN(IIO_ANGL_VEL, IIO_MOD_Y, INV_MPU6050_SCAN_GYRO_Y),
> +	INV_MPU6050_CHAN(IIO_ANGL_VEL, IIO_MOD_Z, INV_MPU6050_SCAN_GYRO_Z),
> +
> +	INV_MPU6050_CHAN(IIO_ACCEL, IIO_MOD_X, INV_MPU6050_SCAN_ACCL_X),
> +	INV_MPU6050_CHAN(IIO_ACCEL, IIO_MOD_Y, INV_MPU6050_SCAN_ACCL_Y),
> +	INV_MPU6050_CHAN(IIO_ACCEL, IIO_MOD_Z, INV_MPU6050_SCAN_ACCL_Z),
> +
> +	/* Magnetometer resolution is 16 bits */
> +	INV_MPU9X50_MAGN_CHAN(IIO_MOD_X, 16, INV_MPU9X50_SCAN_MAGN_X),
> +	INV_MPU9X50_MAGN_CHAN(IIO_MOD_Y, 16, INV_MPU9X50_SCAN_MAGN_Y),
> +	INV_MPU9X50_MAGN_CHAN(IIO_MOD_Z, 16, INV_MPU9X50_SCAN_MAGN_Z),
> +};
> +
> +static const unsigned long inv_mpu9x50_scan_masks[] = {
> +	/* 3-axis accel */
> +	BIT(INV_MPU6050_SCAN_ACCL_X)
> +		| BIT(INV_MPU6050_SCAN_ACCL_Y)
> +		| BIT(INV_MPU6050_SCAN_ACCL_Z),
> +	/* 3-axis gyro */
> +	BIT(INV_MPU6050_SCAN_GYRO_X)
> +		| BIT(INV_MPU6050_SCAN_GYRO_Y)
> +		| BIT(INV_MPU6050_SCAN_GYRO_Z),
> +	/* 3-axis magn */
> +	BIT(INV_MPU9X50_SCAN_MAGN_X)
> +		| BIT(INV_MPU9X50_SCAN_MAGN_Y)
> +		| BIT(INV_MPU9X50_SCAN_MAGN_Z),
> +	/* 6-axis accel + gyro */
> +	BIT(INV_MPU6050_SCAN_ACCL_X)
> +		| BIT(INV_MPU6050_SCAN_ACCL_Y)
> +		| BIT(INV_MPU6050_SCAN_ACCL_Z)
> +		| BIT(INV_MPU6050_SCAN_GYRO_X)
> +		| BIT(INV_MPU6050_SCAN_GYRO_Y)
> +		| BIT(INV_MPU6050_SCAN_GYRO_Z),
> +	/* 6-axis accel + magn */
> +	BIT(INV_MPU6050_SCAN_ACCL_X)
> +		| BIT(INV_MPU6050_SCAN_ACCL_Y)
> +		| BIT(INV_MPU6050_SCAN_ACCL_Z)
> +		| BIT(INV_MPU9X50_SCAN_MAGN_X)
> +		| BIT(INV_MPU9X50_SCAN_MAGN_Y)
> +		| BIT(INV_MPU9X50_SCAN_MAGN_Z),
> +	/* 6-axis gyro + magn */
> +	BIT(INV_MPU6050_SCAN_GYRO_X)
> +		| BIT(INV_MPU6050_SCAN_GYRO_Y)
> +		| BIT(INV_MPU6050_SCAN_GYRO_Z)
> +		| BIT(INV_MPU9X50_SCAN_MAGN_X)
> +		| BIT(INV_MPU9X50_SCAN_MAGN_Y)
> +		| BIT(INV_MPU9X50_SCAN_MAGN_Z),
> +	/* 9-axis accel + gyro + magn */
> +	BIT(INV_MPU6050_SCAN_ACCL_X)
> +		| BIT(INV_MPU6050_SCAN_ACCL_Y)
> +		| BIT(INV_MPU6050_SCAN_ACCL_Z)
> +		| BIT(INV_MPU6050_SCAN_GYRO_X)
> +		| BIT(INV_MPU6050_SCAN_GYRO_Y)
> +		| BIT(INV_MPU6050_SCAN_GYRO_Z)
> +		| BIT(INV_MPU9X50_SCAN_MAGN_X)
> +		| BIT(INV_MPU9X50_SCAN_MAGN_Y)
> +		| BIT(INV_MPU9X50_SCAN_MAGN_Z),
> +	0,
> +};
> +
>  static const struct iio_chan_spec inv_icm20602_channels[] = {
>  	IIO_CHAN_SOFT_TIMESTAMP(INV_ICM20602_SCAN_TIMESTAMP),
>  	{
> @@ -1145,6 +1259,11 @@ int inv_mpu_core_probe(struct regmap *regmap, int irq, const char *name,
>  		return result;
>  	}
>  
> +	/* fill magnetometer orientation */
> +	result = inv_mpu_magn_set_orient(st);
> +	if (result)
> +		return result;
> +
>  	/* power is turned on inside check chip type*/
>  	result = inv_check_and_setup_chip(st);
>  	if (result)
> @@ -1168,14 +1287,33 @@ int inv_mpu_core_probe(struct regmap *regmap, int irq, const char *name,
>  	if (inv_mpu_bus_setup)
>  		inv_mpu_bus_setup(indio_dev);
>  
> -	if (chip_type == INV_ICM20602) {
> +	switch (chip_type) {
> +	case INV_MPU9250:
> +	case INV_MPU9255:
> +		/*
> +		 * Use magnetometer inside the chip only if there is no i2c
> +		 * auxiliary device in use.
> +		 */
> +		if (!st->magn_disabled) {
> +			indio_dev->channels = inv_mpu9250_channels;
> +			indio_dev->num_channels = ARRAY_SIZE(inv_mpu9250_channels);
> +			indio_dev->available_scan_masks = inv_mpu9x50_scan_masks;
> +		} else {
> +			indio_dev->channels = inv_mpu_channels;
> +			indio_dev->num_channels = ARRAY_SIZE(inv_mpu_channels);
> +			indio_dev->available_scan_masks = inv_mpu_scan_masks;
> +		}
> +		break;
> +	case INV_ICM20602:
>  		indio_dev->channels = inv_icm20602_channels;
>  		indio_dev->num_channels = ARRAY_SIZE(inv_icm20602_channels);
>  		indio_dev->available_scan_masks = inv_icm20602_scan_masks;
> -	} else {
> +		break;
> +	default:
>  		indio_dev->channels = inv_mpu_channels;
>  		indio_dev->num_channels = ARRAY_SIZE(inv_mpu_channels);
>  		indio_dev->available_scan_masks = inv_mpu_scan_masks;
> +		break;
>  	}
>  
>  	indio_dev->info = &mpu_info;
> diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h b/drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h
> index 04215bc6e8ab..953f85618199 100644
> --- a/drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h
> +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h
> @@ -130,6 +130,8 @@ struct inv_mpu6050_hw {
>   *  @data_timestamp:	timestamp for next data sample.
>   *  @vddio_supply	voltage regulator for the chip.
>   *  @magn_disabled:     magnetometer disabled for backward compatibility reason.
> + *  @magn_raw_to_gauss:	coefficient to convert mag raw value to Gauss.
> + *  @magn_orient:       magnetometer sensor chip orientation if available.
>   */
>  struct inv_mpu6050_state {
>  	struct mutex lock;
> @@ -152,6 +154,8 @@ struct inv_mpu6050_state {
>  	s64 data_timestamp;
>  	struct regulator *vddio_supply;
>  	bool magn_disabled;
> +	s32 magn_raw_to_gauss[3];
> +	struct iio_mount_matrix magn_orient;
>  };
>  
>  /*register and associated bit definition*/
> diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_magn.c b/drivers/iio/imu/inv_mpu6050/inv_mpu_magn.c
> new file mode 100644
> index 000000000000..415d8acece54
> --- /dev/null
> +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_magn.c
> @@ -0,0 +1,355 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (C) 2019 TDK-InvenSense, Inc.
> + */
> +
> +#include <linux/kernel.h>
> +#include <linux/device.h>
> +#include <linux/string.h>
> +
> +#include "inv_mpu_iio.h"
> +#include "inv_mpu_aux.h"
> +
> +/*
> + * MPU9250 magnetometer is an AKM AK8963 chip on I2C aux bus
> + */
> +#define INV_MPU_MAGN_I2C_ADDR		0x0C
> +
> +#define INV_MPU_MAGN_REG_WIA		0x00
> +#define INV_MPU_MAGN_BITS_WIA		0x48
> +
> +#define INV_MPU_MAGN_REG_ST1		0x02
> +#define INV_MPU_MAGN_BIT_DRDY		0x01
> +#define INV_MPU_MAGN_BIT_DOR		0x02
> +
> +#define INV_MPU_MAGN_REG_DATA		0x03
> +
> +#define INV_MPU_MAGN_REG_ST2		0x09
> +#define INV_MPU_MAGN_BIT_HOFL		0x08
> +#define INV_MPU_MAGN_BIT_BITM		0x10
> +
> +#define INV_MPU_MAGN_REG_CNTL1		0x0A
> +#define INV_MPU_MAGN_BITS_MODE_PWDN	0x00
> +#define INV_MPU_MAGN_BITS_MODE_SINGLE	0x01
> +#define INV_MPU_MAGN_BITS_MODE_FUSE	0x0F
> +#define INV_MPU_MAGN_BIT_OUTPUT_BIT	0x10
> +
> +#define INV_MPU_MAGN_REG_CNTL2		0x0B
> +#define INV_MPU_MAGN_BIT_SRST		0x01
> +
> +#define INV_MPU_MAGN_REG_ASAX		0x10
> +#define INV_MPU_MAGN_REG_ASAY		0x11
> +#define INV_MPU_MAGN_REG_ASAZ		0x12
> +
> +/* Magnetometer maximum frequency */
> +#define INV_MPU_MAGN_FREQ_HZ_MAX	50
> +
> +static bool inv_magn_supported(const struct inv_mpu6050_state *st)
> +{
> +	switch (st->chip_type) {
> +	case INV_MPU9250:
> +	case INV_MPU9255:
> +		return true;
> +	default:
> +		return false;
> +	}
> +}
> +
> +/* init magnetometer chip */
> +static int inv_magn_init(struct inv_mpu6050_state *st)
> +{
> +	uint8_t val;
> +	uint8_t asa[3];
> +	int ret;
> +
> +	/* check whoami */
> +	ret = inv_mpu_aux_read(st, INV_MPU_MAGN_I2C_ADDR, INV_MPU_MAGN_REG_WIA,
> +			       &val, sizeof(val));
> +	if (ret)
> +		return ret;
> +	if (val != INV_MPU_MAGN_BITS_WIA)
> +		return -ENODEV;
> +
> +	/* reset chip */
> +	ret = inv_mpu_aux_write(st, INV_MPU_MAGN_I2C_ADDR,
> +				INV_MPU_MAGN_REG_CNTL2,
> +				INV_MPU_MAGN_BIT_SRST);
> +	if (ret)
> +		return ret;
> +
> +	/* read fuse ROM data */
> +	ret = inv_mpu_aux_write(st, INV_MPU_MAGN_I2C_ADDR,
> +				INV_MPU_MAGN_REG_CNTL1,
> +				INV_MPU_MAGN_BITS_MODE_FUSE);
> +	if (ret)
> +		return ret;
> +
> +	ret = inv_mpu_aux_read(st, INV_MPU_MAGN_I2C_ADDR, INV_MPU_MAGN_REG_ASAX,
> +			       asa, sizeof(asa));
> +	if (ret)
> +		return ret;
> +
> +	/* switch back to power-down */
> +	ret = inv_mpu_aux_write(st, INV_MPU_MAGN_I2C_ADDR,
> +				INV_MPU_MAGN_REG_CNTL1,
> +				INV_MPU_MAGN_BITS_MODE_PWDN);
> +	if (ret)
> +		return ret;
> +
> +	/*
> +	 * Sensitivity adjustement and scale to Gauss
> +	 *
> +	 * Hadj = H * (((ASA - 128) * 0.5 / 128) + 1)
> +	 * Factor simplification:
> +	 * Hadj = H * ((ASA + 128) / 256)
> +	 *
> +	 * Sensor sentivity
> +	 * 0.15 uT in 16 bits mode
> +	 * 1 uT = 0.01 G and value is in micron (1e6)
> +	 * sensitvity = 0.15 uT * 0.01 * 1e6
> +	 *
> +	 * raw_to_gauss = Hadj * 1500
> +	 */
> +	st->magn_raw_to_gauss[0] = (((int32_t)asa[0] + 128) * 1500) / 256;
> +	st->magn_raw_to_gauss[1] = (((int32_t)asa[1] + 128) * 1500) / 256;
> +	st->magn_raw_to_gauss[2] = (((int32_t)asa[2] + 128) * 1500) / 256;
> +
> +	return 0;
> +}
> +
> +/**
> + * inv_mpu_magn_probe() - probe and setup magnetometer chip
> + * @st: driver internal state
> + *
> + * Returns 0 on success, a negative error code otherwise
> + *
> + * It is probing the chip and setting up all needed i2c transfers.
> + * Noop if there is no magnetometer in the chip.
> + */
> +int inv_mpu_magn_probe(struct inv_mpu6050_state *st)
> +{
> +	int ret;
> +
> +	/* quit if chip is not supported */
> +	if (!inv_magn_supported(st))
> +		return 0;
> +
> +	/* configure i2c master aux port */
> +	ret = inv_mpu_aux_init(st);
> +	if (ret)
> +		return ret;
> +
> +	/* check and init mag chip */
> +	ret = inv_magn_init(st);
> +	if (ret)
> +		return ret;
> +
> +	/*
> +	 * configure mpu i2c master accesses
> +	 * i2c SLV0: read sensor data, 7 bytes data(6)-ST2
> +	 * Byte swap data to store them in big-endian in impair address groups
> +	 */
> +	ret = regmap_write(st->map, INV_MPU6050_REG_I2C_SLV_ADDR(0),
> +			   INV_MPU6050_BIT_I2C_SLV_RNW | INV_MPU_MAGN_I2C_ADDR);
> +	if (ret)
> +		return ret;
> +
> +	ret = regmap_write(st->map, INV_MPU6050_REG_I2C_SLV_REG(0),
> +			   INV_MPU_MAGN_REG_DATA);
> +	if (ret)
> +		return ret;
> +
> +	ret = regmap_write(st->map, INV_MPU6050_REG_I2C_SLV_CTRL(0),
> +			   INV_MPU6050_BIT_SLV_EN |
> +			   INV_MPU6050_BIT_SLV_BYTE_SW |
> +			   INV_MPU6050_BIT_SLV_GRP |
> +			   INV_MPU9X50_BYTES_MAGN);
> +	if (ret)
> +		return ret;
> +
> +	/* i2c SLV1: launch single measurement */
> +	ret = regmap_write(st->map, INV_MPU6050_REG_I2C_SLV_ADDR(1),
> +			   INV_MPU_MAGN_I2C_ADDR);
> +	if (ret)
> +		return ret;
> +
> +	ret = regmap_write(st->map, INV_MPU6050_REG_I2C_SLV_REG(1),
> +			   INV_MPU_MAGN_REG_CNTL1);
> +	if (ret)
> +		return ret;
> +
> +	/* add 16 bits mode */
> +	ret = regmap_write(st->map, INV_MPU6050_REG_I2C_SLV_DO(1),
> +			   INV_MPU_MAGN_BITS_MODE_SINGLE |
> +			   INV_MPU_MAGN_BIT_OUTPUT_BIT);
> +	if (ret)
> +		return ret;
> +
> +	return regmap_write(st->map, INV_MPU6050_REG_I2C_SLV_CTRL(1),
> +			    INV_MPU6050_BIT_SLV_EN | 1);
> +}
> +
> +/**
> + * inv_mpu_magn_set_rate() - set magnetometer sampling rate
> + * @st: driver internal state
> + * @fifo_rate: mpu set fifo rate
> + *
> + * Returns 0 on success, a negative error code otherwise
> + *
> + * Limit sampling frequency to the maximum value supported by the
> + * magnetometer chip. Resulting in duplicated data for higher frequencies.
> + * Noop if there is no magnetometer in the chip.
> + */
> +int inv_mpu_magn_set_rate(const struct inv_mpu6050_state *st, int fifo_rate)
> +{
> +	uint8_t d;
> +
> +	/* quit if chip is not supported */
> +	if (!inv_magn_supported(st))
> +		return 0;
> +
> +	/*
> +	 * update i2c master delay to limit mag sampling to max frequency
> +	 * compute fifo_rate divider d: rate = fifo_rate / (d + 1)
> +	 */
> +	if (fifo_rate > INV_MPU_MAGN_FREQ_HZ_MAX)
> +		d = fifo_rate / INV_MPU_MAGN_FREQ_HZ_MAX - 1;
> +	else
> +		d = 0;
> +
> +	return regmap_write(st->map, INV_MPU6050_REG_I2C_SLV4_CTRL, d);
> +}
> +
> +/**
> + * inv_mpu_magn_set_orient() - fill magnetometer mounting matrix
> + * @st: driver internal state
> + *
> + * Returns 0 on success, a negative error code otherwise
> + *
> + * Fill magnetometer mounting matrix using the provided chip matrix.
> + */
> +int inv_mpu_magn_set_orient(struct inv_mpu6050_state *st)
> +{
> +	const char *orient;
> +	char *str;
> +	int i;
> +
> +	/* fill magnetometer orientation */
> +	switch (st->chip_type) {
> +	case INV_MPU9250:
> +	case INV_MPU9255:
> +		/* x <- y */
> +		st->magn_orient.rotation[0] = st->orientation.rotation[3];
> +		st->magn_orient.rotation[1] = st->orientation.rotation[4];
> +		st->magn_orient.rotation[2] = st->orientation.rotation[5];
> +		/* y <- x */
> +		st->magn_orient.rotation[3] = st->orientation.rotation[0];
> +		st->magn_orient.rotation[4] = st->orientation.rotation[1];
> +		st->magn_orient.rotation[5] = st->orientation.rotation[2];
> +		/* z <- -z */
> +		for (i = 0; i < 3; ++i) {
> +			orient = st->orientation.rotation[6 + i];
> +			/* use length + 2 for adding minus sign if needed */
> +			str = devm_kzalloc(regmap_get_device(st->map),
> +					   strlen(orient) + 2, GFP_KERNEL);
> +			if (str == NULL)
> +				return -ENOMEM;
> +			if (strcmp(orient, "0") == 0) {
> +				strcpy(str, orient);
> +			} else if (orient[0] == '-') {
> +				strcpy(str, &orient[1]);
> +			} else {
> +				str[0] = '-';
> +				strcpy(&str[1], orient);
> +			}
> +			st->magn_orient.rotation[6 + i] = str;
> +		}
> +		break;
> +	default:
> +		st->magn_orient = st->orientation;
> +		break;
> +	}
> +
> +	return 0;
> +}
> +
> +/**
> + * inv_mpu_magn_read() - read magnetometer data
> + * @st: driver internal state
> + * @axis: IIO modifier axis value
> + * @val: store corresponding axis value
> + *
> + * Returns 0 on success, a negative error code otherwise
> + */
> +int inv_mpu_magn_read(const struct inv_mpu6050_state *st, int axis, int *val)
> +{
> +	unsigned int user_ctrl, status;
> +	__be16 data[3];
> +	uint8_t addr;
> +	uint8_t d;
> +	unsigned int period_ms;
> +	int ret;
> +
> +	/* quit if chip is not supported */
> +	if (!inv_magn_supported(st))
> +		return -ENODEV;
> +
> +	/* Mag data: X - Y - Z */
> +	switch (axis) {
> +	case IIO_MOD_X:
> +		addr = 0;
> +		break;
> +	case IIO_MOD_Y:
> +		addr = 1;
> +		break;
> +	case IIO_MOD_Z:
> +		addr = 2;
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	/* set sample rate to max mag freq */
> +	d = INV_MPU6050_FIFO_RATE_TO_DIVIDER(INV_MPU_MAGN_FREQ_HZ_MAX);
> +	ret = regmap_write(st->map, st->reg->sample_rate_div, d);
> +	if (ret)
> +		return ret;
> +
> +	/* start i2c master, wait for xfer, stop */
> +	user_ctrl = st->chip_config.user_ctrl | INV_MPU6050_BIT_I2C_MST_EN;
> +	ret = regmap_write(st->map, st->reg->user_ctrl, user_ctrl);
> +	if (ret)
> +		return ret;
> +
> +	/* need to wait 2 periods + half-period margin */
> +	period_ms = 1000 / INV_MPU_MAGN_FREQ_HZ_MAX;
> +	msleep(period_ms * 2 + period_ms / 2);
> +	user_ctrl = st->chip_config.user_ctrl;
> +	ret = regmap_write(st->map, st->reg->user_ctrl, user_ctrl);
> +	if (ret)
> +		return ret;
> +
> +	/* restore sample rate */
> +	d = st->chip_config.divider;
> +	ret = regmap_write(st->map, st->reg->sample_rate_div, d);
> +	if (ret)
> +		return ret;
> +
> +	/* check i2c status and read raw data */
> +	ret = regmap_read(st->map, INV_MPU6050_REG_I2C_MST_STATUS, &status);
> +	if (ret)
> +		return ret;
> +
> +	if (status & INV_MPU6050_BIT_I2C_SLV0_NACK ||
> +			status & INV_MPU6050_BIT_I2C_SLV1_NACK)
> +		return -EIO;
> +
> +	ret = regmap_bulk_read(st->map, INV_MPU6050_REG_EXT_SENS_DATA,
> +			       data, sizeof(data));
> +	if (ret)
> +		return ret;
> +
> +	*val = (int16_t)be16_to_cpu(data[addr]);
> +
> +	return IIO_VAL_INT;
> +}
> diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_magn.h b/drivers/iio/imu/inv_mpu6050/inv_mpu_magn.h
> new file mode 100644
> index 000000000000..b41bd0578478
> --- /dev/null
> +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_magn.h
> @@ -0,0 +1,36 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright (C) 2019 TDK-InvenSense, Inc.
> + */
> +
> +#ifndef INV_MPU_MAGN_H_
> +#define INV_MPU_MAGN_H_
> +
> +#include <linux/kernel.h>
> +
> +#include "inv_mpu_iio.h"
> +
> +int inv_mpu_magn_probe(struct inv_mpu6050_state *st);
> +
> +/**
> + * inv_mpu_magn_get_scale() - get magnetometer scale value
> + * @st: driver internal state
> + *
> + * Returns IIO data format.
> + */
> +static inline int inv_mpu_magn_get_scale(const struct inv_mpu6050_state *st,
> +					 const struct iio_chan_spec *chan,
> +					 int *val, int *val2)
> +{
> +	*val = 0;
> +	*val2 = st->magn_raw_to_gauss[chan->address];
> +	return IIO_VAL_INT_PLUS_MICRO;
> +}
> +
> +int inv_mpu_magn_set_rate(const struct inv_mpu6050_state *st, int fifo_rate);
> +
> +int inv_mpu_magn_set_orient(struct inv_mpu6050_state *st);
> +
> +int inv_mpu_magn_read(const struct inv_mpu6050_state *st, int axis, int *val);
> +
> +#endif		/* INV_MPU_MAGN_H_ */
diff mbox series

Patch

diff --git a/drivers/iio/imu/inv_mpu6050/Makefile b/drivers/iio/imu/inv_mpu6050/Makefile
index 2cfbd926522f..c103441a906b 100644
--- a/drivers/iio/imu/inv_mpu6050/Makefile
+++ b/drivers/iio/imu/inv_mpu6050/Makefile
@@ -5,7 +5,7 @@ 
 
 obj-$(CONFIG_INV_MPU6050_IIO) += inv-mpu6050.o
 inv-mpu6050-y := inv_mpu_core.o inv_mpu_ring.o inv_mpu_trigger.o \
-		 inv_mpu_aux.o
+		 inv_mpu_aux.o inv_mpu_magn.o
 
 obj-$(CONFIG_INV_MPU6050_I2C) += inv-mpu6050-i2c.o
 inv-mpu6050-i2c-y := inv_mpu_i2c.o inv_mpu_acpi.o
diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c b/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c
index 7b2e4d81bbba..f1c65e0dd1a5 100644
--- a/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c
+++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c
@@ -17,6 +17,7 @@ 
 #include <linux/platform_device.h>
 #include <linux/regulator/consumer.h>
 #include "inv_mpu_iio.h"
+#include "inv_mpu_magn.h"
 
 /*
  * this is the gyro scale translated from dynamic range plus/minus
@@ -332,6 +333,11 @@  static int inv_mpu6050_init_config(struct iio_dev *indio_dev)
 	 */
 	st->chip_period = NSEC_PER_MSEC;
 
+	/* magn chip init, noop if not present in the chip */
+	result = inv_mpu_magn_probe(st);
+	if (result)
+		goto error_power_off;
+
 	return inv_mpu6050_set_power_itg(st, false);
 
 error_power_off:
@@ -411,6 +417,9 @@  static int inv_mpu6050_read_channel_data(struct iio_dev *indio_dev,
 		ret = inv_mpu6050_sensor_show(st, st->reg->temperature,
 					      IIO_MOD_X, val);
 		break;
+	case IIO_MAGN:
+		ret = inv_mpu_magn_read(st, chan->channel2, val);
+		break;
 	default:
 		ret = -EINVAL;
 		break;
@@ -469,6 +478,8 @@  inv_mpu6050_read_raw(struct iio_dev *indio_dev,
 				*val2 = INV_MPU6050_TEMP_SCALE;
 
 			return IIO_VAL_INT_PLUS_MICRO;
+		case IIO_MAGN:
+			return inv_mpu_magn_get_scale(st, chan, val, val2);
 		default:
 			return -EINVAL;
 		}
@@ -710,6 +721,11 @@  inv_mpu6050_fifo_rate_store(struct device *dev, struct device_attribute *attr,
 	if (result)
 		goto fifo_rate_fail_power_off;
 
+	/* update rate for magn, noop if not present in chip */
+	result = inv_mpu_magn_set_rate(st, fifo_rate);
+	if (result)
+		goto fifo_rate_fail_power_off;
+
 fifo_rate_fail_power_off:
 	result |= inv_mpu6050_set_power_itg(st, false);
 fifo_rate_fail_unlock:
@@ -795,8 +811,14 @@  inv_get_mount_matrix(const struct iio_dev *indio_dev,
 		     const struct iio_chan_spec *chan)
 {
 	struct inv_mpu6050_state *data = iio_priv(indio_dev);
+	const struct iio_mount_matrix *matrix;
+
+	if (chan->type == IIO_MAGN)
+		matrix = &data->magn_orient;
+	else
+		matrix = &data->orientation;
 
-	return &data->orientation;
+	return matrix;
 }
 
 static const struct iio_chan_spec_ext_info inv_ext_info[] = {
@@ -864,6 +886,98 @@  static const unsigned long inv_mpu_scan_masks[] = {
 	0,
 };
 
+#define INV_MPU9X50_MAGN_CHAN(_chan2, _bits, _index)			\
+	{								\
+		.type = IIO_MAGN,					\
+		.modified = 1,						\
+		.channel2 = _chan2,					\
+		.info_mask_separate = BIT(IIO_CHAN_INFO_SCALE) |	\
+				      BIT(IIO_CHAN_INFO_RAW),		\
+		.scan_index = _index,					\
+		.scan_type = {						\
+			.sign = 's',					\
+			.realbits = _bits,				\
+			.storagebits = 16,				\
+			.shift = 0,					\
+			.endianness = IIO_BE,				\
+		},							\
+		.ext_info = inv_ext_info,				\
+	}
+
+static const struct iio_chan_spec inv_mpu9250_channels[] = {
+	IIO_CHAN_SOFT_TIMESTAMP(INV_MPU9X50_SCAN_TIMESTAMP),
+	/*
+	 * Note that temperature should only be via polled reading only,
+	 * not the final scan elements output.
+	 */
+	{
+		.type = IIO_TEMP,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW)
+				| BIT(IIO_CHAN_INFO_OFFSET)
+				| BIT(IIO_CHAN_INFO_SCALE),
+		.scan_index = -1,
+	},
+	INV_MPU6050_CHAN(IIO_ANGL_VEL, IIO_MOD_X, INV_MPU6050_SCAN_GYRO_X),
+	INV_MPU6050_CHAN(IIO_ANGL_VEL, IIO_MOD_Y, INV_MPU6050_SCAN_GYRO_Y),
+	INV_MPU6050_CHAN(IIO_ANGL_VEL, IIO_MOD_Z, INV_MPU6050_SCAN_GYRO_Z),
+
+	INV_MPU6050_CHAN(IIO_ACCEL, IIO_MOD_X, INV_MPU6050_SCAN_ACCL_X),
+	INV_MPU6050_CHAN(IIO_ACCEL, IIO_MOD_Y, INV_MPU6050_SCAN_ACCL_Y),
+	INV_MPU6050_CHAN(IIO_ACCEL, IIO_MOD_Z, INV_MPU6050_SCAN_ACCL_Z),
+
+	/* Magnetometer resolution is 16 bits */
+	INV_MPU9X50_MAGN_CHAN(IIO_MOD_X, 16, INV_MPU9X50_SCAN_MAGN_X),
+	INV_MPU9X50_MAGN_CHAN(IIO_MOD_Y, 16, INV_MPU9X50_SCAN_MAGN_Y),
+	INV_MPU9X50_MAGN_CHAN(IIO_MOD_Z, 16, INV_MPU9X50_SCAN_MAGN_Z),
+};
+
+static const unsigned long inv_mpu9x50_scan_masks[] = {
+	/* 3-axis accel */
+	BIT(INV_MPU6050_SCAN_ACCL_X)
+		| BIT(INV_MPU6050_SCAN_ACCL_Y)
+		| BIT(INV_MPU6050_SCAN_ACCL_Z),
+	/* 3-axis gyro */
+	BIT(INV_MPU6050_SCAN_GYRO_X)
+		| BIT(INV_MPU6050_SCAN_GYRO_Y)
+		| BIT(INV_MPU6050_SCAN_GYRO_Z),
+	/* 3-axis magn */
+	BIT(INV_MPU9X50_SCAN_MAGN_X)
+		| BIT(INV_MPU9X50_SCAN_MAGN_Y)
+		| BIT(INV_MPU9X50_SCAN_MAGN_Z),
+	/* 6-axis accel + gyro */
+	BIT(INV_MPU6050_SCAN_ACCL_X)
+		| BIT(INV_MPU6050_SCAN_ACCL_Y)
+		| BIT(INV_MPU6050_SCAN_ACCL_Z)
+		| BIT(INV_MPU6050_SCAN_GYRO_X)
+		| BIT(INV_MPU6050_SCAN_GYRO_Y)
+		| BIT(INV_MPU6050_SCAN_GYRO_Z),
+	/* 6-axis accel + magn */
+	BIT(INV_MPU6050_SCAN_ACCL_X)
+		| BIT(INV_MPU6050_SCAN_ACCL_Y)
+		| BIT(INV_MPU6050_SCAN_ACCL_Z)
+		| BIT(INV_MPU9X50_SCAN_MAGN_X)
+		| BIT(INV_MPU9X50_SCAN_MAGN_Y)
+		| BIT(INV_MPU9X50_SCAN_MAGN_Z),
+	/* 6-axis gyro + magn */
+	BIT(INV_MPU6050_SCAN_GYRO_X)
+		| BIT(INV_MPU6050_SCAN_GYRO_Y)
+		| BIT(INV_MPU6050_SCAN_GYRO_Z)
+		| BIT(INV_MPU9X50_SCAN_MAGN_X)
+		| BIT(INV_MPU9X50_SCAN_MAGN_Y)
+		| BIT(INV_MPU9X50_SCAN_MAGN_Z),
+	/* 9-axis accel + gyro + magn */
+	BIT(INV_MPU6050_SCAN_ACCL_X)
+		| BIT(INV_MPU6050_SCAN_ACCL_Y)
+		| BIT(INV_MPU6050_SCAN_ACCL_Z)
+		| BIT(INV_MPU6050_SCAN_GYRO_X)
+		| BIT(INV_MPU6050_SCAN_GYRO_Y)
+		| BIT(INV_MPU6050_SCAN_GYRO_Z)
+		| BIT(INV_MPU9X50_SCAN_MAGN_X)
+		| BIT(INV_MPU9X50_SCAN_MAGN_Y)
+		| BIT(INV_MPU9X50_SCAN_MAGN_Z),
+	0,
+};
+
 static const struct iio_chan_spec inv_icm20602_channels[] = {
 	IIO_CHAN_SOFT_TIMESTAMP(INV_ICM20602_SCAN_TIMESTAMP),
 	{
@@ -1145,6 +1259,11 @@  int inv_mpu_core_probe(struct regmap *regmap, int irq, const char *name,
 		return result;
 	}
 
+	/* fill magnetometer orientation */
+	result = inv_mpu_magn_set_orient(st);
+	if (result)
+		return result;
+
 	/* power is turned on inside check chip type*/
 	result = inv_check_and_setup_chip(st);
 	if (result)
@@ -1168,14 +1287,33 @@  int inv_mpu_core_probe(struct regmap *regmap, int irq, const char *name,
 	if (inv_mpu_bus_setup)
 		inv_mpu_bus_setup(indio_dev);
 
-	if (chip_type == INV_ICM20602) {
+	switch (chip_type) {
+	case INV_MPU9250:
+	case INV_MPU9255:
+		/*
+		 * Use magnetometer inside the chip only if there is no i2c
+		 * auxiliary device in use.
+		 */
+		if (!st->magn_disabled) {
+			indio_dev->channels = inv_mpu9250_channels;
+			indio_dev->num_channels = ARRAY_SIZE(inv_mpu9250_channels);
+			indio_dev->available_scan_masks = inv_mpu9x50_scan_masks;
+		} else {
+			indio_dev->channels = inv_mpu_channels;
+			indio_dev->num_channels = ARRAY_SIZE(inv_mpu_channels);
+			indio_dev->available_scan_masks = inv_mpu_scan_masks;
+		}
+		break;
+	case INV_ICM20602:
 		indio_dev->channels = inv_icm20602_channels;
 		indio_dev->num_channels = ARRAY_SIZE(inv_icm20602_channels);
 		indio_dev->available_scan_masks = inv_icm20602_scan_masks;
-	} else {
+		break;
+	default:
 		indio_dev->channels = inv_mpu_channels;
 		indio_dev->num_channels = ARRAY_SIZE(inv_mpu_channels);
 		indio_dev->available_scan_masks = inv_mpu_scan_masks;
+		break;
 	}
 
 	indio_dev->info = &mpu_info;
diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h b/drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h
index 04215bc6e8ab..953f85618199 100644
--- a/drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h
+++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h
@@ -130,6 +130,8 @@  struct inv_mpu6050_hw {
  *  @data_timestamp:	timestamp for next data sample.
  *  @vddio_supply	voltage regulator for the chip.
  *  @magn_disabled:     magnetometer disabled for backward compatibility reason.
+ *  @magn_raw_to_gauss:	coefficient to convert mag raw value to Gauss.
+ *  @magn_orient:       magnetometer sensor chip orientation if available.
  */
 struct inv_mpu6050_state {
 	struct mutex lock;
@@ -152,6 +154,8 @@  struct inv_mpu6050_state {
 	s64 data_timestamp;
 	struct regulator *vddio_supply;
 	bool magn_disabled;
+	s32 magn_raw_to_gauss[3];
+	struct iio_mount_matrix magn_orient;
 };
 
 /*register and associated bit definition*/
diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_magn.c b/drivers/iio/imu/inv_mpu6050/inv_mpu_magn.c
new file mode 100644
index 000000000000..415d8acece54
--- /dev/null
+++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_magn.c
@@ -0,0 +1,355 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2019 TDK-InvenSense, Inc.
+ */
+
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/string.h>
+
+#include "inv_mpu_iio.h"
+#include "inv_mpu_aux.h"
+
+/*
+ * MPU9250 magnetometer is an AKM AK8963 chip on I2C aux bus
+ */
+#define INV_MPU_MAGN_I2C_ADDR		0x0C
+
+#define INV_MPU_MAGN_REG_WIA		0x00
+#define INV_MPU_MAGN_BITS_WIA		0x48
+
+#define INV_MPU_MAGN_REG_ST1		0x02
+#define INV_MPU_MAGN_BIT_DRDY		0x01
+#define INV_MPU_MAGN_BIT_DOR		0x02
+
+#define INV_MPU_MAGN_REG_DATA		0x03
+
+#define INV_MPU_MAGN_REG_ST2		0x09
+#define INV_MPU_MAGN_BIT_HOFL		0x08
+#define INV_MPU_MAGN_BIT_BITM		0x10
+
+#define INV_MPU_MAGN_REG_CNTL1		0x0A
+#define INV_MPU_MAGN_BITS_MODE_PWDN	0x00
+#define INV_MPU_MAGN_BITS_MODE_SINGLE	0x01
+#define INV_MPU_MAGN_BITS_MODE_FUSE	0x0F
+#define INV_MPU_MAGN_BIT_OUTPUT_BIT	0x10
+
+#define INV_MPU_MAGN_REG_CNTL2		0x0B
+#define INV_MPU_MAGN_BIT_SRST		0x01
+
+#define INV_MPU_MAGN_REG_ASAX		0x10
+#define INV_MPU_MAGN_REG_ASAY		0x11
+#define INV_MPU_MAGN_REG_ASAZ		0x12
+
+/* Magnetometer maximum frequency */
+#define INV_MPU_MAGN_FREQ_HZ_MAX	50
+
+static bool inv_magn_supported(const struct inv_mpu6050_state *st)
+{
+	switch (st->chip_type) {
+	case INV_MPU9250:
+	case INV_MPU9255:
+		return true;
+	default:
+		return false;
+	}
+}
+
+/* init magnetometer chip */
+static int inv_magn_init(struct inv_mpu6050_state *st)
+{
+	uint8_t val;
+	uint8_t asa[3];
+	int ret;
+
+	/* check whoami */
+	ret = inv_mpu_aux_read(st, INV_MPU_MAGN_I2C_ADDR, INV_MPU_MAGN_REG_WIA,
+			       &val, sizeof(val));
+	if (ret)
+		return ret;
+	if (val != INV_MPU_MAGN_BITS_WIA)
+		return -ENODEV;
+
+	/* reset chip */
+	ret = inv_mpu_aux_write(st, INV_MPU_MAGN_I2C_ADDR,
+				INV_MPU_MAGN_REG_CNTL2,
+				INV_MPU_MAGN_BIT_SRST);
+	if (ret)
+		return ret;
+
+	/* read fuse ROM data */
+	ret = inv_mpu_aux_write(st, INV_MPU_MAGN_I2C_ADDR,
+				INV_MPU_MAGN_REG_CNTL1,
+				INV_MPU_MAGN_BITS_MODE_FUSE);
+	if (ret)
+		return ret;
+
+	ret = inv_mpu_aux_read(st, INV_MPU_MAGN_I2C_ADDR, INV_MPU_MAGN_REG_ASAX,
+			       asa, sizeof(asa));
+	if (ret)
+		return ret;
+
+	/* switch back to power-down */
+	ret = inv_mpu_aux_write(st, INV_MPU_MAGN_I2C_ADDR,
+				INV_MPU_MAGN_REG_CNTL1,
+				INV_MPU_MAGN_BITS_MODE_PWDN);
+	if (ret)
+		return ret;
+
+	/*
+	 * Sensitivity adjustement and scale to Gauss
+	 *
+	 * Hadj = H * (((ASA - 128) * 0.5 / 128) + 1)
+	 * Factor simplification:
+	 * Hadj = H * ((ASA + 128) / 256)
+	 *
+	 * Sensor sentivity
+	 * 0.15 uT in 16 bits mode
+	 * 1 uT = 0.01 G and value is in micron (1e6)
+	 * sensitvity = 0.15 uT * 0.01 * 1e6
+	 *
+	 * raw_to_gauss = Hadj * 1500
+	 */
+	st->magn_raw_to_gauss[0] = (((int32_t)asa[0] + 128) * 1500) / 256;
+	st->magn_raw_to_gauss[1] = (((int32_t)asa[1] + 128) * 1500) / 256;
+	st->magn_raw_to_gauss[2] = (((int32_t)asa[2] + 128) * 1500) / 256;
+
+	return 0;
+}
+
+/**
+ * inv_mpu_magn_probe() - probe and setup magnetometer chip
+ * @st: driver internal state
+ *
+ * Returns 0 on success, a negative error code otherwise
+ *
+ * It is probing the chip and setting up all needed i2c transfers.
+ * Noop if there is no magnetometer in the chip.
+ */
+int inv_mpu_magn_probe(struct inv_mpu6050_state *st)
+{
+	int ret;
+
+	/* quit if chip is not supported */
+	if (!inv_magn_supported(st))
+		return 0;
+
+	/* configure i2c master aux port */
+	ret = inv_mpu_aux_init(st);
+	if (ret)
+		return ret;
+
+	/* check and init mag chip */
+	ret = inv_magn_init(st);
+	if (ret)
+		return ret;
+
+	/*
+	 * configure mpu i2c master accesses
+	 * i2c SLV0: read sensor data, 7 bytes data(6)-ST2
+	 * Byte swap data to store them in big-endian in impair address groups
+	 */
+	ret = regmap_write(st->map, INV_MPU6050_REG_I2C_SLV_ADDR(0),
+			   INV_MPU6050_BIT_I2C_SLV_RNW | INV_MPU_MAGN_I2C_ADDR);
+	if (ret)
+		return ret;
+
+	ret = regmap_write(st->map, INV_MPU6050_REG_I2C_SLV_REG(0),
+			   INV_MPU_MAGN_REG_DATA);
+	if (ret)
+		return ret;
+
+	ret = regmap_write(st->map, INV_MPU6050_REG_I2C_SLV_CTRL(0),
+			   INV_MPU6050_BIT_SLV_EN |
+			   INV_MPU6050_BIT_SLV_BYTE_SW |
+			   INV_MPU6050_BIT_SLV_GRP |
+			   INV_MPU9X50_BYTES_MAGN);
+	if (ret)
+		return ret;
+
+	/* i2c SLV1: launch single measurement */
+	ret = regmap_write(st->map, INV_MPU6050_REG_I2C_SLV_ADDR(1),
+			   INV_MPU_MAGN_I2C_ADDR);
+	if (ret)
+		return ret;
+
+	ret = regmap_write(st->map, INV_MPU6050_REG_I2C_SLV_REG(1),
+			   INV_MPU_MAGN_REG_CNTL1);
+	if (ret)
+		return ret;
+
+	/* add 16 bits mode */
+	ret = regmap_write(st->map, INV_MPU6050_REG_I2C_SLV_DO(1),
+			   INV_MPU_MAGN_BITS_MODE_SINGLE |
+			   INV_MPU_MAGN_BIT_OUTPUT_BIT);
+	if (ret)
+		return ret;
+
+	return regmap_write(st->map, INV_MPU6050_REG_I2C_SLV_CTRL(1),
+			    INV_MPU6050_BIT_SLV_EN | 1);
+}
+
+/**
+ * inv_mpu_magn_set_rate() - set magnetometer sampling rate
+ * @st: driver internal state
+ * @fifo_rate: mpu set fifo rate
+ *
+ * Returns 0 on success, a negative error code otherwise
+ *
+ * Limit sampling frequency to the maximum value supported by the
+ * magnetometer chip. Resulting in duplicated data for higher frequencies.
+ * Noop if there is no magnetometer in the chip.
+ */
+int inv_mpu_magn_set_rate(const struct inv_mpu6050_state *st, int fifo_rate)
+{
+	uint8_t d;
+
+	/* quit if chip is not supported */
+	if (!inv_magn_supported(st))
+		return 0;
+
+	/*
+	 * update i2c master delay to limit mag sampling to max frequency
+	 * compute fifo_rate divider d: rate = fifo_rate / (d + 1)
+	 */
+	if (fifo_rate > INV_MPU_MAGN_FREQ_HZ_MAX)
+		d = fifo_rate / INV_MPU_MAGN_FREQ_HZ_MAX - 1;
+	else
+		d = 0;
+
+	return regmap_write(st->map, INV_MPU6050_REG_I2C_SLV4_CTRL, d);
+}
+
+/**
+ * inv_mpu_magn_set_orient() - fill magnetometer mounting matrix
+ * @st: driver internal state
+ *
+ * Returns 0 on success, a negative error code otherwise
+ *
+ * Fill magnetometer mounting matrix using the provided chip matrix.
+ */
+int inv_mpu_magn_set_orient(struct inv_mpu6050_state *st)
+{
+	const char *orient;
+	char *str;
+	int i;
+
+	/* fill magnetometer orientation */
+	switch (st->chip_type) {
+	case INV_MPU9250:
+	case INV_MPU9255:
+		/* x <- y */
+		st->magn_orient.rotation[0] = st->orientation.rotation[3];
+		st->magn_orient.rotation[1] = st->orientation.rotation[4];
+		st->magn_orient.rotation[2] = st->orientation.rotation[5];
+		/* y <- x */
+		st->magn_orient.rotation[3] = st->orientation.rotation[0];
+		st->magn_orient.rotation[4] = st->orientation.rotation[1];
+		st->magn_orient.rotation[5] = st->orientation.rotation[2];
+		/* z <- -z */
+		for (i = 0; i < 3; ++i) {
+			orient = st->orientation.rotation[6 + i];
+			/* use length + 2 for adding minus sign if needed */
+			str = devm_kzalloc(regmap_get_device(st->map),
+					   strlen(orient) + 2, GFP_KERNEL);
+			if (str == NULL)
+				return -ENOMEM;
+			if (strcmp(orient, "0") == 0) {
+				strcpy(str, orient);
+			} else if (orient[0] == '-') {
+				strcpy(str, &orient[1]);
+			} else {
+				str[0] = '-';
+				strcpy(&str[1], orient);
+			}
+			st->magn_orient.rotation[6 + i] = str;
+		}
+		break;
+	default:
+		st->magn_orient = st->orientation;
+		break;
+	}
+
+	return 0;
+}
+
+/**
+ * inv_mpu_magn_read() - read magnetometer data
+ * @st: driver internal state
+ * @axis: IIO modifier axis value
+ * @val: store corresponding axis value
+ *
+ * Returns 0 on success, a negative error code otherwise
+ */
+int inv_mpu_magn_read(const struct inv_mpu6050_state *st, int axis, int *val)
+{
+	unsigned int user_ctrl, status;
+	__be16 data[3];
+	uint8_t addr;
+	uint8_t d;
+	unsigned int period_ms;
+	int ret;
+
+	/* quit if chip is not supported */
+	if (!inv_magn_supported(st))
+		return -ENODEV;
+
+	/* Mag data: X - Y - Z */
+	switch (axis) {
+	case IIO_MOD_X:
+		addr = 0;
+		break;
+	case IIO_MOD_Y:
+		addr = 1;
+		break;
+	case IIO_MOD_Z:
+		addr = 2;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* set sample rate to max mag freq */
+	d = INV_MPU6050_FIFO_RATE_TO_DIVIDER(INV_MPU_MAGN_FREQ_HZ_MAX);
+	ret = regmap_write(st->map, st->reg->sample_rate_div, d);
+	if (ret)
+		return ret;
+
+	/* start i2c master, wait for xfer, stop */
+	user_ctrl = st->chip_config.user_ctrl | INV_MPU6050_BIT_I2C_MST_EN;
+	ret = regmap_write(st->map, st->reg->user_ctrl, user_ctrl);
+	if (ret)
+		return ret;
+
+	/* need to wait 2 periods + half-period margin */
+	period_ms = 1000 / INV_MPU_MAGN_FREQ_HZ_MAX;
+	msleep(period_ms * 2 + period_ms / 2);
+	user_ctrl = st->chip_config.user_ctrl;
+	ret = regmap_write(st->map, st->reg->user_ctrl, user_ctrl);
+	if (ret)
+		return ret;
+
+	/* restore sample rate */
+	d = st->chip_config.divider;
+	ret = regmap_write(st->map, st->reg->sample_rate_div, d);
+	if (ret)
+		return ret;
+
+	/* check i2c status and read raw data */
+	ret = regmap_read(st->map, INV_MPU6050_REG_I2C_MST_STATUS, &status);
+	if (ret)
+		return ret;
+
+	if (status & INV_MPU6050_BIT_I2C_SLV0_NACK ||
+			status & INV_MPU6050_BIT_I2C_SLV1_NACK)
+		return -EIO;
+
+	ret = regmap_bulk_read(st->map, INV_MPU6050_REG_EXT_SENS_DATA,
+			       data, sizeof(data));
+	if (ret)
+		return ret;
+
+	*val = (int16_t)be16_to_cpu(data[addr]);
+
+	return IIO_VAL_INT;
+}
diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_magn.h b/drivers/iio/imu/inv_mpu6050/inv_mpu_magn.h
new file mode 100644
index 000000000000..b41bd0578478
--- /dev/null
+++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_magn.h
@@ -0,0 +1,36 @@ 
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2019 TDK-InvenSense, Inc.
+ */
+
+#ifndef INV_MPU_MAGN_H_
+#define INV_MPU_MAGN_H_
+
+#include <linux/kernel.h>
+
+#include "inv_mpu_iio.h"
+
+int inv_mpu_magn_probe(struct inv_mpu6050_state *st);
+
+/**
+ * inv_mpu_magn_get_scale() - get magnetometer scale value
+ * @st: driver internal state
+ *
+ * Returns IIO data format.
+ */
+static inline int inv_mpu_magn_get_scale(const struct inv_mpu6050_state *st,
+					 const struct iio_chan_spec *chan,
+					 int *val, int *val2)
+{
+	*val = 0;
+	*val2 = st->magn_raw_to_gauss[chan->address];
+	return IIO_VAL_INT_PLUS_MICRO;
+}
+
+int inv_mpu_magn_set_rate(const struct inv_mpu6050_state *st, int fifo_rate);
+
+int inv_mpu_magn_set_orient(struct inv_mpu6050_state *st);
+
+int inv_mpu_magn_read(const struct inv_mpu6050_state *st, int axis, int *val);
+
+#endif		/* INV_MPU_MAGN_H_ */