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

/******************************************************************************/
/**
 * @file xusbps_intr.c
* @addtogroup usbps Overview
* @{
 *
 * This file contains the functions that are related to interrupt processing
 * for the EPB USB driver.
 *
 * <pre>
 * MODIFICATION HISTORY:
 *
 * Ver   Who  Date     Changes
 * ----- ---- -------- ----------------------------------------------------------
 * 1.00a jz  10/10/10 First release
 * 1.03a nm  09/21/12 Fixed CR#678977. Added proper sequence for setup packet
 *                    handling.
 * 2.3   bss 01/19/16 Modified XUsbPs_EpQueueRequest function to fix CR#873972
 *            (moving of dTD Head/Tail Pointers properly).
 * 2.5   pm  02/20/20 Added ISO endpoint support.
 * </pre>
 ******************************************************************************/

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

#include "xusbps.h"
#include "xusbps_endpoint.h"

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

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

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

/************************** Variable Definitions ******************************/

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

static void XUsbPs_IntrHandleTX(XUsbPs *InstancePtr, u32 EpCompl);
static void XUsbPs_IntrHandleRX(XUsbPs *InstancePtr, u32 EpCompl);
static void XUsbPs_IntrHandleReset(XUsbPs *InstancePtr, u32 IrqSts);
static void XUsbPs_IntrHandleEp0Setup(XUsbPs *InstancePtr);

/*****************************************************************************/
/**
* This function is the first-level interrupt handler for the USB core. All USB
* interrupts will be handled here. Depending on the type of the interrupt,
* second level interrupt handler may be called. Second level interrupt
* handlers will be registered by the user using the:
*    XUsbPs_IntrSetHandler()
* and/or
*    XUsbPs_EpSetHandler()
* functions.
*
*
* @param	HandlerRef is a Reference passed to the interrupt register
*		function. In our case this will be a pointer to the XUsbPs
*		instance.
*
* @return	None
*
* @note		None
*
******************************************************************************/
void XUsbPs_IntrHandler(void *HandlerRef)
{
	XUsbPs	*InstancePtr;

	u32	IrqSts;

	Xil_AssertVoid(HandlerRef != NULL);

	InstancePtr = (XUsbPs *) HandlerRef;

	/* Handle controller (non-endpoint) related interrupts. */
	IrqSts = XUsbPs_ReadReg(InstancePtr->Config.BaseAddress,
				XUSBPS_ISR_OFFSET);

	/* Clear the interrupt status register. */
	XUsbPs_WriteReg(InstancePtr->Config.BaseAddress,
			XUSBPS_ISR_OFFSET, IrqSts);

	/* Nak interrupt, used to respond to host's IN request */
	if (IrqSts & XUSBPS_IXR_NAK_MASK) {
		/* Ack the hardware	 */
		XUsbPs_WriteReg(InstancePtr->Config.BaseAddress,
				XUSBPS_EPNAKISR_OFFSET,
				XUsbPs_ReadReg(InstancePtr->Config.BaseAddress,
					       XUSBPS_EPNAKISR_OFFSET));
	}


	/***************************************************************
	 *
	 * Handle general interrupts. Endpoint interrupts will be handler
	 * later.
	 *
	 */

	/* RESET interrupt.*/
	if (IrqSts & XUSBPS_IXR_UR_MASK) {
		XUsbPs_IntrHandleReset(InstancePtr, IrqSts);
		return;
	}

	/* Check if we have a user handler that needs to be called. Note that
	 * this is the handler for general interrupts. Endpoint interrupts will
	 * be handled below.
	 */
	if ((IrqSts & InstancePtr->HandlerMask) && InstancePtr->HandlerFunc) {
		(InstancePtr->HandlerFunc)(InstancePtr->HandlerRef, IrqSts);
	}


	/***************************************************************
	 *
	 * Handle Endpoint interrupts.
	 *
	 */
	if (IrqSts & XUSBPS_IXR_UI_MASK) {
		u32	EpStat;
		u32	EpCompl;

		/* ENDPOINT 0 SETUP PACKET HANDLING
		 *
		 * Check if we got a setup packet on endpoint 0. Currently we
		 * only check for setup packets on endpoint 0 as we would not
		 * expect setup packets on any other endpoint (even though it
		 * is possible to send setup packets on other endpoints).
		 */
		EpStat = XUsbPs_ReadReg(InstancePtr->Config.BaseAddress,
					XUSBPS_EPSTAT_OFFSET);
		if (EpStat & 0x0001) {
			/* Handle the setup packet */
			XUsbPs_IntrHandleEp0Setup(InstancePtr);

			/* Re-Prime the endpoint.
			 * Endpoint is de-primed if a setup packet comes in.
			 */
			XUsbPs_EpPrime(InstancePtr, 0, XUSBPS_EP_DIRECTION_OUT);
		}

		/* Check for RX and TX complete interrupts. */
		EpCompl = XUsbPs_ReadReg(InstancePtr->Config.BaseAddress,
					 XUSBPS_EPCOMPL_OFFSET);


		/* ACK the complete interrupts. */
		XUsbPs_WriteReg(InstancePtr->Config.BaseAddress,
				XUSBPS_EPCOMPL_OFFSET, EpCompl);

		/* Check OUT (RX) endpoints. */
		if (EpCompl & XUSBPS_EP_OUT_MASK) {
			XUsbPs_IntrHandleRX(InstancePtr, EpCompl);
		}

		/* Check IN (TX) endpoints. */
		if (EpCompl & XUSBPS_EP_IN_MASK) {
			XUsbPs_IntrHandleTX(InstancePtr, EpCompl);
		}
	}
}


