/*
 * bladeRF FX3 firmware (bladeRF.c)
 *
 * Copyright (c) 2013 Nuand LLC
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
#include "cyu3system.h"
#include "cyu3os.h"
#include "cyu3dma.h"
#include "cyu3error.h"
#include "cyu3usb.h"
#include "bladeRF.h"
#include "cyu3gpif.h"
#include "cyu3pib.h"
#include "cyu3gpio.h"
#include "pib_regs.h"

#include "flash.h"
#include "spi_flash_lib.h"
#include "rf.h"
#include "fpga.h"
#include "version.h"            /* Generated by CMake */

#define THIS_FILE LOGGER_ID_BLADERF_C

uint32_t glAppMode = MODE_NO_CONFIG;

CyU3PThread bladeRFAppThread;

uint8_t glUsbConfiguration = 0;             /* Active USB configuration. */
uint8_t glUsbAltInterface = 0;                 /* Active USB interface. */


uint8_t glSelBuffer[32];
uint8_t glPageBuffer[FLASH_PAGE_SIZE] __attribute__ ((aligned (32)));

CyBool_t glCalCacheValid = CyFalse;
uint8_t glCal[CAL_BUFFER_SIZE] __attribute__ ((aligned (32)));

CyBool_t glDeviceReady   = CyFalse;         /* Used to denote that the device
                                             * can't be accessed until the FPGA
                                             * autoload is finished */
CyBool_t glAutoLoadValid = CyFalse;
uint8_t glAutoLoad[CAL_BUFFER_SIZE] __attribute__ ((aligned (32)));

/* Standard product string descriptor */
uint8_t CyFxUSBSerial[] __attribute__ ((aligned (32))) =
{
    0x42,                           /* Descriptor size */
    CY_U3P_USB_STRING_DESCR,        /* Device descriptor type */
    '0',0x00,'0',0x00,'0',0x00,'0',0x00,'0',0x00,'0',0x00,'0',0x00,'0',0x00,
    '0',0x00,'0',0x00,'0',0x00,'0',0x00,'0',0x00,'0',0x00,'0',0x00,'0',0x00,
    '0',0x00,'0',0x00,'0',0x00,'0',0x00,'0',0x00,'0',0x00,'0',0x00,'0',0x00,
    '0',0x00,'0',0x00,'0',0x00,'0',0x00,'0',0x00,'0',0x00,'0',0x00,'0',0x00,
};

uint8_t CyFxUSBVersion[sizeof(FIRMWARE_VERSION)*2+2] __attribute__ ((aligned (32)));

void populateVersionString() {
    uint32_t i;
    CyFxUSBVersion[0] = sizeof(CyFxUSBVersion);
    CyFxUSBVersion[1] = CY_U3P_USB_STRING_DESCR;
    for (i = 0; i < sizeof(FIRMWARE_VERSION) ; i++) {
        CyFxUSBVersion[2*i+2] = FIRMWARE_VERSION[i];
    }
    return;
}

/* Application Error Handler */
void CyFxAppErrorHandler(CyU3PReturnStatus_t apiRetStatus)
{
    /* firmware failed with the error code apiRetStatus */

    /* Loop Indefinitely */
    for (;;)
        /* Thread sleep : 100 ms */
        CyU3PThreadSleep(100);
}

void NuandGPIOReconfigure(CyBool_t fullGpif, CyBool_t warm)
{
    CyU3PReturnStatus_t apiRetStatus = CY_U3P_SUCCESS;
    CyU3PIoMatrixConfig_t io_cfg;
    CyU3PGpioSimpleConfig_t gpioConfig;
    CyU3PReturnStatus_t status = CY_U3P_SUCCESS;
    size_t i;

    struct {
        int pin;        // pin number
        int input;      // is this an input pin

        int warm;       // should this pin be enabled via IOMatrix() call?
                        // GPIF pins should be marked False, because they have to overriden
    } pins[] = {
        {GPIO_nSTATUS,  CyTrue,  CyTrue},
        {GPIO_CONFDONE, CyTrue,  CyTrue},
        {GPIO_SYS_RST,  CyFalse, CyFalse},
        {GPIO_RX_EN,    CyFalse, CyFalse},
        {GPIO_TX_EN,    CyFalse, CyFalse},
        {GPIO_nCONFIG,  CyFalse, CyTrue},
        {GPIO_ID,       CyTrue,  CyTrue},
        {GPIO_LED,      CyFalse,  CyTrue}
    };
#define ARR_SIZE(x) (sizeof(x)/sizeof(x[0]))

    io_cfg.useUart   = CyTrue;
    io_cfg.useI2C    = CyFalse;
    io_cfg.useI2S    = CyFalse;
    io_cfg.useSpi    = !fullGpif;
    io_cfg.isDQ32Bit = fullGpif;
    io_cfg.lppMode   = CY_U3P_IO_MATRIX_LPP_DEFAULT;

    io_cfg.gpioSimpleEn[0]  = 0;
    io_cfg.gpioSimpleEn[1]  = 0;
    if (warm) {
        for (i = 0; i < ARR_SIZE(pins); i++) {
            if (!pins[i].warm)
                continue;

            if (pins[i].pin < 32) {
                io_cfg.gpioSimpleEn[0] |= 1 << (pins[i].pin - 1);
            } else {
                io_cfg.gpioSimpleEn[1] |= 1 << (pins[i].pin - 32);
            }
        }
    }
    io_cfg.gpioComplexEn[0] = 0;
    io_cfg.gpioComplexEn[1] = 0;

    status = CyU3PDeviceConfigureIOMatrix (&io_cfg);
    if (status != CY_U3P_SUCCESS) {
        while(1);
    }

    for (i = 0; i < ARR_SIZE(pins); i++) {
        // the pin has already been activated by the call to IOMatrix()
        if (warm && pins[i].warm)
            continue;

        apiRetStatus = CyU3PDeviceGpioOverride(pins[i].pin, CyTrue);
        if (apiRetStatus != 0) {
            LOG_ERROR(apiRetStatus);
            CyFxAppErrorHandler(apiRetStatus);
        }

        gpioConfig.intrMode = CY_U3P_GPIO_NO_INTR;

        if (pins[i].input) {
            // input config
            gpioConfig.outValue = CyTrue;
            gpioConfig.inputEn = CyTrue;
            gpioConfig.driveLowEn = CyFalse;
            gpioConfig.driveHighEn = CyFalse;
        } else {
            // output config
            gpioConfig.outValue = CyFalse;
            gpioConfig.driveLowEn = CyTrue;
            gpioConfig.driveHighEn = CyTrue;
            gpioConfig.inputEn = CyFalse;
        }

        apiRetStatus = CyU3PGpioSetSimpleConfig(pins[i].pin, &gpioConfig);
        if (apiRetStatus != CY_U3P_SUCCESS) {
            LOG_ERROR(apiRetStatus);
            CyFxAppErrorHandler(apiRetStatus);
        }
    }
}

