/******************************************************************************
* Copyright (C) 2010 - 2021 Xilinx, Inc.  All rights reserved.
* Copyright (C) 2022 - 2024 Advanced Micro Devices, Inc. All Rights Reserved.
* SPDX-License-Identifier: MIT
******************************************************************************/

/*****************************************************************************/
/**
*
* @file xiicps_options.c
* @addtogroup iicps_api IICPS APIs
* @{
*
* The xiicps_options.c file contains functions for the configuration of the XIccPs driver.
*
* <pre>
* MODIFICATION HISTORY:
*
* Ver   Who     Date     Changes
* ----- ------  -------- -----------------------------------------------
* 1.00a drg/jz  01/30/10 First release
* 1.02a sg	08/29/12 Updated the logic to arrive at the best divisors
*			 to achieve I2C clock with minimum error.
*			 This is a fix for CR #674195
* 1.03a hk  05/04/13 Initialized BestDivA and BestDivB to 0.
*			 This is fix for CR#704398 to remove warning.
* 2.0   hk  03/07/14 Limited frequency set when 100KHz or 400KHz is
*                    selected. This is a hardware limitation. CR#779290.
* 2.1   hk  04/24/14 Fix for CR# 761060 - provision for repeated start.
* 2.3	sk	10/07/14 Repeated start feature removed.
* 3.0	sk	12/06/14 Implemented Repeated start feature.
*			01/31/15 Modified the code according to MISRAC 2012 Compliant.
* 3.3   kvn 05/05/16 Modified latest code for MISRA-C:2012 Compliance.
* 3.11  rna 12/23/19 Add 10 bit address support for Master/Slave
*	    02/02/20 Correct return value of XIicPs_GetOptions function
*	    02/04/20 Modified latest code for MISRA-C:2012 Compliance
* </pre>
*
******************************************************************************/

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

#include "xiicps.h"

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


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


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


/************************** Function Prototypes ******************************/


/************************** Variable Definitions *****************************/
/**
 * Create the table of options which are processed to get/set the device
 * options. These options are table driven to allow easy maintenance and
 * expansion of the options.
 */
typedef struct {
		u32 Option;	/**< Option to select */
		u32 Mask;	/**< Mask to select */
} OptionsMap;

static OptionsMap OptionsTable[] = {
		{XIICPS_7_BIT_ADDR_OPTION, XIICPS_CR_NEA_MASK},
		{XIICPS_10_BIT_ADDR_OPTION, XIICPS_CR_NEA_MASK},
		{XIICPS_SLAVE_MON_OPTION, XIICPS_CR_SLVMON_MASK},
		{XIICPS_REP_START_OPTION, XIICPS_CR_HOLD_MASK},
};

#define XIICPS_NUM_OPTIONS      (sizeof(OptionsTable) / sizeof(OptionsMap)) /**< Calculated no. of options */

/*****************************************************************************/
/**
*
* @brief
* Sets the options for the IIC device driver. The options control
* how the device behaves relative to the IIC bus. The device must be idle
* rather than busy transferring data before setting these device options.
*
* @param	InstancePtr Pointer to the XIicPs instance.
* @param	Options Contains the specified options to be set. This is a bit
*		mask where a 1 means to turn the option on. One or more bit
*		values may be contained in the mask. See the bit definitions
*		named XIICPS_*_OPTION in xiicps.h.
*
* @return
*		- XST_SUCCESS if options are successfully set.
*		- XST_DEVICE_IS_STARTED if the device is currently transferring
*		data. The transfer must complete or be aborted before setting
*		options.
*
* @note		None.
*
******************************************************************************/
s32 XIicPs_SetOptions(XIicPs *InstancePtr, u32 Options)
{
	u32 ControlReg;
	u32 Index;
	u32 OptionsVar = Options;

	Xil_AssertNonvoid(InstancePtr != NULL);
	Xil_AssertNonvoid(InstancePtr->IsReady == (u32)XIL_COMPONENT_IS_READY);

	ControlReg = XIicPs_ReadReg(InstancePtr->Config.BaseAddress,
				      XIICPS_CR_OFFSET);

	/*
	 * If repeated start option is requested, set the flag.
	 * The hold bit in CR will be written by driver when the next transfer
	 * is initiated.
	 */
	if ((OptionsVar & (u32)XIICPS_REP_START_OPTION) != (u32)0 ) {
		InstancePtr->IsRepeatedStart = 1;
		OptionsVar = OptionsVar & (~XIICPS_REP_START_OPTION);
	}

	/*
	 * Loop through the options table, turning the option on.
	 */
	for (Index = 0U; Index < XIICPS_NUM_OPTIONS; Index++) {
		if ((OptionsVar & OptionsTable[Index].Option) != (u32)0x0U) {
			/*
			 * 10-bit option is specially treated, because it is
			 * using the 7-bit option, so turning it on means
			 * turning 7-bit option off.
			 */
			if ((OptionsTable[Index].Option &
				XIICPS_10_BIT_ADDR_OPTION) != (u32)0x0U) {
				/* Turn 7-bit off */
				InstancePtr->Is10BitAddr = 1;
				ControlReg &= ~OptionsTable[Index].Mask;
			} else {
				/* Turn 7-bit on */
				InstancePtr->Is10BitAddr = 0;
				ControlReg |= OptionsTable[Index].Mask;
			}
		}
	}

	/*
	 * Now write to the control register. Leave it to the upper layers
	 * to restart the device.
	 */
	XIicPs_WriteReg(InstancePtr->Config.BaseAddress, XIICPS_CR_OFFSET,
			  ControlReg);

	/*
	 * Keep a copy of what options this instance has.
	 */
	InstancePtr->Options = XIicPs_GetOptions(InstancePtr);

	return (s32)XST_SUCCESS;
}

