/* SPDX-License-Identifier: LGPL-2.1-only OR BSD-3-Clause */
/******************************************************************************
 *
 * This file is provided under a dual license.  When you use or
 * distribute this software, you may choose to be licensed under
 * version 2.1 of the GNU Lesser General Public License
 * ("LGPLv2.1 License") or BSD License.
 *
 * LGPLv2.1 License
 *
 * Copyright(C) 2019 MediaTek Inc.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of version 2.1 of the GNU Lesser General Public
 * License as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html for more
 * details.
 *
 * BSD LICENSE
 *
 * Copyright(C) 2019 MediaTek Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *  * Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *  * 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.
 *  * 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.
 *
 * 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.
 *
 *****************************************************************************/

/*-----------------------------------------------------------------------------
 *
 * $Author: jason.chen $
 * $Date: 2015/11/25 $
 * $RCSfile: fops_cli.c,v $
 * $Revision: #2 $
 *
 *---------------------------------------------------------------------------*/

/** @file cli_mt538x.c
 *  Command Line Interface of MT538X.
 */


//-----------------------------------------------------------------------------
// Include files
//-----------------------------------------------------------------------------

#include "x_linux.h"
#include "x_serial.h"
#include "x_assert.h"
#include "x_hal_5381.h"
#include "x_os.h"
#include "dmx_if.h"
#include "ionodes.h"
#include <linux/version.h>
#include <linux/mt53xx_cli.h>
#include "fbm_drvif.h"


#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,36)
#define USE_UNLOCK_IOCTL
#endif

//-----------------------------------------------------------------------------
// Macro definitions
//-----------------------------------------------------------------------------

/// Copy data from user space to kernel space
#define COPY_FROM_USER(dst, src, size) \
    if (!access_ok(VERIFY_READ, (void __user *)(src), (size))) \
    { \
        return -EFAULT; \
    } \
    if (copy_from_user((dst), (void __user *)(src), (size))) \
    { \
        return -EFAULT; \
    }

/// Copy data from kernel space to user space
#define COPY_TO_USER(dst, src, size) \
    if (copy_to_user((void __user *)(dst), (src), (size))) \
    { \
        return -EFAULT; \
    }


//-----------------------------------------------------------------------------
// External function prototypes
//-----------------------------------------------------------------------------

EXTERN INT32 CLI_Parser(const CHAR *szCmdBuf);

EXTERN void Serial_PutKey(char cKey);

//-----------------------------------------------------------------------------
// Static variables
//-----------------------------------------------------------------------------
#ifndef CC_ANDROID_VARIANT_USER
/// Capture semaphore
static HANDLE_T _hSemCapture = (HANDLE_T)NULL;
#endif

//---------------------------------------------------------------------------
// Static functions
//---------------------------------------------------------------------------
#ifndef CC_ANDROID_VARIANT_USER
static BOOL _OnReceiveData(const DMX_CAPTURE_INFO_T* prInfo)
{
    INT32 i4Ret;

    i4Ret = x_sema_unlock(_hSemCapture);
    if (i4Ret != OSR_OK)
    {
        printk("x_sema_unlock failed, err = %d\n", (int) i4Ret);
    }

    UNUSED(prInfo);

#if 0
    // Debug
    {
        static UINT32 _u4Count = 0;

        // Debug, drop captured data directly
//        VERIFY(DMX_UpdateCaptureReadPointer(prInfo->u4Wp));

        printk("[%u] Captured WP: 0x%08x, RP: 0x%08x, size: %u\n",
            _u4Count++, VIRTUAL(prInfo->u4Wp), VIRTUAL(prInfo->u4Rp),
            prInfo->u4DataSize);
    }
#endif

    return TRUE;
}

static BOOL _OnCaptureBufferFull(const DMX_CAPTURE_INFO_T* prInfo)
{
    printk("Capture buffer full!\n");

    UNUSED(prInfo);

    return TRUE;
}

