/*
 * SPDX-FileCopyrightText: Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
 * SPDX-FileCopyrightText: Copyright (c) 2021 Ha Thach (tinyusb.org) for Double Buffered
 * SPDX-License-Identifier: MIT
 *
 * This file is part of the TinyUSB stack.
 */

#include "tusb_option.h"

#if CFG_TUH_ENABLED && (CFG_TUSB_MCU == OPT_MCU_RP2040) && !CFG_TUH_RPI_PIO_USB && !CFG_TUH_MAX3421

  #include "pico.h"

  #if defined(PICO_RP2350) && PICO_RP2350 == 1
    #define HAS_STOP_EPX_ON_NAK
  #endif

// port 0 is native USB port, other is counted as software PIO
  #define RHPORT_NATIVE 0

  //--------------------------------------------------------------------+
  // INCLUDE
  //--------------------------------------------------------------------+
  #include "rp2040_usb.h"
  #include "osal/osal.h"

  #include "host/hcd.h"
  #include "host/usbh.h"

//--------------------------------------------------------------------+
//
//--------------------------------------------------------------------+

// Host mode uses one shared endpoint register for non-interrupt endpoint
static hw_endpoint_t  ep_pool[USB_MAX_ENDPOINTS];
static hw_endpoint_t *epx = &ep_pool[0]; // current active endpoint

  #ifndef HAS_STOP_EPX_ON_NAK
static volatile bool epx_switch_request = false;
  #endif

enum {
  SIE_CTRL_SPEED_DISCONNECT = 0,
  SIE_CTRL_SPEED_LOW        = 1,
  SIE_CTRL_SPEED_FULL       = 2,
};

enum {
  EPX_CTRL_DEFAULT = EP_CTRL_ENABLE_BITS | EP_CTRL_INTERRUPT_PER_BUFFER | offsetof(usb_host_dpram_t, epx_data)
};

//--------------------------------------------------------------------+
//
//--------------------------------------------------------------------+

static hw_endpoint_t *edpt_alloc(void) {
  for (uint i = 1; i < TU_ARRAY_SIZE(ep_pool); i++) {
    hw_endpoint_t *ep = &ep_pool[i];
    if (ep->max_packet_size == 0) {
      return ep;
    }
  }
  return NULL;
}

static hw_endpoint_t *edpt_find(uint8_t daddr, uint8_t ep_addr) {
  for (uint32_t i = 0; i < TU_ARRAY_SIZE(ep_pool); i++) {
    hw_endpoint_t *ep = &ep_pool[i];
    if ((ep->dev_addr == daddr) && (ep->max_packet_size > 0) &&
        (ep->ep_addr == ep_addr || (tu_edpt_number(ep_addr) == 0 && tu_edpt_number(ep->ep_addr) == 0))) {
      return ep;
    }
  }

  return NULL;
}

TU_ATTR_ALWAYS_INLINE static inline io_rw_32 *dpram_int_ep_ctrl(uint8_t int_num) {
  return &usbh_dpram->int_ep_ctrl[int_num - 1].ctrl;
}

TU_ATTR_ALWAYS_INLINE static inline io_rw_32 *dpram_int_ep_buffer_ctrl(uint8_t int_num) {
  return &usbh_dpram->int_ep_buffer_ctrl[int_num - 1].ctrl;
}

//--------------------------------------------------------------------+
//
//--------------------------------------------------------------------+

TU_ATTR_ALWAYS_INLINE static inline uint8_t dev_speed(void) {
  return (usb_hw->sie_status & USB_SIE_STATUS_SPEED_BITS) >> USB_SIE_STATUS_SPEED_LSB;
}

TU_ATTR_ALWAYS_INLINE static inline bool need_pre(uint8_t dev_addr) {
  // If this device is different to the speed of the root device
  // (i.e. is a low speed device on a full speed hub) then need pre
  return hcd_port_speed_get(0) != tuh_speed_get(dev_addr);
}

