/******************************************************************************
 *
 *  Copyright (C) 1999-2012 Broadcom Corporation
 *
 *  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.
 *
 ******************************************************************************/

#define LOG_TAG "NfcNciHal"

#include "_OverrideLog.h"
#include "config.h"
#include "nfc_hal_int.h"
#include "userial.h"
extern "C" {
#include "nfc_hal_post_reset.h"
}
#include <cutils/properties.h>
#include <malloc.h>
#include <string>
#include "StartupConfig.h"
#include "spdhelper.h"

#define FW_PRE_PATCH "FW_PRE_PATCH"
#define FW_PATCH "FW_PATCH"
#define MAX_RF_DATA_CREDITS "MAX_RF_DATA_CREDITS"

#define MAX_BUFFER (512)
static char sPrePatchFn[MAX_BUFFER + 1];
static char sPatchFn[MAX_BUFFER + 1];
static void* sPrmBuf = NULL;
static void* sI2cFixPrmBuf = NULL;

#define CONFIG_MAX_LEN 256
static uint8_t sConfig[CONFIG_MAX_LEN];
static StartupConfig sStartupConfig;
static StartupConfig sLptdConfig;
static StartupConfig sPreDiscoveryConfig;
static StartupConfig sXtalCustomParam;
extern uint8_t* p_nfc_hal_dm_start_up_cfg;  // defined in the HAL
static uint8_t nfa_dm_start_up_vsc_cfg[CONFIG_MAX_LEN];
extern uint8_t* p_nfc_hal_dm_start_up_vsc_cfg;  // defined in the HAL
extern uint8_t* p_nfc_hal_dm_lptd_cfg;          // defined in the HAL
static uint8_t sDontSendLptd[] = {0};
extern uint8_t* p_nfc_hal_pre_discover_cfg;    // defined in the HAL
extern uint8_t* p_nfc_hal_dm_xtal_params_cfg;  // defined in HAL

extern tSNOOZE_MODE_CONFIG gSnoozeModeCfg;
extern tNFC_HAL_CFG* p_nfc_hal_cfg;
static void mayDisableSecureElement(StartupConfig& config);

/* Default patchfile (in NCD format) */
#ifndef NFA_APP_DEFAULT_PATCHFILE_NAME
#define NFA_APP_DEFAULT_PATCHFILE_NAME "\0"
#endif

/* Default patchfile (in NCD format) */
#ifndef NFA_APP_DEFAULT_I2C_PATCHFILE_NAME
#define NFA_APP_DEFAULT_I2C_PATCHFILE_NAME "\0"
#endif

tNFC_POST_RESET_CB nfc_post_reset_cb = {
    /* Default Patch & Pre-Patch */
    NFA_APP_DEFAULT_PATCHFILE_NAME,
    NULL,
    NFA_APP_DEFAULT_I2C_PATCHFILE_NAME,
    NULL,

    /* Default UART baud rate */
    NFC_HAL_DEFAULT_BAUD,

    /* Default tNFC_HAL_DEV_INIT_CFG (flags, num_xtal_cfg, {brcm_hw_id,
       xtal-freq, xtal-index} ) */
    {2, /* number of valid entries */
     {
         {0x43341000, 37400,
          NFC_HAL_XTAL_INDEX_37400},  // All revisions of 43341 use 37,400
         {0x20795000, 26000, NFC_HAL_XTAL_INDEX_26000},
         {0, 0, 0},
         {0, 0, 0},
         {0, 0, 0},
     }},

    /* Default low power mode settings */
    NFC_HAL_LP_SNOOZE_MODE_NONE,    /* Snooze Mode          */
    NFC_HAL_LP_IDLE_THRESHOLD_HOST, /* Idle Threshold Host  */
    NFC_HAL_LP_IDLE_THRESHOLD_HC,   /* Idle Threshold HC    */
    NFC_HAL_LP_ACTIVE_LOW,          /* NFC_WAKE Active Mode */
    NFC_HAL_LP_ACTIVE_HIGH,         /* DH_WAKE Active Mode  */

    NFA_APP_MAX_NUM_REINIT, /* max retry to get NVM type */
    0,                      /* current retry count */
    TRUE,                   /* debug mode for downloading patchram */
    FALSE /* skip downloading patchram after reinit because of patch download
             failure */
};