static BOOL _CaptureHandler(DMX_CAPTURE_NOTIFY_CODE_T eCode,
    const DMX_CAPTURE_INFO_T* prInfo, void* pvTag)
{
    BOOL fgRet = FALSE;

    switch (eCode)
    {
    case DMX_CAPTURE_NOTIFY_CODE_RECEIVE_DATA:
        fgRet = _OnReceiveData(prInfo);
        break;

    case DMX_CAPTURE_NOTIFY_CODE_BUFFER_FULL:
        fgRet = _OnCaptureBufferFull(prInfo);
        break;

    default:
        ASSERT(FALSE);
        break;
    }

    return fgRet;
}

static int _OnCaptureInit(UINT32 u4Arg)
{
    // Create capture semaphore
    if (x_sema_create(&_hSemCapture, X_SEMA_TYPE_BINARY, X_SEMA_STATE_UNLOCK)
        != OSR_OK)
    {
        return -1;
    }

    if (x_sema_lock(_hSemCapture, X_SEMA_OPTION_WAIT) != OSR_OK)
    {
        return -1;
    }

    return 0;
}


static int _OnCaptureExit(UINT32 u4Arg)
{
    // Delete capture semaphore
    if (x_sema_delete(_hSemCapture) != OSR_OK)
    {
        return -1;
    }

    return 0;
}

static int _OnCaptureSet(UINT32 u4Arg)
{
    CLI_IOCTL_8ARG_T rArg;
    DMX_CAPTURE_T rCapture;
    UINT32 u4BufSize, u4Threshold;
    BOOL fgEnable;
	FBM_POOL_T* prFbmPool;

    COPY_FROM_USER(&rArg, u4Arg, sizeof (CLI_IOCTL_8ARG_T));

    // Get parameters
    fgEnable = (BOOL)rArg.au4Arg[0];
    u4BufSize = (UINT32)rArg.au4Arg[1];
    u4Threshold = (UINT32)rArg.au4Arg[2];
#ifndef CONFIG_TV_DRV_VFY
	prFbmPool = FBM_GetPoolInfo((UINT8)FBM_POOL_TYPE_PVR);
    ASSERT(prFbmPool != NULL);
    ASSERT(prFbmPool->u4Addr != 0);

    rCapture.u4BufStart= prFbmPool->u4Addr;
    rCapture.u4BytePassDev = DMX_CAPTURE_BYPASS_FRAMER0;
    rCapture.u4BufSize = u4BufSize;
    rCapture.u4Threshold = u4Threshold;
    rCapture.pfnCaptureHandler = _CaptureHandler;
    rCapture.pvCaptureTag = NULL;

    if (!DMX_SetCapture(fgEnable, &rCapture))
    {
        return -1;
    }
#endif
    return 0;
}