//--------------------------------------------------------------------+
// EPX
//--------------------------------------------------------------------+
TU_ATTR_ALWAYS_INLINE static inline void sie_stop_xfer(void) {
  uint32_t sie_ctrl = (usb_hw->sie_ctrl & SIE_CTRL_BASE_MASK) | USB_SIE_CTRL_STOP_TRANS_BITS;
  usb_hw->sie_ctrl  = sie_ctrl;
  while (usb_hw->sie_ctrl & USB_SIE_CTRL_STOP_TRANS_BITS) {}
}

static void __tusb_irq_path_func(sie_start_xfer)(bool send_setup, bool is_rx, bool need_pre) {
  uint32_t sie_ctrl = usb_hw->sie_ctrl & SIE_CTRL_BASE_MASK; // preserve base bits
  if (send_setup) {
    sie_ctrl |= USB_SIE_CTRL_SEND_SETUP_BITS;
  } else {
    sie_ctrl |= (is_rx ? USB_SIE_CTRL_RECEIVE_DATA_BITS : USB_SIE_CTRL_SEND_DATA_BITS);
  }
  if (need_pre) {
    sie_ctrl |= USB_SIE_CTRL_PREAMBLE_EN_BITS;
  }

  // START_TRANS bit on SIE_CTRL has the same behavior as the AVAILABLE bit
  // described in RP2040 Datasheet, release 2.1, section "4.1.2.5.1. Concurrent access".!
  // We write everything except the START_TRANS bit first, then wait some cycles.
  usb_hw->sie_ctrl = sie_ctrl;
  busy_wait_at_least_cycles(12);
  usb_hw->sie_ctrl = sie_ctrl | USB_SIE_CTRL_START_TRANS_BITS;
}

// prepare epx_ctrl register for new endpoint
TU_ATTR_ALWAYS_INLINE static inline void epx_ctrl_prepare(uint8_t transfer_type) {
  usbh_dpram->epx_ctrl = EPX_CTRL_DEFAULT | ((uint32_t)transfer_type << EP_CTRL_BUFFER_TYPE_LSB);
}

// Save buffer context for EPX preemption (called after STOP_TRANS).
// Undo PID toggle and buffer accounting for buffers NOT completed on the wire.
// A buffer completed on wire means: controller reached STATUS phase (ACK received).
//   OUT completed: FULL cleared to 0 in STATUS phase (was 1 when armed)
//   IN  completed: FULL set to 1 in STATUS phase (was 0 when armed)
// So undo when: AVAIL=1 (never started), or (OUT: FULL=1) or (IN: FULL=0)
static void __tusb_irq_path_func(epx_save_context)(hw_endpoint_t *ep) {
  uint32_t   buf_ctrl = usbh_dpram->epx_buf_ctrl;
  const bool is_out   = (tu_edpt_dir(ep->ep_addr) == TUSB_DIR_OUT);

  do {
    const uint16_t bc16 = (uint16_t)buf_ctrl;
    if (bc16) {
      const bool avail = (bc16 & USB_BUF_CTRL_AVAIL);
      const bool full  = (bc16 & USB_BUF_CTRL_FULL);
      if (avail || (is_out ? full : !full)) {
        const uint16_t buf_len = bc16 & USB_BUF_CTRL_LEN_MASK;
        ep->remaining_len += buf_len;
        ep->next_pid ^= 1u;
        if (is_out) {
          ep->user_buf -= buf_len;
        }
      }
    }

    if (usbh_dpram->epx_ctrl & EP_CTRL_DOUBLE_BUFFERED_BITS) {
      buf_ctrl >>= 16;
    } else {
      buf_ctrl = 0;
    }
  } while (buf_ctrl > 0);

  usbh_dpram->epx_buf_ctrl = 0;

  ep->state = EPSTATE_PENDING;
}

