/** @brief
 * @copyright 2020 Remsdaq Ltd.
 *
 * @file platform_eeprom.c
 *
 *  @date: 8 Sep 2020
 *  @author: dsmit
 */





/*******************************************************************************
 * Includes
 ******************************************************************************/

#include "platform-eeprom-header.h"

#include "at24c01d.h"
#include "fsl_lpi2c.h"
#include "fsl_clock.h"
#include "fsl_iomuxc.h"

#include <stdio.h>

/*******************************************************************************
 * External Definitions
 ******************************************************************************/

/*******************************************************************************
 * Defines
 ******************************************************************************/


#define LPI2C_CLOCK_SOURCE_SELECT (0U)  ///< Select USB1 PLL (480 MHz) as master lpi2c clock source
#define LPI2C_CLOCK_SOURCE_DIVIDER (5U) ///< Clock divider for master lpi2c clock source
#define LPI2C_MASTER_CLOCK_FREQUENCY ((CLOCK_GetFreq(kCLOCK_Usb1PllClk) / 8) / (LPI2C_CLOCK_SOURCE_DIVIDER + 1U))

#define LPI2C2_MASTER       ((LPI2C_Type *)LPI2C2_BASE)
#define LPI2C2_BAUDRATE     100000U

/*******************************************************************************
 * Structures
 ******************************************************************************/

/*******************************************************************************
 * Prototypes
 ******************************************************************************/

/*******************************************************************************
 * Variables
 ******************************************************************************/

/*******************************************************************************
 * Code
 ******************************************************************************/

/*******************************************************************************
 * Interface Implementation
 ******************************************************************************/

/*******************************************************************************
 * Local Functions
 ******************************************************************************/

/**
 * @brief Calculate a config header checksum
 *
 * @param[in] pvBase        Base address of configuration block
 * @param[in] uiNumBytes    Length (in bytes) of the configuration
 * @return                  32-bit additive checksum value
 *
 * Calculate the checksum of a platform configuration EEPROM header
 */
uint32_t eeprom_header_calc_checksum(void *pvBase, uint32_t uiNumBytes)
{
    uint32_t * puiPtr = (uint32_t *) pvBase;
    uint32_t uiChecksum;
    uint32_t uiNumWords;
    uint32_t i;

    uiChecksum = 0U;
    uiNumWords = ((uiNumBytes + 3) >> 2);
    for (i = uiNumWords; i; i--)
    {
        uiChecksum += *puiPtr++;
    }

    return uiChecksum;
}

/**
 * @brief Read an EEPROM platform configuration block
 * @param[in] pstHeader Header to read bytes into
 * @retval true         All went well
 * @retval false        Problem encountered
 *
 * Read a platform configuration block in from the EEPROM
 */
bool eeprom_header_read(stEEPROM_Header_t * pstHeader)
{
    int32_t result;
    uint32_t uiChecksum;

    result = At24c01d_Read(PLATFORM_EEPROM_HEADER_OFFSET, (uint8_t *)pstHeader, sizeof(stEEPROM_Header_t));

    //Check that we have read all the bytes in the header, not the length of the pointer (4 bytes)
    if (result < (int32_t) sizeof(stEEPROM_Header_t))
    {
        // Unable to read full header from EEPROM, so inform user via serial and return
        printf( "Error: EEPROM config header could not be read in %ld vs %d\r\n", result,  sizeof(stEEPROM_Header_t));
        return false;
    }

    if ((pstHeader->u16HeaderLength < 32U) || (pstHeader->u16HeaderLength > AT24C01D_SIZE_BYTES))
    {
        // Header length malformed, so inform user via serial and return
        printf( "Error: EEPROM config header had unexpected length (%d)\r\n", (int) pstHeader->u16HeaderLength);
        return false;
    }

    if (pstHeader->uiHeaderMagic != PLATFORM_EEPROM_HEADER_MAGIC)
    {
        // Header magic malformed, so inform user via serial and return
        printf( "Error: EEPROM config header had missing magic number (%08x)\r\n", (int) pstHeader->uiHeaderMagic);
        return false;
    }

    uiChecksum = eeprom_header_calc_checksum(pstHeader, pstHeader->u16HeaderLength);
    if (uiChecksum != 0U)
    {
        // Header fails checksum test, so inform user via serial and return
        printf( "Error: EEPROM config header failed checksum test\r\n");
        return false;
    }

#ifdef DEBUG
    printf( "EEPROM config header is valid!\r\n" );
#endif

    return true;
}