/*******************************************************************************
**
** Function         getFileLength
**
** Description      return the size of a file
**
** Returns          file size in number of bytes
**
*******************************************************************************/
static long getFileLength(FILE* fp) {
  long sz;
  fseek(fp, 0L, SEEK_END);
  sz = ftell(fp);
  fseek(fp, 0L, SEEK_SET);

  return (sz > 0) ? sz : 0;
}

/*******************************************************************************
**
** Function         isFileExist
**
** Description      Check if file name exists (android does not support fexists)
**
** Returns          TRUE if file exists
**
*******************************************************************************/
static bool isFileExist(const char* pFilename) {
  FILE* pf;

  pf = fopen(pFilename, "r");
  if (pf != NULL) {
    fclose(pf);
    return TRUE;
  }
  return FALSE;
}

/*******************************************************************************
**
** Function         findPatchramFile
**
** Description      Find the patchram file name specified in the .conf
**
** Returns          pointer to the file name
**
*******************************************************************************/
static const char* findPatchramFile(const char* pConfigName, char* pBuffer,
                                    int bufferLen) {
  ALOGD("%s: config=%s", __func__, pConfigName);

  if (pConfigName == NULL) {
    ALOGD("%s No patchfile defined\n", __func__);
    return NULL;
  }

  if (GetStrValue(pConfigName, &pBuffer[0], bufferLen)) {
    ALOGD("%s found patchfile %s\n", __func__, pBuffer);
    return (pBuffer[0] == '\0') ? NULL : pBuffer;
  }

  ALOGD("%s Cannot find patchfile '%s'\n", __func__, pConfigName);
  return NULL;
}

/*******************************************************************************
**
** Function:    continueAfterSetSnoozeMode
**
** Description: Called after Snooze Mode is enabled.
**
** Returns:     none
**
*******************************************************************************/
static void continueAfterSetSnoozeMode(tHAL_NFC_STATUS status) {
  ALOGD("%s: status=%u", __func__, status);
  // let stack download firmware during next initialization
  nfc_post_reset_cb.spd_skip_on_power_cycle = FALSE;
  if (status == NCI_STATUS_OK)
    HAL_NfcPreInitDone(HAL_NFC_STATUS_OK);
  else
    HAL_NfcPreInitDone(HAL_NFC_STATUS_FAILED);
}

/*******************************************************************************
**
** Function:    postDownloadPatchram
**
** Description: Called after patch download
**
** Returns:     none
**
*******************************************************************************/
static void postDownloadPatchram(tHAL_NFC_STATUS status) {
  ALOGD("%s: status=%i", __func__, status);
  GetStrValue(NAME_SNOOZE_MODE_CFG, (char*)&gSnoozeModeCfg,
              sizeof(gSnoozeModeCfg));
  if (status != HAL_NFC_STATUS_OK) {
    ALOGE("%s: Patch download failed", __func__);
    if (status == HAL_NFC_STATUS_REFUSED) {
      SpdHelper::setPatchAsBad();
    } else
      SpdHelper::incErrorCount();

    /* If in SPD Debug mode, fail immediately and obviously */
    if (SpdHelper::isSpdDebug())
      HAL_NfcPreInitDone(HAL_NFC_STATUS_FAILED);
    else {
      /* otherwise, power cycle the chip and let the stack startup normally */
      ALOGD("%s: re-init; don't download firmware", __func__);
      // stop stack from downloading firmware during next initialization
      nfc_post_reset_cb.spd_skip_on_power_cycle = TRUE;
      USERIAL_PowerupDevice(0);
      HAL_NfcReInit();
    }
  }
  /* Set snooze mode here */
  else if (gSnoozeModeCfg.snooze_mode != NFC_HAL_LP_SNOOZE_MODE_NONE) {
    status = HAL_NfcSetSnoozeMode(
        gSnoozeModeCfg.snooze_mode, gSnoozeModeCfg.idle_threshold_dh,
        gSnoozeModeCfg.idle_threshold_nfcc, gSnoozeModeCfg.nfc_wake_active_mode,
        gSnoozeModeCfg.dh_wake_active_mode, continueAfterSetSnoozeMode);
    if (status != NCI_STATUS_OK) {
      ALOGE("%s: Setting snooze mode failed, status=%i", __func__, status);
      HAL_NfcPreInitDone(HAL_NFC_STATUS_FAILED);
    }
  } else {
    ALOGD("%s: Not using Snooze Mode", __func__);
    HAL_NfcPreInitDone(HAL_NFC_STATUS_OK);
  }
}