// switch epx to new endpoint and start the transfer
static void __tusb_irq_path_func(epx_switch_ep)(hw_endpoint_t *ep) {
  const bool is_setup = (ep->state == EPSTATE_PENDING_SETUP);

  epx       = ep; // switch pointer
  ep->state = EPSTATE_ACTIVE;

  if (is_setup) {
    // panic("new setup \n");
    usb_hw->dev_addr_ctrl = ep->dev_addr;
    sie_start_xfer(true, false, ep->need_pre);
  } else {
    const bool is_rx   = (tu_edpt_dir(ep->ep_addr) == TUSB_DIR_IN);
    io_rw_32  *ep_reg  = &usbh_dpram->epx_ctrl;
    io_rw_32 *buf_reg = &usbh_dpram->epx_buf_ctrl;

    epx_ctrl_prepare(ep->transfer_type);
    rp2usb_buffer_start(ep, ep_reg, buf_reg, is_rx);

    usb_hw->dev_addr_ctrl = (uint32_t)(ep->dev_addr | (tu_edpt_number(ep->ep_addr) << USB_ADDR_ENDP_ENDPOINT_LSB));
    sie_start_xfer(is_setup, is_rx, ep->need_pre);
  }
}

// Round-robin find next pending ep after current epx
static hw_endpoint_t *__tusb_irq_path_func(epx_next_pending)(hw_endpoint_t *cur_ep) {
  const uint cur_idx = (uint)(cur_ep - &ep_pool[0]);
  for (uint i = cur_idx + 1; i < TU_ARRAY_SIZE(ep_pool); i++) {
    if (ep_pool[i].state >= EPSTATE_PENDING) {
      return &ep_pool[i];
    }
  }
  for (uint i = 0; i < cur_idx; i++) {
    if (ep_pool[i].state >= EPSTATE_PENDING) {
      return &ep_pool[i];
    }
  }
  return NULL;
}


//--------------------------------------------------------------------+
// Interrupt handlers
//--------------------------------------------------------------------+
static void __tusb_irq_path_func(xfer_complete_isr)(hw_endpoint_t *ep, xfer_result_t xfer_result, bool is_more) {
  // Mark transfer as done before we tell the tinyusb stack
  uint32_t xferred_len = ep->xferred_len;
  rp2usb_reset_transfer(ep);
  hcd_event_xfer_complete(ep->dev_addr, ep->ep_addr, xferred_len, xfer_result, true);

  // Carry more transfer on epx
  if (is_more) {
    hw_endpoint_t *next_ep = epx_next_pending(epx);
    if (next_ep != NULL) {
      epx_switch_ep(next_ep);
    }
  }
}

static void __tusb_irq_path_func(handle_buf_status_isr)(void) {
  pico_trace("buf_status 0x%08lx\n", buf_status);
  enum {
    BUF_STATUS_EPX = 1u
  };

  // Check EPX first (bit 0).
  // Double-buffered: if both buffers completed at once, buf_status re-sets
  // immediately after clearing (datasheet Table 406). Process the second buffer too.
  while (usb_hw->buf_status & BUF_STATUS_EPX) {
    const uint8_t buf_id     = (usb_hw->buf_cpu_should_handle & BUF_STATUS_EPX) ? 1 : 0;
    usb_hw_clear->buf_status = 1u; // clear

    io_rw_32 *ep_reg  = &usbh_dpram->epx_ctrl;
    io_rw_32 *buf_reg = &usbh_dpram->epx_buf_ctrl;
  #ifndef HAS_STOP_EPX_ON_NAK
    // Any packet completion (mid-transfer or final) means data is flowing.
    // Clear switch request so the 2-SOF fallback only fires for NAK-retrying endpoints.
    epx_switch_request = false;
  #endif
    if (rp2usb_xfer_continue(epx, ep_reg, buf_reg, buf_id, tu_edpt_dir(epx->ep_addr) == TUSB_DIR_IN)) {
      xfer_complete_isr(epx, XFER_RESULT_SUCCESS, true);
    }
  }

  // Check "interrupt" (asynchronous) endpoints for both IN and OUT
  uint32_t buf_status = usb_hw->buf_status & ~(uint32_t)BUF_STATUS_EPX;
  while (buf_status) {
    // ctz/clz is faster than loop which has only a few bit set in general
    const uint8_t  idx       = (uint8_t)__builtin_ctz(buf_status);
    const uint32_t bit       = TU_BIT(idx);
    usb_hw_clear->buf_status = bit;
    buf_status &= ~bit;

    // IN transfer for even i, OUT transfer for odd i
    // EPX  is bit 0. Bit 1 is not used
    // IEP1 IN/OUT is bit 2, 3
    // IEP2 IN/OUT is bit 4, 5 etc
    const uint8_t epnum = idx >> 1u;
    for (size_t e = 0; e < TU_ARRAY_SIZE(ep_pool); e++) {
      hw_endpoint_t *ep = &ep_pool[e];
      if (ep->interrupt_num == epnum) {
        io_rw_32  *ep_reg  = dpram_int_ep_ctrl(ep->interrupt_num);
        io_rw_32  *buf_reg = dpram_int_ep_buffer_ctrl(ep->interrupt_num);
        const bool done    = rp2usb_xfer_continue(ep, ep_reg, buf_reg, 0, tu_edpt_dir(ep->ep_addr) == TUSB_DIR_IN);
        if (done) {
          xfer_complete_isr(ep, XFER_RESULT_SUCCESS, false);
        }
        break;
      }
    }
  }
}

