This is an automated email from the ASF dual-hosted git repository.

xiaoxiang pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nuttx.git

commit 532fede38b2cd4caf448a3d2ddbffb7cfe1493ec
Author: daniellizewski <[email protected]>
AuthorDate: Thu Apr 9 13:49:50 2026 -0400

    arch/arm/src/stm32h5: Add support for USB host
    
    Added stm32_usbdrdhost.c which adds USB FS host mode support using the
    embedded PHY. Added Kconfig to select Device or Host.
    
    Example config nucleo-h563zi:usbmsc added to test functionality
    
    Signed-off-by: daniellizewski <[email protected]>
---
 Documentation/platforms/arm/stm32h5/index.rst      |   22 +-
 arch/arm/src/stm32h5/CMakeLists.txt                |    4 +
 arch/arm/src/stm32h5/Kconfig                       |   46 +-
 arch/arm/src/stm32h5/Make.defs                     |    4 +
 arch/arm/src/stm32h5/hardware/stm32_usbfs.h        |  117 +-
 arch/arm/src/stm32h5/stm32_usbdrdhost.c            | 2799 ++++++++++++++++++++
 arch/arm/src/stm32h5/stm32_usbdrdhost.h            |  144 +
 arch/arm/src/stm32h5/stm32h5xx_rcc.c               |    2 +-
 .../stm32h5/nucleo-h563zi/configs/usbmsc/defconfig |   57 +
 boards/arm/stm32h5/nucleo-h563zi/include/board.h   |   37 +-
 .../arm/stm32h5/nucleo-h563zi/src/CMakeLists.txt   |    4 +
 boards/arm/stm32h5/nucleo-h563zi/src/Makefile      |    4 +
 .../arm/stm32h5/nucleo-h563zi/src/nucleo-h563zi.h  |    4 +
 .../arm/stm32h5/nucleo-h563zi/src/stm32_bringup.c  |    9 +
 boards/arm/stm32h5/nucleo-h563zi/src/stm32_usb.c   |  226 ++
 15 files changed, 3468 insertions(+), 11 deletions(-)

diff --git a/Documentation/platforms/arm/stm32h5/index.rst 
b/Documentation/platforms/arm/stm32h5/index.rst
index 344d51c88a2..882a75ea5e4 100644
--- a/Documentation/platforms/arm/stm32h5/index.rst
+++ b/Documentation/platforms/arm/stm32h5/index.rst
@@ -49,7 +49,7 @@ OCTOSPI     Yes      Implemented as QSPI.
 PWR         Yes      Partial.
 SPI         Yes
 TIM         Yes
-USB_FS      Yes      USB Device Support.
+USB_FS      Yes      USB Device and Host Support.
 
 AES         No
 CEC         No
@@ -86,6 +86,26 @@ WWDG        No
 
 ==========  =======  =====
 
+USB FS Host
+-----------
+
+STM32 USB FS Host Driver Support. The STM32H5 is equipped with a Dual Role USB 
device
+capable of operating as a device or host. 
+
+Pre-requisites:
+
+- CONFIG_USBHOST         - Enable USB host support
+- CONFIG_STM32H5_USBFS_HOST  - Enable the STM32 USB OTG FS block in host mode
+
+USB host requires a stable 48MHz clock. This should come from a PLL driven by 
the HSE.
+HSI48 cannot be reliably used in host mode due to drift. It can only be used 
in device mode.
+
+Options:
+
+- STM32H5_USBDRD_NCHANNELS - Number of host channels. Default 8
+
+- STM32H5_USBDRD_DESCSIZE - Maximum size of a descriptor.  Default: 128
+
 References
 =================
 [RM0481] Reference Manual: STM32H523/33xx, STM32H562/63xx, and STM32H573xx 
ArmĀ® -based 32-bit MCUs
diff --git a/arch/arm/src/stm32h5/CMakeLists.txt 
b/arch/arm/src/stm32h5/CMakeLists.txt
index f6e4f4e493b..23875324447 100644
--- a/arch/arm/src/stm32h5/CMakeLists.txt
+++ b/arch/arm/src/stm32h5/CMakeLists.txt
@@ -96,6 +96,10 @@ if(CONFIG_STM32H5_USBFS)
   list(APPEND SRCS stm32_usbfs.c)
 endif()
 
+if(CONFIG_STM32H5_USBFS_HOST)
+  list(APPEND SRCS stm32_usbdrdhost.c)
+endif()
+
 if(CONFIG_STM32H5_ETHMAC)
   list(APPEND SRCS stm32_ethernet.c)
 endif()
diff --git a/arch/arm/src/stm32h5/Kconfig b/arch/arm/src/stm32h5/Kconfig
index 23d72b0a3b9..a7c0eaeba0c 100644
--- a/arch/arm/src/stm32h5/Kconfig
+++ b/arch/arm/src/stm32h5/Kconfig
@@ -651,12 +651,30 @@ config STM32H5_TIM17
 
 endmenu # STM32H5 Timer Selection
 
+choice STM32H5_USBFS_MODE
+       prompt "USB FS Mode"
+       depends on STM32H5_HAVE_USBFS
+       default STM32H5_USBFS_NONE
+       ---help---
+         Select the operating mode for the USB_DRD_FS peripheral.
+         The hardware supports Device or Host, but not simultaneously.
+
+config STM32H5_USBFS_NONE
+       bool "Disabled"
+
 config STM32H5_USBFS
        bool "USB Device"
-       default n
-       depends on STM32H5_HAVE_USBFS
        select USBDEV
 
+config STM32H5_USBFS_HOST
+       bool "USB Host"
+       select USBHOST_HAVE_ASYNCH
+       select USBHOST
+       ---help---
+         Enable USB host mode for USB_DRD_FS peripheral.
+
+endchoice
+
 endmenu # STM32H5 Peripheral Selection
 
 menu "DTS Configuration"
@@ -4473,7 +4491,7 @@ endmenu # Timer Configuration
 
 comment "USB Device Configuration"
 
-menu "USB Full Speed Debug Configuration"
+menu "USB Full Speed Device Configuration"
        depends on STM32H5_USBFS
 
 config STM32H5_USBFS_REGDEBUG
@@ -4485,6 +4503,28 @@ config STM32H5_USBFS_REGDEBUG
 
 endmenu
 
+comment "USB Host Configuration"
+
+menu "USB Full Speed Host Configuration"
+       depends on STM32H5_USBFS_HOST
+
+config STM32H5_USBDRD_NCHANNELS
+       int "Number of host channels"
+       default 8
+       range 1 8
+       depends on STM32H5_USBFS_HOST
+       ---help---
+               Number of USB host channels to use.
+
+config STM32H5_USBDRD_DESCSIZE
+       int "Descriptor buffer size"
+       default 128
+       depends on STM32H5_USBFS_HOST
+       ---help---
+               Size of descriptor/request buffers.
+
+endmenu
+
 config STM32H5_SERIALDRIVER
        bool
 
diff --git a/arch/arm/src/stm32h5/Make.defs b/arch/arm/src/stm32h5/Make.defs
index 9b065074050..55c8db2ee60 100644
--- a/arch/arm/src/stm32h5/Make.defs
+++ b/arch/arm/src/stm32h5/Make.defs
@@ -92,6 +92,10 @@ ifeq ($(CONFIG_STM32H5_USBFS),y)
 CHIP_CSRCS += stm32_usbfs.c
 endif
 
+ifeq ($(CONFIG_STM32H5_USBFS_HOST),y)
+CHIP_CSRCS += stm32_usbdrdhost.c
+endif
+
 ifeq ($(CONFIG_STM32H5_ETHMAC),y)
 CHIP_CSRCS += stm32_ethernet.c
 endif
diff --git a/arch/arm/src/stm32h5/hardware/stm32_usbfs.h 
b/arch/arm/src/stm32h5/hardware/stm32_usbfs.h
index 24bd3a13bcb..b4d800c986e 100644
--- a/arch/arm/src/stm32h5/hardware/stm32_usbfs.h
+++ b/arch/arm/src/stm32h5/hardware/stm32_usbfs.h
@@ -151,6 +151,9 @@
 #define USB_CNTR_ERRM                  (1 << 13) /* Bit 13: Error Interrupt 
Mask */
 #define USB_CNTR_PMAOVRN               (1 << 14) /* Bit 14: Packet Memory Area 
Over / Underrun Interrupt Mask */
 #define USB_CNTR_CTRM                  (1 << 15) /* Bit 15: Correct Transfer 
Interrupt Mask */
+#define USB_CNTR_THR512M               (1 << 16) /* Bit 16: 512byte Threshold 
interrupt mask */
+#define USB_CNTR_DDISCM                (1 << 17) /* Bit 17: Device 
disconnection mask */
+#define USB_CNTR_HOST                  (1 << 31) /* Bit 31: Host Mode */
 
 #define USB_CNTR_ALLINTS               
(USB_CNTR_L1REQ|USB_CNTR_ESOFM|USB_CNTR_SOFM|USB_CNTR_RESETM|\
                                         
USB_CNTR_SUSPM|USB_CNTR_WKUPM|USB_CNTR_ERRM|USB_CNTR_PMAOVRN|\
@@ -164,12 +167,16 @@
 #define USB_ISTR_L1REQ                 (1 << 7)  /* Bit 7:  LPM L1 state 
request */
 #define USB_ISTR_ESOF                  (1 << 8)  /* Bit 8:  Expected Start Of 
Frame */
 #define USB_ISTR_SOF                   (1 << 9)  /* Bit 9:  Start Of Frame */
-#define USB_ISTR_RESET                 (1 << 10) /* Bit 10: USB RESET request 
*/
+#define USB_ISTR_RESET                 (1 << 10) /* Bit 10: USB RESET request. 
Device connection in Host mode */
 #define USB_ISTR_SUSP                  (1 << 11) /* Bit 11: Suspend mode 
request */
 #define USB_ISTR_WKUP                  (1 << 12) /* Bit 12: Wake up */
 #define USB_ISTR_ERR                   (1 << 13) /* Bit 13: Error */
 #define USB_ISTR_PMAOVRN               (1 << 14) /* Bit 14: Packet Memory Area 
Over / Underrun */
 #define USB_ISTR_CTR                   (1 << 15) /* Bit 15: Correct Transfer */
+#define USB_ISTR_THR512                (1 << 16) /* Bit 16: 512byte threshold 
interrupt */
+#define USB_ISTR_DDISC                 (1 << 17) /* Bit 17: Device 
disconnection */
+#define USB_ISTR_DCON_STAT             (1 << 29) /* Bit 29: Device connection 
status */
+#define USB_ISTR_LS_DCONN              (1 << 30) /* Bit 30: Low-speed device 
connected */
 
 #define USB_ISTR_ALLINTS               
(USB_ISTR_L1REQ|USB_ISTR_ESOF|USB_ISTR_SOF|USB_ISTR_RESET|\
                                         
USB_ISTR_SUSP|USB_ISTR_WKUP|USB_ISTR_ERR|USB_ISTR_PMAOVRN|\
@@ -210,18 +217,118 @@
 #define USB_BCDR_SDET                  (1 << 6)  /* Bit 6:  Secondary 
detection (SD) status */
 #define USB_BCDR_PS2DET                (1 << 7)  /* Bit 7:  DM pull-up 
detection status */
 #define USB_BCDR_DPPU                  (1 << 15) /* Bit 15: DP pull-up control 
*/
+#define USB_BCDR_DPPD                  (1 << 15) /* Bit 15: DP pull-down 
control (host mode) */
+
+/****************************************************************************
+ * USB DRD Host Mode Register Definitions
+ ****************************************************************************/
+
+/* Channel/Endpoint register */
+
+#define USB_CHEP_ADDR_SHIFT            (0)       /* Bits 3-0: Endpoint address 
*/
+#define USB_CHEP_ADDR_MASK             (0xf << USB_CHEP_ADDR_SHIFT)
+#define USB_CHEP_TX_STTX_SHIFT         (4)       /* Bits 5-4: Status TX */
+#define USB_CHEP_TX_STTX_MASK          (3 << USB_CHEP_TX_STTX_SHIFT)
+#  define USB_CHEP_TX_STTX_DIS         (0 << USB_CHEP_TX_STTX_SHIFT) /* 
Channel TX disabled */
+#  define USB_CHEP_TX_STTX_STALL       (1 << USB_CHEP_TX_STTX_SHIFT) /* 
Channel TX stalled */
+#  define USB_CHEP_TX_STTX_NAK         (2 << USB_CHEP_TX_STTX_SHIFT) /* 
Channel TX NAK */
+#  define USB_CHEP_TX_STTX_VALID       (3 << USB_CHEP_TX_STTX_SHIFT) /* 
Channel TX valid */
+#  define USB_CHEP_TX_DTOG1            (1 << USB_CHEP_TX_STTX_SHIFT) /* TX 
Data Toggle bit 1 */
+#  define USB_CHEP_TX_DTOG2            (2 << USB_CHEP_TX_STTX_SHIFT) /* TX 
Data Toggle bit 2 */
+
+#define USB_CHEP_DTOG_TX               (1 << 6)  /* Bit 6: Data Toggle TX */
+#define USB_CHEP_VTTX                  (1 << 7)  /* Bit 7: Valid transaction 
transmitted */
+#define USB_CHEP_KIND                  (1 << 8)  /* Bit 8: Endpoint/Channel 
KIND */
+#define USB_CHEP_UTYPE_SHIFT           (9)       /* Bits 10-9: USB type of 
transaction */
+#define USB_CHEP_UTYPE_MASK            (3 << USB_CHEP_UTYPE_SHIFT)
+#  define USB_CHEP_UTYPE_BULK          (0 << USB_CHEP_UTYPE_SHIFT) /* Bulk 
transfer */
+#  define USB_CHEP_UTYPE_CTRL          (1 << USB_CHEP_UTYPE_SHIFT) /* Control 
transfer */
+#  define USB_CHEP_UTYPE_ISOC          (2 << USB_CHEP_UTYPE_SHIFT) /* 
Isochronous transfer */
+#  define USB_CHEP_UTYPE_INTR          (3 << USB_CHEP_UTYPE_SHIFT) /* 
Interrupt transfer */
+
+#define USB_CHEP_SETUP                 (1 << 11) /* Bit 11: Setup transaction 
completed */
+#define USB_CHEP_RX_STRX_SHIFT         (12)      /* Bits 13-12: Status RX */
+#define USB_CHEP_RX_STRX_MASK          (3 << USB_CHEP_RX_STRX_SHIFT)
+#  define USB_CHEP_RX_STRX_DIS         (0 << USB_CHEP_RX_STRX_SHIFT) /* 
Channel RX disabled */
+#  define USB_CHEP_RX_STRX_STALL       (1 << USB_CHEP_RX_STRX_SHIFT) /* 
Channel RX stalled */
+#  define USB_CHEP_RX_STRX_NAK         (2 << USB_CHEP_RX_STRX_SHIFT) /* 
Channel RX NAK */
+#  define USB_CHEP_RX_STRX_VALID       (3 << USB_CHEP_RX_STRX_SHIFT) /* 
Channel RX valid */
+#  define USB_CHEP_RX_DTOG1            (1 << USB_CHEP_RX_STRX_SHIFT) /* RX 
Data Toggle bit 1 */
+#  define USB_CHEP_RX_DTOG2            (2 << USB_CHEP_RX_STRX_SHIFT) /* RX 
Data Toggle bit 2 */
+
+#define USB_CHEP_DTOG_RX               (1 << 14) /* Bit 14: Data Toggle RX */
+#define USB_CHEP_VTRX                  (1 << 15) /* Bit 15: Valid transaction 
received */
+#define USB_CHEP_DEVADDR_SHIFT         (16)      /* Bits 22-16: Target device 
address */
+#define USB_CHEP_DEVADDR_MASK          (0x7f << USB_CHEP_DEVADDR_SHIFT)
+#define USB_CHEP_NAK                   (1 << 23) /* Bit 23: Previous NAK 
detected */
+#define USB_CHEP_LSEP                  (1 << 24) /* Bit 24: Low Speed Endpoint 
*/
+#define USB_CHEP_ERRTX                 (1 << 25) /* Bit 25: Transmit error */
+#define USB_CHEP_ERRRX                 (1 << 26) /* Bit 26: Receive error */
+
+/* Register mask for preserving r/w bits when modifying toggle bits */
+
+#define USB_CHEP_REG_MASK              (USB_CHEP_ERRRX | USB_CHEP_ERRTX | \
+                                        USB_CHEP_LSEP | USB_CHEP_DEVADDR_MASK 
| \
+                                        USB_CHEP_VTRX | USB_CHEP_SETUP | \
+                                        USB_CHEP_UTYPE_MASK | USB_CHEP_KIND | \
+                                        USB_CHEP_VTTX | USB_CHEP_ADDR_MASK | \
+                                        USB_CHEP_NAK)
+
+#define USB_CHEP_TX_DTOGMASK           (USB_CHEP_TX_STTX_MASK | 
USB_CHEP_REG_MASK)
+#define USB_CHEP_RX_DTOGMASK           (USB_CHEP_RX_STRX_MASK | 
USB_CHEP_REG_MASK)
+#define USB_CH_T_MASK                  ((~USB_CHEP_UTYPE_MASK) & 
USB_CHEP_REG_MASK)
+#define USB_CHEP_DB_MSK                (0xffff0f0f)
+
+/* PMA (Packet Memory Area) definitions for host mode */
+
+#define USB_DRD_PMA_SIZE               (2048)     /* 2KB PMA */
+#define USB_DRD_NCHANNELS              (8)        /* 8 host channels */
+
+/* PMA buffer descriptor structure address calculation
+ * Each channel has TX and RX buffer descriptors (8 bytes total per channel)
+ */
+
+#define USB_PMA_TXBD_OFFSET(ch)        ((ch) * 8)
+#define USB_PMA_RXBD_OFFSET(ch)        ((ch) * 8 + 4)
+
+/* PMA TX buffer descriptor bit definitions */
+
+#define USB_PMA_TXBD_ADDR_SHIFT        (2)        /* Bits 15:2: TX buffer 
address */
+#define USB_PMA_TXBD_ADDR_MASK         (0x3fff << USB_PMA_TXBD_ADDR_SHIFT)
+#define USB_PMA_TXBD_COUNT_SHIFT       (16)       /* Bits 25:16: TX byte count 
*/
+#define USB_PMA_TXBD_COUNT_MASK        (0x3ff << USB_PMA_TXBD_COUNT_SHIFT)
+#define USB_PMA_TXBD_ADDMSK            (0xffff0000)
+#define USB_PMA_TXBD_COUNTMSK          (0x0000ffff)
+
+/* PMA RX buffer descriptor bit definitions */
+
+#define USB_PMA_RXBD_ADDR_SHIFT        (2)        /* Bits 15-2: RX buffer 
address */
+#define USB_PMA_RXBD_ADDR_MASK         (0x3fff << USB_PMA_RXBD_ADDR_SHIFT)
+#define USB_PMA_RXBD_COUNT_SHIFT       (16)       /* Bits 25-16: RX byte count 
*/
+#define USB_PMA_RXBD_COUNT_MASK        (0x3ff << USB_PMA_RXBD_COUNT_SHIFT)
+#define USB_PMA_RXBD_NUM_BLOCK_SHIFT   (26)      /* Bits 30-26: Number of 
blocks */
+#define USB_PMA_RXBD_NUM_BLOCK_MASK    (0x1f << USB_PMA_RXBD_NUM_BLOCK_SHIFT)
+#define USB_PMA_RXBD_BLSIZE            (1 << 31) /* Bit 31: Block size: 
0=2bytes, 1=32bytes */
+#define USB_PMA_RXBD_ADDMSK            (0xffff0000)
+
+/* PMA start address (after buffer descriptor table)
+ * BDT size = 8 channels * 8 bytes = 64 bytes, aligned to 64
+ */
+
+#define USB_DRD_PMA_BDT_SIZE           (USB_DRD_NCHANNELS * 8)
+#define USB_DRD_PMA_START_ADDR         (USB_DRD_PMA_BDT_SIZE)
 
 /* Reception buffer address */
 