/*******************************************************************************
**
** Function:    prmCallback
**
** Description: Patchram callback (for static patchram mode)
**
** Returns:     none
**
*******************************************************************************/
void prmCallback(uint8_t event) {
  ALOGD("%s: event=0x%x", __func__, event);
  switch (event) {
    case NFC_HAL_PRM_CONTINUE_EVT:
      /* This event does not occur if static patchram buf is used */
      break;

    case NFC_HAL_PRM_COMPLETE_EVT:
      postDownloadPatchram(HAL_NFC_STATUS_OK);
      break;

    case NFC_HAL_PRM_ABORT_EVT:
      postDownloadPatchram(HAL_NFC_STATUS_FAILED);
      break;

    case NFC_HAL_PRM_ABORT_INVALID_PATCH_EVT:
      ALOGD("%s: invalid patch...skipping patch download", __func__);
      postDownloadPatchram(HAL_NFC_STATUS_REFUSED);
      break;

    case NFC_HAL_PRM_ABORT_BAD_SIGNATURE_EVT:
      ALOGD("%s: patch authentication failed", __func__);
      postDownloadPatchram(HAL_NFC_STATUS_REFUSED);
      break;

    case NFC_HAL_PRM_ABORT_NO_NVM_EVT:
      ALOGD("%s: No NVM detected", __func__);
      HAL_NfcPreInitDone(HAL_NFC_STATUS_FAILED);
      break;

    default:
      ALOGD("%s: not handled event=0x%x", __func__, event);
      break;
  }
}

