/*
 $License:
   Copyright 2011 InvenSense, Inc.

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
  $
 */

/******************************************************************************
 *
 * $Id: mputest.c 5637 2011-06-14 01:13:53Z mcaramello $
 *
 *****************************************************************************/

/*
 *  MPU Self Test functions
 *  Version 2.4
 *  May 13th, 2011
 */

/**
 *  @defgroup MPU_SELF_TEST
 *  @brief  MPU Self Test functions
 *
 *  These functions provide an in-site test of the MPU 3xxx chips. The main
 *      entry point is the inv_mpu_test function.
 *  This runs the tests (as described in the accompanying documentation) and
 *      writes a configuration file containing initial calibration data.
 *  inv_mpu_test returns INV_SUCCESS if the chip passes the tests.
 *  Otherwise, an error code is returned.
 *  The functions in this file rely on MLSL and MLOS: refer to the MPL
 *      documentation for more information regarding the system interface
 *      files.
 *
 *  @{
 *      @file   mputest.c
 *      @brief  MPU Self Test routines for assessing gyro sensor status
 *              after surface mount has happened on the target host platform.
 */

#include <stdio.h>
#include <time.h>
#include <string.h>
#include <math.h>
#include <stdlib.h>
#ifdef LINUX
#include <unistd.h>
#endif

#include "mpu.h"
#include "mldl.h"
#include "mldl_cfg.h"
#include "accel.h"
#include "mlFIFO.h"
#include "slave.h"
#include "ml.h"
#include "ml_stored_data.h"
#include "checksum.h"

#include "mlsl.h"
#include "mlos.h"

#include "log.h"
#undef MPL_LOG_TAG
#define MPL_LOG_TAG "MPL-mpust"

#ifdef __cplusplus
extern "C" {
#endif

/*
    Defines
*/

#define VERBOSE_OUT 0

/*#define TRACK_IDS*/

/*--- Test parameters defaults. See set_test_parameters for more details ---*/

#define DEF_MPU_ADDR             (0x68)        /* I2C address of the mpu     */

#if (USE_SENSE_PATH_TEST == 1)                 /* gyro full scale dps        */
#define DEF_GYRO_FULLSCALE       (2000)
#else
#define DEF_GYRO_FULLSCALE       (250)
#endif

#define DEF_GYRO_SENS            (32768.f / DEF_GYRO_FULLSCALE)
                                               /* gyro sensitivity LSB/dps   */
#define DEF_PACKET_THRESH        (75)          /* 600 ms / 8ms / sample      */
#define DEF_TOTAL_TIMING_TOL     (.03f)        /* 3% = 2 pkts + 1% proc tol. */
#define DEF_BIAS_THRESH          (40 * DEF_GYRO_SENS)
                                               /* 40 dps in LSBs             */
#define DEF_RMS_THRESH           (0.4f * DEF_GYRO_SENS)
                                               /* 0.4 dps-rms in LSB-rms     */
#define DEF_SP_SHIFT_THRESH_CUST (.05f)        /* 5%                         */
#define DEF_TEST_TIME_PER_AXIS   (600)         /* ms of time spent collecting
                                                  data for each axis,
                                                  multiple of 600ms          */
#define DEF_N_ACCEL_SAMPLES      (20)          /* num of accel samples to
                                                  average from, if applic.   */

#define ML_INIT_CAL_LEN          (36)          /* length in bytes of
                                                  calibration data file      */

/*
    Macros
*/

#define CHECK_TEST_ERROR(x)                                                 \
    if (x) {                                                                \
        MPL_LOGI("error %d @ %s|%d\n", x, __func__, __LINE__);              \
        return (-1);                                                        \
    }

#define SHORT_TO_TEMP_C(shrt)         (((shrt+13200.f)/280.f)+35.f)

#define USHORT_TO_CHARS(chr,shrt)     (chr)[0]=(unsigned char)(shrt>>8);    \
                                      (chr)[1]=(unsigned char)(shrt);

#define UINT_TO_CHARS(chr,ui)         (chr)[0]=(unsigned char)(ui>>24);     \
                                      (chr)[1]=(unsigned char)(ui>>16);     \
                                      (chr)[2]=(unsigned char)(ui>>8);      \
                                      (chr)[3]=(unsigned char)(ui);

#define FLOAT_TO_SHORT(f)             (                                     \
                                        (fabs(f-(short)f)>=0.5) ? (         \
                                            ((short)f)+(f<0?(-1):(+1))) :   \
                                            ((short)f)                      \
                                      )

#define CHARS_TO_SHORT(d)             ((((short)(d)[0])<<8)+(d)[1])
#define CHARS_TO_SHORT_SWAPPED(d)     ((((short)(d)[1])<<8)+(d)[0])

#define ACCEL_UNPACK(d) d[0], d[1], d[2], d[3], d[4], d[5]

#define CHECK_NACKS(d)  (                               \
                         d[0]==0xff && d[1]==0xff &&    \
                         d[2]==0xff && d[3]==0xff &&    \
                         d[4]==0xff && d[5]==0xff       \
                        )

/*
    Prototypes
*/

static inv_error_t test_get_data(
                    void *mlsl_handle,
                    struct mldl_cfg *mputestCfgPtr,
                    short *vals);

/*
    Types
*/
typedef struct {
    float gyro_sens;
    int gyro_fs;
    int packet_thresh;
    float total_timing_tol;
    int bias_thresh;
    float rms_threshSq;
    float sp_shift_thresh;
    unsigned int test_time_per_axis;
    unsigned short accel_samples;
} tTestSetup;

/*
    Global variables
*/
static unsigned char dataout[20];
static unsigned char dataStore[ML_INIT_CAL_LEN];

static tTestSetup test_setup = {
    DEF_GYRO_SENS,
    DEF_GYRO_FULLSCALE,
    DEF_PACKET_THRESH,
    DEF_TOTAL_TIMING_TOL,
    (int)DEF_BIAS_THRESH,
    DEF_RMS_THRESH * DEF_RMS_THRESH,
    DEF_SP_SHIFT_THRESH_CUST,
    DEF_TEST_TIME_PER_AXIS,
    DEF_N_ACCEL_SAMPLES
};

static float adjGyroSens;
static char a_name[3][2] = {"X", "Y", "Z"};

/*
    NOTE :  modify get_slave_descr parameter below to reflect
                the DEFAULT accelerometer in use. The accelerometer in use
                can be modified at run-time using the inv_test_setup_accel API.
    NOTE :  modify the expected z axis orientation (Z axis pointing
                upward or downward)
*/

signed char g_z_sign = +1;
struct mldl_cfg *mputestCfgPtr = NULL;