/*****************************************************************************/
/**
* This function registers the user callback handler for controller
* (non-endpoint) interrupts.
*
* @param	InstancePtr is a pointer to the XUsbPs instance of the
*		controller.
* @param	CallBackFunc is the Callback function to register.
*		CallBackFunc may be NULL to clear the entry.
* @param	CallBackRef is the user data reference passed to the
*		callback function. CallBackRef may be NULL.
* @param	Mask is the User interrupt mask. Defines which interrupts
*		will cause the callback to be called.
*
* @return
*		- XST_SUCCESS: Callback registered successfully.
*		- XST_FAILURE: Callback could not be registered.
*
* @note		None.
*
******************************************************************************/
int XUsbPs_IntrSetHandler(XUsbPs *InstancePtr,
			  XUsbPs_IntrHandlerFunc CallBackFunc,
			  void *CallBackRef, u32 Mask)
{
	Xil_AssertNonvoid(InstancePtr != NULL);

	InstancePtr->HandlerFunc	= CallBackFunc;
	InstancePtr->HandlerRef		= CallBackRef;
	InstancePtr->HandlerMask	= Mask;

	return XST_SUCCESS;
}


/*****************************************************************************/
/**
* This function handles TX buffer interrupts. It is called by the interrupt
* when a transmit complete interrupt occurs. It returns buffers of completed
* descriptors to the caller.
*
* @param	InstancePtr is a pointer to the XUsbPs instance of the
*		controller.
* @param	EpCompl is the Bit mask of endpoints that caused a transmit
*		complete interrupt.
*
* @return	None
*
* @note		None.
*
******************************************************************************/
static void XUsbPs_IntrHandleTX(XUsbPs *InstancePtr, u32 EpCompl)
{
	int Index;
	u32 Mask;
	int NumEp;

	/* Check all endpoints for TX complete bits.
	 */
	Mask	= 0x00010000;
	NumEp	= InstancePtr->DeviceConfig.NumEndpoints;

	/* Check for every endpoint if its TX complete bit is
	 * set.
	 */
	for (Index = 0; Index < NumEp; Index++, Mask <<= 1) {
		XUsbPs_EpIn	*Ep;

		if (!(EpCompl & Mask)) {
			continue;
		}
		/* The TX complete bit for this endpoint is
		 * set. Walk the list of descriptors to see
		 * which ones are completed.
		 */
		Ep = &InstancePtr->DeviceConfig.Ep[Index].In;
		do {

			XUsbPs_dTDInvalidateCache(Ep->dTDTail);

			/* If the descriptor is not active then the buffer has
			 * not been sent yet.
			 */
			if (XUsbPs_dTDIsActive(Ep->dTDTail)) {
				break;
			}


			if (InstancePtr->DeviceConfig.EpCfg[Index].In.Type ==
			    XUSBPS_EP_TYPE_ISOCHRONOUS) {
				if (Ep->HandlerIsoFunc) {
					Ep->HandlerIsoFunc(Ep->HandlerRef,
							   Ep->RequestedBytes,
							   Ep->BytesTxed);
				}
			} else {
				if (Ep->HandlerFunc) {
					void *BufPtr;

					BufPtr = (void *) XUsbPs_ReaddTD(Ep->dTDTail,
									 XUSBPS_dTDUSERDATA);

					Ep->HandlerFunc(Ep->HandlerRef, Index,
							XUSBPS_EP_EVENT_DATA_TX,
							BufPtr);
				}
			}

			Ep->dTDTail = XUsbPs_dTDGetNLP(Ep->dTDTail);
		} while (Ep->dTDTail != Ep->dTDHead);
	}
}


