#include <windows.h>
#include <ceddk.h>
#include <nkintr.h>

#include "base_regs.h"  // BASE_REG_PA_PMU_MISC
#include "syscon_reg.h" // PMU_MISC_REG
#include "gpio_reg.h"   // GPIO_REG
#include "intr_reg.h"   // IRQ_CEC
#include "SGIPLIB\sgip_GPIO_register_map.h"

#include "cecdrv.h"
#include "cec.h"

#include "cec_reg.h"

//TODO: move
#define FIN 24000000 // 24MHz

#define CEC_TIMEOUT 300 // 300 milliseconds

#define CEC_DEBUG


DBGPARAM dpCurSettings =
{
    __MODULE__,
    {
        TEXT("Init"),
        TEXT("Open"),
        TEXT("Read"),
        TEXT("Write"),
        TEXT("Close"),
        TEXT("Ioctl"),
        TEXT("IRQ"),
        TEXT("Power")
        TEXT("Warning"),
        TEXT("Error"),
        TEXT(""),
        TEXT(""),
        TEXT(""),
        TEXT(""),
        TEXT(""),
        TEXT("")
    },
#ifndef DEBUG
    RETAILZONES
#else
    DEBUGZONES
#endif
};

static volatile GPIO_REG *g_pGpioReg = NULL;
static volatile PMU_MISC_REG *g_pPMUMISCRegs = NULL;

static volatile CEC_REGS *g_pCECRegs = NULL;
static DWORD g_dwSysIntrCEC = SYSINTR_UNDEFINED;
static HANDLE g_hEventCEC = NULL;
static HANDLE g_hThreadCEC = NULL;
static BOOL g_bRunning = FALSE;

static HANDLE g_hEventCECTxDone = NULL;
static HANDLE g_hEventCECRxDone = NULL;

static enum CECState {
    STATE_ERROR,
    STATE_DONE,
};

static struct {
    CRITICAL_SECTION cs;
    enum CECState state;
} CECTxContext;

static struct {
    CRITICAL_SECTION cs;
    enum CECState state;
    PBYTE pBuf;
    DWORD nSize;
} CECRxContext;


static BOOL CEC_SetLogicalAddress(BYTE bLogicalAddr);
static BOOL CEC_SendFrame(PBYTE pBuffer, DWORD dwSize);
static BOOL CEC_ReceiveFrame(PBYTE pBuffer, DWORD dwSize, PDWORD pdwActualSize);

BOOL WINAPI DllEntry(HINSTANCE hinstDll, DWORD dwReason, LPVOID lpReserved)
{
    switch (dwReason)
    {
        case DLL_PROCESS_ATTACH:
            RETAILREGISTERZONES(hinstDll);
            RETAILMSG(ZONE_INIT, (TEXT("[CEC] %s() : Process Attach\r\n"), TEXT(__FUNCTION__)));
            DisableThreadLibraryCalls((HMODULE) hinstDll);
            break;
        case DLL_PROCESS_DETACH:
            RETAILMSG(ZONE_INIT, (TEXT("[CEC] %s() : Process Detach\r\n"), TEXT(__FUNCTION__)));
            break;
    }

    return TRUE;
}


static void cec_enable_rx(void)
{
    DWORD reg;
    reg = g_pCECRegs->RX_CTRL;
    reg |= CEC_RX_CTRL_ENABLE;
    g_pCECRegs->RX_CTRL = reg;
}

void cec_mask_rx_interrupts(void)
{
    DWORD reg;
    reg = g_pCECRegs->IRQ_MASK;
    reg |= CEC_IRQ_RX_DONE;
    reg |= CEC_IRQ_RX_ERROR;
    g_pCECRegs->IRQ_MASK = reg;
}

void cec_unmask_rx_interrupts(void)
{
    DWORD reg;
    reg = g_pCECRegs->IRQ_MASK;
    reg &= ~CEC_IRQ_RX_DONE;
    reg &= ~CEC_IRQ_RX_ERROR;
    g_pCECRegs->IRQ_MASK = reg;
}

