//*****************************************************************************
//
//! @file hci_drv.c
//!
//! @brief HCI driver interface.
//
//*****************************************************************************

//*****************************************************************************
//
// Copyright (c) 2019, Ambiq Micro
// All rights reserved.
// 
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// 
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// 
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// 
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from this
// software without specific prior written permission.
// 
// Third party software included in this distribution is subject to the
// additional license terms as defined in the /docs/licenses directory.
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
// This is part of revision v2.2.0-7-g63f7c2ba1 of the AmbiqSuite Development Package.
//
//*****************************************************************************
#include <stdint.h>
#include <stdbool.h>
#include <string.h>

#include "am_mcu_apollo.h"
#include "am_util.h"
#include "am_devices_em9304.h"
#include "em9304_init.h"
#include "em9304_patches.h"
#include "hci_apollo_config.h"

#define INVALIDATE_UNKNOWN_PATCHES
#define ENABLE_32K_CLK_FROM_APOLLO
#define SLEEP_CLK_PATCH_CONTAINER_ID (0x16)

// if a product is designed with step-up DCDC mode for EM9304
// and was programmed HCI v6 patch on OTP, it's required
// to define INVALIDATE_HCI_V6_PATCH_ON_OTP to do a special
// handling to invalidate the v6 patch on OTP and program
// v8 or later HCI patch.
#define INVALIDATE_HCI_V6_PATCH_ON_OTP

#ifdef  INVALIDATE_HCI_V6_PATCH_ON_OTP
#define HCI_V6_PATCH_CONTAINER_ID     53
#endif
// This should be defined as the currently included HCI code patch
// e.g. currently v8 is latest, which has container ID 77.
#define HCI_CURRENT_CODE_PATCH_CONTAINER_ID 77

// Define the HCI Command Type locally.
#define HCI_CMD_TYPE 1

// EM_PatchQuery field offsets
#define PATCH_INDEX_OFFSET    3

// EM_PatchQuery response field offsets
#define CONTAINER_COUNT_INDEX 7
#define CONTAINER_ADDR_INDEX 15
#define CONTAINER_SIZE_INDEX 19
#define BUILD_NUMBER_INDEX 27
#define USER_BUILD_NUMBER_INDEX 29
#define CONTAINER_VERSION_INDEX 32
#define CONTAINER_TYPE_INDEX 33
#define CONTAINER_ID_INDEX 34

// EM_PatchQuery response values
#define CONTAINER_TYPE_CONFIG_DATA_WORD     1
#define CONTAINER_TYPE_RANDOM_DATA_WORD     2
#define CONTAINER_TYPE_RANDOM_DATA_BYTE     3
#define CONTAINER_TYPE_CONFIG_DATA_BYTE     11

// EM_PatchWrite and EM_PatchContine field offsets
#define PATCH_LENGTH_OFFSET 2


// EM_PatchWrite destination memory field offsets
#define PATCH_DEST_MEMORY_OFFSET 3

// EM_PatchWrite and EM_PatchContinue response field offsets
#define HCI_STATUS_OFFSET 6
#define EM_PATCH_STATUS_OFFSET 7

// EM_PatchWrite and EM_PatchContinue Patch Status values
#define EM_PATCH_APPLIED 1
#define EM_PATCH_CONTINUE 2

// Maximum number of attempts to wait for a response from EM9304.
#define EM9304_MAX_ATTEMPTS     100
#define EM9304_ATTEMPT_DELAY_MS 1

#define EM9304_IRAM1_START_ADDRESS 0x20000

// Initialization function error return status.
enum
{
  EM9304_INIT_STATUS_SUCCESS,
  EM9304_INIT_STATUS_ERROR
} e_em9304_init_status;

//*****************************************************************************
//
// HCI Commands for EM9304
//
//*****************************************************************************
uint8_t g_pui8EM_SleepDisable[] = {0x2D, 0xFC, 0x01, 0x00};
uint8_t g_pui8EM_SetOTPOn[]         = {0x2B, 0xFC, 0x01, 0x01};
uint8_t g_pui8EM_SetOTPOff[]         = {0x2B, 0xFC, 0x01, 0x00};
uint8_t g_pui8EM_SetIRAMOn[]         = {0x2B, 0xFC, 0x01, 0x07};
uint8_t g_pui8EM_PatchQuery[]     = {0x34, 0xFC, 0x02, 0x00, 0x00};
uint8_t g_pui8EM_SleepEnable[]     = {0x2D, 0xFC, 0x01, 0x01};
uint8_t g_pui8EM_CpuReset[]         = {0x32, 0xFC, 0x00};