#ifndef LINUX
/**
 *  @internal
 *  @brief  usec precision sleep function.
 *  @param  number of micro seconds (us) to sleep for.
 */
static void usleep(unsigned long t)
{
    unsigned long start = inv_get_tick_count();
    while (inv_get_tick_count()-start < t / 1000);
}
#endif

/**
 *  @brief  Modify the self test limits from their default values.
 *
 *  @param  slave_addr
 *              the slave address the MPU device is setup to respond at.
 *              The default is DEF_MPU_ADDR = 0x68.
 *  @param  sensitivity
 *              the read sensitivity of the device in LSB/dps as it is trimmed.
 *              NOTE :  if using the self test as part of the MPL, the
 *                      sensitivity the different sensitivity trims are already
 *                      taken care of.
 *  @param  p_thresh
 *              number of packets expected to be received in a 600 ms period.
 *              Depends on the sampling frequency of choice (set by default to
 *              125 Hz) and low pass filter cut-off frequency selection (set
 *              to 42 Hz).
 *              The default is DEF_PACKET_THRESH = 75 packets.
 *  @param  total_time_tol
 *              time skew tolerance, taking into account imprecision in turning
 *              the FIFO on and off and the processor time imprecision (for
 *              1 GHz processor).
 *              The default is DEF_TOTAL_TIMING_TOL = 3 %, about 2 packets.
 *  @param  bias_thresh
 *              bias level threshold, the maximun acceptable no motion bias
 *              for a production quality part.
 *              The default is DEF_BIAS_THRESH = 40 dps.
 *  @param  rms_thresh
 *              the limit standard deviation (=~ RMS) set to assess whether
 *              the noise level on the part is acceptable.
 *              The default is DEF_RMS_THRESH = 0.2 dps-rms.
 *  @param  sp_shift_thresh
 *              the limit shift applicable to the Sense Path self test
 *              calculation.
 */
void inv_set_test_parameters(unsigned int slave_addr, float sensitivity,
                             int p_thresh, float total_time_tol,
                             int bias_thresh, float rms_thresh,
                             float sp_shift_thresh,
                             unsigned short accel_samples)
{
    mputestCfgPtr->addr = slave_addr;
    test_setup.gyro_sens = sensitivity;
    test_setup.gyro_fs = (int)(32768.f / sensitivity);
    test_setup.packet_thresh = p_thresh;
    test_setup.total_timing_tol = total_time_tol;
    test_setup.bias_thresh = bias_thresh;
    test_setup.rms_threshSq = rms_thresh * rms_thresh;
    test_setup.sp_shift_thresh = sp_shift_thresh;
    test_setup.accel_samples = accel_samples;
}

#define X   (0)
#define Y   (1)
#define Z   (2)

#ifdef CONFIG_MPU_SENSORS_MPU3050
/**
 *  @brief  Test the gyroscope sensor.
 *          Implements the core logic of the MPU Self Test.
 *          Produces the PASS/FAIL result. Loads the calculated gyro biases
 *          and temperature datum into the corresponding pointers.
 *  @param  mlsl_handle
 *              serial interface handle to allow serial communication with the
 *              device, both gyro and accelerometer.
 *  @param  gyro_biases
 *              output pointer to store the initial bias calculation provided
 *              by the MPU Self Test.  Requires 3 elements for gyro X, Y,
 *              and Z.
 *  @param  temp_avg
 *              output pointer to store the initial average temperature as
 *              provided by the MPU Self Test.
 *  @param  perform_full_test
 *              If 1:
 *              calculates offset, drive frequency, and noise and compare it
 *              against set thresholds.
 *              Report also the final result using a bit-mask like error code
 *              as explained in return value description.
 *              When 0:
 *              skip the noise and drive frequency calculation and pass/fail
 *              assessment; simply calculates the gyro biases.
 *
 *  @return 0 on success.
 *          On error, the return value is a bitmask representing:
 *          0, 1, 2     Failures with PLLs on X, Y, Z gyros respectively
 *                      (decimal values will be 1, 2, 4 respectively).
 *          3, 4, 5     Excessive offset with X, Y, Z gyros respectively
 *                      (decimal values will be 8, 16, 32 respectively).
 *          6, 7, 8     Excessive noise with X, Y, Z gyros respectively
 *                      (decimal values will be 64, 128, 256 respectively).
 *          9           If any of the RMS noise values is zero, it could be
 *                      due to a non-functional gyro or FIFO/register failure.
 *                      (decimal value will be 512).
 *                      (decimal values will be 1024, 2048, 4096 respectively).
 */