/*****************************************************************************/
/**
 * This function handles RX buffer interrupts. It is called by the interrupt
 * when a receive complete interrupt occurs. It notifies the callback functions
 * that have been registered with the individual endpoints that data has been
 * received.
 *
 * @param	InstancePtr
 * 		Pointer to the XUsbPs instance of the controller.
 *
 * @param	EpCompl
 * 		Bit mask of endpoints that caused a receive complete interrupt.
 * @return
 *		none
 *
 ******************************************************************************/
static void XUsbPs_IntrHandleRX(XUsbPs *InstancePtr, u32 EpCompl)
{
	XUsbPs_EpOut	*Ep;
	int		Index;
	u32		Mask;
	int		NumEp;

	/* Check all endpoints for RX complete bits. */
	Mask	= 0x00000001;
	NumEp	= InstancePtr->DeviceConfig.NumEndpoints;


	/* Check for every endpoint if its RX complete bit is set.*/
	for (Index = 0; Index < NumEp; Index++, Mask <<= 1) {
		int numP = 0;

		if (!(EpCompl & Mask)) {
			continue;
		}
		Ep = &InstancePtr->DeviceConfig.Ep[Index].Out;

		XUsbPs_dTDInvalidateCache(Ep->dTDCurr);

		/* Handle all finished dTDs */
		while (!XUsbPs_dTDIsActive(Ep->dTDCurr)) {
			numP += 1;
			/* We get data RX events for 0 length packets on
			 * endpoint 0. We receive and immediately release
			 * them again here, but there's no action to be
			 * taken.
			 */
			if (InstancePtr->DeviceConfig.EpCfg[Index].Out.Type ==
			    XUSBPS_EP_TYPE_ISOCHRONOUS) {
				if (Index == 0) {
					u8	BufferPtr[64] = {0};

					/* Get the data buffer. */
					XUsbPs_EpDataBufferReceive(InstancePtr,
								   0, &BufferPtr[0], 64);
					Ep->MemAlloted = 0;
				}

				if (Ep->MemAlloted == 1) {
					XUsbPs_EpGetData(InstancePtr, Index,
							 Ep->RequestedBytes);
				} else {
					break;
				}
			} else {
				if (Ep->HandlerFunc) {
					Ep->HandlerFunc(Ep->HandlerRef, Index,
							XUSBPS_EP_EVENT_DATA_RX, NULL);
				}

				Ep->dTDCurr = XUsbPs_dTDGetNLP(Ep->dTDCurr);
				XUsbPs_dTDInvalidateCache(Ep->dTDCurr);
			}
		}
		/* Re-Prime the endpoint.*/
		XUsbPs_EpPrime(InstancePtr, Index, XUSBPS_EP_DIRECTION_OUT);
	}
}


