//*****************************************************************************
//
// lis3dsh.c - Driver for the ST LIS3DSH accelerometer

// * THis provides an interface to access accelerometer data from the LIS3DSH.

// * This code is modified from lsm303dhlc.c that is provided as part of
// * revision 1.1 of the Tiva Firmware Development Package, from Texas Instruments Incorporated.
// *
// * Modified by Dale Tardiff, Innovative Power Solutions Inc.
// * Version 0.1 - Started Jan 31, 2014
// 
// 

//
//*****************************************************************************

#include <stdint.h>
#include "hw_lis3dsh.h"
#include "sensorlib/i2cm_drv.h"
#include "lis3dsh.h"

//*****************************************************************************
//
//! \addtogroup lis3dsh_api
//! @{
//
//*****************************************************************************

//*****************************************************************************
//
// The states of the LIS3DSH state machine.
//
//*****************************************************************************
#define LIS3DSH_STATE_IDLE   0           // State machine is idle
#define LIS3DSH_STATE_INIT   1           // Waiting for init
#define LIS3DSH_STATE_READ   2           // Waiting for read
#define LIS3DSH_STATE_WRITE  3           // Waiting for write
#define LIS3DSH_STATE_RMW    4           // Waiting for read-modify-write

//*****************************************************************************
//
// The factors used to convert the acceleration readings from the LIS3DSH
// into floating point values in meters per second squared.
//
// Values are obtained by taking the So conversion factors from the data sheet.
// (In Table 3).
//
//*****************************************************************************
static const float g_pfLIS3DSHAccelFactors[] =
{
    0.000061035,                             // Range = +/- 2 g
    0.00012207,                             // Range = +/- 4 g
    0.0001831,								// Range = +/- 6 g
    0.00024414,                             // Range = +/- 8 g
    0.0048828                              // Range = +/- 16 g
};

//
// Uninitialized values will default to zero which is what we want.  0x80 is
// ORed into the register address so the writes auto-increment
//
/*This section is from lsm303. Not likely needed, but kept as an example if needed
 * static const uint8_t g_pui8ZeroCtrl1[8] =
{
    0x80 | LIS3DSH_CTRL1
};
static const uint8_t g_pui8ZeroFifoCtl[14] =
{
    0x80 | LIS3DSH_FIFO_CTRL
};
 *
 */


//*****************************************************************************
//
// The callback function that is called when I2C transactions to/from the
// LIS3DSH have completed.
//
//*****************************************************************************
void
LIS3DSHCallback(void *pvCallbackData, uint_fast8_t ui8Status)
{
    tLIS3DSH *psInst;

    //
    // Convert the instance data into a pointer to a tLIS3DSH structure.
    //
    psInst = pvCallbackData;

    //
    // If the I2C master driver encountered a failure, force the state machine
    // to the idle state (which will also result in a callback to propagate the
    // error).
    //
    if(ui8Status != I2CM_STATUS_SUCCESS)
    {
        psInst->ui8State = LIS3DSH_STATE_IDLE;
    }

    //
    // Determine the current state of the LIS3DSH state machine.
    //
    switch(psInst->ui8State)
    {
        //
        // All states that trivially transition to IDLE, and all unknown
        // states.
        //
        case LIS3DSH_STATE_READ:
        default:
        {
            //
            // The state machine is now idle.
            //
            psInst->ui8State = LIS3DSH_STATE_IDLE;

            //
            // Done.
            //
            break;
        }

        case LIS3DSH_STATE_INIT:
        {
            psInst->ui8State = LIS3DSH_STATE_IDLE;
//            I2CMWrite(psInst->psI2CInst, psInst->ui8Addr, g_pui8ZeroFifoCtl,
//                      2, LIS3DSHCallback, pvCallbackData);

            //
            // Done.
            //
            break;
        }

        //
        // A write just completed
        //
        case LIS3DSH_STATE_WRITE:
        {
            //
            // Set the accelerometer ranges to the new values.  If the register
            // was not modified, the values will be the same so this has no
            // effect.
            //
            psInst->ui8AccelAfsSel = psInst->ui8NewAccelAfsSel;

            //
            // The state machine is now idle.
            //
            psInst->ui8State = LIS3DSH_STATE_IDLE;

            //
            // Done.
            //
            break;
        }

        //
        // A read-modify-write just completed
        //
        case LIS3DSH_STATE_RMW:
        {
            //
            // See if CTRL_REG5 register was just modified.
        	// This register contains the FS selection
            //
            if(psInst->uCommand.sReadModifyWriteState.pui8Buffer[0] ==
               LIS3DSH_CTRL_REG5)
            {
                //
                // Extract the FS_SEL from the CTRL_REG5 register value.
                //
                psInst->ui8AccelAfsSel =
                    ((psInst->uCommand.sReadModifyWriteState.pui8Buffer[1] &
                    		LIS3DSH_CTRL_REG5_FSCALE) >> 3);	//need to shift FSCALE value 3 positions
            }

            //
            // The state machine is now idle.
            //
            psInst->ui8State = LIS3DSH_STATE_IDLE;

            //
            // Done.
            //
            break;
        }
    }

    //
    // See if the state machine is now idle and there is a callback function.
    //
    if((psInst->ui8State == LIS3DSH_STATE_IDLE) && psInst->pfnCallback)
    {
        //
        // Call the application-supplied callback function.
        //
    	psInst->pfnCallback(psInst->pvCallbackData, ui8Status);
    }
}