static void __tusb_irq_path_func(hcd_rp2040_irq)(void) {
  const uint32_t status = usb_hw->ints;

  if (status & USB_INTS_HOST_CONN_DIS_BITS) {
    uint8_t speed = dev_speed();
    if (speed == SIE_CTRL_SPEED_DISCONNECT) {
      hcd_event_device_remove(RHPORT_NATIVE, true);
    } else {
      if (speed == SIE_CTRL_SPEED_LOW) {
        usb_hw->sie_ctrl = SIE_CTRL_BASE | USB_SIE_CTRL_KEEP_ALIVE_EN_BITS;
      } else {
        usb_hw->sie_ctrl = SIE_CTRL_BASE | USB_SIE_CTRL_SOF_EN_BITS;
      }
      hcd_event_device_attach(RHPORT_NATIVE, true);
    }
    usb_hw_clear->sie_status = USB_SIE_STATUS_SPEED_BITS;
  }

  if (status & USB_INTS_STALL_BITS) {
    usb_hw_clear->sie_status = USB_SIE_STATUS_STALL_REC_BITS;
    xfer_complete_isr(epx, XFER_RESULT_STALLED, true);
  }

  if (status & USB_INTS_ERROR_RX_TIMEOUT_BITS) {
    usb_hw_clear->sie_status = USB_SIE_STATUS_RX_TIMEOUT_BITS;

    const uint32_t sie_ctrl = (usb_hw->sie_ctrl & SIE_CTRL_BASE_MASK) | USB_SIE_CTRL_STOP_TRANS_BITS;
    usb_hw->sie_ctrl        = sie_ctrl;
    // while (usb_hw->sie_ctrl & USB_SIE_CTRL_STOP_TRANS_BITS) {}

    // Even if STOP_TRANS bit is clear, controller maybe in middle of retrying and may re-raise timeout once extra time
    // Only handle if epx is active, don't carry more epx transfer since STOP_TRANS is raced and not safe.
    if (epx->state == EPSTATE_ACTIVE) {
      xfer_complete_isr(epx, XFER_RESULT_FAILED, false);
    }
  }

  if (status & USB_INTS_TRANS_COMPLETE_BITS) {
    // only applies for epx, interrupt endpoint does not seem to raise this
    usb_hw_clear->sie_status = USB_SIE_STATUS_TRANS_COMPLETE_BITS;
    if (usb_hw->sie_ctrl & USB_SIE_CTRL_SEND_SETUP_BITS) {
      uint32_t sie_ctrl = usb_hw->sie_ctrl & SIE_CTRL_BASE_MASK;
      usb_hw->sie_ctrl  = sie_ctrl; // clear setup bit
      epx->xferred_len  = 8;
      xfer_complete_isr(epx, XFER_RESULT_SUCCESS, true);
    }
  }

  if (status & USB_INTS_BUFF_STATUS_BITS) {
    handle_buf_status_isr();
  }

  // SOF-based round-robin MUST run BEFORE BUFF_STATUS to avoid processing
  // buf_status on the wrong EPX after a completion+switch in handle_buf_status_isr.
  #ifdef HAS_STOP_EPX_ON_NAK
  if (status & USB_INTS_EPX_STOPPED_ON_NAK_BITS) {
    usb_hw_clear->nak_poll = USB_NAK_POLL_EPX_STOPPED_ON_NAK_BITS;
    hw_endpoint_t *next_ep = epx_next_pending(epx);
    if (next_ep != NULL) {
      epx_save_context(epx);
      epx_switch_ep(next_ep);
    } else {
      usb_hw_clear->nak_poll = USB_NAK_POLL_STOP_EPX_ON_NAK_BITS;
      sie_start_xfer(false, TUSB_DIR_IN == tu_edpt_dir(epx->ep_addr), epx->need_pre);
    }
  }
  #else
  // RP2040: on SOF, switch EPX if another endpoint is pending.
  // First SOF sets epx_switch_request. If a transfer completes before next SOF, the flag is
  // cleared (data is flowing, no need to force-switch). Second SOF with flag still set means
  // no data exchanged (endpoint NAK-retrying): STOP_TRANS is safe and we switch.
  // This avoids stopping mid-data-transfer which corrupts double-buffered PID tracking.
  if (status & USB_INTS_HOST_SOF_BITS) {
    (void)usb_hw->sof_rd; // clear SOF by reading SOF_RD
    hw_endpoint_t *next_ep = epx_next_pending(epx);
    if (next_ep == NULL) {
      usb_hw_clear->inte = USB_INTE_HOST_SOF_BITS;
      usb_hw->nak_poll   = USB_NAK_POLL_RESET;
      epx_switch_request = false;
    } else if (epx->state == EPSTATE_ACTIVE) {
      if (epx_switch_request) {
        // Second SOF with no transfer completion: endpoint is NAK-retrying, safe to switch.
        epx_switch_request = false;
        sie_stop_xfer();
        epx_save_context(epx);
        epx_switch_ep(next_ep);
      } else {
        epx_switch_request = true;
      }
    }
  }
  #endif

  if (status & USB_INTS_ERROR_DATA_SEQ_BITS) {
    usb_hw_clear->sie_status = USB_SIE_STATUS_DATA_SEQ_ERROR_BITS;
    panic("Data Seq Error \n");
  }
}

