/* 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: wangliang.wang $
 * $Date: 2015/07/09 $
 * $RCSfile: pdwnc_gspi.c,v $
 * $Revision: #2 $
 *
 *---------------------------------------------------------------------------*/

/** @file pdwnc_gspi.c
 *  pdwnc_gspi.c The PDWNC (Power Down Controller) GSPI functions
 */


//-----------------------------------------------------------------------------
// Include files
//-----------------------------------------------------------------------------
#include "x_pinmux.h"
#include "x_pdwnc.h"
#include "x_bim.h"
#include "x_assert.h"
#include "x_os.h"
#include "x_ckgen.h"
#include "x_timer.h"
#include "x_hal_arm.h"
#include "x_gpio.h"
#include "drvcust_if.h"
#include "spi_hw_comm.h"
#include "spi_debug.h"
#include "spi_comm_if.h"


#define CC_SUPORT_PDWNC_GSPI 1


#if defined(CC_SUPORT_PDWNC_GSPI)

#ifndef CC_MTK_LOADER
#ifndef CC_SUPORT_PDWNC_GSPI_INT

#define CC_SUPORT_PDWNC_GSPI_INT

#define GSPI_TIMEOUT_MAX     (2000)

static HANDLE_T _hGspiSema     = NULL_HANDLE;

static HANDLE_T _hGspiIsrSema  = NULL_HANDLE;

#endif
#endif
//-----------------------------------------------------------------------------
/** _PdwncGspiClock() set Clock freq. (Clock rate: 24M/(2*u4ClockDiv + 2))
 *  @param u4ClockDiv, 0x000 ~ 0xFFF.
 *  Clock Min = 2929.6875Hz, Max = 12000000Hz.
 */
//-----------------------------------------------------------------------------
void _PdwncGspiClockFreq(UINT32 u4ClockDiv)
{
	vIO32WriteFldAlign(PDWNC_GSPI_CKCFG,u4ClockDiv,FLD_CLK_DIV);
}

//CPOL = 0, default is low; CPOL = 1, default is high.
void _PdwncGspiClockPol(UINT32 u4ClockPol)
{
	vIO32WriteFldAlign(PDWNC_GSPI_CKCFG,u4ClockPol,FLD_CPOL);
}
//CPHA = 0 or 1.
void _PdwncGspiClockPha(UINT32 u4ClockPha)
{
	vIO32WriteFldAlign(PDWNC_GSPI_CKCFG,u4ClockPha,FLD_CPHA);
}

void _PdwncGspiCsLeadLatency(UINT32 u4CsLead)
{
	vIO32WriteFldAlign(PDWNC_GSPI_CSACC,u4CsLead,FLD_CS_LEAD_LATENCY);
}

void _PdwncGspiCsLagLatency(UINT32 u4CsLag)
{
	vIO32WriteFldAlign(PDWNC_GSPI_CSACC,u4CsLag,FLD_CS_LAG_LATENCY);
}

void _PdwncGspiCsHoldLatency(UINT32 u4CsHold)
{
	vIO32WriteFldAlign(PDWNC_GSPI_CSACC2,u4CsHold,FLD_CS_HOLD_LATENCY);
}
void _PdwncGspiLsbMode(UINT32 u4LsbMode)
{
	vIO32WriteFldAlign(PDWNC_GSPI_CON,u4LsbMode,FLD_LSB_MODE);
}
void _PdwncGspiSetEnable(UINT32 u4Enable)
{
	vIO32WriteFldAlign(PDWNC_GSPI_CON,u4Enable,FLD_GSPI_EN);
}

#if 0
void _PdwncGspiNdataLen(UINT32 u4NdataLen)
{
	vIO32WriteFldAlign(PDWNC_GSPI_NOB,u4NdataLen,FLD_NDATA_LEN);
}

void _PdwncGspiMdataLen(UINT32 u4MdataLen)
{
	vIO32WriteFldAlign(PDWNC_GSPI_NOB,u4MdataLen,FLD_MDATA_LEN);
}
void _PdwncGspiSetIntEnable(UINT32 u4Enable)
{
	vIO32WriteFldAlign(PDWNC_GSPI_INTR_EN,0x1,FLD_GSPI_DONE_INTR_EN); //The data transfer completion interrupt enable.
}

void _PdwncGspiTriger(UINT32 u4Trig)
{
	vIO32WriteFldAlign(PDWNC_GSPI_CON,u4Trig,FLD_GSPI_TRIG);
}
#endif