//*****************************************************************************
//
//! Initializes the LIS3DSH driver.
//!
//! \param psInst is a pointer to the LIS3DSH instance data.
//! \param psI2CInst is a pointer to the I2C master driver instance data.
//! \param ui8I2CAddr is the I2C address of the LIS3DSH device.
//! \param pfnCallback is the function to be called when the initialization has
//! completed (can be \b NULL if a callback is not required).
//! \param pvCallbackData is a pointer that is passed to the callback function.
//!
//! This function initializes the LIS3DSH driver, preparing it for
//! operation.
//!
//! \return Returns 1 if the LIS3DSH driver was successfully initialized and
//! 0 if it was not.
//
//*****************************************************************************
uint_fast8_t
LIS3DSHInit(tLIS3DSH *psInst, tI2CMInstance *psI2CInst,
                    uint_fast8_t ui8I2CAddr, tSensorCallback *pfnCallback,
                    void *pvCallbackData)
{
    //
    // Initialize the LIS3DSH instance structure.
    //
	uint_fast8_t ui8Status;
    psInst->psI2CInst = psI2CInst;
    psInst->ui8Addr = ui8I2CAddr;

    //
    // Save the callback information.
    //
    psInst->pfnCallback = pfnCallback;
    psInst->pvCallbackData = pvCallbackData;

    //
    // Use the soft reset feature to set LIS3DSH to spec'ed POR defaults.
    //
    psInst->ui8State = LIS3DSH_STATE_INIT;
    psInst->pui8Data[0] = LIS3DSH_CTRL_REG3;
    psInst->pui8Data[1] = LIS3DSH_CTRL_REG3_STRT;
    if(I2CMWrite(psInst->psI2CInst, psInst->ui8Addr, psInst->pui8Data, 2,
                 LIS3DSHCallback, (void *)psInst) == 0)
    {
        // report failure
    	psInst->ui8State = LIS3DSH_STATE_IDLE;
        return(0);
    }
     else if(pfnCallback)
    {
    	 //
    	 // Call the callback function.
    	 //
    	 ui8Status = I2CM_STATUS_SUCCESS;
    	 pfnCallback(pvCallbackData, ui8Status);
    }
     else
    	 psInst->ui8State = LIS3DSH_STATE_IDLE;

    // report success
    return(1);
}

