Sync betaflight to Gitea

This commit is contained in:
2026-08-03 16:37:10 +08:00
commit ad3163cf91
4712 changed files with 3301380 additions and 0 deletions
@@ -0,0 +1,371 @@
/*!
\file drv_usb_core.c
\brief USB core driver which can operate in host and device mode
\version 2024-12-20, V3.3.1, firmware for GD32F4xx
*/
/*
Copyright (c) 2024, GigaDevice Semiconductor Inc.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
OF SUCH DAMAGE.
*/
#include "drv_usb_core.h"
#include "drv_usb_hw.h"
/* local function prototypes ('static') */
static void usb_core_reset(usb_core_regs *usb_regs);
/*!
\brief configure USB core basic
\param[in] usb_basic: pointer to USB capabilities
\param[in] usb_regs: USB core registers
\param[in] usb_core: USB core
\param[out] none
\retval operation status
*/
usb_status usb_basic_init(usb_core_basic *usb_basic, \
usb_core_regs *usb_regs, \
usb_core_enum usb_core)
{
/* configure USB default transfer mode as FIFO mode */
usb_basic->transfer_mode = (uint8_t)USB_USE_FIFO;
/* USB default speed is full-speed */
usb_basic->core_speed = (uint8_t)USB_SPEED_FULL;
usb_basic->core_enum = (uint8_t)usb_core;
switch(usb_core) {
case USB_CORE_ENUM_HS:
usb_basic->base_reg = (uint32_t)USBHS_REG_BASE;
/* set the host channel numbers */
usb_basic->num_pipe = USBHS_MAX_CHANNEL_COUNT;
/* set the device endpoint numbers */
usb_basic->num_ep = USBHS_MAX_EP_COUNT;
#ifdef USB_ULPI_PHY_ENABLED
usb_basic->phy_itf = USB_ULPI_PHY;
#else
usb_basic->phy_itf = USB_EMBEDDED_PHY;
#endif /* USB_ULPI_PHY_ENABLED */
#ifdef USB_HS_INTERNAL_DMA_ENABLED
usb_basic->transfer_mode = USB_USE_DMA;
#endif /* USB_HS_INTERNAL_DMA_ENABLED */
#ifdef USB_HS_CORE
/* configure the SOF output and the low power support */
usb_basic->sof_enable = USBHS_SOF_OUTPUT;
usb_basic->low_power = USBHS_LOW_POWER;
#endif /* USB_HS_CORE */
break;
case USB_CORE_ENUM_FS:
usb_basic->base_reg = (uint32_t)USBFS_REG_BASE;
/* set the host channel numbers */
usb_basic->num_pipe = USBFS_MAX_CHANNEL_COUNT;
/* set the device endpoint numbers */
usb_basic->num_ep = USBFS_MAX_EP_COUNT;
/* USBFS core use embedded physical layer */
usb_basic->phy_itf = USB_EMBEDDED_PHY;
#ifdef USB_FS_CORE
/* configure the SOF output and the low power support */
usb_basic->sof_enable = USBFS_SOF_OUTPUT;
usb_basic->low_power = USBFS_LOW_POWER;
#endif /* USB_FS_CORE */
break;
default:
return USB_FAIL;
}
/* assign main registers address */
*usb_regs = (usb_core_regs) {
.gr = (usb_gr *)(usb_basic->base_reg + USB_REG_OFFSET_CORE),
.hr = (usb_hr *)(usb_basic->base_reg + USB_REG_OFFSET_HOST),
.dr = (usb_dr *)(usb_basic->base_reg + USB_REG_OFFSET_DEV),
.HPCS = (uint32_t *)(usb_basic->base_reg + USB_REG_OFFSET_PORT),
.PWRCLKCTL = (uint32_t *)(usb_basic->base_reg + USB_REG_OFFSET_PWRCLKCTL)
};
/* assign device endpoint registers address */
for(uint8_t i = 0U; i < usb_basic->num_ep; i++) {
usb_regs->er_in[i] = (usb_erin *) \
(usb_basic->base_reg + USB_REG_OFFSET_EP_IN + (i * USB_REG_OFFSET_EP));
usb_regs->er_out[i] = (usb_erout *)\
(usb_basic->base_reg + USB_REG_OFFSET_EP_OUT + (i * USB_REG_OFFSET_EP));
}
/* assign host pipe registers address */
for(uint8_t i = 0U; i < usb_basic->num_pipe; i++) {
usb_regs->pr[i] = (usb_pr *) \
(usb_basic->base_reg + USB_REG_OFFSET_CH_INOUT + (i * USB_REG_OFFSET_CH));
usb_regs->DFIFO[i] = (uint32_t *) \
(usb_basic->base_reg + USB_DATA_FIFO_OFFSET + (i * USB_DATA_FIFO_SIZE));
}
return USB_OK;
}
/*!
\brief initializes the USB controller registers and
prepares the core device mode or host mode operation
\param[in] usb_basic: pointer to USB capabilities
\param[in] usb_regs: pointer to USB core registers
\param[out] none
\retval operation status
*/
usb_status usb_core_init(usb_core_basic usb_basic, usb_core_regs *usb_regs)
{
if(USB_ULPI_PHY == usb_basic.phy_itf) {
usb_regs->gr->GCCFG &= ~GCCFG_PWRON;
if(usb_basic.sof_enable) {
usb_regs->gr->GCCFG |= GCCFG_SOFOEN;
}
/* initialize the ULPI interface */
usb_regs->gr->GUSBCS &= ~(GUSBCS_EMBPHY | GUSBCS_ULPIEOI);
#ifdef USBHS_EXTERNAL_VBUS_ENABLED
/* use external VBUS driver */
usb_regs->gr->GUSBCS |= GUSBCS_ULPIEVD;
#else
/* use internal VBUS driver */
usb_regs->gr->GUSBCS &= ~GUSBCS_ULPIEVD;
#endif /* USBHS_EXTERNAL_VBUS_ENABLED */
/* soft reset the core */
usb_core_reset(usb_regs);
} else {
usb_regs->gr->GUSBCS |= GUSBCS_EMBPHY;
/* soft reset the core */
usb_core_reset(usb_regs);
/* active the transceiver and enable VBUS sensing */
usb_regs->gr->GCCFG |= GCCFG_PWRON | GCCFG_VBUSACEN | GCCFG_VBUSBCEN;
#ifndef VBUS_SENSING_ENABLED
usb_regs->gr->GCCFG |= GCCFG_VBUSIG;
#endif /* VBUS_SENSING_ENABLED */
/* enable SOF output */
if(usb_basic.sof_enable) {
usb_regs->gr->GCCFG |= GCCFG_SOFOEN;
}
usb_mdelay(20U);
}
if((uint8_t)USB_USE_DMA == usb_basic.transfer_mode) {
usb_regs->gr->GAHBCS &= ~GAHBCS_BURST;
usb_regs->gr->GAHBCS |= DMA_INCR8 | GAHBCS_DMAEN;
}
#ifdef USE_OTG_MODE
/* enable USB OTG features */
usb_regs->gr->GUSBCS |= GUSBCS_HNPCEN | GUSBCS_SRPCEN;
/* enable the USB wakeup and suspend interrupts */
usb_regs->gr->GINTF = 0xBFFFFFFFU;
usb_regs->gr->GINTEN = GINTEN_WKUPIE | GINTEN_SPIE | \
GINTEN_OTGIE | GINTEN_SESIE | GINTEN_CIDPSCIE;
#endif /* USE_OTG_MODE */
return USB_OK;
}
/*!
\brief write a packet into the TX FIFO associated with the endpoint
\param[in] usb_regs: pointer to USB core registers
\param[in] src_buf: pointer to source buffer
\param[in] fifo_num: FIFO number which is in (0..3 or 0..5)
\param[in] byte_count: packet byte count
\param[out] none
\retval operation status
*/
usb_status usb_txfifo_write(usb_core_regs *usb_regs, \
uint8_t *src_buf, \
uint8_t fifo_num, \
uint16_t byte_count)
{
uint32_t word_count = (byte_count + 3U) / 4U;
__IO uint32_t *fifo = usb_regs->DFIFO[fifo_num];
while(word_count-- > 0U) {
*fifo = *((uint32_t *)src_buf);
src_buf += 4U;
}
return USB_OK;
}
/*!
\brief read a packet from the RX FIFO associated with the endpoint
\param[in] usb_regs: pointer to USB core registers
\param[in] dest_buf: pointer to destination buffer
\param[in] byte_count: packet byte count
\param[out] none
\retval void type pointer
*/
void *usb_rxfifo_read(usb_core_regs *usb_regs, uint8_t *dest_buf, uint16_t byte_count)
{
uint32_t word_count = (byte_count + 3U) / 4U;
__IO uint32_t *fifo = usb_regs->DFIFO[0];
while(word_count-- > 0U) {
*(uint32_t *)dest_buf = *fifo;
dest_buf += 4U;
}
return ((void *)dest_buf);
}
/*!
\brief flush a TX FIFO or all TX FIFOs
\param[in] usb_regs: pointer to USB core registers
\param[in] fifo_num: FIFO number which is in (0..3 or 0..5)
\param[out] none
\retval operation status
*/
usb_status usb_txfifo_flush(usb_core_regs *usb_regs, uint8_t fifo_num)
{
usb_regs->gr->GRSTCTL = ((uint32_t)fifo_num << 6) | GRSTCTL_TXFF;
/* wait for TX FIFO flush bit is set */
while(usb_regs->gr->GRSTCTL & GRSTCTL_TXFF) {
/* no operation */
}
/* wait for 3 PHY clocks*/
usb_udelay(3U);
return USB_OK;
}
/*!
\brief flush the entire RX FIFO
\param[in] usb_regs: pointer to USB core registers
\param[out] none
\retval operation status
*/
usb_status usb_rxfifo_flush(usb_core_regs *usb_regs)
{
usb_regs->gr->GRSTCTL = GRSTCTL_RXFF;
/* wait for RX FIFO flush bit is set */
while(usb_regs->gr->GRSTCTL & GRSTCTL_RXFF) {
/* no operation */
}
/* wait for 3 PHY clocks */
usb_udelay(3U);
return USB_OK;
}
/*!
\brief set endpoint or channel TX FIFO size
\param[in] usb_regs: pointer to USB core registers
\param[in] fifo: TX FIFO number
\param[in] size: assigned TX FIFO size
\param[out] none
\retval none
*/
void usb_set_txfifo(usb_core_regs *usb_regs, uint8_t fifo, uint16_t size)
{
uint32_t tx_offset = usb_regs->gr->GRFLEN;
if(0U == fifo) {
usb_regs->gr->DIEP0TFLEN_HNPTFLEN = ((uint32_t)size << 16) | tx_offset;
} else {
tx_offset += (usb_regs->gr->DIEP0TFLEN_HNPTFLEN) >> 16;
for(uint8_t i = 0U; i < (fifo - 1U); i++) {
tx_offset += (usb_regs->gr->DIEPTFLEN[i] >> 16);
}
/* multiply Tx_Size by 2 to get higher performance */
usb_regs->gr->DIEPTFLEN[fifo - 1U] = ((uint32_t)size << 16) | tx_offset;
}
}
/*!
\brief set USB current mode
\param[in] usb_regs: pointer to USB core registers
\param[in] mode: USB current mode
\param[out] none
\retval none
*/
void usb_curmode_set(usb_core_regs *usb_regs, uint8_t mode)
{
usb_regs->gr->GUSBCS &= ~(GUSBCS_FDM | GUSBCS_FHM);
if(DEVICE_MODE == mode) {
usb_regs->gr->GUSBCS |= GUSBCS_FDM;
} else if(HOST_MODE == mode) {
usb_regs->gr->GUSBCS |= GUSBCS_FHM;
} else {
/* OTG mode and other mode can not be here! */
}
}
/*!
\brief configure USB core to soft reset
\param[in] usb_regs: pointer to USB core registers
\param[out] none
\retval none
*/
static void usb_core_reset(usb_core_regs *usb_regs)
{
/* enable core soft reset */
usb_regs->gr->GRSTCTL |= GRSTCTL_CSRST;
/* wait for the core to be soft reset */
while(usb_regs->gr->GRSTCTL & GRSTCTL_CSRST) {
/* no operation */
}
/* wait for additional 3 PHY clocks */
usb_udelay(3U);
}
@@ -0,0 +1,660 @@
/*!
\file drv_usb_dev.c
\brief USB device mode low level driver
\version 2024-12-20, V3.3.1, firmware for GD32F4xx
*/
/*
Copyright (c) 2024, GigaDevice Semiconductor Inc.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
OF SUCH DAMAGE.
*/
#include "drv_usb_hw.h"
#include "drv_usb_dev.h"
/* endpoint 0 max packet length */
static const uint8_t EP0_MAXLEN[4] = {
[DSTAT_EM_HS_PHY_30MHZ_60MHZ] = EP0MPL_64,
[DSTAT_EM_FS_PHY_30MHZ_60MHZ] = EP0MPL_64,
[DSTAT_EM_FS_PHY_48MHZ] = EP0MPL_64,
[DSTAT_EM_LS_PHY_6MHZ] = EP0MPL_8
};
#ifdef USB_FS_CORE
/* USBFS endpoint TX FIFO size */
static uint16_t USBFS_TX_FIFO_SIZE[USBFS_MAX_EP_COUNT] = {
(uint16_t)TX0_FIFO_FS_SIZE,
(uint16_t)TX1_FIFO_FS_SIZE,
(uint16_t)TX2_FIFO_FS_SIZE,
(uint16_t)TX3_FIFO_FS_SIZE
};
#endif /* USBFS_CORE */
#ifdef USB_HS_CORE
/* USBHS endpoint TX FIFO size */
uint16_t USBHS_TX_FIFO_SIZE[USBHS_MAX_EP_COUNT] = {
(uint16_t)TX0_FIFO_HS_SIZE,
(uint16_t)TX1_FIFO_HS_SIZE,
(uint16_t)TX2_FIFO_HS_SIZE,
(uint16_t)TX3_FIFO_HS_SIZE,
(uint16_t)TX4_FIFO_HS_SIZE,
(uint16_t)TX5_FIFO_HS_SIZE
};
#endif /* USBHS_CORE */
/*!
\brief initialize USB core registers for device mode
\param[in] udev: pointer to USB device
\param[out] none
\retval operation status
*/
usb_status usb_devcore_init(usb_core_driver *udev)
{
uint8_t i = 0U;
/* restart the PHY clock (maybe don't need to...) */
*udev->regs.PWRCLKCTL = 0U;
/* configure periodic frame interval to default value */
udev->regs.dr->DCFG &= ~DCFG_EOPFT;
udev->regs.dr->DCFG |= FRAME_INTERVAL_80;
udev->regs.dr->DCFG &= ~DCFG_DS;
#ifdef USB_FS_CORE
if((uint8_t)USB_CORE_ENUM_FS == udev->bp.core_enum) {
/* set full-speed PHY */
udev->regs.dr->DCFG |= USB_SPEED_INP_FULL;
/* set RX FIFO size */
usb_set_rxfifo(&udev->regs, RX_FIFO_FS_SIZE);
/* set endpoint 0 to 3's TX FIFO length and RAM address */
for(i = 0U; i < USBFS_MAX_EP_COUNT; i++) {
usb_set_txfifo(&udev->regs, i, USBFS_TX_FIFO_SIZE[i]);
}
}
#endif /* USB_FS_CORE */
#ifdef USB_HS_CORE
if(USB_CORE_ENUM_HS == udev->bp.core_enum) {
if(USB_ULPI_PHY == udev->bp.phy_itf) {
udev->regs.dr->DCFG |= USB_SPEED_EXP_HIGH;
} else {/* set high speed PHY in full speed mode */
udev->regs.dr->DCFG |= USB_SPEED_EXP_FULL;
}
/* Set RX FIFO size */
usb_set_rxfifo(&udev->regs, RX_FIFO_HS_SIZE);
/* set endpoint 0 to 6's TX FIFO length and RAM address */
for(i = 0U; i < USBHS_MAX_EP_COUNT; i++) {
usb_set_txfifo(&udev->regs, i, USBHS_TX_FIFO_SIZE[i]);
}
}
#endif /* USB_FS_CORE */
/* make sure all FIFOs are flushed */
/* flush all TX FIFOs */
(void)usb_txfifo_flush(&udev->regs, 0x10U);
/* flush entire RX FIFO */
(void)usb_rxfifo_flush(&udev->regs);
/* clear all pending device interrupts */
udev->regs.dr->DIEPINTEN = 0U;
udev->regs.dr->DOEPINTEN = 0U;
udev->regs.dr->DAEPINT = 0xFFFFFFFFU;
udev->regs.dr->DAEPINTEN = 0U;
/* configure all IN/OUT endpoints */
for(i = 0U; i < udev->bp.num_ep; i++) {
if(udev->regs.er_in[i]->DIEPCTL & DEPCTL_EPEN) {
udev->regs.er_in[i]->DIEPCTL |= DEPCTL_EPD | DEPCTL_SNAK;
} else {
udev->regs.er_in[i]->DIEPCTL = 0U;
}
/* set IN endpoint transfer length to 0 */
udev->regs.er_in[i]->DIEPLEN = 0U;
/* clear all pending IN endpoint interrupts */
udev->regs.er_in[i]->DIEPINTF = 0xFFU;
if(udev->regs.er_out[i]->DOEPCTL & DEPCTL_EPEN) {
udev->regs.er_out[i]->DOEPCTL |= DEPCTL_EPD | DEPCTL_SNAK;
} else {
udev->regs.er_out[i]->DOEPCTL = 0U;
}
/* set OUT endpoint transfer length to 0 */
udev->regs.er_out[i]->DOEPLEN = 0U;
/* clear all pending OUT endpoint interrupts */
udev->regs.er_out[i]->DOEPINTF = 0xFFU;
}
udev->regs.dr->DIEPINTEN |= DIEPINTEN_EPTXFUDEN;
(void)usb_devint_enable(udev);
return USB_OK;
}
/*!
\brief enable the USB device mode interrupts
\param[in] udev: pointer to USB device
\param[out] none
\retval operation status
*/
usb_status usb_devint_enable(usb_core_driver *udev)
{
/* clear any pending USB OTG interrupts */
udev->regs.gr->GOTGINTF = 0xFFFFFFFFU;
/* clear any pending interrupts */
udev->regs.gr->GINTF = 0xBFFFFFFFU;
/* enable the USB wakeup and suspend interrupts */
udev->regs.gr->GINTEN = GINTEN_WKUPIE | GINTEN_SPIE;
/* enable device_mode-related interrupts */
if((uint8_t)USB_USE_FIFO == udev->bp.transfer_mode) {
udev->regs.gr->GINTEN |= GINTEN_RXFNEIE;
}
udev->regs.gr->GINTEN |= GINTEN_RSTIE | GINTEN_ENUMFIE | GINTEN_IEPIE | \
GINTEN_OEPIE | GINTEN_SOFIE | GINTEN_ISOONCIE | GINTEN_ISOINCIE;
#ifdef VBUS_SENSING_ENABLED
udev->regs.gr->GINTEN |= GINTEN_SESIE | GINTEN_OTGIE;
#endif /* VBUS_SENSING_ENABLED */
return USB_OK;
}
/*!
\brief active the USB endpoint0 transaction
\param[in] udev: pointer to USB device
\param[in] transc: the USB endpoint0 transaction
\param[out] none
\retval operation status
*/
usb_status usb_transc0_active(usb_core_driver *udev, usb_transc *transc)
{
__IO uint32_t *reg_addr = NULL;
uint32_t enum_speed = udev->regs.dr->DSTAT & DSTAT_ES;
/* get the endpoint number */
uint8_t ep_num = transc->ep_addr.num;
if(ep_num) {
/* not endpoint 0 */
return USB_FAIL;
}
if(transc->ep_addr.dir) {
reg_addr = &udev->regs.er_in[0]->DIEPCTL;
} else {
reg_addr = &udev->regs.er_out[0]->DOEPCTL;
}
/* endpoint 0 is activated after USB clock is enabled */
*reg_addr &= ~(DEPCTL_MPL | DEPCTL_EPTYPE | DIEPCTL_TXFNUM);
/* set endpoint 0 maximum packet length */
*reg_addr |= EP0_MAXLEN[enum_speed];
/* activate endpoint */
*reg_addr |= ((uint32_t)transc->ep_type << 18) | ((uint32_t)ep_num << 22) | DEPCTL_SD0PID | DEPCTL_EPACT;
return USB_OK;
}
/*!
\brief active the USB transaction
\param[in] udev: pointer to USB device
\param[in] transc: the USB transaction
\param[out] none
\retval status
*/
usb_status usb_transc_active(usb_core_driver *udev, usb_transc *transc)
{
__IO uint32_t *reg_addr = NULL;
uint32_t epinten = 0U;
uint32_t enum_speed = udev->regs.dr->DSTAT & DSTAT_ES;
/* get the endpoint number */
uint8_t ep_num = transc->ep_addr.num;
/* enable endpoint interrupt number */
if(transc->ep_addr.dir) {
reg_addr = &udev->regs.er_in[ep_num]->DIEPCTL;
epinten = 1U << ep_num;
} else {
reg_addr = &udev->regs.er_out[ep_num]->DOEPCTL;
epinten = 1U << (16U + ep_num);
}
/* if the endpoint is not active, need change the endpoint control register */
if(!(*reg_addr & DEPCTL_EPACT)) {
*reg_addr &= ~(DEPCTL_MPL | DEPCTL_EPTYPE | DIEPCTL_TXFNUM);
/* set endpoint maximum packet length */
if(0U == ep_num) {
*reg_addr |= EP0_MAXLEN[enum_speed];
} else {
*reg_addr |= transc->max_len;
}
/* activate endpoint */
*reg_addr |= ((uint32_t)transc->ep_type << 18) | ((uint32_t)ep_num << 22) | DEPCTL_SD0PID | DEPCTL_EPACT;
}
#ifdef USB_HS_DEDICATED_EP1_ENABLED
if((1U == ep_num) && (USB_CORE_ENUM_HS == udev->bp.core_enum)) {
udev->regs.dr->DEP1INTEN |= epinten;
} else
#endif /* USB_HS_DEDICATED_EP1_ENABLED */
{
/* enable the interrupts for this endpoint */
udev->regs.dr->DAEPINTEN |= epinten;
}
return USB_OK;
}
/*!
\brief deactivate the USB transaction
\param[in] udev: pointer to USB device
\param[in] transc: the USB transaction
\param[out] none
\retval status
*/
usb_status usb_transc_deactivate(usb_core_driver *udev, usb_transc *transc)
{
uint32_t epinten = 0U;
uint8_t ep_num = transc->ep_addr.num;
/* disable endpoint interrupt number */
if(transc->ep_addr.dir) {
epinten = 1U << ep_num;
udev->regs.er_in[ep_num]->DIEPCTL &= ~DEPCTL_EPACT;
} else {
epinten = 1U << (ep_num + 16U);
udev->regs.er_out[ep_num]->DOEPCTL &= ~DEPCTL_EPACT;
}
#ifdef USB_HS_DEDICATED_EP1_ENABLED
if((1U == ep_num) && (USB_CORE_ENUM_HS == udev->bp.core_enum)) {
udev->regs.dr->DEP1INTEN &= ~epinten;
} else
#endif /* USB_HS_DEDICATED_EP1_ENABLED */
{
/* disable the interrupts for this endpoint */
udev->regs.dr->DAEPINTEN &= ~epinten;
}
return USB_OK;
}
/*!
\brief configure USB transaction to start IN transfer
\param[in] udev: pointer to USB device
\param[in] transc: the USB IN transaction
\param[out] none
\retval operation status
*/
usb_status usb_transc_inxfer(usb_core_driver *udev, usb_transc *transc)
{
usb_status status = USB_OK;
uint8_t ep_num = transc->ep_addr.num;
__IO uint32_t epctl = udev->regs.er_in[ep_num]->DIEPCTL;
__IO uint32_t eplen = udev->regs.er_in[ep_num]->DIEPLEN;
eplen &= ~(DEPLEN_TLEN | DEPLEN_PCNT);
/* zero length packet or endpoint 0 */
if(0U == transc->xfer_len) {
/* set transfer packet count to 1 */
eplen |= 1U << 19;
} else {
/* set transfer packet count */
if(0U == ep_num) {
transc->xfer_len = USB_MIN(transc->xfer_len, transc->max_len);
eplen |= 1U << 19;
} else {
eplen |= (((transc->xfer_len - 1U) + transc->max_len) / transc->max_len) << 19;
}
/* set endpoint transfer length */
eplen |= transc->xfer_len;
if((uint8_t)USB_EPTYPE_ISOC == transc->ep_type) {
eplen |= DIEPLEN_MCNT & (1U << 29);
}
}
udev->regs.er_in[ep_num]->DIEPLEN = eplen;
if((uint8_t)USB_EPTYPE_ISOC == transc->ep_type) {
if(((udev->regs.dr->DSTAT & DSTAT_FNRSOF) >> 8) & 0x01U) {
epctl |= DEPCTL_SEVNFRM;
} else {
epctl |= DEPCTL_SODDFRM;
}
}
if((uint8_t)USB_USE_DMA == udev->bp.transfer_mode) {
udev->regs.er_in[ep_num]->DIEPDMAADDR = transc->dma_addr;
}
/* enable the endpoint and clear the NAK */
epctl |= DEPCTL_CNAK | DEPCTL_EPEN;
udev->regs.er_in[ep_num]->DIEPCTL = epctl;
if((uint8_t)USB_USE_FIFO == udev->bp.transfer_mode) {
if((uint8_t)USB_EPTYPE_ISOC != transc->ep_type) {
/* enable the TX FIFO empty interrupt for this endpoint */
if(transc->xfer_len > 0U) {
udev->regs.dr->DIEPFEINTEN |= 1U << ep_num;
}
} else {
(void)usb_txfifo_write(&udev->regs, transc->xfer_buf, ep_num, (uint16_t)transc->xfer_len);
}
}
return status;
}
/*!
\brief configure USB transaction to start OUT transfer
\param[in] udev: pointer to USB device
\param[in] transc: the USB OUT transaction
\param[out] none
\retval status
*/
usb_status usb_transc_outxfer(usb_core_driver *udev, usb_transc *transc)
{
usb_status status = USB_OK;
uint8_t ep_num = transc->ep_addr.num;
uint32_t epctl = udev->regs.er_out[ep_num]->DOEPCTL;
uint32_t eplen = udev->regs.er_out[ep_num]->DOEPLEN;
eplen &= ~(DEPLEN_TLEN | DEPLEN_PCNT);
/* zero length packet or endpoint 0 */
if((0U == transc->xfer_len) || (0U == ep_num)) {
/* set the transfer length to max packet size */
eplen |= transc->max_len;
/* set the transfer packet count to 1 */
eplen |= 1U << 19;
} else {
/* configure the transfer size and packet count as follows:
* pktcnt = N
* xfersize = N * maxpacket
*/
uint32_t packet_count = (transc->xfer_len + transc->max_len - 1U) / transc->max_len;
eplen |= packet_count << 19;
eplen |= packet_count * transc->max_len;
#ifdef INT_HIGH_BW
if(transc->ep_type == (uint8_t)USB_EPTYPE_INTR) {
eplen |= DIEPLEN_MCNT & (3U << 29);
}
#endif /* INT_HIGH_BW */
}
udev->regs.er_out[ep_num]->DOEPLEN = eplen;
if((uint8_t)USB_USE_DMA == udev->bp.transfer_mode) {
udev->regs.er_out[ep_num]->DOEPDMAADDR = transc->dma_addr;
}
if((uint8_t)USB_EPTYPE_ISOC == transc->ep_type) {
if(transc->frame_num) {
epctl |= DEPCTL_SD1PID;
} else {
epctl |= DEPCTL_SD0PID;
}
}
/* enable the endpoint and clear the NAK */
epctl |= DEPCTL_EPEN | DEPCTL_CNAK;
udev->regs.er_out[ep_num]->DOEPCTL = epctl;
return status;
}
/*!
\brief set the USB transaction STALL status
\param[in] udev: pointer to USB device
\param[in] transc: the USB transaction
\param[out] none
\retval status
*/
usb_status usb_transc_stall(usb_core_driver *udev, usb_transc *transc)
{
__IO uint32_t *reg_addr = NULL;
uint8_t ep_num = transc->ep_addr.num;
if(transc->ep_addr.dir) {
reg_addr = &(udev->regs.er_in[ep_num]->DIEPCTL);
/* set the endpoint disable bit */
if(*reg_addr & DEPCTL_EPEN) {
*reg_addr |= DEPCTL_EPD;
}
} else {
/* set the endpoint STALL bit */
reg_addr = &(udev->regs.er_out[ep_num]->DOEPCTL);
}
/* set the endpoint STALL bit */
*reg_addr |= DEPCTL_STALL;
return USB_OK;
}
/*!
\brief clear the USB transaction STALL status
\param[in] udev: pointer to USB device
\param[in] transc: the USB transaction
\param[out] none
\retval operation status
*/
usb_status usb_transc_clrstall(usb_core_driver *udev, usb_transc *transc)
{
__IO uint32_t *reg_addr = NULL;
uint8_t ep_num = transc->ep_addr.num;
if(transc->ep_addr.dir) {
reg_addr = &(udev->regs.er_in[ep_num]->DIEPCTL);
} else {
reg_addr = &(udev->regs.er_out[ep_num]->DOEPCTL);
}
/* clear the endpoint STALL bit */
*reg_addr &= ~DEPCTL_STALL;
/* reset data PID of the periodic endpoints */
if(((uint8_t)USB_EPTYPE_INTR == transc->ep_type) || ((uint8_t)USB_EPTYPE_BULK == transc->ep_type)) {
*reg_addr |= DEPCTL_SD0PID;
}
return USB_OK;
}
/*!
\brief read device IN endpoint interrupt flag register
\param[in] udev: pointer to USB device
\param[in] ep_num: endpoint number
\param[out] none
\retval interrupt value
*/
uint32_t usb_iepintr_read(usb_core_driver *udev, uint8_t ep_num)
{
uint32_t value = 0U, fifoemptymask, commonintmask;
commonintmask = udev->regs.dr->DIEPINTEN;
fifoemptymask = udev->regs.dr->DIEPFEINTEN;
/* check FIFO empty interrupt enable bit */
commonintmask |= ((fifoemptymask >> ep_num) & 0x1U) << 7;
value = udev->regs.er_in[ep_num]->DIEPINTF & commonintmask;
return value;
}
/*!
\brief configures OUT endpoint 0 to receive SETUP packets
\param[in] udev: pointer to USB device
\param[out] none
\retval none
*/
void usb_ctlep_startout(usb_core_driver *udev)
{
/* set OUT endpoint 0 receive length to 24 bytes, 1 packet and 3 SETUP packets */
udev->regs.er_out[0]->DOEPLEN = DOEP0_TLEN(8U * 3U) | DOEP0_PCNT(1U) | DOEP0_STPCNT(3U);
if((uint8_t)USB_USE_DMA == udev->bp.transfer_mode) {
udev->regs.er_out[0]->DOEPDMAADDR = (uint32_t)&udev->dev.control.req;
/* endpoint enable */
udev->regs.er_out[0]->DOEPCTL |= DEPCTL_EPACT | DEPCTL_EPEN;
}
}
/*!
\brief active remote wakeup signaling
\param[in] udev: pointer to USB device
\param[out] none
\retval none
*/
void usb_rwkup_active(usb_core_driver *udev)
{
if(udev->dev.pm.dev_remote_wakeup) {
if(udev->regs.dr->DSTAT & DSTAT_SPST) {
if(udev->bp.low_power) {
/* ungate USB core clock */
*udev->regs.PWRCLKCTL &= ~(PWRCLKCTL_SHCLK | PWRCLKCTL_SUCLK);
}
/* active remote wakeup signaling */
udev->regs.dr->DCTL |= DCTL_RWKUP;
usb_mdelay(5U);
udev->regs.dr->DCTL &= ~DCTL_RWKUP;
}
}
}
/*!
\brief active USB core clock
\param[in] udev: pointer to USB device
\param[out] none
\retval none
*/
void usb_clock_active(usb_core_driver *udev)
{
if(udev->bp.low_power) {
if(udev->regs.dr->DSTAT & DSTAT_SPST) {
/* ungate USB Core clock */
*udev->regs.PWRCLKCTL &= ~(PWRCLKCTL_SHCLK | PWRCLKCTL_SUCLK);
}
}
}
/*!
\brief USB device suspend
\param[in] udev: pointer to USB device
\param[out] none
\retval none
*/
void usb_dev_suspend(usb_core_driver *udev)
{
__IO uint32_t devstat = udev->regs.dr->DSTAT;
if((udev->bp.low_power) && (devstat & DSTAT_SPST)) {
/* switch-off the USB clocks */
*udev->regs.PWRCLKCTL |= PWRCLKCTL_SHCLK;
/* enter DEEP_SLEEP mode with LDO in low power mode */
pmu_to_deepsleepmode(PMU_LDO_LOWPOWER, PMU_LOWDRIVER_DISABLE, WFI_CMD);
}
}
/*!
\brief stop the device and clean up FIFOs
\param[in] udev: pointer to USB device
\param[out] none
\retval none
*/
void usb_dev_stop(usb_core_driver *udev)
{
uint32_t i;
udev->dev.cur_status = 1U;
/* clear all interrupt flag and enable bits */
for(i = 0U; i < udev->bp.num_ep; i++) {
udev->regs.er_in[i]->DIEPINTF = 0xFFU;
udev->regs.er_out[i]->DOEPINTF = 0xFFU;
}
udev->regs.dr->DIEPINTEN = 0U;
udev->regs.dr->DOEPINTEN = 0U;
udev->regs.dr->DAEPINTEN = 0U;
udev->regs.dr->DAEPINT = 0xFFFFFFFFU;
/* flush the FIFO */
(void)usb_rxfifo_flush(&udev->regs);
(void)usb_txfifo_flush(&udev->regs, 0x10U);
}
@@ -0,0 +1,472 @@
/*!
\file drv_usb_host.c
\brief USB host mode low level driver
\version 2024-12-20, V3.3.1, firmware for GD32F4xx
*/
/*
Copyright (c) 2024, GigaDevice Semiconductor Inc.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
OF SUCH DAMAGE.
*/
#include "drv_usb_hw.h"
#include "drv_usb_host.h"
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;
/* configure USB clock of PHY */
if(USB_ULPI_PHY == udev->bp.phy_itf) {
usb_phyclock_config(udev, HCTL_30_60MHZ);
} else {
usb_phyclock_config(udev, HCTL_48MHZ);
}
/* support FS/LS only */
udev->regs.hr->HCTL &= ~HCTL_SPDFSLS;
/* configure data FIFOs size */
#ifdef USB_FS_CORE
if(USB_CORE_ENUM_FS == udev->bp.core_enum) {
/* set RX FIFO size */
udev->regs.gr->GRFLEN = USB_RX_FIFO_FS_SIZE;
/* set non-periodic TX FIFO size and address */
nptxfifolen |= USB_RX_FIFO_FS_SIZE;
nptxfifolen |= USB_HTX_NPFIFO_FS_SIZE << 16;
udev->regs.gr->DIEP0TFLEN_HNPTFLEN = nptxfifolen;
/* set periodic TX FIFO size and address */
ptxfifolen |= USB_RX_FIFO_FS_SIZE + USB_HTX_NPFIFO_FS_SIZE;
ptxfifolen |= USB_HTX_PFIFO_FS_SIZE << 16;
udev->regs.gr->HPTFLEN = ptxfifolen;
}
#endif /* USB_FS_CORE */
#ifdef USB_HS_CORE
if(USB_CORE_ENUM_HS == udev->bp.core_enum) {
/* set RX FIFO size */
udev->regs.gr->GRFLEN = USB_RX_FIFO_HS_SIZE;
/* set non-periodic TX FIFO size and address */
nptxfifolen |= USB_RX_FIFO_HS_SIZE;
nptxfifolen |= USB_HTX_NPFIFO_HS_SIZE << 16;
udev->regs.gr->DIEP0TFLEN_HNPTFLEN = nptxfifolen;
/* set periodic TX FIFO size and address */
ptxfifolen |= USB_RX_FIFO_HS_SIZE + USB_HTX_NPFIFO_HS_SIZE;
ptxfifolen |= USB_HTX_PFIFO_HS_SIZE << 16;
udev->regs.gr->HPTFLEN = ptxfifolen;
}
#endif /* USB_HS_CORE */
#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;
}
inten |= GINTEN_SESIE | 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 or 0..11)
\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);
}
@@ -0,0 +1,591 @@
/*!
\file drv_usbd_int.c
\brief USB device mode interrupt routines
\version 2024-12-20, V3.3.1, firmware for GD32F4xx
*/
/*
Copyright (c) 2024, GigaDevice Semiconductor Inc.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
OF SUCH DAMAGE.
*/
#include "drv_usbd_int.h"
#include "usbd_transc.h"
static const uint8_t USB_SPEED[4] = {
[DSTAT_EM_HS_PHY_30MHZ_60MHZ] = (uint8_t)USB_SPEED_HIGH,
[DSTAT_EM_FS_PHY_30MHZ_60MHZ] = (uint8_t)USB_SPEED_FULL,
[DSTAT_EM_FS_PHY_48MHZ] = (uint8_t)USB_SPEED_FULL,
[DSTAT_EM_LS_PHY_6MHZ] = (uint8_t)USB_SPEED_LOW
};
/* local function prototypes ('static') */
static uint32_t usbd_int_epout(usb_core_driver *udev);
static uint32_t usbd_int_epin(usb_core_driver *udev);
static uint32_t usbd_int_rxfifo(usb_core_driver *udev);
static uint32_t usbd_int_reset(usb_core_driver *udev);
static uint32_t usbd_int_enumfinish(usb_core_driver *udev);
static uint32_t usbd_int_suspend(usb_core_driver *udev);
static uint32_t usbd_emptytxfifo_write(usb_core_driver *udev, uint32_t ep_num);
/*!
\brief USB device-mode interrupts global service routine handler
\param[in] udev: pointer to USB device instance
\param[out] none
\retval none
*/
void usbd_isr(usb_core_driver *udev)
{
if(HOST_MODE != (udev->regs.gr->GINTF & GINTF_COPM)) {
uint32_t intr = udev->regs.gr->GINTF;
intr &= udev->regs.gr->GINTEN;
/* there are no interrupts, avoid spurious interrupt */
if(!intr) {
return;
}
/* OUT endpoints interrupts */
if(intr & GINTF_OEPIF) {
(void)usbd_int_epout(udev);
}
/* IN endpoints interrupts */
if(intr & GINTF_IEPIF) {
(void)usbd_int_epin(udev);
}
/* suspend interrupt */
if(intr & GINTF_SP) {
(void)usbd_int_suspend(udev);
}
/* wakeup interrupt */
if(intr & GINTF_WKUPIF) {
if(USBD_SUSPENDED == udev->dev.cur_status) {
/* inform upper layer by the resume event */
udev->dev.cur_status = udev->dev.backup_status;
}
/* clear interrupt */
udev->regs.gr->GINTF = GINTF_WKUPIF;
}
/* start of frame interrupt */
if(intr & GINTF_SOF) {
if(udev->dev.class_core->SOF) {
(void)udev->dev.class_core->SOF(udev);
}
/* clear interrupt */
udev->regs.gr->GINTF = GINTF_SOF;
}
/* receive FIFO not empty interrupt */
if(intr & GINTF_RXFNEIF) {
(void)usbd_int_rxfifo(udev);
}
/* USB reset interrupt */
if(intr & GINTF_RST) {
(void)usbd_int_reset(udev);
}
/* enumeration has been done interrupt */
if(intr & GINTF_ENUMFIF) {
(void)usbd_int_enumfinish(udev);
}
/* incomplete synchronization IN transfer interrupt*/
if(intr & GINTF_ISOINCIF) {
if(NULL != udev->dev.class_core->incomplete_isoc_in) {
(void)udev->dev.class_core->incomplete_isoc_in(udev);
}
/* clear interrupt */
udev->regs.gr->GINTF = GINTF_ISOINCIF;
}
/* incomplete synchronization OUT transfer interrupt*/
if(intr & GINTF_ISOONCIF) {
if(NULL != udev->dev.class_core->incomplete_isoc_out) {
(void)udev->dev.class_core->incomplete_isoc_out(udev);
}
/* clear interrupt */
udev->regs.gr->GINTF = GINTF_ISOONCIF;
}
#ifdef VBUS_SENSING_ENABLED
/* session request interrupt */
if(intr & GINTF_SESIF) {
udev->regs.gr->GINTF = GINTF_SESIF;
}
/* OTG mode interrupt */
if(intr & GINTF_OTGIF) {
if(udev->regs.gr->GOTGINTF & GOTGINTF_SESEND) {
}
/* clear OTG interrupt */
udev->regs.gr->GINTF = GINTF_OTGIF;
}
#endif /* VBUS_SENSING_ENABLED */
}
}
#ifdef USB_HS_DEDICATED_EP1_ENABLED
/*!
\brief USB dedicated OUT endpoint 1 interrupt service routine handler
\param[in] udev: pointer to USB device instance
\param[out] none
\retval operation status
*/
uint32_t usbd_int_dedicated_ep1out(usb_core_driver *udev)
{
uint32_t oepintr = 0U;
uint32_t oeplen = 0U;
oepintr = udev->regs.er_out[1]->DOEPINTF;
oepintr &= udev->regs.dr->DOEP1INTEN;
/* transfer complete */
if(oepintr & DOEPINTF_TF) {
/* clear the bit in DOEPINTn for this interrupt */
udev->regs.er_out[1]->DOEPINTF = DOEPINTF_TF;
if(USB_USE_DMA == udev->bp.transfer_mode) {
usb_transc *transc = &udev->dev.transc_out[1];
uint32_t set_len = ((transc->xfer_len + transc->max_len - 1U) / transc->max_len) * transc->max_len;
oeplen = udev->regs.er_out[1]->DOEPLEN;
/* to do : handle more than one single max packet size packet */
udev->dev.transc_out[1].xfer_count = set_len - (oeplen & DEPLEN_TLEN);
}
/* RX complete */
usbd_out_transc(udev, 1U);
}
return 1U;
}
/*!
\brief USB dedicated IN endpoint 1 interrupt service routine handler
\param[in] udev: pointer to USB device instance
\param[out] none
\retval operation status
*/
uint32_t usbd_int_dedicated_ep1in(usb_core_driver *udev)
{
uint32_t inten, intr, emptyen;
inten = udev->regs.dr->DIEP1INTEN;
emptyen = udev->regs.dr->DIEPFEINTEN;
inten |= ((emptyen >> 1U) & 0x1U) << 7;
intr = udev->regs.er_in[1]->DIEPINTF & inten;
if(intr & DIEPINTF_TF) {
udev->regs.dr->DIEPFEINTEN &= ~(0x1U << 1);
udev->regs.er_in[1]->DIEPINTF = DIEPINTF_TF;
/* TX complete */
usbd_in_transc(udev, 1U);
}
if(intr & DIEPINTF_TXFE) {
usbd_emptytxfifo_write(udev, 1U);
udev->regs.er_in[1]->DIEPINTF = DIEPINTF_TXFE;
}
return 1U;
}
#endif /* USB_HS_DEDICATED_EP1_ENABLED */
/*!
\brief indicates that an OUT endpoint has a pending interrupt
\param[in] udev: pointer to USB device instance
\param[out] none
\retval operation status
*/
static uint32_t usbd_int_epout(usb_core_driver *udev)
{
uint32_t epintnum = 0U;
uint8_t ep_num = 0U;
for(epintnum = usb_oepintnum_read(udev); epintnum; epintnum >>= 1, ep_num++) {
if(epintnum & 0x01U) {
__IO uint32_t oepintr = usb_oepintr_read(udev, ep_num);
/* transfer complete interrupt */
if(oepintr & DOEPINTF_TF) {
/* clear the bit in DOEPINTF for this interrupt */
udev->regs.er_out[ep_num]->DOEPINTF = DOEPINTF_TF;
if((uint8_t)USB_USE_DMA == udev->bp.transfer_mode) {
usb_transc *transc = &udev->dev.transc_out[ep_num];
__IO uint32_t eplen = udev->regs.er_out[ep_num]->DOEPLEN;
uint32_t set_len = ((transc->xfer_len + transc->max_len - 1U) / transc->max_len) * transc->max_len;
udev->dev.transc_out[ep_num].xfer_count = set_len - (eplen & DEPLEN_TLEN);
}
/* inform upper layer: data ready */
(void)usbd_out_transc(udev, ep_num);
if((uint8_t)USB_USE_DMA == udev->bp.transfer_mode) {
if((0U == ep_num) && ((uint8_t)USB_CTL_STATUS_OUT == udev->dev.control.ctl_state)) {
usb_ctlep_startout(udev);
}
}
}
/* SETUP phase finished interrupt (control endpoints) */
if(oepintr & DOEPINTF_STPF) {
/* inform the upper layer that a SETUP packet is available */
(void)usbd_setup_transc(udev);
udev->regs.er_out[ep_num]->DOEPINTF = DOEPINTF_STPF;
}
}
}
return 1U;
}
/*!
\brief indicates that an IN endpoint has a pending interrupt
\param[in] udev: pointer to USB device instance
\param[out] none
\retval operation status
*/
static uint32_t usbd_int_epin(usb_core_driver *udev)
{
uint32_t epintnum = 0U;
uint8_t ep_num = 0U;
for(epintnum = usb_iepintnum_read(udev); epintnum; epintnum >>= 1, ep_num++) {
if(epintnum & 0x1U) {
__IO uint32_t iepintr = usb_iepintr_read(udev, ep_num);
if(iepintr & DIEPINTF_TF) {
udev->regs.er_in[ep_num]->DIEPINTF = DIEPINTF_TF;
/* data transmission is completed */
(void)usbd_in_transc(udev, ep_num);
if((uint8_t)USB_USE_DMA == udev->bp.transfer_mode) {
if((0U == ep_num) && ((uint8_t)USB_CTL_STATUS_IN == udev->dev.control.ctl_state)) {
usb_ctlep_startout(udev);
}
}
}
if(iepintr & DIEPINTF_TXFE) {
usbd_emptytxfifo_write(udev, (uint32_t)ep_num);
udev->regs.er_in[ep_num]->DIEPINTF = DIEPINTF_TXFE;
}
}
}
return 1U;
}
/*!
\brief handle the RX status queue level interrupt
\param[in] udev: pointer to USB device instance
\param[out] none
\retval operation status
*/
static uint32_t usbd_int_rxfifo(usb_core_driver *udev)
{
usb_transc *transc = NULL;
uint8_t data_PID = 0U;
uint32_t bcount = 0U;
__IO uint32_t devrxstat = 0U;
/* disable the RX status queue non-empty interrupt */
udev->regs.gr->GINTEN &= ~GINTEN_RXFNEIE;
/* get the status from the top of the FIFO */
devrxstat = udev->regs.gr->GRSTATP;
uint8_t ep_num = (uint8_t)(devrxstat & GRSTATRP_EPNUM);
transc = &udev->dev.transc_out[ep_num];
bcount = (devrxstat & GRSTATRP_BCOUNT) >> 4;
data_PID = (uint8_t)((devrxstat & GRSTATRP_DPID) >> 15);
#if defined(USE_USB_HS) && defined(USE_ULPI_PHY)
#ifndef USE_450Z_EVAL
/* ensure no-DMA mode can work */
if(0U == (udev->regs.er_out[ep_num]->DOEPLEN & DEPLEN_PCNT)) {
uint32_t devepctl = udev->regs.er_out[ep_num]->DOEPCTL;
devepctl |= DEPCTL_SNAK;
devepctl &= ~DEPCTL_EPEN;
devepctl &= ~DEPCTL_EPD;
udev->regs.er_out[ep_num]->DOEPCTL = devepctl;
}
#endif /* USE_450Z_EVAL */
#endif /* USE_USB_HS && USE_ULPI_PHY */
switch((devrxstat & GRSTATRP_RPCKST) >> 17) {
case RSTAT_GOUT_NAK:
break;
case RSTAT_DATA_UPDT:
if(bcount > 0U) {
(void)usb_rxfifo_read(&udev->regs, transc->xfer_buf, (uint16_t)bcount);
transc->xfer_buf += bcount;
transc->xfer_count += bcount;
}
break;
case RSTAT_XFER_COMP:
/* trigger the OUT endpoint interrupt */
break;
case RSTAT_SETUP_COMP:
/* trigger the OUT endpoint interrupt */
break;
case RSTAT_SETUP_UPDT:
if((0U == transc->ep_addr.num) && (8U == bcount) && (DPID_DATA0 == data_PID)) {
/* copy the SETUP packet received in FIFO into the setup buffer in RAM */
(void)usb_rxfifo_read(&udev->regs, (uint8_t *)&udev->dev.control.req, (uint16_t)bcount);
transc->xfer_count += bcount;
}
break;
default:
break;
}
/* enable the RX status queue level interrupt */
udev->regs.gr->GINTEN |= GINTEN_RXFNEIE;
return 1U;
}
/*!
\brief handle USB reset interrupt
\param[in] udev: pointer to USB device instance
\param[out] none
\retval status
*/
static uint32_t usbd_int_reset(usb_core_driver *udev)
{
uint32_t i;
/* clear the remote wakeup signaling */
udev->regs.dr->DCTL &= ~DCTL_RWKUP;
/* flush the TX FIFO */
(void)usb_txfifo_flush(&udev->regs, 0U);
for(i = 0U; i < udev->bp.num_ep; i++) {
udev->regs.er_in[i]->DIEPINTF = 0xFFU;
udev->regs.er_out[i]->DOEPINTF = 0xFFU;
}
/* clear all pending device endpoint interrupts */
udev->regs.dr->DAEPINT = 0xFFFFFFFFU;
/* enable endpoint 0 interrupts */
udev->regs.dr->DAEPINTEN = 1U | (1U << 16);
/* enable OUT endpoint interrupts */
udev->regs.dr->DOEPINTEN = DOEPINTEN_STPFEN | DOEPINTEN_TFEN;
#ifdef USB_HS_DEDICATED_EP1_ENABLED
udev->regs.dr->DOEP1INTEN = DOEPINTEN_STPFEN | DOEPINTEN_TFEN;
#endif /* USB_HS_DEDICATED_EP1_ENABLED */
/* enable IN endpoint interrupts */
udev->regs.dr->DIEPINTEN = DIEPINTEN_TFEN;
#ifdef USB_HS_DEDICATED_EP1_ENABLED
udev->regs.dr->DIEP1INTEN = DIEPINTEN_TFEN;
#endif /* USB_HS_DEDICATED_EP1_ENABLED */
/* reset device address */
udev->regs.dr->DCFG &= ~DCFG_DAR;
/* configure endpoint 0 to receive SETUP packets */
usb_ctlep_startout(udev);
/* clear USB reset interrupt */
udev->regs.gr->GINTF = GINTF_RST;
udev->dev.transc_out[0] = (usb_transc) {
.ep_type = USB_EPTYPE_CTRL,
.max_len = USB_FS_EP0_MAX_LEN
};
(void)usb_transc_active(udev, &udev->dev.transc_out[0]);
udev->dev.transc_in[0] = (usb_transc) {
.ep_addr = {
.dir = 1U
},
.ep_type = USB_EPTYPE_CTRL,
.max_len = USB_FS_EP0_MAX_LEN
};
(void)usb_transc_active(udev, &udev->dev.transc_in[0]);
/* upon reset call user call back */
udev->dev.cur_status = (uint8_t)USBD_DEFAULT;
return 1U;
}
/*!
\brief handle USB speed enumeration finish interrupt
\param[in] udev: pointer to USB device instance
\param[out] none
\retval status
*/
static uint32_t usbd_int_enumfinish(usb_core_driver *udev)
{
uint8_t enum_speed = (uint8_t)((udev->regs.dr->DSTAT & DSTAT_ES) >> 1);
udev->regs.dr->DCTL &= ~DCTL_CGINAK;
udev->regs.dr->DCTL |= DCTL_CGINAK;
udev->regs.gr->GUSBCS &= ~GUSBCS_UTT;
/* set USB turn-around time based on device speed and PHY interface */
if((uint8_t)USB_SPEED_HIGH == USB_SPEED[enum_speed]) {
udev->bp.core_speed = (uint8_t)USB_SPEED_HIGH;
udev->regs.gr->GUSBCS |= 0x09U << 10;
} else {
udev->bp.core_speed = (uint8_t)USB_SPEED_FULL;
udev->regs.gr->GUSBCS |= 0x05U << 10;
}
/* clear interrupt */
udev->regs.gr->GINTF = GINTF_ENUMFIF;
return 1U;
}
/*!
\brief USB suspend interrupt handler
\param[in] udev: pointer to USB device instance
\param[out] none
\retval operation status
*/
static uint32_t usbd_int_suspend(usb_core_driver *udev)
{
__IO uint8_t low_power = udev->bp.low_power;
__IO uint8_t suspend = (uint8_t)(udev->regs.dr->DSTAT & DSTAT_SPST);
__IO uint8_t is_configured = ((uint8_t)USBD_CONFIGURED == udev->dev.cur_status) ? 1U : 0U;
udev->dev.backup_status = udev->dev.cur_status;
udev->dev.cur_status = (uint8_t)USBD_SUSPENDED;
if(low_power && suspend && is_configured) {
/* switch-off the USB clocks */
*udev->regs.PWRCLKCTL |= PWRCLKCTL_SUCLK | PWRCLKCTL_SHCLK;
/* enter DEEP_SLEEP mode with LDO in low power mode */
pmu_to_deepsleepmode(PMU_LDO_LOWPOWER, PMU_LOWDRIVER_DISABLE, WFI_CMD);
}
/* clear interrupt */
udev->regs.gr->GINTF = GINTF_SP;
return 1U;
}
/*!
\brief check FIFO for the next packet to be loaded
\param[in] udev: pointer to USB device instance
\param[in] ep_num: endpoint identifier which is in (0..3)
\param[out] none
\retval status
*/
static uint32_t usbd_emptytxfifo_write(usb_core_driver *udev, uint32_t ep_num)
{
uint32_t len;
uint32_t word_count;
usb_transc *transc = &udev->dev.transc_in[ep_num];
len = transc->xfer_len - transc->xfer_count;
/* get the data length to write */
if(len > transc->max_len) {
len = transc->max_len;
}
word_count = (len + 3U) / 4U;
while(((udev->regs.er_in[ep_num]->DIEPTFSTAT & DIEPTFSTAT_IEPTFS) >= word_count) && \
(transc->xfer_count < transc->xfer_len)) {
len = transc->xfer_len - transc->xfer_count;
if(len > transc->max_len) {
len = transc->max_len;
}
/* write FIFO in word(4bytes) */
word_count = (len + 3U) / 4U;
/* write the FIFO */
(void)usb_txfifo_write(&udev->regs, transc->xfer_buf, (uint8_t)ep_num, (uint16_t)len);
transc->xfer_buf += len;
transc->xfer_count += len;
if(transc->xfer_count == transc->xfer_len) {
/* disable the device endpoint FIFO empty interrupt */
udev->regs.dr->DIEPFEINTEN &= ~(0x01U << ep_num);
}
}
return 1U;
}
@@ -0,0 +1,643 @@
/*!
\file drv_usbh_int.c
\brief USB host mode interrupt handler file
\version 2024-12-20, V3.3.1, firmware for GD32F4xx
*/
/*
Copyright (c) 2024, GigaDevice Semiconductor Inc.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
OF SUCH DAMAGE.
*/
#include "drv_usbh_int.h"
#if defined (__CC_ARM) /*!< ARM compiler */
#pragma O0
#elif defined (__GNUC__) /*!< GNU compiler */
#pragma GCC optimize ("O0")
#elif defined (__TASKING__) /*!< TASKING compiler */
#pragma optimize=0
#endif /* __CC_ARM */
/* local function prototypes ('static') */
static uint32_t usbh_int_port(usb_core_driver *udev);
static uint32_t usbh_int_pipe(usb_core_driver *udev);
static uint32_t usbh_int_pipe_in(usb_core_driver *udev, uint32_t pp_num);
static uint32_t usbh_int_pipe_out(usb_core_driver *udev, uint32_t pp_num);
static uint32_t usbh_int_rxfifonoempty(usb_core_driver *udev);
static uint32_t usbh_int_txfifoempty(usb_core_driver *udev, usb_pipe_mode pp_mode);
/*!
\brief handle global host interrupt
\param[in] udev: pointer to USB core instance
\param[out] none
\retval operation status
*/
uint32_t usbh_isr(usb_core_driver *udev)
{
uint32_t retval = 0U;
__IO uint32_t intr = 0U;
/* check if host mode */
if(HOST_MODE == (udev->regs.gr->GINTF & GINTF_COPM)) {
intr = usb_coreintr_get(&udev->regs);
if(!intr) {
return 0U;
}
if(intr & GINTF_SOF) {
usbh_int_fop->SOF(udev->host.data);
/* clear interrupt */
udev->regs.gr->GINTF = GINTF_SOF;
}
if(intr & GINTF_RXFNEIF) {
retval |= usbh_int_rxfifonoempty(udev);
}
if(intr & GINTF_NPTXFEIF) {
retval |= usbh_int_txfifoempty(udev, PIPE_NON_PERIOD);
}
if(intr & GINTF_PTXFEIF) {
retval |= usbh_int_txfifoempty(udev, PIPE_PERIOD);
}
if(intr & GINTF_HCIF) {
retval |= usbh_int_pipe(udev);
}
if(intr & GINTF_HPIF) {
retval |= usbh_int_port(udev);
}
if(intr & GINTF_WKUPIF) {
/* clear interrupt */
udev->regs.gr->GINTF = GINTF_WKUPIF;
}
if(intr & GINTF_DISCIF) {
usbh_int_fop->disconnect(udev->host.data);
/* clear interrupt */
udev->regs.gr->GINTF = GINTF_DISCIF;
}
if(intr & GINTF_ISOONCIF) {
udev->regs.pr[0]->HCHCTL |= HCHCTL_CEN | HCHCTL_CDIS;
/* clear interrupt */
udev->regs.gr->GINTF = GINTF_ISOONCIF;
}
if(intr & GINTF_SESIF) {
usb_portvbus_switch(udev, 1U);
udev->regs.gr->GINTF = GINTF_SESIF;
}
}
return retval;
}
/*!
\brief handle USB pipe halt
\param[in] udev: pointer to USB core instance
\param[in] pp_num: pp_num: host channel number which is in (0..7)
\param[in] pp_int: pipe interrupt
\param[in] pp_status: pipe status
\param[out] none
\retval none
*/
static inline void usb_pp_halt(usb_core_driver *udev, \
uint8_t pp_num, \
uint32_t pp_int, \
usb_pipe_status pp_status)
{
udev->regs.pr[pp_num]->HCHINTEN |= HCHINTEN_CHIE;
usb_pipe_halt(udev, pp_num);
udev->regs.pr[pp_num]->HCHINTF = pp_int;
udev->host.pipe[pp_num].pp_status = pp_status;
}
/*!
\brief handle the host port interrupt
\param[in] udev: pointer to USB device instance
\param[out] none
\retval operation status
*/
#if defined (__ICCARM__) /*!< IAR compiler */
#pragma optimize = none
#endif /* __ICCARM */
static uint32_t usbh_int_port(usb_core_driver *udev)
{
uint32_t retval = 0U;
/* note: when the USB PHY use USB HS PHY, the flag is needed */
uint8_t port_reset = 0U;
__IO uint32_t port_state = *udev->regs.HPCS;
/* clear the interrupt bit in GINTF */
port_state &= ~(HPCS_PE | HPCS_PCD | HPCS_PEDC);
/* port connect detected */
if(*udev->regs.HPCS & HPCS_PCD) {
port_state |= HPCS_PCD;
usbh_int_fop->connect(udev->host.data);
retval |= 1U;
}
/* port enable changed */
if(*udev->regs.HPCS & HPCS_PEDC) {
port_state |= HPCS_PEDC;
if(*udev->regs.HPCS & HPCS_PE) {
uint32_t port_speed = usb_curspeed_get(udev);
uint32_t clock_type = udev->regs.hr->HCTL & HCTL_CLKSEL;
udev->host.connect_status = 1U;
if(PORT_SPEED_LOW == port_speed) {
udev->regs.hr->HFT = 6000U;
if(HCTL_6MHZ != clock_type) {
if(USB_EMBEDDED_PHY == udev->bp.phy_itf) {
usb_phyclock_config(udev, HCTL_6MHZ);
}
port_reset = 1U;
}
} else if(PORT_SPEED_FULL == port_speed) {
udev->regs.hr->HFT = 48000U;
if(HCTL_48MHZ != clock_type) {
if(USB_EMBEDDED_PHY == udev->bp.phy_itf) {
usb_phyclock_config(udev, HCTL_48MHZ);
}
port_reset = 1U;
}
} else {
/* for high speed device and others */
port_reset = 1U;
}
udev->host.port_enabled = 1U;
udev->regs.gr->GINTEN |= GINTEN_DISCIE | GINTEN_SOFIE;
} else {
udev->host.port_enabled = 0U;
}
}
if(port_reset) {
usb_port_reset(udev);
}
/* clear port interrupts */
*udev->regs.HPCS = port_state;
return retval;
}
/*!
\brief handle all host channels interrupt
\param[in] udev: pointer to USB device instance
\param[out] none
\retval operation status
*/
static uint32_t usbh_int_pipe(usb_core_driver *udev)
{
uint32_t pp_num = 0U;
uint32_t retval = 0U;
for(pp_num = 0U; pp_num < udev->bp.num_pipe; pp_num++) {
if((udev->regs.hr->HACHINT & HACHINT_HACHINT) & (1UL << pp_num)) {
if(udev->regs.pr[pp_num]->HCHCTL & HCHCTL_EPDIR) {
retval |= usbh_int_pipe_in(udev, pp_num);
} else {
retval |= usbh_int_pipe_out(udev, pp_num);
}
}
}
return retval;
}
/*!
\brief handle the IN channel interrupt
\param[in] udev: pointer to USB device instance
\param[in] pp_num: host channel number which is in (0..7)
\param[out] none
\retval operation status
*/
#if defined (__ICCARM__) /*!< IAR compiler */
#pragma optimize = none
#endif /* __ICCARM */
static uint32_t usbh_int_pipe_in(usb_core_driver *udev, uint32_t pp_num)
{
usb_pr *pp_reg = udev->regs.pr[pp_num];
usb_pipe *pp = &udev->host.pipe[pp_num];
uint32_t intr_pp = pp_reg->HCHINTF;
intr_pp &= pp_reg->HCHINTEN;
uint8_t ep_type = (uint8_t)((pp_reg->HCHCTL & HCHCTL_EPTYPE) >> 18);
if(intr_pp & HCHINTF_ACK) {
pp_reg->HCHINTF = HCHINTF_ACK;
} else if(intr_pp & HCHINTF_STALL) {
usb_pp_halt(udev, (uint8_t)pp_num, HCHINTF_STALL, PIPE_STALL);
pp_reg->HCHINTF = HCHINTF_NAK;
/* note: When there is a 'STALL', reset also NAK,
else, the udev->host.pp_status = HC_STALL
will be overwritten by 'NAK' in code below */
intr_pp &= ~HCHINTF_NAK;
} else if(intr_pp & HCHINTF_DTER) {
usb_pp_halt(udev, (uint8_t)pp_num, HCHINTF_DTER, PIPE_DTGERR);
pp_reg->HCHINTF = HCHINTF_NAK;
} else {
/* no operation */
}
if(intr_pp & HCHINTF_REQOVR) {
usb_pp_halt(udev, (uint8_t)pp_num, HCHINTF_REQOVR, PIPE_REQOVR);
} else if(intr_pp & HCHINTF_TF) {
if((uint8_t)USB_USE_DMA == udev->bp.transfer_mode) {
udev->host.backup_xfercount[pp_num] = pp->xfer_len - (pp_reg->HCHLEN & HCHLEN_TLEN);
}
pp->pp_status = PIPE_XF;
pp->err_count = 0U;
pp_reg->HCHINTF = HCHINTF_TF;
switch(ep_type) {
case USB_EPTYPE_CTRL:
case USB_EPTYPE_BULK:
if(USB_USE_DMA == udev->bp.transfer_mode) {
udev->regs.pr[pp_num]->HCHINTEN |= HCHINTEN_CHIE;
} else {
usb_pp_halt(udev, (uint8_t)pp_num, HCHINTF_NAK, PIPE_XF);
}
pp->data_toggle_in ^= 1U;
break;
case USB_EPTYPE_INTR:
case USB_EPTYPE_ISOC:
pp_reg->HCHCTL |= HCHCTL_ODDFRM;
pp->urb_state = URB_DONE;
break;
default:
break;
}
} else if(intr_pp & HCHINTF_CH) {
pp_reg->HCHINTEN &= ~HCHINTEN_CHIE;
switch(pp->pp_status) {
case PIPE_XF:
pp->urb_state = URB_DONE;
break;
case PIPE_STALL:
pp->urb_state = URB_STALL;
break;
case PIPE_TRACERR:
case PIPE_DTGERR:
pp->err_count = 0U;
pp->urb_state = URB_ERROR;
pp->data_toggle_in ^= 1U;
break;
case PIPE_IDLE:
case PIPE_HALTED:
case PIPE_NAK:
case PIPE_NYET:
case PIPE_BBERR:
case PIPE_REQOVR:
default:
if((uint8_t)USB_EPTYPE_INTR == ep_type) {
pp->data_toggle_in ^= 1U;
}
break;
}
pp_reg->HCHINTF = HCHINTF_CH;
} else if(intr_pp & HCHINTF_USBER) {
pp->err_count++;
usb_pp_halt(udev, (uint8_t)pp_num, HCHINTF_USBER, PIPE_TRACERR);
} else if(intr_pp & HCHINTF_NAK) {
switch(ep_type) {
case USB_EPTYPE_CTRL:
case USB_EPTYPE_BULK:
/* re-activate the channel */
pp_reg->HCHCTL = (pp_reg->HCHCTL | HCHCTL_CEN) & ~HCHCTL_CDIS;
break;
case USB_EPTYPE_INTR:
pp_reg->HCHINTEN |= HCHINTEN_CHIE;
(void)usb_pipe_halt(udev, (uint8_t)pp_num);
break;
default:
break;
}
pp->pp_status = PIPE_NAK;
pp_reg->HCHINTF = HCHINTF_NAK;
} else {
/* no operation */
}
return 1U;
}
/*!
\brief handle the OUT channel interrupt
\param[in] udev: pointer to USB device instance
\param[in] pp_num: host channel number which is in (0..7)
\param[out] none
\retval operation status
*/
#if defined (__ICCARM__) /*!< IAR compiler */
#pragma optimize = none
#endif /* __ICCARM */
static uint32_t usbh_int_pipe_out(usb_core_driver *udev, uint32_t pp_num)
{
usbh_host *uhost = udev->host.data;
usb_pr *pp_reg = udev->regs.pr[pp_num];
usb_pipe *pp = &udev->host.pipe[pp_num];
uint32_t intr_pp = pp_reg->HCHINTF;
intr_pp &= pp_reg->HCHINTEN;
if(intr_pp & HCHINTF_ACK) {
if(1U == udev->host.pipe[pp_num].do_ping) {
udev->host.pipe[pp_num].do_ping = 0U;
pp->err_count = 0U;
usb_pp_halt(udev, (uint8_t)pp_num, HCHINTF_ACK, PIPE_NAK);
}
pp_reg->HCHINTF = HCHINTF_ACK;
} else if(intr_pp & HCHINTF_STALL) {
usb_pp_halt(udev, (uint8_t)pp_num, HCHINTF_STALL, PIPE_STALL);
} else if(intr_pp & HCHINTF_DTER) {
usb_pp_halt(udev, (uint8_t)pp_num, HCHINTF_DTER, PIPE_DTGERR);
pp_reg->HCHINTF = HCHINTF_NAK;
} else if(intr_pp & HCHINTF_REQOVR) {
usb_pp_halt(udev, (uint8_t)pp_num, HCHINTF_REQOVR, PIPE_REQOVR);
} else if(intr_pp & HCHINTF_TF) {
pp->err_count = 0U;
usb_pp_halt(udev, (uint8_t)pp_num, HCHINTF_TF, PIPE_XF);
} else if(intr_pp & HCHINTF_NAK) {
if(0U == udev->host.pipe[pp_num].do_ping) {
if(1U == udev->host.pipe[pp_num].supp_ping) {
udev->host.pipe[pp_num].do_ping = 1U;
}
}
pp->err_count = 0U;
if(USB_USE_FIFO == udev->bp.transfer_mode) {
usb_pp_halt(udev, (uint8_t)pp_num, HCHINTF_NAK, PIPE_NAK);
} else {
pp_reg->HCHINTF = HCHINTF_NAK;
}
usb_pp_halt(udev, (uint8_t)pp_num, HCHINTF_NAK, PIPE_NAK);
} else if(intr_pp & HCHINTF_USBER) {
pp->err_count++;
usb_pp_halt(udev, (uint8_t)pp_num, HCHINTF_USBER, PIPE_TRACERR);
} else if(intr_pp & HCHINTF_NYET) {
if(CTL_STATUS_OUT != uhost->control.ctl_state) {
if(0U == udev->host.pipe[pp_num].do_ping) {
if(1U == udev->host.pipe[pp_num].supp_ping) {
udev->host.pipe[pp_num].do_ping = 1U;
}
}
usb_pp_halt(udev, (uint8_t)pp_num, HCHINTF_NYET, PIPE_NYET);
} else {
usb_pp_halt(udev, (uint8_t)pp_num, HCHINTF_NYET, PIPE_XF);
}
pp->err_count = 0U;
} else if(intr_pp & HCHINTF_CH) {
udev->regs.pr[pp_num]->HCHINTEN &= ~HCHINTEN_CHIE;
switch(pp->pp_status) {
case PIPE_XF:
pp->urb_state = URB_DONE;
if((uint8_t)USB_EPTYPE_BULK == ((pp_reg->HCHCTL & HCHCTL_EPTYPE) >> 18)) {
pp->data_toggle_out ^= 1U;
}
break;
case PIPE_NAK:
pp->urb_state = URB_NOTREADY;
break;
case PIPE_NYET:
pp->urb_state = URB_DONE;
if((uint8_t)USB_EPTYPE_BULK == ((pp_reg->HCHCTL & HCHCTL_EPTYPE) >> 18)) {
pp->data_toggle_out ^= 1U;
}
break;
case PIPE_STALL:
pp->urb_state = URB_STALL;
break;
case PIPE_TRACERR:
if(3U == pp->err_count) {
pp->urb_state = URB_ERROR;
pp->err_count = 0U;
}
break;
case PIPE_IDLE:
case PIPE_HALTED:
case PIPE_BBERR:
case PIPE_REQOVR:
case PIPE_DTGERR:
default:
break;
}
pp_reg->HCHINTF = HCHINTF_CH;
} else {
/* no operation */
}
return 1U;
}
/*!
\brief handle the RX FIFO non-empty interrupt
\param[in] udev: pointer to USB device instance
\param[out] none
\retval operation status
*/
#if defined (__ICCARM__) /*!< IAR compiler */
#pragma optimize = none
#endif /* __ICCARM */
static uint32_t usbh_int_rxfifonoempty(usb_core_driver *udev)
{
uint32_t count = 0U, xfer_count = 0U;
__IO uint8_t pp_num = 0U;
__IO uint32_t rx_stat = 0U;
/* disable the RX status queue level interrupt */
udev->regs.gr->GINTEN &= ~GINTEN_RXFNEIE;
rx_stat = udev->regs.gr->GRSTATP;
pp_num = (uint8_t)(rx_stat & GRSTATRP_CNUM);
switch((rx_stat & GRSTATRP_RPCKST) >> 17) {
case GRXSTS_PKTSTS_IN:
count = (rx_stat & GRSTATRP_BCOUNT) >> 4;
/* read the data into the host buffer. */
if((count > 0U) && (NULL != udev->host.pipe[pp_num].xfer_buf)) {
(void)usb_rxfifo_read(&udev->regs, udev->host.pipe[pp_num].xfer_buf, (uint16_t)count);
/* manage multiple transfer packet */
udev->host.pipe[pp_num].xfer_buf += count;
udev->host.pipe[pp_num].xfer_count += count;
xfer_count = udev->host.pipe[pp_num].xfer_count;
udev->host.backup_xfercount[pp_num] = xfer_count;
if(udev->regs.pr[pp_num]->HCHLEN & HCHLEN_PCNT) {
/* re-activate the channel when more packets are expected */
uint32_t pp_ctl = udev->regs.pr[pp_num]->HCHCTL;
pp_ctl |= HCHCTL_CEN;
pp_ctl &= ~HCHCTL_CDIS;
udev->regs.pr[pp_num]->HCHCTL = pp_ctl;
}
}
break;
case GRXSTS_PKTSTS_IN_XFER_COMP:
break;
case GRXSTS_PKTSTS_DATA_TOGGLE_ERR:
count = (rx_stat & GRSTATRP_BCOUNT) >> 4;
while(count > 0U) {
rx_stat = udev->regs.gr->GRSTATP;
count--;
}
break;
case GRXSTS_PKTSTS_CH_HALTED:
break;
default:
break;
}
/* enable the RX status queue level interrupt */
udev->regs.gr->GINTEN |= GINTEN_RXFNEIE;
return 1U;
}
/*!
\brief handle the TX FIFO empty interrupt
\param[in] udev: pointer to USB device instance
\param[in] pp_mode: pipe mode
\param[out] none
\retval operation status
*/
#if defined (__ICCARM__) /*!< IAR compiler */
#pragma optimize = none
#endif /* __ICCARM */
static uint32_t usbh_int_txfifoempty(usb_core_driver *udev, usb_pipe_mode pp_mode)
{
uint8_t pp_num = 0U;
uint16_t word_count = 0U, len = 0U;
__IO uint32_t *txfiforeg = 0U, txfifostate = 0U;
if(PIPE_NON_PERIOD == pp_mode) {
txfiforeg = &udev->regs.gr->HNPTFQSTAT;
} else if(PIPE_PERIOD == pp_mode) {
txfiforeg = &udev->regs.hr->HPTFQSTAT;
} else {
return 0U;
}
txfifostate = *txfiforeg;
pp_num = (uint8_t)((txfifostate & TFQSTAT_CNUM) >> 27);
word_count = (uint16_t)(udev->host.pipe[pp_num].xfer_len + 3U) / 4U;
while(((txfifostate & TFQSTAT_TXFS) >= word_count) && (0U != udev->host.pipe[pp_num].xfer_len)) {
len = (uint16_t)(txfifostate & TFQSTAT_TXFS) * 4U;
if(len > udev->host.pipe[pp_num].xfer_len) {
/* last packet */
len = (uint16_t)udev->host.pipe[pp_num].xfer_len;
if(PIPE_NON_PERIOD == pp_mode) {
udev->regs.gr->GINTEN &= ~GINTEN_NPTXFEIE;
} else {
udev->regs.gr->GINTEN &= ~GINTEN_PTXFEIE;
}
}
word_count = (uint16_t)((udev->host.pipe[pp_num].xfer_len + 3U) / 4U);
usb_txfifo_write(&udev->regs, udev->host.pipe[pp_num].xfer_buf, pp_num, len);
udev->host.pipe[pp_num].xfer_buf += len;
udev->host.pipe[pp_num].xfer_len -= len;
udev->host.pipe[pp_num].xfer_count += len;
txfifostate = *txfiforeg;
}
return 1U;
}