-#define USB_ADDR_RX_SHIFT              (2)       /* Bits 15:2 ADDRn_RX[15:2]: 
Reception Buffer Address */
+#define USB_ADDR_RX_SHIFT              (2)       /* Bits 15-2: Reception 
Buffer Address */
 #define USB_ADDR_RX_MASK               (0x3fff << USB_ADDR_RX_SHIFT)
 
 /* Reception byte count */
 
-#define USB_COUNT_RX_BL_SIZE           (1 << 31) /* Bit 15: BLock SIZE. */
-#define USB_COUNT_RX_NUM_BLOCK_SHIFT   (26)      /* Bits 14-10: Number of 
blocks */
+#define USB_COUNT_RX_BL_SIZE           (1 << 31) /* Bit 31: Block size: 
0=2bytes, 1=32bytes */
+#define USB_COUNT_RX_NUM_BLOCK_SHIFT   (26)      /* Bits 30-26: Number of 
blocks */
 #define USB_COUNT_RX_NUM_BLOCK_MASK    (0x1f << USB_COUNT_RX_NUM_BLOCK_SHIFT)
-#define USB_COUNT_RX_SHIFT             (16)       /* Bits 9-0: Reception Byte 
Count */
+#define USB_COUNT_RX_SHIFT             (16)      /* Bits 25-16: Reception Byte 
Count */
 #define USB_COUNT_RX_MASK              (0x3ff << USB_COUNT_RX_SHIFT)
 
 #endif /* CONFIG_STM32H5_HAVE_USBFS */