void cec_mask_tx_interrupts(void)
{
    DWORD reg;
    reg = g_pCECRegs->IRQ_MASK;
    reg |= CEC_IRQ_TX_DONE;
    reg |= CEC_IRQ_TX_ERROR;
    g_pCECRegs->IRQ_MASK = reg;
}

void cec_unmask_tx_interrupts(void)
{
    DWORD reg;
    reg = g_pCECRegs->IRQ_MASK;
    reg &= ~CEC_IRQ_TX_DONE;
    reg &= ~CEC_IRQ_TX_ERROR;
    g_pCECRegs->IRQ_MASK = reg;
}

void cec_set_divider(void)
{
    DWORD uDivRatio, uTemp;
	
    uDivRatio = FIN/160000-1; // 160KHz

    uTemp = g_pPMUMISCRegs->IP_CON_REG.HDMI_CONTROL;
    uTemp = (uTemp & ~(0x3FF<<16)) | (uDivRatio<<16);
    g_pPMUMISCRegs->IP_CON_REG.HDMI_CONTROL = uTemp;

    // (CEC_DIVISOR) * (clock cycle time) = 0.05ms
    g_pCECRegs->DIVISOR_3 = 0x0;
    g_pCECRegs->DIVISOR_2 = 0x0;
    g_pCECRegs->DIVISOR_1 = 0x0;
    g_pCECRegs->DIVISOR_0 = 0x7;
}


void CEC_PowerUp(DWORD hDeviceContext)
{
    RETAILMSG(ZONE_POWER, (TEXT("[CEC] %s(0x%08x)\r\n"), TEXT(__FUNCTION__), hDeviceContext));

//TODO: implement
}


void CEC_PowerDown(DWORD hDeviceContext)
{
    RETAILMSG(ZONE_POWER, (TEXT("[CEC] %s(0x%08x)\r\n"), TEXT(__FUNCTION__), hDeviceContext));

//TODO: implement
}


DWORD WINAPI CECThread(LPVOID lpParameter)
{
    DWORD dwStatus;

    RETAILMSG(ZONE_IRQ, (TEXT("[CEC] %s()\r\n"), TEXT(__FUNCTION__)));

    while (g_bRunning)
    {
        WaitForSingleObject(g_hEventCEC, INFINITE);

        if (!g_bRunning)
            break;

        dwStatus = g_pCECRegs->STATUS_0;
        dwStatus |= (g_pCECRegs->STATUS_1) << 8;
        dwStatus |= (g_pCECRegs->STATUS_2) << 16;
        dwStatus |= (g_pCECRegs->STATUS_3) << 24;

        RETAILMSG(ZONE_IRQ, (TEXT("[CEC] %s() : status = 0x%x\r\n"), TEXT(__FUNCTION__), dwStatus));
        
        if (dwStatus & CEC_STATUS_TX_DONE)
        {
            if (dwStatus & CEC_STATUS_TX_ERROR)
            {
                RETAILMSG(ZONE_ERROR, (TEXT("[CEC] %s() : CEC_STATUS_TX_ERROR!\r\n"), TEXT(__FUNCTION__)));
                EnterCriticalSection(&CECTxContext.cs);
                CECTxContext.state = STATE_ERROR;
                LeaveCriticalSection(&CECTxContext.cs);
            }
            else
            {
                RETAILMSG(ZONE_IRQ, (TEXT("[CEC] %s() : CEC_STATUS_TX_DONE!\r\n"), TEXT(__FUNCTION__)));
                EnterCriticalSection(&CECTxContext.cs);
                CECTxContext.state = STATE_DONE;
                LeaveCriticalSection(&CECTxContext.cs);
            }
            /* clear interrupt pending bit */
            g_pCECRegs->IRQ_CLEAR = CEC_IRQ_TX_DONE | CEC_IRQ_TX_ERROR;
            SetEvent(g_hEventCECTxDone);
        }

        if (dwStatus & CEC_STATUS_RX_DONE)
        {
            if (dwStatus & CEC_STATUS_RX_ERROR)
            {
                RETAILMSG(ZONE_ERROR, (TEXT("[CEC] %s() : CEC_STATUS_RX_ERROR!\r\n"), TEXT(__FUNCTION__)));
                /* reset CEC Rx */
                g_pCECRegs->RX_CTRL = CEC_RX_CTRL_RESET;
            }
            else
            {
                DWORD nSize, i;

                RETAILMSG(ZONE_IRQ, (TEXT("[CEC] %s() : CEC_STATUS_RX_DONE!\r\n"), TEXT(__FUNCTION__)));

                /* copy data from HW buffer */
                nSize = dwStatus >> 24;

                EnterCriticalSection(&CECRxContext.cs);
                for (i = 0; i < nSize; ++i)
                    CECRxContext.pBuf[i] = (BYTE) g_pCECRegs->RX_BUFF[i];

                CECRxContext.nSize = nSize;
                CECRxContext.state = STATE_DONE;
                LeaveCriticalSection(&CECRxContext.cs);

                cec_enable_rx();
            }
            /* clear interrupt pending bit */
            g_pCECRegs->IRQ_CLEAR = CEC_IRQ_RX_DONE | CEC_IRQ_RX_ERROR;
            SetEvent(g_hEventCECRxDone);
        }

        InterruptDone(g_dwSysIntrCEC);
    }

    return 0;
}