/*******************************************************************************
**
** Function         getNfaValues
**
** Description      Get configuration values needed by NFA layer
**
** Returns:         None
**
*******************************************************************************/
static void getNfaValues(uint32_t chipid) {
  unsigned long num = 0;
  int actualLen = 0;

  sStartupConfig.initialize();
  sLptdConfig.initialize();
  sPreDiscoveryConfig.initialize();

  actualLen =
      GetStrValue(NAME_NFA_DM_START_UP_CFG, (char*)sConfig, sizeof(sConfig));
  if (actualLen) sStartupConfig.append(sConfig, actualLen);

  // Set antenna tuning configuration if configured.
  actualLen =
      GetStrValue(NAME_PREINIT_DSP_CFG, (char*)sConfig, sizeof(sConfig));
  if (actualLen) sStartupConfig.append(sConfig, actualLen);

  if (GetStrValue(NAME_NFA_DM_START_UP_VSC_CFG, (char*)nfa_dm_start_up_vsc_cfg,
                  sizeof(nfa_dm_start_up_vsc_cfg))) {
    p_nfc_hal_dm_start_up_vsc_cfg = &nfa_dm_start_up_vsc_cfg[0];
    ALOGD("START_UP_VSC_CFG[0] = %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x\n",
          nfa_dm_start_up_vsc_cfg[0], nfa_dm_start_up_vsc_cfg[1],
          nfa_dm_start_up_vsc_cfg[2], nfa_dm_start_up_vsc_cfg[3],
          nfa_dm_start_up_vsc_cfg[4], nfa_dm_start_up_vsc_cfg[5],
          nfa_dm_start_up_vsc_cfg[6], nfa_dm_start_up_vsc_cfg[7]);
  }

  actualLen = GetStrValue(NAME_LPTD_CFG, (char*)sConfig, sizeof(sConfig));
  if (actualLen) {
    sLptdConfig.append(sConfig, actualLen);
    p_nfc_hal_dm_lptd_cfg =
        const_cast<uint8_t*>(sLptdConfig.getInternalBuffer());
  } else {
    // Default to not sending any LPTD setting.
    p_nfc_hal_dm_lptd_cfg = sDontSendLptd;
  }

  mayDisableSecureElement(sStartupConfig);
  p_nfc_hal_dm_start_up_cfg =
      const_cast<uint8_t*>(sStartupConfig.getInternalBuffer());

  actualLen = GetStrValue(NAME_NFA_DM_PRE_DISCOVERY_CFG, (char*)sConfig,
                          sizeof(sConfig));
  if (actualLen) {
    sPreDiscoveryConfig.append(sConfig, actualLen);
    mayDisableSecureElement(sPreDiscoveryConfig);
    p_nfc_hal_pre_discover_cfg =
        const_cast<uint8_t*>(sPreDiscoveryConfig.getInternalBuffer());
  }

  // configure how many secure elements are available for each type of chip
  if (p_nfc_hal_cfg->nfc_hal_hci_uicc_support > 0) {
    if ((chipid & BRCM_NFC_GEN_MASK) == BRCM_NFC_20791_GEN) {
      nfc_hal_cb.max_ee = BRCM_NFC_20791_GEN_MAX_EE;
      p_nfc_hal_cfg->nfc_hal_hci_uicc_support =
          HAL_NFC_HCI_UICC0_HOST | HAL_NFC_HCI_UICC1_HOST;
    } else if ((chipid & BRCM_NFC_GEN_MASK) == BRCM_NFC_43341_GEN) {
      nfc_hal_cb.max_ee = BRCM_NFC_43341_GEN_MAX_EE;
      p_nfc_hal_cfg->nfc_hal_hci_uicc_support =
          HAL_NFC_HCI_UICC0_HOST | HAL_NFC_HCI_UICC1_HOST;
    } else if ((chipid & BRCM_NFC_GEN_MASK) == BRCM_NFC_20795_GEN) {
      nfc_hal_cb.max_ee = BRCM_NFC_20795_GEN_MAX_EE;
      p_nfc_hal_cfg->nfc_hal_hci_uicc_support = HAL_NFC_HCI_UICC0_HOST |
                                                HAL_NFC_HCI_UICC1_HOST |
                                                HAL_NFC_HCI_UICC2_HOST;
    }

    // let .conf variable determine how many EE's to discover
    if (GetNumValue(NAME_NFA_MAX_EE_SUPPORTED, &num, sizeof(num)))
      nfc_hal_cb.max_ee = num;
  }
}