uint16_t NuandGetProductID() {
    if (NuandGetSPIManufacturer() == 0xEF) {
        return USB_NUAND_BLADERF2_PRODUCT_ID;
    }
    return USB_NUAND_BLADERF_PRODUCT_ID;
}

void NuandFpgaConfigSwFlipLut(uint16_t flipLut[256]) {
    if (NuandGetProductID() == USB_NUAND_BLADERF_PRODUCT_ID) {
        NuandFpgaConfigSwFlipLut_bladeRF1(flipLut);
    } else {
        NuandFpgaConfigSwFlipLut_bladeRF2(flipLut);
    }
}

void CyFxGpioInit(void)
{
    CyU3PGpioClock_t gpioClock;
    CyU3PReturnStatus_t apiRetStatus = CY_U3P_SUCCESS;

    /* Init the GPIO module */
    gpioClock.fastClkDiv = 2;
    gpioClock.slowClkDiv = 0;
    gpioClock.simpleDiv = CY_U3P_GPIO_SIMPLE_DIV_BY_2;
    gpioClock.clkSrc = CY_U3P_SYS_CLK;
    gpioClock.halfDiv = 0;

    apiRetStatus = CyU3PGpioInit(&gpioClock, NULL);
    if (apiRetStatus != 0)
    {
        /* Error Handling */
        LOG_ERROR(apiRetStatus);
        CyFxAppErrorHandler(apiRetStatus);
    }

    NuandGPIOReconfigure(CyTrue, CyFalse);
}

static void StopApplication()
{
    if (glAppMode == MODE_RF_CONFIG) {
        NuandRFLink.stop();
    } else if (glAppMode == MODE_FPGA_CONFIG) {
        /* if a flash autoload is in progress, don't stop it */
        if (glDeviceReady || !glAutoLoadValid) {
            NuandFpgaConfig.stop();
        }
    }
}

CyBool_t GetStatus(uint16_t endpoint) {
    CyBool_t isHandled = CyFalse;
    uint8_t get_status_reply[] = {0x00, 0x00};

    switch(glUsbAltInterface) {
    case USB_IF_RF_LINK:
        isHandled = NuandRFLink.halted(endpoint, &get_status_reply[0]);
        break;
    case USB_IF_CONFIG:
        isHandled = NuandFpgaConfig.halted(endpoint, &get_status_reply[0]);
        break;
    default:
    case USB_IF_SPI_FLASH:
        /* USB_IF_CONFIG has no end points */
        break;
    }

    if(isHandled) {
        CyU3PUsbSendEP0Data(sizeof(get_status_reply), get_status_reply);
    }

    return isHandled;
}

CyU3PReturnStatus_t ClearDMAChannel(uint8_t ep,
                                    CyU3PDmaChannel * handle,
                                    uint32_t count)
{
    CyU3PReturnStatus_t status;

    status = CyU3PDmaChannelReset(handle);
    if (status != CY_U3P_SUCCESS) {
        LOG_ERROR(status);
        return status;
    }

    status = CyU3PUsbFlushEp(ep);
    if (status != CY_U3P_SUCCESS) {
        LOG_ERROR(status);
        return status;
    }

    status = CyU3PUsbResetEp(ep);
    if (status != CY_U3P_SUCCESS) {
        LOG_ERROR(status);
        return status;
    }

    status = CyU3PDmaChannelSetXfer (handle, count);
    if (status != CY_U3P_SUCCESS) {
        LOG_ERROR(status);
    }

    return status;
}

