/******************************************************************************
* Copyright (C) 2010 - 2021 Xilinx, Inc.  All rights reserved.
* SPDX-License-Identifier: MIT
******************************************************************************/

/*****************************************************************************/
/**
*
* @file xttcps.c
* @addtogroup ttcps_v3_15
* @{
*
* This file contains the implementation of the XTtcPs driver. This driver
* controls the operation of one timer counter in the Triple Timer Counter (TTC)
* module in the Ps block. Refer to xttcps.h for more detailed description
* of the driver.
*
* <pre>
* MODIFICATION HISTORY:
*
* Ver   Who    Date     Changes
* ----- ------ -------- -------------------------------------------------
* 1.00a drg/jz 01/21/10 First release
* 3.00  kvn    02/13/15 Modified code for MISRA-C:2012 compliance.
* 3.01	pkp	   01/30/16 Modified XTtcPs_CfgInitialize to add XTtcps_Stop
*						to stop the timer before configuring
* 3.2   mus    10/28/16 Modified XTtcPs_CalcIntervalFromFreq to calculate
*                       32 bit interval count for zynq ultrascale+mpsoc
* 3.5   srm    10/06/17 Updated XTtcPs_GetMatchValue and XTtcPs_SetMatchValue
*                       APIs to use correct match register width for zynq
*                       (i.e. 16 bit) and zynq ultrascale+mpsoc (i.e. 32 bit).
*                       It fixes CR# 986617
* 3.6   srm    04/25/18 Corrected the Match register initialization in
						XTtcPs_CfgInitialize API.
* 3.7   mus    09/20/18 Modified XTtcPs_CalcIntervalFromFreq API to use
*						XTTCPS_MAX_INTERVAL_COUNT instead of hardcoding
*						MAX interval count to 16 bit value(i.e.65532),
*						which is incorrect for  zynq ultrascale+mpsoc
*						(i.e. max interval count is 32 bit).
* 3.10  aru    05/06/19 Added assert check for driver instance and freq
*			parameter in  XTtcPs_CalcIntervalFromFreq().
* 3.10  aru    05/30/19 Added interrupt handler to clear ISR
* </pre>
*
******************************************************************************/

/***************************** Include Files *********************************/

#include "xttcps.h"

/************************** Constant Definitions *****************************/

/**************************** Type Definitions *******************************/

/***************** Macros (Inline Functions) Definitions *********************/

/************************** Function Prototypes ******************************/
static void StubStatusHandler(const void *CallBackRef, u32 StatusEvent);
/************************** Variable Definitions *****************************/


/*****************************************************************************/
/**
*
* Initializes a specific XTtcPs instance such that the driver is ready to use.
* This function initializes a single timer counter in the triple timer counter
* function block.
*
* The state of the device after initialization is:
*  - Overflow Mode
*  - Internal (pclk) selected
*  - Counter disabled
*  - All Interrupts disabled
*  - Output waveforms disabled
*
* @param	InstancePtr is a pointer to the XTtcPs instance.
* @param	ConfigPtr is a reference to a structure containing information
*		about a specific TTC device.
* @param	EffectiveAddr is the device base address in the virtual memory
*		address space. The caller is responsible for keeping the address
*		mapping from EffectiveAddr to the device physical base address
*		unchanged once this function is invoked. Unexpected errors may
*		occur if the address mapping changes after this function is
*		called. If address translation is not used, then use
*		ConfigPtr->BaseAddress for this parameter, passing the physical
*		address instead.
*
* @return
*
* 		- XST_SUCCESS if the initialization is successful.
*		- XST_DEVICE_IS_STARTED if the device is started. It must be
*		  stopped to re-initialize.
*
* @note		Device has to be stopped first to call this function to
*		initialize it.
*
******************************************************************************/
s32 XTtcPs_CfgInitialize(XTtcPs *InstancePtr, XTtcPs_Config *ConfigPtr,
			      u32 EffectiveAddr)
{
	s32 Status;
	u32 IsStartResult;
	/*
	 * Assert to validate input arguments.
	 */
	Xil_AssertNonvoid(InstancePtr != NULL);
	Xil_AssertNonvoid(ConfigPtr != NULL);

	/*
	 * Set some default values
	 */
	InstancePtr->Config.DeviceId = ConfigPtr->DeviceId;
	InstancePtr->Config.BaseAddress = EffectiveAddr;
	InstancePtr->Config.InputClockHz = ConfigPtr->InputClockHz;
	InstancePtr->StatusHandler = StubStatusHandler;
#ifdef XIL_INTERRUPT
	InstancePtr->Config.IntrId = ConfigPtr->IntrId;
	InstancePtr->Config.IntrParent = ConfigPtr->IntrParent;
#endif

	IsStartResult = XTtcPs_IsStarted(InstancePtr);
	/*
	 * If the timer counter has already started, return an error
	 * Device should be stopped first.
	 */
	if(IsStartResult == (u32)TRUE) {
		Status = XST_DEVICE_IS_STARTED;
	} else {

		/*
		 * stop the timer before configuring
		 */
		XTtcPs_Stop(InstancePtr);
		/*
		 * Reset the count control register to it's default value.
		 */
		XTtcPs_WriteReg(InstancePtr->Config.BaseAddress,
				  XTTCPS_CNT_CNTRL_OFFSET,
				  XTTCPS_CNT_CNTRL_RESET_VALUE);

		/*
		 * Reset the rest of the registers to the default values.
		 */
		XTtcPs_WriteReg(InstancePtr->Config.BaseAddress,
				  XTTCPS_CLK_CNTRL_OFFSET, 0x00U);
		XTtcPs_WriteReg(InstancePtr->Config.BaseAddress,
				  XTTCPS_INTERVAL_VAL_OFFSET, 0x00U);
		XTtcPs_WriteReg(InstancePtr->Config.BaseAddress,
				  XTTCPS_MATCH_0_OFFSET, 0x00U);
		XTtcPs_WriteReg(InstancePtr->Config.BaseAddress,
				  XTTCPS_MATCH_1_OFFSET, 0x00U);
		XTtcPs_WriteReg(InstancePtr->Config.BaseAddress,
				  XTTCPS_MATCH_2_OFFSET, 0x00U);
		XTtcPs_WriteReg(InstancePtr->Config.BaseAddress,
				  XTTCPS_IER_OFFSET, 0x00U);
		XTtcPs_WriteReg(InstancePtr->Config.BaseAddress,
				  XTTCPS_ISR_OFFSET, XTTCPS_IXR_ALL_MASK);

		InstancePtr->IsReady = XIL_COMPONENT_IS_READY;

		/*
		 * Reset the counter value
		 */
		XTtcPs_ResetCounterValue(InstancePtr);
		Status = XST_SUCCESS;
	}
	return Status;
}