/**
 * @brief Write a platform configuration block out to the EEPROM
 * @param[in] pstHeader Header to read bytes from
 * @retval true         All went well
 * @retval false        Problem encountered
 *
 * Write a platform configuration block out to the EEPROM
 */
bool eeprom_header_write(const stEEPROM_Header_t * pstHeader)
{
    int32_t result;     // holds number of bytes successfully written out

    result = At24c01d_Write(PLATFORM_EEPROM_HEADER_OFFSET, (uint8_t *)pstHeader, sizeof(stEEPROM_Header_t));
    printf("%s wrote %ld vs %d\r\n", __PRETTY_FUNCTION__, result, sizeof(stEEPROM_Header_t));
    //Check that we have written all the bytes in the header, not the length of the pointer (4 bytes)
    return (result == (int32_t) sizeof(stEEPROM_Header_t));
}

/**
 * @brief Initialise the I2C master clock
 *
 * Initialise the I2C master clock (shared by all I2C buses)
 */
void InitialiseI2CMasterClock(void)
{
    /* Master clock setting for I2C */
    CLOCK_SetMux(kCLOCK_Lpi2cMux, LPI2C_CLOCK_SOURCE_SELECT);
    CLOCK_SetDiv(kCLOCK_Lpi2cDiv, LPI2C_CLOCK_SOURCE_DIVIDER);
}

/**
 * @brief Initialise the I2C2 device
 *
 * Initialise the I2C2 device
 */
void InitialiseI2C2(void)
{
    lpi2c_master_config_t masterConfig;

    IOMUXC_SetPinConfig(
        IOMUXC_GPIO_B0_04_LPI2C2_SCL,           /* GPIO_B0_04 PAD functional properties : */
        0xD8B0U);                               /*  Slew Rate Field: Slow Slew Rate
                                                    Drive Strength Field: R0/6
                                                    Speed Field: medium(100MHz)
                                                    Open Drain Enable Field: Open Drain Enabled
                                                    Pull / Keep Enable Field: Pull/Keeper Enabled
                                                    Pull / Keep Select Field: Keeper
                                                    Pull Up / Down Config. Field: 22K Ohm Pull Up
                                                    Hyst. Enable Field: Hysteresis Disabled */
     IOMUXC_SetPinConfig(
        IOMUXC_GPIO_B0_05_LPI2C2_SDA,           /* GPIO_B0_05 PAD functional properties : */
        0xD8B0U);                               /*  Slew Rate Field: Slow Slew Rate
                                                    Drive Strength Field: R0/6
                                                    Speed Field: medium(100MHz)
                                                    Open Drain Enable Field: Open Drain Enabled
                                                    Pull / Keep Enable Field: Pull/Keeper Enabled
                                                    Pull / Keep Select Field: Keeper
                                                    Pull Up / Down Config. Field: 22K Ohm Pull Up
                                                    Hyst. Enable Field: Hysteresis Disabled */

// Note: Jeremy Mills found that these I2C pins should have their input path set to 1U.
    IOMUXC_SetPinMux(
        IOMUXC_GPIO_B0_04_LPI2C2_SCL,           /* GPIO_B0_04 is configured as LPI2C2_SCL */
        1U);                                    /* Software Input On Field: Input Path is always on */
    IOMUXC_SetPinMux(
        IOMUXC_GPIO_B0_05_LPI2C2_SDA,           /* GPIO_B0_05 is configured as LPI2C2_SDA */
        1U);                                    /* Software Input On Field: Input Path is always on */

    /*
     * masterConfig.debugEnable = false;
     * masterConfig.ignoreAck = false;
     * masterConfig.pinConfig = kLPI2C_2PinOpenDrain;
     * masterConfig.baudRate_Hz = 100000U;
     * masterConfig.busIdleTimeout_ns = 0;
     * masterConfig.pinLowTimeout_ns = 0;
     * masterConfig.sdaGlitchFilterWidth_ns = 0;
     * masterConfig.sclGlitchFilterWidth_ns = 0;
     */
    LPI2C_MasterGetDefaultConfig(&masterConfig);

    // Change the default baudrate configuration
    masterConfig.baudRate_Hz = LPI2C2_BAUDRATE;

    // Initialise the LPI2C master peripheral
    LPI2C_MasterInit(LPI2C2_MASTER, &masterConfig, LPI2C_MASTER_CLOCK_FREQUENCY);
}