static int _OnCaptureGet(UINT32 u4Arg)
{
    CLI_IOCTL_8ARG_T rArg;
    DMX_CAPTURE_INFO_T rInfo;
    UINT32 u4UserBufSize, u4MinSize, u4BufStart, u4BufEnd, u4Rp, u4Wp;
    UINT32 u4DataSize, u4RealSize, u4Size, u4BufSize;
    UINT32 *pu4RealSize;
    UINT8 *pu1UserBuf;

    COPY_FROM_USER(&rArg, u4Arg, sizeof (CLI_IOCTL_8ARG_T));

    // Get parameters
    u4UserBufSize = (UINT32)rArg.au4Arg[0];
    u4MinSize = (UINT32)rArg.au4Arg[1];
    pu1UserBuf = (UINT8*)rArg.au4Arg[2];
    pu4RealSize = (UINT32*)rArg.au4Arg[3];

    // Check parameters
    if ((u4MinSize > u4UserBufSize) || (pu1UserBuf == NULL) ||
        (pu4RealSize == NULL))
    {
        return -1;
    }

    while (1)
    {
        // Get capture info
     #ifndef CONFIG_TV_DRV_VFY
        if (!DMX_GetCaptureInfo(&rInfo))
        {
            return -1;
        }

        if (rInfo.u4DataSize >= u4MinSize)
        {
            break;
        }
     #endif
        // Not enough data, sleep
        VERIFY(x_sema_lock(_hSemCapture, X_SEMA_OPTION_WAIT) == OSR_OK);
    }

    // Check if user buffer is valid
    if (!access_ok(VERIFY_WRITE, (void __user *)pu1UserBuf, u4UserBufSize))
    {
        return -1;
    }

    u4BufStart = VIRTUAL(rInfo.u4BufStart);
    u4BufEnd = VIRTUAL(rInfo.u4BufEnd);
    u4BufSize = u4BufEnd - u4BufStart;
    u4Rp = VIRTUAL(rInfo.u4Rp);
    u4Wp = VIRTUAL(rInfo.u4Wp);
    u4DataSize = MIN(rInfo.u4DataSize, u4UserBufSize);
    u4RealSize = u4DataSize;

    // Copy data
    if ((u4Rp > u4Wp) && (u4DataSize > 0))
    {
        u4Size = MIN((u4BufEnd - u4Rp), u4DataSize);
        if (copy_to_user((void __user *)pu1UserBuf, (void*)u4Rp, u4Size))
        {
            return -1;
        }

        u4Rp += u4Size;
        if (u4Rp >= u4BufEnd)
        {
            u4Rp -= u4BufSize;
        }
        pu1UserBuf += u4Size;
        u4DataSize -= u4Size;
    }

    if ((u4Rp < u4Wp) && (u4DataSize > 0))
    {
        u4Size = MIN(u4Wp - u4Rp, u4DataSize);
        if (copy_to_user((void __user *)pu1UserBuf, (void*)u4Rp, u4Size))
        {
            return -1;
        }

        u4Rp += u4Size;
        if (u4Rp >= u4BufEnd)
        {
            u4Rp -= u4BufSize;
        }
    }

    // Update read pointer of capture buffer
    #ifndef CONFIG_TV_DRV_VFY
    VERIFY(DMX_UpdateCaptureReadPointer(PHYSICAL(u4Rp)));
    #endif
    // Update real size
    if (!access_ok(VERIFY_WRITE, (void __user *)pu4RealSize, sizeof (UINT32)))
    {
        return -1;
    }
    if (copy_to_user((void __user *)pu4RealSize, &u4RealSize, sizeof (UINT32)))
    {
        return -1;
    }

    return 0;
}
#endif

//---------------------------------------------------------------------------
// Public functions
//---------------------------------------------------------------------------

//-----------------------------------------------------------------------------
/** cli_ioctl.
 */
//-----------------------------------------------------------------------------
#ifndef USE_UNLOCK_IOCTL
int cli_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
              unsigned long arg)
#else
long cli_ioctl(struct file *file, unsigned int cmd,
               unsigned long arg)
#endif
{
    char cKey;
    char szCmd[MAX_CMD_LEN];
    int retval = 0;

#ifndef CC_ANDROID_VARIANT_USER
	switch (cmd) {
    case CMD_PASSSTR:        
        if (copy_from_user(&cKey, (void __user *)arg, sizeof(char)))
        {
            return -EFAULT;
        }

        Serial_PutKey(cKey);
        break;

    case CMD_PASSCMD:
        if (copy_from_user(szCmd, (void __user *)arg, sizeof(char) * MAX_CMD_LEN))
        {
            return -EFAULT;
        }

        retval = CLI_Parser(szCmd);
        break;

    case CMD_CAP_INIT:
        retval = _OnCaptureInit(arg);
        break;

    case CMD_CAP_EXIT:
        retval = _OnCaptureExit(arg);
        break;

    case CMD_CAP_SET:
        retval = _OnCaptureSet(arg);
        break;

    case CMD_CAP_GET:
        retval = _OnCaptureGet(arg);
        break;

    default:
        // Unknown command
        break;
	}
#else
    UNUSED(file);
    UNUSED(cmd);
    UNUSED(arg);
    UNUSED(cKey);
    UNUSED(szCmd);
    printk("Warning: cli device is disabled\n");
#endif

	return retval;
}

struct file_operations cli_fops = {
#ifndef USE_UNLOCK_IOCTL
	.ioctl		= cli_ioctl,
#else
	.unlocked_ioctl = cli_ioctl,
#endif
};