/*****************************************************************************/
/**
*
* This function is used to set the match registers. There are three match
* registers.
*
* The match 0 register is special. If the waveform output mode is enabled, the
* waveform will change polarity when the count matches the value in the match 0
* register. The polarity of the waveform output can also be set using the
* XTtcPs_SetOptions() function.
*
* @param	InstancePtr is a pointer to the XTtcPs instance.
* @param	MatchIndex is the index to the match register to be set.
*		    Valid values are: 0 - 2.
* @param	Value is the 16-bit value to be set in the match register.
*           Valid Values are: (For Zynq):
*                             0 - ((2^16)-1)
*                             (For Zynq UltraScale + MpSoc) and Versal:
*                             0 - ((2^32) - 1)
*
* @return	None
*
* @note		None
*
****************************************************************************/
void XTtcPs_SetMatchValue(XTtcPs *InstancePtr, u8 MatchIndex, XMatchRegValue Value)
{
	/*
	 * Assert to validate input arguments.
	 */
	Xil_AssertVoid(InstancePtr != NULL);
	Xil_AssertVoid(InstancePtr->IsReady == XIL_COMPONENT_IS_READY);
	Xil_AssertVoid(MatchIndex < (u8)XTTCPS_NUM_MATCH_REG);

	/*
	 * Write the value to the correct match register with MatchIndex
	 */
	XTtcPs_WriteReg(InstancePtr->Config.BaseAddress,
			  XTtcPs_Match_N_Offset(MatchIndex), Value);
}

/*****************************************************************************/
/**
*
* This function is used to get the value of the match registers. There are
* three match registers.
*
* @param	InstancePtr is a pointer to the XTtcPs instance.
* @param	MatchIndex is the index to the match register to be set.
*           There are three match registers are there.
*		    Valid values are: 0 - 2.
*
* @return	The match register value
*
* @note		None
*
****************************************************************************/
XMatchRegValue XTtcPs_GetMatchValue(XTtcPs *InstancePtr, u8 MatchIndex)
{
	u32 MatchReg;

	/*
	 * Assert to validate input arguments.
	 */
	Xil_AssertNonvoid(InstancePtr != NULL);
	Xil_AssertNonvoid(InstancePtr->IsReady == XIL_COMPONENT_IS_READY);
	Xil_AssertNonvoid(MatchIndex < XTTCPS_NUM_MATCH_REG);

	MatchReg = XTtcPs_ReadReg(InstancePtr->Config.BaseAddress,
			    XTtcPs_Match_N_Offset(MatchIndex));

	return (XMatchRegValue) MatchReg;
}

