360 lines
8.3 KiB
C
360 lines
8.3 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)
|
|
{
|
|
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 = 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(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;
|
|
}
|