diff --git a/arch/arm/src/stm32h5/stm32_usbdrdhost.c 
b/arch/arm/src/stm32h5/stm32_usbdrdhost.c
new file mode 100644
index 00000000000..a562046dc1b
--- /dev/null
+++ b/arch/arm/src/stm32h5/stm32_usbdrdhost.c
@@ -0,0 +1,2799 @@
+/****************************************************************************
+ * arch/arm/src/stm32h5/stm32_usbdrdhost.c
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.  The
+ * ASF licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ ****************************************************************************/
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include <sys/types.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <errno.h>
+#include <debug.h>
+
+#include <nuttx/arch.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/clock.h>
+#include <nuttx/signal.h>
+#include <nuttx/mutex.h>
+#include <nuttx/semaphore.h>
+#include <nuttx/usb/usb.h>
+
+#include <arch/barriers.h>
+#include <nuttx/usb/usbhost.h>
+#include <nuttx/usb/usbhost_devaddr.h>
+#include <nuttx/usb/usbhost_trace.h>
+
+#include <nuttx/irq.h>
+
+#include "chip.h"
+#include <arch/board/board.h>
+
+#include "arm_internal.h"
+#include "stm32_gpio.h"
+#include "stm32_rcc.h"
+#include "hardware/stm32_pinmap.h"
+#include "hardware/stm32_usbfs.h"
+#include "hardware/stm32h5xxx_rcc.h"
+#include "hardware/stm32h5xxx_pwr.h"
+#include "stm32_usbdrdhost.h"
+
+#if defined(CONFIG_USBHOST) && defined(CONFIG_STM32H5_USBFS_HOST)
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* Configuration */
+
+#ifndef CONFIG_STM32H5_USBDRD_NCHANNELS
+#  define CONFIG_STM32H5_USBDRD_NCHANNELS 8
+#endif
+
+#ifndef CONFIG_STM32H5_USBDRD_DESCSIZE
+#  define CONFIG_STM32H5_USBDRD_DESCSIZE 128
+#endif
+
+#ifndef CONFIG_STM32H5_USBDRD_TRANSFER_TIMEOUT
+#  define CONFIG_STM32H5_USBDRD_TRANSFER_TIMEOUT 5000
+#endif
+
+/* Hardware definitions */
+
+#define STM32H5_NHOST_CHANNELS    CONFIG_STM32H5_USBDRD_NCHANNELS
+#define STM32H5_EP0_MAX_PACKET_SIZE 64
+#define STM32H5_RETRY_COUNT       3   /* Control transfer retries */
+
+/* PMA Buffer allocation (fixed-size bitmap allocator) */
+
+#define STM32H5_PMA_BUFFER_SIZE        64          /* Fixed buffer size 
(bytes) */
+#define STM32H5_PMA_NBUFFERS           30          /* Total allocatable 
buffers */
+#define STM32H5_PMA_BUFFER_ALLSET      0x3fffffff  /* All 30 buffers available 
*/
+#define STM32H5_PMA_BUFFER_BIT(bn)     (1U << (bn))
+#define STM32H5_PMA_BUFNO2ADDR(bn)     (USB_DRD_PMA_START_ADDR + ((bn) * 
STM32H5_PMA_BUFFER_SIZE))
+#define STM32H5_PMA_BUFFER_NONE        0xFF   /* Invalid buffer number */
+
+/* Delays */
+
+#define STM32H5_DATANAK_DELAY     SEC2TICK(5)
+#define STM32H5_RESET_DELAY       100 /* ms */
+
+/* USB DRD base addresses */
+
+#define STM32H5_USBDRD_BASE       STM32_USB_FS_BASE
+#define STM32H5_USBDRD_PMA_BASE   STM32_USB_FS_RAM_BASE
+
+/* Register access helpers */
+
+#define stm32_getreg(addr)      getreg32(addr)
+#define stm32_putreg(addr, val) putreg32(val, addr)
+#define stm32_modifyreg(addr, clearbits, setbits) modifyreg32(addr, clearbits, 
setbits)
+
+/* Channel register access */
+
+#define STM32H5_USB_CHEP(n)       (STM32H5_USBDRD_BASE + ((n) << 2))
+
+/* Host channel data PID values */
+
+#define HC_PID_DATA0                   (0)
+#define HC_PID_DATA1                   (1)
+#define HC_PID_DATA2                   (2)
+#define HC_PID_SETUP                   (3)
+
+/* Host channel direction */
+
+#define CH_OUT_DIR                     (0)
+#define CH_IN_DIR                      (1)
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/* USB host state machine states */
+
+enum stm32_smstate_e
+{
+  SMSTATE_DETACHED = 0,  /* Not attached to a device */
+  SMSTATE_ATTACHED,      /* Attached to a device */
+  SMSTATE_ENUM,          /* Attached, enumerating */
+  SMSTATE_CLASS_BOUND,   /* Enumeration complete, class bound */
+};
+
+/* Channel halt reason */
+
+enum stm32_chreason_e
+{
+  CHREASON_IDLE = 0,     /* Inactive */
+  CHREASON_FREED,        /* Channel no longer in use */
+  CHREASON_XFRC,         /* Transfer complete */
+  CHREASON_NAK,          /* NAK received */
+  CHREASON_STALL,        /* Endpoint stalled */
+  CHREASON_TXERR,        /* Transfer error */
+  CHREASON_DTERR,        /* Data toggle error */
+  CHREASON_CANCELLED     /* Transfer cancelled */
+};
+
+/* Host channel state */
+
+struct stm32_chan_s
+{
+  sem_t             waitsem;   /* Channel wait semaphore */
+  volatile uint8_t  result;    /* Transfer result */
+  volatile uint8_t  chreason;  /* Halt reason */
+  uint8_t           chidx;     /* Channel index */
+  uint8_t           epno;      /* Device endpoint number */
+  uint8_t           eptype;    /* Endpoint type */
+  uint8_t           funcaddr;  /* Device function address */
+  uint8_t           speed;     /* Device speed */
+  uint8_t           interval;  /* Polling interval */
+  uint8_t           pid;       /* Data PID */
+  bool              inuse;     /* Channel in use */
+  volatile bool     indata1;   /* IN data toggle */
+  volatile bool     outdata1;  /* OUT data toggle */
+  bool              in;        /* IN endpoint */
+  volatile bool     waiter;    /* Thread waiting */
+  uint16_t          maxpacket; /* Max packet size */
+  uint16_t          buflen;    /* Buffer length */
+  volatile uint16_t xfrd;      /* Bytes transferred */
+  uint16_t          pmaaddr;   /* PMA buffer address */
+  uint8_t           pmabufno;  /* PMA buffer number (0xFF = none) */
+  uint8_t          *buffer;    /* Transfer buffer */
+  usbhost_asynch_t  callback;  /* Async callback */
+  void             *arg;       /* Callback argument */
+};
+
+/* Control endpoint info */
+
+struct stm32_ctrlinfo_s
+{
+  uint8_t           inndx;     /* EP0 IN channel index */
+  uint8_t           outndx;    /* EP0 OUT channel index */
+};
+
+/* USB host driver state */
+
+struct stm32_usbhost_s
+{
+  /* NuttX driver interface - MUST be first */
+
+  struct usbhost_driver_s drvr;
+
+  /* Root hub port description */
+
+  struct usbhost_roothubport_s rhport;
+
+  /* Driver status */
+
+  volatile uint8_t  smstate;   /* State machine state */
+  uint8_t           chidx;     /* Channel waiting for TX */
+  volatile bool     connected; /* Device connected */
+  volatile bool     change;    /* Connection change */
+  volatile bool     pscwait;   /* Waiting for port event */
+  mutex_t           lock;      /* Access mutex */
+  sem_t             pscsem;    /* Port status change sem */
+  struct stm32_ctrlinfo_s ep0; /* EP0 description */
+
+  struct usbhost_devaddr_s devgen;  /* Address generation data */
+
+  /* Host channels */
+
+  struct stm32_chan_s chan[STM32H5_NHOST_CHANNELS];
+
+  /* PMA allocation */
+
+  uint32_t          pma_bufavail;  /* Bitmap of available PMA buffers */
+};
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* Data conversion helpers */
+
+static inline uint16_t stm32_getle16(const uint8_t *val);
+
+/* PMA buffer management */
+
+static void stm32_pma_write(const uint8_t *buffer, uint16_t pmaaddr,
+                               uint16_t nbytes);
+static void stm32_pma_read(uint8_t *buffer, uint16_t pmaaddr,
+                              uint16_t nbytes);
+static int stm32_pma_alloc_buffer(struct stm32_usbhost_s *priv);
+static void stm32_pma_free_buffer(struct stm32_usbhost_s *priv,
+                                   uint8_t bufno);
+
+/* Channel management */
+
+static int stm32_chan_alloc(struct stm32_usbhost_s *priv);
+static inline void stm32_chan_free(struct stm32_usbhost_s *priv,
+                                      int chidx);
+static inline void stm32_chan_freeall(struct stm32_usbhost_s *priv);
+static void stm32_chan_configure(struct stm32_usbhost_s *priv,
+                                    int chidx);
+static int stm32_chan_waitsetup(struct stm32_usbhost_s *priv,
+                                   struct stm32_chan_s *chan);
+static int stm32_chan_wait(struct stm32_usbhost_s *priv,
+                              struct stm32_chan_s *chan,
+                              int timeout_ms);
+static void stm32_chan_wakeup(struct stm32_usbhost_s *priv,
+                                 struct stm32_chan_s *chan);
+
+/* Control endpoint helpers */
+
+static int stm32_ctrlchan_alloc(struct stm32_usbhost_s *priv,
+                                   uint8_t epno, uint8_t funcaddr,
+                                   uint8_t speed,
+                                   struct stm32_ctrlinfo_s *ctrlep);
+static int stm32_ctrlep_alloc(struct stm32_usbhost_s *priv,
+                                 const struct usbhost_epdesc_s *epdesc,
+                                 usbhost_ep_t *ep);
+static int stm32_xfrep_alloc(struct stm32_usbhost_s *priv,
+                                const struct usbhost_epdesc_s *epdesc,
+                                usbhost_ep_t *ep);
+
+/* Transfer functions */
+
+static void stm32_transfer_start(struct stm32_usbhost_s *priv,
+                                    int chidx);
+static int stm32_ctrl_sendsetup(struct stm32_usbhost_s *priv,
+                                   struct stm32_ctrlinfo_s *ep0,
+                                   const struct usb_ctrlreq_s *req);
+static int stm32_ctrl_senddata(struct stm32_usbhost_s *priv,
+                                  struct stm32_ctrlinfo_s *ep0,
+                                  uint8_t *buffer, unsigned int buflen);
+static int stm32_ctrl_recvdata(struct stm32_usbhost_s *priv,
+                                  struct stm32_ctrlinfo_s *ep0,
+                                  uint8_t *buffer, unsigned int buflen);
+static ssize_t stm32_in_transfer(struct stm32_usbhost_s *priv,
+                                    int chidx, uint8_t *buffer,
+                                    size_t buflen);
+static ssize_t stm32_out_transfer(struct stm32_usbhost_s *priv,
+                                     int chidx, uint8_t *buffer,
+                                     size_t buflen);
+
+/* Interrupt handling */
+
+static void stm32_hc_in_irq(struct stm32_usbhost_s *priv, int chidx);
+static void stm32_hc_out_irq(struct stm32_usbhost_s *priv, int chidx);
+static int stm32_usbdrd_interrupt(int irq, void *context, void *arg);
+static void stm32_gint_connected(struct stm32_usbhost_s *priv);
+static void stm32_gint_disconnected(struct stm32_usbhost_s *priv);
+
+/* USB host driver interface */
+
+static int stm32_wait(struct usbhost_connection_s *conn,
+                         struct usbhost_hubport_s **hport);
+static int stm32_rh_enumerate(struct stm32_usbhost_s *priv,
+                                 struct usbhost_connection_s *conn,
+                                 struct usbhost_hubport_s *hport);
+static int stm32_enumerate(struct usbhost_connection_s *conn,
+                              struct usbhost_hubport_s *hport);
+
+static int stm32_ep0configure(struct usbhost_driver_s *drvr,
+                                 usbhost_ep_t ep0, uint8_t funcaddr,
+                                 uint8_t speed, uint16_t maxpacketsize);
+static int stm32_epalloc(struct usbhost_driver_s *drvr,
+                            const struct usbhost_epdesc_s *epdesc,
+                            usbhost_ep_t *ep);
+static int stm32_epfree(struct usbhost_driver_s *drvr, usbhost_ep_t ep);
+static int stm32_alloc(struct usbhost_driver_s *drvr,
+                          uint8_t **buffer, size_t *maxlen);
+static int stm32_free(struct usbhost_driver_s *drvr, uint8_t *buffer);
+static int stm32_ioalloc(struct usbhost_driver_s *drvr,
+                            uint8_t **buffer, size_t buflen);
+static int stm32_iofree(struct usbhost_driver_s *drvr, uint8_t *buffer);
+static int stm32_ctrlin(struct usbhost_driver_s *drvr, usbhost_ep_t ep0,
+                           const struct usb_ctrlreq_s *req, uint8_t *buffer);
+static int stm32_ctrlout(struct usbhost_driver_s *drvr, usbhost_ep_t ep0,
+                            const struct usb_ctrlreq_s *req,
+                            const uint8_t *buffer);
+static ssize_t stm32_transfer(struct usbhost_driver_s *drvr,
+                                 usbhost_ep_t ep, uint8_t *buffer,
+                                 size_t buflen);
+#ifdef CONFIG_USBHOST_ASYNCH
+static int stm32_asynch(struct usbhost_driver_s *drvr, usbhost_ep_t ep,
+                           uint8_t *buffer, size_t buflen,
+                           usbhost_asynch_t callback, void *arg);
+#endif
+static int stm32_cancel(struct usbhost_driver_s *drvr, usbhost_ep_t ep);
+#ifdef CONFIG_USBHOST_HUB
+static int stm32_connect(struct usbhost_driver_s *drvr,
+                            struct usbhost_hubport_s *hport, bool connected);
+#endif
+static void stm32_disconnect(struct usbhost_driver_s *drvr,
+                                struct usbhost_hubport_s *hport);
+
+/* Initialization */
+
+static void stm32_portreset(struct stm32_usbhost_s *priv);
+static void stm32_host_initialize(struct stm32_usbhost_s *priv);
+static void stm32_sw_initialize(struct stm32_usbhost_s *priv);
+static int stm32_hw_initialize(struct stm32_usbhost_s *priv);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+/* Single USB host instance */
+
+static struct stm32_usbhost_s g_usbhost =
+{
+  .lock = NXMUTEX_INITIALIZER,
+  .pscsem = SEM_INITIALIZER(0),
+};
+
+/* Connection/enumeration interface */
+
+static struct usbhost_connection_s g_usbconn =
+{
+  .wait      = stm32_wait,
+  .enumerate = stm32_enumerate,
+};
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: stm32_getle16
+ *
+ * Description:
+ *   Get a 16-bit value from a little-endian byte stream
+ *
+ ****************************************************************************/
+
+static inline uint16_t stm32_getle16(const uint8_t *val)
+{
+  return (uint16_t)val[1] << 8 | (uint16_t)val[0];
+}
+
+/****************************************************************************
+ * Name: stm32_pma_write
+ *
+ * Description:
+ *   Write data to the Packet Memory Area (PMA)
+ *
+ ****************************************************************************/
+
+static void stm32_pma_write(const uint8_t *buffer, uint16_t pmaaddr,
+                               uint16_t nbytes)
+{
+  volatile uint32_t *pdwval;
+  uint32_t count;
+  uint32_t remaining;
+
+  pdwval = (volatile uint32_t *)(STM32H5_USBDRD_PMA_BASE + pmaaddr);
+  count = nbytes >> 2;          /* Number of 32-bit words */
+  remaining = nbytes & 0x03;    /* Remaining bytes */
+
+  /* Write full 32-bit words */
+
+  while (count > 0)
+    {
+      *pdwval++ = (uint32_t)buffer[0] |
+                  ((uint32_t)buffer[1] << 8) |
+                  ((uint32_t)buffer[2] << 16) |
+                  ((uint32_t)buffer[3] << 24);
+      buffer += 4;
+      count--;
+    }
+
+  /* Write remaining bytes */
+
+  if (remaining > 0)
+    {
+      uint32_t val = 0;
+      for (uint32_t i = 0; i < remaining; i++)
+        {
+          val |= ((uint32_t)buffer[i] << (8 * i));
+        }
+
+      *pdwval = val;
+    }
+}
+
+/****************************************************************************
+ * Name: stm32_pma_read
+ *
+ * Description:
+ *   Read data from the Packet Memory Area (PMA)
+ *
+ ****************************************************************************/
+
+static void stm32_pma_read(uint8_t *buffer, uint16_t pmaaddr,
+                              uint16_t nbytes)
+{
+  volatile uint32_t *pdwval;
+  uint32_t count;
+  uint32_t remaining;
+  uint32_t val;
+
+  /* Memory barrier to ensure USB peripheral writes are visible to CPU */
+
+  UP_DSB();
+
+  pdwval = (volatile uint32_t *)(STM32H5_USBDRD_PMA_BASE + pmaaddr);
+  count = nbytes >> 2;
+  remaining = nbytes & 0x03;
+
+  /* Read full 32-bit words */
+
+  while (count > 0)
+    {
+      *(uint32_t *)buffer = *pdwval++;
+      buffer += 4;
+      count--;
+    }
+
+  /* Read remaining bytes */
+
+  if (remaining > 0)
+    {
+      val = *pdwval;
+      for (uint32_t i = 0; i < remaining; i++)
+        {
+          *buffer++ = (uint8_t)((val >> (8 * i)) & 0xff);
+        }
+    }
+}
+
+/****************************************************************************
+ * Name: stm32_pma_alloc_buffer
+ *
+ * Description:
+ *   Allocate a PMA buffer using bitmap allocation. Returns buffer number
+ *   on success, negative error code on failure.
+ *
+ ****************************************************************************/
+
+static int stm32_pma_alloc_buffer(struct stm32_usbhost_s *priv)
+{
+  irqstate_t flags;
+  int bufno = -ENOMEM;
+  int bufndx;
+
+  flags = enter_critical_section();
+
+  for (bufndx = 0; bufndx < STM32H5_PMA_NBUFFERS; bufndx++)
+    {
+      uint32_t bit = STM32H5_PMA_BUFFER_BIT(bufndx);
+      if ((priv->pma_bufavail & bit) != 0)
+        {
+          priv->pma_bufavail &= ~bit;  /* Mark allocated */
+          bufno = bufndx;
+          break;
+        }
+    }
+
+  leave_critical_section(flags);
+
+  if (bufno >= 0)
+    {
+      uinfo("PMA buffer allocated: bufno=%d addr=0x%04x\n",
+            bufno, STM32H5_PMA_BUFNO2ADDR(bufno));
+    }
+  else
+    {
+      uerr("ERROR: PMA buffer allocation failed, all %d buffers in use\n",
+           STM32H5_PMA_NBUFFERS);
+    }
+
+  return bufno;
+}
+
+/****************************************************************************
+ * Name: stm32_pma_free_buffer
+ *
+ * Description:
+ *   Free a PMA buffer by buffer number.
+ *
+ ****************************************************************************/
+
+static void stm32_pma_free_buffer(struct stm32_usbhost_s *priv,
+                                   uint8_t bufno)
+{
+  irqstate_t flags;
+
+  DEBUGASSERT(bufno < STM32H5_PMA_NBUFFERS);
+
+  flags = enter_critical_section();
+  priv->pma_bufavail |= STM32H5_PMA_BUFFER_BIT(bufno);  /* Mark available */
+  leave_critical_section(flags);
+
+  uinfo("PMA buffer freed: bufno=%d addr=0x%04x\n",
+        bufno, STM32H5_PMA_BUFNO2ADDR(bufno));
+}
+
+/****************************************************************************
+ * Name: stm32_chan_alloc
+ *
+ * Description:
+ *   Allocate a host channel
+ *
+ ****************************************************************************/
+
+static int stm32_chan_alloc(struct stm32_usbhost_s *priv)
+{
+  int chidx;
+
+  for (chidx = 0; chidx < STM32H5_NHOST_CHANNELS; chidx++)
+    {
+      if (!priv->chan[chidx].inuse)
+        {
+          int bufno = stm32_pma_alloc_buffer(priv);
+          if (bufno < 0)
+            {
+              return -ENOMEM;
+            }
+
+          priv->chan[chidx].inuse = true;
+          priv->chan[chidx].pmabufno = (uint8_t)bufno;
+          priv->chan[chidx].pmaaddr = STM32H5_PMA_BUFNO2ADDR(bufno);
+          return chidx;
+        }
+    }
+
+  return -ENODEV;
+}
+
+/****************************************************************************
+ * Name: stm32_chan_free
+ *
+ * Description:
+ *   Free a host channel
+ *
+ ****************************************************************************/
+
+static inline void stm32_chan_free(struct stm32_usbhost_s *priv,
+                                      int chidx)
+{
+  struct stm32_chan_s *chan;
+
+  DEBUGASSERT((unsigned)chidx < STM32H5_NHOST_CHANNELS);
+
+  chan = &priv->chan[chidx];
+
+  /* Free PMA buffer if allocated */
+
+  if (chan->pmabufno != STM32H5_PMA_BUFFER_NONE)
+    {
+      stm32_pma_free_buffer(priv, chan->pmabufno);
+      chan->pmabufno = STM32H5_PMA_BUFFER_NONE;
+      chan->pmaaddr = 0;
+    }
+
+  chan->inuse = false;
+}
+
+/****************************************************************************
+ * Name: stm32_set_chep_tx_status
+ *
+ * Description:
+ *   Sets the status of a channel's TX endpoint by toggling the
+ *   appropriate DTOG bits.
+ *
+ ****************************************************************************/
+
+static void stm32_set_chep_tx_status(struct stm32_usbhost_s *priv,
+                                     int chidx, uint32_t status)
+{
+  uint32_t regval;
+
+  /* Status changes work by toggling the DTOG bits */
+
+  regval = stm32_getreg(STM32H5_USB_CHEP(priv->chan[chidx].chidx))
+                        & USB_CHEP_TX_DTOGMASK;
+  if (status & USB_CHEP_TX_DTOG1)
+    {
+      regval ^= USB_CHEP_TX_DTOG1;
+    }
+
+  if (status & USB_CHEP_TX_DTOG2)
+    {
+      regval ^= USB_CHEP_TX_DTOG2;
+    }
+
+  stm32_putreg(STM32H5_USB_CHEP(priv->chan[chidx].chidx),
+               regval | USB_CHEP_VTRX | USB_CHEP_VTTX);
+}
+
+/****************************************************************************
+ * Name: stm32_set_chep_rx_status
+ *
+ * Description:
+ *   Sets the status of a channel's RX endpoint
+ *   by toggling the appropriate DTOG bits.
+ *
+ ****************************************************************************/
+
+static void stm32_set_chep_rx_status(struct stm32_usbhost_s *priv,
+                                     int chidx, uint32_t status)
+{
+  uint32_t regval;
+
+  /* Status changes work by toggling the DTOG bits */
+
+  regval = stm32_getreg(STM32H5_USB_CHEP(priv->chan[chidx].chidx))
+                        & USB_CHEP_RX_DTOGMASK;
+  if (status & USB_CHEP_RX_DTOG1)
+    {
+      regval ^= USB_CHEP_RX_DTOG1;
+    }
+
+  if (status & USB_CHEP_RX_DTOG2)
+    {
+      regval ^= USB_CHEP_RX_DTOG2;
+    }
+
+  stm32_putreg(STM32H5_USB_CHEP(priv->chan[chidx].chidx),
+               regval | USB_CHEP_VTRX | USB_CHEP_VTTX);
+}
+
+/****************************************************************************
+ * Name: stm32_chan_freeall
+ *
+ * Description:
+ *   Free all channels.
+ *
+ ****************************************************************************/
+
+static inline void stm32_chan_freeall(struct stm32_usbhost_s *priv)
+{
+  int chidx;
+
+  /* Free all host channels */
+
+  for (chidx = 0; chidx < STM32H5_NHOST_CHANNELS; chidx++)
+    {
+      if (priv->chan[chidx].inuse)
+        {
+          stm32_chan_free(priv, chidx);
+        }
+    }
+}
+
+/****************************************************************************
+ * Name: stm32_chan_configure
+ *
+ * Description:
+ *   Configure a host channel for a transfer
+ *
+ ****************************************************************************/
+
+static void stm32_chan_configure(struct stm32_usbhost_s *priv,
+                                    int chidx)
+{
+  struct stm32_chan_s *chan = &priv->chan[chidx];
+  uint32_t regval;
+  uint32_t eptype;
+
+  /* Get endpoint type for register */
+
+  switch (chan->eptype)
+    {
+      case USB_EP_ATTR_XFER_CONTROL:
+        eptype = USB_CHEP_UTYPE_CTRL;
+        break;
+      case USB_EP_ATTR_XFER_BULK:
+        eptype = USB_CHEP_UTYPE_BULK;
+        break;
+      case USB_EP_ATTR_XFER_INT:
+        eptype = USB_CHEP_UTYPE_INTR;
+        break;
+      case USB_EP_ATTR_XFER_ISOC:
+        eptype = USB_CHEP_UTYPE_ISOC;
+        break;
+      default:
+        eptype = USB_CHEP_UTYPE_BULK;
+        break;
+    }
+
+  /* Read current register value and mask toggleable bits */
+
+  regval = stm32_getreg(STM32H5_USB_CHEP(chidx)) & USB_CH_T_MASK;
+
+  /* Set endpoint type */
+
+  regval |= eptype;
+
+  /* Clear and set device address and endpoint number */
+
+  regval &= ~(USB_CHEP_DEVADDR_MASK | USB_CHEP_ADDR_MASK |
+              USB_CHEP_LSEP | USB_CHEP_NAK |
+              USB_CHEP_KIND | USB_CHEP_ERRTX | USB_CHEP_ERRRX);
+
+  regval |= ((uint32_t)chan->funcaddr << USB_CHEP_DEVADDR_SHIFT);
+  regval |= ((uint32_t)chan->epno & 0x0f);
+
+  /* Set low-speed endpoint flag if needed */
+
+  if (chan->speed == USB_SPEED_LOW)
+    {
+      regval |= USB_CHEP_LSEP;
+    }
+
+  /* Write the channel register with VT bits preserved */
+
+  stm32_putreg(STM32H5_USB_CHEP(chidx),
+                 regval | USB_CHEP_VTRX | USB_CHEP_VTTX);
+}
+
+/****************************************************************************
+ * Name: stm32_chan_waitsetup
+ *
+ * Description:
+ *   Set up to wait for transfer completion
+ *
+ ****************************************************************************/
+
+static int stm32_chan_waitsetup(struct stm32_usbhost_s *priv,
+                                   struct stm32_chan_s *chan)
+{
+  irqstate_t flags = enter_critical_section();
+
+  /* Is the device still connected? */
+
+  if (!priv->connected)
+    {
+      leave_critical_section(flags);
+      return -ENODEV;
+    }
+
+  chan->waiter = true;
+  chan->callback = NULL;
+  chan->result = EBUSY;
+
+  leave_critical_section(flags);
+  return OK;
+}
+
+/****************************************************************************
+ * Name: stm32_chan_wait
+ *
+ * Description:
+ *   Wait for transfer completion
+ *
+ ****************************************************************************/
+
+static int stm32_chan_wait(struct stm32_usbhost_s *priv,
+                              struct stm32_chan_s *chan,
+                              int timeout_ms)
+{
+  irqstate_t flags;
+  int ret;
+
+  struct timespec abstime;
+  clock_gettime(CLOCK_MONOTONIC, &abstime);
+  abstime.tv_sec += timeout_ms / 1000;
+  abstime.tv_nsec += (timeout_ms % 1000) * 1000000;
+  if (abstime.tv_nsec >= 1000000000)
+    {
+      abstime.tv_sec += 1;
+      abstime.tv_nsec -= 1000000000;
+    }
+
+  flags = enter_critical_section();
+
+  while (chan->waiter)
+    {
+      ret = nxsem_clockwait(&chan->waitsem, CLOCK_MONOTONIC, &abstime);
+      if (ret < 0)
+        {
+          /* Cancel the transfer if still active */
+
+          if (chan->in)
+            {
+              stm32_set_chep_rx_status(priv, chan->chidx,
+                                       USB_CHEP_RX_STRX_STALL);
+            }
+          else
+            {
+              stm32_set_chep_tx_status(priv, chan->chidx,
+                                       USB_CHEP_TX_STTX_STALL);
+            }
+
+          leave_critical_section(flags);
+          return ret;
+        }
+    }
+
+  ret = -(int)chan->result;
+  leave_critical_section(flags);
+  return ret;
+}
+
+/****************************************************************************
+ * Name: stm32_chan_wakeup
+ *
+ * Description:
+ *   Wake up a waiting thread
+ *
+ ****************************************************************************/
+
+static void stm32_chan_wakeup(struct stm32_usbhost_s *priv,
+                                 struct stm32_chan_s *chan)
+{
+  if (chan->waiter)
+    {
+      chan->waiter = false;
+      nxsem_post(&chan->waitsem);
+    }
+
+#ifdef CONFIG_USBHOST_ASYNCH
+  else if (chan->callback)
+    {
+      usbhost_asynch_t callback = chan->callback;
+      void *arg = chan->arg;
+      int nbytes = chan->xfrd;
+      int result = chan->result;
+
+      chan->callback = NULL;
+      chan->arg = NULL;
+
+      if (result != OK)
+        {
+          nbytes = -(int)result;
+        }
+
+      callback(arg, nbytes);
+    }
+  else
+    {
+      uerr("ERROR: No waiter or callback for channel %d\n", chan->chidx);
+    }
+#endif
+}
+
+/****************************************************************************
+ * Name: stm32_ctrlchan_alloc
+ *
+ * Description:
+ *   Allocate channels for control endpoint (IN and OUT)
+ *
+ ****************************************************************************/
+
+static int stm32_ctrlchan_alloc(struct stm32_usbhost_s *priv,
+                                   uint8_t epno, uint8_t funcaddr,
+                                   uint8_t speed,
+                                   struct stm32_ctrlinfo_s *ctrlep)
+{
+  struct stm32_chan_s *chan;
+  int inndx;
+  int outndx;
+
+  /* Allocate IN channel */
+
+  inndx = stm32_chan_alloc(priv);
+  if (inndx < 0)
+    {
+      return -ENOMEM;
+    }
+
+  /* Allocate OUT channel */
+
+  outndx = stm32_chan_alloc(priv);
+  if (outndx < 0)
+    {
+      stm32_chan_free(priv, inndx);
+      return -ENOMEM;
+    }
+
+  /* Configure IN channel */
+
+  chan = &priv->chan[inndx];
+  chan->epno = epno;
+  chan->in = true;
+  chan->eptype = USB_EP_ATTR_XFER_CONTROL;
+  chan->funcaddr = funcaddr;
+  chan->speed = speed;
+  chan->maxpacket = STM32H5_EP0_MAX_PACKET_SIZE;
+  chan->indata1 = false;
+  chan->outdata1 = false;
+
+  /* Configure OUT channel */
+
+  chan = &priv->chan[outndx];
+  chan->epno = epno;
+  chan->in = false;
+  chan->eptype = USB_EP_ATTR_XFER_CONTROL;
+  chan->funcaddr = funcaddr;
+  chan->speed = speed;
+  chan->maxpacket = STM32H5_EP0_MAX_PACKET_SIZE;
+  chan->indata1 = false;
+  chan->outdata1 = false;
+
+  /* Configure channels */
+
+  stm32_chan_configure(priv, inndx);
+  stm32_chan_configure(priv, outndx);
+
+  ctrlep->inndx = inndx;
+  ctrlep->outndx = outndx;
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: stm32_ctrlep_alloc
+ *
+ * Description:
+ *   Allocate a control endpoint
+ *
+ ****************************************************************************/
+
+static int stm32_ctrlep_alloc(struct stm32_usbhost_s *priv,
+                                 const struct usbhost_epdesc_s *epdesc,
+                                 usbhost_ep_t *ep)
+{
+  struct stm32_ctrlinfo_s *ctrlep;
+  int ret;
+
+  ctrlep = kmm_malloc(sizeof(struct stm32_ctrlinfo_s));
+  if (!ctrlep)
+    {
+      return -ENOMEM;
+    }
+
+  ret = stm32_ctrlchan_alloc(priv, 0, epdesc->hport->funcaddr,
+                                epdesc->hport->speed, ctrlep);
+  if (ret < 0)
+    {
+      kmm_free(ctrlep);
+      return ret;
+    }
+
+  *ep = (usbhost_ep_t)ctrlep;
+  return OK;
+}
+
+/****************************************************************************
+ * Name: stm32_xfrep_alloc
+ *
+ * Description:
+ *   Allocate a bulk/interrupt endpoint
+ *
+ ****************************************************************************/
+
+static int stm32_xfrep_alloc(struct stm32_usbhost_s *priv,
+                                const struct usbhost_epdesc_s *epdesc,
+                                usbhost_ep_t *ep)
+{
+  struct stm32_chan_s *chan;
+  int chidx;
+
+  chidx = stm32_chan_alloc(priv);
+  if (chidx < 0)
+    {
+      return -ENOMEM;
+    }
+
+  chan = &priv->chan[chidx];
+  chan->epno = epdesc->addr & USB_EPNO_MASK;
+  chan->in = epdesc->in;
+  chan->eptype = epdesc->xfrtype;
+  chan->funcaddr = epdesc->hport->funcaddr;
+  chan->speed = epdesc->hport->speed;
+  chan->interval = epdesc->interval;
+  chan->maxpacket = epdesc->mxpacketsize;
+  chan->indata1 = false;
+  chan->outdata1 = false;
+
+  /* Clamp all endpoints to max 64 bytes. To support larger packets,
+   * additional PMA logic is required
+   */
+
+  if (chan->maxpacket > 64)
+    {
+      chan->maxpacket = 64;
+    }
+
+  stm32_chan_configure(priv, chidx);
+
+  *ep = (usbhost_ep_t)chidx;
+  return OK;
+}
+
+/****************************************************************************
+ * Name: stm32_transfer_start
+ *
+ * Description:
+ *   Start a USB transfer
+ *
+ ****************************************************************************/
+
+static void stm32_transfer_start(struct stm32_usbhost_s *priv,
+                                    int chidx)
+{
+  struct stm32_chan_s *chan = &priv->chan[chidx];
+  uint32_t regval;
+  uint16_t len;
+  uint32_t bdval;
+  volatile uint32_t *pbd;
+
+  len = chan->buflen > chan->maxpacket ? chan->maxpacket : chan->buflen;
+
+  if (chan->in)
+    {
+      /* Set up RX buffer in PMA */
+
+      bdval = chan->pmaaddr;  /* chan->pmaaddr is already 4 byte aligned */
+
+      if (len > 62)
+        {
+          /* BL_SIZE = 1, NUM_BLOCK = (len / 32) - 1 */
+
+          uint32_t nblocks = ((len + 31) / 32);
+          bdval |= USB_PMA_RXBD_BLSIZE |
+                   ((nblocks - 1) << USB_PMA_RXBD_NUM_BLOCK_SHIFT);
+        }
+      else
+        {
+          /* BL_SIZE = 0, NUM_BLOCK = len / 2 */
+
+          uint32_t nblocks = (len + 1) / 2;
+          bdval |= (nblocks << USB_PMA_RXBD_NUM_BLOCK_SHIFT);
+        }
+
+      pbd = (volatile uint32_t *)(STM32H5_USBDRD_PMA_BASE +
+                                  USB_PMA_RXBD_OFFSET(chidx));
+      *pbd = bdval;
+
+      /* Memory barrier to ensure BD write completes
+       * before enabling transfer
+       */
+
+      UP_DSB();
+
+      /* Clear data toggle if starting new transfer */
+
+      regval = stm32_getreg(STM32H5_USB_CHEP(chidx));
+      if ((regval & USB_CHEP_DTOG_RX) != 0)
+        {
+          if (!chan->indata1)
+            {
+              /* Need DATA0, but DTOG shows DATA1 - toggle it */
+
+              regval = (regval & USB_CHEP_REG_MASK) |
+                       USB_CHEP_VTRX | USB_CHEP_VTTX | USB_CHEP_DTOG_RX;
+              stm32_putreg(STM32H5_USB_CHEP(chidx), regval);
+            }
+        }
+      else
+        {
+          if (chan->indata1)
+            {
+              /* Need DATA1, but DTOG shows DATA0 - toggle it */
+
+              regval = (regval & USB_CHEP_REG_MASK) |
+                       USB_CHEP_VTRX | USB_CHEP_VTTX | USB_CHEP_DTOG_RX;
+              stm32_putreg(STM32H5_USB_CHEP(chidx), regval);
+            }
+        }
+
+      /* Enable RX */
+
+      stm32_set_chep_rx_status(priv, chan->chidx, USB_CHEP_RX_STRX_VALID);
+    }
+  else
+    {
+      /* OUT transfer - write data to PMA and set TX count */
+
+      if (len > 0)
+        {
+          stm32_pma_write(chan->buffer, chan->pmaaddr, len);
+        }
+
+      /* Update TX count in buffer descriptor */
+
+      bdval = chan->pmaaddr;  /* chan->pmaaddr is already 4 byte aligned */
+      bdval |= ((uint32_t)len << USB_PMA_TXBD_COUNT_SHIFT);
+
+      pbd = (volatile uint32_t *)(STM32H5_USBDRD_PMA_BASE +
+                                  USB_PMA_TXBD_OFFSET(chidx));
+      *pbd = bdval;
+
+      /* Handle SETUP token for control transfers */
+
+      if (chan->pid == HC_PID_SETUP)
+        {
+          regval = stm32_getreg(STM32H5_USB_CHEP(chidx)) & USB_CHEP_REG_MASK;
+          stm32_putreg(STM32H5_USB_CHEP(chidx),
+                         regval | USB_CHEP_SETUP |
+                         USB_CHEP_VTRX | USB_CHEP_VTTX);
+        }
+
+      /* Sync data toggle (only needed at transfer start,
+       * hardware auto-toggles after successful transmit)
+       */
+
+      regval = stm32_getreg(STM32H5_USB_CHEP(chidx));
+      if ((regval & USB_CHEP_DTOG_TX) != 0)
+        {
+          if (!chan->outdata1)
+            {
+              /* Need DATA0, toggle it */
+
+              regval = (regval & USB_CHEP_REG_MASK) |
+                       USB_CHEP_VTRX | USB_CHEP_VTTX | USB_CHEP_DTOG_TX;
+              stm32_putreg(STM32H5_USB_CHEP(chidx), regval);
+            }
+        }
+      else
+        {
+          if (chan->outdata1)
+            {
+              /* Need DATA1, toggle it */
+
+              regval = (regval & USB_CHEP_REG_MASK) |
+                       USB_CHEP_VTRX | USB_CHEP_VTTX | USB_CHEP_DTOG_TX;
+              stm32_putreg(STM32H5_USB_CHEP(chidx), regval);
+            }
+        }
+
+      /* Enable TX */
+
+      stm32_set_chep_tx_status(priv, chan->chidx, USB_CHEP_TX_STTX_VALID);
+    }
+}
+
+/****************************************************************************
+ * Name: stm32_ctrl_sendsetup
+ *
+ * Description:
+ *   Send a SETUP packet
+ *
+ ****************************************************************************/
+
+static int stm32_ctrl_sendsetup(struct stm32_usbhost_s *priv,
+                                   struct stm32_ctrlinfo_s *ep0,
+                                   const struct usb_ctrlreq_s *req)
+{
+  struct stm32_chan_s *chan;
+  int ret;
+
+  chan = &priv->chan[ep0->outndx];
+
+  /* Set up the transfer */
+
+  chan->buffer = (uint8_t *)req;
+  chan->buflen = USB_SIZEOF_CTRLREQ;
+  chan->pid = HC_PID_SETUP;
+  chan->outdata1 = false;
+
+  ret = stm32_chan_waitsetup(priv, chan);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  stm32_transfer_start(priv, ep0->outndx);
+  return stm32_chan_wait(priv, chan, CONFIG_STM32H5_USBDRD_TRANSFER_TIMEOUT);
+}
+
+/****************************************************************************
+ * Name: stm32_ctrl_senddata
+ *
+ * Description:
+ *   Send control data
+ *
+ ****************************************************************************/
+
+static int stm32_ctrl_senddata(struct stm32_usbhost_s *priv,
+                                  struct stm32_ctrlinfo_s *ep0,
+                                  uint8_t *buffer, unsigned int buflen)
+{
+  struct stm32_chan_s *chan;
+  int ret;
+
+  chan = &priv->chan[ep0->outndx];
+
+  chan->buffer = buffer;
+  chan->buflen = buflen;
+  chan->pid = HC_PID_DATA1;
+  chan->outdata1 = true;
+
+  ret = stm32_chan_waitsetup(priv, chan);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  stm32_transfer_start(priv, ep0->outndx);
+  return stm32_chan_wait(priv, chan, CONFIG_STM32H5_USBDRD_TRANSFER_TIMEOUT);
+}
+
+/****************************************************************************
+ * Name: stm32_ctrl_recvdata
+ *
+ * Description:
+ *   Receive control data
+ *
+ ****************************************************************************/
+
+static int stm32_ctrl_recvdata(struct stm32_usbhost_s *priv,
+                                  struct stm32_ctrlinfo_s *ep0,
+                                  uint8_t *buffer, unsigned int buflen)
+{
+  struct stm32_chan_s *chan;
+  int ret;
+
+  chan = &priv->chan[ep0->inndx];
+
+  chan->buffer = buffer;
+  chan->buflen = buflen;
+  chan->pid = HC_PID_DATA1;
+  chan->indata1 = true;
+
+  ret = stm32_chan_waitsetup(priv, chan);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  stm32_transfer_start(priv, ep0->inndx);
+  return stm32_chan_wait(priv, chan, CONFIG_STM32H5_USBDRD_TRANSFER_TIMEOUT);
+}
+
+/****************************************************************************
+ * Name: stm32_in_transfer
+ *
+ * Description:
+ *   Perform an IN transfer
+ *
+ ****************************************************************************/
+
+static ssize_t stm32_in_transfer(struct stm32_usbhost_s *priv,
+                                    int chidx, uint8_t *buffer,
+                                    size_t buflen)
+{
+  struct stm32_chan_s *chan = &priv->chan[chidx];
+  int ret;
+
+  /* Set up for ENTIRE transfer (IRQ will handle multi-packet) */
+
+  chan->buffer = buffer;
+  chan->buflen = buflen;
+  chan->xfrd = 0;
+
+  ret = stm32_chan_waitsetup(priv, chan);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  stm32_transfer_start(priv, chidx);
+
+  ret = stm32_chan_wait(priv, chan, CONFIG_STM32H5_USBDRD_TRANSFER_TIMEOUT);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return chan->xfrd;
+}
+
+/****************************************************************************
+ * Name: stm32_out_transfer
+ *
+ * Description:
+ *   Perform an OUT transfer
+ *
+ ****************************************************************************/
+
+static ssize_t stm32_out_transfer(struct stm32_usbhost_s *priv,
+                                     int chidx, uint8_t *buffer,
+                                     size_t buflen)
+{
+  struct stm32_chan_s *chan = &priv->chan[chidx];
+  int ret;
+
+  /* Set up for ENTIRE transfer (IRQ will handle multi-packet) */
+
+  chan->buffer = buffer;
+  chan->buflen = buflen;
+  chan->xfrd = 0;
+
+  ret = stm32_chan_waitsetup(priv, chan);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  stm32_transfer_start(priv, chidx);
+
+  ret = stm32_chan_wait(priv, chan, CONFIG_STM32H5_USBDRD_TRANSFER_TIMEOUT);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return chan->xfrd;
+}
+
+/****************************************************************************
+ * Name: stm32_gint_connected
+ *
+ * Description:
+ *   Handle device connection
+ *
+ ****************************************************************************/
+
+static void stm32_gint_connected(struct stm32_usbhost_s *priv)
+{
+  /* Were we previously disconnected? */
+
+  if (!priv->connected)
+    {
+      /* Yes.. then now we are connected */
+
+      uinfo("USB device connected\n");
+      priv->connected = true;
+      priv->change    = true;
+      DEBUGASSERT(priv->smstate == SMSTATE_DETACHED);
+
+      /* Notify any waiters */
+
+      priv->smstate = SMSTATE_ATTACHED;
+      if (priv->pscwait)
+        {
+          nxsem_post(&priv->pscsem);
+          priv->pscwait = false;
+        }
+    }
+}
+
+/****************************************************************************
+ * Name: stm32_gint_disconnected
+ *
+ * Description:
+ *   Handle device disconnection
+ *
+ ****************************************************************************/
+
+static void stm32_gint_disconnected(struct stm32_usbhost_s *priv)
+{
+  /* Were we previously connected? */
+
+  if (priv->connected)
+    {
+      /* Yes.. then we no longer connected */
+
+      uinfo("USB device disconnected\n");
+
+      /* Are we bound to a class driver? */
+
+      if (priv->rhport.hport.devclass)
+        {
+          /* Yes.. Disconnect the class driver */
+
+          CLASS_DISCONNECTED(priv->rhport.hport.devclass);
+          priv->rhport.hport.devclass = NULL;
+        }
+
+      /* Re-Initialize Host for new Enumeration */
+
+      priv->smstate   = SMSTATE_DETACHED;
+      priv->connected = false;
+      priv->change    = true;
+      stm32_chan_freeall(priv);
+
+      priv->rhport.hport.speed = USB_SPEED_FULL;
+      priv->rhport.hport.funcaddr = 0;
+
+      /* Notify any waiters that there is a change in the connection state */
+
+      if (priv->pscwait)
+        {
+          nxsem_post(&priv->pscsem);
+          priv->pscwait = false;
+        }
+    }
+}
+
+/****************************************************************************
+ * Name: stm32_hc_in_irq
+ *
+ * Description:
+ *   Handle IN channel interrupt (device-to-host transfers).
+ *   Based on ST HAL HCD_HC_IN_IRQHandler pattern.
+ *
+ ****************************************************************************/
+
+static void stm32_hc_in_irq(struct stm32_usbhost_s *priv, int chidx)
+{
+  struct stm32_chan_s *chan = &priv->chan[chidx];
+  uint32_t chepval = stm32_getreg(STM32H5_USB_CHEP(chidx));
+  uint32_t rx_status = chepval & USB_CHEP_RX_STRX_MASK;
+  bool wakeup = false;
+
+  if ((chepval & USB_CHEP_ERRRX) != 0)
+    {
+      /* Clear error bit: write 0 to ERRRX to clear,
+       * write 1 to VTRX/VTTX to preserve
+       */
+
+      chepval = stm32_getreg(STM32H5_USB_CHEP(chidx));
+      chepval = (chepval & USB_CHEP_REG_MASK & ~USB_CHEP_ERRRX) |
+                USB_CHEP_VTRX | USB_CHEP_VTTX;
+      stm32_putreg(STM32H5_USB_CHEP(chidx), chepval);
+
+      chan->result = EIO;
+      chan->chreason = CHREASON_TXERR;
+      stm32_chan_wakeup(priv, chan);
+      return;
+    }
+
+  /* Check RX status for ACK/NAK/STALL
+   * After successful transfer, status becomes USB_CHEP_RX_STRX_DIS (0x0)
+   * meaning "ACK received, channel disabled".
+   */
+
+  if (rx_status == USB_CHEP_RX_STRX_DIS)
+    {
+      /* ACK - successful transfer, read data from PMA */
+
+      volatile uint32_t *pbd;
+      uint16_t count;
+      bool transfer_complete;
+
+      pbd = (volatile uint32_t *)(STM32H5_USBDRD_PMA_BASE +
+                                  USB_PMA_RXBD_OFFSET(chidx));
+      count = (*pbd >> USB_PMA_RXBD_COUNT_SHIFT) & 0x3ff;
+
+      /* Clip to remaining buffer space */
+
+      if (count > chan->buflen)
+        {
+          count = chan->buflen;
+        }
+
+      /* Read data from PMA using the address from the BD - this ensures we
+       * read from wherever the hardware actually wrote the data.
+       */
+
+      if (count > 0 && chan->buffer != NULL)
+        {
+          stm32_pma_read(chan->buffer, chan->pmaaddr, count);
+        }
+
+      /* Accumulate bytes, advance buffer, reduce remaining */
+
+      chan->xfrd += count;
+      chan->buffer += count;
+      chan->buflen -= count;
+
+      /* Toggle DATA0/DATA1 for this packet */
+
+      chan->indata1 ^= true;
+
+      /* Check if transfer is complete:
+       * - All requested bytes received (buflen == 0)
+       * - Short packet received (count < maxpacket)
+       * - Zero-length packet received (count == 0)
+       */
+
+      transfer_complete = (chan->buflen == 0) ||
+                          (count < chan->maxpacket) ||
+                          (count == 0);
+
+      if (!transfer_complete && (chan->waiter || chan->callback))
+        {
+          /* More data expected - reactivate channel for next packet */
+
+          stm32_transfer_start(priv, chidx);
+        }
+      else
+        {
+          /* Transfer complete - set success result */
+
+          chan->result = OK;
+          chan->chreason = CHREASON_XFRC;
+          wakeup = true;
+        }
+    }
+  else if (rx_status == USB_CHEP_RX_STRX_NAK)
+    {
+      /* NAK is normal flow control
+       * If we have a waiter, just let hardware keep retrying indefinitely.
+       * We will timeout in stm32_chan_wait
+       * and cancel if the device never answers
+       */
+
+      if (!chan->waiter && !chan->callback)
+        {
+          /* No waiters on this. Cancel the transaction */
+
+          stm32_set_chep_rx_status(priv, chan->chidx,
+                                   USB_CHEP_RX_STRX_STALL);
+        }
+    }
+  else if (rx_status == USB_CHEP_RX_STRX_STALL)
+    {
+      /* STALL - endpoint halted, disable channel */
+
+      chan->result = EPERM;
+      chan->chreason = CHREASON_STALL;
+
+      /* Disable the channel */
+
+      stm32_set_chep_rx_status(priv, chan->chidx, USB_CHEP_RX_STRX_DIS);
+      wakeup = true;
+    }
+
+  /* Clear VTRX by writing 0 to it
+   * (toggle bit, write 1 keeps, write 0 clears)
+   */
+
+  chepval = stm32_getreg(STM32H5_USB_CHEP(chidx));
+  chepval = (chepval & (0xffff7fff & USB_CHEP_REG_MASK)) | USB_CHEP_VTTX;
+  stm32_putreg(STM32H5_USB_CHEP(chidx), chepval);
+
+  if (wakeup)
+    {
+      stm32_chan_wakeup(priv, chan);
+    }
+}
+
+/****************************************************************************
+ * Name: stm32_hc_out_irq
+ *
+ * Description:
+ *   Handle OUT channel interrupt (host-to-device transfers including SETUP).
+ *   Based on ST HAL HCD_HC_OUT_IRQHandler pattern.
+ *
+ ****************************************************************************/
+
+static void stm32_hc_out_irq(struct stm32_usbhost_s *priv, int chidx)
+{
+  struct stm32_chan_s *chan = &priv->chan[chidx];
+  uint32_t chepval = stm32_getreg(STM32H5_USB_CHEP(chidx));
+  uint32_t tx_status = chepval & USB_CHEP_TX_STTX_MASK;
+  bool wakeup = false;
+
+  if ((chepval & USB_CHEP_ERRTX) != 0)
+    {
+      /* Clear error bit: write 0 to ERRTX to clear,
+       * write 1 to VTRX/VTTX to preserve
+       */
+
+      chepval = stm32_getreg(STM32H5_USB_CHEP(chidx));
+      chepval = (chepval & USB_CHEP_REG_MASK & ~USB_CHEP_ERRTX) |
+                USB_CHEP_VTRX | USB_CHEP_VTTX;
+      stm32_putreg(STM32H5_USB_CHEP(chidx), chepval);
+
+      chan->result = EIO;
+      chan->chreason = CHREASON_TXERR;
+      stm32_chan_wakeup(priv, chan);
+      return;
+    }
+
+  /* Check TX status for ACK/NAK/STALL
+   * After successful transfer, status becomes USB_CHEP_TX_STTX_DIS (0x0)
+   * meaning "ACK received, channel disabled".
+   */
+
+  if (tx_status == USB_CHEP_TX_STTX_DIS)
+    {
+      /* ACK - successful transfer of one packet */
+
+      uint16_t sent_count;
+
+      /* Calculate how much was sent (capped at maxpacket) */
+
+      sent_count = (chan->buflen > chan->maxpacket) ?
+                    chan->maxpacket : chan->buflen;
+
+      /* Accumulate bytes, advance buffer, reduce remaining */
+
+      chan->xfrd += sent_count;
+      chan->buffer += sent_count;
+      chan->buflen -= sent_count;
+
+      /* Toggle DATA0/DATA1 */
+
+      chan->outdata1 ^= true;
+
+      /* Check if more data to send */
+
+      if (chan->buflen > 0 && (chan->waiter || chan->callback))
+        {
+          /* More data to send.
+           * prepare and restart channel without waking thread
+           */
+
+          stm32_transfer_start(priv, chidx);
+        }
+      else
+        {
+          /* All data sent - mark complete */
+
+          chan->result = OK;
+          chan->chreason = CHREASON_XFRC;
+          wakeup = true;
+        }
+    }
+  else if (((chepval & USB_CHEP_NAK) == USB_CHEP_NAK) ||
+           (tx_status == USB_CHEP_TX_STTX_NAK))
+    {
+      /* NAK is normal flow control
+       * If we have a waiter, just let hardware keep retrying indefinitely.
+       * We will timeout in stm32_chan_wait and cancel
+       * if the device never answers
+       */
+
+      /* Clear NAK if it was set */
+
+      if ((chepval & USB_CHEP_NAK) != 0)
+        {
+          chepval = stm32_getreg(STM32H5_USB_CHEP(chidx));
+          chepval = (chepval & USB_CHEP_REG_MASK & ~USB_CHEP_NAK) |
+                    USB_CHEP_VTRX | USB_CHEP_VTTX;
+          stm32_putreg(STM32H5_USB_CHEP(chidx), chepval);
+        }
+
+      if (!chan->waiter && !chan->callback)
+        {
+          /* No waiters on this. Cancel the transaction */
+
+          stm32_set_chep_tx_status(priv, chan->chidx,
+                                   USB_CHEP_TX_STTX_STALL);
+        }
+    }
+  else if (tx_status == USB_CHEP_TX_STTX_STALL)
+    {
+      /* STALL - endpoint halted, disable channel */
+
+      chan->result = EPERM;
+      chan->chreason = CHREASON_STALL;
+      wakeup = true;
+
+      /* Disable the channel */
+
+      stm32_set_chep_tx_status(priv, chan->chidx, USB_CHEP_TX_STTX_DIS);
+    }
+
+  /* Clear VTTX by writing 0 to it */
+
+  chepval = stm32_getreg(STM32H5_USB_CHEP(chidx));
+  chepval = (chepval & (0xffffff7f & USB_CHEP_REG_MASK)) | USB_CHEP_VTRX;
+  stm32_putreg(STM32H5_USB_CHEP(chidx), chepval);
+
+  if (wakeup)
+    {
+      stm32_chan_wakeup(priv, chan);
+    }
+}
+
+/****************************************************************************
+ * Name: stm32_usbdrd_interrupt
+ *
+ * Description:
+ *   USB DRD interrupt handler.
+ *   Dispatches channel transfer interrupts to IN/OUT handlers based on
+ *   direction, following ST HAL pattern.
+ *
+ ****************************************************************************/
+
+static int stm32_usbdrd_interrupt(int irq, void *context, void *arg)
+{
+  struct stm32_usbhost_s *priv = &g_usbhost;
+  uint32_t istr;
+
+  istr = stm32_getreg(STM32_USB_ISTR);
+
+  /* Device connection */
+
+  if ((istr & USB_ISTR_RESET) != 0)
+    {
+      if ((istr & USB_ISTR_DCON_STAT) != 0)
+        {
+          stm32_gint_connected(priv);
+        }
+
+      stm32_putreg(STM32_USB_ISTR, ~USB_ISTR_RESET);
+    }
+
+  /* Device disconnection */
+
+  if ((istr & USB_ISTR_DDISC) != 0)
+    {
+      stm32_gint_disconnected(priv);
+      stm32_putreg(STM32_USB_ISTR, ~USB_ISTR_DDISC);
+    }
+
+  /* Correct transfer - dispatch to IN or OUT handler based on direction */
+
+  if ((istr & USB_ISTR_CTR) != 0)
+    {
+      int chidx = istr & USB_ISTR_EPID_MASK;
+      bool is_in = (istr & USB_ISTR_DIR) != 0;
+
+      if (is_in)
+        {
+          stm32_hc_in_irq(priv, chidx);
+        }
+      else
+        {
+          stm32_hc_out_irq(priv, chidx);
+        }
+    }
+
+  /* Error */
+
+  if ((istr & USB_ISTR_ERR) != 0)
+    {
+      uerr("ERROR: USB error\n");
+      stm32_putreg(STM32_USB_ISTR, ~USB_ISTR_ERR);
+    }
+
+  /* PMA overrun */
+
+  if ((istr & USB_ISTR_PMAOVRN) != 0)
+    {
+      uerr("ERROR: PMA overrun\n");
+      stm32_putreg(STM32_USB_ISTR, ~USB_ISTR_PMAOVRN);
+    }
+
+  /* Other interrupts - not used, just clear */
+
+  if ((istr & USB_ISTR_WKUP) != 0)
+    {
+      stm32_putreg(STM32_USB_ISTR, ~USB_ISTR_WKUP);
+    }
+
+  if ((istr & USB_ISTR_SUSP) != 0)
+    {
+      stm32_putreg(STM32_USB_ISTR, ~USB_ISTR_SUSP);
+    }
+
+  if ((istr & USB_ISTR_SOF) != 0)
+    {
+      stm32_putreg(STM32_USB_ISTR, ~USB_ISTR_SOF);
+    }
+
+  if ((istr & USB_ISTR_ESOF) != 0)
+    {
+      stm32_putreg(STM32_USB_ISTR, ~USB_ISTR_ESOF);
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: stm32_wait
+ *
+ * Description:
+ *   Wait for device connection/disconnection
+ *
+ ****************************************************************************/
+
+static int stm32_wait(struct usbhost_connection_s *conn,
+                         struct usbhost_hubport_s **hport)
+{
+  struct stm32_usbhost_s *priv = &g_usbhost;
+  struct usbhost_hubport_s *connport;
+  irqstate_t flags;
+  int ret;
+
+  flags = enter_critical_section();
+
+  for (; ; )
+    {
+      if (priv->change)
+        {
+          priv->change = false;
+
+          connport = &priv->rhport.hport;
+          connport->connected = priv->connected;
+
+          *hport = connport;
+
+          leave_critical_section(flags);
+          return OK;
+        }
+
+      priv->pscwait = true;
+      ret = nxsem_wait(&priv->pscsem);
+      if (ret < 0)
+        {
+          leave_critical_section(flags);
+          return ret;
+        }
+    }
+}
+
+/****************************************************************************
+ * Name: stm32_rh_enumerate
+ *
+ * Description:
+ *   Enumerate the root hub
+ *
+ ****************************************************************************/
+
+static int stm32_rh_enumerate(struct stm32_usbhost_s *priv,
+                                 struct usbhost_connection_s *conn,
+                                 struct usbhost_hubport_s *hport)
+{
+  uint32_t istr;
+
+  DEBUGASSERT(hport != NULL && hport->port == 0);
+
+  /* Are we connected? */
+
+  if (!priv->connected)
+    {
+      return -ENODEV;
+    }
+
+  /* USB 2.0 spec says at least 50ms delay before port reset */
+
+  nxsched_usleep(50 * 1000);
+
+  /* Reset the port */
+
+  stm32_portreset(priv);
+
+  /* Check for low-speed device */
+
+  istr = stm32_getreg(STM32_USB_ISTR);
+  if ((istr & USB_ISTR_LS_DCONN) != 0)
+    {
+      hport->speed = USB_SPEED_LOW;
+    }
+  else
+    {
+      hport->speed = USB_SPEED_FULL;
+    }
+
+  uinfo("Enumerated device at %s speed\n",
+        hport->speed == USB_SPEED_LOW ? "low" : "full");
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: stm32_enumerate
+ *
+ * Description:
+ *   Enumerate a connected device
+ *
+ ****************************************************************************/
+
+static int stm32_enumerate(struct usbhost_connection_s *conn,
+                              struct usbhost_hubport_s *hport)
+{
+  struct stm32_usbhost_s *priv = &g_usbhost;
+  int ret;
+
+  DEBUGASSERT(hport != NULL);
+
+  /* If this is the root hub port */
+
+  if (hport->port == 0)
+    {
+      ret = stm32_rh_enumerate(priv, conn, hport);
+      if (ret < 0)
+        {
+          return ret;
+        }
+    }
+
+  /* Get exclusive access */
+
+  ret = nxmutex_lock(&priv->lock);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  /* Allocate control endpoint */
+
+  ret = stm32_ctrlchan_alloc(priv, 0, 0, hport->speed, &priv->ep0);
+  if (ret < 0)
+    {
+      uerr("ERROR: Failed to allocate EP0: %d\n", ret);
+      nxmutex_unlock(&priv->lock);
+      return ret;
+    }
+
+  nxmutex_unlock(&priv->lock);
+
+  /* Perform standard enumeration */
+
+  ret = usbhost_enumerate(hport, &hport->devclass);
+
+  /* The enumeration may fail either because of some HCD interfaces failure
+   * or because the device class is not supported.  In either case, we just
+   * need to perform the disconnection operation and make ready for a new
+   * enumeration.
+   */
+
+  if (ret < 0)
+    {
+      /* Return to the disconnected state */
+
+      uerr("ERROR: Enumeration failed: %d\n", ret);
+      stm32_gint_disconnected(priv);
+    }
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: stm32_ep0configure
+ *
+ * Description:
+ *   Configure endpoint 0
+ *
+ ****************************************************************************/
+
+static int stm32_ep0configure(struct usbhost_driver_s *drvr,
+                                 usbhost_ep_t ep0, uint8_t funcaddr,
+                                 uint8_t speed, uint16_t maxpacketsize)
+{
+  struct stm32_usbhost_s *priv = (struct stm32_usbhost_s *)drvr;
+  struct stm32_ctrlinfo_s *ep0info = (struct stm32_ctrlinfo_s *)ep0;
+  struct stm32_chan_s *chan;
+  int ret;
+
+  DEBUGASSERT(drvr != NULL && ep0info != NULL);
+
+  ret = nxmutex_lock(&priv->lock);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  /* Configure IN channel */
+
+  chan = &priv->chan[ep0info->inndx];
+  chan->funcaddr = funcaddr;
+  chan->speed = speed;
+  chan->maxpacket = maxpacketsize;
+  stm32_chan_configure(priv, ep0info->inndx);
+
+  /* Configure OUT channel */
+
+  chan = &priv->chan[ep0info->outndx];
+  chan->funcaddr = funcaddr;
+  chan->speed = speed;
+  chan->maxpacket = maxpacketsize;
+  stm32_chan_configure(priv, ep0info->outndx);
+
+  nxmutex_unlock(&priv->lock);
+  return OK;
+}
+
+/****************************************************************************
+ * Name: stm32_epalloc
+ *
+ * Description:
+ *   Allocate an endpoint
+ *
+ ****************************************************************************/
+
+static int stm32_epalloc(struct usbhost_driver_s *drvr,
+                            const struct usbhost_epdesc_s *epdesc,
+                            usbhost_ep_t *ep)
+{
+  struct stm32_usbhost_s *priv = (struct stm32_usbhost_s *)drvr;
+  int ret;
+
+  DEBUGASSERT(drvr != NULL && epdesc != NULL && ep != NULL);
+  DEBUGASSERT(priv->connected);
+
+  ret = nxmutex_lock(&priv->lock);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  if (epdesc->xfrtype == USB_EP_ATTR_XFER_CONTROL)
+    {
+      ret = stm32_ctrlep_alloc(priv, epdesc, ep);
+    }
+  else
+    {
+      ret = stm32_xfrep_alloc(priv, epdesc, ep);
+    }
+
+  nxmutex_unlock(&priv->lock);
+  return ret;
+}
+
+/****************************************************************************
+ * Name: stm32_epfree
+ *
+ * Description:
+ *   Free an endpoint
+ *
+ ****************************************************************************/
+
+static int stm32_epfree(struct usbhost_driver_s *drvr, usbhost_ep_t ep)
+{
+  struct stm32_usbhost_s *priv = (struct stm32_usbhost_s *)drvr;
+  int ret;
+
+  DEBUGASSERT(drvr != NULL);
+
+  ret = nxmutex_lock(&priv->lock);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  /* Free channel(s) */
+
+  stm32_chan_free(priv, (int)ep);
+
+  nxmutex_unlock(&priv->lock);
+  return OK;
+}
+
+/****************************************************************************
+ * Name: stm32_alloc
+ *
+ * Description:
+ *   Allocate a request/descriptor buffer
+ *
+ ****************************************************************************/
+
+static int stm32_alloc(struct usbhost_driver_s *drvr,
+                          uint8_t **buffer, size_t *maxlen)
+{
+  uint8_t *alloc;
+
+  DEBUGASSERT(drvr && buffer && maxlen);
+
+  alloc = kmm_malloc(CONFIG_STM32H5_USBDRD_DESCSIZE);
+  if (!alloc)
+    {
+      return -ENOMEM;
+    }
+
+  *buffer = alloc;
+  *maxlen = CONFIG_STM32H5_USBDRD_DESCSIZE;
+  return OK;
+}
+
+/****************************************************************************
+ * Name: stm32_free
+ *
+ * Description:
+ *   Free a request/descriptor buffer
+ *
+ ****************************************************************************/
+
+static int stm32_free(struct usbhost_driver_s *drvr, uint8_t *buffer)
+{
+  DEBUGASSERT(drvr && buffer);
+  kmm_free(buffer);
+  return OK;
+}
+
+/****************************************************************************
+ * Name: stm32_ioalloc
+ *
+ * Description:
+ *   Allocate an I/O buffer
+ *
+ ****************************************************************************/
+
+static int stm32_ioalloc(struct usbhost_driver_s *drvr,
+                            uint8_t **buffer, size_t buflen)
+{
+  uint8_t *alloc;
+
+  DEBUGASSERT(drvr && buffer && buflen > 0);
+
+  alloc = kmm_malloc(buflen);
+  if (!alloc)
+    {
+      return -ENOMEM;
+    }
+
+  *buffer = alloc;
+  return OK;
+}
+
+/****************************************************************************
+ * Name: stm32_iofree
+ *
+ * Description:
+ *   Free an I/O buffer
+ *
+ ****************************************************************************/
+
+static int stm32_iofree(struct usbhost_driver_s *drvr, uint8_t *buffer)
+{
+  DEBUGASSERT(drvr && buffer);
+  kmm_free(buffer);
+  return OK;
+}
+
+/****************************************************************************
+ * Name: stm32_ctrlin
+ *
+ * Description:
+ *   Control IN transfer
+ *
+ ****************************************************************************/
+
+static int stm32_ctrlin(struct usbhost_driver_s *drvr, usbhost_ep_t ep0,
+                           const struct usb_ctrlreq_s *req, uint8_t *buffer)
+{
+  struct stm32_usbhost_s *priv = (struct stm32_usbhost_s *)drvr;
+  struct stm32_ctrlinfo_s *ep0info = (struct stm32_ctrlinfo_s *)ep0;
+  uint16_t buflen;
+  int retries;
+  int ret;
+
+  DEBUGASSERT(drvr != NULL && ep0info != NULL && req != NULL);
+
+  buflen = stm32_getle16(req->len);
+
+  uinfo("type=%02x req=%02x value=%04x index=%04x len=%04x\n",
+        req->type, req->req,
+        stm32_getle16(req->value),
+        stm32_getle16(req->index),
+        buflen);
+
+  ret = nxmutex_lock(&priv->lock);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  for (retries = 0; retries < STM32H5_RETRY_COUNT; retries++)
+    {
+      /* Send SETUP */
+
+      ret = stm32_ctrl_sendsetup(priv, ep0info, req);
+      if (ret < 0)
+        {
+          uerr("ERROR: SETUP failed: %d\n", ret);
+          continue;
+        }
+
+      /* Receive data */
+
+      if (buflen > 0)
+        {
+          ret = stm32_ctrl_recvdata(priv, ep0info, buffer, buflen);
+          if (ret < 0)
+            {
+              uerr("ERROR: Data IN failed: %d\n", ret);
+              continue;
+            }
+        }
+
+      /* Send status OUT */
+
+      ret = stm32_ctrl_senddata(priv, ep0info, NULL, 0);
+      if (ret < 0)
+        {
+          uerr("ERROR: Status OUT failed: %d\n", ret);
+          continue;
+        }
+
+      /* Success */
+
+      nxmutex_unlock(&priv->lock);
+      return OK;
+    }
+
+  nxmutex_unlock(&priv->lock);
+  return ret;
+}
+
+/****************************************************************************
+ * Name: stm32_ctrlout
+ *
+ * Description:
+ *   Control OUT transfer
+ *
+ ****************************************************************************/
+
+static int stm32_ctrlout(struct usbhost_driver_s *drvr, usbhost_ep_t ep0,
+                            const struct usb_ctrlreq_s *req,
+                            const uint8_t *buffer)
+{
+  struct stm32_usbhost_s *priv = (struct stm32_usbhost_s *)drvr;
+  struct stm32_ctrlinfo_s *ep0info = (struct stm32_ctrlinfo_s *)ep0;
+  uint16_t buflen;
+  int retries;
+  int ret;
+
+  DEBUGASSERT(drvr != NULL && ep0info != NULL && req != NULL);
+
+  buflen = stm32_getle16(req->len);
+
+  uinfo("type=%02x req=%02x value=%04x index=%04x len=%04x\n",
+        req->type, req->req,
+        stm32_getle16(req->value),
+        stm32_getle16(req->index),
+        buflen);
+
+  ret = nxmutex_lock(&priv->lock);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  for (retries = 0; retries < STM32H5_RETRY_COUNT; retries++)
+    {
+      /* Send SETUP */
+
+      ret = stm32_ctrl_sendsetup(priv, ep0info, req);
+      if (ret < 0)
+        {
+          uerr("ERROR: SETUP failed: %d\n", ret);
+          continue;
+        }
+
+      /* Send data */
+
+      if (buflen > 0)
+        {
+          ret = stm32_ctrl_senddata(priv, ep0info,
+                                       (uint8_t *)buffer, buflen);
+          if (ret < 0)
+            {
+              uerr("ERROR: Data OUT failed: %d\n", ret);
+              continue;
+            }
+        }
+
+      /* Receive status IN */
+
+      ret = stm32_ctrl_recvdata(priv, ep0info, NULL, 0);
+      if (ret < 0)
+        {
+          uerr("ERROR: Status IN failed: %d\n", ret);
+          continue;
+        }
+
+      nxmutex_unlock(&priv->lock);
+      return OK;
+    }
+
+  nxmutex_unlock(&priv->lock);
+  return ret;
+}
+
+/****************************************************************************
+ * Name: stm32_transfer
+ *
+ * Description:
+ *   Bulk/interrupt transfer
+ *
+ ****************************************************************************/
+
+static ssize_t stm32_transfer(struct usbhost_driver_s *drvr,
+                                 usbhost_ep_t ep, uint8_t *buffer,
+                                 size_t buflen)
+{
+  struct stm32_usbhost_s *priv = (struct stm32_usbhost_s *)drvr;
+  unsigned int chidx = (unsigned int)ep;
+  ssize_t nbytes;
+  int ret;
+
+  DEBUGASSERT(priv && buffer && chidx < STM32H5_NHOST_CHANNELS);
+
+  ret = nxmutex_lock(&priv->lock);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  if (priv->chan[chidx].in)
+    {
+      nbytes = stm32_in_transfer(priv, chidx, buffer, buflen);
+    }
+  else
+    {
+      nbytes = stm32_out_transfer(priv, chidx, buffer, buflen);
+      if (nbytes > 0 && (buflen % priv->chan[chidx].maxpacket == 0))
+        {
+          /* For OUT transfers that are a multiple of maxpacket,
+           * we need to send a zero-length packet to complete the transfer.
+           */
+
+          int zlp_ret = stm32_out_transfer(priv, chidx, NULL, 0);
+          if (zlp_ret < 0)
+            {
+              uerr("ERROR: Failed to send ZLP: %d\n", zlp_ret);
+              nbytes = zlp_ret;
+            }
+        }
+    }
+
+  nxmutex_unlock(&priv->lock);
+  return nbytes;
+}
+
+/****************************************************************************
+ * Name: stm32_asynch
+ *
+ * Description:
+ *   Async transfer
+ *
+ ****************************************************************************/
+
+#ifdef CONFIG_USBHOST_ASYNCH
+static int stm32_asynch(struct usbhost_driver_s *drvr, usbhost_ep_t ep,
+                           uint8_t *buffer, size_t buflen,
+                           usbhost_asynch_t callback, void *arg)
+{
+  struct stm32_usbhost_s *priv = (struct stm32_usbhost_s *)drvr;
+  unsigned int chidx = (unsigned int)ep;
+  struct stm32_chan_s *chan;
+  int ret;
+
+  DEBUGASSERT(priv && buffer && chidx < STM32H5_NHOST_CHANNELS);
+
+  ret = nxmutex_lock(&priv->lock);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  chan = &priv->chan[chidx];
+  chan->waiter = false;
+  chan->callback = callback;
+  chan->arg = arg;
+  chan->buffer = buffer;
+  chan->buflen = buflen;
+  chan->xfrd = 0;
+
+  stm32_transfer_start(priv, chidx);
+
+  nxmutex_unlock(&priv->lock);
+  return OK;
+}
+#endif
+
+/****************************************************************************
+ * Name: stm32_cancel
+ *
+ * Description:
+ *   Cancel a pending transfer
+ *
+ ****************************************************************************/
+
+static int stm32_cancel(struct usbhost_driver_s *drvr, usbhost_ep_t ep)
+{
+  struct stm32_usbhost_s *priv = (struct stm32_usbhost_s *)drvr;
+  unsigned int chidx = (unsigned int)ep;
+  struct stm32_chan_s *chan;
+  irqstate_t flags;
+
+  DEBUGASSERT(priv && chidx < STM32H5_NHOST_CHANNELS);
+
+  chan = &priv->chan[chidx];
+
+  flags = enter_critical_section();
+
+  if (chan->in)
+    {
+      stm32_set_chep_rx_status(priv, chidx, USB_CHEP_RX_STRX_DIS);
+    }
+  else
+    {
+      stm32_set_chep_tx_status(priv, chidx, USB_CHEP_TX_STTX_DIS);
+    }
+
+  chan->result = ECANCELED;
+  chan->chreason = CHREASON_CANCELLED;
+  stm32_chan_wakeup(priv, chan);
+
+  leave_critical_section(flags);
+  return OK;
+}
+
+/****************************************************************************
+ * Name: stm32_connect
+ *
+ * Description:
+ *   Hub port connection notification
+ *
+ ****************************************************************************/
+
+#ifdef CONFIG_USBHOST_HUB
+static int stm32_connect(struct usbhost_driver_s *drvr,
+                            struct usbhost_hubport_s *hport, bool connected)
+{
+  struct stm32_usbhost_s *priv = (struct stm32_usbhost_s *)drvr;
+  irqstate_t flags;
+
+  DEBUGASSERT(priv != NULL && hport != NULL);
+
+  flags = enter_critical_section();
+  priv->change = true;
+
+  if (priv->pscwait)
+    {
+      priv->pscwait = false;
+      nxsem_post(&priv->pscsem);
+    }
+
+  leave_critical_section(flags);
+  return OK;
+}
+#endif
+
+/****************************************************************************
+ * Name: stm32_disconnect
+ *
+ * Description:
+ *   Device disconnection notification
+ *
+ ****************************************************************************/
+
+static void stm32_disconnect(struct usbhost_driver_s *drvr,
+                                struct usbhost_hubport_s *hport)
+{
+  DEBUGASSERT(hport != NULL);
+  hport->devclass = NULL;
+}
+
+/****************************************************************************
+ * Name: stm32_portreset
+ *
+ * Description:
+ *   Reset the USB port
+ *
+ ****************************************************************************/
+
+static void stm32_portreset(struct stm32_usbhost_s *priv)
+{
+  uint32_t regval;
+
+  /* Force USB reset */
+
+  regval = stm32_getreg(STM32_USB_CNTR);
+  regval |= USB_CNTR_FRES;
+  stm32_putreg(STM32_USB_CNTR, regval);
+
+  /* Wait for reset */
+
+  nxsched_usleep(STM32H5_RESET_DELAY * 1000);
+
+  /* Release reset */
+
+  regval &= ~USB_CNTR_FRES;
+  stm32_putreg(STM32_USB_CNTR, regval);
+
+  /* Wait for device to be ready */
+
+  nxsched_usleep(30 * 1000);
+}
+
+/****************************************************************************
+ * Name: stm32_host_initialize
+ *
+ * Description:
+ *   Initialize the USB host hardware
+ *
+ ****************************************************************************/
+
+static void stm32_host_initialize(struct stm32_usbhost_s *priv)
+{
+  uint32_t regval;
+
+  UNUSED(priv);
+
+  /* Clear all pending interrupts */
+
+  stm32_putreg(STM32_USB_ISTR, 0);
+
+  /* Disable all interrupts initially (preserve HOST bit) */
+
+  regval = stm32_getreg(STM32_USB_CNTR);
+  regval &= ~(USB_CNTR_CTRM | USB_CNTR_PMAOVRN | USB_CNTR_ERRM |
+              USB_CNTR_WKUPM | USB_CNTR_SUSPM | USB_CNTR_RESETM |
+              USB_CNTR_SOFM | USB_CNTR_ESOFM | USB_CNTR_L1REQ |
+              USB_CNTR_DDISCM | USB_CNTR_THR512M);
+  stm32_putreg(STM32_USB_CNTR, regval);
+
+  /* Clear pending interrupts again */
+
+  stm32_putreg(STM32_USB_ISTR, 0);
+
+  /* Set pull-down on D+ for device detection (host mode) */
+
+  regval = stm32_getreg(STM32_USB_BCDR);
+  regval |= USB_BCDR_DPPD;
+  stm32_putreg(STM32_USB_BCDR, regval);
+
+  /* Enable host mode interrupts:
+   * - CTRM: Correct transfer
+   * - PMAOVRN: PMA overrun - not used
+   * - ERRM: Error
+   * - WKUPM: Wakeup - not used
+   * - SUSPM: Suspend - not used
+   * - RESETM/DCON: Device connection (in host mode, RESETM serves as DCON)
+   * - SOFM: Start of frame - not used
+   * - ESOFM: Expected start of frame - not used
+   * - L1REQ: LPM L1 request - not used
+   */
+
+  regval = stm32_getreg(STM32_USB_CNTR);
+  regval |= (USB_CNTR_CTRM | USB_CNTR_ERRM | USB_CNTR_RESETM);
+  stm32_putreg(STM32_USB_CNTR, regval);
+}
+
+/****************************************************************************
+ * Name: stm32_sw_initialize
+ *
+ * Description:
+ *   Initialize driver software state
+ *
+ ****************************************************************************/
+
+static void stm32_sw_initialize(struct stm32_usbhost_s *priv)
+{
+  struct usbhost_driver_s *drvr;
+  struct usbhost_hubport_s *hport;
+  int i;
+
+  /* Initialize driver interface */
+
+  drvr = &priv->drvr;
+  drvr->ep0configure = stm32_ep0configure;
+  drvr->epalloc = stm32_epalloc;
+  drvr->epfree = stm32_epfree;
+  drvr->alloc = stm32_alloc;
+  drvr->free = stm32_free;
+  drvr->ioalloc = stm32_ioalloc;
+  drvr->iofree = stm32_iofree;
+  drvr->ctrlin = stm32_ctrlin;
+  drvr->ctrlout = stm32_ctrlout;
+  drvr->transfer = stm32_transfer;
+#ifdef CONFIG_USBHOST_ASYNCH
+  drvr->asynch = stm32_asynch;
+#endif
+  drvr->cancel = stm32_cancel;
+#ifdef CONFIG_USBHOST_HUB
+  drvr->connect = stm32_connect;
+#endif
+  drvr->disconnect = stm32_disconnect;
+
+  /* Initialize root hub port */
+
+  hport = &priv->rhport.hport;
+  hport->drvr = drvr;
+#ifdef CONFIG_USBHOST_HUB
+  hport->parent = NULL;
+#endif
+  hport->ep0 = &priv->ep0;
+  hport->port = 0;
+  hport->speed = USB_SPEED_FULL;
+
+  /* Initialize function address generation logic */
+
+  usbhost_devaddr_initialize(&priv->devgen);
+  priv->rhport.pdevgen = &priv->devgen;
+
+  /* Initialize state */
+
+  priv->smstate = SMSTATE_DETACHED;
+  priv->connected = false;
+  priv->change = false;
+  priv->pscwait = false;
+
+  /* Initialize PMA allocation - all buffers available */
+
+  priv->pma_bufavail = STM32H5_PMA_BUFFER_ALLSET;
+
+  /* Initialize channels */
+
+  for (i = 0; i < STM32H5_NHOST_CHANNELS; i++)
+    {
+      priv->chan[i].chidx = i;
+      priv->chan[i].inuse = false;
+      priv->chan[i].pmabufno = STM32H5_PMA_BUFFER_NONE;
+      nxsem_init(&priv->chan[i].waitsem, 0, 0);
+    }
+}
+
+/****************************************************************************
+ * Name: stm32_hw_initialize
+ *
+ * Description:
+ *   Initialize the USB hardware
+ *
+ ****************************************************************************/
+
+static int stm32_hw_initialize(struct stm32_usbhost_s *priv)
+{
+  uint32_t regval;
+  int ret;
+
+  /* Enable VDDUSB supply - required for USB PHY operation */
+
+  putreg32(PWR_USBSCR_USB33DEN, STM32_PWR_USBSCR);
+
+  /* Wait for VDDUSB to be ready */
+
+  while ((getreg32(STM32_PWR_VMSR) & PWR_VMSR_USB33RDY) == 0)
+    {
+    }
+
+  /* Enable and validate USB supply */
+
+  putreg32(PWR_USBSCR_USB33DEN | PWR_USBSCR_USB33SV, STM32_PWR_USBSCR);
+
+  /* Configure USB GPIO pins (PA11=D-, PA12=D+) */
+
+  stm32_configgpio(GPIO_USB_DM);
+  stm32_configgpio(GPIO_USB_DP);
+
+  /* Enable USB clock via RCC */
+
+  regval = getreg32(STM32_RCC_APB2ENR);
+  regval |= RCC_APB2ENR_USBEN;
+  putreg32(regval, STM32_RCC_APB2ENR);
+
+  /* Wait for USB clock to stabilize */
+
+  nxsched_usleep(2000);
+
+  /* Perform USB core reset sequence:
+   * 1. Disable host mode
+   * 2. Set USBRST to reset the core
+   */
+
+  regval = stm32_getreg(STM32_USB_CNTR);
+  regval &= ~USB_CNTR_HOST;
+  regval |= USB_CNTR_FRES;  /* Force USB reset */
+  stm32_putreg(STM32_USB_CNTR, regval);
+
+  /* Clear all pending interrupts */
+
+  stm32_putreg(STM32_USB_ISTR, 0);
+
+  /* Exit power-down mode and release reset:
+   * Clear PDWN to enable the analog transceiver
+   * Clear FRES to release the USB reset
+   */
+
+  regval = stm32_getreg(STM32_USB_CNTR);
+  regval &= ~(USB_CNTR_PDWN | USB_CNTR_FRES);
+  stm32_putreg(STM32_USB_CNTR, regval);
+
+  /* Wait for analog circuits to stabilize (tSTARTUP) */
+
+  nxsched_usleep(1000);
+
+  /* Set host mode */
+
+  regval = stm32_getreg(STM32_USB_CNTR);
+  regval |= USB_CNTR_HOST;
+  stm32_putreg(STM32_USB_CNTR, regval);
+
+  /* Initialize host mode (interrupts, pull-downs, etc.) */
+
+  stm32_host_initialize(priv);
+
+  /* Attach interrupt handler */
+
+  ret = irq_attach(STM32_IRQ_USB_FS, stm32_usbdrd_interrupt, priv);
+  if (ret < 0)
+    {
+      uerr("ERROR: Failed to attach IRQ: %d\n", ret);
+      return ret;
+    }
+
+  /* Enable interrupt */
+
+  up_enable_irq(STM32_IRQ_USB_FS);
+
+  /* Enable VBUS drive */
+
+  stm32h5_usbhost_vbusdrive(0, true);
+
+  uinfo("USB Host initialized\n");
+
+  return OK;
+}
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: stm32h5_usbhost_initialize
+ *
+ * Description:
+ *   Initialize USB host controller
+ *
+ ****************************************************************************/
+
+struct usbhost_connection_s *stm32h5_usbhost_initialize(void)
+{
+  struct stm32_usbhost_s *priv = &g_usbhost;
+  int ret;
+
+  uinfo("Initializing USB DRD host controller\n");
+
+  /* Initialize software state */
+
+  stm32_sw_initialize(priv);
+
+  /* Initialize hardware */
+
+  ret = stm32_hw_initialize(priv);
+  if (ret < 0)
+    {
+      uerr("ERROR: Hardware initialization failed: %d\n", ret);
+      return NULL;
+    }
+
+  return &g_usbconn;
+}
+
+/****************************************************************************
+ * Name: stm32_usbhost_vbusdrive
+ *
+ * Description:
+ *   Control VBUS power
+ *   This is a weak function that should be overridden by board-specific code
+ *
+ ****************************************************************************/
+
+__attribute__((weak))
+void stm32_usbhost_vbusdrive(int port, bool enable)
+{
+  /* Default implementation - do nothing.
+   * Board-specific code should override this to control VBUS power.
+   */
+
+  uinfo("VBUS drive port=%d enable=%d (default - no-op)\n", port, enable);
+}
+
+#endif /* CONFIG_USBHOST && CONFIG_STM32H5_USBFS_HOST */
diff --git a/arch/arm/src/stm32h5/stm32_usbdrdhost.h 
b/arch/arm/src/stm32h5/stm32_usbdrdhost.h
new file mode 100644
index 00000000000..f96b56e0cb7
--- /dev/null
+++ b/arch/arm/src/stm32h5/stm32_usbdrdhost.h
@@ -0,0 +1,144 @@
+/****************************************************************************
+ * arch/arm/src/stm32h5/stm32_usbdrdhost.h
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.  The
+ * ASF licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ ****************************************************************************/
+
+#ifndef __ARCH_ARM_SRC_STM32H5_STM32_USBDRDHOST_H
+#define __ARCH_ARM_SRC_STM32H5_STM32_USBDRDHOST_H
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+#include <nuttx/usb/usbhost.h>
+#include <stdint.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* Configuration ************************************************************/
+
+/* Pre-requisites */
+
+#if !defined(CONFIG_STM32H5_USBFS_HOST)
+#  error "CONFIG_STM32H5_USBFS_HOST is required"
+#endif
+
+/* USB DRD Host Driver Configuration */
+
+#ifndef CONFIG_STM32H5_USBDRD_NCHANNELS
+#  define CONFIG_STM32H5_USBDRD_NCHANNELS 8
+#endif
+
+/* Default descriptor buffer size */
+
+#ifndef CONFIG_STM32H5_USBDRD_DESCSIZE
+#  define CONFIG_STM32H5_USBDRD_DESCSIZE 128
+#endif
+
+/****************************************************************************
+ * Public Types
+ ****************************************************************************/
+
+/* This structure defines the interface provided by the USB host controller
+ * to the board-level USB host logic.
+ */
+
+struct stm32h5_usbhost_connection_s
+{
+  /* Wait for device connection/disconnection */
+
+  struct usbhost_connection_s *conn;
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+#ifndef __ASSEMBLY__
+
+#undef EXTERN
+#if defined(__cplusplus)
+#define EXTERN extern "C"
+extern "C"
+{
+#else
+#define EXTERN extern
+#endif
+
+/****************************************************************************
+ * Public Function Prototypes
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: stm32h5_usbhost_initialize
+ *
+ * Description:
+ *   Initialize USB host controller hardware.
+ *
+ *   This function is called very early during system initialization to
+ *   initialize the USB host controller. The USB host controller is then
+ *   made available for use by the USB host driver.
+ *
+ * Input Parameters:
+ *   None
+ *
+ * Returned Value:
+ *   On success, a pointer to the usbhost_connection_s interface is returned.
+ *   On failure, NULL is returned.
+ *
+ ****************************************************************************/
+
+struct usbhost_connection_s *stm32h5_usbhost_initialize(void);
+
+/****************************************************************************
+ * Name: stm32h5_usbhost_vbusdrive
+ *
+ * Description:
+ *   Enable/disable VBUS power to the connected USB device.
+ *
+ *   The USB host driver calls this function during enumeration to control
+ *   VBUS power delivery to the device. This function should be implemented
+ *   by board-specific code since VBUS control is typically board-specific.
+ *
+ * Input Parameters:
+ *   port   - USB host port number (0-based)
+ *   enable - true: Enable VBUS power
+ *            false: Disable VBUS power
+ *
+ * Returned Value:
+ *   None
+ *
+ * Assumptions:
+ *   Called from the USB host driver during enumeration.
+ *
+ ****************************************************************************/
+
+void stm32h5_usbhost_vbusdrive(int port, bool enable);
+
+#undef EXTERN
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* __ASSEMBLY__ */
+#endif /* __ARCH_ARM_SRC_STM32H5_STM32_USBDRDHOST_H */
diff --git a/arch/arm/src/stm32h5/stm32h5xx_rcc.c 
b/arch/arm/src/stm32h5/stm32h5xx_rcc.c
index 78e38aec998..62f94162fbb 100644
--- a/arch/arm/src/stm32h5/stm32h5xx_rcc.c
+++ b/arch/arm/src/stm32h5/stm32h5xx_rcc.c
@@ -580,7 +580,7 @@ static inline void rcc_enableapb2(void)
   regval |= RCC_APB2ENR_SAI2EN;
 #endif
 
-#ifdef CONFIG_STM32H5_USBFS
+#if defined(CONFIG_STM32H5_USBFS) || defined(CONFIG_STM32H5_USBFS_HOST)
   /* USB clock enable */
 
   regval |= RCC_APB2ENR_USBEN;
diff --git a/boards/arm/stm32h5/nucleo-h563zi/configs/usbmsc/defconfig 
b/boards/arm/stm32h5/nucleo-h563zi/configs/usbmsc/defconfig
new file mode 100644
index 00000000000..64ae9cfffd1
--- /dev/null
+++ b/boards/arm/stm32h5/nucleo-h563zi/configs/usbmsc/defconfig
@@ -0,0 +1,57 @@
+#
+# This file is autogenerated: PLEASE DO NOT EDIT IT.
+#
+# You can use "make menuconfig" to make any modifications to the installed 
.config file.
+# You can then do "make savedefconfig" to generate a new defconfig file that 
includes your
+# modifications.
+#
+# CONFIG_NSH_ARGCAT is not set
+# CONFIG_STANDARD_SERIAL is not set
+CONFIG_ARCH="arm"
+CONFIG_ARCH_BOARD="nucleo-h563zi"
+CONFIG_ARCH_BOARD_NUCLEO_H563ZI=y
+CONFIG_ARCH_BUTTONS=y
+CONFIG_ARCH_CHIP="stm32h5"
+CONFIG_ARCH_CHIP_STM32H563ZI=y
+CONFIG_ARCH_CHIP_STM32H5=y
+CONFIG_ARCH_INTERRUPTSTACK=2048
+CONFIG_ARCH_STACKDUMP=y
+CONFIG_ARMV8M_STACKCHECK_NONE=y
+CONFIG_BOARD_LOOPSPERMSEC=9251
+CONFIG_BUILTIN=y
+CONFIG_DEBUG_ASSERTIONS=y
+CONFIG_DEBUG_FEATURES=y
+CONFIG_DEBUG_SYMBOLS=y
+CONFIG_DEBUG_USB=y
+CONFIG_DEBUG_USB_ERROR=y
+CONFIG_DEBUG_USB_WARN=y
+CONFIG_FS_FAT=y
+CONFIG_FS_PROCFS=y
+CONFIG_FS_PROCFS_REGISTER=y
+CONFIG_HAVE_CXX=y
+CONFIG_HAVE_CXXINITIALIZE=y
+CONFIG_IDLETHREAD_STACKSIZE=2048
+CONFIG_INIT_ENTRYPOINT="nsh_main"
+CONFIG_LINE_MAX=64
+CONFIG_NSH_ARCHINIT=y
+CONFIG_NSH_BUILTIN_APPS=y
+CONFIG_NSH_DISABLE_IFUPDOWN=y
+CONFIG_NSH_FILEIOSIZE=512
+CONFIG_NSH_READLINE=y
+CONFIG_PREALLOC_TIMERS=4
+CONFIG_RAM_SIZE=655360
+CONFIG_RAM_START=0x20000000
+CONFIG_RAW_BINARY=y
+CONFIG_READLINE_CMD_HISTORY=y
+CONFIG_READLINE_TABCOMPLETION=y
+CONFIG_RR_INTERVAL=200
+CONFIG_SCHED_LPWORK=y
+CONFIG_SCHED_WAITPID=y
+CONFIG_STACK_COLORATION=y
+CONFIG_STM32H5_USART3=y
+CONFIG_STM32H5_USBFS_HOST=y
+CONFIG_STM32H5_USE_HSE=y
+CONFIG_SYSTEM_NSH=y
+CONFIG_TASK_NAME_SIZE=0
+CONFIG_USART3_SERIAL_CONSOLE=y
+CONFIG_USBHOST_MSC=y
diff --git a/boards/arm/stm32h5/nucleo-h563zi/include/board.h 
b/boards/arm/stm32h5/nucleo-h563zi/include/board.h
index 60d84588720..11ff18fb037 100644
--- a/boards/arm/stm32h5/nucleo-h563zi/include/board.h
+++ b/boards/arm/stm32h5/nucleo-h563zi/include/board.h
@@ -36,6 +36,13 @@
  * Pre-processor Definitions
  ****************************************************************************/
 
+#if defined(CONFIG_STM32H5_USBFS_HOST) && !defined(CONFIG_STM32H5_USE_HSE)
+    #error "This board config requires HSE to use the USB HOST."
+    "HSI48 is not stable enough to use as a host."
+    "To use HSE on the nucleo-H563ZI,"
+    "you need to connect SB3/SB4 and disconnect SB49"
+#endif
+
 /* Clocking *****************************************************************/
 
 /* The Nucleo-H563ZI-Q supports using a HSE crystal (X3). It is shipped with
@@ -82,7 +89,7 @@
                                    STM32_PLLCFG_PLL1Q | \
                                    STM32_PLLCFG_PLL1R)
 
-#define STM32_VC01_FRQ            ((STM32_HSE_FREQUENCY / 5) * 100)
+#define STM32_VCO1_FRQ            ((STM32_HSE_FREQUENCY / 5) * 100)
 #define STM32_PLL1P_FREQUENCY     (STM32_VCO1_FRQ / 2)
 #define STM32_PLL1Q_FREQUENCY     (STM32_VCO1_FRQ / 4)
 #define STM32_PLL1R_FREQUENCY     (STM32_VCO1_FRQ / 2)
@@ -101,6 +108,34 @@
 #define STM32_VCO2_FRQ            ((STM32_HSE_FREQUENCY / 5) * 60)
 #define STM32_PLL2R_FREQUENCY     (STM32_VCO2_FRQ / 4)
 
+#if defined(CONFIG_STM32H5_USBFS_HOST)
+/* PLL3 config: Generate 48 MHz for USB from 25 MHz HSE.
+ * VCO input = 25 MHz / 5 = 5 MHz
+ * VCO output = 5 MHz * 96 = 480 MHz
+ * PLL3Q = 480 MHz / 10 = 48 MHz
+ */
+
+#define STM32_PLLCFG_PLL3CFG      (RCC_PLL3CFGR_PLL3SRC_HSE | \
+                                   RCC_PLL3CFGR_PLL3RGE_4_8M | \
+                                   RCC_PLL3CFGR_PLL3M(5) | \
+                                   RCC_PLL3CFGR_PLL3QEN)
+#define STM32_PLLCFG_PLL3N         RCC_PLL3DIVR_PLL3N(96)  /* VCO 480 MHz */
+#define STM32_PLLCFG_PLL3P         RCC_PLL3DIVR_PLL3P(2)   /* Not used */
+#define STM32_PLLCFG_PLL3Q         RCC_PLL3DIVR_PLL3Q(10)  /* 3Q 48 MHz */
+#define STM32_PLLCFG_PLL3R         RCC_PLL3DIVR_PLL3R(2)   /* Not used */
+#define STM32_PLLCFG_PLL3DIVR     (STM32_PLLCFG_PLL3N | \
+                                   STM32_PLLCFG_PLL3P | \
+                                   STM32_PLLCFG_PLL3Q | \
+                                   STM32_PLLCFG_PLL3R)
+
+#define STM32_VCO3_FRQ            ((STM32_HSE_FREQUENCY / 5) * 96)  /* 480 MHz 
*/
+#define STM32_PLL3Q_FREQUENCY     (STM32_VCO3_FRQ / 10)             /* 48 MHz 
*/
+
+/* Use PLL3Q (48 MHz) for USB - more stable than HSI48 */
+#define STM32H5_CLKUSB_SEL      RCC_CCIPR4_USBSEL_PLL3QCK
+
+#endif /* CONFIG_STM32H5_USBFS_HOST */
+
 #else
 
 #define STM32_BOARD_USEHSI       1
diff --git a/boards/arm/stm32h5/nucleo-h563zi/src/CMakeLists.txt 
b/boards/arm/stm32h5/nucleo-h563zi/src/CMakeLists.txt
index 9576df119db..4999c854abb 100644
--- a/boards/arm/stm32h5/nucleo-h563zi/src/CMakeLists.txt
+++ b/boards/arm/stm32h5/nucleo-h563zi/src/CMakeLists.txt
@@ -48,6 +48,10 @@ if(CONFIG_STM32H5_FDCAN)
   list(APPEND SRCS stm32_can.c)
 endif()
 
+if(CONFIG_STM32H5_USBFS_HOST)
+  list(APPEND SRCS stm32_usb.c)
+endif()
+
 target_sources(board PRIVATE ${SRCS})
 
 set_property(GLOBAL PROPERTY LD_SCRIPT "${NUTTX_BOARD_DIR}/scripts/flash.ld")
diff --git a/boards/arm/stm32h5/nucleo-h563zi/src/Makefile 
b/boards/arm/stm32h5/nucleo-h563zi/src/Makefile
index d0c78ae59e6..e3c0288e28e 100644
--- a/boards/arm/stm32h5/nucleo-h563zi/src/Makefile
+++ b/boards/arm/stm32h5/nucleo-h563zi/src/Makefile
@@ -55,4 +55,8 @@ ifeq ($(CONFIG_STM32H5_PWM),y)
 CSRCS += stm32_pwm.c
 endif
 
+ifeq ($(CONFIG_STM32H5_USBFS_HOST),y)
+CSRCS += stm32_usb.c
+endif
+
 include $(TOPDIR)/boards/Board.mk
diff --git a/boards/arm/stm32h5/nucleo-h563zi/src/nucleo-h563zi.h 
b/boards/arm/stm32h5/nucleo-h563zi/src/nucleo-h563zi.h
index d1c7ba9828f..860dec6449f 100644
--- a/boards/arm/stm32h5/nucleo-h563zi/src/nucleo-h563zi.h
+++ b/boards/arm/stm32h5/nucleo-h563zi/src/nucleo-h563zi.h
@@ -157,5 +157,9 @@ int stm32_can_setup(uint8_t port);
 int stm32_pwm_setup(void);
 #endif
 
+#ifdef CONFIG_USBHOST
+int stm32_usbhost_initialize(void);
+#endif
+
 #endif /* __ASSEMBLY__ */
 #endif /* __BOARDS_ARM_STM32H5_NUCLEO_H563ZI_SRC_NUCLEO_H563ZI_H */
diff --git a/boards/arm/stm32h5/nucleo-h563zi/src/stm32_bringup.c 
b/boards/arm/stm32h5/nucleo-h563zi/src/stm32_bringup.c
index c4d979ffa74..382f2768615 100644
--- a/boards/arm/stm32h5/nucleo-h563zi/src/stm32_bringup.c
+++ b/boards/arm/stm32h5/nucleo-h563zi/src/stm32_bringup.c
@@ -149,6 +149,15 @@ int stm32_bringup(void)
     }
 #endif
 
+#ifdef CONFIG_USBHOST
+  ret = stm32_usbhost_initialize();
+  if (ret < 0)
+    {
+      syslog(LOG_ERR, "ERROR: Failed to initialize USB host: %d\n", ret);
+      return ret;
+    }
+#endif
+
   UNUSED(ret);
   return OK;
 }
diff --git a/boards/arm/stm32h5/nucleo-h563zi/src/stm32_usb.c 
b/boards/arm/stm32h5/nucleo-h563zi/src/stm32_usb.c
new file mode 100644
index 00000000000..bed293d1a06
--- /dev/null
+++ b/boards/arm/stm32h5/nucleo-h563zi/src/stm32_usb.c
@@ -0,0 +1,226 @@
+/****************************************************************************
+ * boards/arm/stm32h5/nucleo-h563zi/src/stm32_usb.c
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.  The
+ * ASF licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ ****************************************************************************/
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include <sys/types.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <sched.h>
+#include <errno.h>
+#include <assert.h>
+#include <nuttx/debug.h>
+
+#include <nuttx/kthread.h>
+#include <nuttx/usb/usbhost.h>
+#include <nuttx/usb/usbdev_trace.h>
+
+#include "arm_internal.h"
+#include "stm32.h"
+#include "stm32_usbdrdhost.h"
+
+#ifdef CONFIG_STM32H5_USBFS_HOST
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#if !defined(CONFIG_USBHOST)
+#  warning "CONFIG_STM32_OTGFS is enabled but neither CONFIG_USBDEV nor 
CONFIG_USBHOST"
+#endif
+
+#ifndef CONFIG_USBHOST_DEFPRIO
+#  define CONFIG_USBHOST_DEFPRIO 100
+#endif
+
+#ifndef CONFIG_USBHOST_STACKSIZE
+#  define CONFIG_USBHOST_STACKSIZE 2048
+#endif
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+#ifdef CONFIG_USBHOST
+static struct usbhost_connection_s *g_usbconn;
+#endif
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: usbhost_waiter
+ *
+ * Description:
+ *   Wait for USB devices to be connected.
+ *
+ ****************************************************************************/
+
+#ifdef CONFIG_USBHOST
+static int usbhost_waiter(int argc, char *argv[])
+{
+  struct usbhost_hubport_s *hport;
+
+  uinfo("Running\n");
+  for (; ; )
+    {
+      /* Wait for the device to change state */
+
+      DEBUGVERIFY(CONN_WAIT(g_usbconn, &hport));
+      uinfo("%s\n", hport->connected ? "connected" : "disconnected");
+
+      /* Did we just become connected? */
+
+      if (hport->connected)
+        {
+          /* Yes.. enumerate the newly connected device */
+
+          CONN_ENUMERATE(g_usbconn, hport);
+        }
+    }
+
+  /* Keep the compiler from complaining */
+
+  return 0;
+}
+#endif
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: stm32_usbinitialize
+ *
+ * Description:
+ *   Called from stm32_usbinitialize very early in initialization to setup
+ *   USB-related GPIO pins for the STM32F411 board.
+ *
+ ****************************************************************************/
+
+void stm32_usbinitialize(void)
+{
+  /* The OTG FS has an internal soft pull-up.
+   * No GPIO configuration is required
+   */
+}
+
+/****************************************************************************
+ * Name: stm32_usbhost_initialize
+ *
+ * Description:
+ *   Called at application startup time to initialize the USB host
+ *   functionality.
+ *   This function will start a thread that will monitor for device
+ *   connection/disconnection events.
+ *
+ ****************************************************************************/
+
+#ifdef CONFIG_USBHOST
+int stm32_usbhost_initialize(void)
+{
+  int ret;
+
+  /* First, register all of the class drivers needed to support the drivers
+   * that we care about:
+   */
+
+  uinfo("Register class drivers\n");
+
+#ifdef CONFIG_USBHOST_MSC
+  /* Register the USB mass storage class class */
+
+  ret = usbhost_msc_initialize();
+  if (ret != OK)
+    {
+      uerr("ERROR: Failed to register the mass storage class: %d\n", ret);
+    }
+#endif
+
+  /* Then get an instance of the USB host interface */
+
+  uinfo("Initialize USB host\n");
+  g_usbconn = stm32h5_usbhost_initialize();
+  if (g_usbconn)
+    {
+      /* Start a thread to handle device connection. */
+
+      uinfo("Start usbhost_waiter\n");
+
+      ret = kthread_create("usbhost", CONFIG_USBHOST_DEFPRIO,
+                           CONFIG_USBHOST_STACKSIZE,
+                           usbhost_waiter, NULL);
+      return ret < 0 ? -ENOEXEC : OK;
+    }
+
+  return -ENODEV;
+}
+#endif
+
+/****************************************************************************
+ * Name: stm32_usbhost_vbusdrive
+ *
+ * Description:
+ *   Enable/disable driving of VBUS 5V output.  This function must be
+ *   provided be each platform that implements the STM32 OTG FS host
+ *   interface
+ *
+ *   "On-chip 5 V VBUS generation is not supported. For this reason, a
+ *    charge pump or, if 5 V are available on the application board, a
+ *    basic power switch, must be added externally to drive the 5 V VBUS
+ *    line. The external charge pump can be driven by any GPIO output.
+ *    When the application decides to power on VBUS using the chosen GPIO,
+ *    it must also set the port power bit in the host port control and
+ *    status register (PPWR bit in OTG_FS_HPRT).
+ *
+ *   "The application uses this field to control power to this port,
+ *    and the core clears this bit on an overcurrent condition."
+ *
+ * Input Parameters:
+ *   iface - For future growth to handle multiple USB host interface.
+ *           Should be zero.
+ *   enable - true: enable VBUS power; false: disable VBUS power
+ *
+ * Returned Value:
+ *   None
+ *
+ ****************************************************************************/
+
+#ifdef CONFIG_USBHOST
+void stm32h5_usbhost_vbusdrive(int port, bool enable)
+{
+  /* The Nucleo-h563zi doesn't have hardware for a vbus drive.
+   * Instead to get host working, you need to put an extra jumper
+   * on the "PWR SEL" to jump "STLK" and "USB USER".
+   * This effectively supplies 5V power form the STLink to the USB device.
+   * The power output is limited so only relatively low power
+   * devices can work.
+   */
+}
+#endif
+
+#endif /* CONFIG_STM32H5_USBFS_HOST */

Reply via email to