366 lines
		
	
	
		
			8.5 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			366 lines
		
	
	
		
			8.5 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
// 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_aux.h"
 | 
						|
#include "inv_mpu_iio.h"
 | 
						|
#include "inv_mpu_magn.h"
 | 
						|
 | 
						|
/*
 | 
						|
 * MPU9xxx magnetometer are AKM chips on I2C aux bus
 | 
						|
 * MPU9150 is AK8975
 | 
						|
 * MPU9250 is AK8963
 | 
						|
 */
 | 
						|
#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_MPU9250_MAGN_BIT_OUTPUT_BIT	0x10
 | 
						|
 | 
						|
#define INV_MPU9250_MAGN_REG_CNTL2	0x0B
 | 
						|
#define INV_MPU9250_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
 | 
						|
 | 
						|
static bool inv_magn_supported(const struct inv_mpu6050_state *st)
 | 
						|
{
 | 
						|
	switch (st->chip_type) {
 | 
						|
	case INV_MPU9150:
 | 
						|
	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];
 | 
						|
	int32_t sensitivity;
 | 
						|
	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;
 | 
						|
 | 
						|
	/* software reset for MPU925x only */
 | 
						|
	switch (st->chip_type) {
 | 
						|
	case INV_MPU9250:
 | 
						|
	case INV_MPU9255:
 | 
						|
		ret = inv_mpu_aux_write(st, INV_MPU_MAGN_I2C_ADDR,
 | 
						|
					INV_MPU9250_MAGN_REG_CNTL2,
 | 
						|
					INV_MPU9250_MAGN_BIT_SRST);
 | 
						|
		if (ret)
 | 
						|
			return ret;
 | 
						|
		break;
 | 
						|
	default:
 | 
						|
		break;
 | 
						|
	}
 | 
						|
 | 
						|
	/* 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;
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Sensor sentivity
 | 
						|
	 * 1 uT = 0.01 G and value is in micron (1e6)
 | 
						|
	 * sensitvity = x uT * 0.01 * 1e6
 | 
						|
	 */
 | 
						|
	switch (st->chip_type) {
 | 
						|
	case INV_MPU9150:
 | 
						|
		/* sensor sensitivity is 0.3 uT */
 | 
						|
		sensitivity = 3000;
 | 
						|
		break;
 | 
						|
	case INV_MPU9250:
 | 
						|
	case INV_MPU9255:
 | 
						|
		/* sensor sensitivity in 16 bits mode: 0.15 uT */
 | 
						|
		sensitivity = 1500;
 | 
						|
		break;
 | 
						|
	default:
 | 
						|
		return -EINVAL;
 | 
						|
	}
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Sensitivity adjustement and scale to Gauss
 | 
						|
	 *
 | 
						|
	 * Hadj = H * (((ASA - 128) * 0.5 / 128) + 1)
 | 
						|
	 * Factor simplification:
 | 
						|
	 * Hadj = H * ((ASA + 128) / 256)
 | 
						|
	 *
 | 
						|
	 * raw_to_gauss = Hadj * sensitivity
 | 
						|
	 */
 | 
						|
	st->magn_raw_to_gauss[0] = (((int32_t)asa[0] + 128) * sensitivity) / 256;
 | 
						|
	st->magn_raw_to_gauss[1] = (((int32_t)asa[1] + 128) * sensitivity) / 256;
 | 
						|
	st->magn_raw_to_gauss[2] = (((int32_t)asa[2] + 128) * sensitivity) / 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)
 | 
						|
{
 | 
						|
	uint8_t val;
 | 
						|
	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 for MPU925x */
 | 
						|
	val = INV_MPU_MAGN_BITS_MODE_SINGLE;
 | 
						|
	switch (st->chip_type) {
 | 
						|
	case INV_MPU9250:
 | 
						|
	case INV_MPU9255:
 | 
						|
		val |= INV_MPU9250_MAGN_BIT_OUTPUT_BIT;
 | 
						|
		break;
 | 
						|
	default:
 | 
						|
		break;
 | 
						|
	}
 | 
						|
	ret = regmap_write(st->map, INV_MPU6050_REG_I2C_SLV_DO(1), val);
 | 
						|
	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)
 | 
						|
{
 | 
						|
	struct device *dev = regmap_get_device(st->map);
 | 
						|
	const char *orient;
 | 
						|
	char *str;
 | 
						|
	int i;
 | 
						|
 | 
						|
	/* fill magnetometer orientation */
 | 
						|
	switch (st->chip_type) {
 | 
						|
	case INV_MPU9150:
 | 
						|
	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 = 6; i < 9; ++i) {
 | 
						|
			orient = st->orientation.rotation[i];
 | 
						|
 | 
						|
			/*
 | 
						|
			 * The value is negated according to one of the following
 | 
						|
			 * rules:
 | 
						|
			 *
 | 
						|
			 * 1) Drop leading minus.
 | 
						|
			 * 2) Leave 0 as is.
 | 
						|
			 * 3) Add leading minus.
 | 
						|
			 */
 | 
						|
			if (orient[0] == '-')
 | 
						|
				str = devm_kstrdup(dev, orient + 1, GFP_KERNEL);
 | 
						|
			else if (!strcmp(orient, "0"))
 | 
						|
				str = devm_kstrdup(dev, orient, GFP_KERNEL);
 | 
						|
			else
 | 
						|
				str = devm_kasprintf(dev, GFP_KERNEL, "-%s", orient);
 | 
						|
			if (!str)
 | 
						|
				return -ENOMEM;
 | 
						|
 | 
						|
			st->magn_orient.rotation[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(struct inv_mpu6050_state *st, int axis, int *val)
 | 
						|
{
 | 
						|
	unsigned int status;
 | 
						|
	__be16 data;
 | 
						|
	uint8_t addr;
 | 
						|
	int ret;
 | 
						|
 | 
						|
	/* quit if chip is not supported */
 | 
						|
	if (!inv_magn_supported(st))
 | 
						|
		return -ENODEV;
 | 
						|
 | 
						|
	/* Mag data: XH,XL,YH,YL,ZH,ZL */
 | 
						|
	switch (axis) {
 | 
						|
	case IIO_MOD_X:
 | 
						|
		addr = 0;
 | 
						|
		break;
 | 
						|
	case IIO_MOD_Y:
 | 
						|
		addr = 2;
 | 
						|
		break;
 | 
						|
	case IIO_MOD_Z:
 | 
						|
		addr = 4;
 | 
						|
		break;
 | 
						|
	default:
 | 
						|
		return -EINVAL;
 | 
						|
	}
 | 
						|
	addr += INV_MPU6050_REG_EXT_SENS_DATA;
 | 
						|
 | 
						|
	/* 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, addr, &data, sizeof(data));
 | 
						|
	if (ret)
 | 
						|
		return ret;
 | 
						|
 | 
						|
	*val = (int16_t)be16_to_cpu(data);
 | 
						|
 | 
						|
	return IIO_VAL_INT;
 | 
						|
}
 |