BOOL CEC_Deinit(DWORD hDeviceContext)
{
    RETAILMSG(ZONE_INIT, (TEXT("[CEC] %s()\r\n"), TEXT(__FUNCTION__)));

    if (g_pGpioReg)
    {
        MmUnmapIoSpace((PVOID) g_pGpioReg, sizeof(GPIO_REG));
        g_pGpioReg = NULL;
    }

    if (g_pPMUMISCRegs)
    {
        MmUnmapIoSpace((PVOID) g_pPMUMISCRegs, sizeof(PMU_MISC_REG));
        g_pPMUMISCRegs = NULL;
    }

    if (g_pCECRegs)
    {
        MmUnmapIoSpace((PVOID) g_pCECRegs, sizeof(CEC_REGS));
        g_pCECRegs = NULL;
    }

    if (g_dwSysIntrCEC != SYSINTR_UNDEFINED)
    {
        InterruptDisable(g_dwSysIntrCEC);
        KernelIoControl(IOCTL_HAL_RELEASE_SYSINTR, &g_dwSysIntrCEC, sizeof(DWORD), NULL, 0, NULL);
        g_dwSysIntrCEC = SYSINTR_UNDEFINED;
    }

    if (g_hEventCEC)
    {
        CloseHandle(g_hEventCEC);
        g_hEventCEC = NULL;
    }

    return TRUE;
}