/*****************************************************************************/
/**
*
* This function sets the prescaler enable bit and if needed sets the prescaler
* bits in the control register.
*
* @param	InstancePtr is a pointer to the XTtcPs instance.
* @param	PrescalerValue is a number from 0-16 that sets the prescaler
*		    to use.
*		    If the parameter is 0 - 15, use a prescaler on the clock of
*		    2^(PrescalerValue+1), or 2-65536.
*		    If the parameter is XTTCPS_CLK_CNTRL_PS_DISABLE, do not use a
*		    prescaler.
*
*		    Valid values are: 0 - 15
*
* @return	None
*
* @note		None
*
****************************************************************************/
void XTtcPs_SetPrescaler(XTtcPs *InstancePtr, u8 PrescalerValue)
{
	u32 ClockReg;

	/*
	 * Assert to validate input arguments.
	 */
	Xil_AssertVoid(InstancePtr != NULL);
	Xil_AssertVoid(InstancePtr->IsReady == XIL_COMPONENT_IS_READY);
	Xil_AssertVoid(PrescalerValue <= XTTCPS_CLK_CNTRL_PS_DISABLE);

	/*
	 * Read the clock control register
	 */
	ClockReg = XTtcPs_ReadReg(InstancePtr->Config.BaseAddress,
			   XTTCPS_CLK_CNTRL_OFFSET);

	/*
	 * Clear all of the prescaler control bits in the register
	 */
	ClockReg &=
		~(XTTCPS_CLK_CNTRL_PS_VAL_MASK | XTTCPS_CLK_CNTRL_PS_EN_MASK);

	if (PrescalerValue < XTTCPS_CLK_CNTRL_PS_DISABLE) {
		/*
		 * Set the prescaler value and enable prescaler
		 */
		ClockReg |= (u32)(((u32)PrescalerValue << (u32)XTTCPS_CLK_CNTRL_PS_VAL_SHIFT) &
			(u32)XTTCPS_CLK_CNTRL_PS_VAL_MASK);
		ClockReg |= (u32)XTTCPS_CLK_CNTRL_PS_EN_MASK;
	}

	/*
	 * Write the register with the new values.
	 */
	XTtcPs_WriteReg(InstancePtr->Config.BaseAddress,
			  XTTCPS_CLK_CNTRL_OFFSET, ClockReg);
}

/*****************************************************************************/
/**
*
* This function gets the input clock prescaler
*
* @param	InstancePtr is a pointer to the XTtcPs instance.
*
* <pre>
* @return	The value(n) from which the prescalar value is calculated
*		    as 2^(n+1). Some example values are given below :
*
* 	Value		Prescaler
* 	0		2
* 	1		4
* 	N		2^(n+1)
* 	15		65536
* 	16		1
*
*           Valid values are: 0 - 16
* </pre>
*
* @note		None.
*
****************************************************************************/
u8 XTtcPs_GetPrescaler(XTtcPs *InstancePtr)
{
	u8 Status;
	u32 ClockReg;

	/*
	 * Assert to validate input arguments.
	 */
	Xil_AssertNonvoid(InstancePtr != NULL);
	Xil_AssertNonvoid(InstancePtr->IsReady == XIL_COMPONENT_IS_READY);

	/*
	 * Read the clock control register
	 */
	ClockReg = XTtcPs_ReadReg(InstancePtr->Config.BaseAddress,
				    XTTCPS_CLK_CNTRL_OFFSET);

	if (0 == (ClockReg & XTTCPS_CLK_CNTRL_PS_EN_MASK)) {
		/*
		 * Prescaler is disabled. Return the correct flag value
		 */
		Status = (u8)XTTCPS_CLK_CNTRL_PS_DISABLE;
	}
	else {

		Status = (u8)((ClockReg & (u32)XTTCPS_CLK_CNTRL_PS_VAL_MASK) >>
			(u32)XTTCPS_CLK_CNTRL_PS_VAL_SHIFT);
	}
	return Status;
}