int inv_test_gyro_3050(void *mlsl_handle,
                       short gyro_biases[3], short *temp_avg,
                       uint_fast8_t perform_full_test)
{
    int retVal = 0;
    inv_error_t result;

    int total_count = 0;
    int total_count_axis[3] = {0, 0, 0};
    int packet_count;
    short x[DEF_TEST_TIME_PER_AXIS / 8 * 4] = {0};
    short y[DEF_TEST_TIME_PER_AXIS / 8 * 4] = {0};
    short z[DEF_TEST_TIME_PER_AXIS / 8 * 4] = {0};
    unsigned char regs[7];

    int temperature;
    float Avg[3];
    float RMS[3];
    int i, j, tmp;
    char tmpStr[200];

    temperature = 0;

    /* sample rate = 8ms */
    result = inv_serial_single_write(
                mlsl_handle, mputestCfgPtr->addr,
                MPUREG_SMPLRT_DIV, 0x07);
    CHECK_TEST_ERROR(result);

    regs[0] = 0x03; /* filter = 42Hz, analog_sample rate = 1 KHz */
    switch (DEF_GYRO_FULLSCALE) {
        case 2000:
            regs[0] |= 0x18;
            break;
        case 1000:
            regs[0] |= 0x10;
            break;
        case 500:
            regs[0] |= 0x08;
            break;
        case 250:
        default:
            regs[0] |= 0x00;
            break;
    }
    result = inv_serial_single_write(
                mlsl_handle, mputestCfgPtr->addr,
                MPUREG_DLPF_FS_SYNC, regs[0]);
    CHECK_TEST_ERROR(result);
    result = inv_serial_single_write(
                mlsl_handle, mputestCfgPtr->addr,
                MPUREG_INT_CFG, 0x00);
    CHECK_TEST_ERROR(result);

    /* 1st, timing test */
    for (j = 0; j < 3; j++) {

        MPL_LOGI("Collecting gyro data from %s gyro PLL\n", a_name[j]);

        /* turn on all gyros, use gyro X for clocking
           Set to Y and Z for 2nd and 3rd iteration */
        result = inv_serial_single_write(
                    mlsl_handle, mputestCfgPtr->addr,
                    MPUREG_PWR_MGM, j + 1);
        CHECK_TEST_ERROR(result);

        /* wait for 2 ms after switching clock source */
        usleep(2000);

        /* we will enable XYZ gyro in FIFO and nothing else */
        result = inv_serial_single_write(
                    mlsl_handle, mputestCfgPtr->addr,
                    MPUREG_FIFO_EN2, 0x00);
        CHECK_TEST_ERROR(result);
        /* enable/reset FIFO */
        result = inv_serial_single_write(
                    mlsl_handle, mputestCfgPtr->addr,
                    MPUREG_USER_CTRL, BIT_FIFO_EN | BIT_FIFO_RST);
        CHECK_TEST_ERROR(result);

        tmp = (int)(test_setup.test_time_per_axis / 600);
        while (tmp-- > 0) {
            /* enable XYZ gyro in FIFO and nothing else */
            result = inv_serial_single_write(mlsl_handle,
                        mputestCfgPtr->addr, MPUREG_FIFO_EN1,
                        BIT_GYRO_XOUT | BIT_GYRO_YOUT | BIT_GYRO_ZOUT);
            CHECK_TEST_ERROR(result);

            /* wait for 600 ms for data */
            usleep(600000);

            /* stop storing gyro in the FIFO */
            result = inv_serial_single_write(
                        mlsl_handle, mputestCfgPtr->addr,
                        MPUREG_FIFO_EN1, 0x00);
            CHECK_TEST_ERROR(result);

            /* Getting number of bytes in FIFO */
            result = inv_serial_read(
                           mlsl_handle, mputestCfgPtr->addr,
                           MPUREG_FIFO_COUNTH, 2, dataout);
            CHECK_TEST_ERROR(result);
            /* number of 6 B packets in the FIFO */
            packet_count = CHARS_TO_SHORT(dataout) / 6;
            sprintf(tmpStr, "Packet Count: %d - ", packet_count);

            if ( abs(packet_count - test_setup.packet_thresh)
                        <=      /* Within +/- total_timing_tol % range */
                     test_setup.total_timing_tol * test_setup.packet_thresh) {
                for (i = 0; i < packet_count; i++) {
                    /* getting FIFO data */
                    result = inv_serial_read_fifo(mlsl_handle,
                                mputestCfgPtr->addr, 6, dataout);
                    CHECK_TEST_ERROR(result);
                    x[total_count + i] = CHARS_TO_SHORT(&dataout[0]);
                    y[total_count + i] = CHARS_TO_SHORT(&dataout[2]);
                    z[total_count + i] = CHARS_TO_SHORT(&dataout[4]);
                    if (VERBOSE_OUT) {
                        MPL_LOGI("Gyros %-4d    : %+13d %+13d %+13d\n",
                                    total_count + i, x[total_count + i],
                                    y[total_count + i], z[total_count + i]);
                    }
                }
                total_count += packet_count;
                total_count_axis[j] += packet_count;
                sprintf(tmpStr, "%sOK", tmpStr);
            } else {
                if (perform_full_test)
                    retVal |= 1 << j;
                sprintf(tmpStr, "%sNOK - samples ignored", tmpStr);
            }
            MPL_LOGI("%s\n", tmpStr);
        }

        /* remove gyros from FIFO */
        result = inv_serial_single_write(
                    mlsl_handle, mputestCfgPtr->addr,
                    MPUREG_FIFO_EN1, 0x00);
        CHECK_TEST_ERROR(result);

        /* Read Temperature */
        result = inv_serial_read(mlsl_handle, mputestCfgPtr->addr,
                    MPUREG_TEMP_OUT_H, 2, dataout);
        CHECK_TEST_ERROR(result);
        temperature += (short)CHARS_TO_SHORT(dataout);
    }

    MPL_LOGI("\n");
    MPL_LOGI("Total %d samples\n", total_count);
    MPL_LOGI("\n");

    /* 2nd, check bias from X and Y PLL clock source */
    tmp = total_count != 0 ? total_count : 1;
    for (i = 0,
            Avg[X] = .0f, Avg[Y] = .0f, Avg[Z] = .0f;
         i < total_count; i++) {
        Avg[X] += 1.f * x[i] / tmp;
        Avg[Y] += 1.f * y[i] / tmp;
        Avg[Z] += 1.f * z[i] / tmp;
    }
    MPL_LOGI("bias          : %+13.3f %+13.3f %+13.3f (LSB)\n",
             Avg[X], Avg[Y], Avg[Z]);
    if (VERBOSE_OUT) {
        MPL_LOGI("              : %+13.3f %+13.3f %+13.3f (dps)\n",
                 Avg[X] / adjGyroSens,
                 Avg[Y] / adjGyroSens,
                 Avg[Z] / adjGyroSens);
    }
    if(perform_full_test) {
        for (j = 0; j < 3; j++) {
            if (fabs(Avg[j]) > test_setup.bias_thresh) {
                MPL_LOGI("%s-Gyro bias (%.0f) exceeded threshold "
                         "(threshold = %d)\n",
                         a_name[j], Avg[j], test_setup.bias_thresh);
                retVal |= 1 << (3+j);
            }
        }
    }

    /* 3rd, check RMS */
    if (perform_full_test) {
        for (i = 0,
                RMS[X] = 0.f, RMS[Y] = 0.f, RMS[Z] = 0.f;
             i < total_count; i++) {
            RMS[X] += (x[i] - Avg[X]) * (x[i] - Avg[X]);
            RMS[Y] += (y[i] - Avg[Y]) * (y[i] - Avg[Y]);
            RMS[Z] += (z[i] - Avg[Z]) * (z[i] - Avg[Z]);
        }
        for (j = 0; j < 3; j++) {
            if (RMS[j] > test_setup.rms_threshSq * total_count) {
                MPL_LOGI("%s-Gyro RMS (%.2f) exceeded threshold "
                         "(threshold = %.2f)\n",
                         a_name[j], sqrt(RMS[j] / total_count),
                         sqrt(test_setup.rms_threshSq));
                retVal |= 1 << (6+j);
            }
        }

        MPL_LOGI("RMS           : %+13.3f %+13.3f %+13.3f (LSB-rms)\n",
                    sqrt(RMS[X] / total_count),
                    sqrt(RMS[Y] / total_count),
                    sqrt(RMS[Z] / total_count));
        if (VERBOSE_OUT) {
            MPL_LOGI("RMS ^ 2       : %+13.3f %+13.3f %+13.3f\n",
                        RMS[X] / total_count,
                        RMS[Y] / total_count,
                        RMS[Z] / total_count);
        }

        if (RMS[X] == 0 || RMS[Y] == 0 || RMS[Z] == 0) {
            /*  If any of the RMS noise value returns zero,
                then we might have dead gyro or FIFO/register failure,
                the part is sleeping, or the part is not responsive */
            retVal |= 1 << 9;
        }
    }

    /* 4th, temperature average */
    temperature /= 3;
    if (VERBOSE_OUT)
        MPL_LOGI("Temperature   : %+13.3f %13s %13s (deg. C)\n",
                 SHORT_TO_TEMP_C(temperature), "", "");

    /* load into final storage */
    *temp_avg = (short)temperature;
    gyro_biases[X] = FLOAT_TO_SHORT(Avg[X]);
    gyro_biases[Y] = FLOAT_TO_SHORT(Avg[Y]);
    gyro_biases[Z] = FLOAT_TO_SHORT(Avg[Z]);

    return retVal;
}