void __tusb_irq_path_func(hcd_int_handler)(uint8_t rhport, bool in_isr) {
  (void)rhport;
  (void)in_isr;
  hcd_rp2040_irq();
}

//--------------------------------------------------------------------+
// HCD API
//--------------------------------------------------------------------+
bool hcd_init(uint8_t rhport, const tusb_rhport_init_t *rh_init) {
  (void)rhport;
  (void)rh_init;
  pico_trace("hcd_init %d\n", rhport);
  assert(rhport == 0);

  // Reset any previous state
  rp2usb_init();

  // Force VBUS detect to always present, for now we assume vbus is always provided (without using VBUS En)
  usb_hw->pwr = USB_USB_PWR_VBUS_DETECT_BITS | USB_USB_PWR_VBUS_DETECT_OVERRIDE_EN_BITS;

  // Remove shared irq if it was previously added so as not to fill up shared irq slots
  irq_remove_handler(USBCTRL_IRQ, hcd_rp2040_irq);
  irq_add_shared_handler(USBCTRL_IRQ, hcd_rp2040_irq, PICO_SHARED_IRQ_HANDLER_HIGHEST_ORDER_PRIORITY);

  // clear epx and interrupt eps
  memset(&ep_pool, 0, sizeof(ep_pool));

  // Enable in host mode with SOF / Keep alive on
  usb_hw->main_ctrl = USB_MAIN_CTRL_CONTROLLER_EN_BITS | USB_MAIN_CTRL_HOST_NDEVICE_BITS;
  usb_hw->sie_ctrl  = SIE_CTRL_BASE;
  usb_hw->inte      = USB_INTE_BUFF_STATUS_BITS | USB_INTE_HOST_CONN_DIS_BITS | USB_INTE_HOST_RESUME_BITS |
                 USB_INTE_STALL_BITS | USB_INTE_TRANS_COMPLETE_BITS | USB_INTE_ERROR_RX_TIMEOUT_BITS |
                 USB_INTE_ERROR_DATA_SEQ_BITS;

  #ifdef HAS_STOP_EPX_ON_NAK
  usb_hw_set->inte = USB_INTE_EPX_STOPPED_ON_NAK_BITS;
  #endif

  return true;
}