uint32_t
applyEM9304Patches(uint32_t target_memory, uint32_t containerID);
//*****************************************************************************
//
// HCI RX packet buffer for EM9304 Driver.
//
//*****************************************************************************
static uint32_t g_pui32HCIRXBuffer[64];

//*****************************************************************************
//
// Static record of the EM9304 patch errors
//
//*****************************************************************************

uint32_t g_EMPatchErrors = 0;

//*****************************************************************************
//
//! @brief Patch Response helper functions for the EM9304 patches.  This
//!        routine blocks on a response from the EM9304 and filters the
//!        vendor specific events.
//!
//! @return none.
//
//*****************************************************************************
uint32_t
waitEM9304Response(void)
{
  uint32_t numBytesRx;

  // HCI Respone should return in 1-2 messages at most, but driver returns
  // 0 bytes when nothing is available, so wait up to 10msec.
  for (uint32_t attempts = 0; attempts < EM9304_MAX_ATTEMPTS; attempts++)
  {
    numBytesRx = am_devices_em9304_block_read(&g_sEm9304, g_pui32HCIRXBuffer, 0);
    // Look for "no message" return while filtering out the EM9304 vendor specific events.
    if ((numBytesRx !=0) && (!((numBytesRx == 4) && (0x0000FF04 == (g_pui32HCIRXBuffer[0] & 0x0000FFFF)))))
    {
      return EM9304_INIT_STATUS_SUCCESS;
    }
    am_util_delay_ms(EM9304_ATTEMPT_DELAY_MS);
  }
  return EM9304_INIT_STATUS_ERROR;

}

//*****************************************************************************
//
//! @brief Function to check for valid patches in the em9304_patches.* files.
//!        Invalid patches means that the scripts to generate the patch files
//!        were run without valid *.emp files as input.
//!
//! @return bool (TRUE = patches are valid).
//
//*****************************************************************************
bool
validEM9304Patches(void)
{
  //
  // Check to see if we have valid patches.
  // NULL patch has a specific signature.
  //
  if ((1 == EM9304_PATCHES_NUM_PATCHES) &&
      (0xFFFF == g_pEm9304Patches[0].buildNumber) &&
        (0xFFFF == g_pEm9304Patches[0].userBuildNumber) &&
          (0xFF == g_pEm9304Patches[0].containerVersion) &&
            (0xFF == g_pEm9304Patches[0].containerType) &&
              (0xFF == g_pEm9304Patches[0].containerID) &&
                (0x00 == g_pEm9304Patches[0].applyPatch) &&
                  (0x00 == g_pEm9304Patches[0].startingPatch) &&
                    (0x00 == g_pEm9304Patches[0].endingPatch))
  {
    am_util_debug_printf("em9304_patches.c contains NULL patch only\n");
    return false;
  }
  else
  {
    am_util_debug_printf("Valid em9304_patches.c file found\n");
    return true;
  }
}

//*****************************************************************************
//
//! @brief Function to invalidate a patch at a given address.  The size field
//!        is changed to corrupt the patch in OTP.
//!
//! @return status.
//
//*****************************************************************************
#ifdef INVALIDATE_UNKNOWN_PATCHES
static uint32_t invalidateEM9304Patch(uint32_t addr, uint32_t size)
{
  uint8_t   *bytePtr = (uint8_t *)&g_pui32HCIRXBuffer;

  uint8_t payload[] =
  {
      0x22, 0xFC,               //WriteAtAddr command
      0x0C,                     //HCI param length
      0, 0, 0, 0,               // container address placeholder
      0x33, 0x39, 0x6D, 0x65,   //signature
      0, 0, 0, 0                //size placeholder
  };

  payload[3] = (uint8_t)(addr & 0xFF);
  payload[4] = (uint8_t)((addr & 0xFF00) >> 8);
  payload[5] = (uint8_t)((addr & 0xFF0000) >> 16);
  payload[6] = (uint8_t)((addr & 0xFF000000) >> 24);

  size |= 0x36000000;  // mask the size to change the patch (invalidate it).

  payload[11] = (uint8_t)(size & 0xFF);
  payload[12] = (uint8_t)((size & 0xFF00) >> 8);
  payload[13] = (uint8_t)((size & 0xFF0000) >> 16);
  payload[14] = (uint8_t)((size & 0xFF000000) >> 24);

  am_devices_em9304_block_write(&g_sEm9304, HCI_CMD_TYPE, payload, sizeof(payload));

  if ((EM9304_INIT_STATUS_SUCCESS != waitEM9304Response()) || (bytePtr[HCI_STATUS_OFFSET] != 0))
  {
    am_util_debug_printf("Invalidating patch at %x status %d\n", addr, bytePtr[HCI_STATUS_OFFSET]);

    return EM9304_INIT_STATUS_ERROR;
  }

  am_util_debug_printf("Invalidating patch at %x status OK\n", addr);

  return EM9304_INIT_STATUS_SUCCESS;
}
#endif