#else /* CONFIG_MPU_SENSORS_MPU3050 */

/**
 *  @brief  Test the gyroscope sensor.
 *          Implements the core logic of the MPU Self Test but does not provide
 *          a PASS/FAIL output as in the MPU-3050 implementation.
 *  @param  mlsl_handle
 *              serial interface handle to allow serial communication with the
 *              device, both gyro and accelerometer.
 *  @param  gyro_biases
 *              output pointer to store the initial bias calculation provided
 *              by the MPU Self Test.  Requires 3 elements for gyro X, Y,
 *              and Z.
 *  @param  temp_avg
 *              output pointer to store the initial average temperature as
 *              provided by the MPU Self Test.
 *
 *  @return 0 on success.
 *          A non-zero error code on error.
 */
int inv_test_gyro_6050(void *mlsl_handle,
                       short gyro_biases[3], short *temp_avg)
{
    inv_error_t result;

    int total_count = 0;
    int total_count_axis[3] = {0, 0, 0};
    int packet_count;
    short x[DEF_TEST_TIME_PER_AXIS / 8 * 4] = {0};
    short y[DEF_TEST_TIME_PER_AXIS / 8 * 4] = {0};
    short z[DEF_TEST_TIME_PER_AXIS / 8 * 4] = {0};
    unsigned char regs[7];

    int temperature = 0;
    float Avg[3];
    int i, j, tmp;
    char tmpStr[200];

    /* sample rate = 8ms */
    result = inv_serial_single_write(
                mlsl_handle, mputestCfgPtr->addr,
                MPUREG_SMPLRT_DIV, 0x07);
    if (result) {
        LOG_RESULT_LOCATION(result);
        return result;
    }

    regs[0] = 0x03; /* filter = 42Hz, analog_sample rate = 1 KHz */
    switch (DEF_GYRO_FULLSCALE) {
        case 2000:
            regs[0] |= 0x18;
            break;
        case 1000:
            regs[0] |= 0x10;
            break;
        case 500:
            regs[0] |= 0x08;
            break;
        case 250:
        default:
            regs[0] |= 0x00;
            break;
    }
    result = inv_serial_single_write(
                mlsl_handle, mputestCfgPtr->addr,
                MPUREG_CONFIG, regs[0]);
    if (result) {
        LOG_RESULT_LOCATION(result);
        return result;
    }
    result = inv_serial_single_write(
                mlsl_handle, mputestCfgPtr->addr,
                MPUREG_INT_ENABLE, 0x00);
    if (result) {
        LOG_RESULT_LOCATION(result);
        return result;
    }

    /* 1st, timing test */
    for (j = 0; j < 3; j++) {
        MPL_LOGI("Collecting gyro data from %s gyro PLL\n", a_name[j]);

        /* turn on all gyros, use gyro X for clocking
           Set to Y and Z for 2nd and 3rd iteration */
#ifdef CONFIG_MPU_SENSORS_MPU6050A2
        result = inv_serial_single_write(
                    mlsl_handle, mputestCfgPtr->addr,
                    MPUREG_PWR_MGMT_1, BITS_PWRSEL | (j + 1));
#else
        result = inv_serial_single_write(
                    mlsl_handle, mputestCfgPtr->addr,
                    MPUREG_PWR_MGMT_1, j + 1);
#endif
        if (result) {
            LOG_RESULT_LOCATION(result);
            return result;
        }

        /* wait for 2 ms after switching clock source */
        usleep(2000);

        /* enable/reset FIFO */
        result = inv_serial_single_write(
                    mlsl_handle, mputestCfgPtr->addr,
                    MPUREG_USER_CTRL, BIT_FIFO_EN | BIT_FIFO_RST);
        if (result) {
            LOG_RESULT_LOCATION(result);
            return result;
        }

        tmp = (int)(test_setup.test_time_per_axis / 600);
        while (tmp-- > 0) {
            /* enable XYZ gyro in FIFO and nothing else */
            result = inv_serial_single_write(mlsl_handle,
                        mputestCfgPtr->addr, MPUREG_FIFO_EN,
                        BIT_GYRO_XOUT | BIT_GYRO_YOUT | BIT_GYRO_ZOUT);
            if (result) {
                LOG_RESULT_LOCATION(result);
                return result;
            }

            /* wait for 600 ms for data */
            usleep(600000);
            /* stop storing gyro in the FIFO */
            result = inv_serial_single_write(
                        mlsl_handle, mputestCfgPtr->addr,
                        MPUREG_FIFO_EN, 0x00);
            if (result) {
                LOG_RESULT_LOCATION(result);
                return result;
            }
            /* Getting number of bytes in FIFO */
            result = inv_serial_read(
                           mlsl_handle, mputestCfgPtr->addr,
                           MPUREG_FIFO_COUNTH, 2, dataout);
            if (result) {
                LOG_RESULT_LOCATION(result);
                return result;
            }
            /* number of 6 B packets in the FIFO */
            packet_count = CHARS_TO_SHORT(dataout) / 6;
            sprintf(tmpStr, "Packet Count: %d - ", packet_count);

            if (abs(packet_count - test_setup.packet_thresh)
                        <=      /* Within +/- total_timing_tol % range */
                     test_setup.total_timing_tol * test_setup.packet_thresh) {
                for (i = 0; i < packet_count; i++) {
                    /* getting FIFO data */
                    result = inv_serial_read_fifo(mlsl_handle,
                                mputestCfgPtr->addr, 6, dataout);
                    if (result) {
                        LOG_RESULT_LOCATION(result);
                        return result;
                    }
                    x[total_count + i] = CHARS_TO_SHORT(&dataout[0]);
                    y[total_count + i] = CHARS_TO_SHORT(&dataout[2]);
                    z[total_count + i] = CHARS_TO_SHORT(&dataout[4]);
                    if (VERBOSE_OUT) {
                        MPL_LOGI("Gyros %-4d    : %+13d %+13d %+13d\n",
                                    total_count + i, x[total_count + i],
                                    y[total_count + i], z[total_count + i]);
                    }
                }
                total_count += packet_count;
                total_count_axis[j] += packet_count;
                sprintf(tmpStr, "%sOK", tmpStr);
            } else {
                sprintf(tmpStr, "%sNOK - samples ignored", tmpStr);
            }
            MPL_LOGI("%s\n", tmpStr);
        }

        /* remove gyros from FIFO */
        result = inv_serial_single_write(
                    mlsl_handle, mputestCfgPtr->addr,
                    MPUREG_FIFO_EN, 0x00);
        if (result) {
            LOG_RESULT_LOCATION(result);
            return result;
        }

        /* Read Temperature */
        result = inv_serial_read(mlsl_handle, mputestCfgPtr->addr,
                    MPUREG_TEMP_OUT_H, 2, dataout);
        if (result) {
            LOG_RESULT_LOCATION(result);
            return result;
        }
        temperature += (short)CHARS_TO_SHORT(dataout);
    }

    MPL_LOGI("\n");
    MPL_LOGI("Total %d samples\n", total_count);
    MPL_LOGI("\n");

    /* 2nd, check bias from X and Y PLL clock source */
    tmp = total_count != 0 ? total_count : 1;
    for (i = 0,
            Avg[X] = .0f, Avg[Y] = .0f, Avg[Z] = .0f;
         i < total_count; i++) {
        Avg[X] += 1.f * x[i] / tmp;
        Avg[Y] += 1.f * y[i] / tmp;
        Avg[Z] += 1.f * z[i] / tmp;
    }
    MPL_LOGI("bias          : %+13.3f %+13.3f %+13.3f (LSB)\n",
             Avg[X], Avg[Y], Avg[Z]);
    if (VERBOSE_OUT) {
        MPL_LOGI("              : %+13.3f %+13.3f %+13.3f (dps)\n",
                 Avg[X] / adjGyroSens,
                 Avg[Y] / adjGyroSens,
                 Avg[Z] / adjGyroSens);
    }

    temperature /= 3;
    if (VERBOSE_OUT)
        MPL_LOGI("Temperature   : %+13.3f %13s %13s (deg. C)\n",
                 SHORT_TO_TEMP_C(temperature), "", "");

    /* load into final storage */
    *temp_avg = (short)temperature;
    gyro_biases[X] = FLOAT_TO_SHORT(Avg[X]);
    gyro_biases[Y] = FLOAT_TO_SHORT(Avg[Y]);
    gyro_biases[Z] = FLOAT_TO_SHORT(Avg[Z]);

    return INV_SUCCESS;
}