//*****************************************************************************
//
//! Reads data from LIS3DSH registers.
//!
//! \param psInst is a pointer to the LIS3DSH instance data.
//! \param ui8Reg is the first register to read.
//! \param pui8Data is a pointer to the location to store the data that is
//! read.
//! \param ui16Count is the number of data bytes to read.
//! \param pfnCallback is the function to be called when the data has been read
//! (can be \b NULL if a callback is not required).
//! \param pvCallbackData is a pointer that is passed to the callback function.
//!
//! This function reads a sequence of data values from consecutive registers in
//! the LIS3DSH.
//!
//! \return Returns 1 if the write was successfully started and 0 if it was
//! not.
//
//*****************************************************************************
uint_fast8_t
LIS3DSHRead(tLIS3DSH *psInst, uint_fast8_t ui8Reg,
                    uint8_t *pui8Data, uint_fast16_t ui16Count,
                    tSensorCallback *pfnCallback, void *pvCallbackData)
{
    //
    // Return a failure if the LIS3DSH driver is not idle (in other words,
    // there is already an outstanding request to the LIS3DSH).
    //
    if(psInst->ui8State != LIS3DSH_STATE_IDLE)
    {
        return(0);
    }

    //
    // Save the callback information.
    //
    psInst->pfnCallback = pfnCallback;
    psInst->pvCallbackData = pvCallbackData;

    //
    // Move the state machine to the wait for read state.
    //
    psInst->ui8State = LIS3DSH_STATE_READ;

    //
    // Read the requested registers from the LIS3DSH.
    //
    psInst->uCommand.pui8Buffer[0] = ui8Reg;
    if(I2CMRead(psInst->psI2CInst, psInst->ui8Addr,
                psInst->uCommand.pui8Buffer, 1, pui8Data, ui16Count,
                LIS3DSHCallback, psInst) == 0)
    {
        //
        // The I2C write failed, so move to the idle state and return a
        // failure.
        //
        psInst->ui8State = LIS3DSH_STATE_IDLE;
        return(0);
    }

    //
    // Success.
    //
    return(1);
}

//*****************************************************************************
//
//! Writes data to LIS3DSH registers.
//!
//! \param psInst is a pointer to the LIS3DSH instance data.
//! \param ui8Reg is the first register to write.
//! \param pui8Data is a pointer to the data to write.
//! \param ui16Count is the number of data bytes to write.
//! \param pfnCallback is the function to be called when the data has been
//! written (can be \b NULL if a callback is not required).
//! \param pvCallbackData is a pointer that is passed to the callback function.
//!
//! This function writes a sequence of data values to consecutive registers in
//! the LIS3DSH.  The first byte of the \e pui8Data buffer contains the
//! value to be written into the \e ui8Reg register, the second value contains
//! the data to be written into the next register, and so on.
//!
//! \return Returns 1 if the write was successfully started and 0 if it was
//! not.
//
//*****************************************************************************
uint_fast8_t
LIS3DSHWrite(tLIS3DSH *psInst, uint_fast8_t ui8Reg,
                     const uint8_t *pui8Data, uint_fast16_t ui16Count,
                     tSensorCallback *pfnCallback, void *pvCallbackData)
{
    //
    // Return a failure if the LIS3DSH driver is not idle (in other words,
    // there is already an outstanding request to the LIS3DSH).
    //
    if(psInst->ui8State != LIS3DSH_STATE_IDLE)
    {
        return(0);
    }

    //
    // Save the callback information.
    //
    psInst->pfnCallback = pfnCallback;
    psInst->pvCallbackData = pvCallbackData;

    //
    // See if they're rebooting via CTRL_REG3
    //
    if((ui8Reg <= LIS3DSH_CTRL_REG3) &&
       ((ui8Reg + ui16Count) > LIS3DSH_CTRL_REG3))
    {
        //
        // See if a soft reset is being requested.
        //
        if(pui8Data[ui8Reg - LIS3DSH_CTRL_REG3] & LIS3DSH_CTRL_REG3_STRT)
        {
            //
            // Default range setting is +/- 2 g.
            //
            psInst->ui8NewAccelAfsSel = 0;
        }
    }

    //
    // See if CTRL_REG5 register was just modified.
    // This register contains the FS selection
    //
    if(psInst->uCommand.sReadModifyWriteState.pui8Buffer[0] ==
    		LIS3DSH_CTRL_REG5)
    {
    	//
    	// Extract the FS_SEL from the CTRL_REG5 register value.
    	//
    	psInst->ui8AccelAfsSel =
    			((psInst->uCommand.sReadModifyWriteState.pui8Buffer[1] &
    					LIS3DSH_CTRL_REG5_FSCALE) >> 3);	//need to shift FSCALE value 3 positions
    }

    //
    // Move the state machine to the wait for write state.
    //
    psInst->ui8State = LIS3DSH_STATE_WRITE;

    //
    // Write the requested registers to the LIS3DSH.
    //
    if(I2CMWrite8(&(psInst->uCommand.sWriteState), psInst->psI2CInst,
                  psInst->ui8Addr, ui8Reg, pui8Data, ui16Count,
                  LIS3DSHCallback, psInst) == 0)
    {
        //
        // The I2C write failed, so move to the idle state and return a
        // failure.
        //
        psInst->ui8State = LIS3DSH_STATE_IDLE;
        return(0);
    }

    //
    // Success.
    //
    return(1);
}