/*****************************************************************************/
/**
*
* This function calculates the interval value as well as the prescaler value
* for a given frequency.
*
* @param	InstancePtr is a pointer to the XTtcPs instance.
* @param	Freq is the requested output frequency for the device.
*           valid values are: 1 - (2^32)-1
* @param	Interval is the interval value for the given frequency,
*		    it is the output value for this function.
* @param	Prescaler is the prescaler value for the given frequency,
*		    it is the output value for this function.
*
* @return	None.
*
* @note
*  Upon successful calculation for the given frequency, Interval and Prescaler
*  carry the settings for the timer counter; Upon unsuccessful calculation,
*  Interval and Prescaler are set to 0xFF(FF) for their maximum values to
*  signal the caller of failure. Therefore, caller needs to check the return
*  interval or prescaler values for whether the function has succeeded.
*
****************************************************************************/
void XTtcPs_CalcIntervalFromFreq(XTtcPs *InstancePtr, u32 Freq,
        XInterval *Interval, u8 *Prescaler)
{
	u8 TmpPrescaler;
	UINTPTR TempValue;
	u32 InputClock;

	/*
         * Assert to validate input arguments.
         */
	Xil_AssertVoid(InstancePtr != NULL);
        Xil_AssertVoid(Freq > 0U);

	InputClock = InstancePtr->Config.InputClockHz;
	/*
	 * Find the smallest prescaler that will work for a given frequency. The
	 * smaller the prescaler, the larger the count and the more accurate the
	 *  PWM setting.
	 */
	TempValue = InputClock/ Freq;

	if (TempValue < 4U) {
		/*
		 * The frequency is too high, it is too close to the input
		 * clock value. Use maximum values to signal caller.
		 */
		*Interval = XTTCPS_MAX_INTERVAL_COUNT;
		*Prescaler = 0xFFU;
		return;
	}

	/*
	 * First, do we need a prescaler or not?
	 */
	if (((UINTPTR)XTTCPS_MAX_INTERVAL_COUNT) > TempValue) {
		/*
		 * We do not need a prescaler, so set the values appropriately
		 */
		*Interval = (XInterval)TempValue;
		*Prescaler = XTTCPS_CLK_CNTRL_PS_DISABLE;
		return;
	}


	for (TmpPrescaler = 0U; TmpPrescaler < XTTCPS_CLK_CNTRL_PS_DISABLE;
	     TmpPrescaler++) {
		TempValue =	InputClock/ (Freq * (1U << (TmpPrescaler + 1U)));

		/*
		 * The first value less than 2^16 is the best bet
		 */
		if (((UINTPTR)XTTCPS_MAX_INTERVAL_COUNT) > TempValue) {
			/*
			 * Set the values appropriately
			 */
			*Interval = (XInterval)TempValue;
			*Prescaler = TmpPrescaler;
			return;
		}
	}

	/* Can not find interval values that work for the given frequency.
	 * Return maximum values to signal caller.
	 */
	*Interval = XTTCPS_MAX_INTERVAL_COUNT;
	*Prescaler = 0XFFU;
	return;
}

/*****************************************************************************/
/**
 *
 * Handles interrupts by resetting the counter value
 * and clearing the status register
 *
 * @param	InstancePtr is a pointer to the XTtcPs instance.
 *
 * @return
 *		- XST_SUCCESS if successful.
 *
 * @note	None.
 *
 ******************************************************************************/

u32 XTtcPs_InterruptHandler(XTtcPs *InstancePtr)
{
	u32 XTtcPsStatusReg;

	Xil_AssertNonvoid(InstancePtr != NULL);

	XTtcPsStatusReg = XTtcPs_GetInterruptStatus(InstancePtr);
	XTtcPs_ClearInterruptStatus(InstancePtr, XTtcPsStatusReg);
	InstancePtr->StatusHandler(InstancePtr->StatusRef,
			                                XTtcPsStatusReg);
	return XST_SUCCESS;



}

/*****************************************************************************/
/**
 *
 * Sets the status callback function, the status handler, which the driver
 * calls when it encounters conditions that should be reported to upper
 * layer software. The handler executes in an interrupt context, so it must
 * minimize the amount of processing performed. One of the following status
 * events is passed to the status handler.
 *
 * </pre>
 * @param	InstancePtr is a pointer to the XTtcPs instance.
 * @param	CallBackRef is the upper layer callback reference passed back
 *		when the callback function is invoked.
 * @param	FuncPointer is the pointer to the callback function.
 *
 * @return	None.
 *
 * @note
 *
 * The handler is called within interrupt context, so it should do its work
 * quickly and queue potentially time-consuming work to a task-level thread.
 *
 ******************************************************************************/
void XTtcPs_SetStatusHandler(XTtcPs *InstancePtr, void *CallBackRef,
		XTtcPs_StatusHandler FuncPointer)
{
	Xil_AssertVoid(InstancePtr != NULL);
	Xil_AssertVoid(FuncPointer != NULL);
	Xil_AssertVoid(InstancePtr->IsReady == XIL_COMPONENT_IS_READY);

	InstancePtr->StatusHandler = FuncPointer;
	InstancePtr->StatusRef = CallBackRef;
}


/*****************************************************************************/
/**
 *
 * This is a stub for the status callback. The stub is here in case the upper
 * layers forget to set the handler.
 *
 * @param	CallBackRef is a pointer to the upper layer callback reference
 * @param	StatusEvent is the event that just occurred.
 *
 * @return	None.
 *
 * @note	None.
 *
 ******************************************************************************/
static void StubStatusHandler(const void *CallBackRef, u32 StatusEvent)
{
	(void) CallBackRef;
	(void) StatusEvent;

	Xil_AssertVoidAlways();
}
/** @} */