DWORD CEC_Init(LPCTSTR pContext)
{
    DWORD dwIRQ;
    PHYSICAL_ADDRESS ioPhysicalBase = {0, 0};

    RETAILMSG(ZONE_INIT, (TEXT("[CEC] %s()\r\n"), TEXT(__FUNCTION__)));

    ioPhysicalBase.LowPart = BASE_REG_PA_GPIO;
    g_pGpioReg = (GPIO_REG *) MmMapIoSpace(ioPhysicalBase, sizeof(GPIO_REG), FALSE);
    if (g_pGpioReg == NULL)
    {
        RETAILMSG(ZONE_ERROR, (_T("[CEC:ERR] %s() : MmMapIoSpace() failed!\r\n"), _T(__FUNCTION__)));
        return FALSE;
    }

    ioPhysicalBase.LowPart = BASE_REG_PA_PMU_MISC;
    g_pPMUMISCRegs = (PMU_MISC_REG *) MmMapIoSpace(ioPhysicalBase, sizeof(PMU_MISC_REG), FALSE);
    if (g_pPMUMISCRegs == NULL)
    {
        RETAILMSG(ZONE_ERROR, (_T("[CEC:ERR] %s() : MmMapIoSpace() failed!\r\n"), _T(__FUNCTION__)));
        return FALSE;
    }

    ioPhysicalBase.LowPart = BASE_REG_PA_CEC;
    g_pCECRegs = (CEC_REGS *) MmMapIoSpace(ioPhysicalBase, sizeof(CEC_REGS), FALSE);
    if (g_pCECRegs == NULL)
    {
        RETAILMSG(ZONE_ERROR, (TEXT("[CEC:ERR] %s() : MmMapIoSpace() failed!\r\n"), TEXT(__FUNCTION__)));
        goto CleanUp;
    }

    Set_PinFunction(g_pGpioReg, GPH14_HDMI_CEC);
    Set_PinPullUD(g_pGpioReg, GPH14_HDMI_CEC, 0);

    dwIRQ = IRQ_CEC;
    if (!KernelIoControl(IOCTL_HAL_REQUEST_SYSINTR, &dwIRQ, sizeof(DWORD), &g_dwSysIntrCEC, sizeof(DWORD), NULL))
    {
        RETAILMSG(ZONE_ERROR, (TEXT("[CEC:ERR] %s() : IOCTL_HAL_REQUEST_SYSINTR failed!\r\n"), TEXT(__FUNCTION__)));
        g_dwSysIntrCEC = SYSINTR_UNDEFINED;
        goto CleanUp;
    }

    g_hEventCEC = CreateEvent(NULL, FALSE, FALSE, NULL);
    if (g_hEventCEC == NULL)
    {
        RETAILMSG(ZONE_ERROR, (TEXT("[CEC:ERR] %s() : CreateEvent() failed!\r\n"), TEXT(__FUNCTION__)));
        goto CleanUp;
    }

    //disable interrupts (Rx, Tx) if they are enabled
    //TODO: before enabling interrupts from CEC

    if (!(InterruptInitialize(g_dwSysIntrCEC, g_hEventCEC, 0, 0)))
    {
        RETAILMSG(ZONE_ERROR, (TEXT("[CEC:ERR] %s() : InterruptInitialize() failed!\r\n"), TEXT(__FUNCTION__)));
        goto CleanUp;
    }

    return TRUE;

CleanUp:
    CEC_Deinit(0);
    return FALSE;
}


DWORD CEC_Open(DWORD hDeviceContext, DWORD AccessCode, DWORD ShareMode)
{
    RETAILMSG(ZONE_OPEN, (TEXT("[CEC] %s()\r\n"), TEXT(__FUNCTION__)));

    g_hEventCECTxDone = CreateEvent(NULL, FALSE, FALSE, NULL);
    if (g_hEventCECTxDone == NULL)
    {
        RETAILMSG(ZONE_ERROR, (TEXT("[CEC:ERR] %s() : CreateEvent() failed!\r\n"), TEXT(__FUNCTION__)));
        goto CleanUp;
    }

    g_hEventCECRxDone = CreateEvent(NULL, FALSE, FALSE, NULL);
    if (g_hEventCECRxDone == NULL)
    {
        RETAILMSG(ZONE_ERROR, (TEXT("[CEC:ERR] %s() : CreateEvent() failed!\r\n"), TEXT(__FUNCTION__)));
        goto CleanUp;
    }

    InitializeCriticalSection(&CECTxContext.cs);
    InitializeCriticalSection(&CECRxContext.cs);

    CECRxContext.pBuf = LocalAlloc(LMEM_FIXED, CEC_MAX_FRAME_SIZE);
    if (CECRxContext.pBuf == NULL)
    {
        RETAILMSG(ZONE_ERROR, (TEXT("[CEC:ERR] %s() : LocalAlloc() failed!\r\n"), TEXT(__FUNCTION__)));
        goto CleanUp;
    }

    // Reset CEC Rx and Tx
    g_pCECRegs->RX_CTRL = CEC_RX_CTRL_RESET;
    g_pCECRegs->TX_CTRL = CEC_TX_CTRL_RESET;

    cec_set_divider();

    /* Setup filter */
    g_pCECRegs->RX_FILTER_TH = CEC_FILTER_THRESHOLD;
    g_pCECRegs->RX_FILTER_CTRL = 0;

    g_bRunning = TRUE;
    g_hThreadCEC = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) CECThread, NULL, 0, NULL);
    if (g_hThreadCEC == NULL)
    {
        RETAILMSG(ZONE_ERROR, (TEXT("[CEC:ERR] %s() : CreateThread() failed!\r\n"), TEXT(__FUNCTION__)));
        g_bRunning = FALSE;
        goto CleanUp;
    }

    // Enable interrupts