//*****************************************************************************
//
//! Performs a read-modify-write of a LIS3DSH register.
//!
//! \param psInst is a pointer to the LIS3DSH instance data.
//! \param ui8Reg is the register to modify.
//! \param ui8Mask is the bit mask that is ANDed with the current register
//! value.
//! \param ui8Value is the bit mask that is ORed with the result of the AND
//! operation.
//! \param pfnCallback is the function to be called when the data has been
//! changed (can be \b NULL if a callback is not required).
//! \param pvCallbackData is a pointer that is passed to the callback function.
//!
//! This function changes the value of a register in the LIS3DSH via a
//! read-modify-write operation, allowing one of the fields to be changed
//! without disturbing the other fields.  The \e ui8Reg register is read, ANDed
//! with \e ui8Mask, ORed with \e ui8Value, and then written back to the
//! LIS3DSH.
//!
//! \return Returns 1 if the read-modify-write was successfully started and 0
//! if it was not.
//
//*****************************************************************************
uint_fast8_t
LIS3DSHReadModifyWrite(tLIS3DSH *psInst, uint_fast8_t ui8Reg,
                               uint_fast8_t ui8Mask, uint_fast8_t ui8Value,
                               tSensorCallback *pfnCallback,
                               void *pvCallbackData)
{
    //
    // Return a failure if the LIS3DSH driver is not idle (in other words,
    // there is already an outstanding request to the LIS3DSH).
    //
    if(psInst->ui8State != LIS3DSH_STATE_IDLE)
    {
        return(0);
    }

    //
    // Save the callback information.
    //
    psInst->pfnCallback = pfnCallback;
    psInst->pvCallbackData = pvCallbackData;

    //
    // Move the state machine to the wait for read-modify-write state.
    //
    psInst->ui8State = LIS3DSH_STATE_RMW;

    //
    // Submit the read-modify-write request to the LIS3DSH.
    //
    if(I2CMReadModifyWrite8(&(psInst->uCommand.sReadModifyWriteState),
                            psInst->psI2CInst, psInst->ui8Addr, ui8Reg,
                            ui8Mask, ui8Value, LIS3DSHCallback,
                            psInst) == 0)
    {
        //
        // The I2C read-modify-write failed, so move to the idle state and
        // return a failure.
        //
        psInst->ui8State = LIS3DSH_STATE_IDLE;
        return(0);
    }

    //
    // Success.
    //
    return(1);
}

//*****************************************************************************
//
//! Reads the accelerometer data from the LIS3DSH.
//!
//! \param psInst is a pointer to the LIS3DSH instance data.
//! \param pfnCallback is the function to be called when the data has been read
//! (can be \b NULL if a callback is not required).
//! \param pvCallbackData is a pointer that is passed to the callback function.
//!
//! This function initiates a read of the LIS3DSH data registers.  When the
//! read has completed (as indicated by calling the callback function), the new
//! readings can be obtained via:
//!
//! - LIS3DSHDataGetRaw()
//! - LIS3DSHDataGetFloat()
//!
//! \return Returns 1 if the read was successfully started and 0 if it was not.
//
//*****************************************************************************
uint_fast8_t
LIS3DSHDataRead(tLIS3DSH *psInst, tSensorCallback *pfnCallback,
                        void *pvCallbackData)
{
    //
    // Return a failure if the LIS3DSH driver is not idle (in other words,
    // there is already an outstanding request to the LIS3DSH).
    //
    if(psInst->ui8State != LIS3DSH_STATE_IDLE)
    {
        return(0);
    }

    //
    // Save the callback information.
    //
    psInst->pfnCallback = pfnCallback;
    psInst->pvCallbackData = pvCallbackData;

    //
    // Move the state machine to the wait for data read state.
    //
    psInst->ui8State = LIS3DSH_STATE_READ;

    //
    // Read the data registers from the LIS3DSH.
    //
    psInst->pui8Data[0] = LIS3DSH_OUT_X_L;
    if(I2CMRead(psInst->psI2CInst, psInst->ui8Addr, psInst->pui8Data, 1,
                psInst->pui8Data, 6, LIS3DSHCallback, psInst) == 0)
    {
        //
        // The I2C read failed, so move to the idle state and return a failure.
        //
        psInst->ui8State = LIS3DSH_STATE_IDLE;
        return(0);
    }

    //
    // Success.
    //
    return(1);
}