#endif /* CONFIG_MPU_SENSORS_MPU3050 */

#ifdef TRACK_IDS
/**
 *  @internal
 *  @brief  Retrieve the unique MPU device identifier from the internal OTP
 *          bank 0 memory.
 *  @param  mlsl_handle
 *              serial interface handle to allow serial communication with the
 *              device, both gyro and accelerometer.
 *  @return 0 on success, a non-zero error code from the serial layer on error.
 */
static inv_error_t test_get_mpu_id(void *mlsl_handle)
{
    inv_error_t result;
    unsigned char otp0[8];


    result =
        inv_serial_read_mem(mlsl_handle, mputestCfgPtr->addr,
            (BIT_PRFTCH_EN | BIT_CFG_USER_BANK | MPU_MEM_OTP_BANK_0) << 8 |
            0x00, 6, otp0);
    if (result)
        goto close;

    MPL_LOGI("\n");
    MPL_LOGI("DIE_ID   : %06X\n",
                ((int)otp0[1] << 8 | otp0[0]) & 0x1fff);
    MPL_LOGI("WAFER_ID : %06X\n",
                (((int)otp0[2] << 8 | otp0[1]) & 0x03ff ) >> 5);
    MPL_LOGI("A_LOT_ID : %06X\n",
                ( ((int)otp0[4] << 16 | (int)otp0[3] << 8 |
                otp0[2]) & 0x3ffff) >> 2);
    MPL_LOGI("W_LOT_ID : %06X\n",
                ( ((int)otp0[5] << 8 | otp0[4]) & 0x3fff) >> 2);
    MPL_LOGI("WP_ID    : %06X\n",
                ( ((int)otp0[6] << 8 | otp0[5]) & 0x03ff) >> 7);
    MPL_LOGI("REV_ID   : %06X\n", otp0[6] >> 2);
    MPL_LOGI("\n");

close:
    result =
        inv_serial_single_write(mlsl_handle, mputestCfgPtr->addr, MPUREG_BANK_SEL, 0x00);
    return result;
}
#endif /* TRACK_IDS */

/**
 *  @brief  If requested via inv_test_setup_accel(), test the accelerometer biases
 *          and calculate the necessary bias correction.
 *  @param  mlsl_handle
 *              serial interface handle to allow serial communication with the
 *              device, both gyro and accelerometer.
 *  @param  bias
 *              output pointer to store the initial bias calculation provided
 *              by the MPU Self Test.  Requires 3 elements to store accel X, Y,
 *              and Z axis bias.
 *  @param  perform_full_test
 *              If 1:
 *              calculates offsets and noise and compare it against set
 *              thresholds. The final exist status will reflect if any of the
 *              value is outside of the expected range.
 *              When 0;
 *              skip the noise calculation and pass/fail assessment; simply
 *              calculates the accel biases.
 *
 *  @return 0 on success. A non-zero error code on error.
 */