//???

    //unmask tx interrupts
    cec_unmask_tx_interrupts();
    //unmask rx
    cec_unmask_rx_interrupts();
    //enable rx
    cec_enable_rx();

    return TRUE;

CleanUp:
    if (g_hEventCECTxDone)
    {
        CloseHandle(g_hEventCECTxDone);
        g_hEventCECTxDone = NULL;
    }

    if (g_hEventCECRxDone)
    {
        CloseHandle(g_hEventCECRxDone);
        g_hEventCECRxDone = NULL;
    }

    DeleteCriticalSection(&CECTxContext.cs);
    DeleteCriticalSection(&CECRxContext.cs);

    if (CECRxContext.pBuf)
    {
        LocalFree(CECRxContext.pBuf);
        CECRxContext.pBuf = NULL;
    }

    SetLastError(ERROR_CREATE_FAILED);
    return FALSE;
}


BOOL CEC_Close(DWORD hOpenContext)
{
    RETAILMSG(ZONE_CLOSE, (TEXT("[CEC] %s()\r\n"), TEXT(__FUNCTION__)));

    // mask rx and tx interrupts
    cec_mask_tx_interrupts();
    cec_mask_rx_interrupts();

    if (g_hThreadCEC)
    {
        g_bRunning = FALSE;
        SetEvent(g_hEventCEC);
        WaitForSingleObject(g_hThreadCEC, INFINITE);
        CloseHandle(g_hThreadCEC);
        g_hThreadCEC = NULL;
    }

    if (g_hEventCECTxDone)
    {
        CloseHandle(g_hEventCECTxDone);
        g_hEventCECTxDone = NULL;
    }

    if (g_hEventCECRxDone)
    {
        CloseHandle(g_hEventCECRxDone);
        g_hEventCECRxDone = NULL;
    }

    DeleteCriticalSection(&CECTxContext.cs);
    DeleteCriticalSection(&CECRxContext.cs);

    if (CECRxContext.pBuf)
    {
        LocalFree(CECRxContext.pBuf);
        CECRxContext.pBuf = NULL;
    }

    return TRUE;
}


BOOL CEC_IOControl(
  DWORD hOpenContext,
  DWORD dwCode,
  PBYTE pBufIn,
  DWORD dwLenIn,
  PBYTE pBufOut,
  DWORD dwLenOut,
  PDWORD pdwActualOut)
{
    BOOL bResult = TRUE;

//    RETAILMSG(ZONE_IOCTL, (TEXT("[CEC] %s() : IOCTL code = 0x%x\r\n"), TEXT(__FUNCTION__), dwCode));

    switch (dwCode)
    {
#if 0
        case IOCTL_POWER_CAPABILITIES: // determines device-specific capabilities
        case IOCTL_POWER_QUERY: // determines whether changing power state is feasible
        case IOCTL_POWER_SET: // requests a change from one device power state to another
        case IOCTL_POWER_GET: // gets the current device power state
#endif
        case IOCTL_CEC_SET_LADDR:
        {
            BYTE bLogicalAddr;
            RETAILMSG(ZONE_IOCTL, (TEXT("[CEC] IOCTL_CEC_SET_LADDR\r\n")));
            bLogicalAddr = *(PBYTE) pBufIn;
            if (!CEC_SetLogicalAddress(bLogicalAddr))
                bResult = FALSE;
            break;
        }
        case IOCTL_CEC_SND_FRAME:
            RETAILMSG(ZONE_IOCTL, (TEXT("[CEC] IOCTL_CEC_SND_FRAME\r\n")));
            if (!CEC_SendFrame(pBufIn, dwLenIn))
                bResult = FALSE;
            break;
        case IOCTL_CEC_RCV_FRAME:
//            RETAILMSG(ZONE_IOCTL, (TEXT("[CEC] IOCTL_CEC_RCV_FRAME\r\n")));
            if (!CEC_ReceiveFrame(pBufOut, dwLenOut, pdwActualOut))
                bResult = FALSE;
            break;
        default:
            RETAILMSG(ZONE_ERROR, (TEXT("[CEC:ERR] Unhandled ioctl!\r\n")));
            bResult = FALSE;
            break;
    }

    return bResult;
}