CyBool_t ClearHaltCondition(uint16_t endpoint) {
    CyBool_t isHandled = CyFalse;

    switch(glUsbAltInterface) {
    case USB_IF_RF_LINK:
        isHandled = NuandRFLink.halt_endpoint(CyFalse, endpoint);
        break;
    case USB_IF_CONFIG:
        isHandled = NuandFpgaConfig.halt_endpoint(CyFalse, endpoint);
        break;
    default:
    case USB_IF_SPI_FLASH:
        /* USB_IF_SPI_FLASH has no end points */
        break;
    }

    return isHandled;
}

CyBool_t SetHaltCondition(uint16_t endpoint) {
    CyBool_t isHandled = CyFalse;

    switch(glUsbAltInterface) {
    case USB_IF_RF_LINK:
        isHandled = NuandRFLink.halt_endpoint(CyTrue, endpoint);
        break;
    case USB_IF_CONFIG:
        isHandled = NuandFpgaConfig.halt_endpoint(CyTrue, endpoint);
        break;
    default:
    case USB_IF_SPI_FLASH:
        /* USB_IF_CONFIG has no end points */
        break;
    }

    return isHandled;
}

void CyU3PUsbSendRetCode(CyU3PReturnStatus_t ret_status) {
    CyU3PReturnStatus_t apiRetStatus;
    apiRetStatus = CyU3PUsbSendEP0Data(
            sizeof(ret_status),
            (void*)&ret_status);

    if(apiRetStatus != CY_U3P_SUCCESS) {
        LOG_ERROR(apiRetStatus);
    }
}

static CyU3PReturnStatus_t NuandReadCalTable(uint8_t *cal_buff) {
    CyU3PReturnStatus_t apiRetStatus;
    apiRetStatus = CyFxSpiTransfer(CAL_PAGE, CAL_BUFFER_SIZE, cal_buff, CyTrue, CyFalse);

    /* FIXME: Validate table */

    return apiRetStatus;
}

static CyU3PReturnStatus_t NuandReadAutoLoad(uint8_t *cal_buff) {
    CyU3PReturnStatus_t apiRetStatus;
    apiRetStatus = CyFxSpiTransfer(AUTOLOAD_PAGE, AUTOLOAD_BUFFER_SIZE, cal_buff, CyTrue, CyFalse);

    /* FIXME: Validate table */

    return apiRetStatus;
}