//defined(CC_SUPORT_PDWNC_GSPI_INT)
#ifdef CC_SUPORT_PDWNC_GSPI_INT
static void _GspiDoneIsr(UINT16 u2VctId)
{
	//read to clear int flag
	//UNUSED( IO32ReadFldAlign(PDWNC_INTSTA,FLD_GSPI_INT_STA));
	//...
	UNUSED(IO32ReadFldAlign(PDWNC_GSPI_INTR,FLD_GSPI_DONE_INTR));

	VERIFY(x_sema_unlock(_hGspiIsrSema) == OSR_OK);
	//unlock isr sema
}
#endif




//-----------------------------------------------------------------------------
/** PDWNC_GspiInit() set GSPI agrument
 *  @param prGspiMode->u4Mode  GSPI config.
 *  @param prGspiMode->u4Freq  SPI clock, (uint:1hz,Min = 2929.6875Hz, Max = 12000000Hz).
 */
//-----------------------------------------------------------------------------
INT32 PDWNC_GspiInit(PDWNC_GSPI_MODE_T *prGspiMode)
{
#if 0//defined(CC_SUPORT_PDWNC_GSPI_INT)
	void (* pfnOldIsr)(UINT16);
	vIO32WriteFldAlign(PDWNC_GSPI_INTR_EN,1,FLD_GSPI_DONE_INTR_EN);
	if (PDWNC_RegIsr((UINT16)PDWNC_INTNO_GSPI, _GspiDoneIsr, &pfnOldIsr) != OSR_OK)
    {
        LOG(0,"GSPI Init failed\n");
		return;
    }
#endif
	UINT32 u4Val;
	#ifdef CC_SUPORT_PDWNC_GSPI_INT
	x_os_isr_fct pfnOldIsr;
	_PdwncGspiSetEnable(FALSE);	
	UNUSED(IO32ReadFldAlign(PDWNC_INTSTA,FLD_GSPI_INT_STA));
	UNUSED(IO32ReadFldAlign(PDWNC_GSPI_INTR,FLD_GSPI_DONE_INTR));
	VERIFY(x_sema_create(&_hGspiSema, X_SEMA_TYPE_BINARY, X_SEMA_STATE_UNLOCK) == OSR_OK);	
	VERIFY(x_sema_create(&_hGspiIsrSema, X_SEMA_TYPE_BINARY, X_SEMA_STATE_LOCK) == OSR_OK);
	#endif
#if 0
	BSP_PinSet(PIN_OPCTRL8,PINMUX_FUNCTION1);
	BSP_PinSet(PIN_OPCTRL9,PINMUX_FUNCTION1);
	BSP_PinSet(PIN_OPCTRL10,PINMUX_FUNCTION1);
	BSP_PinSet(PIN_OPCTRL11,PINMUX_FUNCTION1);
	//use opctrl 8/9/10/11 as spi  function
#endif
	if(prGspiMode->u4Freq == 0){
		LOG(0,"the frequency of spi can not set 0");
		return -1;
	}else{
		u4Val = 12000000/(prGspiMode->u4Freq) - 1;
	}
	LOG(0,"gspi init freq=0x%x  mode=0x%x\n",u4Val, prGspiMode->u4Mode);
	_PdwncGspiClockFreq(u4Val & 0xfff); //24M/(2*CLK_DIV+2)
	_PdwncGspiClockPha((prGspiMode->u4Mode & GSPI_CPHA)? 1:0);
	_PdwncGspiClockPol((prGspiMode->u4Mode & GSPI_CPOL)? 1:0);
	_PdwncGspiLsbMode((prGspiMode->u4Mode & GSPI_LSB_FIRST)? 1:0);
	_PdwncGspiCsLeadLatency(prGspiMode->u4CsLead);
	_PdwncGspiCsLagLatency(prGspiMode->u4CsLag);
	_PdwncGspiCsHoldLatency(prGspiMode->u4CsHold);

	#ifdef CC_SUPORT_PDWNC_GSPI_INT
	VERIFY(PDWNC_RegIsr(PDWNC_INTNO_GSPI, _GspiDoneIsr, &pfnOldIsr) == PDWNC_OK);
	
	vIO32WriteFldAlign(PDWNC_GSPI_INTR_EN,0x1,FLD_GSPI_DONE_INTR_EN); //The data transfer completion interrupt enable.
	vIO32WriteFldAlign(PDWNC_ARM_INTEN,0x1,FLD_GSPI_INTEN);
	//register and enable gspi pdwnc int
	#endif
	
	_PdwncGspiSetEnable(TRUE);
	return 0;
}

