initial commit
This commit is contained in:
commit
b44ce7bd77
2716 files changed
+559005
No files matched your search
@@ -0,0 +1,319 @@
|
||||
/*!
|
||||
\file drv_usb_core.c
|
||||
\brief USB core driver which can operate in host and device mode
|
||||
|
||||
\version 2019-6-5, V1.0.0, firmware for GD32 USBFS&USBHS
|
||||
*/
|
||||
|
||||
/*
|
||||
Copyright (c) 2019, 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"
|
||||
|
||||
/*!
|
||||
\brief config USB core to soft reset
|
||||
\param[in] usb_regs: 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);
|
||||
|
||||
/* wait for addtional 3 PHY clocks */
|
||||
usb_udelay(3);
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief config 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)
|
||||
{
|
||||
uint32_t i = 0, reg_base = 0;
|
||||
|
||||
/* config USB default transfer mode as FIFO mode */
|
||||
usb_basic->transfer_mode = USB_USE_FIFO;
|
||||
|
||||
/* USB default speed is full-speed */
|
||||
usb_basic->core_speed = USB_SPEED_FULL;
|
||||
|
||||
usb_basic->core_enum = usb_core;
|
||||
|
||||
switch (usb_core) {
|
||||
case USB_CORE_ENUM_HS:
|
||||
reg_base = 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
|
||||
bp->transfer_mode = USB_USE_DMA;
|
||||
#endif /* USB_HS_INTERNAL_DMA_ENABLED */
|
||||
break;
|
||||
|
||||
case USB_CORE_ENUM_FS:
|
||||
reg_base = 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;
|
||||
break;
|
||||
|
||||
default:
|
||||
return USB_FAIL;
|
||||
}
|
||||
|
||||
usb_basic->sof_enable = USB_SOF_OUTPUT;
|
||||
usb_basic->low_power = USB_LOW_POWER;
|
||||
|
||||
/* assign main registers address */
|
||||
*usb_regs = (usb_core_regs) {
|
||||
.gr = (usb_gr*)(reg_base + USB_REG_OFFSET_CORE),
|
||||
.hr = (usb_hr*)(reg_base + USB_REG_OFFSET_HOST),
|
||||
.dr = (usb_dr*)(reg_base + USB_REG_OFFSET_DEV),
|
||||
|
||||
.HPCS = (uint32_t*)(reg_base + USB_REG_OFFSET_PORT),
|
||||
.PWRCLKCTL = (uint32_t*)(reg_base + USB_REG_OFFSET_PWRCLKCTL)
|
||||
};
|
||||
|
||||
/* assign device endpoint registers address */
|
||||
for (i = 0; i < usb_basic->num_ep; i++) {
|
||||
usb_regs->er_in[i] = (usb_erin*) \
|
||||
(reg_base + USB_REG_OFFSET_EP_IN + (i * USB_REG_OFFSET_EP));
|
||||
|
||||
usb_regs->er_out[i] = (usb_erout*)\
|
||||
(reg_base + USB_REG_OFFSET_EP_OUT + (i * USB_REG_OFFSET_EP));
|
||||
}
|
||||
|
||||
/* assign host pipe registers address */
|
||||
for (i = 0; i < usb_basic->num_pipe; i++) {
|
||||
usb_regs->pr[i] = (usb_pr*) \
|
||||
(reg_base + USB_REG_OFFSET_CH_INOUT + (i * USB_REG_OFFSET_CH));
|
||||
|
||||
usb_regs->DFIFO[i] = (uint32_t*) \
|
||||
(reg_base + 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] bp: usb capabilities
|
||||
\param[in] core_regs: usb core registers
|
||||
\param[out] none
|
||||
\retval operation status
|
||||
*/
|
||||
usb_status usb_core_init(usb_core_basic usb_basic, usb_core_regs* usb_regs)
|
||||
{
|
||||
uint32_t reg_value = usb_regs->gr->GCCFG;
|
||||
|
||||
/* disable USB global interrupt */
|
||||
usb_regs->gr->GAHBCS &= ~GAHBCS_GINTEN;
|
||||
|
||||
if (USB_ULPI_PHY == usb_basic.phy_itf) {
|
||||
reg_value &= ~GCCFG_PWRON;
|
||||
|
||||
if (usb_basic.sof_enable) {
|
||||
reg_value |= GCCFG_SOFOEN;
|
||||
}
|
||||
|
||||
usb_regs->gr->GCCFG = GCCFG_SOFOEN;
|
||||
|
||||
/* init 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
|
||||
|
||||
/* 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 */
|
||||
reg_value = GCCFG_PWRON | GCCFG_VBUSACEN | GCCFG_VBUSBCEN;
|
||||
|
||||
#ifndef VBUS_SENSING_ENABLED
|
||||
reg_value |= GCCFG_VBUSIG;
|
||||
#endif /* VBUS_SENSING_ENABLED */
|
||||
|
||||
/* enable SOF output */
|
||||
if (usb_basic.sof_enable) {
|
||||
reg_value |= GCCFG_SOFOEN;
|
||||
}
|
||||
|
||||
usb_regs->gr->GCCFG = reg_value;
|
||||
|
||||
usb_mdelay(20);
|
||||
}
|
||||
|
||||
if (USB_USE_DMA == usb_basic.transfer_mode) {
|
||||
usb_regs->gr->GAHBCS |= GAHBCS_DMAEN;
|
||||
usb_regs->gr->GAHBCS &= ~GAHBCS_BURST;
|
||||
usb_regs->gr->GAHBCS |= DMA_INCR8;
|
||||
}
|
||||
|
||||
#ifdef USE_OTG_MODE
|
||||
|
||||
/* enable USB OTG features */
|
||||
usb_regs->gr->GUSBCS |= GUSBCS_HNPCAP | GUSBCS_SRPCAP;
|
||||
|
||||
/* 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] core_regs: usb core registers
|
||||
\param[in] src_buf: pointer to source buffer
|
||||
\param[in] fifo_num: FIFO number which is in (0..3)
|
||||
\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-- > 0) {
|
||||
*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] core_regs: 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-- > 0) {
|
||||
*(uint32_t*)dest_buf = *fifo;
|
||||
|
||||
dest_buf += 4U;
|
||||
}
|
||||
|
||||
return ((void*)dest_buf);
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief flush a Tx FIFO or all Tx FIFOs
|
||||
\param[in] core_regs: pointer to usb core registers
|
||||
\param[in] fifo_num: FIFO number which is in (0..3)
|
||||
\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 << 6U) | GRSTCTL_TXFF;
|
||||
|
||||
/* wait for Tx FIFO flush bit is set */
|
||||
while (usb_regs->gr->GRSTCTL & GRSTCTL_TXFF);
|
||||
|
||||
/* wait for 3 PHY clocks*/
|
||||
usb_udelay(3);
|
||||
|
||||
return USB_OK;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief flush the entire Rx FIFO
|
||||
\param[in] core_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);
|
||||
|
||||
/* wait for 3 PHY clocks */
|
||||
usb_udelay(3);
|
||||
|
||||
return USB_OK;
|
||||
}
|
||||
@@ -0,0 +1,745 @@
|
||||
/*!
|
||||
\file drv_usb_dev.c
|
||||
\brief USB device mode low level driver
|
||||
|
||||
\version 2019-6-5, V1.0.0, firmware for GD32 USBFS&USBHS
|
||||
*/
|
||||
|
||||
/*
|
||||
Copyright (c) 2019, 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 "gd32vf103_libopt.h"
|
||||
#include "drv_usb_hw.h"
|
||||
#include "drv_usb_core.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
|
||||
|
||||
/* USB 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
|
||||
};
|
||||
|
||||
#elif defined(USB_HS_CORE)
|
||||
|
||||
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 /* USBFS_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)
|
||||
{
|
||||
uint32_t i, ram_addr = 0;
|
||||
|
||||
/* force to peripheral mode */
|
||||
udev->regs.gr->GUSBCS &= ~(GUSBCS_FDM | GUSBCS_FHM);
|
||||
udev->regs.gr->GUSBCS |= GUSBCS_FDM;
|
||||
// udev->regs.gr->GUSBCS &= ~(GUSBCS_FHM);
|
||||
|
||||
/* restart the Phy Clock (maybe don't need to...) */
|
||||
*udev->regs.PWRCLKCTL = 0U;
|
||||
|
||||
/* config 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 (udev->bp.core_enum == USB_CORE_ENUM_FS) {
|
||||
/* set full-speed PHY */
|
||||
udev->regs.dr->DCFG |= USB_SPEED_INP_FULL;
|
||||
|
||||
/* set Rx FIFO size */
|
||||
udev->regs.gr->GRFLEN = RX_FIFO_FS_SIZE;
|
||||
|
||||
/* set endpoint 0 Tx FIFO length and RAM address */
|
||||
udev->regs.gr->DIEP0TFLEN_HNPTFLEN = ((uint32_t)TX0_FIFO_FS_SIZE << 16) | \
|
||||
((uint32_t)RX_FIFO_FS_SIZE);
|
||||
|
||||
ram_addr = RX_FIFO_FS_SIZE;
|
||||
|
||||
/* set endpoint 1 to 3's Tx FIFO length and RAM address */
|
||||
for (i = 1; i < USBFS_MAX_EP_COUNT; i++) {
|
||||
ram_addr += USBFS_TX_FIFO_SIZE[i - 1];
|
||||
|
||||
udev->regs.gr->DIEPTFLEN[i - 1] = ((uint32_t)USBFS_TX_FIFO_SIZE[i] << 16U) | \
|
||||
ram_addr;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USB_HS_CORE
|
||||
if (udev->bp.core == USB_CORE_HS) {
|
||||
if (udev->bp.core_phy == USB_ULPI_PHY) {
|
||||
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 */
|
||||
udev->regs.gr->GRFLEN &= ~GRFLEN_RXFD;
|
||||
udev->regs.gr->GRFLEN |= RX_FIFO_HS_SIZE;
|
||||
|
||||
/* Set endpoint 0 Tx FIFO length and RAM address */
|
||||
udev->regs.gr->DIEP0TFLEN_HNPTFLEN = ((uint32_t)TX0_FIFO_HS_SIZE << 16) | \
|
||||
RX_FIFO_HS_SIZE;
|
||||
|
||||
ram_addr = RX_FIFO_HS_SIZE;
|
||||
|
||||
/* Set endpoint 1 to 3's Tx FIFO length and RAM address */
|
||||
for (i = 1; i < USBHS_MAX_EP_COUNT; i++) {
|
||||
ram_addr += USBHS_TX_FIFO_SIZE[i - 1];
|
||||
|
||||
udev->regs.gr->DIEPTFLEN[i - 1] = ((uint32_t)USBHS_TX_FIFO_SIZE[i] << 16) | \
|
||||
ram_addr;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/* make sure all FIFOs are flushed */
|
||||
|
||||
/* flush all Tx FIFOs */
|
||||
usb_txfifo_flush(&udev->regs, 0x10);
|
||||
|
||||
/* flush entire Rx FIFO */
|
||||
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 = 0; 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;
|
||||
}
|
||||
|
||||
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 (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_MFIE;
|
||||
|
||||
#ifdef VBUS_SENSING_ENABLED
|
||||
udev->regs.gr->GINTEN |= GINTEN_SESIE | GINTEN_OTGIE;
|
||||
#endif /* VBUS_SENSING_ENABLED */
|
||||
|
||||
/* enable USB global interrupt */
|
||||
udev->regs.gr->GAHBCS |= GAHBCS_GINTEN;
|
||||
|
||||
return USB_OK;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief config the USB device to be disconnected
|
||||
\param[in] udev: pointer to usb device
|
||||
\param[out] none
|
||||
\retval operation status
|
||||
*/
|
||||
void usb_dev_disconnect(usb_core_driver* udev)
|
||||
{
|
||||
udev->regs.dr->DCTL |= DCTL_SD;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief config the USB device to be connected
|
||||
\param[in] udev: pointer to usb device
|
||||
\param[out] none
|
||||
\retval operation status
|
||||
*/
|
||||
void usb_dev_connect(usb_core_driver* udev)
|
||||
{
|
||||
udev->regs.dr->DCTL &= ~DCTL_SD;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief set the USB device address
|
||||
\param[in] udev: pointer to usb device
|
||||
\param[in] dev_addr: device address for setting
|
||||
\param[out] none
|
||||
\retval operation status
|
||||
*/
|
||||
void usb_devaddr_set(usb_core_driver* udev, uint8_t dev_addr)
|
||||
{
|
||||
udev->regs.dr->DCFG &= ~DCFG_DAR;
|
||||
udev->regs.dr->DCFG |= dev_addr << 4;
|
||||
}
|
||||
|
||||
/*!
|
||||
\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;
|
||||
|
||||
__IO uint32_t epinten = 0U;
|
||||
|
||||
/* 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 = 1 << ep_num;
|
||||
} else {
|
||||
reg_addr = &udev->regs.er_out[ep_num]->DOEPCTL;
|
||||
|
||||
epinten = 1 << (16 + 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[udev->regs.dr->DSTAT & DSTAT_ES];
|
||||
} else {
|
||||
*reg_addr |= transc->max_len;
|
||||
}
|
||||
|
||||
/* activate endpoint */
|
||||
*reg_addr |= (transc->ep_type << 18) | (ep_num << 22) | DEPCTL_SD0PID | DEPCTL_EPACT;
|
||||
}
|
||||
|
||||
#ifdef USB_OTG_HS_DEDICATED_EP1_ENABLED
|
||||
if ((ep_num == 1) && (udev->bp.core == USB_HS_CORE_ID)) {
|
||||
udev->regs.dr->DEP1INTEN |= epinten;
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
/* enable the interrupts for this endpoint */
|
||||
udev->regs.dr->DAEPINTEN |= epinten;
|
||||
}
|
||||
|
||||
return USB_OK;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief deactive 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 = 1 << ep_num;
|
||||
|
||||
udev->regs.er_in[ep_num]->DIEPCTL &= ~DEPCTL_EPACT;
|
||||
} else {
|
||||
epinten = 1 << (ep_num + 16);
|
||||
|
||||
udev->regs.er_out[ep_num]->DOEPCTL &= ~DEPCTL_EPACT;
|
||||
}
|
||||
|
||||
#ifdef USB_OTG_HS_DEDICATED_EP1_ENABLED
|
||||
if ((ep_num == 1) && (udev->bp.core == USB_CORE_HS)) {
|
||||
udev->regs.dr->DEP1INTEN &= ~epinten;
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
/* 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 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 |= 1 << 19;
|
||||
} else {
|
||||
/* set transfer packet count */
|
||||
if (0U == ep_num) {
|
||||
transc->xfer_len = USB_MIN(transc->xfer_len, transc->max_len);
|
||||
|
||||
eplen |= 1 << 19;
|
||||
} else {
|
||||
eplen |= ((transc->xfer_len - 1 + transc->max_len) / transc->max_len) << 19;
|
||||
}
|
||||
|
||||
/* set endpoint transfer length */
|
||||
eplen |= transc->xfer_len;
|
||||
|
||||
if (transc->ep_type == USB_EPTYPE_ISOC) {
|
||||
eplen |= DIEPLEN_MCNT;
|
||||
}
|
||||
}
|
||||
|
||||
udev->regs.er_in[ep_num]->DIEPLEN = eplen;
|
||||
|
||||
if (USB_USE_DMA == udev->bp.transfer_mode) {
|
||||
udev->regs.er_in[ep_num]->DIEPDMAADDR = transc->dma_addr;
|
||||
}
|
||||
|
||||
if (transc->ep_type == USB_EPTYPE_ISOC) {
|
||||
if (((udev->regs.dr->DSTAT & DSTAT_FNRSOF) >> 8) & 0x1) {
|
||||
epctl |= DEPCTL_SD1PID;
|
||||
} else {
|
||||
epctl |= DEPCTL_SD0PID;
|
||||
}
|
||||
}
|
||||
|
||||
/* enable the endpoint and clear the NAK */
|
||||
epctl |= DEPCTL_CNAK | DEPCTL_EPEN;
|
||||
|
||||
udev->regs.er_in[ep_num]->DIEPCTL = epctl;
|
||||
|
||||
if (transc->ep_type != USB_EPTYPE_ISOC) {
|
||||
/* enable the Tx FIFO empty interrupt for this endpoint */
|
||||
if (transc->xfer_len > 0) {
|
||||
udev->regs.dr->DIEPFEINTEN |= 1 << ep_num;
|
||||
}
|
||||
} else {
|
||||
usb_txfifo_write(&udev->regs, transc->xfer_buf, ep_num, 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 - 1) / transc->max_len;
|
||||
|
||||
eplen |= packet_count << 19;
|
||||
eplen |= packet_count * transc->max_len;
|
||||
}
|
||||
|
||||
udev->regs.er_out[ep_num]->DOEPLEN = eplen;
|
||||
|
||||
if (USB_USE_DMA == udev->bp.transfer_mode) {
|
||||
udev->regs.er_out[ep_num]->DOEPDMAADDR = transc->dma_addr;
|
||||
}
|
||||
|
||||
if (transc->ep_type == USB_EPTYPE_ISOC) {
|
||||
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 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 bits */
|
||||
*reg_addr &= ~DEPCTL_STALL;
|
||||
|
||||
/* reset data PID of the periodic endpoints */
|
||||
if ((transc->ep_type == USB_EPTYPE_INTR) || (transc->ep_type == USB_EPTYPE_BULK)) {
|
||||
*reg_addr |= DEPCTL_SD0PID;
|
||||
}
|
||||
|
||||
return USB_OK;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief read device all OUT endpoint interrupt register
|
||||
\param[in] udev: pointer to usb device
|
||||
\param[out] none
|
||||
\retval none
|
||||
*/
|
||||
uint32_t usb_oepintnum_read(usb_core_driver* udev)
|
||||
{
|
||||
uint32_t value = udev->regs.dr->DAEPINT;
|
||||
|
||||
value &= udev->regs.dr->DAEPINTEN;
|
||||
|
||||
return (value & DAEPINT_OEPITB) >> 16;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief read device OUT endpoint interrupt flag register
|
||||
\param[in] udev: pointer to usb device
|
||||
\param[in] ep_num: endpoint number
|
||||
\param[out] none
|
||||
\retval none
|
||||
*/
|
||||
uint32_t usb_oepintr_read(usb_core_driver* udev, uint8_t ep_num)
|
||||
{
|
||||
uint32_t value = udev->regs.er_out[ep_num]->DOEPINTF;
|
||||
|
||||
value &= udev->regs.dr->DOEPINTEN;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief read device all IN endpoint interrupt register
|
||||
\param[in] udev: pointer to usb device
|
||||
\param[out] none
|
||||
\retval none
|
||||
*/
|
||||
uint32_t usb_iepintnum_read(usb_core_driver* udev)
|
||||
{
|
||||
uint32_t value = udev->regs.dr->DAEPINT;
|
||||
|
||||
value &= udev->regs.dr->DAEPINTEN;
|
||||
|
||||
return value & DAEPINT_IEPITB;
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
\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 none
|
||||
*/
|
||||
uint32_t usb_iepintr_read(usb_core_driver* udev, uint8_t ep_num)
|
||||
{
|
||||
uint32_t value = 0U, fifoemptymask = 0U, commonintmask = 0U;
|
||||
|
||||
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 (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 set remote wakeup signalling
|
||||
\param[in] udev: pointer to usb device
|
||||
\param[out] none
|
||||
\retval none
|
||||
*/
|
||||
void usb_rwkup_set(usb_core_driver* udev)
|
||||
{
|
||||
if (udev->dev.pm.dev_remote_wakeup) {
|
||||
/* enable remote wakeup signaling */
|
||||
udev->regs.dr->DCTL |= DCTL_RWKUP;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief reset remote wakeup signalling
|
||||
\param[in] udev: pointer to usb device
|
||||
\param[out] none
|
||||
\retval none
|
||||
*/
|
||||
void usb_rwkup_reset(usb_core_driver* udev)
|
||||
{
|
||||
if (udev->dev.pm.dev_remote_wakeup) {
|
||||
/* disable remote wakeup signaling */
|
||||
udev->regs.dr->DCTL &= ~DCTL_RWKUP;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief active remote wakeup signalling
|
||||
\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(5);
|
||||
|
||||
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) {
|
||||
/* un-gate 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, 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 = 1;
|
||||
|
||||
/* clear all interrupt flag and enable bits */
|
||||
for (i = 0; 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 */
|
||||
usb_rxfifo_flush(&udev->regs);
|
||||
usb_txfifo_flush(&udev->regs, 0x10);
|
||||
}
|
||||
@@ -0,0 +1,514 @@
|
||||
/*!
|
||||
\file drv_usb_host.c
|
||||
\brief USB host mode low level driver
|
||||
|
||||
\version 2019-6-5, V1.0.0, firmware for GD32 USBFS&USBHS
|
||||
*/
|
||||
|
||||
/*
|
||||
Copyright (c) 2019, 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_core.h"
|
||||
#include "drv_usb_host.h"
|
||||
|
||||
const uint32_t PIPE_DPID[] = {
|
||||
PIPE_DPID_DATA0,
|
||||
PIPE_DPID_DATA1
|
||||
};
|
||||
|
||||
//__STATIC_INLINE uint8_t usb_frame_even (usb_core_driver *pudev)
|
||||
uint32_t usb_frame_even(usb_core_driver* pudev)
|
||||
{
|
||||
return !(pudev->regs.hr->HFINFR & 0x01U);
|
||||
}
|
||||
|
||||
//__STATIC_INLINE void usb_phyclock_config (usb_core_driver *pudev, uint8_t clock)
|
||||
void usb_phyclock_config(usb_core_driver* pudev, uint8_t clock)
|
||||
{
|
||||
pudev->regs.hr->HCTL &= ~HCTL_CLKSEL;
|
||||
pudev->regs.hr->HCTL |= clock;
|
||||
}
|
||||
|
||||
//__STATIC_INLINE uint32_t usb_port_read (usb_core_driver *pudev)
|
||||
uint32_t usb_port_read(usb_core_driver* pudev)
|
||||
{
|
||||
return *pudev->regs.HPCS & ~(HPCS_PE | HPCS_PCD | HPCS_PEDC);
|
||||
}
|
||||
|
||||
//__STATIC_INLINE uint32_t usb_curspeed_get (usb_core_driver *pudev)
|
||||
|
||||
uint32_t usb_curspeed_get(usb_core_driver* pudev)
|
||||
{
|
||||
return *pudev->regs.HPCS & HPCS_PS;
|
||||
}
|
||||
|
||||
uint32_t usb_curframe_get(usb_core_driver* pudev)
|
||||
{
|
||||
return (pudev->regs.hr->HFINFR & 0xFFFFU);
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief initializes USB core for host mode
|
||||
\param[in] pudev: pointer to selected usb host
|
||||
\param[out] none
|
||||
\retval operation status
|
||||
*/
|
||||
usb_status usb_host_init(usb_core_driver* pudev)
|
||||
{
|
||||
uint32_t i = 0, inten = 0U;
|
||||
|
||||
uint32_t nptxfifolen = 0U;
|
||||
uint32_t ptxfifolen = 0U;
|
||||
|
||||
pudev->regs.gr->GUSBCS &= ~GUSBCS_FDM;
|
||||
pudev->regs.gr->GUSBCS |= GUSBCS_FHM;
|
||||
|
||||
/* restart the PHY Clock */
|
||||
*pudev->regs.PWRCLKCTL = 0U;
|
||||
|
||||
/* initialize host configuration register */
|
||||
if (USB_ULPI_PHY == pudev->bp.phy_itf) {
|
||||
usb_phyclock_config(pudev, HCTL_30_60MHZ);
|
||||
} else {
|
||||
usb_phyclock_config(pudev, HCTL_48MHZ);
|
||||
}
|
||||
|
||||
usb_port_reset(pudev);
|
||||
|
||||
/* support FS/LS only */
|
||||
pudev->regs.hr->HCTL &= ~HCTL_SPDFSLS;
|
||||
|
||||
/* configure data FIFOs size */
|
||||
#ifdef USB_FS_CORE
|
||||
if (USB_CORE_ENUM_FS == pudev->bp.core_enum) {
|
||||
/* set Rx FIFO size */
|
||||
pudev->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 << 16U;
|
||||
pudev->regs.gr->DIEP0TFLEN_HNPTFLEN = nptxfifolen;
|
||||
|
||||
/* set periodic Tx FIFO size and address */
|
||||
ptxfifolen |= USB_RX_FIFO_FS_SIZE + USB_HTX_PFIFO_FS_SIZE;
|
||||
ptxfifolen |= USB_HTX_PFIFO_FS_SIZE << 16U;
|
||||
pudev->regs.gr->HPTFLEN = ptxfifolen;
|
||||
}
|
||||
#endif /* USB_FS_CORE */
|
||||
|
||||
#ifdef USB_HS_CORE
|
||||
if (USB_CORE_HS == pudev->cfg.core) {
|
||||
/* set Rx FIFO size */
|
||||
pudev->regs.gr->GRFLEN = USBHS_RX_FIFO_SIZE;
|
||||
|
||||
/* set non-periodic Tx FIFO size and address */
|
||||
nptxfifolen |= USBHS_RX_FIFO_SIZE;
|
||||
nptxfifolen |= USBHS_HTX_NPFIFO_SIZE;
|
||||
pudev->regs.gr->DIEP0TFLEN_HNPTFLEN = nptxfifolen;
|
||||
|
||||
/* set periodic Tx FIFO size and address */
|
||||
ptxfifolen |= USBHS_RX_FIFO_SIZE + USBHS_HTX_PFIFO_SIZE;
|
||||
ptxfifolen |= USBHS_HTX_PFIFO_SIZE;
|
||||
pudev->regs.gr->HPTFLEN = ptxfifolen;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_OTG_MODE
|
||||
|
||||
/* clear host set hnp enable in the usb_otg control register */
|
||||
pudev->regs.gr->GOTGCS &= ~GOTGCS_HHNPEN;
|
||||
|
||||
#endif
|
||||
|
||||
/* disable all interrupts */
|
||||
pudev->regs.gr->GINTEN = 0U;
|
||||
|
||||
/* clear any pending USB OTG interrupts */
|
||||
pudev->regs.gr->GOTGINTF = 0xFFFFFFFFU;
|
||||
|
||||
/* enable the USB wakeup and suspend interrupts */
|
||||
pudev->regs.gr->GINTF = 0xBFFFFFFFU;
|
||||
|
||||
/* make sure the FIFOs are flushed */
|
||||
|
||||
/* flush all Tx FIFOs in device or host mode */
|
||||
usb_txfifo_flush(&pudev->regs, 0x10U);
|
||||
|
||||
/* flush the entire Rx FIFO */
|
||||
usb_rxfifo_flush(&pudev->regs);
|
||||
|
||||
/* clear all pending host channel interrupts */
|
||||
for (i = 0U; i < pudev->bp.num_pipe; i++) {
|
||||
pudev->regs.pr[i]->HCHINTF = 0xFFFFFFFFU;
|
||||
pudev->regs.pr[i]->HCHINTEN = 0U;
|
||||
}
|
||||
|
||||
#ifndef USE_OTG_MODE
|
||||
usb_portvbus_switch(pudev, 1U);
|
||||
#endif /* USE_OTG_MODE */
|
||||
|
||||
pudev->regs.gr->GINTEN = GINTEN_WKUPIE | GINTEN_SPIE;
|
||||
|
||||
/* enable host_mode-related interrupts */
|
||||
if (USB_USE_FIFO == pudev->bp.transfer_mode) {
|
||||
inten = GINTEN_RXFNEIE;
|
||||
}
|
||||
|
||||
inten |= GINTEN_HPIE | GINTEN_HCIE | GINTEN_ISOINCIE;
|
||||
|
||||
pudev->regs.gr->GINTEN |= inten;
|
||||
|
||||
inten = GINTEN_DISCIE | GINTEN_SOFIE;
|
||||
|
||||
pudev->regs.gr->GINTEN &= ~inten;
|
||||
|
||||
pudev->regs.gr->GAHBCS |= GAHBCS_GINTEN;
|
||||
|
||||
return USB_OK;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief control the VBUS to power
|
||||
\param[in] pudev: pointer to selected usb host
|
||||
\param[in] state: VBUS state
|
||||
\param[out] none
|
||||
\retval none
|
||||
*/
|
||||
void usb_portvbus_switch(usb_core_driver* pudev, 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(pudev);
|
||||
|
||||
if (!(port & HPCS_PP) && (1U == state)) {
|
||||
port |= HPCS_PP;
|
||||
}
|
||||
|
||||
if ((port & HPCS_PP) && (0U == state)) {
|
||||
port &= ~HPCS_PP;
|
||||
}
|
||||
|
||||
*pudev->regs.HPCS = port;
|
||||
|
||||
usb_mdelay(200U);
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief reset host port
|
||||
\param[in] pudev: pointer to usb device
|
||||
\param[out] none
|
||||
\retval operation status
|
||||
*/
|
||||
uint32_t usb_port_reset(usb_core_driver* pudev)
|
||||
{
|
||||
__IO uint32_t port = usb_port_read(pudev);
|
||||
|
||||
*pudev->regs.HPCS = port | HPCS_PRST;
|
||||
|
||||
usb_mdelay(100U); /* see note */
|
||||
|
||||
*pudev->regs.HPCS = port & ~HPCS_PRST;
|
||||
|
||||
usb_mdelay(20U);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief initialize host pipe
|
||||
\param[in] pudev: 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* pudev, 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 = &pudev->host.pipe[pipe_num];
|
||||
|
||||
/* clear old interrupt conditions for this host channel */
|
||||
pudev->regs.pr[pipe_num]->HCHINTF = 0xFFFFFFFFU;
|
||||
|
||||
if (USB_USE_DMA == pudev->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) {
|
||||
pp_inten |= HCHINTEN_NYETIE;
|
||||
|
||||
if (pp->ping) {
|
||||
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;
|
||||
}
|
||||
|
||||
pudev->regs.pr[pipe_num]->HCHINTEN = pp_inten;
|
||||
|
||||
/* enable the top level host channel interrupt */
|
||||
pudev->regs.hr->HACHINTEN |= 1U << pipe_num;
|
||||
|
||||
/* make sure host channel interrupts are enabled */
|
||||
pudev->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(pp->dev_speed == PORT_SPEED_LOW);
|
||||
|
||||
pp_ctl |= pp->ep.mps;
|
||||
pp_ctl |= ((uint32_t)(pp->ep.type == USB_EPTYPE_INTR) << 29U) & HCHCTL_ODDFRM;
|
||||
|
||||
pudev->regs.pr[pipe_num]->HCHCTL = pp_ctl;
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief prepare host channel for transferring packets
|
||||
\param[in] pudev: 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* pudev, 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 = &pudev->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 = (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 = packet_count * max_packet_len;
|
||||
}
|
||||
} else {
|
||||
packet_count = 1U;
|
||||
}
|
||||
|
||||
if (pp->ep.dir) {
|
||||
pp->xfer_len = packet_count * max_packet_len;
|
||||
}
|
||||
|
||||
/* initialize the host channel transfer information */
|
||||
pudev->regs.pr[pipe_num]->HCHLEN = pp->xfer_len | pp->DPID | PIPE_XFER_PCNT(packet_count);
|
||||
|
||||
if (USB_USE_DMA == pudev->bp.transfer_mode) {
|
||||
pudev->regs.pr[pipe_num]->HCHDMAADDR = (unsigned int)pp->xfer_buf;
|
||||
}
|
||||
|
||||
pp_ctl = pudev->regs.pr[pipe_num]->HCHCTL;
|
||||
|
||||
if (usb_frame_even(pudev)) {
|
||||
pp_ctl |= HCHCTL_ODDFRM;
|
||||
} else {
|
||||
pp_ctl &= ~HCHCTL_ODDFRM;
|
||||
}
|
||||
|
||||
/* set host channel enabled */
|
||||
pp_ctl |= HCHCTL_CEN;
|
||||
pp_ctl &= ~HCHCTL_CDIS;
|
||||
|
||||
pudev->regs.pr[pipe_num]->HCHCTL = pp_ctl;
|
||||
|
||||
if (USB_USE_FIFO == pudev->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 = (pp->xfer_len + 3U) / 4U;
|
||||
|
||||
/* check if there is enough space in fifo space */
|
||||
if (dword_len > (pudev->regs.gr->HNPTFQSTAT & HNPTFQSTAT_NPTXFS)) {
|
||||
/* need to process data in nptxfempty interrupt */
|
||||
pudev->regs.gr->GINTEN |= GINTEN_NPTXFEIE;
|
||||
}
|
||||
break;
|
||||
|
||||
/* periodic transfer */
|
||||
case USB_EPTYPE_INTR:
|
||||
case USB_EPTYPE_ISOC:
|
||||
dword_len = (pp->xfer_len + 3U) / 4U;
|
||||
|
||||
/* check if there is enough space in fifo space */
|
||||
if (dword_len > (pudev->regs.hr->HPTFQSTAT & HPTFQSTAT_PTXFS)) {
|
||||
/* need to process data in ptxfempty interrupt */
|
||||
pudev->regs.gr->GINTEN |= GINTEN_PTXFEIE;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
/* write packet into the tx fifo. */
|
||||
usb_txfifo_write(&pudev->regs, pp->xfer_buf, pipe_num, pp->xfer_len);
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief halt pipe
|
||||
\param[in] pudev: 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* pudev, uint8_t pipe_num)
|
||||
{
|
||||
__IO uint32_t pp_ctl = pudev->regs.pr[pipe_num]->HCHCTL;
|
||||
|
||||
uint8_t ep_type = (pp_ctl & HCHCTL_EPTYPE) >> 18U;
|
||||
|
||||
pp_ctl |= HCHCTL_CEN | HCHCTL_CDIS;
|
||||
|
||||
switch (ep_type) {
|
||||
case USB_EPTYPE_CTRL:
|
||||
case USB_EPTYPE_BULK:
|
||||
if (0U == (pudev->regs.gr->HNPTFQSTAT & HNPTFQSTAT_NPTXFS)) {
|
||||
pp_ctl &= ~HCHCTL_CEN;
|
||||
}
|
||||
break;
|
||||
|
||||
case USB_EPTYPE_INTR:
|
||||
case USB_EPTYPE_ISOC:
|
||||
if (0U == (pudev->regs.hr->HPTFQSTAT & HPTFQSTAT_PTXFS)) {
|
||||
pp_ctl &= ~HCHCTL_CEN;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
pudev->regs.pr[pipe_num]->HCHCTL = pp_ctl;
|
||||
|
||||
return USB_OK;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief configure host pipe to do ping operation
|
||||
\param[in] pudev: 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* pudev, uint8_t pipe_num)
|
||||
{
|
||||
uint32_t pp_ctl = 0U;
|
||||
|
||||
pudev->regs.pr[pipe_num]->HCHLEN = HCHLEN_PING | (HCHLEN_PCNT & (1U << 19U));
|
||||
|
||||
pp_ctl = pudev->regs.pr[pipe_num]->HCHCTL;
|
||||
|
||||
pp_ctl |= HCHCTL_CEN;
|
||||
pp_ctl &= ~HCHCTL_CDIS;
|
||||
|
||||
pudev->regs.pr[pipe_num]->HCHCTL = pp_ctl;
|
||||
|
||||
return USB_OK;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief stop the USB host and clean up FIFO
|
||||
\param[in] none
|
||||
\param[out] none
|
||||
\retval none
|
||||
*/
|
||||
void usb_host_stop(usb_core_driver* pudev)
|
||||
{
|
||||
uint32_t i;
|
||||
__IO uint32_t pp_ctl = 0U;
|
||||
|
||||
pudev->regs.hr->HACHINTEN = 0x0U;
|
||||
pudev->regs.hr->HACHINT = 0xFFFFFFFFU;
|
||||
|
||||
/* flush out any leftover queued requests. */
|
||||
for (i = 0U; i < pudev->bp.num_pipe; i++) {
|
||||
pp_ctl = pudev->regs.pr[i]->HCHCTL;
|
||||
|
||||
pp_ctl &= ~(HCHCTL_CEN | HCHCTL_EPDIR);
|
||||
pp_ctl |= HCHCTL_CDIS;
|
||||
|
||||
pudev->regs.pr[i]->HCHCTL = pp_ctl;
|
||||
}
|
||||
|
||||
/* flush the FIFO */
|
||||
usb_rxfifo_flush(&pudev->regs);
|
||||
usb_txfifo_flush(&pudev->regs, 0x10U);
|
||||
}
|
||||
@@ -0,0 +1,591 @@
|
||||
/*!
|
||||
\file drv_usbd_int.c
|
||||
\brief USB device mode interrupt routines
|
||||
|
||||
\version 2019-6-5, V1.0.0, firmware for GD32 USBFS&USBHS
|
||||
*/
|
||||
|
||||
/*
|
||||
Copyright (c) 2019, 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 "gd32vf103_libopt.h"
|
||||
//#include "usbd_conf.h"
|
||||
#include "drv_usbd_int.h"
|
||||
#include "usbd_transc.h"
|
||||
|
||||
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);
|
||||
|
||||
static const uint8_t USB_SPEED[4] = {
|
||||
[DSTAT_EM_HS_PHY_30MHZ_60MHZ] = USB_SPEED_HIGH,
|
||||
[DSTAT_EM_FS_PHY_30MHZ_60MHZ] = USB_SPEED_FULL,
|
||||
[DSTAT_EM_FS_PHY_48MHZ] = USB_SPEED_FULL,
|
||||
[DSTAT_EM_LS_PHY_6MHZ] = USB_SPEED_LOW
|
||||
};
|
||||
|
||||
__IO uint8_t setupc_flag = 0U;
|
||||
|
||||
#ifdef USB_OTG_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_OTG_EP1OUT_ISR_Handler(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) {
|
||||
oeplen = udev->regs.er_out[1]->DOEPLEN;
|
||||
|
||||
/* ToDo : handle more than one single MPS size packet */
|
||||
udev->dev.transc_out[1].xfer_count = udev->dev.transc_out[1].usb_transc - \
|
||||
(oeplen & DEPLEN_TLEN);
|
||||
}
|
||||
|
||||
/* RX COMPLETE */
|
||||
USBD_DCD_INT_fops->DataOutStage(udev, 1);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*!
|
||||
\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_OTG_EP1IN_ISR_Handler(usb_core_driver* udev)
|
||||
{
|
||||
uint32_t inten, intr, emptyen;
|
||||
|
||||
inten = udev->regs.dr->DIEP1INTEN;
|
||||
emptyen = udev->regs.dr->DIEPFEINTEN;
|
||||
|
||||
inten |= ((emptyen >> 1) & 0x1) << 7;
|
||||
|
||||
intr = udev->regs.er_in[1]->DIEPINTF & inten;
|
||||
|
||||
if (intr & DIEPINTF_TF) {
|
||||
udev->regs.dr->DIEPFEINTEN &= ~(0x1 << 1);
|
||||
|
||||
udev->regs.er_in[1]->DIEPINTF = DIEPINTF_TF;
|
||||
|
||||
/* TX COMPLETE */
|
||||
USBD_DCD_INT_fops->DataInStage(udev, 1);
|
||||
}
|
||||
|
||||
if (intr & DIEPINTF_TXFE) {
|
||||
DCD_WriteEmptyTxFifo(udev, 1);
|
||||
|
||||
udev->regs.er_in[1]->DIEPINTF = DIEPINTF_TXFE;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
/*!
|
||||
\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 & udev->regs.gr->GINTEN;
|
||||
|
||||
/* there are no interrupts, avoid spurious interrupt */
|
||||
if (!intr) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* OUT endpoints interrupts */
|
||||
if (intr & GINTF_OEPIF) {
|
||||
usbd_int_epout(udev);
|
||||
}
|
||||
|
||||
/* IN endpoints interrupts */
|
||||
if (intr & GINTF_IEPIF) {
|
||||
usbd_int_epin(udev);
|
||||
}
|
||||
|
||||
/* suspend interrupt */
|
||||
if (intr & GINTF_SP) {
|
||||
usbd_int_suspend(udev);
|
||||
}
|
||||
|
||||
/* wakeup interrupt */
|
||||
if (intr & GINTF_WKUPIF) {
|
||||
/* inform upper layer by the resume event */
|
||||
udev->dev.cur_status = udev->dev.backup_status;
|
||||
|
||||
/* clear interrupt */
|
||||
udev->regs.gr->GINTF = GINTF_WKUPIF;
|
||||
}
|
||||
|
||||
/* wakeup interrupt */
|
||||
if (intr & GINTF_MFIF) {
|
||||
|
||||
/* clear interrupt */
|
||||
udev->regs.gr->GINTF = GINTF_MFIF;
|
||||
}
|
||||
|
||||
/* start of frame interrupt */
|
||||
if (intr & GINTF_SOF) {
|
||||
if (udev->dev.class_core->SOF) {
|
||||
udev->dev.class_core->SOF(udev);
|
||||
}
|
||||
|
||||
if (0U != setupc_flag) {
|
||||
setupc_flag ++;
|
||||
|
||||
if (setupc_flag >= 3U) {
|
||||
usbd_setup_transc(udev);
|
||||
|
||||
setupc_flag = 0U;
|
||||
}
|
||||
}
|
||||
|
||||
/* clear interrupt */
|
||||
udev->regs.gr->GINTF = GINTF_SOF;
|
||||
}
|
||||
|
||||
/* receive FIFO not empty interrupt */
|
||||
if (intr & GINTF_RXFNEIF) {
|
||||
usbd_int_rxfifo(udev);
|
||||
}
|
||||
|
||||
/* USB reset interrupt */
|
||||
if (intr & GINTF_RST) {
|
||||
usbd_int_reset(udev);
|
||||
}
|
||||
|
||||
/* enumeration has been done interrupt */
|
||||
if (intr & GINTF_ENUMFIF) {
|
||||
usbd_int_enumfinish(udev);
|
||||
}
|
||||
|
||||
/* incomplete synchronization IN transfer interrupt*/
|
||||
if (intr & GINTF_ISOINCIF) {
|
||||
if (NULL != udev->dev.class_core->incomplete_isoc_in) {
|
||||
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) {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
\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;
|
||||
uint32_t ep_num = 0U;
|
||||
|
||||
for (epintnum = usb_oepintnum_read(udev); epintnum; epintnum >>= 1, ep_num++) {
|
||||
if (epintnum & 0x1) {
|
||||
__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 (USB_USE_DMA == udev->bp.transfer_mode) {
|
||||
__IO uint32_t eplen = udev->regs.er_out[ep_num]->DOEPLEN;
|
||||
|
||||
udev->dev.transc_out[ep_num].xfer_count = udev->dev.transc_out[ep_num].max_len - \
|
||||
(eplen & DEPLEN_TLEN);
|
||||
}
|
||||
|
||||
/* inform upper layer: data ready */
|
||||
usbd_out_transc(udev, ep_num);
|
||||
|
||||
if (USB_USE_DMA == udev->bp.transfer_mode) {
|
||||
if ((0U == ep_num) && (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 */
|
||||
if ((0U == ep_num) && (0U != setupc_flag)) {
|
||||
usbd_setup_transc(udev);
|
||||
|
||||
setupc_flag = 0U;
|
||||
|
||||
udev->regs.er_out[ep_num]->DOEPINTF = DOEPINTF_STPF;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*!
|
||||
\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;
|
||||
uint32_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 transmittion is completed */
|
||||
usbd_in_transc(udev, ep_num);
|
||||
|
||||
if (USB_USE_DMA == udev->bp.transfer_mode) {
|
||||
if ((0U == ep_num) && (USB_CTL_STATUS_IN == udev->dev.control.ctl_state)) {
|
||||
usb_ctlep_startout(udev);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (iepintr & DIEPINTF_TXFE) {
|
||||
usbd_emptytxfifo_write(udev, ep_num);
|
||||
|
||||
udev->regs.er_in[ep_num]->DIEPINTF = DIEPINTF_TXFE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*!
|
||||
\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 = 0;
|
||||
uint32_t bcount = 0;
|
||||
|
||||
__IO uint32_t devrxstat = 0;
|
||||
|
||||
/* 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;
|
||||
|
||||
transc = &udev->dev.transc_out[devrxstat & GRSTATRP_EPNUM];
|
||||
|
||||
bcount = (devrxstat & GRSTATRP_BCOUNT) >> 4;
|
||||
data_PID = (devrxstat & GRSTATRP_DPID) >> 15;
|
||||
|
||||
switch ((devrxstat & GRSTATRP_RPCKST) >> 17) {
|
||||
case RSTAT_GOUT_NAK:
|
||||
break;
|
||||
|
||||
case RSTAT_DATA_UPDT:
|
||||
if (bcount > 0) {
|
||||
usb_rxfifo_read(&udev->regs, transc->xfer_buf, bcount);
|
||||
|
||||
transc->xfer_buf += bcount;
|
||||
transc->xfer_count += bcount;
|
||||
}
|
||||
break;
|
||||
|
||||
case RSTAT_XFER_COMP:
|
||||
/* trigger the OUT enpoint interrupt */
|
||||
break;
|
||||
|
||||
case RSTAT_SETUP_COMP:
|
||||
/* trigger the OUT enpoint interrupt */
|
||||
break;
|
||||
|
||||
case RSTAT_SETUP_UPDT:
|
||||
if ((transc->ep_addr.num == 0) && (bcount == 8) && (data_PID == DPID_DATA0)) {
|
||||
/* copy the setup packet received in FIFO into the setup buffer in RAM */
|
||||
usb_rxfifo_read(&udev->regs, (uint8_t*)&udev->dev.control.req, bcount);
|
||||
|
||||
transc->xfer_count += bcount;
|
||||
|
||||
setupc_flag = 1;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
/* enable the Rx status queue level interrupt */
|
||||
udev->regs.gr->GINTEN |= GINTEN_RXFNEIE;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*!
|
||||
\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 */
|
||||
usb_txfifo_flush(&udev->regs, 0);
|
||||
|
||||
for (i = 0; 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;
|
||||
|
||||
/* enable IN endpoint interrupts */
|
||||
udev->regs.dr->DIEPINTEN = DIEPINTEN_TFEN;
|
||||
|
||||
/* reset device address */
|
||||
udev->regs.dr->DCFG &= ~DCFG_DAR;
|
||||
udev->dev.dev_addr = 0U;
|
||||
|
||||
/* 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
|
||||
};
|
||||
|
||||
usb_transc_active(udev, &udev->dev.transc_out[0]);
|
||||
|
||||
udev->dev.transc_in[0] = (usb_transc) {
|
||||
.ep_addr = {
|
||||
.dir = 1
|
||||
},
|
||||
|
||||
.ep_type = USB_EPTYPE_CTRL,
|
||||
.max_len = USB_FS_EP0_MAX_LEN
|
||||
};
|
||||
|
||||
usb_transc_active(udev, &udev->dev.transc_in[0]);
|
||||
|
||||
/* upon reset call usr call back */
|
||||
udev->dev.cur_status = USBD_DEFAULT;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*!
|
||||
\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) >> 1U);
|
||||
|
||||
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 (USB_SPEED[enum_speed] == USB_SPEED_HIGH) {
|
||||
udev->bp.core_speed = USB_SPEED_HIGH;
|
||||
|
||||
udev->regs.gr->GUSBCS |= 0x09 << 10;
|
||||
} else {
|
||||
udev->bp.core_speed = USB_SPEED_FULL;
|
||||
|
||||
udev->regs.gr->GUSBCS |= 0x05 << 10;
|
||||
}
|
||||
|
||||
/* clear interrupt */
|
||||
udev->regs.gr->GINTF = GINTF_ENUMFIF;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*!
|
||||
\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 = (udev->dev.cur_status == USBD_CONFIGURED) ? 1U : 0U;
|
||||
|
||||
udev->dev.backup_status = udev->dev.cur_status;
|
||||
udev->dev.cur_status = USBD_SUSPENDED;
|
||||
|
||||
if (low_power && suspend && is_configured) {
|
||||
/* switch-off the otg clocks */
|
||||
*udev->regs.PWRCLKCTL |= PWRCLKCTL_SUCLK | PWRCLKCTL_SHCLK;
|
||||
|
||||
/* enter DEEP_SLEEP mode with LDO in low power mode */
|
||||
pmu_to_deepsleepmode(PMU_LDO_LOWPOWER, 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)
|
||||
{
|
||||
usb_transc* transc = NULL;
|
||||
|
||||
uint32_t len = 0;
|
||||
uint32_t word_count = 0;
|
||||
|
||||
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 + 3) / 4;
|
||||
|
||||
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 + 3) / 4;
|
||||
|
||||
/* write the FIFO */
|
||||
usb_txfifo_write(&udev->regs, transc->xfer_buf, ep_num, 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 &= ~(0x01 << ep_num);
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
@@ -0,0 +1,536 @@
|
||||
/*!
|
||||
\file drv_usbh_int.c
|
||||
\brief USB host mode interrupt handler file
|
||||
|
||||
\version 2019-6-5, V1.0.0, firmware for GD32 USBFS&USBHS
|
||||
*/
|
||||
|
||||
/*
|
||||
Copyright (c) 2019, 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_host.h"
|
||||
#include "drv_usbh_int.h"
|
||||
|
||||
#if defined (__GNUC__) /*!< GNU compiler */
|
||||
#pragma GCC optimize ("O0")
|
||||
#endif /* __GNUC__ */
|
||||
|
||||
static uint32_t usbh_int_port(usb_core_driver* pudev);
|
||||
static uint32_t usbh_int_pipe(usb_core_driver* pudev);
|
||||
static uint32_t usbh_int_pipe_in(usb_core_driver* pudev, uint32_t pp_num);
|
||||
static uint32_t usbh_int_pipe_out(usb_core_driver* pudev, uint32_t pp_num);
|
||||
static uint32_t usbh_int_rxfifonoempty(usb_core_driver* pudev);
|
||||
static uint32_t usbh_int_txfifoempty(usb_core_driver* pudev, usb_pipe_mode pp_mode);
|
||||
|
||||
static inline void usb_pp_halt(usb_core_driver* pudev,
|
||||
uint8_t pp_num,
|
||||
uint32_t pp_int,
|
||||
usb_pipe_staus pp_status)
|
||||
{
|
||||
pudev->regs.pr[pp_num]->HCHINTEN |= HCHINTEN_CHIE;
|
||||
|
||||
usb_pipe_halt(pudev, pp_num);
|
||||
|
||||
pudev->regs.pr[pp_num]->HCHINTF = pp_int;
|
||||
|
||||
pudev->host.pipe[pp_num].pp_status = pp_status;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief handle global host interrupt
|
||||
\param[in] pudev: pointer to usb core instance
|
||||
\param[out] none
|
||||
\retval operation status
|
||||
*/
|
||||
uint32_t usbh_isr(usb_core_driver* pudev)
|
||||
{
|
||||
uint32_t Retval = 0U;
|
||||
|
||||
__IO uint32_t intr = 0U;
|
||||
|
||||
/* check if host mode */
|
||||
if (HOST_MODE == (pudev->regs.gr->GINTF & GINTF_COPM)) {
|
||||
intr = usb_coreintr_get(&pudev->regs);
|
||||
|
||||
if (!intr) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (intr & GINTF_SOF) {
|
||||
usbh_int_fop->SOF(pudev);
|
||||
|
||||
/* clear interrupt */
|
||||
pudev->regs.gr->GINTF = GINTF_SOF;
|
||||
}
|
||||
|
||||
if (intr & GINTF_RXFNEIF) {
|
||||
Retval |= usbh_int_rxfifonoempty(pudev);
|
||||
}
|
||||
|
||||
if (intr & GINTF_NPTXFEIF) {
|
||||
Retval |= usbh_int_txfifoempty(pudev, PIPE_NON_PERIOD);
|
||||
}
|
||||
|
||||
if (intr & GINTF_PTXFEIF) {
|
||||
Retval |= usbh_int_txfifoempty(pudev, PIPE_PERIOD);
|
||||
}
|
||||
|
||||
if (intr & GINTF_HCIF) {
|
||||
Retval |= usbh_int_pipe(pudev);
|
||||
}
|
||||
|
||||
if (intr & GINTF_HPIF) {
|
||||
Retval |= usbh_int_port(pudev);
|
||||
}
|
||||
|
||||
if (intr & GINTF_DISCIF) {
|
||||
pudev->host.connect_status = 0U;
|
||||
|
||||
/* clear interrupt */
|
||||
pudev->regs.gr->GINTF = GINTF_DISCIF;
|
||||
}
|
||||
|
||||
if (intr & GINTF_ISOONCIF) {
|
||||
pudev->regs.pr[0]->HCHCTL |= HCHCTL_CEN | HCHCTL_CDIS;
|
||||
|
||||
/* clear interrupt */
|
||||
pudev->regs.gr->GINTF = GINTF_ISOONCIF;
|
||||
}
|
||||
}
|
||||
|
||||
return Retval;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief handle all host channels interrupt
|
||||
\param[in] pudev: pointer to usb device instance
|
||||
\param[out] none
|
||||
\retval operation status
|
||||
*/
|
||||
static uint32_t usbh_int_pipe(usb_core_driver* pudev)
|
||||
{
|
||||
uint32_t pp_num = 0U;
|
||||
uint32_t retval = 0U;
|
||||
|
||||
for (pp_num = 0U; pp_num < pudev->bp.num_pipe; pp_num++) {
|
||||
if ((pudev->regs.hr->HACHINT & HACHINT_HACHINT) & (1U << pp_num)) {
|
||||
if (pudev->regs.pr[pp_num]->HCHCTL & HCHCTL_EPDIR) {
|
||||
retval |= usbh_int_pipe_in(pudev, pp_num);
|
||||
} else {
|
||||
retval |= usbh_int_pipe_out(pudev, pp_num);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief handle the TX FIFO empty interrupt
|
||||
\param[in] pudev: pointer to usb device instance
|
||||
\param[out] none
|
||||
\retval operation status
|
||||
*/
|
||||
static uint32_t usbh_int_txfifoempty(usb_core_driver* pudev, 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 = &pudev->regs.gr->HNPTFQSTAT;
|
||||
} else if (PIPE_PERIOD == pp_mode) {
|
||||
txfiforeg = &pudev->regs.hr->HPTFQSTAT;
|
||||
} else {
|
||||
return 0U;
|
||||
}
|
||||
|
||||
txfifostate = *txfiforeg;
|
||||
|
||||
pp_num = (txfifostate & TFQSTAT_CNUM) >> 27U;
|
||||
|
||||
word_count = (pudev->host.pipe[pp_num].xfer_len + 3U) / 4U;
|
||||
|
||||
while (((txfifostate & TFQSTAT_TXFS) > word_count) && (0U != pudev->host.pipe[pp_num].xfer_len)) {
|
||||
len = (txfifostate & TFQSTAT_TXFS) * 4U;
|
||||
|
||||
if (len > pudev->host.pipe[pp_num].xfer_len) {
|
||||
/* last packet */
|
||||
len = pudev->host.pipe[pp_num].xfer_len;
|
||||
|
||||
if (PIPE_NON_PERIOD == pp_mode) {
|
||||
pudev->regs.gr->GINTEN &= ~GINTEN_NPTXFEIE;
|
||||
} else {
|
||||
pudev->regs.gr->GINTEN &= ~GINTEN_PTXFEIE;
|
||||
}
|
||||
}
|
||||
|
||||
word_count = (pudev->host.pipe[pp_num].xfer_len + 3U) / 4U;
|
||||
usb_txfifo_write(&pudev->regs, pudev->host.pipe[pp_num].xfer_buf, pp_num, len);
|
||||
|
||||
pudev->host.pipe[pp_num].xfer_buf += len;
|
||||
pudev->host.pipe[pp_num].xfer_len -= len;
|
||||
pudev->host.pipe[pp_num].xfer_count += len;
|
||||
|
||||
txfifostate = *txfiforeg;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief handle the host port interrupt
|
||||
\param[in] pudev: pointer to usb device instance
|
||||
\param[out] none
|
||||
\retval operation status
|
||||
*/
|
||||
static uint32_t usbh_int_port(usb_core_driver* pudev)
|
||||
{
|
||||
uint32_t retval = 0U;
|
||||
|
||||
__IO uint32_t port_state = *pudev->regs.HPCS;
|
||||
|
||||
/* clear the interrupt bits in GINTSTS */
|
||||
port_state &= ~(HPCS_PE | HPCS_PCD | HPCS_PEDC);
|
||||
|
||||
/* port connect detected */
|
||||
if (*pudev->regs.HPCS & HPCS_PCD) {
|
||||
port_state |= HPCS_PCD;
|
||||
|
||||
pudev->host.connect_status = 1U;
|
||||
|
||||
retval |= 1U;
|
||||
}
|
||||
|
||||
/* port enable changed */
|
||||
if (*pudev->regs.HPCS & HPCS_PEDC) {
|
||||
port_state |= HPCS_PEDC;
|
||||
|
||||
if (*pudev->regs.HPCS & HPCS_PE) {
|
||||
uint32_t port_speed = usb_curspeed_get(pudev);
|
||||
uint32_t clock_type = pudev->regs.hr->HCTL & HCTL_CLKSEL;
|
||||
|
||||
pudev->host.connect_status = 1U;
|
||||
|
||||
if (PORT_SPEED_LOW == port_speed) {
|
||||
pudev->regs.hr->HFT = 6000U;
|
||||
|
||||
if (HCTL_6MHZ != clock_type) {
|
||||
if (USB_EMBEDDED_PHY == pudev->bp.phy_itf) {
|
||||
usb_phyclock_config(pudev, HCTL_6MHZ);
|
||||
}
|
||||
}
|
||||
} else if (PORT_SPEED_FULL == port_speed) {
|
||||
pudev->regs.hr->HFT = 48000U;
|
||||
|
||||
if (HCTL_48MHZ != clock_type) {
|
||||
usb_phyclock_config(pudev, HCTL_48MHZ);
|
||||
}
|
||||
} else {
|
||||
/* for high speed device and others */
|
||||
}
|
||||
|
||||
pudev->host.port_enabled = 1U;
|
||||
|
||||
pudev->regs.gr->GINTEN |= GINTEN_DISCIE;
|
||||
} else {
|
||||
pudev->host.port_enabled = 0U;
|
||||
}
|
||||
}
|
||||
|
||||
/* clear port interrupts */
|
||||
*pudev->regs.HPCS = port_state;
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief handle the OUT channel interrupt
|
||||
\param[in] pudev: pointer to usb device instance
|
||||
\param[in] pp_num: host channel number which is in (0..7)
|
||||
\param[out] none
|
||||
\retval operation status
|
||||
*/
|
||||
uint32_t usbh_int_pipe_out(usb_core_driver* pudev, uint32_t pp_num)
|
||||
{
|
||||
usb_pr* pp_reg = pudev->regs.pr[pp_num];
|
||||
|
||||
usb_pipe* pp = &pudev->host.pipe[pp_num];
|
||||
|
||||
uint32_t intr_pp = pp_reg->HCHINTF & pp_reg->HCHINTEN;
|
||||
|
||||
if (intr_pp & HCHINTF_ACK) {
|
||||
pp_reg->HCHINTF = HCHINTF_ACK;
|
||||
} else if (intr_pp & HCHINTF_STALL) {
|
||||
usb_pp_halt(pudev, pp_num, HCHINTF_STALL, PIPE_STALL);
|
||||
} else if (intr_pp & HCHINTF_DTER) {
|
||||
usb_pp_halt(pudev, pp_num, HCHINTF_DTER, PIPE_DTGERR);
|
||||
pp_reg->HCHINTF = HCHINTF_NAK;
|
||||
} else if (intr_pp & HCHINTF_REQOVR) {
|
||||
usb_pp_halt(pudev, pp_num, HCHINTF_REQOVR, PIPE_REQOVR);
|
||||
} else if (intr_pp & HCHINTF_TF) {
|
||||
pp->err_count = 0U;
|
||||
usb_pp_halt(pudev, pp_num, HCHINTF_TF, PIPE_XF);
|
||||
} else if (intr_pp & HCHINTF_NAK) {
|
||||
pp->err_count = 0U;
|
||||
usb_pp_halt(pudev, pp_num, HCHINTF_NAK, PIPE_NAK);
|
||||
} else if (intr_pp & HCHINTF_USBER) {
|
||||
pp->err_count++;
|
||||
usb_pp_halt(pudev, pp_num, HCHINTF_USBER, PIPE_TRACERR);
|
||||
} else if (intr_pp & HCHINTF_NYET) {
|
||||
pp->err_count = 0U;
|
||||
usb_pp_halt(pudev, pp_num, HCHINTF_NYET, PIPE_NYET);
|
||||
} else if (intr_pp & HCHINTF_CH) {
|
||||
pudev->regs.pr[pp_num]->HCHINTEN &= ~HCHINTEN_CHIE;
|
||||
|
||||
switch (pp->pp_status) {
|
||||
case PIPE_XF:
|
||||
pp->urb_state = URB_DONE;
|
||||
|
||||
if (USB_EPTYPE_BULK == ((pp_reg->HCHCTL & HCHCTL_EPTYPE) >> 18U)) {
|
||||
pp->data_toggle_out ^= 1U;
|
||||
}
|
||||
break;
|
||||
|
||||
case PIPE_NAK:
|
||||
pp->urb_state = URB_NOTREADY;
|
||||
break;
|
||||
|
||||
case PIPE_NYET:
|
||||
if (1U == pudev->host.pipe[pp_num].ping) {
|
||||
usb_pipe_ping(pudev, pp_num);
|
||||
}
|
||||
|
||||
pp->urb_state = URB_NOTREADY;
|
||||
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;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
pp_reg->HCHINTF = HCHINTF_CH;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief handle the IN channel interrupt
|
||||
\param[in] pudev: pointer to usb device instance
|
||||
\param[in] pp_num: host channel number which is in (0..7)
|
||||
\param[out] none
|
||||
\retval operation status
|
||||
*/
|
||||
uint32_t usbh_int_pipe_in(usb_core_driver* pudev, uint32_t pp_num)
|
||||
{
|
||||
usb_pr* pp_reg = pudev->regs.pr[pp_num];
|
||||
|
||||
usb_pipe* pp = &pudev->host.pipe[pp_num];
|
||||
|
||||
__IO uint32_t intr_pp = pp_reg->HCHINTF & pp_reg->HCHINTEN;
|
||||
|
||||
uint8_t ep_type = (pp_reg->HCHCTL & HCHCTL_EPTYPE) >> 18U;
|
||||
|
||||
if (intr_pp & HCHINTF_ACK) {
|
||||
pp_reg->HCHINTF = HCHINTF_ACK;
|
||||
} else if (intr_pp & HCHINTF_STALL) {
|
||||
usb_pp_halt(pudev, pp_num, HCHINTF_STALL, PIPE_STALL);
|
||||
pp_reg->HCHINTF = HCHINTF_NAK;
|
||||
|
||||
/* note: When there is a 'STALL', reset also nak,
|
||||
else, the pudev->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(pudev, pp_num, HCHINTF_DTER, PIPE_DTGERR);
|
||||
pp_reg->HCHINTF = HCHINTF_NAK;
|
||||
}
|
||||
|
||||
if (intr_pp & HCHINTF_REQOVR) {
|
||||
usb_pp_halt(pudev, pp_num, HCHINTF_REQOVR, PIPE_REQOVR);
|
||||
} else if (intr_pp & HCHINTF_TF) {
|
||||
if (USB_USE_DMA == pudev->bp.transfer_mode) {
|
||||
pudev->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:
|
||||
usb_pp_halt(pudev, pp_num, HCHINTF_NAK, PIPE_XF);
|
||||
|
||||
pp->data_toggle_in ^= 1U;
|
||||
break;
|
||||
|
||||
case USB_EPTYPE_INTR:
|
||||
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;
|
||||
break;
|
||||
|
||||
default:
|
||||
if (USB_EPTYPE_INTR == ep_type) {
|
||||
pp->data_toggle_in ^= 1U;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
pp_reg->HCHINTF = HCHINTF_CH;
|
||||
} else if (intr_pp & HCHINTF_BBER) {
|
||||
pp->err_count++;
|
||||
usb_pp_halt(pudev, pp_num, HCHINTF_BBER, 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;
|
||||
|
||||
usb_pipe_halt(pudev, pp_num);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
pp->pp_status = PIPE_NAK;
|
||||
|
||||
pp_reg->HCHINTF = HCHINTF_NAK;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief handle the rx fifo non-empty interrupt
|
||||
\param[in] pudev: pointer to usb device instance
|
||||
\param[out] none
|
||||
\retval operation status
|
||||
*/
|
||||
static uint32_t usbh_int_rxfifonoempty(usb_core_driver* pudev)
|
||||
{
|
||||
uint32_t count = 0U;
|
||||
|
||||
__IO uint8_t pp_num = 0U;
|
||||
__IO uint32_t rx_stat = 0U;
|
||||
|
||||
/* disable the rx status queue level interrupt */
|
||||
pudev->regs.gr->GINTEN &= ~GINTEN_RXFNEIE;
|
||||
|
||||
rx_stat = pudev->regs.gr->GRSTATP;
|
||||
pp_num = rx_stat & GRSTATRP_CNUM;
|
||||
|
||||
switch ((rx_stat & GRSTATRP_RPCKST) >> 17U) {
|
||||
case GRXSTS_PKTSTS_IN:
|
||||
count = (rx_stat & GRSTATRP_BCOUNT) >> 4U;
|
||||
|
||||
/* read the data into the host buffer. */
|
||||
if ((count > 0U) && (NULL != pudev->host.pipe[pp_num].xfer_buf)) {
|
||||
usb_rxfifo_read(&pudev->regs, pudev->host.pipe[pp_num].xfer_buf, count);
|
||||
|
||||
/* manage multiple transfer packet */
|
||||
pudev->host.pipe[pp_num].xfer_buf += count;
|
||||
pudev->host.pipe[pp_num].xfer_count += count;
|
||||
|
||||
pudev->host.backup_xfercount[pp_num] = pudev->host.pipe[pp_num].xfer_count;
|
||||
|
||||
if (pudev->regs.pr[pp_num]->HCHLEN & HCHLEN_PCNT) {
|
||||
/* re-activate the channel when more packets are expected */
|
||||
__IO uint32_t pp_ctl = pudev->regs.pr[pp_num]->HCHCTL;
|
||||
|
||||
pp_ctl |= HCHCTL_CEN;
|
||||
pp_ctl &= ~HCHCTL_CDIS;
|
||||
|
||||
pudev->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) >> 4U;
|
||||
|
||||
while (count > 0U) {
|
||||
rx_stat = pudev->regs.gr->GRSTATP;
|
||||
count--;
|
||||
}
|
||||
break;
|
||||
|
||||
case GRXSTS_PKTSTS_CH_HALTED:
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
/* enable the rx status queue level interrupt */
|
||||
pudev->regs.gr->GINTEN |= GINTEN_RXFNEIE;
|
||||
|
||||
return 1;
|
||||
}
|
||||
@@ -0,0 +1,329 @@
|
||||
/*!
|
||||
\file usbd_core.c
|
||||
\brief USB device mode core functions
|
||||
|
||||
\version 2019-6-5, V1.0.0, firmware for GD32 USBFS&USBHS
|
||||
*/
|
||||
|
||||
/*
|
||||
Copyright (c) 2019, 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 "usbd_core.h"
|
||||
#include "drv_usb_hw.h"
|
||||
|
||||
/* endpoint type */
|
||||
const uint32_t ep_type[] = {
|
||||
[USB_EP_ATTR_CTL] = USB_EPTYPE_CTRL,
|
||||
[USB_EP_ATTR_BULK] = USB_EPTYPE_BULK,
|
||||
[USB_EP_ATTR_INT] = USB_EPTYPE_INTR,
|
||||
[USB_EP_ATTR_ISO] = USB_EPTYPE_ISOC
|
||||
};
|
||||
|
||||
/*!
|
||||
\brief initailizes the USB device-mode stack and load the class driver
|
||||
\param[in] udev: pointer to USB core instance
|
||||
\param[in] core: usb core type
|
||||
\param[in] class_core: class driver
|
||||
\param[out] none
|
||||
\retval none
|
||||
*/
|
||||
void usbd_init(usb_core_driver* udev, usb_core_enum core, usb_class_core* class_core)
|
||||
{
|
||||
/* device descriptor, class and user callbacks */
|
||||
udev->dev.class_core = class_core;
|
||||
|
||||
/* configure USB capabilites */
|
||||
usb_basic_init(&udev->bp, &udev->regs, core);
|
||||
|
||||
/* initailizes the USB core*/
|
||||
usb_core_init(udev->bp, &udev->regs);
|
||||
|
||||
/* set device disconnect */
|
||||
usbd_disconnect(udev);
|
||||
|
||||
/* initailizes device mode */
|
||||
usb_devcore_init(udev);
|
||||
|
||||
/* set device connect */
|
||||
usbd_connect(udev);
|
||||
|
||||
udev->dev.cur_status = USBD_DEFAULT;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief endpoint initialization
|
||||
\param[in] udev: pointer to USB core instance
|
||||
\param[in] ep_desc: pointer to endpoint descriptor
|
||||
\param[out] none
|
||||
\retval none
|
||||
*/
|
||||
uint32_t usbd_ep_setup(usb_core_driver* udev, const usb_desc_ep* ep_desc)
|
||||
{
|
||||
usb_transc* transc;
|
||||
|
||||
uint8_t ep_addr = ep_desc->bEndpointAddress;
|
||||
uint8_t max_len = ep_desc->wMaxPacketSize;
|
||||
|
||||
/* set endpoint direction */
|
||||
if (EP_DIR(ep_addr)) {
|
||||
transc = &udev->dev.transc_in[EP_ID(ep_addr)];
|
||||
|
||||
transc->ep_addr.dir = 1U;
|
||||
} else {
|
||||
transc = &udev->dev.transc_out[ep_addr];
|
||||
|
||||
transc->ep_addr.dir = 0U;
|
||||
}
|
||||
|
||||
transc->ep_addr.num = EP_ID(ep_addr);
|
||||
transc->max_len = max_len;
|
||||
transc->ep_type = ep_type[ep_desc->bmAttributes & USB_EPTYPE_MASK];
|
||||
|
||||
/* active USB endpoint function */
|
||||
usb_transc_active(udev, transc);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief configure the endpoint when it is disabled
|
||||
\param[in] udev: pointer to USB core instance
|
||||
\param[in] ep_addr: endpoint address
|
||||
in this parameter:
|
||||
bit0..bit6: endpoint number (0..7)
|
||||
bit7: endpoint direction which can be IN(1) or OUT(0)
|
||||
\param[out] none
|
||||
\retval none
|
||||
*/
|
||||
uint32_t usbd_ep_clear(usb_core_driver* udev, uint8_t ep_addr)
|
||||
{
|
||||
usb_transc* transc;
|
||||
|
||||
if (EP_DIR(ep_addr)) {
|
||||
transc = &udev->dev.transc_in[EP_ID(ep_addr)];
|
||||
} else {
|
||||
transc = &udev->dev.transc_out[ep_addr];
|
||||
}
|
||||
|
||||
/* deactive USB endpoint function */
|
||||
usb_transc_deactivate(udev, transc);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
\brief endpoint prepare to receive data
|
||||
\param[in] udev: pointer to usb core instance
|
||||
\param[in] ep_addr: endpoint address
|
||||
in this parameter:
|
||||
bit0..bit6: endpoint number (0..7)
|
||||
bit7: endpoint direction which can be IN(1) or OUT(0)
|
||||
\param[in] pbuf: user buffer address pointer
|
||||
\param[in] len: buffer length
|
||||
\param[out] none
|
||||
\retval none
|
||||
*/
|
||||
uint32_t usbd_ep_recev(usb_core_driver* udev, uint8_t ep_addr, uint8_t* pbuf, uint16_t len)
|
||||
{
|
||||
usb_transc* transc = &udev->dev.transc_out[EP_ID(ep_addr)];
|
||||
|
||||
/* setup the transfer */
|
||||
transc->xfer_buf = pbuf;
|
||||
transc->xfer_len = len;
|
||||
transc->xfer_count = 0;
|
||||
|
||||
if (USB_USE_DMA == udev->bp.transfer_mode) {
|
||||
transc->dma_addr = (uint32_t)pbuf;
|
||||
}
|
||||
|
||||
/* start the transfer */
|
||||
usb_transc_outxfer(udev, transc);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief endpoint prepare to transmit data
|
||||
\param[in] udev: pointer to USB core instance
|
||||
\param[in] ep_addr: endpoint address
|
||||
in this parameter:
|
||||
bit0..bit6: endpoint number (0..7)
|
||||
bit7: endpoint direction which can be IN(1) or OUT(0)
|
||||
\param[in] pbuf: transmit buffer address pointer
|
||||
\param[in] len: buffer length
|
||||
\param[out] none
|
||||
\retval none
|
||||
*/
|
||||
uint32_t usbd_ep_send(usb_core_driver* udev, uint8_t ep_addr, uint8_t* pbuf, uint16_t len)
|
||||
{
|
||||
usb_transc* transc = &udev->dev.transc_in[EP_ID(ep_addr)];
|
||||
|
||||
/* setup the transfer */
|
||||
transc->xfer_buf = pbuf;
|
||||
transc->xfer_len = len;
|
||||
transc->xfer_count = 0;
|
||||
|
||||
if (USB_USE_DMA == udev->bp.transfer_mode) {
|
||||
transc->dma_addr = (uint32_t)pbuf;
|
||||
}
|
||||
|
||||
/* start the transfer */
|
||||
usb_transc_inxfer(udev, transc);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief set an endpoint to STALL status
|
||||
\param[in] udev: pointer to USB core instance
|
||||
\param[in] ep_addr: endpoint address
|
||||
in this parameter:
|
||||
bit0..bit6: endpoint number (0..7)
|
||||
bit7: endpoint direction which can be IN(1) or OUT(0)
|
||||
\param[out] none
|
||||
\retval none
|
||||
*/
|
||||
uint32_t usbd_ep_stall(usb_core_driver* udev, uint8_t ep_addr)
|
||||
{
|
||||
usb_transc* transc = NULL;
|
||||
|
||||
if (EP_DIR(ep_addr)) {
|
||||
transc = &udev->dev.transc_in[EP_ID(ep_addr)];
|
||||
} else {
|
||||
transc = &udev->dev.transc_out[ep_addr];
|
||||
}
|
||||
|
||||
transc->ep_stall = 1;
|
||||
|
||||
usb_transc_stall(udev, transc);
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief clear endpoint STALLed status
|
||||
\param[in] udev: pointer to usb core instance
|
||||
\param[in] ep_addr: endpoint address
|
||||
in this parameter:
|
||||
bit0..bit6: endpoint number (0..7)
|
||||
bit7: endpoint direction which can be IN(1) or OUT(0)
|
||||
\param[out] none
|
||||
\retval none
|
||||
*/
|
||||
uint32_t usbd_ep_stall_clear(usb_core_driver* udev, uint8_t ep_addr)
|
||||
{
|
||||
usb_transc* transc = NULL;
|
||||
|
||||
if (EP_DIR(ep_addr)) {
|
||||
transc = &udev->dev.transc_in[EP_ID(ep_addr)];
|
||||
} else {
|
||||
transc = &udev->dev.transc_out[ep_addr];
|
||||
}
|
||||
|
||||
transc->ep_stall = 0;
|
||||
|
||||
usb_transc_clrstall(udev, transc);
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief flush the endpoint FIFOs
|
||||
\param[in] udev: pointer to usb core instance
|
||||
\param[in] ep_addr: endpoint address
|
||||
in this parameter:
|
||||
bit0..bit6: endpoint number (0..7)
|
||||
bit7: endpoint direction which can be IN(1) or OUT(0)
|
||||
\param[out] none
|
||||
\retval none
|
||||
*/
|
||||
uint32_t usbd_fifo_flush(usb_core_driver* udev, uint8_t ep_addr)
|
||||
{
|
||||
if (EP_DIR(ep_addr)) {
|
||||
usb_txfifo_flush(&udev->regs, EP_ID(ep_addr));
|
||||
} else {
|
||||
usb_rxfifo_flush(&udev->regs);
|
||||
}
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief set USB device address
|
||||
\param[in] udev: pointer to USB core instance
|
||||
\param[in] addr: device address to set
|
||||
\param[out] none
|
||||
\retval none
|
||||
*/
|
||||
void usbd_addr_set(usb_core_driver* udev, uint8_t addr)
|
||||
{
|
||||
usb_devaddr_set(udev, addr);
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief get the received data length
|
||||
\param[in] udev: pointer to USB device instance
|
||||
\param[in] ep_num: endpoint number
|
||||
\param[out] none
|
||||
\retval USB device operation cur_status
|
||||
*/
|
||||
uint16_t usbd_rxcount_get(usb_core_driver* udev, uint8_t ep_num)
|
||||
{
|
||||
return udev->dev.transc_out[ep_num].xfer_count;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief device connect
|
||||
\param[in] udev: pointer to USB device instance
|
||||
\param[out] none
|
||||
\retval none
|
||||
*/
|
||||
void usbd_connect(usb_core_driver* udev)
|
||||
{
|
||||
#ifndef USE_OTG_MODE
|
||||
/* connect device */
|
||||
usb_dev_connect(udev);
|
||||
usb_mdelay(3);
|
||||
|
||||
#endif /* USE_OTG_MODE */
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief device disconnect
|
||||
\param[in] udev: pointer to USB device instance
|
||||
\param[out] none
|
||||
\retval none
|
||||
*/
|
||||
void usbd_disconnect(usb_core_driver* udev)
|
||||
{
|
||||
#ifndef USE_OTG_MODE
|
||||
/* disconnect device for 3ms */
|
||||
usb_dev_disconnect(udev);
|
||||
usb_mdelay(3);
|
||||
#endif /* USE_OTG_MODE */
|
||||
}
|
||||
@@ -0,0 +1,693 @@
|
||||
/*!
|
||||
\file usbd_enum.c
|
||||
\brief USB enumeration function
|
||||
|
||||
\version 2019-6-5, V1.0.0, firmware for GD32 USBFS&USBHS
|
||||
*/
|
||||
|
||||
/*
|
||||
Copyright (c) 2019, 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 "usbd_enum.h"
|
||||
#include "usb_ch9_std.h"
|
||||
|
||||
static usb_reqsta _usb_std_getstatus(usb_core_driver* udev, usb_req* req);
|
||||
static usb_reqsta _usb_std_setaddress(usb_core_driver* udev, usb_req* req);
|
||||
static usb_reqsta _usb_std_setconfiguration(usb_core_driver* udev, usb_req* req);
|
||||
static usb_reqsta _usb_std_getconfiguration(usb_core_driver* udev, usb_req* req);
|
||||
static usb_reqsta _usb_std_getdescriptor(usb_core_driver* udev, usb_req* req);
|
||||
static usb_reqsta _usb_std_setfeature(usb_core_driver* udev, usb_req* req);
|
||||
static usb_reqsta _usb_std_clearfeature(usb_core_driver* udev, usb_req* req);
|
||||
static usb_reqsta _usb_std_reserved(usb_core_driver* udev, usb_req* req);
|
||||
static usb_reqsta _usb_std_setdescriptor(usb_core_driver* udev, usb_req* req);
|
||||
static usb_reqsta _usb_std_getinterface(usb_core_driver* udev, usb_req* req);
|
||||
static usb_reqsta _usb_std_setinterface(usb_core_driver* udev, usb_req* req);
|
||||
static usb_reqsta _usb_std_synchframe(usb_core_driver* udev, usb_req* req);
|
||||
|
||||
static uint8_t* _usb_dev_desc_get(usb_core_driver* udev, uint8_t index, uint16_t* len);
|
||||
static uint8_t* _usb_config_desc_get(usb_core_driver* udev, uint8_t index, uint16_t* len);
|
||||
static uint8_t* _usb_str_desc_get(usb_core_driver* udev, uint8_t index, uint16_t* len);
|
||||
static uint8_t* _usb_bos_desc_get(usb_core_driver* udev, uint8_t index, uint16_t* len);
|
||||
|
||||
static usb_reqsta(*_std_dev_req[])(usb_core_driver* udev, usb_req* req) = {
|
||||
[USB_GET_STATUS] = _usb_std_getstatus,
|
||||
[USB_CLEAR_FEATURE] = _usb_std_clearfeature,
|
||||
[USB_RESERVED2] = _usb_std_reserved,
|
||||
[USB_SET_FEATURE] = _usb_std_setfeature,
|
||||
[USB_RESERVED4] = _usb_std_reserved,
|
||||
[USB_SET_ADDRESS] = _usb_std_setaddress,
|
||||
[USB_GET_DESCRIPTOR] = _usb_std_getdescriptor,
|
||||
[USB_SET_DESCRIPTOR] = _usb_std_setdescriptor,
|
||||
[USB_GET_CONFIGURATION] = _usb_std_getconfiguration,
|
||||
[USB_SET_CONFIGURATION] = _usb_std_setconfiguration,
|
||||
[USB_GET_INTERFACE] = _usb_std_getinterface,
|
||||
[USB_SET_INTERFACE] = _usb_std_setinterface,
|
||||
[USB_SYNCH_FRAME] = _usb_std_synchframe,
|
||||
};
|
||||
|
||||
/* get standard descriptor handler */
|
||||
static uint8_t* (*std_desc_get[])(usb_core_driver* udev, uint8_t index, uint16_t* len) = {
|
||||
[USB_DESCTYPE_DEV - 1] = _usb_dev_desc_get,
|
||||
[USB_DESCTYPE_CONFIG - 1] = _usb_config_desc_get,
|
||||
[USB_DESCTYPE_STR - 1] = _usb_str_desc_get
|
||||
};
|
||||
|
||||
/*!
|
||||
\brief handle USB standard device request
|
||||
\param[in] udev: pointer to USB device instance
|
||||
\param[in] req: pointer to USB device request
|
||||
\param[out] none
|
||||
\retval USB device request status
|
||||
*/
|
||||
usb_reqsta usbd_standard_request(usb_core_driver* udev, usb_req* req)
|
||||
{
|
||||
return (*_std_dev_req[req->bRequest])(udev, req);
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief handle USB device class request
|
||||
\param[in] udev: pointer to USB device instance
|
||||
\param[in] req: pointer to USB device class request
|
||||
\param[out] none
|
||||
\retval USB device request status
|
||||
*/
|
||||
usb_reqsta usbd_class_request(usb_core_driver* udev, usb_req* req)
|
||||
{
|
||||
if (USBD_CONFIGURED == udev->dev.cur_status) {
|
||||
if (BYTE_LOW(req->wIndex) <= USBD_ITF_MAX_NUM) {
|
||||
/* call device class handle function */
|
||||
return (usb_reqsta)udev->dev.class_core->req_proc(udev, req);
|
||||
}
|
||||
}
|
||||
|
||||
return REQ_NOTSUPP;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief handle USB vendor request
|
||||
\param[in] udev: pointer to USB device instance
|
||||
\param[in] req: pointer to USB vendor request
|
||||
\param[out] none
|
||||
\retval USB device request status
|
||||
*/
|
||||
usb_reqsta usbd_vendor_request(usb_core_driver* udev, usb_req* req)
|
||||
{
|
||||
/* added by user... */
|
||||
|
||||
return REQ_SUPP;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief no operation, just for reserved
|
||||
\param[in] udev: pointer to USB device instance
|
||||
\param[in] req: pointer to USB vendor request
|
||||
\param[out] none
|
||||
\retval USB device request status
|
||||
*/
|
||||
static usb_reqsta _usb_std_reserved(usb_core_driver* udev, usb_req* req)
|
||||
{
|
||||
/* no operation... */
|
||||
|
||||
return REQ_NOTSUPP;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief get the device descriptor
|
||||
\param[in] udev: pointer to USB device instance
|
||||
\param[in] index: no use
|
||||
\param[out] len: data length pointer
|
||||
\retval descriptor buffer pointer
|
||||
*/
|
||||
static uint8_t* _usb_dev_desc_get(usb_core_driver* udev, uint8_t index, uint16_t* len)
|
||||
{
|
||||
*len = udev->dev.desc.dev_desc[0];
|
||||
|
||||
return udev->dev.desc.dev_desc;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief get the configuration descriptor
|
||||
\brief[in] udev: pointer to USB device instance
|
||||
\brief[in] index: no use
|
||||
\param[out] len: data length pointer
|
||||
\retval descriptor buffer pointer
|
||||
*/
|
||||
static uint8_t* _usb_config_desc_get(usb_core_driver* udev, uint8_t index, uint16_t* len)
|
||||
{
|
||||
*len = udev->dev.desc.config_desc[2];
|
||||
|
||||
return udev->dev.desc.config_desc;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief get the BOS descriptor
|
||||
\brief[in] udev: pointer to USB device instance
|
||||
\brief[in] index: no use
|
||||
\param[out] len: data length pointer
|
||||
\retval descriptor buffer pointer
|
||||
*/
|
||||
static uint8_t* _usb_bos_desc_get(usb_core_driver* udev, uint8_t index, uint16_t* len)
|
||||
{
|
||||
*len = udev->dev.desc.bos_desc[2];
|
||||
|
||||
return udev->dev.desc.bos_desc;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief get string descriptor
|
||||
\param[in] udev: pointer to USB device instance
|
||||
\param[in] index: string descriptor index
|
||||
\param[out] len: pointer to string length
|
||||
\retval descriptor buffer pointer
|
||||
*/
|
||||
static uint8_t* _usb_str_desc_get(usb_core_driver* udev, uint8_t index, uint16_t* len)
|
||||
{
|
||||
uint8_t* desc = udev->dev.desc.strings[index];
|
||||
|
||||
*len = desc[0];
|
||||
|
||||
return desc;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief handle Get_Status request
|
||||
\param[in] udev: pointer to USB device instance
|
||||
\param[in] req: pointer to USB device request
|
||||
\param[out] none
|
||||
\retval USB device request status
|
||||
*/
|
||||
static usb_reqsta _usb_std_getstatus(usb_core_driver* udev, usb_req* req)
|
||||
{
|
||||
uint8_t recp = BYTE_LOW(req->wIndex);
|
||||
|
||||
usb_transc* transc = &udev->dev.transc_in[0];
|
||||
|
||||
static uint8_t status[2] = {0};
|
||||
|
||||
switch (req->bmRequestType & USB_RECPTYPE_MASK) {
|
||||
case USB_RECPTYPE_DEV:
|
||||
if ((USBD_ADDRESSED == udev->dev.cur_status) || \
|
||||
(USBD_CONFIGURED == udev->dev.cur_status)) {
|
||||
|
||||
if (udev->dev.pm.power_mode) {
|
||||
status[0] = USB_STATUS_SELF_POWERED;
|
||||
} else {
|
||||
status[0] = 0U;
|
||||
}
|
||||
|
||||
if (udev->dev.pm.dev_remote_wakeup) {
|
||||
status[0] |= USB_STATUS_REMOTE_WAKEUP;
|
||||
} else {
|
||||
status[0] = 0U;
|
||||
}
|
||||
|
||||
transc->xfer_buf = status;
|
||||
transc->remain_len = 2U;
|
||||
|
||||
return REQ_SUPP;
|
||||
}
|
||||
break;
|
||||
|
||||
case USB_RECPTYPE_ITF:
|
||||
if ((USBD_CONFIGURED == udev->dev.cur_status) && (recp <= USBD_ITF_MAX_NUM)) {
|
||||
transc->xfer_buf = status;
|
||||
transc->remain_len = 2U;
|
||||
|
||||
return REQ_SUPP;
|
||||
}
|
||||
break;
|
||||
|
||||
case USB_RECPTYPE_EP:
|
||||
if (USBD_CONFIGURED == udev->dev.cur_status) {
|
||||
if (0x80U == (recp & 0x80U)) {
|
||||
status[0] = udev->dev.transc_in[EP_ID(recp)].ep_stall;
|
||||
} else {
|
||||
status[0] = udev->dev.transc_out[recp].ep_stall;
|
||||
}
|
||||
|
||||
transc->xfer_buf = status;
|
||||
transc->remain_len = 2U;
|
||||
|
||||
return REQ_SUPP;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return REQ_NOTSUPP;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief handle USB Clear_Feature request
|
||||
\param[in] udev: pointer to USB device instance
|
||||
\param[in] req: USB device request
|
||||
\param[out] none
|
||||
\retval USB device request status
|
||||
*/
|
||||
static usb_reqsta _usb_std_clearfeature(usb_core_driver* udev, usb_req* req)
|
||||
{
|
||||
uint8_t ep = 0;
|
||||
|
||||
switch (req->bmRequestType & USB_RECPTYPE_MASK) {
|
||||
case USB_RECPTYPE_DEV:
|
||||
if ((USBD_ADDRESSED == udev->dev.cur_status) || \
|
||||
(USBD_CONFIGURED == udev->dev.cur_status)) {
|
||||
|
||||
/* clear device remote wakeup feature */
|
||||
if (USB_FEATURE_REMOTE_WAKEUP == req->wValue) {
|
||||
udev->dev.pm.dev_remote_wakeup = 0U;
|
||||
|
||||
return REQ_SUPP;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case USB_RECPTYPE_ITF:
|
||||
break;
|
||||
|
||||
case USB_RECPTYPE_EP:
|
||||
/* get endpoint address */
|
||||
ep = BYTE_LOW(req->wIndex);
|
||||
|
||||
if (USBD_CONFIGURED == udev->dev.cur_status) {
|
||||
/* clear endpoint halt feature */
|
||||
if ((USB_FEATURE_EP_HALT == req->wValue) && (!CTL_EP(ep))) {
|
||||
usbd_ep_stall_clear(udev, ep);
|
||||
|
||||
udev->dev.class_core->req_proc(udev, req);
|
||||
}
|
||||
|
||||
return REQ_SUPP;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return REQ_NOTSUPP;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief handle USB Set_Feature request
|
||||
\param[in] udev: pointer to USB device instance
|
||||
\param[in] req: pointer to USB device request
|
||||
\param[out] none
|
||||
\retval USB device request status
|
||||
*/
|
||||
static usb_reqsta _usb_std_setfeature(usb_core_driver* udev, usb_req* req)
|
||||
{
|
||||
uint8_t ep = 0;
|
||||
|
||||
switch (req->bmRequestType & USB_RECPTYPE_MASK) {
|
||||
case USB_RECPTYPE_DEV:
|
||||
if ((USBD_ADDRESSED == udev->dev.cur_status) || \
|
||||
(USBD_CONFIGURED == udev->dev.cur_status)) {
|
||||
/* set device remote wakeup feature */
|
||||
if (USB_FEATURE_REMOTE_WAKEUP == req->wValue) {
|
||||
udev->dev.pm.dev_remote_wakeup = 1U;
|
||||
}
|
||||
|
||||
return REQ_SUPP;
|
||||
}
|
||||
break;
|
||||
|
||||
case USB_RECPTYPE_ITF:
|
||||
break;
|
||||
|
||||
case USB_RECPTYPE_EP:
|
||||
/* get endpoint address */
|
||||
ep = BYTE_LOW(req->wIndex);
|
||||
|
||||
if (USBD_CONFIGURED == udev->dev.cur_status) {
|
||||
/* set endpoint halt feature */
|
||||
if ((USB_FEATURE_EP_HALT == req->wValue) && (!CTL_EP(ep))) {
|
||||
usbd_ep_stall(udev, ep);
|
||||
}
|
||||
|
||||
return REQ_SUPP;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return REQ_NOTSUPP;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief handle USB Set_Address request
|
||||
\param[in] udev: pointer to USB device instance
|
||||
\param[in] req: pointer to USB device request
|
||||
\param[out] none
|
||||
\retval USB device request status
|
||||
*/
|
||||
static usb_reqsta _usb_std_setaddress(usb_core_driver* udev, usb_req* req)
|
||||
{
|
||||
if ((0U == req->wIndex) && (0U == req->wLength)) {
|
||||
udev->dev.dev_addr = (uint8_t)(req->wValue) & 0x7FU;
|
||||
|
||||
if (udev->dev.cur_status != USBD_CONFIGURED) {
|
||||
usbd_addr_set(udev, udev->dev.dev_addr);
|
||||
|
||||
if (udev->dev.dev_addr) {
|
||||
udev->dev.cur_status = USBD_ADDRESSED;
|
||||
} else {
|
||||
udev->dev.cur_status = USBD_DEFAULT;
|
||||
}
|
||||
|
||||
return REQ_SUPP;
|
||||
}
|
||||
}
|
||||
|
||||
return REQ_NOTSUPP;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief handle USB Get_Descriptor request
|
||||
\param[in] udev: pointer to USB device instance
|
||||
\param[in] req: pointer to USB device request
|
||||
\param[out] none
|
||||
\retval USB device request status
|
||||
*/
|
||||
static usb_reqsta _usb_std_getdescriptor(usb_core_driver* udev, usb_req* req)
|
||||
{
|
||||
uint8_t desc_type = 0;
|
||||
uint8_t desc_index = 0;
|
||||
|
||||
usb_transc* transc = &udev->dev.transc_in[0];
|
||||
|
||||
/* get device standard descriptor */
|
||||
switch (req->bmRequestType & USB_RECPTYPE_MASK) {
|
||||
case USB_RECPTYPE_DEV:
|
||||
desc_type = BYTE_HIGH(req->wValue);
|
||||
desc_index = BYTE_LOW(req->wValue);
|
||||
|
||||
switch (desc_type) {
|
||||
case USB_DESCTYPE_DEV:
|
||||
transc->xfer_buf = std_desc_get[desc_type - 1U](udev, desc_index, (uint16_t*) & (transc->remain_len));
|
||||
|
||||
if (64U == req->wLength) {
|
||||
transc->remain_len = 8U;
|
||||
}
|
||||
break;
|
||||
|
||||
case USB_DESCTYPE_CONFIG:
|
||||
transc->xfer_buf = std_desc_get[desc_type - 1U](udev, desc_index, (uint16_t*) & (transc->remain_len));
|
||||
break;
|
||||
|
||||
case USB_DESCTYPE_STR:
|
||||
if (desc_index < STR_IDX_MAX) {
|
||||
transc->xfer_buf = std_desc_get[desc_type - 1U](udev, desc_index, (uint16_t*) & (transc->remain_len));
|
||||
}
|
||||
break;
|
||||
|
||||
case USB_DESCTYPE_ITF:
|
||||
case USB_DESCTYPE_EP:
|
||||
case USB_DESCTYPE_DEV_QUALIFIER:
|
||||
case USB_DESCTYPE_OTHER_SPD_CONFIG:
|
||||
case USB_DESCTYPE_ITF_POWER:
|
||||
break;
|
||||
|
||||
case USB_DESCTYPE_BOS:
|
||||
transc->xfer_buf = _usb_bos_desc_get(udev, desc_index, (uint16_t*) & (transc->remain_len));
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case USB_RECPTYPE_ITF:
|
||||
/* get device class special descriptor */
|
||||
return (usb_reqsta)(udev->dev.class_core->req_proc(udev, req));
|
||||
|
||||
case USB_RECPTYPE_EP:
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if ((0U != transc->remain_len) && (0U != req->wLength)) {
|
||||
if (transc->remain_len < req->wLength) {
|
||||
if ((transc->remain_len >= transc->max_len) && (0U == (transc->remain_len % transc->max_len))) {
|
||||
udev->dev.control.ctl_zlp = 1;
|
||||
}
|
||||
} else {
|
||||
transc->remain_len = req->wLength;
|
||||
}
|
||||
|
||||
return REQ_SUPP;
|
||||
}
|
||||
|
||||
return REQ_NOTSUPP;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief handle USB Set_Descriptor request
|
||||
\param[in] udev: pointer to USB device instance
|
||||
\param[in] req: pointer to USB device request
|
||||
\param[out] none
|
||||
\retval USB device request status
|
||||
*/
|
||||
static usb_reqsta _usb_std_setdescriptor(usb_core_driver* udev, usb_req* req)
|
||||
{
|
||||
/* no handle... */
|
||||
return REQ_SUPP;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief handle USB Get_Configuration request
|
||||
\param[in] udev: pointer to USB device instance
|
||||
\param[in] req: pointer to USB device request
|
||||
\param[out] none
|
||||
\retval USB device request status
|
||||
*/
|
||||
static usb_reqsta _usb_std_getconfiguration(usb_core_driver* udev, usb_req* req)
|
||||
{
|
||||
usb_transc* transc = &udev->dev.transc_in[0];
|
||||
|
||||
switch (udev->dev.cur_status) {
|
||||
case USBD_ADDRESSED:
|
||||
if (USB_DEFAULT_CONFIG == udev->dev.config) {
|
||||
transc->xfer_buf = &(udev->dev.config);
|
||||
transc->remain_len = 1U;
|
||||
|
||||
return REQ_SUPP;
|
||||
}
|
||||
break;
|
||||
|
||||
case USBD_CONFIGURED:
|
||||
if (udev->dev.config != USB_DEFAULT_CONFIG) {
|
||||
transc->xfer_buf = &(udev->dev.config);
|
||||
transc->remain_len = 1U;
|
||||
|
||||
return REQ_SUPP;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return REQ_NOTSUPP;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief handle USB Set_Configuration request
|
||||
\param[in] udev: pointer to USB device instance
|
||||
\param[in] req: pointer to USB device request
|
||||
\param[out] none
|
||||
\retval USB device request status
|
||||
*/
|
||||
static usb_reqsta _usb_std_setconfiguration(usb_core_driver* udev, usb_req* req)
|
||||
{
|
||||
static uint8_t config;
|
||||
|
||||
config = (uint8_t)(req->wValue);
|
||||
|
||||
if (config <= USBD_CFG_MAX_NUM) {
|
||||
switch (udev->dev.cur_status) {
|
||||
case USBD_ADDRESSED:
|
||||
if (config) {
|
||||
udev->dev.class_core->init(udev, config);
|
||||
|
||||
udev->dev.config = config;
|
||||
udev->dev.cur_status = USBD_CONFIGURED;
|
||||
}
|
||||
|
||||
return REQ_SUPP;
|
||||
|
||||
case USBD_CONFIGURED:
|
||||
if (USB_DEFAULT_CONFIG == config) {
|
||||
udev->dev.class_core->deinit(udev, config);
|
||||
|
||||
udev->dev.config = config;
|
||||
udev->dev.cur_status = USBD_ADDRESSED;
|
||||
} else if (config != udev->dev.config) {
|
||||
/* clear old configuration */
|
||||
udev->dev.class_core->deinit(udev, config);
|
||||
|
||||
/* set new configuration */
|
||||
udev->dev.config = config;
|
||||
udev->dev.class_core->init(udev, config);
|
||||
}
|
||||
|
||||
return REQ_SUPP;
|
||||
|
||||
case USBD_DEFAULT:
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return REQ_NOTSUPP;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief handle USB Get_Interface request
|
||||
\param[in] udev: pointer to USB device instance
|
||||
\param[in] req: pointer to USB device request
|
||||
\param[out] none
|
||||
\retval USB device request status
|
||||
*/
|
||||
static usb_reqsta _usb_std_getinterface(usb_core_driver* udev, usb_req* req)
|
||||
{
|
||||
switch (udev->dev.cur_status) {
|
||||
case USBD_DEFAULT:
|
||||
break;
|
||||
|
||||
case USBD_ADDRESSED:
|
||||
break;
|
||||
|
||||
case USBD_CONFIGURED:
|
||||
if (BYTE_LOW(req->wIndex) <= USBD_ITF_MAX_NUM) {
|
||||
usb_transc* transc = &udev->dev.transc_in[0];
|
||||
|
||||
transc->xfer_buf = &(udev->dev.class_core->alter_set);
|
||||
transc->remain_len = 1U;
|
||||
|
||||
return REQ_SUPP;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return REQ_NOTSUPP;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief handle USB Set_Interface request
|
||||
\param[in] udev: pointer to USB device instance
|
||||
\param[in] req: pointer to USB device request
|
||||
\param[out] none
|
||||
\retval USB device request status
|
||||
*/
|
||||
static usb_reqsta _usb_std_setinterface(usb_core_driver* udev, usb_req* req)
|
||||
{
|
||||
switch (udev->dev.cur_status) {
|
||||
case USBD_DEFAULT:
|
||||
break;
|
||||
|
||||
case USBD_ADDRESSED:
|
||||
break;
|
||||
|
||||
case USBD_CONFIGURED:
|
||||
if (BYTE_LOW(req->wIndex) <= USBD_ITF_MAX_NUM) {
|
||||
udev->dev.class_core->alter_set = req->wValue;
|
||||
|
||||
return REQ_SUPP;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return REQ_NOTSUPP;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief handle USB SynchFrame request
|
||||
\param[in] udev: pointer to USB device instance
|
||||
\param[in] req: pointer to USB device request
|
||||
\param[out] none
|
||||
\retval USB device request status
|
||||
*/
|
||||
static usb_reqsta _usb_std_synchframe(usb_core_driver* udev, usb_req* req)
|
||||
{
|
||||
/* no handle... */
|
||||
return REQ_SUPP;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief handle USB enumeration error
|
||||
\param[in] udev: pointer to USB device instance
|
||||
\param[in] req: pointer to USB device request
|
||||
\param[out] none
|
||||
\retval none
|
||||
*/
|
||||
void usbd_enum_error(usb_core_driver* udev, usb_req* req)
|
||||
{
|
||||
usbd_ep_stall(udev, 0x80);
|
||||
usbd_ep_stall(udev, 0x00);
|
||||
|
||||
usb_ctlep_startout(udev);
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief convert hex 32bits value into unicode char
|
||||
\param[in] value: hex 32bits value
|
||||
\param[in] pbuf: buffer pointer to store unicode char
|
||||
\param[in] len: value length
|
||||
\param[out] none
|
||||
\retval none
|
||||
*/
|
||||
void int_to_unicode(uint32_t value, uint8_t* pbuf, uint8_t len)
|
||||
{
|
||||
uint8_t index = 0;
|
||||
|
||||
for (index = 0; index < len; index++) {
|
||||
if ((value >> 28) < 0x0A) {
|
||||
pbuf[2 * index] = (value >> 28) + '0';
|
||||
} else {
|
||||
pbuf[2 * index] = (value >> 28) + 'A' - 10;
|
||||
}
|
||||
|
||||
value = value << 4;
|
||||
|
||||
pbuf[2 * index + 1] = 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,254 @@
|
||||
/*!
|
||||
\file usbd_transc.c
|
||||
\brief USB transaction core functions
|
||||
|
||||
\version 2019-6-5, V1.0.0, firmware for GD32 USBFS&USBHS
|
||||
*/
|
||||
|
||||
/*
|
||||
Copyright (c) 2019, 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 "usbd_enum.h"
|
||||
#include "usbd_transc.h"
|
||||
|
||||
/*!
|
||||
\brief USB send data in the control transaction
|
||||
\param[in] udev: pointer to USB device instance
|
||||
\param[out] none
|
||||
\retval USB device operation cur_status
|
||||
*/
|
||||
usbd_status usbd_ctl_send(usb_core_driver* udev)
|
||||
{
|
||||
usb_transc* transc = &udev->dev.transc_in[0];
|
||||
|
||||
usbd_ep_send(udev, 0U, transc->xfer_buf, transc->remain_len);
|
||||
|
||||
if (transc->remain_len > transc->max_len) {
|
||||
udev->dev.control.ctl_state = USB_CTL_DATA_IN;
|
||||
} else {
|
||||
udev->dev.control.ctl_state = USB_CTL_LAST_DATA_IN;
|
||||
}
|
||||
|
||||
return USBD_OK;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief USB receive data in control transaction
|
||||
\param[in] udev: pointer to USB device instance
|
||||
\param[out] none
|
||||
\retval USB device operation cur_status
|
||||
*/
|
||||
usbd_status usbd_ctl_recev(usb_core_driver* udev)
|
||||
{
|
||||
usb_transc* transc = &udev->dev.transc_out[0];
|
||||
|
||||
usbd_ep_recev(udev, 0U, transc->xfer_buf, transc->remain_len);
|
||||
|
||||
if (transc->remain_len > transc->max_len) {
|
||||
udev->dev.control.ctl_state = USB_CTL_DATA_OUT;
|
||||
} else {
|
||||
udev->dev.control.ctl_state = USB_CTL_LAST_DATA_OUT;
|
||||
}
|
||||
|
||||
return USBD_OK;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief USB send control transaction status
|
||||
\param[in] udev: pointer to USB device instance
|
||||
\param[out] none
|
||||
\retval USB device operation cur_status
|
||||
*/
|
||||
usbd_status usbd_ctl_status_send(usb_core_driver* udev)
|
||||
{
|
||||
udev->dev.control.ctl_state = USB_CTL_STATUS_IN;
|
||||
|
||||
usbd_ep_send(udev, 0U, NULL, 0U);
|
||||
|
||||
usb_ctlep_startout(udev);
|
||||
|
||||
return USBD_OK;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief USB control receive status
|
||||
\param[in] udev: pointer to USB device instance
|
||||
\param[out] none
|
||||
\retval USB device operation cur_status
|
||||
*/
|
||||
usbd_status usbd_ctl_status_recev(usb_core_driver* udev)
|
||||
{
|
||||
udev->dev.control.ctl_state = USB_CTL_STATUS_OUT;
|
||||
|
||||
usbd_ep_recev(udev, 0, NULL, 0);
|
||||
|
||||
usb_ctlep_startout(udev);
|
||||
|
||||
return USBD_OK;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief USB setup stage processing
|
||||
\param[in] udev: pointer to USB device instance
|
||||
\param[out] none
|
||||
\retval USB device operation cur_status
|
||||
*/
|
||||
uint8_t usbd_setup_transc(usb_core_driver* udev)
|
||||
{
|
||||
usb_reqsta reqstat = REQ_NOTSUPP;
|
||||
|
||||
usb_req req = udev->dev.control.req;
|
||||
|
||||
switch (req.bmRequestType & USB_REQTYPE_MASK) {
|
||||
/* standard device request */
|
||||
case USB_REQTYPE_STRD:
|
||||
reqstat = usbd_standard_request(udev, &req);
|
||||
break;
|
||||
|
||||
/* device class request */
|
||||
case USB_REQTYPE_CLASS:
|
||||
reqstat = usbd_class_request(udev, &req);
|
||||
break;
|
||||
|
||||
/* vendor defined request */
|
||||
case USB_REQTYPE_VENDOR:
|
||||
reqstat = usbd_vendor_request(udev, &req);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (REQ_SUPP == reqstat) {
|
||||
if (req.wLength == 0) {
|
||||
usbd_ctl_status_send(udev);
|
||||
} else {
|
||||
if (req.bmRequestType & 0x80) {
|
||||
usbd_ctl_send(udev);
|
||||
} else {
|
||||
usbd_ctl_recev(udev);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
usbd_enum_error(udev, &req);
|
||||
}
|
||||
|
||||
return USBD_OK;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief data out stage processing
|
||||
\param[in] udev: pointer to USB device instance
|
||||
\param[in] ep_num: endpoint identifier(0..7)
|
||||
\param[out] none
|
||||
\retval USB device operation cur_status
|
||||
*/
|
||||
uint8_t usbd_out_transc(usb_core_driver* udev, uint8_t ep_num)
|
||||
{
|
||||
if (ep_num == 0) {
|
||||
usb_transc* transc = &udev->dev.transc_out[0];
|
||||
|
||||
switch (udev->dev.control.ctl_state) {
|
||||
case USB_CTL_DATA_OUT:
|
||||
/* update transfer length */
|
||||
transc->remain_len -= transc->max_len;
|
||||
|
||||
usbd_ctl_recev(udev);
|
||||
break;
|
||||
|
||||
case USB_CTL_LAST_DATA_OUT:
|
||||
if (udev->dev.cur_status == USBD_CONFIGURED) {
|
||||
if (udev->dev.class_core->data_out != NULL) {
|
||||
udev->dev.class_core->data_out(udev, 0U);
|
||||
}
|
||||
}
|
||||
|
||||
transc->remain_len = 0U;
|
||||
|
||||
usbd_ctl_status_send(udev);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else if ((udev->dev.class_core->data_out != NULL) && (udev->dev.cur_status == USBD_CONFIGURED)) {
|
||||
udev->dev.class_core->data_out(udev, ep_num);
|
||||
}
|
||||
|
||||
return USBD_OK;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief data in stage processing
|
||||
\param[in] udev: pointer to USB device instance
|
||||
\param[in] ep_num: endpoint identifier(0..7)
|
||||
\param[out] none
|
||||
\retval USB device operation cur_status
|
||||
*/
|
||||
uint8_t usbd_in_transc(usb_core_driver* udev, uint8_t ep_num)
|
||||
{
|
||||
if (0U == ep_num) {
|
||||
usb_transc* transc = &udev->dev.transc_in[0];
|
||||
|
||||
switch (udev->dev.control.ctl_state) {
|
||||
case USB_CTL_DATA_IN:
|
||||
/* update transfer length */
|
||||
transc->remain_len -= transc->max_len;
|
||||
|
||||
usbd_ctl_send(udev);
|
||||
break;
|
||||
|
||||
case USB_CTL_LAST_DATA_IN:
|
||||
/* last packet is MPS multiple, so send ZLP packet */
|
||||
if (udev->dev.control.ctl_zlp) {
|
||||
usbd_ep_send(udev, 0U, NULL, 0U);
|
||||
|
||||
udev->dev.control.ctl_zlp = 0U;
|
||||
} else {
|
||||
if (udev->dev.cur_status == USBD_CONFIGURED) {
|
||||
if (udev->dev.class_core->data_in != NULL) {
|
||||
udev->dev.class_core->data_in(udev, 0U);
|
||||
}
|
||||
}
|
||||
|
||||
transc->remain_len = 0U;
|
||||
|
||||
usbd_ctl_status_recev(udev);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if ((udev->dev.cur_status == USBD_CONFIGURED) && (udev->dev.class_core->data_in != NULL)) {
|
||||
udev->dev.class_core->data_in(udev, ep_num);
|
||||
}
|
||||
}
|
||||
|
||||
return USBD_OK;
|
||||
}
|
||||
@@ -0,0 +1,440 @@
|
||||
/*!
|
||||
\file usbh_core.c
|
||||
\brief USB host core state machine driver
|
||||
|
||||
\version 2019-6-5, V1.0.0, firmware for GD32 USBFS&USBHS
|
||||
*/
|
||||
|
||||
/*
|
||||
Copyright (c) 2019, 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 "usbh_pipe.h"
|
||||
#include "usbh_enum.h"
|
||||
#include "usbh_core.h"
|
||||
#include "drv_usbh_int.h"
|
||||
|
||||
uint8_t usbh_sof(usb_core_driver* pudev);
|
||||
|
||||
usbh_int_cb usbh_int_op = {
|
||||
usbh_sof
|
||||
};
|
||||
|
||||
usbh_int_cb* usbh_int_fop = &usbh_int_op;
|
||||
|
||||
static usbh_status usbh_enum_task(usb_core_driver* pudev, usbh_host* puhost);
|
||||
|
||||
/*!
|
||||
\brief USB SOF callback function from the interrupt
|
||||
\param[in] pudev: pointer to usb core instance
|
||||
\param[out] none
|
||||
\retval operation status
|
||||
*/
|
||||
uint8_t usbh_sof(usb_core_driver* pudev)
|
||||
{
|
||||
/* this callback could be used to implement a scheduler process */
|
||||
return 0U;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief USB host stack initializations
|
||||
\param[in] pudev: pointer to usb core instance
|
||||
\param[in] core: USBFS core or USBHS core
|
||||
\param[in] puhost: pointer to USB host
|
||||
\param[out] none
|
||||
\retval operation status
|
||||
*/
|
||||
void usbh_init(usb_core_driver* pudev, usb_core_enum core, usbh_host* puhost)
|
||||
{
|
||||
uint8_t i = 0U;
|
||||
|
||||
/* host de-initializations */
|
||||
usbh_deinit(pudev, puhost);
|
||||
|
||||
pudev->host.connect_status = 0U;
|
||||
|
||||
for (i = 0U; i < USBFS_MAX_TX_FIFOS; i++) {
|
||||
pudev->host.pipe[i].err_count = 0U;
|
||||
pudev->host.pipe[i].pp_status = PIPE_IDLE;
|
||||
pudev->host.backup_xfercount[i] = 0U;
|
||||
}
|
||||
|
||||
pudev->host.pipe[0].ep.mps = 8U;
|
||||
|
||||
usb_basic_init(&pudev->bp, &pudev->regs, core);
|
||||
|
||||
#ifndef DUAL_ROLE_MODE_ENABLED
|
||||
|
||||
usb_core_init(pudev->bp, &pudev->regs);
|
||||
|
||||
usb_host_init(pudev);
|
||||
|
||||
#endif /* DUAL_ROLE_MODE_ENABLED */
|
||||
|
||||
/* upon init call usr call back */
|
||||
puhost->usr_cb->dev_init();
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief de-initialize USB host
|
||||
\param[in] pudev: pointer to usb core instance
|
||||
\param[in] puhost: pointer to USB host
|
||||
\param[out] none
|
||||
\retval operation status
|
||||
*/
|
||||
usbh_status usbh_deinit(usb_core_driver* pudev, usbh_host* puhost)
|
||||
{
|
||||
/* software init */
|
||||
puhost->cur_state = HOST_DEFAULT;
|
||||
puhost->backup_state = HOST_DEFAULT;
|
||||
puhost->enum_state = ENUM_DEFAULT;
|
||||
|
||||
puhost->control.ctl_state = CTL_IDLE;
|
||||
puhost->control.max_len = USB_FS_EP0_MAX_LEN;
|
||||
|
||||
puhost->dev_prop.addr = USBH_DEV_ADDR_DEFAULT;
|
||||
puhost->dev_prop.speed = PORT_SPEED_FULL;
|
||||
|
||||
usbh_pipe_free(pudev, puhost->control.pipe_in_num);
|
||||
usbh_pipe_free(pudev, puhost->control.pipe_out_num);
|
||||
|
||||
return USBH_OK;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief USB host core main state machine process
|
||||
\param[in] pudev: pointer to usb core instance
|
||||
\param[in] puhost: pointer to USB host
|
||||
\param[out] none
|
||||
\retval none
|
||||
*/
|
||||
void usbh_core_task(usb_core_driver* pudev, usbh_host* puhost)
|
||||
{
|
||||
volatile usbh_status status = USBH_FAIL;
|
||||
|
||||
/* check for host port events */
|
||||
if (((0U == pudev->host.connect_status) || (0U == pudev->host.port_enabled)) && (HOST_DEFAULT != puhost->cur_state)) {
|
||||
if (puhost->cur_state != HOST_DEV_DETACHED) {
|
||||
puhost->cur_state = HOST_DEV_DETACHED;
|
||||
}
|
||||
}
|
||||
|
||||
switch (puhost->cur_state) {
|
||||
case HOST_DEFAULT:
|
||||
if (pudev->host.connect_status) {
|
||||
puhost->cur_state = HOST_DETECT_DEV_SPEED;
|
||||
|
||||
usb_mdelay(100U);
|
||||
// usb_mdelay (2U);
|
||||
usb_port_reset(pudev);
|
||||
|
||||
puhost->usr_cb->dev_reset();
|
||||
}
|
||||
break;
|
||||
|
||||
case HOST_DETECT_DEV_SPEED:
|
||||
if (pudev->host.port_enabled) {
|
||||
puhost->cur_state = HOST_DEV_ATTACHED;
|
||||
puhost->dev_prop.speed = usb_curspeed_get(pudev);
|
||||
puhost->usr_cb->dev_speed_detected(puhost->dev_prop.speed);
|
||||
|
||||
usb_mdelay(50U);
|
||||
}
|
||||
break;
|
||||
|
||||
case HOST_DEV_ATTACHED:
|
||||
puhost->usr_cb->dev_attach();
|
||||
puhost->control.pipe_out_num = usbh_pipe_allocate(pudev, 0x00U);
|
||||
puhost->control.pipe_in_num = usbh_pipe_allocate(pudev, 0x80U);
|
||||
|
||||
/* reset USB device */
|
||||
usb_port_reset(pudev);
|
||||
|
||||
/* open IN control pipe */
|
||||
usbh_pipe_create(pudev,
|
||||
&puhost->dev_prop,
|
||||
puhost->control.pipe_in_num,
|
||||
USB_EPTYPE_CTRL,
|
||||
puhost->control.max_len);
|
||||
|
||||
/* open OUT control pipe */
|
||||
usbh_pipe_create(pudev,
|
||||
&puhost->dev_prop,
|
||||
puhost->control.pipe_out_num,
|
||||
USB_EPTYPE_CTRL,
|
||||
puhost->control.max_len);
|
||||
|
||||
puhost->cur_state = HOST_ENUM;
|
||||
break;
|
||||
|
||||
case HOST_ENUM:
|
||||
|
||||
/* check for enumeration status */
|
||||
if (USBH_OK == usbh_enum_task(pudev, puhost)) {
|
||||
/* the function shall return USBH_OK when full enumeration is complete */
|
||||
|
||||
/* user callback for end of device basic enumeration */
|
||||
puhost->usr_cb->dev_enumerated();
|
||||
puhost->cur_state = HOST_USER_INPUT;
|
||||
}
|
||||
break;
|
||||
|
||||
case HOST_USER_INPUT:
|
||||
/* the function should return user response true to move to class state */
|
||||
if (USBH_USER_RESP_OK == puhost->usr_cb->dev_user_input()) {
|
||||
if ((USBH_OK == puhost->class_cb->class_init(pudev, puhost))) {
|
||||
puhost->cur_state = HOST_CLASS_ENUM;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case HOST_CLASS_ENUM:
|
||||
/* process class standard contol requests state machine */
|
||||
status = puhost->class_cb->class_requests(pudev, puhost);
|
||||
|
||||
if (USBH_OK == status) {
|
||||
puhost->cur_state = HOST_CLASS_HANDLER;
|
||||
} else {
|
||||
usbh_error_handler(puhost, status);
|
||||
}
|
||||
break;
|
||||
|
||||
case HOST_CLASS_HANDLER:
|
||||
/* process class state machine */
|
||||
status = puhost->class_cb->class_machine(pudev, puhost);
|
||||
|
||||
usbh_error_handler(puhost, status);
|
||||
break;
|
||||
|
||||
case HOST_SUSPENDED:
|
||||
break;
|
||||
|
||||
case HOST_ERROR:
|
||||
/* re-initilaize host for new enumeration */
|
||||
usbh_deinit(pudev, puhost);
|
||||
puhost->usr_cb->dev_deinit();
|
||||
puhost->class_cb->class_deinit(pudev, puhost);
|
||||
break;
|
||||
|
||||
case HOST_DEV_DETACHED:
|
||||
/* manage user disconnect operations*/
|
||||
puhost->usr_cb->dev_detach();
|
||||
|
||||
/* re-initilaize host for new enumeration */
|
||||
usbh_deinit(pudev, puhost);
|
||||
puhost->usr_cb->dev_deinit();
|
||||
puhost->class_cb->class_deinit(pudev, puhost);
|
||||
usbh_pipe_delete(pudev);
|
||||
puhost->cur_state = HOST_DEFAULT;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief handle the error on USB host side
|
||||
\param[in] puhost: pointer to USB host
|
||||
\param[in] err_type: type of error or busy/OK state
|
||||
\param[out] none
|
||||
\retval none
|
||||
*/
|
||||
void usbh_error_handler(usbh_host* puhost, usbh_status err_type)
|
||||
{
|
||||
/* error unrecovered or not supported device speed */
|
||||
if ((USBH_SPEED_UNKNOWN_ERROR == err_type) || (USBH_UNRECOVERED_ERROR == err_type)) {
|
||||
puhost->usr_cb->dev_error();
|
||||
|
||||
puhost->cur_state = HOST_ERROR;
|
||||
} else if (USBH_APPLY_DEINIT == err_type) {
|
||||
puhost->cur_state = HOST_ERROR;
|
||||
|
||||
/* user callback for initalization */
|
||||
puhost->usr_cb->dev_init();
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief handle the USB enumeration task
|
||||
\param[in] pudev: pointer to selected USB device
|
||||
\param[in] puhost: pointer to host
|
||||
\param[out] none
|
||||
\retval none
|
||||
*/
|
||||
static usbh_status usbh_enum_task(usb_core_driver* pudev, usbh_host* puhost)
|
||||
{
|
||||
uint8_t str_buf[64];
|
||||
|
||||
usbh_status status = USBH_BUSY;
|
||||
|
||||
static uint8_t index_mfc_str = 0U, index_prod_str = 0U, index_serial_str = 0U;
|
||||
|
||||
switch (puhost->enum_state) {
|
||||
case ENUM_DEFAULT:
|
||||
/* get device descriptor for only 1st 8 bytes : to get ep0 maxpacketsize */
|
||||
if (USBH_OK == usbh_devdesc_get(pudev, puhost, 8U)) {
|
||||
|
||||
puhost->control.max_len = puhost->dev_prop.dev_desc.bMaxPacketSize0;
|
||||
|
||||
/* issue reset */
|
||||
usb_port_reset(pudev);
|
||||
|
||||
/* modify control channels configuration for maximum packet size */
|
||||
usbh_pipe_update(pudev,
|
||||
puhost->control.pipe_out_num,
|
||||
0U, 0U,
|
||||
puhost->control.max_len);
|
||||
|
||||
usbh_pipe_update(pudev,
|
||||
puhost->control.pipe_in_num,
|
||||
0U, 0U,
|
||||
puhost->control.max_len);
|
||||
|
||||
puhost->enum_state = ENUM_GET_DEV_DESC;
|
||||
|
||||
}
|
||||
break;
|
||||
|
||||
case ENUM_GET_DEV_DESC:
|
||||
/* get full device descriptor */
|
||||
if (USBH_OK == usbh_devdesc_get(pudev, puhost, USB_DEV_DESC_LEN)) {
|
||||
puhost->usr_cb->dev_devdesc_assigned(&puhost->dev_prop.dev_desc);
|
||||
|
||||
index_mfc_str = puhost->dev_prop.dev_desc.iManufacturer;
|
||||
index_prod_str = puhost->dev_prop.dev_desc.iProduct;
|
||||
index_serial_str = puhost->dev_prop.dev_desc.iSerialNumber;
|
||||
|
||||
puhost->enum_state = ENUM_SET_ADDR;
|
||||
}
|
||||
break;
|
||||
|
||||
case ENUM_SET_ADDR:
|
||||
/* set address */
|
||||
if (USBH_OK == usbh_setaddress(pudev, puhost, USBH_DEV_ADDR)) {
|
||||
usb_mdelay(2);
|
||||
|
||||
puhost->dev_prop.addr = USBH_DEV_ADDR;
|
||||
|
||||
/* user callback for device address assigned */
|
||||
puhost->usr_cb->dev_address_set();
|
||||
|
||||
/* modify control channels to update device address */
|
||||
usbh_pipe_update(pudev,
|
||||
puhost->control.pipe_in_num,
|
||||
puhost->dev_prop.addr,
|
||||
0U, 0U);
|
||||
|
||||
usbh_pipe_update(pudev,
|
||||
puhost->control.pipe_out_num,
|
||||
puhost->dev_prop.addr,
|
||||
0U, 0U);
|
||||
|
||||
puhost->enum_state = ENUM_GET_CFG_DESC;
|
||||
}
|
||||
break;
|
||||
|
||||
case ENUM_GET_CFG_DESC:
|
||||
/* get standard configuration descriptor */
|
||||
if (USBH_OK == usbh_cfgdesc_get(pudev, puhost, USB_CFG_DESC_LEN)) {
|
||||
puhost->enum_state = ENUM_GET_CFG_DESC_SET;
|
||||
}
|
||||
break;
|
||||
|
||||
case ENUM_GET_CFG_DESC_SET:
|
||||
/* get full config descriptor (config, interface, endpoints) */
|
||||
if (USBH_OK == usbh_cfgdesc_get(pudev, puhost, puhost->dev_prop.cfg_desc.wTotalLength)) {
|
||||
/* user callback for configuration descriptors available */
|
||||
puhost->usr_cb->dev_cfgdesc_assigned(&puhost->dev_prop.cfg_desc,
|
||||
puhost->dev_prop.itf_desc,
|
||||
puhost->dev_prop.ep_desc[0]);
|
||||
|
||||
puhost->enum_state = ENUM_GET_STR_DESC;
|
||||
}
|
||||
break;
|
||||
|
||||
case ENUM_GET_STR_DESC:
|
||||
if (index_mfc_str) {
|
||||
if (USBH_OK == usbh_strdesc_get(pudev,
|
||||
puhost,
|
||||
puhost->dev_prop.dev_desc.iManufacturer,
|
||||
str_buf,
|
||||
0xFFU)) {
|
||||
/* user callback for manufacturing string */
|
||||
puhost->usr_cb->dev_mfc_str(str_buf);
|
||||
|
||||
index_mfc_str = 0U;
|
||||
}
|
||||
} else {
|
||||
if (index_prod_str) {
|
||||
/* check that product string is available */
|
||||
if (USBH_OK == usbh_strdesc_get(pudev,
|
||||
puhost,
|
||||
puhost->dev_prop.dev_desc.iProduct,
|
||||
str_buf,
|
||||
0xFFU)) {
|
||||
puhost->usr_cb->dev_prod_str(str_buf);
|
||||
|
||||
index_prod_str = 0U;
|
||||
}
|
||||
} else {
|
||||
if (index_serial_str) {
|
||||
if (USBH_OK == usbh_strdesc_get(pudev,
|
||||
puhost,
|
||||
puhost->dev_prop.dev_desc.iSerialNumber,
|
||||
str_buf,
|
||||
0xFFU)) {
|
||||
puhost->usr_cb->dev_seral_str(str_buf);
|
||||
puhost->enum_state = ENUM_SET_CONFIGURATION;
|
||||
|
||||
index_serial_str = 0U;
|
||||
}
|
||||
} else {
|
||||
puhost->enum_state = ENUM_SET_CONFIGURATION;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case ENUM_SET_CONFIGURATION:
|
||||
if (USBH_OK == usbh_setcfg(pudev,
|
||||
puhost,
|
||||
puhost->dev_prop.cfg_desc.bConfigurationValue)) {
|
||||
puhost->enum_state = ENUM_DEV_CONFIGURED;
|
||||
}
|
||||
break;
|
||||
|
||||
case ENUM_DEV_CONFIGURED:
|
||||
status = USBH_OK;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
@@ -0,0 +1,549 @@
|
||||
/*!
|
||||
\file usbh_enum.c
|
||||
\brief USB host mode enumberation driver
|
||||
|
||||
\version 2019-6-5, V1.0.0, firmware for GD32 USBFS&USBHS
|
||||
*/
|
||||
|
||||
/*
|
||||
Copyright (c) 2019, 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 "usbh_transc.h"
|
||||
#include "usbh_enum.h"
|
||||
|
||||
static void usbh_devdesc_parse(usb_desc_dev* cfg_desc, uint8_t* buf, uint16_t len);
|
||||
static void usbh_cfgset_parse(usb_dev_prop* udev, uint8_t* buf);
|
||||
static void usbh_cfgdesc_parse(usb_desc_config* cfg_desc, uint8_t* buf);
|
||||
static void usbh_itfdesc_parse(usb_desc_itf* itf_desc, uint8_t* buf);
|
||||
static void usbh_epdesc_parse(usb_desc_ep* ep_desc, uint8_t* buf);
|
||||
static void usbh_strdesc_parse(uint8_t* psrc, uint8_t* pdest, uint16_t len);
|
||||
|
||||
|
||||
/*!
|
||||
\brief configure USB control status parameters
|
||||
\param[in] puhost: pointer to usb host
|
||||
\param[in] buf: control transfer data buffer pointer
|
||||
\param[in] len: length of the data buffer
|
||||
\param[out] none
|
||||
\retval none
|
||||
*/
|
||||
void usbh_ctlstate_config(usbh_host* puhost, uint8_t* buf, uint16_t len)
|
||||
{
|
||||
/* prepare the transactions */
|
||||
puhost->control.buf = buf;
|
||||
puhost->control.ctl_len = len;
|
||||
|
||||
puhost->control.ctl_state = CTL_SETUP;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief get device descriptor from the USB device
|
||||
\param[in] pudev: pointer to usb core instance
|
||||
\param[in] puhost: pointer to usb host
|
||||
\param[in] len: length of the descriptor
|
||||
\param[out] none
|
||||
\retval operation status
|
||||
*/
|
||||
usbh_status usbh_devdesc_get(usb_core_driver* pudev, usbh_host* puhost, uint8_t len)
|
||||
{
|
||||
usbh_status status = USBH_BUSY;
|
||||
|
||||
usbh_control* usb_ctl = &puhost->control;
|
||||
|
||||
if (CTL_IDLE == usb_ctl->ctl_state) {
|
||||
usb_ctl->setup.req = (usb_req) {
|
||||
.bmRequestType = USB_TRX_IN | USB_RECPTYPE_DEV | USB_REQTYPE_STRD,
|
||||
.bRequest = USB_GET_DESCRIPTOR,
|
||||
.wValue = USBH_DESC(USB_DESCTYPE_DEV),
|
||||
.wIndex = 0U,
|
||||
.wLength = len
|
||||
};
|
||||
|
||||
usbh_ctlstate_config(puhost, pudev->host.rx_buf, len);
|
||||
}
|
||||
|
||||
status = usbh_ctl_handler(pudev, puhost);
|
||||
|
||||
if (USBH_OK == status) {
|
||||
/* commands successfully sent and response received */
|
||||
usbh_devdesc_parse(&puhost->dev_prop.dev_desc, pudev->host.rx_buf, len);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief get configuration descriptor from the USB device
|
||||
\param[in] pudev: pointer to usb core instance
|
||||
\param[in] puhost: pointer to usb host
|
||||
\param[in] len: length of the descriptor
|
||||
\param[out] none
|
||||
\retval operation status
|
||||
*/
|
||||
usbh_status usbh_cfgdesc_get(usb_core_driver* pudev, usbh_host* puhost, uint16_t len)
|
||||
{
|
||||
usbh_status status = USBH_BUSY;
|
||||
|
||||
usbh_control* usb_ctl = &puhost->control;
|
||||
|
||||
if (CTL_IDLE == usb_ctl->ctl_state) {
|
||||
usb_ctl->setup.req = (usb_req) {
|
||||
.bmRequestType = USB_TRX_IN | USB_RECPTYPE_DEV | USB_REQTYPE_STRD,
|
||||
.bRequest = USB_GET_DESCRIPTOR,
|
||||
.wValue = USBH_DESC(USB_DESCTYPE_CONFIG),
|
||||
.wIndex = 0U,
|
||||
.wLength = len
|
||||
};
|
||||
|
||||
usbh_ctlstate_config(puhost, pudev->host.rx_buf, len);
|
||||
}
|
||||
|
||||
status = usbh_ctl_handler(pudev, puhost);
|
||||
|
||||
if (USBH_OK == status) {
|
||||
if (len <= USB_CFG_DESC_LEN) {
|
||||
usbh_cfgdesc_parse(&puhost->dev_prop.cfg_desc, pudev->host.rx_buf);
|
||||
} else {
|
||||
usbh_cfgset_parse(&puhost->dev_prop, pudev->host.rx_buf);
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief get string descriptor from the USB device
|
||||
\param[in] pudev: pointer to usb core instance
|
||||
\param[in] puhost: pointer to usb host
|
||||
\param[in] str_index: index for the string descriptor
|
||||
\param[in] buf: buffer pointer to the string descriptor
|
||||
\param[in] len: length of the descriptor
|
||||
\param[out] none
|
||||
\retval operation status
|
||||
*/
|
||||
usbh_status usbh_strdesc_get(usb_core_driver* pudev,
|
||||
usbh_host* puhost,
|
||||
uint8_t str_index,
|
||||
uint8_t* buf,
|
||||
uint16_t len)
|
||||
{
|
||||
usbh_status status = USBH_BUSY;
|
||||
|
||||
usbh_control* usb_ctl = &puhost->control;
|
||||
|
||||
if (CTL_IDLE == usb_ctl->ctl_state) {
|
||||
usb_ctl->setup.req = (usb_req) {
|
||||
.bmRequestType = USB_TRX_IN | USB_RECPTYPE_DEV | USB_REQTYPE_STRD,
|
||||
.bRequest = USB_GET_DESCRIPTOR,
|
||||
.wValue = USBH_DESC(USB_DESCTYPE_STR) | str_index,
|
||||
.wIndex = 0x0409U,
|
||||
.wLength = len
|
||||
};
|
||||
|
||||
usbh_ctlstate_config(puhost, pudev->host.rx_buf, len);
|
||||
}
|
||||
|
||||
status = usbh_ctl_handler(pudev, puhost);
|
||||
|
||||
if (USBH_OK == status) {
|
||||
/* commands successfully sent and response received */
|
||||
usbh_strdesc_parse(pudev->host.rx_buf, buf, len);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief set the address to the connected device
|
||||
\param[in] pudev: pointer to usb core instance
|
||||
\param[in] puhost: pointer to usb host
|
||||
\param[in] dev_addr: device address to assign
|
||||
\param[out] none
|
||||
\retval operation status
|
||||
*/
|
||||
usbh_status usbh_setaddress(usb_core_driver* pudev, usbh_host* puhost, uint8_t dev_addr)
|
||||
{
|
||||
usbh_status status = USBH_BUSY;
|
||||
|
||||
usbh_control* usb_ctl = &puhost->control;
|
||||
|
||||
if (CTL_IDLE == usb_ctl->ctl_state) {
|
||||
usb_ctl->setup.req = (usb_req) {
|
||||
.bmRequestType = USB_TRX_OUT | USB_RECPTYPE_DEV | USB_REQTYPE_STRD,
|
||||
.bRequest = USB_SET_ADDRESS,
|
||||
.wValue = (uint16_t)dev_addr,
|
||||
.wIndex = 0U,
|
||||
.wLength = 0U
|
||||
};
|
||||
|
||||
usbh_ctlstate_config(puhost, NULL, 0U);
|
||||
}
|
||||
|
||||
status = usbh_ctl_handler(pudev, puhost);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief set the configuration value to the connected device
|
||||
\param[in] pudev: pointer to usb core instance
|
||||
\param[in] puhost: pointer to usb host
|
||||
\param[in] config_index: configuration value
|
||||
\param[out] none
|
||||
\retval operation status
|
||||
*/
|
||||
usbh_status usbh_setcfg(usb_core_driver* pudev, usbh_host* puhost, uint16_t config_index)
|
||||
{
|
||||
usbh_status status = USBH_BUSY;
|
||||
|
||||
usbh_control* usb_ctl = &puhost->control;
|
||||
|
||||
if (CTL_IDLE == usb_ctl->ctl_state) {
|
||||
usb_ctl->setup.req = (usb_req) {
|
||||
.bmRequestType = USB_TRX_OUT | USB_RECPTYPE_DEV | USB_REQTYPE_STRD,
|
||||
.bRequest = USB_SET_CONFIGURATION,
|
||||
.wValue = config_index,
|
||||
.wIndex = 0U,
|
||||
.wLength = 0U
|
||||
};
|
||||
|
||||
usbh_ctlstate_config(puhost, NULL, 0U);
|
||||
}
|
||||
|
||||
status = usbh_ctl_handler(pudev, puhost);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief set the interface value to the connected device
|
||||
\param[in] pudev: pointer to usb core instance
|
||||
\param[in] puhost: pointer to usb host
|
||||
\param[in] ep_num: endpoint number
|
||||
\param[in] alter_setting: altnated setting value
|
||||
\param[out] none
|
||||
\retval operation status
|
||||
*/
|
||||
usbh_status usbh_setinterface(usb_core_driver* pudev,
|
||||
usbh_host* puhost,
|
||||
uint8_t ep_num,
|
||||
uint8_t set)
|
||||
{
|
||||
usbh_status status = USBH_BUSY;
|
||||
|
||||
usbh_control* usb_ctl = &puhost->control;
|
||||
|
||||
if (CTL_IDLE == usb_ctl->ctl_state) {
|
||||
usb_ctl->setup.req = (usb_req) {
|
||||
.bmRequestType = USB_TRX_OUT | USB_RECPTYPE_ITF | USB_REQTYPE_STRD,
|
||||
.bRequest = USB_SET_INTERFACE,
|
||||
.wValue = set,
|
||||
.wIndex = ep_num,
|
||||
.wLength = 0U
|
||||
};
|
||||
|
||||
usbh_ctlstate_config(puhost, NULL, 0U);
|
||||
}
|
||||
|
||||
status = usbh_ctl_handler(pudev, puhost);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief clear or disable a specific feature
|
||||
\param[in] pudev: pointer to usb core instance
|
||||
\param[in] puhost: pointer to usb host
|
||||
\param[in] ep_addr: endpoint address
|
||||
\param[in] pp_num: pipe number
|
||||
\param[out] none
|
||||
\retval operation status
|
||||
*/
|
||||
usbh_status usbh_clrfeature(usb_core_driver* pudev,
|
||||
usbh_host* puhost,
|
||||
uint8_t ep_addr,
|
||||
uint8_t pp_num)
|
||||
{
|
||||
usbh_status status = USBH_BUSY;
|
||||
|
||||
usbh_control* usb_ctl = &puhost->control;
|
||||
|
||||
if (CTL_IDLE == usb_ctl->ctl_state) {
|
||||
usb_ctl->setup.req = (usb_req) {
|
||||
.bmRequestType = USB_TRX_OUT | USB_RECPTYPE_EP | USB_REQTYPE_STRD,
|
||||
.bRequest = USB_CLEAR_FEATURE,
|
||||
.wValue = FEATURE_SELECTOR_EP,
|
||||
.wIndex = ep_addr,
|
||||
.wLength = 0
|
||||
};
|
||||
|
||||
if (EP_DIR(ep_addr)) {
|
||||
pudev->host.pipe[pp_num].data_toggle_in = 0U;
|
||||
} else {
|
||||
pudev->host.pipe[pp_num].data_toggle_out = 0U;
|
||||
}
|
||||
|
||||
usbh_ctlstate_config(puhost, NULL, 0U);
|
||||
}
|
||||
|
||||
status = usbh_ctl_handler(pudev, puhost);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief parse the device descriptor
|
||||
\param[in] dev_desc: pointer to usb device descriptor buffer
|
||||
\param[in] buf: pointer to the source descriptor buffer
|
||||
\param[in] len: length of the descriptor
|
||||
\param[out] none
|
||||
\retval operation status
|
||||
*/
|
||||
static void usbh_devdesc_parse(usb_desc_dev* dev_desc, uint8_t* buf, uint16_t len)
|
||||
{
|
||||
*dev_desc = (usb_desc_dev) {
|
||||
.header = {
|
||||
.bLength = *(uint8_t*)(buf + 0U),
|
||||
.bDescriptorType = *(uint8_t*)(buf + 1U)
|
||||
},
|
||||
|
||||
.bcdUSB = BYTE_SWAP(buf + 2U),
|
||||
.bDeviceClass = *(uint8_t*)(buf + 4U),
|
||||
.bDeviceSubClass = *(uint8_t*)(buf + 5U),
|
||||
.bDeviceProtocol = *(uint8_t*)(buf + 6U),
|
||||
.bMaxPacketSize0 = *(uint8_t*)(buf + 7U)
|
||||
};
|
||||
|
||||
if (len > 8U) {
|
||||
/* for 1st time after device connection, host may issue only 8 bytes for device descriptor length */
|
||||
dev_desc->idVendor = BYTE_SWAP(buf + 8U);
|
||||
dev_desc->idProduct = BYTE_SWAP(buf + 10U);
|
||||
dev_desc->bcdDevice = BYTE_SWAP(buf + 12U);
|
||||
dev_desc->iManufacturer = *(uint8_t*)(buf + 14U);
|
||||
dev_desc->iProduct = *(uint8_t*)(buf + 15U);
|
||||
dev_desc->iSerialNumber = *(uint8_t*)(buf + 16U);
|
||||
dev_desc->bNumberConfigurations = *(uint8_t*)(buf + 17U);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief parse the configuration descriptor
|
||||
\param[in] cfg_desc: pointer to usb configuration descriptor buffer
|
||||
\param[in] buf: pointer to the source descriptor buffer
|
||||
\param[out] none
|
||||
\retval operation status
|
||||
*/
|
||||
static void usbh_cfgdesc_parse(usb_desc_config* cfg_desc, uint8_t* buf)
|
||||
{
|
||||
/* parse configuration descriptor */
|
||||
*cfg_desc = (usb_desc_config) {
|
||||
.header = {
|
||||
.bLength = *(uint8_t*)(buf + 0U),
|
||||
.bDescriptorType = *(uint8_t*)(buf + 1U),
|
||||
},
|
||||
|
||||
.wTotalLength = BYTE_SWAP(buf + 2U),
|
||||
.bNumInterfaces = *(uint8_t*)(buf + 4U),
|
||||
.bConfigurationValue = *(uint8_t*)(buf + 5U),
|
||||
.iConfiguration = *(uint8_t*)(buf + 6U),
|
||||
.bmAttributes = *(uint8_t*)(buf + 7U),
|
||||
.bMaxPower = *(uint8_t*)(buf + 8U)
|
||||
};
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief parse the configuration descriptor set
|
||||
\param[in] udev: pointer to USB core instance
|
||||
\param[in] buf: pointer to the source descriptor buffer
|
||||
\param[out] none
|
||||
\retval operation status
|
||||
*/
|
||||
static void usbh_cfgset_parse(usb_dev_prop* udev, uint8_t* buf)
|
||||
{
|
||||
usb_desc_ep* ep = NULL;
|
||||
usb_desc_itf* itf = NULL, itf_value;
|
||||
|
||||
usb_desc_header* pdesc = (usb_desc_header*)buf;
|
||||
|
||||
int8_t itf_index = 0U, ep_index = 0U;
|
||||
uint16_t ptr;
|
||||
|
||||
uint8_t prev_itf = 0U;
|
||||
uint16_t prev_ep_len = 0U;
|
||||
|
||||
/* parse configuration descriptor */
|
||||
usbh_cfgdesc_parse(&udev->cfg_desc, buf);
|
||||
|
||||
ptr = USB_CFG_DESC_LEN;
|
||||
|
||||
if (udev->cfg_desc.bNumInterfaces > USBH_MAX_INTERFACES_NUM) {
|
||||
return;
|
||||
}
|
||||
|
||||
while (ptr < udev->cfg_desc.wTotalLength) {
|
||||
pdesc = usbh_nextdesc_get((uint8_t*)pdesc, &ptr);
|
||||
|
||||
if (pdesc->bDescriptorType == USB_DESCTYPE_ITF) {
|
||||
itf_index = *(((uint8_t*)pdesc) + 2U);
|
||||
itf = &udev->itf_desc[itf_index];
|
||||
|
||||
if ((*((uint8_t*)pdesc + 3U)) < 3U) {
|
||||
usbh_itfdesc_parse(&itf_value, (uint8_t*)pdesc);
|
||||
|
||||
/* parse endpoint descriptors relative to the current interface */
|
||||
if (itf_value.bNumEndpoints > USBH_MAX_EP_NUM) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (ep_index = 0; ep_index < itf_value.bNumEndpoints;) {
|
||||
pdesc = usbh_nextdesc_get((void*)pdesc, &ptr);
|
||||
|
||||
if (pdesc->bDescriptorType == USB_DESCTYPE_EP) {
|
||||
ep = &udev->ep_desc[itf_index][ep_index];
|
||||
|
||||
if (prev_itf != itf_index) {
|
||||
prev_itf = itf_index;
|
||||
usbh_itfdesc_parse(itf, (uint8_t*)&itf_value);
|
||||
} else {
|
||||
if (prev_ep_len > BYTE_SWAP((uint8_t*)pdesc + 4U)) {
|
||||
break;
|
||||
} else {
|
||||
usbh_itfdesc_parse(itf, (uint8_t*)&itf_value);
|
||||
}
|
||||
}
|
||||
|
||||
usbh_epdesc_parse(ep, (uint8_t*)pdesc);
|
||||
prev_ep_len = BYTE_SWAP((uint8_t*)pdesc + 4U);
|
||||
ep_index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief parse the interface descriptor
|
||||
\param[in] itf_desc: pointer to usb interface descriptor buffer
|
||||
\param[in] buf: pointer to the source descriptor buffer
|
||||
\param[out] none
|
||||
\retval operation status
|
||||
*/
|
||||
static void usbh_itfdesc_parse(usb_desc_itf* itf_desc, uint8_t* buf)
|
||||
{
|
||||
*itf_desc = (usb_desc_itf) {
|
||||
.header = {
|
||||
.bLength = *(uint8_t*)(buf + 0U),
|
||||
.bDescriptorType = *(uint8_t*)(buf + 1U),
|
||||
},
|
||||
|
||||
.bInterfaceNumber = *(uint8_t*)(buf + 2U),
|
||||
.bAlternateSetting = *(uint8_t*)(buf + 3U),
|
||||
.bNumEndpoints = *(uint8_t*)(buf + 4U),
|
||||
.bInterfaceClass = *(uint8_t*)(buf + 5U),
|
||||
.bInterfaceSubClass = *(uint8_t*)(buf + 6U),
|
||||
.bInterfaceProtocol = *(uint8_t*)(buf + 7U),
|
||||
.iInterface = *(uint8_t*)(buf + 8U)
|
||||
};
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief parse the endpoint descriptor
|
||||
\param[in] ep_desc: pointer to usb endpoint descriptor buffer
|
||||
\param[in] buf: pointer to the source descriptor buffer
|
||||
\param[out] none
|
||||
\retval operation status
|
||||
*/
|
||||
static void usbh_epdesc_parse(usb_desc_ep* ep_desc, uint8_t* buf)
|
||||
{
|
||||
*ep_desc = (usb_desc_ep) {
|
||||
.header = {
|
||||
.bLength = *(uint8_t*)(buf + 0U),
|
||||
.bDescriptorType = *(uint8_t*)(buf + 1U)
|
||||
},
|
||||
|
||||
.bEndpointAddress = *(uint8_t*)(buf + 2U),
|
||||
.bmAttributes = *(uint8_t*)(buf + 3U),
|
||||
.wMaxPacketSize = BYTE_SWAP(buf + 4U),
|
||||
.bInterval = *(uint8_t*)(buf + 6U)
|
||||
};
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief parse the string descriptor
|
||||
\param[in] psrc: source pointer containing the descriptor data
|
||||
\param[in] pdest: destination address pointer
|
||||
\param[in] len: length of the descriptor
|
||||
\param[out] none
|
||||
\retval operation status
|
||||
*/
|
||||
static void usbh_strdesc_parse(uint8_t* psrc, uint8_t* pdest, uint16_t len)
|
||||
{
|
||||
uint16_t str_len = 0U, index = 0U;
|
||||
|
||||
/* the unicode string descriptor is not NULL-terminated. The string length is
|
||||
* computed by substracting two from the value of the first byte of the descriptor.
|
||||
*/
|
||||
|
||||
/* check which is lower size, the size of string or the length of bytes read from the device */
|
||||
if (USB_DESCTYPE_STR == psrc[1]) {
|
||||
/* make sure the descriptor is string type */
|
||||
|
||||
/* psrc[0] contains Size of Descriptor, subtract 2 to get the length of string */
|
||||
str_len = USB_MIN(psrc[0] - 2U, len);
|
||||
|
||||
psrc += 2U; /* adjust the offset ignoring the string len and descriptor type */
|
||||
|
||||
for (index = 0U; index < str_len; index += 2U) {
|
||||
/* copy only the string and ignore the unicode id, hence add the src */
|
||||
*pdest = psrc[index];
|
||||
|
||||
pdest++;
|
||||
}
|
||||
|
||||
*pdest = 0U; /* mark end of string */
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief get the next descriptor header
|
||||
\param[in] pbuf: pointer to buffer where the configuration descriptor set is available
|
||||
\param[in] ptr: data popinter inside the configuration descriptor set
|
||||
\param[out] none
|
||||
\retval operation status
|
||||
*/
|
||||
usb_desc_header* usbh_nextdesc_get(uint8_t* pbuf, uint16_t* ptr)
|
||||
{
|
||||
usb_desc_header* pnext;
|
||||
|
||||
*ptr += ((usb_desc_header*)pbuf)->bLength;
|
||||
|
||||
pnext = (usb_desc_header*)((uint8_t*)pbuf + ((usb_desc_header*)pbuf)->bLength);
|
||||
|
||||
return (pnext);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
/*!
|
||||
\file usbh_pipe.c
|
||||
\brief USB host mode pipe operation driver
|
||||
|
||||
\version 2019-6-5, V1.0.0, firmware for GD32 USBFS&USBHS
|
||||
*/
|
||||
|
||||
/*
|
||||
Copyright (c) 2019, 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 "usbh_pipe.h"
|
||||
|
||||
static uint16_t usbh_freepipe_get(usb_core_driver* pudev);
|
||||
|
||||
/*!
|
||||
\brief create a pipe
|
||||
\param[in] pudev: pointer to usb core instance
|
||||
\param[in] pp_num: pipe number
|
||||
\param[in] udev: USB device
|
||||
\param[in] ep_type: endpoint type
|
||||
\param[in] ep_mpl: endpoint max packet length
|
||||
\param[out] none
|
||||
\retval operation status
|
||||
*/
|
||||
uint8_t usbh_pipe_create(usb_core_driver* pudev,
|
||||
usb_dev_prop* udev,
|
||||
uint8_t pp_num,
|
||||
uint8_t ep_type,
|
||||
uint16_t ep_mpl)
|
||||
{
|
||||
usb_pipe* pp = &pudev->host.pipe[pp_num];
|
||||
|
||||
pp->dev_addr = udev->addr;
|
||||
pp->dev_speed = udev->speed;
|
||||
pp->ep.type = ep_type;
|
||||
pp->ep.mps = ep_mpl;
|
||||
pp->ping = udev->speed == PORT_SPEED_HIGH;
|
||||
|
||||
usb_pipe_init(pudev, pp_num);
|
||||
|
||||
return HC_OK;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief modify a pipe
|
||||
\param[in] pudev: pointer to usb core instance
|
||||
\param[in] pp_num: pipe number
|
||||
\param[in] dev_addr: device address
|
||||
\param[in] dev_speed: device speed
|
||||
\param[in] ep_type: endpoint type
|
||||
\param[in] ep_mpl: endpoint max packet length
|
||||
\param[out] none
|
||||
\retval operation status
|
||||
*/
|
||||
uint8_t usbh_pipe_update(usb_core_driver* pudev,
|
||||
uint8_t pp_num,
|
||||
uint8_t dev_addr,
|
||||
uint32_t dev_speed,
|
||||
uint16_t ep_mpl)
|
||||
{
|
||||
usb_pipe* pp = &pudev->host.pipe[pp_num];
|
||||
|
||||
if ((pp->dev_addr != dev_addr) && (dev_addr)) {
|
||||
pp->dev_addr = dev_addr;
|
||||
}
|
||||
|
||||
if ((pp->dev_speed != dev_speed) && (dev_speed)) {
|
||||
pp->dev_speed = dev_speed;
|
||||
}
|
||||
|
||||
if ((pp->ep.mps != ep_mpl) && (ep_mpl)) {
|
||||
pp->ep.mps = ep_mpl;
|
||||
}
|
||||
|
||||
usb_pipe_init(pudev, pp_num);
|
||||
|
||||
return HC_OK;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief allocate a new pipe
|
||||
\param[in] pudev: pointer to usb core instance
|
||||
\param[in] ep_addr: endpoint address
|
||||
\param[out] none
|
||||
\retval operation status
|
||||
*/
|
||||
uint8_t usbh_pipe_allocate(usb_core_driver* pudev, uint8_t ep_addr)
|
||||
{
|
||||
uint16_t pp_num = usbh_freepipe_get(pudev);
|
||||
|
||||
if (HC_ERROR != pp_num) {
|
||||
pudev->host.pipe[pp_num].in_used = 1U;
|
||||
pudev->host.pipe[pp_num].ep.dir = EP_DIR(ep_addr);
|
||||
pudev->host.pipe[pp_num].ep.num = EP_ID(ep_addr);
|
||||
}
|
||||
|
||||
return pp_num;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief free a pipe
|
||||
\param[in] pudev: pointer to usb core instance
|
||||
\param[in] pp_num: pipe number
|
||||
\param[out] none
|
||||
\retval operation status
|
||||
*/
|
||||
uint8_t usbh_pipe_free(usb_core_driver* pudev, uint8_t pp_num)
|
||||
{
|
||||
if (pp_num < HC_MAX) {
|
||||
pudev->host.pipe[pp_num].in_used = 0U;
|
||||
}
|
||||
|
||||
return USBH_OK;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief delete all USB host pipe
|
||||
\param[in] pudev: pointer to usb core instance
|
||||
\param[out] none
|
||||
\retval operation status
|
||||
*/
|
||||
uint8_t usbh_pipe_delete(usb_core_driver* pudev)
|
||||
{
|
||||
uint8_t pp_num = 0U;
|
||||
|
||||
for (pp_num = 2U; pp_num < HC_MAX; pp_num++) {
|
||||
pudev->host.pipe[pp_num] = (usb_pipe) {0};
|
||||
}
|
||||
|
||||
return USBH_OK;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief get a free pipe number for allocation
|
||||
\param[in] pudev: pointer to usb core instance
|
||||
\param[out] none
|
||||
\retval operation status
|
||||
*/
|
||||
static uint16_t usbh_freepipe_get(usb_core_driver* pudev)
|
||||
{
|
||||
uint8_t pp_num = 0U;
|
||||
|
||||
for (pp_num = 0U; pp_num < HC_MAX; pp_num++) {
|
||||
if (pudev->host.pipe[pp_num].in_used == 0U) {
|
||||
return pp_num;
|
||||
}
|
||||
}
|
||||
|
||||
return HC_ERROR;
|
||||
}
|
||||
@@ -0,0 +1,391 @@
|
||||
/*!
|
||||
\file usbh_transc.c
|
||||
\brief USB host mode transactions driver
|
||||
|
||||
\version 2019-6-5, V1.0.0, firmware for GD32 USBFS&USBHS
|
||||
*/
|
||||
|
||||
/*
|
||||
Copyright (c) 2019, 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 "usbh_transc.h"
|
||||
|
||||
/*!
|
||||
\brief prepare a pipe and start a transfer
|
||||
\param[in] pudev: pointer to usb core instance
|
||||
\param[in] pp_num: pipe number
|
||||
\param[out] none
|
||||
\retval operation status
|
||||
*/
|
||||
static uint32_t usbh_request_submit(usb_core_driver* pudev, uint8_t pp_num)
|
||||
{
|
||||
pudev->host.pipe[pp_num].urb_state = URB_IDLE;
|
||||
pudev->host.pipe[pp_num].xfer_count = 0U;
|
||||
|
||||
return usb_pipe_xfer(pudev, pp_num);
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief send the setup packet to the USB device
|
||||
\param[in] pudev: pointer to usb core instance
|
||||
\param[in] buf: data buffer which will be sent to USB device
|
||||
\param[in] pp_num: pipe number
|
||||
\param[out] none
|
||||
\retval operation status
|
||||
*/
|
||||
usbh_status usbh_ctlsetup_send(usb_core_driver* pudev, uint8_t* buf, uint8_t pp_num)
|
||||
{
|
||||
usb_pipe* pp = &pudev->host.pipe[pp_num];
|
||||
|
||||
pp->DPID = PIPE_DPID_SETUP;
|
||||
pp->xfer_buf = buf;
|
||||
pp->xfer_len = USB_SETUP_PACKET_LEN;
|
||||
|
||||
return (usbh_status)usbh_request_submit(pudev, pp_num);
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief send a data packet to the USB device
|
||||
\param[in] pudev: pointer to usb core instance
|
||||
\param[in] buf: data buffer which will be sent to USB device
|
||||
\param[in] pp_num: pipe number
|
||||
\param[in] len: length of the data to be sent
|
||||
\param[out] none
|
||||
\retval operation status
|
||||
*/
|
||||
usbh_status usbh_data_send(usb_core_driver* pudev, uint8_t* buf, uint8_t pp_num, uint16_t len)
|
||||
{
|
||||
usb_pipe* pp = &pudev->host.pipe[pp_num];
|
||||
|
||||
pp->xfer_buf = buf;
|
||||
pp->xfer_len = len;
|
||||
|
||||
switch (pp->ep.type) {
|
||||
case USB_EPTYPE_CTRL:
|
||||
if (0U == len) {
|
||||
pp->data_toggle_out = 1U;
|
||||
}
|
||||
|
||||
pp->DPID = PIPE_DPID[pp->data_toggle_out];
|
||||
break;
|
||||
|
||||
case USB_EPTYPE_INTR:
|
||||
pp->DPID = PIPE_DPID[pp->data_toggle_out];
|
||||
|
||||
pp->data_toggle_out ^= 1U;
|
||||
break;
|
||||
|
||||
case USB_EPTYPE_BULK:
|
||||
pp->DPID = PIPE_DPID[pp->data_toggle_out];
|
||||
break;
|
||||
|
||||
case USB_EPTYPE_ISOC:
|
||||
pp->DPID = PIPE_DPID[0];
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
usbh_request_submit(pudev, pp_num);
|
||||
|
||||
return USBH_OK;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief receive a data packet from the USB device
|
||||
\param[in] pudev: pointer to usb core instance
|
||||
\param[in] buf: data buffer which will be received from USB device
|
||||
\param[in] pp_num: pipe number
|
||||
\param[in] len: length of the data to be received
|
||||
\param[out] none
|
||||
\retval operation status
|
||||
*/
|
||||
usbh_status usbh_data_recev(usb_core_driver* pudev, uint8_t* buf, uint8_t pp_num, uint16_t len)
|
||||
{
|
||||
usb_pipe* pp = &pudev->host.pipe[pp_num];
|
||||
|
||||
pp->xfer_buf = buf;
|
||||
pp->xfer_len = len;
|
||||
|
||||
switch (pp->ep.type) {
|
||||
case USB_EPTYPE_CTRL:
|
||||
pp->DPID = PIPE_DPID[1];
|
||||
break;
|
||||
|
||||
case USB_EPTYPE_INTR:
|
||||
pp->DPID = PIPE_DPID[pp->data_toggle_in];
|
||||
|
||||
/* Toggle DATA PID */
|
||||
pp->data_toggle_in ^= 1U;
|
||||
break;
|
||||
|
||||
case USB_EPTYPE_BULK:
|
||||
pp->DPID = PIPE_DPID[pp->data_toggle_in];
|
||||
break;
|
||||
|
||||
case USB_EPTYPE_ISOC:
|
||||
pp->DPID = PIPE_DPID[0];
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
usbh_request_submit(pudev, pp_num);
|
||||
|
||||
return USBH_OK;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief wait for USB URB(USB request block) state
|
||||
\param[in] pudev: pointer to USB core instance
|
||||
\param[in] puhost: pointer to USB host
|
||||
\param[in] pp_num: pipe number
|
||||
\param[in] wait_time: wait time
|
||||
\param[out] none
|
||||
\retval USB URB state
|
||||
*/
|
||||
static usb_urb_state usbh_urb_wait(usb_core_driver* pudev, usbh_host* puhost, uint8_t pp_num, uint32_t wait_time)
|
||||
{
|
||||
usb_urb_state urb_status = URB_IDLE;
|
||||
|
||||
while (URB_DONE != (urb_status = usbh_urbstate_get(pudev, pp_num))) {
|
||||
if (URB_NOTREADY == urb_status) {
|
||||
break;
|
||||
} else if (URB_STALL == urb_status) {
|
||||
puhost->control.ctl_state = CTL_SETUP;
|
||||
break;
|
||||
} else if (URB_ERROR == urb_status) {
|
||||
puhost->control.ctl_state = CTL_ERROR;
|
||||
break;
|
||||
} else if ((wait_time > 0U) && \
|
||||
((usb_curframe_get(pudev) - puhost->control.timer) > wait_time)) {
|
||||
/* timeout for in transfer */
|
||||
puhost->control.ctl_state = CTL_ERROR;
|
||||
break;
|
||||
} else {
|
||||
/* no operation, just wait */
|
||||
}
|
||||
}
|
||||
|
||||
return urb_status;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief USB setup transaction
|
||||
\param[in] pudev: pointer to USB core instance
|
||||
\param[in] puhost: pointer to USB host
|
||||
\param[out] none
|
||||
\retval none
|
||||
*/
|
||||
static void usbh_setup_transc(usb_core_driver* pudev, usbh_host* puhost)
|
||||
{
|
||||
usb_urb_state urb_status = URB_IDLE;
|
||||
|
||||
/* send a SETUP packet */
|
||||
usbh_ctlsetup_send(pudev,
|
||||
puhost->control.setup.data,
|
||||
puhost->control.pipe_out_num);
|
||||
|
||||
urb_status = usbh_urb_wait(pudev, puhost, puhost->control.pipe_out_num, 0U);
|
||||
|
||||
if (URB_DONE == urb_status) {
|
||||
uint8_t dir = (puhost->control.setup.req.bmRequestType & USB_TRX_MASK);
|
||||
|
||||
if (puhost->control.setup.req.wLength) {
|
||||
if (USB_TRX_IN == dir) {
|
||||
puhost->control.ctl_state = CTL_DATA_IN;
|
||||
} else {
|
||||
puhost->control.ctl_state = CTL_DATA_OUT;
|
||||
}
|
||||
} else {
|
||||
if (USB_TRX_IN == dir) {
|
||||
puhost->control.ctl_state = CTL_STATUS_OUT;
|
||||
} else {
|
||||
puhost->control.ctl_state = CTL_STATUS_IN;
|
||||
}
|
||||
}
|
||||
|
||||
/* set the delay timer to enable timeout for data stage completion */
|
||||
puhost->control.timer = usb_curframe_get(pudev);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief USB data IN transaction
|
||||
\param[in] pudev: pointer to USB core instance
|
||||
\param[in] puhost: pointer to USB host
|
||||
\param[out] none
|
||||
\retval none
|
||||
*/
|
||||
static void usbh_data_in_transc(usb_core_driver* pudev, usbh_host* puhost)
|
||||
{
|
||||
usb_urb_state urb_status = URB_IDLE;
|
||||
|
||||
usbh_data_recev(pudev,
|
||||
puhost->control.buf,
|
||||
puhost->control.pipe_in_num,
|
||||
puhost->control.ctl_len);
|
||||
|
||||
urb_status = usbh_urb_wait(pudev, puhost, puhost->control.pipe_in_num, DATA_STAGE_TIMEOUT);
|
||||
|
||||
if (URB_DONE == urb_status) {
|
||||
puhost->control.ctl_state = CTL_STATUS_OUT;
|
||||
|
||||
puhost->control.timer = usb_curframe_get(pudev);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief USB data OUT transaction
|
||||
\param[in] pudev: pointer to USB core instance
|
||||
\param[in] puhost: pointer to USB host
|
||||
\param[out] none
|
||||
\retval none
|
||||
*/
|
||||
static void usbh_data_out_transc(usb_core_driver* pudev, usbh_host* puhost)
|
||||
{
|
||||
usb_urb_state urb_status = URB_IDLE;
|
||||
|
||||
pudev->host.pipe[puhost->control.pipe_out_num].data_toggle_out = 1U;
|
||||
|
||||
usbh_data_send(pudev,
|
||||
puhost->control.buf,
|
||||
puhost->control.pipe_out_num,
|
||||
puhost->control.ctl_len);
|
||||
|
||||
urb_status = usbh_urb_wait(pudev, puhost, puhost->control.pipe_out_num, DATA_STAGE_TIMEOUT);
|
||||
|
||||
if (URB_DONE == urb_status) {
|
||||
puhost->control.ctl_state = CTL_STATUS_IN;
|
||||
|
||||
puhost->control.timer = usb_curframe_get(pudev);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief USB status IN transaction
|
||||
\param[in] pudev: pointer to USB core instance
|
||||
\param[in] puhost: pointer to USB host
|
||||
\param[out] none
|
||||
\retval none
|
||||
*/
|
||||
static void usbh_status_in_transc(usb_core_driver* pudev, usbh_host* puhost)
|
||||
{
|
||||
uint8_t pp_num = puhost->control.pipe_in_num;
|
||||
|
||||
usb_urb_state urb_status = URB_IDLE;
|
||||
|
||||
usbh_data_recev(pudev, NULL, pp_num, 0U);
|
||||
|
||||
urb_status = usbh_urb_wait(pudev, puhost, pp_num, NODATA_STAGE_TIMEOUT);
|
||||
|
||||
if (URB_DONE == urb_status) {
|
||||
puhost->control.ctl_state = CTL_FINISH;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief USB status OUT transaction
|
||||
\param[in] pudev: pointer to USB core instance
|
||||
\param[in] puhost: pointer to USB host
|
||||
\param[out] none
|
||||
\retval none
|
||||
*/
|
||||
static void usbh_status_out_transc(usb_core_driver* pudev, usbh_host* puhost)
|
||||
{
|
||||
uint8_t pp_num = puhost->control.pipe_out_num;
|
||||
|
||||
usb_urb_state urb_status = URB_IDLE;
|
||||
|
||||
pudev->host.pipe[pp_num].data_toggle_out ^= 1U;
|
||||
|
||||
usbh_data_send(pudev, NULL, pp_num, 0U);
|
||||
|
||||
urb_status = usbh_urb_wait(pudev, puhost, pp_num, NODATA_STAGE_TIMEOUT);
|
||||
|
||||
if (URB_DONE == urb_status) {
|
||||
puhost->control.ctl_state = CTL_FINISH;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief USB control transfer handler
|
||||
\param[in] pudev: pointer to USB core instance
|
||||
\param[in] puhost: pointer to USB host
|
||||
\param[out] none
|
||||
\retval operation status
|
||||
*/
|
||||
usbh_status usbh_ctl_handler(usb_core_driver* pudev, usbh_host* puhost)
|
||||
{
|
||||
usbh_status status = USBH_BUSY;
|
||||
|
||||
switch (puhost->control.ctl_state) {
|
||||
case CTL_SETUP:
|
||||
usbh_setup_transc(pudev, puhost);
|
||||
break;
|
||||
|
||||
case CTL_DATA_IN:
|
||||
usbh_data_in_transc(pudev, puhost);
|
||||
break;
|
||||
|
||||
case CTL_DATA_OUT:
|
||||
usbh_data_out_transc(pudev, puhost);
|
||||
break;
|
||||
|
||||
case CTL_STATUS_IN:
|
||||
usbh_status_in_transc(pudev, puhost);
|
||||
break;
|
||||
|
||||
case CTL_STATUS_OUT:
|
||||
usbh_status_out_transc(pudev, puhost);
|
||||
break;
|
||||
|
||||
case CTL_FINISH:
|
||||
puhost->control.ctl_state = CTL_IDLE;
|
||||
|
||||
status = USBH_OK;
|
||||
break;
|
||||
|
||||
case CTL_ERROR:
|
||||
if (++puhost->control.error_count <= USBH_MAX_ERROR_COUNT) {
|
||||
/* do the transmission again, starting from SETUP packet */
|
||||
puhost->control.ctl_state = CTL_SETUP;
|
||||
} else {
|
||||
status = USBH_FAIL;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user