CyBool_t NuandHandleVendorRequest(
        uint8_t bRequest,
        uint16_t wValue,
        uint16_t wIndex,
        uint16_t wLength
        ) {
    CyBool_t isHandled;
    unsigned int ret;
    uint8_t use_feature;
    CyBool_t fpgaProg;
    struct bladerf_fx3_version ver;
    CyU3PReturnStatus_t apiRetStatus = CY_U3P_SUCCESS;
    int retStatus;
    uint16_t readC;
    CyBool_t txen, rxen;
    txen = rxen = CyFalse ;
    isHandled = CyTrue;

    /* Device is not ready to handle requests */
    if (!glDeviceReady && bRequest != BLADE_USB_CMD_QUERY_DEVICE_READY) {
        return -1;
    }

    switch (bRequest)
    {
    case BLADE_USB_CMD_QUERY_VERSION:
        ver.major = FIRMWARE_VERSION_MAJOR;
        ver.minor = FIRMWARE_VERSION_MINOR;
        apiRetStatus = CyU3PUsbSendEP0Data(sizeof(ver), (void*)&ver);
    break;

    case BLADE_USB_CMD_RF_RX:
        apiRetStatus = CY_U3P_SUCCESS;
        use_feature = wValue;

        CyU3PGpioGetValue(GPIO_TX_EN, &txen) ;
        CyU3PGpioGetValue(GPIO_RX_EN, &rxen) ;
        if (txen == CyFalse && rxen == CyFalse) {
            CyU3PGpioSetValue(GPIO_SYS_RST, CyTrue) ;
            CyU3PGpioSetValue(GPIO_SYS_RST, CyFalse);
        }

        CyU3PGpioSetValue(GPIO_RX_EN, use_feature ? CyTrue : CyFalse);

        if (!use_feature) {
            const uint8_t ep = BLADE_RF_SAMPLE_EP_CONSUMER;
            apiRetStatus = NuandRFLink.reset_endpoint(ep);
            if(apiRetStatus != CY_U3P_SUCCESS) {
                LOG_ERROR(apiRetStatus);
            }
        }

        CyU3PUsbSendRetCode(apiRetStatus);
    break;

    case BLADE_USB_CMD_RF_TX:
        apiRetStatus = CY_U3P_SUCCESS;
        use_feature = wValue;

        CyU3PGpioGetValue(GPIO_TX_EN, &txen) ;
        CyU3PGpioGetValue(GPIO_RX_EN, &rxen) ;
        if (txen == CyFalse && rxen == CyFalse) {
            CyU3PGpioSetValue(GPIO_SYS_RST, CyTrue) ;
            CyU3PGpioSetValue(GPIO_SYS_RST, CyFalse);
        }

        CyU3PGpioSetValue(GPIO_TX_EN, use_feature ? CyTrue : CyFalse);

        if (!use_feature) {
            const uint8_t ep = BLADE_RF_SAMPLE_EP_PRODUCER;
            apiRetStatus = NuandRFLink.reset_endpoint(ep);
            if(apiRetStatus != CY_U3P_SUCCESS) {
                LOG_ERROR(apiRetStatus);
            }
        }

        CyU3PUsbSendRetCode(apiRetStatus);
    break;

    case BLADE_USB_CMD_BEGIN_PROG:
        retStatus = FpgaBeginProgram();
        if(0 == retStatus) {
            NuandSetFpgaConfigSource(NUAND_FPGA_CONFIG_SOURCE_HOST);
        }
        CyU3PUsbSendRetCode(retStatus);
    break;

    case BLADE_USB_CMD_QUERY_FPGA_STATUS:
        apiRetStatus = CyU3PGpioGetValue (GPIO_CONFDONE, &fpgaProg);
        if (apiRetStatus == CY_U3P_SUCCESS) {
            ret = fpgaProg ? 1 : 0;
        } else {
            ret = -1;
        }

        CyU3PUsbSendRetCode(ret);
    break;

    case BLADE_USB_CMD_QUERY_DEVICE_READY:
        ret = glDeviceReady ? 1 : 0;
        CyU3PUsbSendRetCode(ret);
    break;

    case BLADE_USB_CMD_QUERY_FLASH_ID:
        ret = (NuandGetSPIManufacturer() << 8) | NuandGetSPIDeviceID();
        CyU3PUsbSendRetCode(ret);
    break;

    case BLADE_USB_CMD_QUERY_FPGA_SOURCE:
        ret = (unsigned int)NuandGetFpgaConfigSource();
        CyU3PUsbSendRetCode(ret);

    case BLADE_USB_CMD_SET_LOOPBACK:
        NuandRFLinkLoopBack(wValue);
        CyU3PUsbSendRetCode(wValue);
    break;

    case BLADE_USB_CMD_GET_LOOPBACK:
    {
        int isLoopbackEnabled = NuandRFLinkGetLoopBack();
        CyU3PUsbSendRetCode(isLoopbackEnabled);
    }
    break;

    case BLADE_USB_CMD_READ_PAGE_BUFFER:
        if(wIndex + wLength > sizeof(glPageBuffer)) {
            apiRetStatus = CyU3PUsbStall(0x80, CyTrue, CyFalse);
        }

        apiRetStatus = CyU3PUsbSendEP0Data(wLength, &glPageBuffer[wIndex]);
        if(apiRetStatus != CY_U3P_SUCCESS) {
            LOG_ERROR(apiRetStatus);
        }
    break;

    case BLADE_USB_CMD_WRITE_PAGE_BUFFER:
        if(wIndex + wLength > sizeof(glPageBuffer)) {
            apiRetStatus = CyU3PUsbStall(0x0, CyTrue, CyFalse);
        }

        apiRetStatus = CyU3PUsbGetEP0Data(wLength, &glPageBuffer[wIndex], &readC);
        if(apiRetStatus != CY_U3P_SUCCESS) {
            LOG_ERROR(apiRetStatus);
        }

        if(readC != wLength) {
            LOG_ERROR(readC);
            LOG_ERROR(wLength);
        }
    break;

    case BLADE_USB_CMD_WRITE_OTP:
        if (glUsbAltInterface != USB_IF_SPI_FLASH) {
            apiRetStatus = CyU3PUsbStall(0x80, CyTrue, CyFalse);
        }

        apiRetStatus = NuandWriteOtp(wIndex, 0x100, glPageBuffer);
        CyU3PUsbSendRetCode(apiRetStatus);
    break;

    case BLADE_USB_CMD_READ_OTP:
        if (glUsbAltInterface != USB_IF_SPI_FLASH) {
            apiRetStatus = CyU3PUsbStall(0x80, CyTrue, CyFalse);
        }

        apiRetStatus = NuandReadOtp(wIndex, 0x100, glPageBuffer);
        CyU3PUsbSendRetCode(apiRetStatus);
    break;

    case BLADE_USB_CMD_LOCK_OTP:
        if (glUsbAltInterface != USB_IF_SPI_FLASH) {
            apiRetStatus = CyU3PUsbStall(0x80, CyTrue, CyFalse);
        }

        apiRetStatus = NuandLockOtp();
        CyU3PUsbSendRetCode(apiRetStatus);
    break;

    case BLADE_USB_CMD_FLASH_READ:
        if (glUsbAltInterface != USB_IF_SPI_FLASH) {
            apiRetStatus = CyU3PUsbStall(0x80, CyTrue, CyFalse);
        }

        apiRetStatus = CyFxSpiTransfer (
                wIndex, 0x100,
                glPageBuffer, CyTrue, CyFalse);
        CyU3PUsbSendRetCode(apiRetStatus);
    break;

    case BLADE_USB_CMD_FLASH_WRITE:
        if (glUsbAltInterface != USB_IF_SPI_FLASH) {
            apiRetStatus = CyU3PUsbStall(0x80, CyTrue, CyFalse);
        }

        apiRetStatus = CyFxSpiTransfer (
                wIndex, 0x100,
                glPageBuffer, CyFalse, CyFalse);
        CyU3PUsbSendRetCode(apiRetStatus);
    break;

    case BLADE_USB_CMD_FLASH_ERASE:
        if (glUsbAltInterface != USB_IF_SPI_FLASH) {
           apiRetStatus = CyU3PUsbStall(0x80, CyTrue, CyFalse);
        }

        apiRetStatus = CyFxSpiEraseSector(CyTrue, wIndex);
        CyU3PUsbSendRetCode(apiRetStatus);
    break;

    case BLADE_USB_CMD_READ_CAL_CACHE:
        if(!glCalCacheValid) {
            /* Fail the request if the cache is invalid */
            apiRetStatus = CyU3PUsbStall(0x80, CyTrue, CyFalse);
        }

        if(wIndex + wLength > sizeof(glCal)) {
            apiRetStatus = CyU3PUsbStall(0x80, CyTrue, CyFalse);
        }

        apiRetStatus = CyU3PUsbSendEP0Data(wLength, &glCal[wIndex]);
        if (apiRetStatus != CY_U3P_SUCCESS) {
            LOG_ERROR(apiRetStatus);
        }
    break;

    case BLADE_USB_CMD_INVALIDATE_CAL_CACHE:
        glCalCacheValid = CyFalse;
        CyU3PUsbAckSetup();
    break;

    case BLADE_USB_CMD_REFRESH_CAL_CACHE:
        if (glUsbAltInterface != USB_IF_SPI_FLASH) {
           apiRetStatus = CyU3PUsbStall(0x80, CyTrue, CyFalse);
        }

        apiRetStatus = NuandReadCalTable(glCal);
        if(apiRetStatus == CY_U3P_SUCCESS) {
            glCalCacheValid = CyTrue;
        }
        CyU3PUsbSendRetCode(apiRetStatus);
    break;

    case BLADE_USB_CMD_RESET:
        CyU3PUsbAckSetup();
        CyU3PDeviceReset(CyFalse);
    break;

    case BLADE_USB_CMD_JUMP_TO_BOOTLOADER:
        StopApplication();
        NuandFlashInit();

        // Erase the first sector so we can write the bootloader
        // header
        apiRetStatus = CyFxSpiEraseSector(CyTrue, 0);
        if(apiRetStatus != CY_U3P_SUCCESS) {
            CyU3PUsbStall(0, CyTrue, CyFalse);
            CyU3PDeviceReset(CyFalse);
            break;
        }

        uint8_t bootloader_header[] = {
            'C','Y', // Common header
            0,       // 10 MHz SPI, image data
            0xB2,    // Bootloader VID/PID
            (USB_NUAND_BLADERF_BOOT_PRODUCT_ID & 0xFF),
            (USB_NUAND_BLADERF_BOOT_PRODUCT_ID & 0xFF00) >> 8,
            (USB_NUAND_VENDOR_ID & 0xFF),
            (USB_NUAND_VENDOR_ID & 0xFF00) >> 8,
        };

        apiRetStatus = CyFxSpiTransfer (
                /* Page */ 0,
                sizeof(bootloader_header), bootloader_header,
                CyFalse, CyFalse /* Writing */
                );

        CyU3PUsbAckSetup();
        CyU3PDeviceReset(CyFalse);
    break;

    case BLADE_USB_CMD_READ_LOG_ENTRY:
        CyU3PUsbSendRetCode(logger_read());
    break;

    default:
        isHandled = CyFalse;
    }

    return isHandled;
}