//*****************************************************************************
//
//! @brief Query the EM9304 patches.  This routine uses the EM_PatchQuery HCI
//!        command to interogate the connected EM9304 about its current patch
//!        state and then update the patch Container Info data structure.
//!
//! @return status.
//
//*****************************************************************************
uint32_t
queryEM9304Patches(void)
{
  uint32_t containerCount;
  uint32_t buildNumber, userBuildNumber, containerVersion, containerType, containerID;
#ifdef INVALIDATE_UNKNOWN_PATCHES
  uint32_t  containerAddr, containerSize;
  bool      invalidatePatch = false;
#endif

  uint8_t *pBuf = (uint8_t *)g_pui32HCIRXBuffer;

  // Initialize the container info patch status
  for (uint32_t patch = 0; patch < EM9304_PATCHES_NUM_PATCHES; patch++)
  {
    // Check patch for enabling 32Khz clck from Apollo MCU
    if ( (g_pEm9304Patches[patch].userBuildNumber == 2)
        && (g_pEm9304Patches[patch].containerID == SLEEP_CLK_PATCH_CONTAINER_ID))
    {
      uint32_t  ui32PN;

      //
      // Device identification
      //
      ui32PN = AM_REG(MCUCTRL, CHIP_INFO)  &
               AM_UTIL_MCUCTRL_CHIP_INFO_PARTNUM_PN_M;

      #ifdef ENABLE_32K_CLK_FROM_APOLLO
      // Currently only enable this for Apollo2-Blue
      if (ui32PN == AM_UTIL_MCUCTRL_CHIP_INFO_PARTNUM_APOLLOBL)
      {
        g_pEm9304Patches[patch].applyPatch = true;

        // GPIO 24 in Apollo2-blue connected to LFCLK in EM9304
        am_hal_gpio_pin_config(24, AM_HAL_PIN_24_CLKOUT);
        am_hal_clkgen_osc_start(AM_HAL_CLKGEN_OSC_XT);
        am_util_delay_ms(500);
        am_hal_clkgen_clkout_enable(AM_HAL_CLKGEN_CLKOUT_CKSEL_XT);
      }
      #endif
    }
    else
    {
      g_pEm9304Patches[patch].applyPatch = true;
    }
  }

  // Send the EM_SetSleepOptions command to disable sleep and check the response.
  am_devices_em9304_block_write(&g_sEm9304, HCI_CMD_TYPE, g_pui8EM_SleepDisable, sizeof(g_pui8EM_SleepDisable) );

  if (EM9304_INIT_STATUS_SUCCESS != waitEM9304Response())
  {
    am_util_debug_printf("No Response to EM9304 Sleep Disable\n");
    return EM9304_INIT_STATUS_ERROR;
  }

  // Check that the response is to the Sleep Disable.
  if ((0x01040E04 != g_pui32HCIRXBuffer[0]) || (0x0000FC2D != (g_pui32HCIRXBuffer[1] & 0x0000FFFF)))
  {
    am_util_debug_printf("Invalid Response to EM9304 Sleep Disable\n");
    return EM9304_INIT_STATUS_ERROR;
  }

  // Send the EM_SetMemoryMode command to turn on OTP and check the response.
  am_devices_em9304_block_write(&g_sEm9304, HCI_CMD_TYPE, g_pui8EM_SetOTPOn, sizeof(g_pui8EM_SetOTPOn) );

  if (EM9304_INIT_STATUS_SUCCESS != waitEM9304Response())
  {
    am_util_debug_printf("No Response to EM9304 OTP Enable\n");
    return EM9304_INIT_STATUS_ERROR;
  }

  // Check that the response is to the OTP enable.
  if ((0x01040E04 != g_pui32HCIRXBuffer[0]) || (0x0000FC2B != (g_pui32HCIRXBuffer[1] & 0x0000FFFF)))
  {
    am_util_debug_printf("Invalid Response to EM9304 OTP Enable\n");
    return EM9304_INIT_STATUS_ERROR;
  }

  // Query the EM9304 with the EM_PatchQuery and Patch Index = 0.  This will return the Container Count.
  am_devices_em9304_block_write(&g_sEm9304, HCI_CMD_TYPE, g_pui8EM_PatchQuery, sizeof(g_pui8EM_PatchQuery) );

  if (EM9304_INIT_STATUS_SUCCESS != waitEM9304Response())
  {
    am_util_debug_printf("No Response to EM9304 Patch Query\n");
    return EM9304_INIT_STATUS_ERROR;
  }

  // Check that the response is to the Patch Query.
  if ((0x01200E04 != g_pui32HCIRXBuffer[0]) || (0x0000FC34 != (g_pui32HCIRXBuffer[1] & 0x0000FFFF)))
  {
    am_util_debug_printf("Invalid Response to EM9304 Patch Query\n");
    return EM9304_INIT_STATUS_ERROR;
  }

  // Extract the container information from the query response.
  containerCount = (uint32_t)pBuf[CONTAINER_COUNT_INDEX] +
    ((uint32_t)pBuf[CONTAINER_COUNT_INDEX + 1] << 8);

  // Assume the first patch is the manufacturing trim patch.
  // This is the only patch that never should be invalidated.
  am_util_debug_printf("Number of patch containers on EM9304 excluding Patch#0: %d\n", containerCount - 1);

  #ifdef INVALIDATE_UNKNOWN_PATCHES
  #ifdef INVALIDATE_HCI_V6_PATCH_ON_OTP

  bool      hci_v6_patch_present_on_otp = false;

  // For each container in Container Count to see if HCI meta patch v6 patch
  // is present
  for (uint32_t container = 1; container < containerCount; container++)
  {
    // Send the EM_PatchQuery for the Container.
    g_pui8EM_PatchQuery[PATCH_INDEX_OFFSET] = container;

    am_devices_em9304_block_write(&g_sEm9304, HCI_CMD_TYPE, g_pui8EM_PatchQuery, sizeof(g_pui8EM_PatchQuery) );

    if (EM9304_INIT_STATUS_SUCCESS != waitEM9304Response())
    {
      am_util_debug_printf("No Response to EM9304 Patch Query\n");
      return EM9304_INIT_STATUS_ERROR;
    }

    containerID = pBuf[CONTAINER_ID_INDEX];

    // v6 patch's container ID is 53 in decimal.
    if ( containerID ==  HCI_V6_PATCH_CONTAINER_ID )
    {

      hci_v6_patch_present_on_otp = true;
      am_util_debug_printf("HCI v6 patch found in OTP\n");

      break;
    }
  }

  if (hci_v6_patch_present_on_otp)
  {
    // here we have to do code-reset to EM9304
    am_hal_gpio_pin_config(HCI_APOLLO_RESET_PIN, AM_HAL_GPIO_OUTPUT);
    am_hal_gpio_out_bit_clear(HCI_APOLLO_RESET_PIN);

    am_util_delay_ms(10);

    am_hal_gpio_out_bit_set(HCI_APOLLO_RESET_PIN);

    am_util_delay_ms(20);

    // Apply the latest code patch into IRAM in order to invalidate v6 patch in otp
    // properly.

    applyEM9304Patches(DEST_MEMORY_IRAM, HCI_CURRENT_CODE_PATCH_CONTAINER_ID);

    // Send EM_CpuReset HCI command.
    am_devices_em9304_block_write(&g_sEm9304, HCI_CMD_TYPE, g_pui8EM_CpuReset, sizeof(g_pui8EM_CpuReset) );

    // HCI Respone should return in 1-2 messages at most, but driver returns
    // 0 bytes when nothing is available, so wait up to 10msec.
    for (uint32_t attempts = 0; attempts < EM9304_MAX_ATTEMPTS; attempts++)
    {
      uint32_t numBytesRx;

      numBytesRx = am_devices_em9304_block_read(&g_sEm9304, g_pui32HCIRXBuffer, 0);
      if ((numBytesRx == 7) && (0x0000FC32 == (g_pui32HCIRXBuffer[1] & 0x0000FFFF)))
      {
        am_util_debug_printf("EM9304 CPU Reset Successfully\n");
        break;
      }
      am_util_delay_ms(EM9304_ATTEMPT_DELAY_MS);
    }


    // Send the EM_SetSleepOptions command to disable sleep and check the response.
    am_devices_em9304_block_write(&g_sEm9304, HCI_CMD_TYPE, g_pui8EM_SleepDisable, sizeof(g_pui8EM_SleepDisable) );

    if (EM9304_INIT_STATUS_SUCCESS != waitEM9304Response())
    {
      am_util_debug_printf("No Response to EM9304 Sleep Disable\n");
      return EM9304_INIT_STATUS_ERROR;
    }

    // Check that the response is to the Sleep Disable.
    if ((0x01040E04 != g_pui32HCIRXBuffer[0]) || (0x0000FC2D != (g_pui32HCIRXBuffer[1] & 0x0000FFFF)))
    {
      am_util_debug_printf("Invalid Response to EM9304 Sleep Disable\n");
      return EM9304_INIT_STATUS_ERROR;
    }

    // Send the EM_SetMemoryMode command to turn on OTP and check the response.
    am_devices_em9304_block_write(&g_sEm9304, HCI_CMD_TYPE, g_pui8EM_SetOTPOn, sizeof(g_pui8EM_SetOTPOn) );

    if (EM9304_INIT_STATUS_SUCCESS != waitEM9304Response())
    {
      am_util_debug_printf("No Response to EM9304 OTP Enable\n");
      return EM9304_INIT_STATUS_ERROR;
    }

    // Check that the response is to the OTP enable.
    if ((0x01040E04 != g_pui32HCIRXBuffer[0]) || (0x0000FC2B != (g_pui32HCIRXBuffer[1] & 0x0000FFFF)))
    {
      am_util_debug_printf("Invalid Response to EM9304 OTP Enable\n");
      return EM9304_INIT_STATUS_ERROR;
    }

    // Query the EM9304 with the EM_PatchQuery and Patch Index = 0.  This will return the Container Count.
    am_devices_em9304_block_write(&g_sEm9304, HCI_CMD_TYPE, g_pui8EM_PatchQuery, sizeof(g_pui8EM_PatchQuery) );

    if (EM9304_INIT_STATUS_SUCCESS != waitEM9304Response())
    {
      am_util_debug_printf("No Response to EM9304 Patch Query\n");
      return EM9304_INIT_STATUS_ERROR;
    }

    // Check that the response is to the Patch Query.
    if ((0x01200E04 != g_pui32HCIRXBuffer[0]) || (0x0000FC34 != (g_pui32HCIRXBuffer[1] & 0x0000FFFF)))
    {
      am_util_debug_printf("Invalid Response to EM9304 Patch Query\n");
      return EM9304_INIT_STATUS_ERROR;
    }

    // Extract the container information from the query response.
    containerCount = (uint32_t)pBuf[CONTAINER_COUNT_INDEX] +
      ((uint32_t)pBuf[CONTAINER_COUNT_INDEX + 1] << 8);

    // Assume the first patch is the manufacturing trim patch.
    // This is the only patch that never should be invalidated.
    am_util_debug_printf("Number of patch containers on EM9304 excluding Patch#0: %d\n", containerCount - 1);

  }

  #endif
  #endif

  // For each container in Container Count
  for (uint32_t container = 1; container < containerCount; container++)
  {
    // Send the EM_PatchQuery for the Container.
    g_pui8EM_PatchQuery[PATCH_INDEX_OFFSET] = container;
    am_devices_em9304_block_write(&g_sEm9304, HCI_CMD_TYPE, g_pui8EM_PatchQuery, sizeof(g_pui8EM_PatchQuery) );

    if (EM9304_INIT_STATUS_SUCCESS != waitEM9304Response())
    {
      am_util_debug_printf("No Response to EM9304 Patch Query\n");
      return EM9304_INIT_STATUS_ERROR;
    }

    // Extract the container information from the query response.
    containerCount = (uint32_t)pBuf[CONTAINER_COUNT_INDEX] +
      ((uint32_t)pBuf[CONTAINER_COUNT_INDEX + 1] << 8);
    buildNumber = (uint32_t)pBuf[BUILD_NUMBER_INDEX] +
      ((uint32_t)(pBuf[BUILD_NUMBER_INDEX + 1] << 8));
    userBuildNumber = (uint32_t)pBuf[USER_BUILD_NUMBER_INDEX] +
      ((uint32_t)(pBuf[USER_BUILD_NUMBER_INDEX + 1] << 8));
    containerVersion = pBuf[CONTAINER_VERSION_INDEX];
    containerType = pBuf[CONTAINER_TYPE_INDEX];
    containerID = pBuf[CONTAINER_ID_INDEX];
#ifdef INVALIDATE_UNKNOWN_PATCHES
    containerAddr = (uint32_t)((pBuf[CONTAINER_ADDR_INDEX + 3] << 24) +
                               (pBuf[CONTAINER_ADDR_INDEX + 2] << 16) +
                                 (pBuf[CONTAINER_ADDR_INDEX + 1] << 8) +
                                   pBuf[CONTAINER_ADDR_INDEX]);
    containerSize = (uint32_t)((pBuf[CONTAINER_SIZE_INDEX + 3] << 24) +
                               (pBuf[CONTAINER_SIZE_INDEX + 2] << 16) +
                                 (pBuf[CONTAINER_SIZE_INDEX + 1] << 8) +
                                   pBuf[CONTAINER_SIZE_INDEX]);

    am_util_debug_printf("Patch #%d: Container Address = %8.8X Container Size = %4.4d Container Type=%d Container ID=%d Container Version=%d Build Number=%d User Build Number=%d\n",
                         container, containerAddr, containerSize, containerType, containerID, containerVersion, buildNumber, userBuildNumber);

    // if patch is on IRAM, we wont' do anything to it.
    if (containerAddr & EM9304_IRAM1_START_ADDRESS)
    {
      continue;
    }

    // Check for patches that are likely not configuration managed by the customer.
    // Avoid invalidating these patches.
    if ( ((CONTAINER_TYPE_CONFIG_DATA_WORD == containerType) ||
          (CONTAINER_TYPE_RANDOM_DATA_WORD == containerType)  ||
            (CONTAINER_TYPE_CONFIG_DATA_BYTE == containerType) ||
              (CONTAINER_TYPE_RANDOM_DATA_BYTE == containerType)) &&
        ((0 == buildNumber) || (3089 == buildNumber)) &&
          (0 == userBuildNumber) )
    {
      invalidatePatch = false;
    }
    else
    {
      // Initialize the invalidate flag.
      invalidatePatch = true;
    }
#endif

    // For each local patch, compare the Container Version, Container Type, and Container ID to the container info.
    for (uint32_t patch = 0; patch < EM9304_PATCHES_NUM_PATCHES; patch++)
    {
      if ((g_pEm9304Patches[patch].buildNumber == buildNumber) &&
          (g_pEm9304Patches[patch].userBuildNumber == userBuildNumber) &&
            (g_pEm9304Patches[patch].containerVersion == containerVersion) &&
              (g_pEm9304Patches[patch].containerType == containerType) &&
                (g_pEm9304Patches[patch].containerID == containerID))
      {
        g_pEm9304Patches[patch].applyPatch = false;     // Patch is already installed, so don't apply.
#ifdef INVALIDATE_UNKNOWN_PATCHES
        // Note that we will "re-enable" patches here even if they met the criteria above (which can happen!)
        invalidatePatch = false;
#endif
        break;
      }
    }

#ifdef INVALIDATE_UNKNOWN_PATCHES
    // Check to see if we need to invalidate the patch.
    if (invalidatePatch)
    {
      invalidateEM9304Patch(containerAddr, containerSize);
    }
#endif

  }

  #ifdef INVALIDATE_UNKNOWN_PATCHES
  #ifdef INVALIDATE_HCI_V6_PATCH_ON_OTP

  if (hci_v6_patch_present_on_otp)
  {
    // If we has invalidate v6 or other unknown patch on OTP
    // we should do a cold reset to em9304 in order to clean
    // the previously programmed patch in IRAM.

    // here we have to do code-reset to EM9304
    am_hal_gpio_pin_config(HCI_APOLLO_RESET_PIN, AM_HAL_GPIO_OUTPUT);
    am_hal_gpio_out_bit_clear(HCI_APOLLO_RESET_PIN);

    am_util_delay_ms(10);

    am_hal_gpio_out_bit_set(HCI_APOLLO_RESET_PIN);

    am_util_delay_ms(20);
  }
  #endif
  #endif

  // if (DEST_MEMORY_IRAM == EM9304_PATCHES_DEST_MEMORY)
  // {
  //   // Send the EM_SetMemoryMode command to turn off OTP and check the response.
  //   am_devices_em9304_block_write(&g_sEm9304, HCI_CMD_TYPE, g_pui8EM_SetOTPOff, sizeof(g_pui8EM_SetOTPOff) );

  //   if (EM9304_INIT_STATUS_SUCCESS != waitEM9304Response())
  //   {
  //     am_util_debug_printf("No Response to EM9304 OTP Disable\n");
  //     return EM9304_INIT_STATUS_ERROR;
  //   }

  //   // Check that the response is to the OTP Disable.
  //   if ((0x01040E04 != g_pui32HCIRXBuffer[0]) || (0x0000FC2B != (g_pui32HCIRXBuffer[1] & 0x0000FFFF)))
  //   {
  //     am_util_debug_printf("Invalid Response to EM9304 OTP Disable\n");
  //     return EM9304_INIT_STATUS_ERROR;
  //   }
  // }

  // Send the EM_SetSleepOptions command to disable sleep and check the response.
  am_devices_em9304_block_write(&g_sEm9304, HCI_CMD_TYPE, g_pui8EM_SleepEnable, sizeof(g_pui8EM_SleepEnable) );

  if (EM9304_INIT_STATUS_SUCCESS != waitEM9304Response())
  {
    am_util_debug_printf("No Response to EM9304 Sleep Enable\n");
    return EM9304_INIT_STATUS_ERROR;
  }


  // Check that the response is to the Sleep Enable.
  if ((0x01040E04 != g_pui32HCIRXBuffer[0]) || (0x0000FC2D != (g_pui32HCIRXBuffer[1] & 0x0000FFFF)))
  {
    am_util_debug_printf("Invalid Response to EM9304 Sleep Enable\n");
    return EM9304_INIT_STATUS_ERROR;
  }

  return EM9304_INIT_STATUS_SUCCESS;

}