//-----------------------------------------------------------------------------
/** PDWNC_GspiReadWrite()
 *  @param fgReadWrite,  GSPI read/write flag.
 *  @param u4Nob, MDATA_LEN * NDATA_LEN is 32bytes,read/write buffer size (bits,1~256).
 *  @param u1RdBuf, read buffer.
 *  @param u1WrBuf, write buffer.
 */
//-----------------------------------------------------------------------------
INT32 PDWNC_GspiReadWrite(UINT32 fgReadWrite, UINT32 u4Nob,UINT8 *u1RdBuf, UINT8 *u1WrBuf)
{
	INT32 i=0,j=0;
	INT32 u4Size=0;
	#ifdef CC_SUPORT_PDWNC_GSPI_INT
	VERIFY(x_sema_lock(_hGspiSema, X_SEMA_OPTION_WAIT) == OSR_OK);
	//CRIT_STATE_T rCrit = x_crit_start();
	#endif
	u4Size = (((u4Nob&0xFF) + 1) * (((u4Nob>>16)&0xFF) + 1));
	if(u4Size > 256)
	{
		#ifdef CC_SUPORT_PDWNC_GSPI_INT
		VERIFY(x_sema_unlock(_hGspiSema) == OSR_OK);
		#endif
		return -1;//The maximum valid value MDATA_LEN*NDATA_LEN is 32 bytes.
	}
	//1st level SPI data,MDATA_LEN,value=0~255.
	//2st level SPI data,NDATA_LEN,value=0~255.
	vIO32Write4B(PDWNC_GSPI_NOB,u4Nob & 0x00FF00FF);

	if((u1WrBuf != NULL) && (fgReadWrite & GSPI_WRITE))//GSPI Write enable.
	{
		if(IO32ReadFldAlign(PDWNC_GSPI_CON,FLD_LSB_MODE))//LSB_MODE,0:MSB first,1:LSB first.
		{	//LSB
			for(i=0;i<u4Size/0x08;i++)
			{
				vIO32Write1B(PDWNC_GSPI_DATA0+i,u1WrBuf[i]);
			}
		}
		else
		{	//MSB
			j = 0;
			for(i=31;i>=(32-(u4Size/0x08));i--)
			{
				vIO32Write1B(PDWNC_GSPI_DATA0+i,u1WrBuf[j++]);
			}
		}
	}
	vIO32WriteFldAlign(PDWNC_GSPI_INTR_EN,0x1,FLD_GSPI_DONE_INTR_EN); //The data transfer completion interrupt enable.
	vIO32WriteFldAlign(PDWNC_GSPI_CON,0x1,FLD_GSPI_TRIG); //Trigger GSPI.
#ifdef CC_SUPORT_PDWNC_GSPI_INT
	if(x_sema_lock_timeout(_hGspiIsrSema, GSPI_TIMEOUT_MAX)!=OSR_OK)
	{
		LOG(0,"gspi wait isrsema timeout\n");
	}
#else
	while((IO32ReadFldAlign(PDWNC_GSPI_INTR,FLD_GSPI_DONE_INTR) == 0) && (i<300)) //wait status.
	{
		HAL_Delay_us(10);
		i++;
	}
#endif

	if((u1RdBuf != NULL) && (fgReadWrite & GSPI_READ)) //GSPI Read enable.
	{
		if(IO32ReadFldAlign(PDWNC_GSPI_CON,FLD_LSB_MODE))//LSB_MODE,0:MSB first,1:LSB first.
		{	//LSB
			j=0;
			for(i=(32-(u4Size/0x08));i<32;i++)
			{
				u1RdBuf[j++] = u1IO32Read1B(PDWNC_GSPI_SHREG0+i);
			}

		}
		else
		{ 	//MSB
			j=0;
			for(i=((u4Size/0x08) - 1);i>=0;i--)
			{
				u1RdBuf[j++] = u1IO32Read1B(PDWNC_GSPI_SHREG0+i);
			}
		}
	}

	for(i=0;i<0x20;i+=4)
	{
		vIO32Write4B(PDWNC_GSPI_DATA0+i,0x00000000);//Clear write date register.
	}
#ifndef CC_SUPORT_PDWNC_GSPI_INT
	//vIO32WriteFldAlign(PDWNC_INTCLR,0x01,FLD_GSPI_INTCLR); //clear interrupt status.
#endif

	#ifdef CC_SUPORT_PDWNC_GSPI_INT
	VERIFY(x_sema_unlock(_hGspiSema) == OSR_OK);
    //x_crit_end(rCrit);
	#endif
	return 0;
}

#endif








