You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
449 lines
11 KiB
449 lines
11 KiB
|
5 months ago
|
/*
|
||
|
|
* Copyright (c) 2006-2025, RT-Thread Development Team
|
||
|
|
*
|
||
|
|
* SPDX-License-Identifier: Apache-2.0
|
||
|
|
*
|
||
|
|
* Change Logs:
|
||
|
|
* Date Author Notes
|
||
|
|
* 2025-11-07 RealThread the first version
|
||
|
|
*/
|
||
|
|
|
||
|
|
#include "drv_usb_hw.h"
|
||
|
|
#include "drv_usb_host.h"
|
||
|
|
|
||
|
|
#if defined(BSP_USING_USB) && defined(RT_USING_USB_HOST)
|
||
|
|
|
||
|
|
const uint32_t PIPE_DPID[2] = {
|
||
|
|
PIPE_DPID_DATA0,
|
||
|
|
PIPE_DPID_DATA1
|
||
|
|
};
|
||
|
|
|
||
|
|
/*!
|
||
|
|
\brief initializes USB core for host mode
|
||
|
|
\param[in] udev: pointer to selected USB host
|
||
|
|
\param[out] none
|
||
|
|
\retval operation status
|
||
|
|
*/
|
||
|
|
usb_status usb_host_init(usb_core_driver *udev)
|
||
|
|
{
|
||
|
|
uint32_t i = 0U, inten = 0U;
|
||
|
|
|
||
|
|
uint32_t nptxfifolen = 0U;
|
||
|
|
uint32_t ptxfifolen = 0U;
|
||
|
|
|
||
|
|
/* restart the PHY Clock */
|
||
|
|
*udev->regs.PWRCLKCTL = 0U;
|
||
|
|
|
||
|
|
/* support HS, FS and LS */
|
||
|
|
udev->regs.hr->HCTL &= ~HCTL_SPDFSLS;
|
||
|
|
|
||
|
|
/* set RX FIFO size */
|
||
|
|
udev->regs.gr->GRFLEN = USB_RX_FIFO_SIZE;
|
||
|
|
|
||
|
|
/* set non-periodic TX FIFO size and address */
|
||
|
|
nptxfifolen |= USB_RX_FIFO_SIZE;
|
||
|
|
nptxfifolen |= USB_HTX_NPFIFO_SIZE << 16;
|
||
|
|
udev->regs.gr->DIEP0TFLEN_HNPTFLEN = nptxfifolen;
|
||
|
|
|
||
|
|
/* set periodic TX FIFO size and address */
|
||
|
|
ptxfifolen |= USB_RX_FIFO_SIZE + USB_HTX_PFIFO_SIZE;
|
||
|
|
ptxfifolen |= USB_HTX_PFIFO_SIZE << 16;
|
||
|
|
udev->regs.gr->HPTFLEN = ptxfifolen;
|
||
|
|
|
||
|
|
#ifdef USE_OTG_MODE
|
||
|
|
/* clear host set HNP enable in the usb_otg control register */
|
||
|
|
udev->regs.gr->GOTGCS &= ~GOTGCS_HHNPEN;
|
||
|
|
#endif /* USE_OTG_MODE */
|
||
|
|
|
||
|
|
/* make sure the FIFOs are flushed */
|
||
|
|
|
||
|
|
/* flush all TX FIFOs in device or host mode */
|
||
|
|
usb_txfifo_flush(&udev->regs, 0x10U);
|
||
|
|
|
||
|
|
/* flush the entire RX FIFO */
|
||
|
|
usb_rxfifo_flush(&udev->regs);
|
||
|
|
|
||
|
|
/* disable all interrupts */
|
||
|
|
udev->regs.gr->GINTEN = 0U;
|
||
|
|
|
||
|
|
/* clear any pending USB OTG interrupts */
|
||
|
|
udev->regs.gr->GOTGINTF = 0xFFFFFFFFU;
|
||
|
|
|
||
|
|
/* enable the USB wakeup and suspend interrupts */
|
||
|
|
udev->regs.gr->GINTF = 0xBFFFFFFFU;
|
||
|
|
|
||
|
|
/* clear all pending host channel interrupts */
|
||
|
|
for (i = 0U; i < udev->bp.num_pipe; i++)
|
||
|
|
{
|
||
|
|
udev->regs.pr[i]->HCHINTF = 0xFFFFFFFFU;
|
||
|
|
udev->regs.pr[i]->HCHINTEN = 0U;
|
||
|
|
}
|
||
|
|
|
||
|
|
#ifndef USE_OTG_MODE
|
||
|
|
usb_portvbus_switch(udev, 1U);
|
||
|
|
#endif /* USE_OTG_MODE */
|
||
|
|
|
||
|
|
udev->regs.gr->GINTEN = GINTEN_WKUPIE | GINTEN_SPIE;
|
||
|
|
|
||
|
|
/* enable host_mode-related interrupts */
|
||
|
|
if (USB_USE_FIFO == udev->bp.transfer_mode)
|
||
|
|
{
|
||
|
|
inten = GINTEN_RXFNEIE;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* configure USBHS interrupts */
|
||
|
|
inten |= GINTEN_HPIE | GINTEN_HCIE | GINTEN_ISOINCIE;
|
||
|
|
udev->regs.gr->GINTEN |= inten;
|
||
|
|
|
||
|
|
inten = GINTEN_DISCIE | GINTEN_SOFIE;
|
||
|
|
udev->regs.gr->GINTEN &= ~inten;
|
||
|
|
|
||
|
|
return USB_OK;
|
||
|
|
}
|
||
|
|
|
||
|
|
/*!
|
||
|
|
\brief control the VBUS to power
|
||
|
|
\param[in] udev: pointer to selected USB host
|
||
|
|
\param[in] state: VBUS state
|
||
|
|
\param[out] none
|
||
|
|
\retval none
|
||
|
|
*/
|
||
|
|
void usb_portvbus_switch(usb_core_driver *udev, uint8_t state)
|
||
|
|
{
|
||
|
|
uint32_t port = 0U;
|
||
|
|
|
||
|
|
/* enable or disable the external charge pump */
|
||
|
|
usb_vbus_drive(state);
|
||
|
|
|
||
|
|
/* turn on the host port power. */
|
||
|
|
port = usb_port_read(udev);
|
||
|
|
|
||
|
|
if ((!(port & HPCS_PP)) && (1U == state))
|
||
|
|
{
|
||
|
|
port |= HPCS_PP;
|
||
|
|
}
|
||
|
|
|
||
|
|
if ((port & HPCS_PP) && (0U == state))
|
||
|
|
{
|
||
|
|
port &= ~HPCS_PP;
|
||
|
|
}
|
||
|
|
|
||
|
|
*udev->regs.HPCS = port;
|
||
|
|
|
||
|
|
usb_mdelay(200U);
|
||
|
|
}
|
||
|
|
|
||
|
|
/*!
|
||
|
|
\brief reset host port
|
||
|
|
\param[in] udev: pointer to USB device
|
||
|
|
\param[out] none
|
||
|
|
\retval operation status
|
||
|
|
*/
|
||
|
|
uint32_t usb_port_reset(usb_core_driver *udev)
|
||
|
|
{
|
||
|
|
__IO uint32_t port = usb_port_read(udev);
|
||
|
|
|
||
|
|
*udev->regs.HPCS = port | HPCS_PRST;
|
||
|
|
|
||
|
|
usb_mdelay(20U); /* see note */
|
||
|
|
|
||
|
|
*udev->regs.HPCS = port & ~HPCS_PRST;
|
||
|
|
|
||
|
|
usb_mdelay(20U);
|
||
|
|
|
||
|
|
return 1U;
|
||
|
|
}
|
||
|
|
|
||
|
|
/*!
|
||
|
|
\brief initialize host pipe
|
||
|
|
\param[in] udev: pointer to USB device
|
||
|
|
\param[in] pipe_num: host pipe number which is in (0..7)
|
||
|
|
\param[out] none
|
||
|
|
\retval operation status
|
||
|
|
*/
|
||
|
|
usb_status usb_pipe_init(usb_core_driver *udev, uint8_t pipe_num)
|
||
|
|
{
|
||
|
|
usb_status status = USB_OK;
|
||
|
|
|
||
|
|
__IO uint32_t pp_ctl = 0U;
|
||
|
|
__IO uint32_t pp_inten = HCHINTEN_TFIE;
|
||
|
|
|
||
|
|
usb_pipe *pp = &udev->host.pipe[pipe_num];
|
||
|
|
|
||
|
|
/* clear old interrupt conditions for this host channel */
|
||
|
|
udev->regs.pr[pipe_num]->HCHINTF = 0xFFFFFFFFU;
|
||
|
|
|
||
|
|
if (USB_USE_DMA == udev->bp.transfer_mode)
|
||
|
|
{
|
||
|
|
pp_inten |= HCHINTEN_DMAERIE;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (pp->ep.dir)
|
||
|
|
{
|
||
|
|
pp_inten |= HCHINTEN_BBERIE;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* enable channel interrupts required for this transfer */
|
||
|
|
switch (pp->ep.type)
|
||
|
|
{
|
||
|
|
case USB_EPTYPE_CTRL:
|
||
|
|
case USB_EPTYPE_BULK:
|
||
|
|
pp_inten |= HCHINTEN_STALLIE | HCHINTEN_USBERIE | HCHINTEN_DTERIE | HCHINTEN_NAKIE;
|
||
|
|
|
||
|
|
if (!pp->ep.dir)
|
||
|
|
{
|
||
|
|
if (PORT_SPEED_HIGH == pp->dev_speed)
|
||
|
|
{
|
||
|
|
pp_inten |= HCHINTEN_NYETIE;
|
||
|
|
pp_inten |= HCHINTEN_ACKIE;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
|
||
|
|
case USB_EPTYPE_INTR:
|
||
|
|
pp_inten |= HCHINTEN_STALLIE | HCHINTEN_USBERIE | HCHINTEN_DTERIE | HCHINTEN_NAKIE | HCHINTEN_REQOVRIE;
|
||
|
|
break;
|
||
|
|
|
||
|
|
case USB_EPTYPE_ISOC:
|
||
|
|
pp_inten |= HCHINTEN_REQOVRIE | HCHINTEN_ACKIE;
|
||
|
|
|
||
|
|
if (pp->ep.dir)
|
||
|
|
{
|
||
|
|
pp_inten |= HCHINTEN_USBERIE;
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
|
||
|
|
default:
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
udev->regs.pr[pipe_num]->HCHINTEN = pp_inten;
|
||
|
|
|
||
|
|
/* enable the top level host channel interrupt */
|
||
|
|
udev->regs.hr->HACHINTEN |= 1U << pipe_num;
|
||
|
|
|
||
|
|
/* make sure host channel interrupts are enabled */
|
||
|
|
udev->regs.gr->GINTEN |= GINTEN_HCIE;
|
||
|
|
|
||
|
|
/* program the host channel control register */
|
||
|
|
pp_ctl |= PIPE_CTL_DAR(pp->dev_addr);
|
||
|
|
pp_ctl |= PIPE_CTL_EPNUM(pp->ep.num);
|
||
|
|
pp_ctl |= PIPE_CTL_EPDIR(pp->ep.dir);
|
||
|
|
pp_ctl |= PIPE_CTL_EPTYPE(pp->ep.type);
|
||
|
|
pp_ctl |= PIPE_CTL_LSD(PORT_SPEED_LOW == pp->dev_speed);
|
||
|
|
|
||
|
|
pp_ctl |= pp->ep.mps;
|
||
|
|
pp_ctl |= ((uint32_t)(USB_EPTYPE_INTR == pp->ep.type) << 29) & HCHCTL_ODDFRM;
|
||
|
|
|
||
|
|
udev->regs.pr[pipe_num]->HCHCTL = pp_ctl;
|
||
|
|
|
||
|
|
return status;
|
||
|
|
}
|
||
|
|
|
||
|
|
/*!
|
||
|
|
\brief prepare host channel for transferring packets
|
||
|
|
\param[in] udev: pointer to USB device
|
||
|
|
\param[in] pipe_num: host pipe number which is in (0..7)
|
||
|
|
\param[out] none
|
||
|
|
\retval operation status
|
||
|
|
*/
|
||
|
|
usb_status usb_pipe_xfer(usb_core_driver *udev, uint8_t pipe_num)
|
||
|
|
{
|
||
|
|
usb_status status = USB_OK;
|
||
|
|
|
||
|
|
uint16_t dword_len = 0U;
|
||
|
|
uint16_t packet_count = 0U;
|
||
|
|
|
||
|
|
__IO uint32_t pp_ctl = 0U;
|
||
|
|
|
||
|
|
usb_pipe *pp = &udev->host.pipe[pipe_num];
|
||
|
|
|
||
|
|
uint16_t max_packet_len = pp->ep.mps;
|
||
|
|
|
||
|
|
/* compute the expected number of packets associated to the transfer */
|
||
|
|
if (pp->xfer_len > 0U)
|
||
|
|
{
|
||
|
|
packet_count = (uint16_t)((pp->xfer_len + max_packet_len - 1U) / max_packet_len);
|
||
|
|
|
||
|
|
if (packet_count > HC_MAX_PACKET_COUNT)
|
||
|
|
{
|
||
|
|
packet_count = HC_MAX_PACKET_COUNT;
|
||
|
|
pp->xfer_len = (uint16_t)(packet_count * max_packet_len);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
packet_count = 1U;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (pp->ep.dir)
|
||
|
|
{
|
||
|
|
pp->xfer_len = (uint16_t)(packet_count * max_packet_len);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* initialize the host channel transfer information */
|
||
|
|
udev->regs.pr[pipe_num]->HCHLEN = pp->xfer_len | pp->DPID | PIPE_XFER_PCNT(packet_count);
|
||
|
|
|
||
|
|
if (USB_USE_DMA == udev->bp.transfer_mode)
|
||
|
|
{
|
||
|
|
udev->regs.pr[pipe_num]->HCHDMAADDR = (unsigned int)pp->xfer_buf;
|
||
|
|
}
|
||
|
|
|
||
|
|
pp_ctl = udev->regs.pr[pipe_num]->HCHCTL;
|
||
|
|
|
||
|
|
if (usb_frame_even(udev))
|
||
|
|
{
|
||
|
|
pp_ctl |= HCHCTL_ODDFRM;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
pp_ctl &= ~HCHCTL_ODDFRM;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* set host channel enabled */
|
||
|
|
pp_ctl |= HCHCTL_CEN;
|
||
|
|
pp_ctl &= ~HCHCTL_CDIS;
|
||
|
|
|
||
|
|
udev->regs.pr[pipe_num]->HCHCTL = pp_ctl;
|
||
|
|
|
||
|
|
if (USB_USE_FIFO == udev->bp.transfer_mode)
|
||
|
|
{
|
||
|
|
if ((0U == pp->ep.dir) && (pp->xfer_len > 0U))
|
||
|
|
{
|
||
|
|
switch (pp->ep.type)
|
||
|
|
{
|
||
|
|
/* non-periodic transfer */
|
||
|
|
case USB_EPTYPE_CTRL:
|
||
|
|
case USB_EPTYPE_BULK:
|
||
|
|
dword_len = (uint16_t)((pp->xfer_len + 3U) / 4U);
|
||
|
|
|
||
|
|
/* check if there is enough space in FIFO space */
|
||
|
|
if (dword_len > (udev->regs.gr->HNPTFQSTAT & HNPTFQSTAT_NPTXFS))
|
||
|
|
{
|
||
|
|
/* need to process data in nptxfempty interrupt */
|
||
|
|
udev->regs.gr->GINTEN |= GINTEN_NPTXFEIE;
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
|
||
|
|
/* periodic transfer */
|
||
|
|
case USB_EPTYPE_INTR:
|
||
|
|
case USB_EPTYPE_ISOC:
|
||
|
|
dword_len = (uint16_t)((pp->xfer_len + 3U) / 4U);
|
||
|
|
|
||
|
|
/* check if there is enough space in FIFO space */
|
||
|
|
if (dword_len > (udev->regs.hr->HPTFQSTAT & HPTFQSTAT_PTXFS))
|
||
|
|
{
|
||
|
|
/* need to process data in ptxfempty interrupt */
|
||
|
|
udev->regs.gr->GINTEN |= GINTEN_PTXFEIE;
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
|
||
|
|
default:
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* write packet into the TX FIFO */
|
||
|
|
usb_txfifo_write(&udev->regs, pp->xfer_buf, pipe_num, (uint16_t)pp->xfer_len);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return status;
|
||
|
|
}
|
||
|
|
|
||
|
|
/*!
|
||
|
|
\brief halt pipe
|
||
|
|
\param[in] udev: pointer to USB device
|
||
|
|
\param[in] pipe_num: host pipe number which is in (0..7)
|
||
|
|
\param[out] none
|
||
|
|
\retval operation status
|
||
|
|
*/
|
||
|
|
usb_status usb_pipe_halt(usb_core_driver *udev, uint8_t pipe_num)
|
||
|
|
{
|
||
|
|
__IO uint32_t pp_ctl = udev->regs.pr[pipe_num]->HCHCTL;
|
||
|
|
|
||
|
|
uint8_t ep_type = (uint8_t)((pp_ctl & HCHCTL_EPTYPE) >> 18U);
|
||
|
|
|
||
|
|
pp_ctl |= HCHCTL_CEN | HCHCTL_CDIS;
|
||
|
|
|
||
|
|
switch (ep_type)
|
||
|
|
{
|
||
|
|
case USB_EPTYPE_CTRL:
|
||
|
|
case USB_EPTYPE_BULK:
|
||
|
|
if (0U == (udev->regs.gr->HNPTFQSTAT & HNPTFQSTAT_NPTXFS))
|
||
|
|
{
|
||
|
|
pp_ctl &= ~HCHCTL_CEN;
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
|
||
|
|
case USB_EPTYPE_INTR:
|
||
|
|
case USB_EPTYPE_ISOC:
|
||
|
|
if (0U == (udev->regs.hr->HPTFQSTAT & HPTFQSTAT_PTXFS))
|
||
|
|
{
|
||
|
|
pp_ctl &= ~HCHCTL_CEN;
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
|
||
|
|
default:
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
udev->regs.pr[pipe_num]->HCHCTL = pp_ctl;
|
||
|
|
|
||
|
|
return USB_OK;
|
||
|
|
}
|
||
|
|
|
||
|
|
/*!
|
||
|
|
\brief configure host pipe to do ping operation
|
||
|
|
\param[in] udev: pointer to USB device
|
||
|
|
\param[in] pipe_num: host pipe number which is in (0..7)
|
||
|
|
\param[out] none
|
||
|
|
\retval operation status
|
||
|
|
*/
|
||
|
|
usb_status usb_pipe_ping(usb_core_driver *udev, uint8_t pipe_num)
|
||
|
|
{
|
||
|
|
uint32_t pp_ctl = 0U;
|
||
|
|
|
||
|
|
udev->regs.pr[pipe_num]->HCHLEN = HCHLEN_PING;
|
||
|
|
|
||
|
|
pp_ctl = udev->regs.pr[pipe_num]->HCHCTL;
|
||
|
|
|
||
|
|
pp_ctl |= HCHCTL_CEN;
|
||
|
|
pp_ctl &= ~HCHCTL_CDIS;
|
||
|
|
|
||
|
|
udev->regs.pr[pipe_num]->HCHCTL = pp_ctl;
|
||
|
|
|
||
|
|
return USB_OK;
|
||
|
|
}
|
||
|
|
|
||
|
|
/*!
|
||
|
|
\brief stop the USB host and clean up FIFO
|
||
|
|
\param[in] udev: pointer to USB device
|
||
|
|
\param[out] none
|
||
|
|
\retval none
|
||
|
|
*/
|
||
|
|
void usb_host_stop(usb_core_driver *udev)
|
||
|
|
{
|
||
|
|
uint32_t i;
|
||
|
|
__IO uint32_t pp_ctl = 0U;
|
||
|
|
|
||
|
|
udev->regs.hr->HACHINTEN = 0x0U;
|
||
|
|
udev->regs.hr->HACHINT = 0xFFFFFFFFU;
|
||
|
|
|
||
|
|
/* flush out any leftover queued requests. */
|
||
|
|
for (i = 0U; i < udev->bp.num_pipe; i++)
|
||
|
|
{
|
||
|
|
pp_ctl = udev->regs.pr[i]->HCHCTL;
|
||
|
|
|
||
|
|
pp_ctl &= ~(HCHCTL_CEN | HCHCTL_EPDIR);
|
||
|
|
pp_ctl |= HCHCTL_CDIS;
|
||
|
|
|
||
|
|
udev->regs.pr[i]->HCHCTL = pp_ctl;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* flush the FIFO */
|
||
|
|
usb_rxfifo_flush(&udev->regs);
|
||
|
|
usb_txfifo_flush(&udev->regs, 0x10U);
|
||
|
|
}
|
||
|
|
|
||
|
|
#endif /* BSP_USING_USB && RT_USING_USB_HOST */
|