bool hcd_deinit(uint8_t rhport) {
  (void)rhport;
  irq_remove_handler(USBCTRL_IRQ, hcd_rp2040_irq);
  reset_block(RESETS_RESET_USBCTRL_BITS);
  unreset_block_wait(RESETS_RESET_USBCTRL_BITS);
  return true;
}

void hcd_port_reset(uint8_t rhport) {
  (void)rhport;
  // TODO: Nothing to do here yet. Perhaps need to reset some state?
}

void hcd_port_reset_end(uint8_t rhport) {
  (void)rhport;
}

bool hcd_port_connect_status(uint8_t rhport) {
  (void)rhport;
  return usb_hw->sie_status & USB_SIE_STATUS_SPEED_BITS;
}

tusb_speed_t hcd_port_speed_get(uint8_t rhport) {
  (void)rhport;
  switch (dev_speed()) {
    case SIE_CTRL_SPEED_LOW:
      return TUSB_SPEED_LOW;
    case SIE_CTRL_SPEED_FULL:
      return TUSB_SPEED_FULL;
    default:
      return TUSB_SPEED_INVALID;
  }
}

// Close all opened endpoint belong to this device
void hcd_device_close(uint8_t rhport, uint8_t dev_addr) {
  (void)rhport;

  if (dev_addr == 0) {
    return; // address 0 is for device enumeration
  }

  rp2usb_critical_enter();

  for (size_t i = 0; i < TU_ARRAY_SIZE(ep_pool); i++) {
    hw_endpoint_t *ep = &ep_pool[i];
    if (ep->dev_addr == dev_addr && ep->max_packet_size > 0) {
      ep->state = EPSTATE_IDLE; // clear any pending transfer

      if (ep->interrupt_num > 0) {
        // disable interrupt endpoint
        usb_hw_clear->int_ep_ctrl                       = TU_BIT(ep->interrupt_num);
        usb_hw->int_ep_addr_ctrl[ep->interrupt_num - 1] = 0;

        io_rw_32 *ep_reg  = dpram_int_ep_ctrl(ep->interrupt_num);
        io_rw_32 *buf_reg = dpram_int_ep_buffer_ctrl(ep->interrupt_num);
        *buf_reg          = 0;
        *ep_reg           = 0;
      }

      ep->max_packet_size = 0; // mark as unused
    }
  }

  rp2usb_critical_exit();
}

uint32_t hcd_frame_number(uint8_t rhport) {
  (void)rhport;
  return usb_hw->sof_rd;
}

void hcd_int_enable(uint8_t rhport) {
  (void)rhport;
  irq_set_enabled(USBCTRL_IRQ, true);
}

void hcd_int_disable(uint8_t rhport) {
  (void)rhport;
  // todo we should check this is disabling from the correct core; note currently this is never called
  irq_set_enabled(USBCTRL_IRQ, false);
}