int inv_test_accel(void *mlsl_handle,
                   short *bias, long gravity,
                   uint_fast8_t perform_full_test)
{
    int i;

    short *p_vals;
    float x = 0.f, y = 0.f, z = 0.f, zg = 0.f;
    float RMS[3];
    float accelRmsThresh = 1000000.f; /* enourmous so that the test always
                                         passes - future deployment */
    unsigned short res;
    unsigned long orig_requested_sensors;
    struct mpu_platform_data *mputestPData = mputestCfgPtr->pdata;

    p_vals = (short*)inv_malloc(sizeof(short) * 3 * test_setup.accel_samples);

    /* load the slave descr from the getter */
    if (mputestPData->accel.get_slave_descr == NULL) {
        MPL_LOGI("\n");
        MPL_LOGI("No accelerometer configured\n");
        return 0;
    }
    if (mputestCfgPtr->accel == NULL) {
        MPL_LOGI("\n");
        MPL_LOGI("No accelerometer configured\n");
        return 0;
    }

    /* resume the accel */
    orig_requested_sensors = mputestCfgPtr->requested_sensors;
    mputestCfgPtr->requested_sensors = INV_THREE_AXIS_ACCEL | INV_THREE_AXIS_GYRO;
    res = inv_mpu_resume(mputestCfgPtr,
                         mlsl_handle, NULL, NULL, NULL,
                         mputestCfgPtr->requested_sensors);
    if(res != INV_SUCCESS)
        goto accel_error;

    /* wait at least a sample cycle for the
       accel data to be retrieved by MPU */
    inv_sleep(inv_mpu_get_sampling_period_us(mputestCfgPtr) / 1000);

    /* collect the samples  */
    for(i = 0; i < test_setup.accel_samples; i++) {
        short *vals = &p_vals[3 * i];
        if (test_get_data(mlsl_handle, mputestCfgPtr, vals)) {
            goto accel_error;
        }
        x += 1.f * vals[X] / test_setup.accel_samples;
        y += 1.f * vals[Y] / test_setup.accel_samples;
        z += 1.f * vals[Z] / test_setup.accel_samples;
    }

    mputestCfgPtr->requested_sensors = orig_requested_sensors;
    res = inv_mpu_suspend(mputestCfgPtr,
                          mlsl_handle, NULL, NULL, NULL,
                          INV_ALL_SENSORS);
    if (res != INV_SUCCESS)
        goto accel_error;

    MPL_LOGI("Accel biases  : %+13.3f %+13.3f %+13.3f (LSB)\n", x, y, z);
    if (VERBOSE_OUT) {
        MPL_LOGI("Accel biases  : %+13.3f %+13.3f %+13.3f (gee)\n",
                    x / gravity, y / gravity, z / gravity);
    }

    bias[0] = FLOAT_TO_SHORT(x);
    bias[1] = FLOAT_TO_SHORT(y);
    zg = z - g_z_sign * gravity;
    bias[2] = FLOAT_TO_SHORT(zg);

    MPL_LOGI("Accel correct.: %+13d %+13d %+13d (LSB)\n",
             bias[0], bias[1], bias[2]);
    if (VERBOSE_OUT) {
        MPL_LOGI("Accel correct.: "
               "%+13.3f %+13.3f %+13.3f (gee)\n",
                    1.f * bias[0] / gravity,
                    1.f * bias[1] / gravity,
                    1.f * bias[2] / gravity);
    }

    if (perform_full_test) {
        /* accel RMS - for now the threshold is only indicative */
        for (i = 0,
                 RMS[X] = 0.f, RMS[Y] = 0.f, RMS[Z] = 0.f;
             i <  test_setup.accel_samples; i++) {
            short *vals = &p_vals[3 * i];
            RMS[X] += (vals[X] - x) * (vals[X] - x);
            RMS[Y] += (vals[Y] - y) * (vals[Y] - y);
            RMS[Z] += (vals[Z] - z) * (vals[Z] - z);
        }
        for (i = 0; i < 3; i++) {
            if (RMS[i] >  accelRmsThresh * accelRmsThresh
                            * test_setup.accel_samples) {
                MPL_LOGI("%s-Accel RMS (%.2f) exceeded threshold "
                         "(threshold = %.2f)\n",
                         a_name[i], sqrt(RMS[i] / test_setup.accel_samples),
                         accelRmsThresh);
                goto accel_error;
            }
        }
        MPL_LOGI("RMS           : %+13.3f %+13.3f %+13.3f (LSB-rms)\n",
                 sqrt(RMS[X] / DEF_N_ACCEL_SAMPLES),
                 sqrt(RMS[Y] / DEF_N_ACCEL_SAMPLES),
                 sqrt(RMS[Z] / DEF_N_ACCEL_SAMPLES));
    }

    return 0; /* success */

accel_error:  /* error */
    bias[0] = bias[1] = bias[2] = 0;
    return 1;
}

/**
 *  @brief  an user-space substitute of the power management function(s)
 *          in mldl_cfg.c for self test usage.
 *          Wake up and sleep the device, whether that is MPU3050, MPU6050A2,
 *          or MPU6050B1.
 *  @param  mlsl_handle
 *              a file handle for the serial communication port used to
 *              communicate with the MPU device.
 *  @param  power_level
 *              the power state to change the device into. Currently only 0 or
 *              1 are supported, for sleep and full-power respectively.
 *  @return 0 on success; a non-zero error code on error.
 */
static inv_error_t inv_device_power_mgmt(void *mlsl_handle,
                                         uint_fast8_t power_level)
{
    inv_error_t result;
    static unsigned char pwr_mgm;
    unsigned char b;

    if (power_level != 0 && power_level != 1) {
        return INV_ERROR_INVALID_PARAMETER;
    }

    if (power_level) {
        result = inv_serial_read(
                    mlsl_handle, mputestCfgPtr->addr,
                    MPUREG_PWR_MGM, 1, &pwr_mgm);
        if (result) {
            LOG_RESULT_LOCATION(result);
            return result;
        }

        /* reset */
        result = inv_serial_single_write(
                    mlsl_handle, mputestCfgPtr->addr,
                    MPUREG_PWR_MGM, pwr_mgm | BIT_H_RESET);
#ifndef CONFIG_MPU_SENSORS_MPU6050A2
        if (result) {
            LOG_RESULT_LOCATION(result);
            return result;
        }
#endif
        inv_sleep(5);

        /* re-read power mgmt reg -
           it may have reset after H_RESET is applied */
        result = inv_serial_read(
                    mlsl_handle, mputestCfgPtr->addr,
                    MPUREG_PWR_MGM, 1, &b);
        if (result) {
            LOG_RESULT_LOCATION(result);
            return result;
        }

        /* wake up */
#ifdef CONFIG_MPU_SENSORS_MPU6050A2
        if ((b & BITS_PWRSEL) != BITS_PWRSEL) {
            result = inv_serial_single_write(
                        mlsl_handle, mputestCfgPtr->addr,
                        MPUREG_PWR_MGM, BITS_PWRSEL);
            if (result) {
                LOG_RESULT_LOCATION(result);
                return result;
            }
        }
#else
        if (pwr_mgm & BIT_SLEEP) {
            result = inv_serial_single_write(
                        mlsl_handle, mputestCfgPtr->addr,
                        MPUREG_PWR_MGM, 0x00);
            if (result) {
                LOG_RESULT_LOCATION(result);
                return result;
            }
        }
#endif
        inv_sleep(60);

#if defined(CONFIG_MPU_SENSORS_MPU6050A2) || \
    defined(CONFIG_MPU_SENSORS_MPU6050B1)
        result = inv_serial_single_write(
                    mlsl_handle, mputestCfgPtr->addr,
                    MPUREG_INT_PIN_CFG, BIT_BYPASS_EN);
        if (result) {
            LOG_RESULT_LOCATION(result);
            return result;
        }
#endif
    } else {
        /* restore the power state the part was found in */
#ifdef CONFIG_MPU_SENSORS_MPU6050A2
        if ((pwr_mgm & BITS_PWRSEL) != BITS_PWRSEL) {
            result = inv_serial_single_write(
                        mlsl_handle, mputestCfgPtr->addr,
                        MPUREG_PWR_MGM, pwr_mgm);
            if (result) {
                LOG_RESULT_LOCATION(result);
                return result;
            }
        }
#else
        if (pwr_mgm & BIT_SLEEP) {
            result = inv_serial_single_write(
                        mlsl_handle, mputestCfgPtr->addr,
                        MPUREG_PWR_MGM, pwr_mgm);
            if (result) {
                LOG_RESULT_LOCATION(result);
                return result;
            }
        }
#endif
    }

    return INV_SUCCESS;
}