//*****************************************************************************
//
//! Gets the raw accelerometer data from the most recent data read.
//!
//! \param psInst is a pointer to the LIS3DSH instance data.
//! \param pui16AccelX is a pointer to the value into which the raw X-axis
//! accelerometer data is stored.
//! \param pui16AccelY is a pointer to the value into which the raw Y-axis
//! accelerometer data is stored.
//! \param pui16AccelZ is a pointer to the value into which the raw Z-axis
//! accelerometer data is stored.
//!
//! This function returns the raw accelerometer data from the most recent data
//! read.  The data is not manipulated in any way by the driver.  If any of the
//! output data pointers are \b NULL, the corresponding data is not provided.
//!
//! \return None.
//
//*****************************************************************************
void
LIS3DSHDataGetRaw(tLIS3DSH *psInst,
                               uint_fast16_t *pui16AccelX,
                               uint_fast16_t *pui16AccelY,
                               uint_fast16_t *pui16AccelZ)
{
    //
    // Return the raw accelerometer values.
    //
    if(pui16AccelX)
    {
        *pui16AccelX = (psInst->pui8Data[1] << 8) | psInst->pui8Data[0];
    }
    if(pui16AccelY)
    {
        *pui16AccelY = (psInst->pui8Data[3] << 8) | psInst->pui8Data[2];
    }
    if(pui16AccelZ)
    {
        *pui16AccelZ = (psInst->pui8Data[5] << 8) | psInst->pui8Data[4];
    }
}

//*****************************************************************************
//
//! Gets the accelerometer data from the most recent data read.
//!
//! \param psInst is a pointer to the LIS3DSH instance data.
//! \param pfAccelX is a pointer to the value into which the X-axis
//! accelerometer data is stored.
//! \param pfAccelY is a pointer to the value into which the Y-axis
//! accelerometer data is stored.
//! \param pfAccelZ is a pointer to the value into which the Z-axis
//! accelerometer data is stored.
//!
//! This function returns the accelerometer data from the most recent data
//! read, converted into meters per second squared (m/s^2).  If any of the
//! output data pointers are \b NULL, the corresponding data is not provided.
//!
//! \return None.
//
//*****************************************************************************
void
LIS3DSHDataGetFloat(tLIS3DSH *psInst, float *pfAccelX,
                                 float *pfAccelY, float *pfAccelZ)
{
    float fFactor;

    //
    // Get the acceleration conversion factor for the current data format.
    //
    fFactor = g_pfLIS3DSHAccelFactors[psInst->ui8AccelAfsSel];

    //
    // Convert the Accelerometer values into floating-point gravity values.
    //
    if(pfAccelX)
    {
        *pfAccelX = (float)(((int16_t)((psInst->pui8Data[1] << 8) |
                                       psInst->pui8Data[0])) * fFactor);
    }
    if(pfAccelY)
    {
        *pfAccelY = (float)(((int16_t)((psInst->pui8Data[3] << 8) |
                                       psInst->pui8Data[2])) * fFactor);
    }
    if(pfAccelZ)
    {
        *pfAccelZ = (float)(((int16_t)((psInst->pui8Data[5] << 8) |
                                       psInst->pui8Data[4])) * fFactor);
    }
}

//*****************************************************************************
//
// Close the Doxygen group.
//! @}
//
//*****************************************************************************