//*****************************************************************************
//
//! @brief Apply the EM9304 patches.  This routine uses the EM_PatchQuery HCI
//!        command to interogate the connected EM9304 about its current patch
//!        state and then update the patch Container Info data structure.
//!
//!
//! @return Returns the status of the patch application (< 0 is an error).
//
//*****************************************************************************
uint32_t
applyEM9304Patches(uint32_t target_memory, uint32_t containerID)
{
  uint8_t   *bytePtr = (uint8_t *)&g_pui32HCIRXBuffer;
  uint32_t  ui32PN;

  g_EMPatchErrors = 0;

  //
  // Device identification
  //
  ui32PN = AM_REG(MCUCTRL, CHIP_INFO)  &
           AM_UTIL_MCUCTRL_CHIP_INFO_PARTNUM_PN_M;

  // only enable clock for Apollo2-blue (maybe later Apollo3 as well)
  if (ui32PN == AM_UTIL_MCUCTRL_CHIP_INFO_PARTNUM_APOLLOBL)
  {

  }

  if (DEST_MEMORY_IRAM == target_memory)
  {
    // Send the EM_SetMemoryMode command to turn on IRAM and check the response.
    am_devices_em9304_block_write(&g_sEm9304, HCI_CMD_TYPE, g_pui8EM_SetIRAMOn, sizeof(g_pui8EM_SetIRAMOn) );

    if (EM9304_INIT_STATUS_SUCCESS != waitEM9304Response())
    {
      am_util_debug_printf("No Response to EM9304 IRAM Enable\n");
      return EM9304_INIT_STATUS_ERROR;
    }

    // Check that the response is to the IRAM enable.
    if ((0x01040E04 != g_pui32HCIRXBuffer[0]) || (0x0000FC2B != (g_pui32HCIRXBuffer[1] & 0x0000FFFF)))
    {
      am_util_debug_printf("Invalid Response to EM9304 IRAM Enable\n");
      return EM9304_INIT_STATUS_ERROR;
    }
  }

  // Loop through the patches and apply those that are not already there.
  // For each local patch, compare the Container Version, Container Type, and Container ID to the container info.
  for (uint32_t patch = 0; patch < EM9304_PATCHES_NUM_PATCHES; patch++)
  {
    if ((containerID != 0) && (containerID != (uint32_t)g_pEm9304Patches[patch].containerID))
    {
      continue;
    }

    if (g_pEm9304Patches[patch].applyPatch)
    {
      am_util_debug_printf("Applying Patch #%d: Container Type=%d Container ID=%d Container Version=%d Build Number=%d User Build Number=%d\n",
                                 patch, g_pEm9304Patches[patch].containerType, g_pEm9304Patches[patch].containerID, g_pEm9304Patches[patch].containerVersion,
                                 g_pEm9304Patches[patch].buildNumber, g_pEm9304Patches[patch].userBuildNumber);


      for (uint32_t index = g_pEm9304Patches[patch].startingPatch; index < g_pEm9304Patches[patch].endingPatch; index ++)
      {
        if ((index == g_pEm9304Patches[patch].startingPatch) &&
           (target_memory != g_pEm9304PatchesHCICmd[index][PATCH_DEST_MEMORY_OFFSET]))
        {
          // max payload is 64 bytes for patch writing
          uint8_t g_pEm9304PatchesHCICmd_temp[80];

          memcpy(g_pEm9304PatchesHCICmd_temp, g_pEm9304PatchesHCICmd[index],
                g_pEm9304PatchesHCICmd[index][PATCH_LENGTH_OFFSET] + 3);

          // change destination memory type
          g_pEm9304PatchesHCICmd_temp[PATCH_DEST_MEMORY_OFFSET] = target_memory;

          am_devices_em9304_block_write(&g_sEm9304, HCI_CMD_TYPE, (uint8_t *)g_pEm9304PatchesHCICmd_temp,
                                      g_pEm9304PatchesHCICmd_temp[PATCH_LENGTH_OFFSET] + 3 );

        }
        else
        {
          am_devices_em9304_block_write(&g_sEm9304, HCI_CMD_TYPE, (uint8_t *)g_pEm9304PatchesHCICmd[index],
                                      g_pEm9304PatchesHCICmd[index][PATCH_LENGTH_OFFSET] + 3 );

        }

        if (EM9304_INIT_STATUS_SUCCESS != waitEM9304Response())
        {
          am_util_debug_printf("No Response to EM9304 Patch Write\n");
          return EM9304_INIT_STATUS_ERROR;
        }

        if ((g_pEm9304PatchesHCICmd[index][0] == 0x27) &&
            ((bytePtr[EM_PATCH_STATUS_OFFSET] != EM_PATCH_CONTINUE) &&
              (bytePtr[EM_PATCH_STATUS_OFFSET] != EM_PATCH_APPLIED)) )
        {
          am_util_debug_printf("Error Response (%d)to EM9304 Patch Write\n", bytePtr[EM_PATCH_STATUS_OFFSET]);
          return EM9304_INIT_STATUS_ERROR;
        }
        else if (g_pEm9304PatchesHCICmd[index][0] == 0x28)
        {
          if (((index + 1) == g_pEm9304Patches[patch].endingPatch) && (bytePtr[EM_PATCH_STATUS_OFFSET] != EM_PATCH_APPLIED))
          {
            am_util_debug_printf("Error Response to EM9304 Patch Continue (next to last patch segment)\n");
            return EM9304_INIT_STATUS_ERROR;
          }
          else if (((index + 1) < g_pEm9304Patches[patch].endingPatch) && (bytePtr[EM_PATCH_STATUS_OFFSET] != EM_PATCH_CONTINUE))
          {
            am_util_debug_printf("Error Response to EM9304 Patch Continue (last patch segment)\n");
            return EM9304_INIT_STATUS_ERROR;
          }
        }
      }
    }
  }

  return EM9304_INIT_STATUS_SUCCESS;
}