/*****************************************************************************/
/**
*
* @brief
* Clears the options for the IIC device driver. The options
* control how the device behaves relative to the IIC bus. The device must be
* idle rather than busy transferring data before setting these device options.
*
* @param	InstancePtr Pointer to the XIicPs instance.
* @param	Options Contains the specified options to be cleared. This is a
*		bit mask where a 1 means to turn the option off. One or more bit
*		values may be contained in the mask. See the bit definitions
*		named XIICPS_*_OPTION in xiicps.h.
*
* @return
*		- XST_SUCCESS if options are successfully set.
*		- XST_DEVICE_IS_STARTED if the device is currently transferring
*		data. The transfer must complete or be aborted before setting
*		options.
*
* @note		None
*
******************************************************************************/
s32 XIicPs_ClearOptions(XIicPs *InstancePtr, u32 Options)
{
	u32 ControlReg;
	u32 Index;
	u32 OptionsVar = Options;

	Xil_AssertNonvoid(InstancePtr != NULL);
	Xil_AssertNonvoid(InstancePtr->IsReady == (u32)XIL_COMPONENT_IS_READY);

	ControlReg = XIicPs_ReadReg(InstancePtr->Config.BaseAddress,
					XIICPS_CR_OFFSET);

	/*
	 * If repeated start option is cleared, set the flag.
	 * The hold bit in CR will be cleared by driver when the
	 * following transfer ends.
	 */
	if ((OptionsVar & XIICPS_REP_START_OPTION) != (u32)0x0U ) {
		InstancePtr->IsRepeatedStart = 0;
		OptionsVar = OptionsVar & (~XIICPS_REP_START_OPTION);
	}

	/*
	 * Loop through the options table and clear the specified options.
	 */
	for (Index = 0U; Index < XIICPS_NUM_OPTIONS; Index++) {
		if ((OptionsVar & OptionsTable[Index].Option) != (u32)0x0U) {

			/*
			 * 10-bit option is specially treated, because it is
			 * using the 7-bit option, so clearing it means turning
			 * 7-bit option on.
			 */
			if ((OptionsTable[Index].Option &
				XIICPS_10_BIT_ADDR_OPTION) != (u32)0x0U) {
				/* Turn 7-bit on */
				InstancePtr->Is10BitAddr = 0;
				ControlReg |= OptionsTable[Index].Mask;
			} else {

				/* Turn 7-bit off */
				InstancePtr->Is10BitAddr = 1;
				ControlReg &= ~OptionsTable[Index].Mask;
			}
		}
	}


	/*
	 * Now write the control register. Leave it to the upper layers
	 * to restart the device.
	 */
	XIicPs_WriteReg(InstancePtr->Config.BaseAddress, XIICPS_CR_OFFSET,
			  ControlReg);

	/*
	 * Keep a copy of what options this instance has.
	 */
	InstancePtr->Options = XIicPs_GetOptions(InstancePtr);

	return (s32)XST_SUCCESS;
}

