/*
 * SPDX-FileCopyrightText: 2020-2025 Espressif Systems (Shanghai) CO LTD
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#pragma once

#include "soc/soc_caps.h"
/*
This header is shared across all targets. Resolve to an empty header for targets
that don't support USB OTG.
*/
#if SOC_USB_OTG_SUPPORTED
#include <stdint.h>
#include <stdbool.h>
#include "hal/usb_dwc_ll.h"
#include "hal/usb_dwc_types.h"
#include "hal/assert.h"
#endif // SOC_USB_OTG_SUPPORTED

#ifdef __cplusplus
extern "C" {
#endif

#if SOC_USB_OTG_SUPPORTED

// ------------------------------------------------ Macros and Types ---------------------------------------------------

// ----------------------- Configs -------------------------

/**
 * @brief MPS limits based on FIFO configuration
 *
 * In bytes
 *
 * The resulting values depend on
 * 1. FIFO total size (chip specific)
 * 2. Set FIFO bias
 */
typedef struct {
    unsigned int in_mps;                 /**< Maximum packet size of IN packet */
    unsigned int non_periodic_out_mps;   /**< Maximum packet size of BULK and CTRL OUT packets */
    unsigned int periodic_out_mps;       /**< Maximum packet size of INTR and ISOC OUT packets */
} usb_hal_fifo_mps_limits_t;

/**
 * @brief FIFO size configuration structure
 */
typedef struct {
    uint32_t rx_fifo_lines;                 /**< Size of the RX FIFO in terms the number of FIFO lines */
    uint32_t nptx_fifo_lines;               /**< Size of the Non-periodic FIFO in terms the number of FIFO lines */
    uint32_t ptx_fifo_lines;                /**< Size of the Periodic FIFO in terms the number of FIFO lines */
} usb_dwc_hal_fifo_config_t;

// --------------------- HAL Events ------------------------

/**
 * @brief Host port HAL events
 */
typedef enum {
    USB_DWC_HAL_PORT_EVENT_NONE,            /**< No event occurred, or could not decode interrupt */
    USB_DWC_HAL_PORT_EVENT_CHAN,            /**< A channel event has occurred. Call the the channel event handler instead */
    USB_DWC_HAL_PORT_EVENT_CONN,            /**< The host port has detected a connection */
    USB_DWC_HAL_PORT_EVENT_DISCONN,         /**< The host port has been disconnected */
    USB_DWC_HAL_PORT_EVENT_ENABLED,         /**< The host port has been enabled (i.e., connected to a device that has been reset. Started sending SOFs) */
    USB_DWC_HAL_PORT_EVENT_DISABLED,        /**< The host port has been disabled (no more SOFs). Could be due to disable/reset request, or a port error (e.g. port babble condition. See 11.8.1 of USB2.0 spec) */
    USB_DWC_HAL_PORT_EVENT_OVRCUR,          /**< The host port has encountered an overcurrent condition */
    USB_DWC_HAL_PORT_EVENT_OVRCUR_CLR,      /**< The host port has been cleared of the overcurrent condition */
    USB_DWC_HAL_PORT_EVENT_REMOTE_WAKEUP,   /**< The host port has detected remote wakeup sequence from a device */
} usb_dwc_hal_port_event_t;

/**
 * @brief Channel events
 */
typedef enum {
    USB_DWC_HAL_CHAN_EVENT_CPLT,            /**< The channel has completed execution of a transfer descriptor that had the USB_DWC_HAL_XFER_DESC_FLAG_HOC flag set. Channel is now halted */
    USB_DWC_HAL_CHAN_EVENT_ERROR,           /**< The channel has encountered an error. Channel is now halted. */
    USB_DWC_HAL_CHAN_EVENT_HALT_REQ,        /**< The channel has been successfully halted as requested */
    USB_DWC_HAL_CHAN_EVENT_NONE,            /**< No event (interrupt ran for internal processing) */
} usb_dwc_hal_chan_event_t;

// --------------------- HAL Errors ------------------------

/**
 * @brief Channel errors
 */
typedef enum {
    USB_DWC_HAL_CHAN_ERROR_XCS_XACT = 0,    /**< Excessive (three consecutive) transaction errors (e.g., no response, bad CRC etc */
    USB_DWC_HAL_CHAN_ERROR_BNA,             /**< Buffer Not Available error (i.e., An inactive transfer descriptor was fetched by the channel) */
    USB_DWC_HAL_CHAN_ERROR_PKT_BBL,         /**< Packet babbler error (packet exceeded MPS) */
    USB_DWC_HAL_CHAN_ERROR_STALL,           /**< STALL response received */
} usb_dwc_hal_chan_error_t;

// ------------- Transfer Descriptor Related ---------------

/**
 * @brief Flags used to describe the type of transfer descriptor to fill
 */
#define USB_DWC_HAL_XFER_DESC_FLAG_IN       0x01    /**< Indicates this transfer descriptor is of the IN direction */
#define USB_DWC_HAL_XFER_DESC_FLAG_SETUP    0x02    /**< Indicates this transfer descriptor is an OUT setup */
#define USB_DWC_HAL_XFER_DESC_FLAG_HOC      0x04    /**< Indicates that the channel will be halted after this transfer descriptor completes */

/**
 * @brief Status value of a transfer descriptor
 *
 * A transfer descriptor's status remains unexecuted until the entire transfer descriptor completes (either successfully
 * or an error). Therefore, if a channel halt is requested before a transfer descriptor completes, the transfer
 * descriptor remains unexecuted.
 */
#define USB_DWC_HAL_XFER_DESC_STS_SUCCESS       USB_DWC_LL_QTD_STATUS_SUCCESS
#define USB_DWC_HAL_XFER_DESC_STS_PKTERR        USB_DWC_LL_QTD_STATUS_PKTERR
#define USB_DWC_HAL_XFER_DESC_STS_BUFFER_ERR    USB_DWC_LL_QTD_STATUS_BUFFER
#define USB_DWC_HAL_XFER_DESC_STS_NOT_EXECUTED  USB_DWC_LL_QTD_STATUS_NOT_EXECUTED

// -------------------- Object Types -----------------------

/**
 * @brief Endpoint characteristics structure
 */
typedef struct {
    union {
        struct {
            usb_dwc_xfer_type_t type: 2;    /**< The type of endpoint */
            uint32_t bEndpointAddress: 8;   /**< Endpoint address (containing endpoint number and direction) */
            uint32_t mps: 11;               /**< Maximum Packet Size */
            uint32_t dev_addr: 8;           /**< Device Address */
            uint32_t ls_via_fs_hub: 1;      /**< The endpoint is on a LS device that is routed through an FS hub.
                                                 Setting this bit will lead to the addition of the PREamble packet */
            uint32_t reserved2: 2;
        };
        uint32_t val;
    };
    struct {
        unsigned int interval;              /**< The interval of the endpoint in frames (FS) or microframes (HS) */
        uint32_t offset;                    /**< Offset of this channel in the periodic scheduler */
        bool is_hs;                         /**< This endpoint is HighSpeed. Needed for Periodic Frame List (HAL layer) scheduling */
    } periodic;     /**< Characteristic for periodic (interrupt/isochronous) endpoints only */
} usb_dwc_hal_ep_char_t;

/**
 * @brief Channel object
 */
typedef struct {
    //Channel control, status, and information
    union {
        struct {
            uint32_t active: 1;             /**< Debugging bit to indicate whether channel is enabled */
            uint32_t halt_requested: 1;     /**< A halt has been requested */
            uint32_t reserved: 2;
            uint32_t chan_idx: 4;           /**< The index number of the channel */
            uint32_t reserved24: 24;
        };
        uint32_t val;
    } flags;                                /**< Flags regarding channel's status and information */
    usb_dwc_host_chan_regs_t *regs;         /**< Pointer to the channel's register set */
    usb_dwc_hal_chan_error_t error;         /**< The last error that occurred on the channel */
    usb_dwc_xfer_type_t type;               /**< The transfer type of the channel */
    void *chan_ctx;                         /**< Context variable for the owner of the channel */
} usb_dwc_hal_chan_t;

/**
 * @brief HAL context structure
 */
typedef struct {
    // HW context
    usb_dwc_dev_t *dev;                         /**< Pointer to base address of DWC_OTG registers */

    // Host Port related
    uint32_t *periodic_frame_list;              /**< Pointer to scheduling frame list */
    usb_hal_frame_list_len_t frame_list_len;    /**< Length of the periodic scheduling frame list */

    // FIFO related
    usb_dwc_hal_fifo_config_t fifo_config;      /**< FIFO sizes configuration */

    // Configuration of the USB-DWC core. Read from read-only HW registers
    struct {
        unsigned chan_num_total;                /**< Total number of channels for this configuration */
        unsigned hsphy_type;                    /**< HS PHY type of this configuration */
        unsigned fifo_size;                     /**< Total FIFO size [in lines] in this configuration */
    } constant_config;

    union {
        struct {
            uint32_t dbnc_lock_enabled: 1;      /**< Debounce lock enabled */
            uint32_t fifo_sizes_set: 1;         /**< Whether the FIFO sizes have been set or not */
            uint32_t periodic_sched_enabled: 1; /**< Periodic scheduling (for interrupt and isochronous transfers) is enabled */
            uint32_t reserved: 5;
            uint32_t reserved24: 24;
        };
        uint32_t val;
    } flags;

    // Channel related
    struct {
        int num_allocated;                      /**< Number of channels currently allocated */
        uint32_t chan_pend_intrs_msk;           /**< Bit mask of channels with pending interrupts */
        usb_dwc_hal_chan_t **hdls;              /**< Handles of each channel. Set to NULL if channel has not been allocated */
    } channels;
} usb_dwc_hal_context_t;

// -------------------------------------------------- Core (Global) ----------------------------------------------------

/**
 * @brief Initialize the HAL context and check if DWC_OTG is alive
 *
 * Entry:
 * - The peripheral must have been reset and clock un-gated
 * - The USB PHY (internal or external) and associated GPIOs must already be configured
 * - GPIO pins configured
 * - Interrupt allocated but DISABLED (in case of an unknown interrupt state)
 * Exit:
 * - Checks to see if DWC_OTG is alive, and if HW version/config is correct
 * - HAL context initialized
 * - Read and save relevant USB-DWC configuration parameters
 * - Sets default values to some global and OTG registers (GAHBCFG and GUSBCFG)
 * - Umask global interrupt signal
 * - Put DWC_OTG into host mode. Require 25ms delay before this takes effect.
 * - State -> USB_DWC_HAL_PORT_STATE_OTG
 * - Interrupts cleared. Users can now enable their ISR
 *
 * @attention The user must allocate memory for channel handlers with
 *            `hal->channels.hdls = malloc(hal->constant_config.chan_num_total * sizeof(usb_dwc_hal_chan_t*))`
 * @param[inout] hal     Context of the HAL layer
 * @param[in]    port_id USB port ID
 */
void usb_dwc_hal_init(usb_dwc_hal_context_t *hal, int port_id);

/**
 * @brief Deinitialize the HAL context
 *
 * Entry:
 * - All channels must be properly disabled, and any pending events handled
 * Exit:
 * - DWC_OTG global interrupt disabled
 * - HAL context deinitialized
 *
 * @param hal Context of the HAL layer
 */
void usb_dwc_hal_deinit(usb_dwc_hal_context_t *hal);

/**
 * @brief Issue a soft reset to the controller
 *
 * This should be called when the host port encounters an error event or has been disconnected. Before calling this,
 * users are responsible for safely freeing all channels as a soft reset will wipe all host port and channel registers.
 * This function will result in the host port being put back into same state as after calling usb_dwc_hal_init().
 *
 * @note This has nothing to do with a USB bus reset. It simply resets the peripheral
 *
 * @param[in] hal Context of the HAL layer
 */
void usb_dwc_hal_core_soft_reset(usb_dwc_hal_context_t *hal);

/**
 * @brief Check if FIFO configuration is valid
 *
 * This function checks that the sum of FIFO sizes does not exceed available space.
 * It does not modify hardware state and is safe to call from HAL or upper layers.
 *
 * @param[in] hal     Pointer to HAL context (must be initialized)
 * @param[in] config  Pointer to FIFO config to validate
 * @return true if config is valid, false otherwise
 */
bool usb_dwc_hal_fifo_config_is_valid(const usb_dwc_hal_context_t *hal, const usb_dwc_hal_fifo_config_t *config);


/**
 * @brief Set the FIFO sizes of the USB-DWC core
 *
 * This function programs the FIFO sizing registers (RX FIFO, Non-Periodic TX FIFO,
 * and Periodic TX FIFO) based on the provided configuration. It must be called
 * during USB initialization, before any channels are allocated or transfers started.
 *
 * The sum of all FIFO sizes must not exceed the hardware-defined limit
 * (see HWCFG3.DfifoDepth and EPINFO_CTL).
 *
 * @note This function must be called exactly once during initialization and after
 *       each USB port reset. It is typically used internally by the USB Host stack.
 *
 * @param[inout] hal     Pointer to the HAL context
 * @param[in]    config  Pointer to the FIFO configuration to apply (must be valid)
 */
void usb_dwc_hal_set_fifo_config(usb_dwc_hal_context_t *hal, const usb_dwc_hal_fifo_config_t *config);

/**
 * @brief Get MPS limits
 *
 * @param[in]  hal        Context of the HAL layer
 * @param[out] mps_limits MPS limits
 */
void usb_dwc_hal_get_mps_limits(usb_dwc_hal_context_t *hal, usb_hal_fifo_mps_limits_t *mps_limits);

// ---------------------------------------------------- Host Port ------------------------------------------------------

// ------------------ Host Port Control --------------------

/**
 * @brief Initialize the host port
 *
 * - Will enable the host port's interrupts allowing port and channel events to occur
 *
 * @param hal Context of the HAL layer
 */
static inline void usb_dwc_hal_port_init(usb_dwc_hal_context_t *hal)
{
    //Configure Host related interrupts
    usb_dwc_ll_haintmsk_dis_chan_intr(hal->dev, 0xFFFFFFFF);   //Disable interrupts for all channels
    usb_dwc_ll_gintmsk_en_intrs(hal->dev, USB_DWC_LL_INTR_CORE_PRTINT | USB_DWC_LL_INTR_CORE_HCHINT | USB_DWC_LL_INTR_CORE_WKUPINT);
}

/**
 * @brief Deinitialize the host port
 *
 * - Will disable the host port's interrupts preventing further port aand channel events from occurring
 *
 * @param hal Context of the HAL layer
 */
static inline void usb_dwc_hal_port_deinit(usb_dwc_hal_context_t *hal)
{
    //Disable Host port and channel interrupts
    usb_dwc_ll_gintmsk_dis_intrs(hal->dev, USB_DWC_LL_INTR_CORE_PRTINT | USB_DWC_LL_INTR_CORE_HCHINT | USB_DWC_LL_INTR_CORE_WKUPINT);
}

/**
 * @brief Toggle the host port's power
 *
 * @param hal Context of the HAL layer
 * @param power_on Whether to power ON or OFF the port
 */
static inline void usb_dwc_hal_port_toggle_power(usb_dwc_hal_context_t *hal, bool power_on)
{
    if (power_on) {
        usb_dwc_ll_hprt_en_pwr(hal->dev);
    } else {
        usb_dwc_ll_hprt_dis_pwr(hal->dev);
    }
}

/**
 * @brief Toggle reset signal on the bus
 *
 * The reset signal should be held for at least 10ms
 * Entry:
 * - Host port detects a device connection or Host port is already enabled
 * Exit:
 * - On release of the reset signal, a USB_DWC_HAL_PORT_EVENT_ENABLED will be generated
 *
 * @note If the host port is already enabled, then issuing a reset will cause it be disabled and generate a
 *       USB_DWC_HAL_PORT_EVENT_DISABLED event. The host port will not be enabled until the reset signal is released (thus
 *       generating the USB_DWC_HAL_PORT_EVENT_ENABLED event)
 *
 * @param hal Context of the HAL layer
 * @param enable Enable/disable reset signal
 */
static inline void usb_dwc_hal_port_toggle_reset(usb_dwc_hal_context_t *hal, bool enable)
{
    usb_dwc_ll_hprt_set_port_reset(hal->dev, enable);
}

/**
 * @brief Enable the host port
 *
 * Entry:
 * - Host port enabled event triggered following a reset
 * Exit:
 * - Host port enabled to operate in scatter/gather DMA mode
 * - DMA fifo sizes configured
 *
 * @param hal Context of the HAL layer
 */
void usb_dwc_hal_port_enable(usb_dwc_hal_context_t *hal);

/**
 * @brief Disable the host port
 *
 * Exit:
 * - Host port disabled event triggered
 *
 * @param hal Context of the HAL layer
 */
static inline void usb_dwc_hal_port_disable(usb_dwc_hal_context_t *hal)
{
    usb_dwc_ll_hprt_port_dis(hal->dev);
}

/**
 * @brief Suspend the host port
 *
 * @param hal Context of the HAL layers
 */
static inline void usb_dwc_hal_port_suspend(usb_dwc_hal_context_t *hal)
{
    usb_dwc_ll_hprt_set_port_suspend(hal->dev);
}

/**
 * @brief Toggle resume signal on the bus
 *
 * Hosts should hold the resume signal for at least 20ms
 *
 * @note If a remote wakeup event occurs, the resume signal is driven and cleared automatically.
 *
 * @param hal Context of the HAL layer
 * @param enable Enable/disable resume signal
 */
static inline void usb_dwc_hal_port_toggle_resume(usb_dwc_hal_context_t *hal, bool enable)
{
    if (enable) {
        usb_dwc_ll_hprt_set_port_resume(hal->dev);
    } else {
        usb_dwc_ll_hprt_clr_port_resume(hal->dev);
    }
}

/**
 * @brief Check whether the resume signal is being driven
 *
 * If a remote wakeup event occurs, the core will automatically drive and clear the resume signal for the required
 * amount of time. Call this function to check whether the resume signal has completed.
 *
 * @param hal Context of the HAL layer
 * @return true Resume signal is still being driven
 * @return false Resume signal is no longer driven
 */
static inline bool usb_dwc_hal_port_check_resume(usb_dwc_hal_context_t *hal)
{
    return usb_dwc_ll_hprt_get_port_resume(hal->dev);
}

// ---------------- Host Port Scheduling -------------------

/**
 * @brief Sets the periodic scheduling frame list
 *
 * @note This function must be called before attempting configuring any channels to be period via
 *       usb_dwc_hal_chan_set_ep_char()
 *
 * @param hal Context of the HAL layer
 * @param frame_list Base address of the frame list
 * @param frame_list_len Number of entries in the frame list (can only be 8, 16, 32, 64)
 */
static inline void usb_dwc_hal_port_set_frame_list(usb_dwc_hal_context_t *hal, uint32_t *frame_list, usb_hal_frame_list_len_t len)
{
    //Clear and save frame list
    hal->periodic_frame_list = frame_list;
    hal->frame_list_len = len;
}

/**
 * @brief Enable periodic scheduling
 *
 * @note The periodic frame list must be set via usb_dwc_hal_port_set_frame_list() should be set before calling this
 *       function
 * @note This function must be called before activating any periodic channels
 *
 * @param hal Context of the HAL layer
 */
static inline void usb_dwc_hal_port_periodic_enable(usb_dwc_hal_context_t *hal)
{
    HAL_ASSERT(hal->periodic_frame_list != NULL);
    usb_dwc_ll_hflbaddr_set_base_addr(hal->dev, (uint32_t)hal->periodic_frame_list);
    usb_dwc_ll_hcfg_set_num_frame_list_entries(hal->dev, hal->frame_list_len);
    usb_dwc_ll_hcfg_en_perio_sched(hal->dev);
    hal->flags.periodic_sched_enabled = 1;
}

/**
 * @brief Disable periodic scheduling
 *
 * Disabling periodic scheduling will save a bit of DMA bandwidth (as the controller will no longer fetch the schedule
 * from the frame list).
 *
 * @note Before disabling periodic scheduling, it is the user's responsibility to ensure that all periodic channels have
 *       halted safely.
 *
 * @param hal Context of the HAL layer
 */
static inline void usb_dwc_hal_port_periodic_disable(usb_dwc_hal_context_t *hal)
{
    HAL_ASSERT(hal->flags.periodic_sched_enabled);
    usb_dwc_ll_hcfg_dis_perio_sched(hal->dev);
    hal->flags.periodic_sched_enabled = 0;
}

static inline uint32_t usb_dwc_hal_port_get_cur_frame_num(usb_dwc_hal_context_t *hal)
{
    return usb_dwc_ll_hfnum_get_frame_num(hal->dev);
}

// --------------- Host Port Status/State ------------------

/**
 * @brief Check if a device is currently connected to the host port
 *
 * This function is intended to be called after one of the following events followed by an adequate debounce delay
 * - USB_DWC_HAL_PORT_EVENT_CONN
 * - USB_DWC_HAL_PORT_EVENT_DISCONN
 *
 * @note No other connection/disconnection event will occur again until the debounce lock is disabled via
 *       usb_dwc_hal_disable_debounce_lock()
 *
 * @param hal Context of the HAL layer
 * @return true A device is connected to the host port
 * @return false A device is not connected to the host port
 */
static inline bool usb_dwc_hal_port_check_if_connected(usb_dwc_hal_context_t *hal)
{
    return usb_dwc_ll_hprt_get_conn_status(hal->dev);
}

/**
 * @brief Check the speed of the device connected to the host port
 *
 * @note This function should only be called after confirming that a device is connected to the host port
 *
 * @param hal Context of the HAL layer
 * @return usb_dwc_speed_t Speed of the connected device
 */
static inline usb_dwc_speed_t usb_dwc_hal_port_get_conn_speed(usb_dwc_hal_context_t *hal)
{
    return usb_dwc_ll_hprt_get_speed(hal->dev);
}

/**
 * @brief Disable the debounce lock
 *
 * This function must be called after calling usb_dwc_hal_port_check_if_connected() and will allow connection/disconnection
 * events to occur again. Any pending connection or disconnection interrupts are cleared.
 *
 * @param hal Context of the HAL layer
 */
static inline void usb_dwc_hal_disable_debounce_lock(usb_dwc_hal_context_t *hal)
{
    hal->flags.dbnc_lock_enabled = 0;
    //Clear Connection and disconnection interrupt in case it triggered again
    usb_dwc_ll_gintsts_clear_intrs(hal->dev, USB_DWC_LL_INTR_CORE_DISCONNINT);
    usb_dwc_ll_hprt_intr_clear(hal->dev, USB_DWC_LL_INTR_HPRT_PRTCONNDET);
    //Re-enable the hprt (connection) and disconnection interrupts
    usb_dwc_ll_gintmsk_en_intrs(hal->dev, USB_DWC_LL_INTR_CORE_PRTINT | USB_DWC_LL_INTR_CORE_DISCONNINT);
}

/**
 * @brief Check if the root port is suspended
 *
 * This function checks if the root port entered suspended state, after calling usb_dwc_hal_port_suspend()
 *
 * @param hal Context of the HAL layer
 * @return true The root port is suspended
 * @return false The root port is not suspended
 */
static inline bool usb_dwc_hal_port_check_if_suspended(usb_dwc_hal_context_t *hal)
{
    return usb_dwc_ll_hprt_get_port_suspend(hal->dev);
}

// ----------------------------------------------------- Power and Clock gating ----------------------------------------

/**
 * @brief Gate internal clock of the DWC_OTG core
 *
 * This function gates the internal clock of the DWC_OTG core to reduce power consumption
 *
 * Internal clock gating:
 *  - stop PHY clock (PCLK)
 *  - gate HCLK to modules other than the AHB slave, AHB master and wakeup logic
 * Internal clock un-gating:
 *  - un-stop PHY clock (PCLK)
 *  - un-gate HCLK
 *
 * @note This is a part of a sequence from the DWC Programming guide, chapter 14.2.2.1
 * @param hal Context of the HAL layer
 * @param enable Enable or disable internal clock gating
 */
static inline void usb_dwc_hal_pwr_clk_internal_clock_gate(usb_dwc_hal_context_t *hal, bool enable)
{
    usb_dwc_ll_set_stoppclk(hal->dev, enable);      // Enable/disable PHY clock stop
    usb_dwc_ll_set_gatehclk(hal->dev, enable);      // Gate/ungate HCLK
}

/**
 * @brief Check if the PHY clock (PCLK) stop is enabled or disabled
 *
 * @param hal Context of the HAL layer
 * @return true The PCLK stop is enabled
 * @return false The PCLK stop is not enabled
 */
static inline bool usb_dwc_hal_pwr_clk_check_phy_clk_stopped(usb_dwc_hal_context_t *hal)
{
    return usb_dwc_ll_get_stoppclk_st(hal->dev);
}

/**
 * @brief Check if the HCLK is gated or ungated
 *
 * @param hal Context of the HAL layer
 * @return true The HCLK is gated
 * @return false The HCLK is not gated
 */
static inline bool usb_dwc_hal_pwr_clk_check_hclk_gated(usb_dwc_hal_context_t *hal)
{
    return usb_dwc_ll_get_gatehclk_st(hal->dev);
}

/**
 * @brief Check if PHY is in sleep state
 *
 * @param hal Context of the HAL layer
 * @return true The USB PHY is in sleep state
 * @return false The USB PHY is not in sleep state
 */
static inline bool usb_dwc_hal_pwr_clk_check_phy_sleep(usb_dwc_hal_context_t *hal)
{
    return usb_dwc_ll_get_physleep_st(hal->dev);
}

// ----------------------------------------------------- Channel -------------------------------------------------------

// ----------------- Channel Allocation --------------------

/**
 * @brief Allocate a channel
 *
 * @param[in] hal Context of the HAL layer
 * @param[inout] chan_obj Empty channel object
 * @param[in] chan_ctx Context variable for the allocator of the channel
 * @return true Channel successfully allocated
 * @return false Failed to allocate channel
 */
bool usb_dwc_hal_chan_alloc(usb_dwc_hal_context_t *hal, usb_dwc_hal_chan_t *chan_obj, void *chan_ctx);

/**
 * @brief Free a channel
 *
 * @param[in] hal Context of the HAL layer
 * @param[in] chan_obj Channel object
 */
void usb_dwc_hal_chan_free(usb_dwc_hal_context_t *hal, usb_dwc_hal_chan_t *chan_obj);

// ---------------- Channel Configuration ------------------

/**
 * @brief Get the context variable of the channel
 *
 * @param[in] chan_obj Channel object
 * @return void* The context variable of the channel
 */
static inline void *usb_dwc_hal_chan_get_context(usb_dwc_hal_chan_t *chan_obj)
{
    return chan_obj->chan_ctx;
}

/**
 * @brief Set the endpoint information for a particular channel
 *
 * This should be called when a channel switches target from one EP to another
 *
 * @note the channel must be in the disabled state in order to change its EP
 *       information
 *
 * @param hal Context of the HAL layer
 * @param chan_obj Channel object
 * @param ep_char Endpoint characteristics
 */
void usb_dwc_hal_chan_set_ep_char(usb_dwc_hal_context_t *hal, usb_dwc_hal_chan_t *chan_obj, usb_dwc_hal_ep_char_t *ep_char);

/**
 * @brief Set the direction of the channel
 *
 * This is a convenience function to flip the direction of a channel without
 * needing to reconfigure all of the channel's EP info. This is used primarily
 * for control transfers.
 *
 * @note This function should only be called when the channel is halted
 *
 * @param chan_obj Channel object
 * @param is_in Whether the direction is IN
 */
static inline void usb_dwc_hal_chan_set_dir(usb_dwc_hal_chan_t *chan_obj, bool is_in)
{
    //Cannot change direction whilst channel is still active or in error
    HAL_ASSERT(!chan_obj->flags.active);
    usb_dwc_ll_hcchar_set_dir(chan_obj->regs, is_in);
}

/**
 * @brief Set the next Packet ID of the channel (e.g., DATA0/DATA1)
 *
 * This should be called when a channel switches target from one EP to another
 * or when change stages for a control transfer
 *
 * @note The channel should only be called when the channel is in the
 *       halted state.
 *
 * @param chan_obj Channel object
 * @param pid PID of the next DATA packet (DATA0 or DATA1)
 */
static inline void usb_dwc_hal_chan_set_pid(usb_dwc_hal_chan_t *chan_obj, int pid)
{
    //Cannot change pid whilst channel is still active or in error
    HAL_ASSERT(!chan_obj->flags.active);
    //Update channel object and set the register
    usb_dwc_ll_hctsiz_set_pid(chan_obj->regs, pid);
}

/**
 * @brief Get the next PID of a channel
 *
 * Returns the next PID (DATA0 or DATA1) of the channel. This function should be
 * used when the next PID of a pipe needs to be saved (e.g., when switching pipes
 * on a channel)
 *
 * @param chan_obj Channel object
 * @return uint32_t Starting PID of the next transfer (DATA0 or DATA1)
 */
static inline uint32_t usb_dwc_hal_chan_get_pid(usb_dwc_hal_chan_t *chan_obj)
{
    HAL_ASSERT(!chan_obj->flags.active);
    return usb_dwc_ll_hctsiz_get_pid(chan_obj->regs);
}

// ------------------- Channel Control ---------------------

/**
 * @brief Activate a channel
 *
 * Activating a channel will cause the channel to start executing transfer descriptors.
 *
 * @note This function should only be called on channels that were previously halted
 * @note An event will be generated when the channel is halted
 *
 * @param chan_obj Channel object
 * @param xfer_desc_list A filled transfer descriptor list
 * @param desc_list_len Transfer descriptor list length
 * @param start_idx Index of the starting transfer descriptor in the list
 */
void usb_dwc_hal_chan_activate(usb_dwc_hal_chan_t *chan_obj, void *xfer_desc_list, int desc_list_len, int start_idx);

/**
 * @brief Get the index of the current transfer descriptor
 *
 * @param chan_obj Channel object
 * @return int Descriptor index
 */
static inline int usb_dwc_hal_chan_get_qtd_idx(usb_dwc_hal_chan_t *chan_obj)
{
    return usb_dwc_ll_hcdam_get_cur_qtd_idx(chan_obj->regs);
}

/**
 * @brief Request to halt a channel
 *
 * This function should be called in order to halt a channel. If the channel is already halted, this function will
 * return true. If the channel is still active, this function will return false and users must wait for the
 * USB_DWC_HAL_CHAN_EVENT_HALT_REQ event before treating the channel as halted.
 *
 * @note When a transfer is in progress (i.e., the channel is active) and a halt is requested, the channel will halt
 *       after the next USB packet is completed. If the transfer has more pending packets, the transfer will just be
 *       marked as USB_DWC_HAL_XFER_DESC_STS_NOT_EXECUTED.
 *
 * @param chan_obj Channel object
 * @return true The channel is already halted
 * @return false The halt was requested, wait for USB_DWC_HAL_CHAN_EVENT_HALT_REQ
 */
bool usb_dwc_hal_chan_request_halt(usb_dwc_hal_chan_t *chan_obj);

/**
 * @brief Indicate that a channel is halted after a port error
 *
 * When a port error occurs (e.g., disconnect, overcurrent):
 * - Any previously active channels will remain active (i.e., they will not receive a channel interrupt)
 * - Attempting to disable them using usb_dwc_hal_chan_request_halt() will NOT generate an interrupt for ISOC channels
 *   (probably something to do with the periodic scheduling)
 *
 * However, the channel's enable bit can be left as 1 since after a port error, a soft reset will be done anyways.
 * This function simply updates the channels internal state variable to indicate it is halted (thus allowing it to be
 * freed).
 *
 * @param chan_obj Channel object
 */
static inline void usb_dwc_hal_chan_mark_halted(usb_dwc_hal_chan_t *chan_obj)
{
    chan_obj->flags.active = 0;
}

/**
 * @brief Get a channel's error
 *
 * @param chan_obj Channel object
 * @return usb_dwc_hal_chan_error_t The type of error the channel has encountered
 */
static inline usb_dwc_hal_chan_error_t usb_dwc_hal_chan_get_error(usb_dwc_hal_chan_t *chan_obj)
{
    return chan_obj->error;
}

// -------------------------------------------- Transfer Descriptor List -----------------------------------------------

/**
 * @brief Fill a single entry in a transfer descriptor list
 *
 * - Depending on the transfer type, a single transfer descriptor may corresponds
 *      - A stage of a transfer (for control transfers)
 *      - A frame of a transfer interval (for interrupt and isoc)
 *      - An entire transfer (for bulk transfers)
 * - Check the various USB_DWC_HAL_XFER_DESC_FLAG_ flags for filling a specific type of descriptor
 * - For IN transfer entries, set the USB_DWC_HAL_XFER_DESC_FLAG_IN. The transfer size must also be an integer multiple of
 *   the endpoint's MPS
 *
 * @note Critical section is not required for this function
 *
 * @param desc_list Transfer descriptor list
 * @param desc_idx Transfer descriptor index
 * @param xfer_data_buff Transfer data buffer
 * @param xfer_len Transfer length
 * @param flags Transfer flags
 */
static inline void usb_dwc_hal_xfer_desc_fill(void *desc_list, uint32_t desc_idx, uint8_t *xfer_data_buff, int xfer_len, uint32_t flags)
{
    usb_dwc_ll_dma_qtd_t *qtd_list = (usb_dwc_ll_dma_qtd_t *)desc_list;
    if (flags & USB_DWC_HAL_XFER_DESC_FLAG_IN) {
        usb_dwc_ll_qtd_set_in(&qtd_list[desc_idx],
                           xfer_data_buff, xfer_len,
                           flags & USB_DWC_HAL_XFER_DESC_FLAG_HOC);
    } else {
        usb_dwc_ll_qtd_set_out(&qtd_list[desc_idx],
                            xfer_data_buff,
                            xfer_len,
                            flags & USB_DWC_HAL_XFER_DESC_FLAG_HOC,
                            flags & USB_DWC_HAL_XFER_DESC_FLAG_SETUP);
    }
}

/**
 * @brief Clear a transfer descriptor (sets all its fields to NULL)
 *
 * @param desc_list Transfer descriptor list
 * @param desc_idx Transfer descriptor index
 */
static inline void usb_dwc_hal_xfer_desc_clear(void *desc_list, uint32_t desc_idx)
{
    usb_dwc_ll_dma_qtd_t *qtd_list = (usb_dwc_ll_dma_qtd_t *)desc_list;
    usb_dwc_ll_qtd_set_null(&qtd_list[desc_idx]);
}

/**
 * @brief Parse a transfer decriptor's results
 *
 * @param desc_list Transfer descriptor list
 * @param desc_idx Transfer descriptor index
 * @param[out] xfer_rem_len Remaining length of the transfer in bytes
 * @param[out] xfer_status Status of the transfer
 *
 * @note Critical section is not required for this function
 */
static inline void usb_dwc_hal_xfer_desc_parse(void *desc_list, uint32_t desc_idx, int *xfer_rem_len, int *xfer_status)
{
    usb_dwc_ll_dma_qtd_t *qtd_list = (usb_dwc_ll_dma_qtd_t *)desc_list;
    usb_dwc_ll_qtd_get_status(&qtd_list[desc_idx], xfer_rem_len, xfer_status);
    //Clear the QTD to prevent it from being read again
    usb_dwc_ll_qtd_set_null(&qtd_list[desc_idx]);
}

// ------------------------------------------------- Event Handling ----------------------------------------------------

/**
 * @brief Decode global and host port interrupts
 *
 * - Reads and clears global and host port interrupt registers
 * - Decodes the interrupt bits to determine what host port event occurred
 *
 * @note This should be the first interrupt decode function to be run
 *
 * @param hal Context of the HAL layer
 * @return usb_dwc_hal_port_event_t Host port event
 */
usb_dwc_hal_port_event_t usb_dwc_hal_decode_intr(usb_dwc_hal_context_t *hal);

/**
 * @brief Gets the next channel with a pending interrupt
 *
 * If no channel is pending an interrupt, this function will return NULL. If one or more channels are pending an
 * interrupt, this function returns one of the channel's objects. Call this function repeatedly until it returns NULL.
 *
 * @param hal Context of the HAL layer
 * @return usb_dwc_hal_chan_t* Channel object. NULL if no channel are pending an interrupt.
 */
usb_dwc_hal_chan_t *usb_dwc_hal_get_chan_pending_intr(usb_dwc_hal_context_t *hal);

/**
 * @brief Decode a particular channel's interrupt
 *
 * - Reads and clears the interrupt register of the channel
 * - Returns the corresponding event for that channel
 *
 * @param chan_obj Channel object
 * @note If the host port has an error (e.g., a sudden disconnect or an port error), any active channels will not
 *       receive an interrupt. Each active channel must be manually halted.
 * @return usb_dwc_hal_chan_event_t Channel event
 */
usb_dwc_hal_chan_event_t usb_dwc_hal_chan_decode_intr(usb_dwc_hal_chan_t *chan_obj);

#endif // SOC_USB_OTG_SUPPORTED

#ifdef __cplusplus
}
#endif