/* Callback to handle the USB setup requests. */
CyBool_t CyFxbladeRFApplnUSBSetupCB(uint32_t setupdat0, uint32_t setupdat1)
{
    uint8_t  bRequest, bReqType;
    uint8_t  bType, bTarget;
    uint16_t wValue, wIndex, wLength;
    uint8_t bTemp;
    CyBool_t isHandled = CyFalse;

    /* Decode the fields from the setup request. */
    bReqType = (setupdat0 & CY_U3P_USB_REQUEST_TYPE_MASK);
    bType    = (bReqType & CY_U3P_USB_TYPE_MASK);
    bTarget  = (bReqType & CY_U3P_USB_TARGET_MASK);
    bRequest = ((setupdat0 & CY_U3P_USB_REQUEST_MASK) >> CY_U3P_USB_REQUEST_POS);
    wValue   = ((setupdat0 & CY_U3P_USB_VALUE_MASK)   >> CY_U3P_USB_VALUE_POS);
    wIndex   = ((setupdat1 & CY_U3P_USB_INDEX_MASK)   >> CY_U3P_USB_INDEX_POS);
    wLength  = ((setupdat1 & CY_U3P_USB_LENGTH_MASK)  >> CY_U3P_USB_LENGTH_POS);

    if (bType == CY_U3P_USB_STANDARD_RQT)
    {
        if (bRequest == CY_U3P_USB_SC_SET_SEL)
        {
            {
                if ((CyU3PUsbGetSpeed () == CY_U3P_SUPER_SPEED) && (wValue == 0) && (wIndex == 0) && (wLength == 6))
                {
                    CyU3PUsbGetEP0Data (32, glSelBuffer, 0);
                }
                else
                {
                    isHandled = CyFalse;
                }
            }
        }


        if (bRequest == CY_U3P_USB_SC_GET_INTERFACE && wLength == 1) {
                bTemp = glUsbAltInterface;
                CyU3PUsbSendEP0Data(wLength, &bTemp);
                isHandled = CyTrue;
        }

        if (bRequest == CY_U3P_USB_SC_GET_CONFIGURATION && wLength == 1) {
                bTemp = glUsbConfiguration;
                CyU3PUsbSendEP0Data(wLength, &bTemp);
                isHandled = CyTrue;
        }

        /* Handle suspend requests */
        if ((bTarget == CY_U3P_USB_TARGET_INTF) && ((bRequest == CY_U3P_USB_SC_SET_FEATURE)
                    || (bRequest == CY_U3P_USB_SC_CLEAR_FEATURE)) && (wValue == 0)) {
            if (glUsbConfiguration != 0) {
                /* FIXME: Actually implement suspend/resume requests */
                CyU3PUsbAckSetup();
            } else {
                CyU3PUsbStall(0, CyTrue, CyFalse);
            }
            isHandled = CyTrue;
        }

        if ((bTarget == CY_U3P_USB_TARGET_ENDPT) && (bRequest == CY_U3P_USB_SC_GET_STATUS))
        {
            if (glAppMode != MODE_NO_CONFIG) {
                isHandled = GetStatus(wIndex);
            }
        }

        if ((bTarget == CY_U3P_USB_TARGET_ENDPT) && (bRequest == CY_U3P_USB_SC_CLEAR_FEATURE)
                && (wValue == CY_U3P_USBX_FS_EP_HALT))
        {
            if (glAppMode != MODE_NO_CONFIG) {
                isHandled = ClearHaltCondition(wIndex);
            }
        }

        /* Flush endpoint memory and reset channel if CLEAR_FEATURE is received */
        if ((bTarget == CY_U3P_USB_TARGET_ENDPT) && (bRequest == CY_U3P_USB_SC_SET_FEATURE)
                && (wValue == CY_U3P_USBX_FS_EP_HALT))
        {
            if (glAppMode != MODE_NO_CONFIG) {
                isHandled = SetHaltCondition(wIndex);
            }
        }
    }

    /* Handle supported bladeRF vendor requests. */
    if (bType == CY_U3P_USB_VENDOR_RQT)
    {
        isHandled = NuandHandleVendorRequest(bRequest, wValue, wIndex, wLength);
    }

    return isHandled;
}

