initial commit

This commit is contained in:
shell committed 2026-10-11 21:48:59 +08:00
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;
}