/**
 *  @brief  The main entry point of the MPU Self Test, triggering the run of
 *          the single tests, for gyros and accelerometers.
 *          Prepares the MPU for the test, taking the device out of low power
 *          state if necessary, switching the MPU secondary I2C interface into
 *          bypass mode and restoring the original power state at the end of
 *          the test.
 *          This function is also responsible for encoding the output of each
 *          test in the correct format as it is stored on the file/medium of
 *          choice (according to inv_serial_write_cal() function).
 *          The format needs to stay perfectly consistent with the one expected
 *          by the corresponding loader in ml_stored_data.c; currectly the
 *          loaded in use is inv_load_cal_V1 (record type 1 - initial
 *          calibration).
 *
 *  @param  mlsl_handle
 *              serial interface handle to allow serial communication with the
 *              device, both gyro and accelerometer.
 *  @param  provide_result
 *              If 1:
 *              perform and analyze the offset, drive frequency, and noise
 *              calculation and compare it against set threshouds. Report
 *              also the final result using a bit-mask like error code as
 *              described in the inv_test_gyro() function.
 *              When 0:
 *              skip the noise and drive frequency calculation and pass/fail
 *              assessment. It simply calculates the gyro and accel biases.
 *              NOTE: for MPU6050 devices, this parameter is currently
 *              ignored.
 *
 *  @return 0 on success.  A non-zero error code on error.
 *          Propagates the errors from the tests up to the caller.
 */
int inv_mpu_test(void *mlsl_handle, uint_fast8_t provide_result)
{
    int result = 0;

    short temp_avg;
    short gyro_biases[3] = {0, 0, 0};
    short accel_biases[3] = {0, 0, 0};

    unsigned long testStart = inv_get_tick_count();
    long accelSens[3] = {0};
    int ptr;
    int tmp;
    long long lltmp;
    uint32_t chk;

    MPL_LOGI("Collecting %d groups of 600 ms samples for each axis\n",
                DEF_TEST_TIME_PER_AXIS / 600);
    MPL_LOGI("\n");

    result = inv_device_power_mgmt(mlsl_handle, TRUE);

#ifdef TRACK_IDS
    result = test_get_mpu_id(mlsl_handle);
    if (result != INV_SUCCESS) {
        MPL_LOGI("Could not read the device's unique ID\n");
        MPL_LOGI("\n");
        return result;
    }
#endif

    /* adjust the gyro sensitivity according to the gyro_sens_trim value */
    adjGyroSens = test_setup.gyro_sens * mputestCfgPtr->gyro_sens_trim / 131.f;
    test_setup.gyro_fs = (int)(32768.f / adjGyroSens);

    /* collect gyro and temperature data */
#ifdef CONFIG_MPU_SENSORS_MPU3050
    result = inv_test_gyro_3050(mlsl_handle,
                gyro_biases, &temp_avg, provide_result);
#else
    MPL_LOGW_IF(provide_result,
                "Self Test for MPU-6050 devices is for bias correction only: "
                "no test PASS/FAIL result will be provided\n");
    result = inv_test_gyro_6050(mlsl_handle, gyro_biases, &temp_avg);
#endif

    MPL_LOGI("\n");
    MPL_LOGI("Test time : %ld ms\n", inv_get_tick_count() - testStart);
    if (result)
        return result;

    /* collect accel data.  if this step is skipped,
       ensure the array still contains zeros. */
    if (mputestCfgPtr->accel != NULL) {
        float fs;
        RANGE_FIXEDPOINT_TO_FLOAT(mputestCfgPtr->accel->range, fs);
        accelSens[0] = (long)(32768L / fs);
        accelSens[1] = (long)(32768L / fs);
        accelSens[2] = (long)(32768L / fs);
#if defined CONFIG_MPU_SENSORS_MPU6050B1
        if (MPL_PROD_KEY(mputestCfgPtr->product_id,
                mputestCfgPtr->product_revision) == MPU_PRODUCT_KEY_B1_E1_5) {
            accelSens[2] /= 2;
        } else {
            unsigned short trim_corr = 16384 / mputestCfgPtr->accel_sens_trim;
            accelSens[0] /= trim_corr;
            accelSens[1] /= trim_corr;
            accelSens[2] /= trim_corr;
        }
#endif
    } else {
        /* would be 0, but 1 to avoid divide-by-0 below */
        accelSens[0] = accelSens[1] = accelSens[2] = 1;
    }
#ifdef CONFIG_MPU_SENSORS_MPU3050
    result = inv_test_accel(mlsl_handle, accel_biases, accelSens[2],
                            provide_result);
#else
    result = inv_test_accel(mlsl_handle, accel_biases, accelSens[2],
                            FALSE);
#endif
    if (result)
        return result;

    result = inv_device_power_mgmt(mlsl_handle, FALSE);
    if (result)
        return result;

    ptr = 0;
    dataStore[ptr++] = 0;       /* total len of factory cal */
    dataStore[ptr++] = 0;
    dataStore[ptr++] = 0;
    dataStore[ptr++] = ML_INIT_CAL_LEN;
    dataStore[ptr++] = 0;
    dataStore[ptr++] = 5;       /* record type 5 - initial calibration */

    tmp = temp_avg;             /* temperature */
    if (tmp < 0) tmp += 2 << 16;
    USHORT_TO_CHARS(&dataStore[ptr], tmp);
    ptr += 2;

    /* NOTE : 2 * test_setup.gyro_fs == 65536 / (32768 / test_setup.gyro_fs) */
    lltmp = (long)gyro_biases[0] * 2 * test_setup.gyro_fs; /* x gyro avg */
    if (lltmp < 0) lltmp += 1LL << 32;
    UINT_TO_CHARS(&dataStore[ptr], (uint32_t)lltmp);
    ptr += 4;
    lltmp = (long)gyro_biases[1] * 2 * test_setup.gyro_fs; /* y gyro avg */
    if (lltmp < 0) lltmp += 1LL << 32;
    UINT_TO_CHARS(&dataStore[ptr], (uint32_t)lltmp);
    ptr += 4;
    lltmp = (long)gyro_biases[2] * 2 * test_setup.gyro_fs; /* z gyro avg */
    if (lltmp < 0) lltmp += 1LL << 32;
    UINT_TO_CHARS(&dataStore[ptr], (uint32_t)lltmp);
    ptr += 4;

    lltmp = (long)accel_biases[0] * 65536L / accelSens[0]; /* x accel avg */
    if (lltmp < 0) lltmp += 1LL << 32;
    UINT_TO_CHARS(&dataStore[ptr], (uint32_t)lltmp);
    ptr += 4;
    lltmp = (long)accel_biases[1] * 65536L / accelSens[1]; /* y accel avg */
    if (lltmp < 0) lltmp += 1LL << 32;
    UINT_TO_CHARS(&dataStore[ptr], (uint32_t)lltmp);
    ptr += 4;
    lltmp = (long)accel_biases[2] * 65536L / accelSens[2]; /* z accel avg */
    if (lltmp < 0) lltmp += 1LL << 32;
    UINT_TO_CHARS(&dataStore[ptr], (uint32_t)lltmp);
    ptr += 4;

    /* add a checksum for data */
    chk = inv_checksum(
        dataStore + INV_CAL_HDR_LEN,
        ML_INIT_CAL_LEN - INV_CAL_HDR_LEN - INV_CAL_CHK_LEN);
    UINT_TO_CHARS(&dataStore[ptr], chk);
    ptr += 4;

    if (ptr != ML_INIT_CAL_LEN) {
        MPL_LOGI("Invalid calibration data length: exp %d, got %d\n",
                    ML_INIT_CAL_LEN, ptr);
        return -1;
    }

    return result;
}