/*****************************************************************************/
/**
* This function handles a RESET interrupt. It will notify the interrupt
* handler callback of the RESET condition.
*
* @param	InstancePtr is pointer to the XUsbPs instance of the controller
* @param	IrqSts is the Interrupt status register content.
*		To be passed on to the user.
*
* @return	None
*
* @Note		None.
*
******************************************************************************/
static void XUsbPs_IntrHandleReset(XUsbPs *InstancePtr, u32 IrqSts)
{
	u32 Timeout;
	u8 Index;

	if (InstancePtr->AppData != NULL) {
		InstancePtr->AppData->State = XUSBPS_STATE_DEFAULT;
	}

	for (Index = 0; Index < InstancePtr->DeviceConfig.NumEndpoints;
	     Index++) {
		InstancePtr->DeviceConfig.Ep[Index].Out.MemAlloted = 0;
		InstancePtr->DeviceConfig.Ep[Index].Out.BufferPtr = NULL;
		InstancePtr->DeviceConfig.Ep[Index].Out.BytesTxed = 0;
		InstancePtr->DeviceConfig.Ep[Index].Out.RequestedBytes = 0;
		InstancePtr->DeviceConfig.Ep[Index].In.BufferPtr = NULL;
		InstancePtr->DeviceConfig.Ep[Index].In.BytesTxed = 0;
		InstancePtr->DeviceConfig.Ep[Index].In.RequestedBytes = 0;
	}

	/* Clear all setup token semaphores by reading the
	 * XUSBPS_EPSTAT_OFFSET register and writing its value back to
	 * itself.
	 */
	XUsbPs_WriteReg(InstancePtr->Config.BaseAddress, XUSBPS_EPSTAT_OFFSET,
			XUsbPs_ReadReg(InstancePtr->Config.BaseAddress,
				       XUSBPS_EPSTAT_OFFSET));

	/* Clear all the endpoint complete status bits by reading the
	 * XUSBPS_EPCOMPL_OFFSET register and writings its value back
	 * to itself.
	 */
	XUsbPs_WriteReg(InstancePtr->Config.BaseAddress,
			XUSBPS_EPCOMPL_OFFSET,
			XUsbPs_ReadReg(InstancePtr->Config.BaseAddress,
				       XUSBPS_EPCOMPL_OFFSET));

	/* Cancel all endpoint prime status by waiting until all bits
	 * in XUSBPS_EPPRIME_OFFSET are 0 and then writing 0xFFFFFFFF
	 * to XUSBPS_EPFLUSH_OFFSET.
	 *
	 * Avoid hanging here by using a Timeout counter...
	 */
	Timeout = XUSBPS_TIMEOUT_COUNTER;
	while ((XUsbPs_ReadReg(InstancePtr->Config.BaseAddress,
			       XUSBPS_EPPRIME_OFFSET) &
		XUSBPS_EP_ALL_MASK) && --Timeout) {
		/* NOP */
	}
	XUsbPs_WriteReg(InstancePtr->Config.BaseAddress,
			XUSBPS_EPFLUSH_OFFSET, 0xFFFFFFFF);

	/* Make sure that the reset bit in XUSBPS_PORTSCR1_OFFSET is
	 * still set at this point. If the code gets to this point and
	 * the reset bit has already been cleared we are in trouble and
	 * hardware reset is necessary.
	 */
	if (!(XUsbPs_ReadReg(InstancePtr->Config.BaseAddress,
			     XUSBPS_PORTSCR1_OFFSET) &
	      XUSBPS_PORTSCR_PR_MASK)) {
		/* Send a notification to the user that a hardware
		 * RESET is required. At this point we can only hope
		 * that the user registered an interrupt handler and
		 * will issue a hardware RESET.
		 */
		if (InstancePtr->HandlerFunc) {
			(InstancePtr->HandlerFunc)(InstancePtr->HandlerRef,
						   IrqSts);
		} else {
			for (;;);
		}

		/* If we get here there is nothing more to do. The user
		 * should have reset the core.
		 */
		return;
	}

	/* Check if we have a user handler that needs to be called.
	 */
	if (InstancePtr->HandlerFunc) {
		(InstancePtr->HandlerFunc)(InstancePtr->HandlerRef, IrqSts);
	}

	/* We are done. After RESET we don't proceed in the interrupt
	 * handler.
	 */
}


/*****************************************************************************/
/**
* This function handles a Setup Packet interrupt. It will notify the interrupt
* handler callback of the RESET condition.
*
* @param	InstancePtr is a pointer to the XUsbPs instance of the
*		controller.
*
* @return	None
*
* @Note 	None
*
******************************************************************************/
static void XUsbPs_IntrHandleEp0Setup(XUsbPs *InstancePtr)
{

	XUsbPs_EpOut	*Ep;

	/* Notify the user. */
	Ep = &InstancePtr->DeviceConfig.Ep[0].Out;

	if (Ep->HandlerFunc) {
		Ep->HandlerFunc(Ep->HandlerRef, 0,
				XUSBPS_EP_EVENT_SETUP_DATA_RECEIVED, NULL);
	}
}


/** @} */