static BOOL CEC_SetLogicalAddress(BYTE bLogicalAddr)
{
    RETAILMSG(ZONE_IOCTL, (TEXT("[CEC] %s() : addr = %d\r\n"), TEXT(__FUNCTION__), bLogicalAddr));

    g_pCECRegs->LOGIC_ADDR = bLogicalAddr & 0x0F;

    return TRUE;
}


static BOOL CEC_SendFrame(PBYTE pBuffer, DWORD dwSize)
{
    BOOL bRetval = TRUE;
    DWORD dwRes;
    DWORD reg;
    DWORD i;

    RETAILMSG(ZONE_IOCTL, (TEXT("[CEC] %s() : buffer = 0x%x, size = %d\r\n"), TEXT(__FUNCTION__), pBuffer, dwSize));

    /* check buffer pointer */
    if (pBuffer == NULL)
    {
        RETAILMSG(ZONE_ERROR, (TEXT("[CEC:ERR] %s() : buffer incorrect!\r\n"), TEXT(__FUNCTION__)));
        SetLastError(ERROR_INVALID_PARAMETER);
        return FALSE;
    }

    /* check frame size */
    if (dwSize > CEC_TX_BUFF_SIZE || dwSize <= 0)
    {
        RETAILMSG(ZONE_ERROR, (TEXT("[CEC:ERR] %s() : size incorrect! size = %d\r\n"), TEXT(__FUNCTION__), dwSize));
        SetLastError(ERROR_INVALID_PARAMETER);
        return FALSE;
    }

#ifdef CEC_DEBUG
    /* print out frame content */
    RETAILMSG(1, (TEXT("Frame content:")));
    for (i = 0; i < dwSize; ++i)
        RETAILMSG(1, (TEXT(" 0x%02x"), pBuffer[i]));
    RETAILMSG(1, (TEXT("\r\n")));
#endif

    /* copy frame to hardware buffer */
    i = 0;
    while (i < dwSize) {
        g_pCECRegs->TX_BUFF[i] = pBuffer[i];
        i++;
    }

    /* set number of bytes to transmit */
    g_pCECRegs->TX_BYTES = dwSize;

    /* start transmitting */
    reg = g_pCECRegs->TX_CTRL;
    reg |= CEC_TX_CTRL_START;

    /* if message is broadcast message - set corresponding bit */
    if ((pBuffer[0] & CEC_MESSAGE_BROADCAST_MASK) == CEC_MESSAGE_BROADCAST)
        reg |= CEC_TX_CTRL_BCAST;
    else
        reg &= ~CEC_TX_CTRL_BCAST;

    /* set number of retransmissions */
    reg |= (5<<4);

    g_pCECRegs->TX_CTRL = reg;

    /* wait for interrupt */
    dwRes = WaitForSingleObject(g_hEventCECTxDone, CEC_TIMEOUT);

    if (dwRes == WAIT_TIMEOUT)
    {
        RETAILMSG(ZONE_ERROR, (TEXT("[CEC:ERR] %s() : Timeout!\r\n"), TEXT(__FUNCTION__)));
        SetLastError(ERROR_TIMEOUT);
        return FALSE;        
    }
    else if (dwRes == WAIT_FAILED)
    {
        RETAILMSG(ZONE_ERROR, (TEXT("[CEC:ERR] %s() : Failed!\r\n"), TEXT(__FUNCTION__)));
        SetLastError(ERROR_FUNCTION_FAILED);
        return FALSE;        
    }

    EnterCriticalSection(&CECTxContext.cs);
    if (CECTxContext.state == STATE_ERROR)
    {
        SetLastError(ERROR_FUNCTION_FAILED);
        bRetval = FALSE;
    }
    LeaveCriticalSection(&CECTxContext.cs);

    return bRetval;
}