//--------------------------------------------------------------------+
// Endpoint API
//--------------------------------------------------------------------+
bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, const tusb_desc_endpoint_t *ep_desc) {
  (void)rhport;
  pico_trace("hcd_edpt_open dev_addr %d, ep_addr %d\n", dev_addr, ep_desc->bEndpointAddress);
  hw_endpoint_t *ep;
  if (dev_addr == 0) {
    ep = &ep_pool[0];
  } else {
    ep = edpt_alloc();
  }
  TU_ASSERT(ep);

  const uint8_t  ep_addr         = ep_desc->bEndpointAddress;
  const uint16_t max_packet_size = tu_edpt_packet_size(ep_desc);

  ep->max_packet_size = max_packet_size;
  ep->ep_addr         = ep_addr;
  ep->dev_addr        = dev_addr;
  ep->transfer_type   = ep_desc->bmAttributes.xfer;
  ep->need_pre        = need_pre(dev_addr);
  ep->next_pid        = 0u;

  if (ep->transfer_type != TUSB_XFER_INTERRUPT) {
    ep->dpram_buf = usbh_dpram->epx_data;
  } else {
    // from 15 interrupt endpoints pool
    uint8_t int_idx;
    for (int_idx = 0; int_idx < USB_HOST_INTERRUPT_ENDPOINTS; int_idx++) {
      if (!tu_bit_test(usb_hw->int_ep_ctrl, 1 + int_idx)) {
        ep->interrupt_num = int_idx + 1;
        break;
      }
    }
    assert(int_idx < USB_HOST_INTERRUPT_ENDPOINTS);
    assert(ep_desc->bInterval > 0);

    //------------- dpram buf -------------//
    // 15x64 last bytes of DPRAM for interrupt endpoint buffers
    ep->dpram_buf    = (uint8_t *)(USBCTRL_DPRAM_BASE + USB_DPRAM_MAX - (int_idx + 1u) * 64u);
    uint32_t ep_ctrl = EP_CTRL_ENABLE_BITS | EP_CTRL_INTERRUPT_PER_BUFFER |
                       (TUSB_XFER_INTERRUPT << EP_CTRL_BUFFER_TYPE_LSB) | hw_data_offset(ep->dpram_buf) |
                       ((uint32_t)(ep_desc->bInterval - 1) << EP_CTRL_HOST_INTERRUPT_INTERVAL_LSB);
    usbh_dpram->int_ep_ctrl[int_idx].ctrl = ep_ctrl;

    //------------- address control -------------//
    const uint8_t epnum     = tu_edpt_number(ep_addr);
    uint32_t      addr_ctrl = (uint32_t)(dev_addr | (epnum << USB_ADDR_ENDP1_ENDPOINT_LSB));
    if (tu_edpt_dir(ep_addr) == TUSB_DIR_OUT) {
      addr_ctrl |= USB_ADDR_ENDP1_INTEP_DIR_BITS;
    }
    if (ep->need_pre) {
      addr_ctrl |= USB_ADDR_ENDP1_INTEP_PREAMBLE_BITS;
    }
    usb_hw->int_ep_addr_ctrl[int_idx] = addr_ctrl;

    // Finally, activate interrupt endpoint
    usb_hw_set->int_ep_ctrl = TU_BIT(ep->interrupt_num);
  }

  return true;
}

bool hcd_edpt_close(uint8_t rhport, uint8_t daddr, uint8_t ep_addr) {
  (void)rhport;
  (void)daddr;
  (void)ep_addr;
  return false; // TODO not implemented yet
}

bool hcd_edpt_abort_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) {
  (void)rhport;
  (void)dev_addr;
  (void)ep_addr;
  // TODO not implemented yet
  return false;
}

bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t *buffer, uint16_t buflen) {
  (void)rhport;

  hw_endpoint_t *ep = edpt_find(dev_addr, ep_addr);
  TU_ASSERT(ep);

  if (ep->interrupt_num > 0) {
    // For interrupt endpoint control and buffer is already configured
    // Note: Interrupt is single buffered only
    io_rw_32 *ep_reg  = dpram_int_ep_ctrl(ep->interrupt_num);
    io_rw_32 *buf_reg = dpram_int_ep_buffer_ctrl(ep->interrupt_num);
    rp2usb_xfer_start(ep, ep_reg, buf_reg, buffer, NULL, buflen);
  } else {
    // Control transfer data and status stages always start with DATA1, regardless of
    // whether the direction changed since the previous stage. SET_REPORT (and any other
    // host-to-device class request with an OUT data stage) keeps the same direction
    // across SETUP -> DATA, so we cannot key off "direction changed" -- we must reset
    // next_pid every time hcd_edpt_xfer is invoked on ep 0. Without this, the data stage
    // of SET_REPORT goes out as DATA0 because ep->next_pid is still 0 from hcd_edpt_open(),
    // which strict devices treat as a protocol violation and disconnect.
    if (tu_edpt_number(ep_addr) == 0) {
      ep->ep_addr  = ep_addr;
      ep->next_pid = 1;
    }

    // If EPX is busy with another transfer, mark as pending
    rp2usb_critical_enter();
    if (epx->state == EPSTATE_ACTIVE) {
      ep->user_buf      = buffer;
      ep->remaining_len = buflen;
      ep->state         = EPSTATE_PENDING;

  #ifdef HAS_STOP_EPX_ON_NAK
      usb_hw_set->nak_poll = USB_NAK_POLL_STOP_EPX_ON_NAK_BITS;
  #else
      // Only enable SOF round-robin for non-control endpoints
      usb_hw->nak_poll = (300 << USB_NAK_POLL_DELAY_FS_LSB) | (300 << USB_NAK_POLL_DELAY_LS_LSB);
      usb_hw_set->inte = USB_INTE_HOST_SOF_BITS;
  #endif
    } else {
      io_rw_32 *ep_reg  = &usbh_dpram->epx_ctrl;
      io_rw_32 *buf_reg = &usbh_dpram->epx_buf_ctrl;

      epx = ep;

      epx_ctrl_prepare(ep->transfer_type);
      rp2usb_xfer_start(ep, ep_reg, buf_reg, buffer, NULL, buflen); // prepare bufctrl
      usb_hw->dev_addr_ctrl = (uint32_t)(ep->dev_addr | (tu_edpt_number(ep->ep_addr) << USB_ADDR_ENDP_ENDPOINT_LSB));
      sie_start_xfer(false, tu_edpt_dir(ep->ep_addr) == TUSB_DIR_IN, ep->need_pre);
    }
    rp2usb_critical_exit();
  }

  return true;
}

bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, const uint8_t setup_packet[8]) {
  (void)rhport;

  hw_endpoint_t *ep = edpt_find(dev_addr, 0x00);
  TU_ASSERT(ep);

  rp2usb_critical_enter();

  // Copy data into setup packet buffer (usbh only schedules one setup at a time)
  for (uint8_t i = 0; i < 8; i++) {
    usbh_dpram->setup_packet[i] = setup_packet[i];
  }

  ep->ep_addr       = 0; // setup is OUT
  ep->remaining_len = 8;
  ep->xferred_len   = 0;

  // If EPX is busy, mark as pending setup (DPRAM already has the packet)
  if (epx->state == EPSTATE_ACTIVE) {
    ep->state = EPSTATE_PENDING_SETUP;
  #ifdef HAS_STOP_EPX_ON_NAK
    usb_hw_set->nak_poll = USB_NAK_POLL_STOP_EPX_ON_NAK_BITS;
  #else
    usb_hw->nak_poll = (300 << USB_NAK_POLL_DELAY_FS_LSB) | (300 << USB_NAK_POLL_DELAY_LS_LSB);
    usb_hw_set->inte = USB_INTE_HOST_SOF_BITS;
  #endif
  } else {
    epx       = ep;
    ep->state = EPSTATE_ACTIVE;

    usb_hw->dev_addr_ctrl = ep->dev_addr;
    sie_start_xfer(true, tu_edpt_dir(ep->ep_addr) == TUSB_DIR_IN, ep->need_pre);
  }

  rp2usb_critical_exit();
  return true;
}

bool hcd_edpt_clear_stall(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) {
  (void)rhport;
  (void)dev_addr;
  (void)ep_addr;

  panic("hcd_clear_stall");
  // return true;
}

#endif