//*****************************************************************************
//
// Configure the necessary pins and start the EM9304 radio.
//
//*****************************************************************************
uint32_t
initEM9304(void)
{

  if (validEM9304Patches())
  {
    //
    // Query the EM9304 for patches
    //
    if (EM9304_INIT_STATUS_SUCCESS == queryEM9304Patches())
    {
      //
      // Apply the patches not already in the EM9304
      //
      if (EM9304_INIT_STATUS_SUCCESS != applyEM9304Patches(EM9304_PATCHES_DEST_MEMORY, 0))
      {
        am_util_debug_printf("EM9304 Patch Application Failed\n");
      }
    }
    else
    {
      am_util_debug_printf("EM9304 Patching Query Failed.  Patch update not applied\n");
    }

  }
  // Send EM_CpuReset HCI command.
  am_devices_em9304_block_write(&g_sEm9304, HCI_CMD_TYPE, g_pui8EM_CpuReset, sizeof(g_pui8EM_CpuReset) );

  // HCI Respone should return in 1-2 messages at most, but driver returns
  // 0 bytes when nothing is available, so wait up to 10msec.
  for (uint32_t attempts = 0; attempts < EM9304_MAX_ATTEMPTS; attempts++)
  {
    uint32_t numBytesRx;

    numBytesRx = am_devices_em9304_block_read(&g_sEm9304, g_pui32HCIRXBuffer, 0);
    if ((numBytesRx == 7) && (0x0000FC32 == (g_pui32HCIRXBuffer[1] & 0x0000FFFF)))
    {
      am_util_debug_printf("EM9304 CPU Reset Successfully\n");
      break;
    }
    am_util_delay_ms(EM9304_ATTEMPT_DELAY_MS);
  }

  return EM9304_PATCHES_DEST_MEMORY;
}