/* This is the callback function to handle the USB events. */
void CyFxbladeRFApplnUSBEventCB (CyU3PUsbEventType_t evtype, uint16_t evdata)
{
    int interface;
    int alt_interface;
    switch (evtype)
    {
        case CY_U3P_USB_EVENT_SETINTF:
            interface = (evdata & 0xf0) >> 4;
            alt_interface = evdata & 0xf;

            /* Only support sets to interface 0 for now */
            if(interface != 0) break;

            /* Don't do anything if we're setting the same interface over */
            if( alt_interface == glUsbAltInterface ) break ;

            /* Stop whatever we were doing */
            switch(glUsbAltInterface) {
                case USB_IF_CONFIG: NuandFpgaConfig.stop() ; break ;
                case USB_IF_RF_LINK: NuandRFLink.stop(); break ;
                case USB_IF_SPI_FLASH: NuandFlashDeinit(); break ;
                default: break ;
            }

            /* Start up the new one */
            if (alt_interface == USB_IF_CONFIG) {
                NuandFpgaConfig.start();
            } else if (alt_interface == USB_IF_RF_LINK) {
                NuandRFLink.start();
            } else if (alt_interface == USB_IF_SPI_FLASH) {
                NuandFlashInit();
            }
            glUsbAltInterface = alt_interface;
        break;

        case CY_U3P_USB_EVENT_SETCONF:
            glUsbConfiguration = evdata;
            break;

        case CY_U3P_USB_EVENT_RESET:
        case CY_U3P_USB_EVENT_DISCONNECT:
            /* Stop the loop back function. */
            StopApplication();
            break;

        default:
            break;
    }
}

/* Callback function to handle LPM requests from the USB 3.0 host. This function is invoked by the API
   whenever a state change from U0 -> U1 or U0 -> U2 happens. If we return CyTrue from this function, the
   FX3 device is retained in the low power state. If we return CyFalse, the FX3 device immediately tries
   to trigger an exit back to U0.

   This application does not have any state in which we should not allow U1/U2 transitions; and therefore
   the function always return CyTrue.
 */

static CyBool_t allow_suspend = CyTrue;

void NuandAllowSuspend(CyBool_t set_allow_suspend) {
    allow_suspend = set_allow_suspend;
}

static CyBool_t CyFxApplnLPMRqtCB (
        CyU3PUsbLinkPowerMode link_mode)
{
    return allow_suspend;
}

static uint8_t otp_buf[0x100];
static void extractSerialAndCal(void)
{
    int status;
    char serial_buf[32];
    int i;

    NuandFlashInit();

    status = NuandReadOtp(0, 0x100, otp_buf);

    /* Call this here while flash chip is init'd */
    cacheSPIManufacturer();

    if (!NuandExtractField((void*)otp_buf, 0x100, "S", serial_buf, 32)) {
        for (i = 0; i < 32; i++) {
            CyFxUSBSerial[2+i*2] = serial_buf[i];
        }
    }

    /* Initialize calibration table cache */
    status = NuandReadCalTable(glCal);
    if(status == CY_U3P_SUCCESS) {
        glCalCacheValid = CyTrue;
    }

    status = NuandReadAutoLoad(glAutoLoad);
    if(status == CY_U3P_SUCCESS) {
        glAutoLoadValid = CyTrue;
    }

    NuandFlashDeinit();
}