/*******************************************************************************
**
** Function         StartPatchDownload
**
** Description      Reads configuration settings, and begins the download
**                  process if patch files are configured.
**
** Returns:         None
**
*******************************************************************************/
static void StartPatchDownload(uint32_t chipid) {
  ALOGD("%s: chipid=%lx", __func__, chipid);

  char chipID[30];
  sprintf(chipID, "%lx", chipid);
  ALOGD("%s: chidId=%s", __func__, chipID);

  readOptionalConfig(chipID);  // Read optional chip specific settings
  readOptionalConfig("fime");  // Read optional FIME specific settings
  getNfaValues(chipid);        // Get NFA configuration values into variables

  findPatchramFile(FW_PATCH, sPatchFn, sizeof(sPatchFn));
  findPatchramFile(FW_PRE_PATCH, sPrePatchFn, sizeof(sPatchFn));

  {
    FILE* fd;
    /* If an I2C fix patch file was specified, then tell the stack about it */
    if (sPrePatchFn[0] != '\0') {
      fd = fopen(sPrePatchFn, "rb");
      if (fd != NULL) {
        uint32_t lenPrmBuffer = getFileLength(fd);

        sI2cFixPrmBuf = malloc(lenPrmBuffer);
        if (sI2cFixPrmBuf != NULL) {
          size_t actualLen = fread(sI2cFixPrmBuf, 1, lenPrmBuffer, fd);
          if (actualLen == lenPrmBuffer) {
            ALOGD("%s Setting I2C fix to %s (size: %lu)", __func__, sPrePatchFn,
                  lenPrmBuffer);
            HAL_NfcPrmSetI2cPatch((uint8_t*)sI2cFixPrmBuf,
                                  (uint16_t)lenPrmBuffer, 0);
          } else
            ALOGE("%s fail reading i2c fix; actual len=%zu; expected len=%lu",
                  __func__, actualLen, lenPrmBuffer);
        } else {
          ALOGE("%s Unable to get buffer to i2c fix (%lu bytes)", __func__,
                lenPrmBuffer);
        }

        fclose(fd);
      } else {
        ALOGE("%s Unable to open i2c fix patchfile %s", __func__, sPrePatchFn);
      }
    }
  }

  {
    FILE* fd;

    /* If a patch file was specified, then download it now */
    if (sPatchFn[0] != '\0') {
      uint32_t bDownloadStarted = false;

      /* open patchfile, read it into a buffer */
      fd = fopen(sPatchFn, "rb");
      if (fd != NULL) {
        uint32_t lenPrmBuffer = getFileLength(fd);
        ALOGD("%s Downloading patchfile %s (size: %lu) format=%u", __func__,
              sPatchFn, lenPrmBuffer, NFC_HAL_PRM_FORMAT_NCD);
        sPrmBuf = malloc(lenPrmBuffer);
        if (sPrmBuf != NULL) {
          size_t actualLen = fread(sPrmBuf, 1, lenPrmBuffer, fd);
          if (actualLen == lenPrmBuffer) {
            if (!SpdHelper::isPatchBad((uint8_t*)sPrmBuf, lenPrmBuffer)) {
              /* Download patch using static memeory mode */
              HAL_NfcPrmDownloadStart(NFC_HAL_PRM_FORMAT_NCD, 0,
                                      (uint8_t*)sPrmBuf, lenPrmBuffer, 0,
                                      prmCallback);
              bDownloadStarted = true;
            }
          } else
            ALOGE("%s fail reading patchram", __func__);
        } else
          ALOGE("%s Unable to buffer to hold patchram (%lu bytes)", __func__,
                lenPrmBuffer);

        fclose(fd);
      } else
        ALOGE("%s Unable to open patchfile %s", __func__, sPatchFn);

      /* If the download never got started */
      if (!bDownloadStarted) {
        /* If debug mode, fail in an obvious way, otherwise try to start stack
         */
        postDownloadPatchram(SpdHelper::isSpdDebug() ? HAL_NFC_STATUS_FAILED
                                                     : HAL_NFC_STATUS_OK);
      }
    } else {
      ALOGE(
          "%s: No patchfile specified or disabled. Proceeding to post-download "
          "procedure...",
          __func__);
      postDownloadPatchram(HAL_NFC_STATUS_OK);
    }
  }

  ALOGD("%s: exit", __func__);
}

/*******************************************************************************
**
** Function:    nfc_hal_post_reset_init
**
** Description: Called by the NFC HAL after controller has been reset.
**              Begin to download firmware patch files.
**
** Returns:     none
**
*******************************************************************************/
void nfc_hal_post_reset_init(uint32_t brcm_hw_id, uint8_t nvm_type) {
  ALOGD("%s: brcm_hw_id=0x%lx, nvm_type=%d", __func__, brcm_hw_id, nvm_type);
  tHAL_NFC_STATUS stat = HAL_NFC_STATUS_FAILED;
  uint8_t max_credits = 1, allow_no_nvm = 0;

  p_nfc_hal_cfg->nfc_hal_prm_nvm_required =
      TRUE;  // don't download firmware if controller cannot detect EERPOM

  if (nvm_type == NCI_SPD_NVM_TYPE_NONE) {
    GetNumValue(NAME_ALLOW_NO_NVM, &allow_no_nvm, sizeof(allow_no_nvm));
    if (allow_no_nvm == 0) {
      ALOGD("%s: No NVM detected, FAIL the init stage to force a retry",
            __func__);
      USERIAL_PowerupDevice(0);
      stat = HAL_NfcReInit();
      return;
    }

    p_nfc_hal_cfg->nfc_hal_prm_nvm_required =
        FALSE;  // allow download firmware if controller cannot detect EERPOM
  }

  /* Start downloading the patch files */
  StartPatchDownload(brcm_hw_id);

  if (GetNumValue(MAX_RF_DATA_CREDITS, &max_credits, sizeof(max_credits)) &&
      (max_credits > 0)) {
    ALOGD("%s : max_credits=%d", __func__, max_credits);
    HAL_NfcSetMaxRfDataCredits(max_credits);
  }
}

