root/drivers/iio/imu/inv_mpu6050/inv_mpu_magn.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;
}