static BOOL CEC_ReceiveFrame(PBYTE pBuffer, DWORD dwSize, PDWORD pdwActualSize)
{
    BOOL bRetval = TRUE;
    DWORD dwRes;
    DWORD i;
    DWORD nBytes;

//    RETAILMSG(ZONE_IOCTL, (TEXT("[CEC] %s() : buffer = 0x%x, size = %d\r\n"), TEXT(__FUNCTION__), pBuffer, dwSize));

    /* check buffer pointer */
    if (pBuffer == NULL)
    {
        RETAILMSG(ZONE_ERROR, (TEXT("[CEC:ERR] %s() : buffer incorrect!\r\n"), TEXT(__FUNCTION__)));
        SetLastError(ERROR_INVALID_PARAMETER);
        return FALSE;
    }

    /* check buffer size */
    if (dwSize <= 0)
    {
        RETAILMSG(ZONE_ERROR, (TEXT("[CEC:ERR] %s() : size incorrect! size = %d\r\n"), TEXT(__FUNCTION__), dwSize));
        SetLastError(ERROR_INVALID_PARAMETER);
        return FALSE;
    }

    dwRes = WaitForSingleObject(g_hEventCECRxDone, CEC_TIMEOUT);

    if (dwRes == WAIT_TIMEOUT)
    {
//        RETAILMSG(ZONE_ERROR, (TEXT("[CEC:ERR] %s() : Timeout!\r\n"), TEXT(__FUNCTION__)));
        SetLastError(ERROR_TIMEOUT);
        return FALSE;        
    }
    else if (dwRes == WAIT_FAILED)
    {
        RETAILMSG(ZONE_ERROR, (TEXT("[CEC:ERR] %s() : Failed!\r\n"), TEXT(__FUNCTION__)));
        SetLastError(ERROR_FUNCTION_FAILED);
        return FALSE;        
    }

    EnterCriticalSection(&CECRxContext.cs);
    if (CECRxContext.state == STATE_ERROR)
    {
        LeaveCriticalSection(&CECRxContext.cs);
        SetLastError(ERROR_FUNCTION_FAILED);
        bRetval = FALSE;
        goto exit;
    }

    nBytes = CECRxContext.nSize;

    /* if buffer size less than frame size */
    if (dwSize < nBytes)
    {
        LeaveCriticalSection(&CECRxContext.cs);
        SetLastError(ERROR_INSUFFICIENT_BUFFER);
        bRetval = FALSE;
        goto exit;
    }

    for (i = 0; i < nBytes; i++)
        pBuffer[i] = CECRxContext.pBuf[i];

    LeaveCriticalSection(&CECRxContext.cs);

    if (pdwActualSize)
        *pdwActualSize = nBytes;

#ifdef CEC_DEBUG
    /* print out frame content */
    RETAILMSG(1, (TEXT("Frame content:")));
    for (i = 0; i < nBytes; i++)
        RETAILMSG(1, (TEXT(" 0x%02x"), pBuffer[i]));
    RETAILMSG(1, (TEXT("\r\n")));
#endif

exit:
    return bRetval;
}