/**
 *  @brief  The main test API. Runs the MPU Self Test and, if successful,
 *          stores the encoded initial calibration data on the final storage
 *          medium of choice (cfr. inv_serial_write_cal() and the MLCAL_FILE
 *          define in your mlsl implementation).
 *
 *  @param  mlsl_handle
 *              serial interface handle to allow serial communication with the
 *              device, both gyro and accelerometer.
 *  @param  provide_result
 *              If 1:
 *              perform and analyze the offset, drive frequency, and noise
 *              calculation and compare it against set threshouds. Report
 *              also the final result using a bit-mask like error code as
 *              described in the inv_test_gyro() function.
 *              When 0:
 *              skip the noise and drive frequency calculation and pass/fail
 *              assessment. It simply calculates the gyro and accel biases.
 *
 *  @return 0 on success or a non-zero error code from the callees on error.
 */
inv_error_t inv_factory_calibrate(void *mlsl_handle,
                                  uint_fast8_t provide_result)
{
    int result;

    result = inv_mpu_test(mlsl_handle, provide_result);
    if (provide_result) {
        MPL_LOGI("\n");
        if (result == 0) {
            MPL_LOGI("Test : PASSED\n");
        } else {
            MPL_LOGI("Test : FAILED %d/%04X - Biases NOT stored\n", result, result);
            return result; /* abort writing the calibration if the
                              test is not successful */
        }
        MPL_LOGI("\n");
    } else {
        MPL_LOGI("\n");
        if (result) {
            LOG_RESULT_LOCATION(result);
            return result;
        }
    }

    result = inv_serial_write_cal(dataStore, ML_INIT_CAL_LEN);
    if (result) {
        MPL_LOGI("Error : cannot write calibration on file - error %d\n",
            result);
        return result;
    }

    return INV_SUCCESS;
}



/* -----------------------------------------------------------------------
    accel interface functions
 -----------------------------------------------------------------------*/

/**
 *  @internal
 *  @brief  Reads data for X, Y, and Z axis from the accelerometer device.
 *          Used only if an accelerometer has been setup using the
 *          inv_test_setup_accel() API.
 *          Takes care of the accelerometer endianess according to how the
 *          device has been described in the corresponding accelerometer driver
 *          file.
 *  @param  mlsl_handle
 *              serial interface handle to allow serial communication with the
 *              device, both gyro and accelerometer.
 *  @param  slave
 *              a pointer to the descriptor of the slave accelerometer device
 *              in use. Contains the necessary information to operate, read,
 *              and communicate with the accelerometer device of choice.
 *              See the declaration of struct ext_slave_descr in mpu.h.
 *  @param  pdata
 *              a pointer to the platform info of the slave accelerometer
 *              device in use. Describes how the device is oriented and
 *              mounted on host platform's PCB.
 *  @param  vals
 *              output pointer to return the accelerometer's X, Y, and Z axis
 *              sensor data collected.
 *  @return 0 on success or a non-zero error code on error.
 */
static inv_error_t test_get_data(
                    void *mlsl_handle,
                    struct mldl_cfg *mputestCfgPtr,
                    short *vals)
{
    inv_error_t result;
    unsigned char data[20];
    struct ext_slave_descr *slave = mputestCfgPtr->accel;
#ifndef CONFIG_MPU_SENSORS_MPU3050
    struct ext_slave_platform_data *pdata = &mputestCfgPtr->pdata->accel;
#endif

#ifdef CONFIG_MPU_SENSORS_MPU3050
    result = inv_serial_read(mlsl_handle, mputestCfgPtr->addr, 0x23,
                             6, data);
#else
    result = inv_serial_read(mlsl_handle, pdata->address, slave->read_reg,
                            slave->read_len, data);
#endif
    if (result) {
        LOG_RESULT_LOCATION(result);
        return result;
    }

    if (VERBOSE_OUT) {
        MPL_LOGI("Accel         :        0x%02X%02X        0x%02X%02X        0x%02X%02X (raw)\n",
            ACCEL_UNPACK(data));
    }

    if (CHECK_NACKS(data)) {
        MPL_LOGI("Error fetching data from the accelerometer : "
                 "all bytes read 0xff\n");
        return INV_ERROR_SERIAL_READ;
    }

    if (slave->endian == EXT_SLAVE_BIG_ENDIAN) {
        vals[0] = CHARS_TO_SHORT(&data[0]);
        vals[1] = CHARS_TO_SHORT(&data[2]);
        vals[2] = CHARS_TO_SHORT(&data[4]);
    } else {
        vals[0] = CHARS_TO_SHORT_SWAPPED(&data[0]);
        vals[1] = CHARS_TO_SHORT_SWAPPED(&data[2]);
        vals[2] = CHARS_TO_SHORT_SWAPPED(&data[4]);
    }

    if (VERBOSE_OUT) {
        MPL_LOGI("Accel         : %+13d %+13d %+13d (LSB)\n",
                 vals[0], vals[1], vals[2]);
    }
    return INV_SUCCESS;
}

#ifdef __cplusplus
}
#endif

/**
 *  @}
 */