void bladeRFInit(void)
{
    CyU3PReturnStatus_t apiRetStatus = CY_U3P_SUCCESS;
    const uint8_t *usb3_device_descr;
    const uint8_t *usb2_device_descr;
    const uint8_t *usb_product_descr;

    if (NuandGetProductID() == USB_NUAND_BLADERF_PRODUCT_ID) {
        usb3_device_descr = CyFxUSB30DeviceDscr_bladeRF1;
        usb2_device_descr = CyFxUSB20DeviceDscr_bladeRF1;
        usb_product_descr = CyFxUSBProductDscr_bladeRF1;
    } else {
        usb3_device_descr = CyFxUSB30DeviceDscr_bladeRF2;
        usb2_device_descr = CyFxUSB20DeviceDscr_bladeRF2;
        usb_product_descr = CyFxUSBProductDscr_bladeRF2;
    }

    /* Start the USB functionality. */
    apiRetStatus = CyU3PUsbStart();
    if (apiRetStatus != CY_U3P_SUCCESS) {
        LOG_ERROR(apiRetStatus);
        CyFxAppErrorHandler(apiRetStatus);
    }

    /* The fast enumeration is the easiest way to setup a USB connection,
     * where all enumeration phase is handled by the library. Only the
     * class / vendor requests need to be handled by the application. */
    CyU3PUsbRegisterSetupCallback(CyFxbladeRFApplnUSBSetupCB, CyFalse);

    /* Setup the callback to handle the USB events. */
    CyU3PUsbRegisterEventCallback(CyFxbladeRFApplnUSBEventCB);

    /* Register a callback to handle LPM requests from the USB 3.0 host. */
    CyU3PUsbRegisterLPMRequestCallback(CyFxApplnLPMRqtCB);

    /* Set the USB Enumeration descriptors */

    /* Super speed device descriptor. */
    apiRetStatus = CyU3PUsbSetDesc(CY_U3P_USB_SET_SS_DEVICE_DESCR, 0, (uint8_t *)usb3_device_descr);
    if (apiRetStatus != CY_U3P_SUCCESS) {
        LOG_ERROR(apiRetStatus);
        CyFxAppErrorHandler(apiRetStatus);
    }

    /* High speed device descriptor. */
    apiRetStatus = CyU3PUsbSetDesc(CY_U3P_USB_SET_HS_DEVICE_DESCR, 0, (uint8_t *)usb2_device_descr);
    if (apiRetStatus != CY_U3P_SUCCESS) {
        LOG_ERROR(apiRetStatus);
        CyFxAppErrorHandler(apiRetStatus);
    }

    /* BOS descriptor */
    apiRetStatus = CyU3PUsbSetDesc(CY_U3P_USB_SET_SS_BOS_DESCR, 0, (uint8_t *)CyFxUSBBOSDscr);
    if (apiRetStatus != CY_U3P_SUCCESS) {
        LOG_ERROR(apiRetStatus);
        CyFxAppErrorHandler(apiRetStatus);
    }

    /* Device qualifier descriptor */
    apiRetStatus = CyU3PUsbSetDesc(CY_U3P_USB_SET_DEVQUAL_DESCR, 0, (uint8_t *)CyFxUSBDeviceQualDscr);
    if (apiRetStatus != CY_U3P_SUCCESS) {
        LOG_ERROR(apiRetStatus);
        CyFxAppErrorHandler(apiRetStatus);
    }

    /* Super speed configuration descriptor */
    apiRetStatus = CyU3PUsbSetDesc(CY_U3P_USB_SET_SS_CONFIG_DESCR, 0, (uint8_t *)CyFxUSBSSConfigDscr);
    if (apiRetStatus != CY_U3P_SUCCESS) {
        LOG_ERROR(apiRetStatus);
        CyFxAppErrorHandler(apiRetStatus);
    }

    /* High speed configuration descriptor */
    apiRetStatus = CyU3PUsbSetDesc(CY_U3P_USB_SET_HS_CONFIG_DESCR, 0, (uint8_t *)CyFxUSBHSConfigDscr);
    if (apiRetStatus != CY_U3P_SUCCESS) {
        LOG_ERROR(apiRetStatus);
        CyFxAppErrorHandler(apiRetStatus);
    }

    /* Full speed configuration descriptor */
    apiRetStatus = CyU3PUsbSetDesc(CY_U3P_USB_SET_FS_CONFIG_DESCR, 0, (uint8_t *)CyFxUSBFSConfigDscr);
    if (apiRetStatus != CY_U3P_SUCCESS) {
        LOG_ERROR(apiRetStatus);
        CyFxAppErrorHandler(apiRetStatus);
    }

    /* String descriptor 0 */
    apiRetStatus = CyU3PUsbSetDesc(CY_U3P_USB_SET_STRING_DESCR, 0, (uint8_t *)CyFxUSBStringLangIDDscr);
    if (apiRetStatus != CY_U3P_SUCCESS) {
        LOG_ERROR(apiRetStatus);
        CyFxAppErrorHandler(apiRetStatus);
    }

    /* Manufacturer string descriptor */
    apiRetStatus = CyU3PUsbSetDesc(CY_U3P_USB_SET_STRING_DESCR,
                                   BLADE_USB_STR_INDEX_MFR,
                                   (uint8_t *)CyFxUSBManufactureDscr);

    if (apiRetStatus != CY_U3P_SUCCESS) {
        LOG_ERROR(apiRetStatus);
        CyFxAppErrorHandler(apiRetStatus);
    }

    /* Product string descriptor */
    apiRetStatus = CyU3PUsbSetDesc(CY_U3P_USB_SET_STRING_DESCR,
                                   BLADE_USB_STR_INDEX_PRODUCT,
                                   (uint8_t *)usb_product_descr);

    if (apiRetStatus != CY_U3P_SUCCESS) {
        LOG_ERROR(apiRetStatus);
        CyFxAppErrorHandler(apiRetStatus);
    }

    /* Configure the serial number string descriptor */
    apiRetStatus = CyU3PUsbSetDesc(CY_U3P_USB_SET_STRING_DESCR,
                                   BLADE_USB_STR_INDEX_SERIAL,
                                   (uint8_t *)CyFxUSBSerial);

    if (apiRetStatus != CY_U3P_SUCCESS) {
        LOG_ERROR(apiRetStatus);
        CyFxAppErrorHandler(apiRetStatus);
    }

    /* Firmware version string descriptor */
    apiRetStatus = CyU3PUsbSetDesc(CY_U3P_USB_SET_STRING_DESCR,
                                  BLADE_USB_STR_INDEX_FW_VER,
                                  (uint8_t *)CyFxUSBVersion);

    if (apiRetStatus != CY_U3P_SUCCESS) {
        LOG_ERROR(apiRetStatus);
        CyFxAppErrorHandler(apiRetStatus);
    }

    /* Connect the USB Pins with super speed operation enabled. */
    apiRetStatus = CyU3PConnectState(CyTrue, CyTrue);
    if (apiRetStatus != CY_U3P_SUCCESS) {
        LOG_ERROR(apiRetStatus);
        CyFxAppErrorHandler(apiRetStatus);
    }

    glAppMode = MODE_NO_CONFIG;
}