/*****************************************************************************/
/**
* @brief
* Gets the options for the IIC device. The options control how
* the device behaves relative to the IIC bus.
*
* @param        InstancePtr Pointer to the XIicPs instance.
*
* @return       32 bit mask of the options, where a 1 means the option is on,
*               and a 0 means to the option is off. One or more bit values may
*               be contained in the mask. See the bit definitions named
*               XIICPS_*_OPTION in the file xiicps.h.
*
* @note         None.
*
******************************************************************************/
u32 XIicPs_GetOptions(XIicPs *InstancePtr)
{
	u32 OptionsFlag = 0U;
	u32 ControlReg;
	u32 Index;

	Xil_AssertNonvoid(InstancePtr != NULL);
	Xil_AssertNonvoid(InstancePtr->IsReady == (u32)XIL_COMPONENT_IS_READY);

	/*
	 * Read control register to find which options are currently set.
	 */
	ControlReg = XIicPs_ReadReg(InstancePtr->Config.BaseAddress,
					XIICPS_CR_OFFSET);

	/*
	 * Loop through the options table to determine which options are set.
	 */
	for (Index = 0U; Index < XIICPS_NUM_OPTIONS; Index++) {
		if ((ControlReg & OptionsTable[Index].Mask) != (u32)0x0U) {
			OptionsFlag |= OptionsTable[Index].Option;
		}
	}

	/*
	 * Handle NEA separately, as 10-bit option and 7-bit option are
	 * using the same mask.
	 */
	if ((ControlReg & XIICPS_CR_NEA_MASK) == (u32)0x0U) {
		OptionsFlag |= XIICPS_10_BIT_ADDR_OPTION;
		OptionsFlag &= ~(XIICPS_7_BIT_ADDR_OPTION);
	}
	else {
		OptionsFlag |= XIICPS_7_BIT_ADDR_OPTION;
		OptionsFlag &= ~(XIICPS_10_BIT_ADDR_OPTION);
	}

	if (InstancePtr->IsRepeatedStart != 0 ) {
		OptionsFlag |= XIICPS_REP_START_OPTION;
	}
	return OptionsFlag;
}