/*******************************************************************************
**
** Function:        mayDisableSecureElement
**
** Description:     Optionally adjust a TLV to disable secure element.  This
*feature
**                  is enabled by setting the system property
**                  nfc.disable_secure_element to a bit mask represented by a
*hex
**                  octet: C0 = do not detect any secure element.
**                         40 = do not detect secure element in slot 0.
**                         80 = do not detect secure element in slot 1.
**
**                  config: a sequence of TLV's.
**
*******************************************************************************/
void mayDisableSecureElement(StartupConfig& config) {
  unsigned int bitmask = 0;
  char valueStr[PROPERTY_VALUE_MAX] = {0};
  int len = property_get("nfc.disable_secure_element", valueStr, "");
  if (len > 0) {
    sscanf(valueStr, "%x", &bitmask);  // read system property as a hex octet
    ALOGD("%s: disable 0x%02X", __func__, (uint8_t)bitmask);
    config.disableSecureElement((uint8_t)(bitmask & 0xC0));
  }
}

/*******************************************************************************
**
** Function:    configureCrystalFrequency
**
** Description: Configure controller's crystal frequency by reading values from
**              .conf file.  If .conf file does not define any value, then use
**              default values defined in struct nfc_post_reset_cb.
**
** Returns:     none
**
*******************************************************************************/
void configureCrystalFrequency() {
  unsigned long num = 0;
  uint32_t hwId = 0;
  uint16_t xtalFreq = 0;
  uint8_t xtalIndex = 0;
  int actualLen = 0;

  if (GetNumValue(NAME_XTAL_HARDWARE_ID, &num, sizeof(num))) hwId = num;

  if (GetNumValue(NAME_XTAL_FREQUENCY, &num, sizeof(num)))
    xtalFreq = (uint16_t)num;

  if (GetNumValue(NAME_XTAL_FREQ_INDEX, &num, sizeof(num)))
    xtalIndex = (uint8_t)num;

  actualLen =
      GetStrValue(NAME_XTAL_PARAMS_CFG, (char*)sConfig, sizeof(sConfig));
  if (actualLen &&
      (xtalIndex ==
       NFC_HAL_XTAL_INDEX_SPECIAL))  // whether to use custom crystal frequency
  {
    sXtalCustomParam.append(sConfig, actualLen);
    p_nfc_hal_dm_xtal_params_cfg =
        const_cast<uint8_t*>(sXtalCustomParam.getInternalBuffer());
  }

  if ((hwId == 0) && (xtalFreq == 0) && (xtalIndex == 0)) return;

  ALOGD("%s: hwId=0x%lX; freq=%u; index=%u", __func__, hwId, xtalFreq,
        xtalIndex);
  nfc_post_reset_cb.dev_init_config.xtal_cfg[0].brcm_hw_id =
      (hwId & BRCM_NFC_GEN_MASK);
  nfc_post_reset_cb.dev_init_config.xtal_cfg[0].xtal_freq = xtalFreq;
  nfc_post_reset_cb.dev_init_config.xtal_cfg[0].xtal_index = xtalIndex;
  nfc_post_reset_cb.dev_init_config.num_xtal_cfg = 1;
}