/* Entry function for the bladeRFAppThread. */
void bladeRFAppThread_Entry( uint32_t input)
{
    CyFxGpioInit();

    populateVersionString();
    extractSerialAndCal();

    NuandFpgaConfigSwInit();

    bladeRFInit();
    /* XXX Why do we need an 800ms delay here? It appears required for the FPGA
     * load...
     *
     * Is there an I/O or state variable that we could instead poll?
     */
    CyU3PThreadSleep(800);

    if (glAutoLoadValid) {
        char fpga_len[11] = {0};
        if (!NuandExtractField((void*)glAutoLoad, 0x100, "LEN", (char *)&fpga_len, 10)) {
            fpga_len[10] = 0;
            NuandLoadFromFlash(atoi(fpga_len));
        }
    }

    glDeviceReady = CyTrue;

    while ( 1 ) {
        /* Additional application-specific code can go here */
        CyU3PThreadSleep(1000);
    }
}

/* Application define function which creates the threads. */
void CyFxApplicationDefine(void)
{
    void *ptr = NULL;
    uint32_t retThrdCreate = CY_U3P_SUCCESS;

    /* Allocate the memory for the threads */
    ptr = CyU3PMemAlloc(BLADE_THREAD_STACK);

    /* Create the thread for the application */
    retThrdCreate = CyU3PThreadCreate(&bladeRFAppThread,    /* Slave FIFO app thread structure */
                          "21:bladeRF_thread",               /* Thread ID and thread name */
                          bladeRFAppThread_Entry,            /* Slave FIFO app thread entry function */
                          0,                                 /* No input parameter to thread */
                          ptr,                               /* Pointer to the allocated thread stack */
                          BLADE_THREAD_STACK,                /* App Thread stack size */
                          BLADE_THREAD_PRIORITY,             /* App Thread priority */
                          BLADE_THREAD_PRIORITY,             /* App Thread pre-emption threshold */
                          CYU3P_NO_TIME_SLICE,               /* No time slice for the application thread */
                          CYU3P_AUTO_START                   /* Start the thread immediately */
                          );

    if (retThrdCreate != 0) {
        /* Application cannot continue */
        /* Loop indefinitely */
        while(1);
    }
}

/*
 * Main function
 */
int main(void)
{
    CyU3PReturnStatus_t status = CY_U3P_SUCCESS;

    /* Initialize the device */
    status = CyU3PDeviceInit(NULL);
    if (status != CY_U3P_SUCCESS) {
        goto handle_fatal_error;
    }

    /* Initialize the caches. Enable both Instruction and Data Caches. */
    status = CyU3PDeviceCacheControl(CyTrue, CyTrue, CyTrue);
    if (status != CY_U3P_SUCCESS) {
        goto handle_fatal_error;
    }

    /* Initialize the logger so we can record failures past this point */
    logger_init();

    /* This is a non returnable call for initializing the RTOS kernel */
    CyU3PKernelEntry();

    /* Dummy return to make the compiler happy */
    return 0;

handle_fatal_error:
    /* Cannot recover from this error. */
    while (1);
}