/*****************************************************************************/
/**
*
* @brief
* Sets the serial clock rate for the IIC device. The device
* must be idle rather than busy transferring data before setting these device
* options.
*
* The data rate is set by values in the control register. The formula for
* determining the correct register values is:
* Fscl = Fpclk/(22 x (divisor_a+1) x (divisor_b+1))
* See the hardware data sheet for a full explanation of setting the serial
* clock rate.
*
* @param	InstancePtr Pointer to the XIicPs instance.
* @param	FsclHz Clock frequency in Hz. The two most common clock
*		rates are 100KHz and 400KHz.
*
* @return
*		- XST_SUCCESS if options are successfully set.
*		- XST_DEVICE_IS_STARTED if the device is currently transferring
*		data. The transfer must complete or be aborted before setting
*		options.
*		- XST_FAILURE if the Fscl frequency can not be set.
*
* @note		The clock can not be faster than the input clock divide by 22.
*
******************************************************************************/
s32 XIicPs_SetSClk(XIicPs *InstancePtr, u32 FsclHz)
{
	u32 Div_a;
	u32 Div_b;
	u32 ActualFscl;
	u32 Temp;
	u32 TempLimit;
	u32 LastError;
	u32 BestError;
	u32 CurrentError;
	u32 ControlReg;
	u32 CalcDivA;
	u32 CalcDivB;
	u32 BestDivA;
	u32 BestDivB;
	u32 FsclHzVar = FsclHz;

	Xil_AssertNonvoid(InstancePtr != NULL);
	Xil_AssertNonvoid(InstancePtr->IsReady == (u32)XIL_COMPONENT_IS_READY);
	Xil_AssertNonvoid(FsclHzVar > 0U);

	if (0U != XIicPs_In32((InstancePtr->Config.BaseAddress) +
					XIICPS_TRANS_SIZE_OFFSET)) {
		return (s32)XST_DEVICE_IS_STARTED;
	}

	/*
	 * Assume Div_a is 0 and calculate (divisor_a+1) x (divisor_b+1).
	 */
	Temp = (InstancePtr->Config.InputClockHz) / ((u32)22U * FsclHzVar);

	/*
	 * If the answer is negative or 0, the Fscl input is out of range.
	 */
	if ((u32)(0U) == Temp) {
		return (s32)XST_FAILURE;
	}

	/*
	 * If frequency 400KHz is selected, 384.6KHz should be set.
	 * If frequency 100KHz is selected, 90KHz should be set.
	 * This is due to a hardware limitation.
	 */
	if(FsclHzVar > (u32)384600U) {
		FsclHzVar = (u32)384600U;
	}

	if((FsclHzVar <= (u32)100000U) && (FsclHzVar > (u32)90000U)) {
		FsclHzVar = (u32)90000U;
	}

	/*
	 * TempLimit helps in iterating over the consecutive value of Temp to
	 * find the closest clock rate achievable with divisors.
	 * Iterate over the next value only if fractional part is involved.
	 */
	TempLimit = (((InstancePtr->Config.InputClockHz) %
			((u32)22 * FsclHzVar)) != 	(u32)0x0U) ?
						 (Temp + (u32)1U) : Temp;
	BestError = FsclHzVar;

	BestDivA = 0U;
	BestDivB = 0U;
	for ( ; Temp <= TempLimit ; Temp++)
	{
		LastError = FsclHzVar;
		CalcDivA = 0U;
		CalcDivB = 0U;

		for (Div_b = 0U; Div_b < 64U; Div_b++) {

			Div_a = Temp / (Div_b + 1U);

			if (Div_a != 0U){
				Div_a = Div_a - (u32)1U;
			}
			if (Div_a > 3U){
				continue;
			}
			ActualFscl = (InstancePtr->Config.InputClockHz) /
						(22U * (Div_a + 1U) * (Div_b + 1U));

			if (ActualFscl > FsclHzVar){
				CurrentError = (ActualFscl - FsclHzVar);}
			else{
				CurrentError = (FsclHzVar - ActualFscl);}

			if (LastError > CurrentError) {
				CalcDivA = Div_a;
				CalcDivB = Div_b;
				LastError = CurrentError;
			}
		}

		/*
		 * Used to capture the best divisors.
		 */
		if (LastError < BestError) {
			BestError = LastError;
			BestDivA = CalcDivA;
			BestDivB = CalcDivB;
		}
	}


	/*
	 * Read the control register and mask the Divisors.
	 */
	ControlReg = XIicPs_ReadReg(InstancePtr->Config.BaseAddress,
					  (u32)XIICPS_CR_OFFSET);
	ControlReg &= ~((u32)XIICPS_CR_DIV_A_MASK | (u32)XIICPS_CR_DIV_B_MASK);
	ControlReg |= (BestDivA << XIICPS_CR_DIV_A_SHIFT) |
		(BestDivB << XIICPS_CR_DIV_B_SHIFT);

	XIicPs_WriteReg(InstancePtr->Config.BaseAddress, (u32)XIICPS_CR_OFFSET,
			  ControlReg);

	return (s32)XST_SUCCESS;
}

/*****************************************************************************/
/**
*
* @brief
* Sets the serial clock rate for the IIC device. The device
* must be idle rather than busy transferring data before setting these device
* options.
*
* @param	InstancePtr Pointer to the XIicPs instance.
*
* @return	The value of the IIC clock to the nearest Hz based on the
*		control register settings. The actual value may not be exact to
*		to integer math rounding errors.
*
* @note		None.
*
******************************************************************************/
u32 XIicPs_GetSClk(XIicPs *InstancePtr)
{
	u32 ControlReg;
	u32 ActualFscl;
	u32 Div_a;
	u32 Div_b;

	Xil_AssertNonvoid(InstancePtr != NULL);
	Xil_AssertNonvoid(InstancePtr->IsReady == (u32)XIL_COMPONENT_IS_READY);

	ControlReg = XIicPs_ReadReg(InstancePtr->Config.BaseAddress,
					  XIICPS_CR_OFFSET);

	Div_a = (ControlReg & XIICPS_CR_DIV_A_MASK) >> XIICPS_CR_DIV_A_SHIFT;
	Div_b = (ControlReg & XIICPS_CR_DIV_B_MASK) >> XIICPS_CR_DIV_B_SHIFT;

	ActualFscl = (InstancePtr->Config.InputClockHz) /
		(22U * (Div_a + 1U) * (Div_b + 1U));

	return ActualFscl;
}
/** @} */
