/*******************************************************************************
* arch/arm/src/stm32/stm32_usbdev.c
*
* Copyright (C) 2012 Gregory Nutt. All rights reserved.
* Author: Gregory Nutt <gnutt@nuttx.org>
*
* 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 NuttX 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 OWNER 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.
*
*******************************************************************************/
/*******************************************************************************
* Included Files
*******************************************************************************/
#include <nuttx/config.h>
#include <sys/types.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <debug.h>
#include <nuttx/arch.h>
#include <nuttx/usb/usb.h>
#include <nuttx/usb/usbdev.h>
#include <nuttx/usb/usbdev_trace.h>
#include <arch/irq.h>
#include <arch/board/board.h>
#include "chip.h"
#include "up_arch.h"
#include "up_internal.h"
#include "stm32_otgfs.h"
#if defined(CONFIG_USBDEV) && defined(CONFIG_STM32_OTGFS)
/*******************************************************************************
* Definitions
*******************************************************************************/
/* Configuration ***************************************************************/
#ifndef CONFIG_USBDEV_EP0_MAXSIZE
# define CONFIG_USBDEV_EP0_MAXSIZE 64
#endif
#ifndef CONFIG_USBDEV_MAXPOWER
# define CONFIG_USBDEV_MAXPOWER 100 /* mA */
#endif
#ifndef CONFIG_USBDEV_RXFIFO_SIZE
# define CONFIG_USBDEV_RXFIFO_SIZE 128
#endif
#ifndef CONFIG_USBDEV_EP0_TXFIFO_SIZE
# define CONFIG_USBDEV_EP0_TXFIFO_SIZE 128
#endif
#if CONFIG_USBDEV_EP0_TXFIFO_SIZE < 16 || CONFIG_USBDEV_EP0_TXFIFO_SIZE > 256
# error "CONFIG_USBDEV_EP0_TXFIFO_SIZE is out of range"
#endif
#ifndef CONFIG_USBDEV_EP1_TXFIFO_SIZE
# define CONFIG_USBDEV_EP1_TXFIFO_SIZE 128
#endif
#ifndef CONFIG_USBDEV_EP2_TXFIFO_SIZE
# define CONFIG_USBDEV_EP2_TXFIFO_SIZE 128
#endif
#ifndef CONFIG_USBDEV_EP3_TXFIFO_SIZE
# define CONFIG_USBDEV_EP3_TXFIFO_SIZE 128
#endif
/* Debug ***********************************************************************/
/* Trace error codes */
#define STM32_TRACEERR_ALLOCFAIL 0x0001
#define STM32_TRACEERR_BADCLEARFEATURE 0x0002
#define STM32_TRACEERR_BADDEVGETSTATUS 0x0003
#define STM32_TRACEERR_BADEPNO 0x0004
#define STM32_TRACEERR_BADEPGETSTATUS 0x0005
#define STM32_TRACEERR_BADGETCONFIG 0x0006
#define STM32_TRACEERR_BADGETSETDESC 0x0007
#define STM32_TRACEERR_BADGETSTATUS 0x0008
#define STM32_TRACEERR_BADSETADDRESS 0x0009
#define STM32_TRACEERR_BADSETCONFIG 0x000a
#define STM32_TRACEERR_BADSETFEATURE 0x000b
#define STM32_TRACEERR_BADTESTMODE 0x000c
#define STM32_TRACEERR_BINDFAILED 0x000d
#define STM32_TRACEERR_DISPATCHSTALL 0x000e
#define STM32_TRACEERR_DRIVER 0x000f
#define STM32_TRACEERR_DRIVERREGISTERED 0x0010
#define STM32_TRACEERR_EP0NOSETUP 0x0011
#define STM32_TRACEERR_EP0SETUPSTALLED 0x0012
#define STM32_TRACEERR_EPINNULLPACKET 0x0013
#define STM32_TRACEERR_EPOUTNULLPACKET 0x0014
#define STM32_TRACEERR_INVALIDCTRLREQ 0x0015
#define STM32_TRACEERR_INVALIDPARMS 0x0016
#define STM32_TRACEERR_IRQREGISTRATION 0x0017
#define STM32_TRACEERR_NOEP 0x0018
#define STM32_TRACEERR_NOTCONFIGURED 0x0019
#define STM32_TRACEERR_EPOUTQEMPTY 0x001a
#define STM32_TRACEERR_EPINQEMPTY 0x001b
/* Trace interrupt codes */
#define STM32_TRACEINTID_USB 0x0001 /* USB Interrupt entry/exit */
#define STM32_TRACEINTID_EPOUT 0x0101 /* First level interrupt decode */
#define STM32_TRACEINTID_EPIN 0x0102
#define STM32_TRACEINTID_MISMATCH 0x0103
#define STM32_TRACEINTID_WAKEUP 0x0104
#define STM32_TRACEINTID_SUSPEND 0x0105
#define STM32_TRACEINTID_SOF 0x0106
#define STM32_TRACEINTID_RXFIFO 0x0107
#define STM32_TRACEINTID_DEVRESET 0x0108
#define STM32_TRACEINTID_ENUMDNE 0x0109
#define STM32_TRACEINTID_IISOIXFR 0x010a
#define STM32_TRACEINTID_IISOOXFR 0x010b
#define STM32_TRACEINTID_SRQ 0x010c
#define STM32_TRACEINTID_OTG 0x010d
#define STM32_TRACEINTID_EPOUT_XFRC 0x0200 /* EPOUT second level decode */
#define STM32_TRACEINTID_EPOUT_EPDISD 0x0201
#define STM32_TRACEINTID_EPOUT_SETUP 0x0202
#define STM32_TRACEINTID_DISPATCH 0x0203
#define STM32_TRACEINTID_GETSTATUS 0x0210 /* EPOUT third level decode */
#define STM32_TRACEINTID_EPGETSTATUS 0x0211
#define STM32_TRACEINTID_DEVGETSTATUS 0x0212
#define STM32_TRACEINTID_IFGETSTATUS 0x0213
#define STM32_TRACEINTID_CLEARFEATURE 0x0214
#define STM32_TRACEINTID_SETFEATURE 0x0215
#define STM32_TRACEINTID_SETADDRESS 0x0216
#define STM32_TRACEINTID_GETSETDESC 0x0217
#define STM32_TRACEINTID_GETCONFIG 0x0218
#define STM32_TRACEINTID_SETCONFIG 0x0219
#define STM32_TRACEINTID_GETSETIF 0x021a
#define STM32_TRACEINTID_SYNCHFRAME 0x021b
#define STM32_TRACEINTID_EPIN_XFRC 0x0300 /* EPIN second level decode */
#define STM32_TRACEINTID_EPIN_TOC 0x0301
#define STM32_TRACEINTID_EPIN_ITTXFE 0x0302
#define STM32_TRACEINTID_EPIN_EPDISD 0x0303
#define STM32_TRACEINTID_EPIN_TXFE 0x0304
#define STM32_TRACEINTID_OUTNAK 0x0400 /* RXFLVL second level decode */
#define STM32_TRACEINTID_OUTRECVD 0x0401
#define STM32_TRACEINTID_OUTDONE 0x0402
#define STM32_TRACEINTID_SETUPDONE 0x0403
#define STM32_TRACEINTID_SETUPRECVD 0x0404
#define STM32_TRACEINTID_EPINCOMPLETE 0x0500 /* Request handling */
#define STM32_TRACEINTID_EPOUTCOMPLETE 0x0503
#define STM32_TRACEINTID_EPOUTQEMPTY 0x0504
/* Endpoints ******************************************************************/
/* Number of endpoints */
#define STM32_NENDPOINTS (4) /* ep0-3 x 2 for IN and OUT */
/* Odd physical endpoint numbers are IN; even are OUT */
#define STM32_EPPHYIN2LOG(epphy) ((uint8_t)(epphy)|USB_DIR_IN)
#define STM32_EPPHYOUT2LOG(epphy) ((uint8_t)(epphy)|USB_DIR_OUT)
/* Endpoint 0 */
#define EP0 (0)
/* The set of all enpoints available to the class implementation (1-3) */
#define STM32_EP_AVAILABLE (0x0e) /* All available endpoints */
/* Maximum packet sizes for full speed endpoints */
#define STM32_MAXPACKET (64) /* Max packet size (1-64) */
/* Delays **********************************************************************/
#define STM32_READY_DELAY 200000
#define STM32_FLUSH_DELAY 200000
/* Request queue operations ****************************************************/
#define stm32_rqempty(ep) ((ep)->head == NULL)
#define stm32_rqpeek(ep) ((ep)->head)
/* Standard stuff **************************************************************/
#ifndef MIN
# define MIN(a,b) ((a) < (b) ? (a) : (b))
#endif
#ifndef MAX
# define MAX(a,b) ((a) > (b) ? (a) : (b))
#endif
/*******************************************************************************
* Private Types
*******************************************************************************/
/* Overall device state */
enum stm32_devstate_e
{
DEVSTATE_DEFAULT = 0, /* Power-up, unconfigured state. This state simply
* means that the device is not yet been given an
* address.
* SET: At initialization, uninitialization,
* reset, and whenever the device address
* is set to zero
* TESTED: Never
*/
DEVSTATE_ADDRESSED, /* Device address has been assigned, not no
* configuration has yet been selected.
* SET: When either a non-zero device address
* is first assigned or when the device
* is unconfigured (with configuration == 0)
* TESTED: never
*/
DEVSTATE_CONFIGURED, /* Address assigned and configured:
* SET: When the device has been addressed and
* an non-zero configuration has been selected.
* TESTED: In many places to assure that the USB device
* has been properly configured by the host.
*/
};
/* Endpoint 0 states */
enum stm32_ep0state_e
{
EP0STATE_IDLE = 0, /* Idle State, leave on receiving a setup packet or
* epsubmit:
* SET: In stm32_epin() and stm32_epout() when
* we revert from request processing to
* SETUP processing.
* TESTED: Never */
EP0STATE_SETUP, /* Setup Packet received by stm32_ep0out_setup():
* SET: When SETUP packet received in EP0 OUT
* TESTED: Never */
EP0STATE_SETUPRESPONSE, /* Short setup response write (without a USB request):
* SET: When SETUP response is sent by
* stm32_ep0in_setupresponse()
* TESTED: Never */
EP0STATE_DATA_IN, /* Waiting for data out stage (with a USB request):
* SET: In stm32_epin_request() when a write
* request is processed on EP0.
* TESTED: In stm32_epin() to see if we should
* revert to SETUP processing.
*/
EP0STATE_DATA_OUT /* Waiting for data in phase to complete ( with a
* USB request)
* SET: In stm32_epout_request() when a read
* request is processed on EP0.
* TESTED: In stm32_epout() to see if we should
* revert to SETUP processing
*/
};
/* Parsed control request */
struct stm32_ctrlreq_s
{
uint8_t type;
uint8_t req;
uint16_t value;
uint16_t index;
uint16_t len;
};
/* A container for a request so that the request may be retained in a list */
struct stm32_req_s
{
struct usbdev_req_s req; /* Standard USB request */
struct stm32_req_s *flink; /* Supports a singly linked list */
};
/* This is the internal representation of an endpoint */
struct stm32_ep_s
{
/* Common endpoint fields. This must be the first thing defined in the
* structure so that it is possible to simply cast from struct usbdev_ep_s
* to struct stm32_ep_s.
*/
struct usbdev_ep_s ep; /* Standard endpoint structure */
/* STM32-specific fields */
struct stm32_usbdev_s *dev; /* Reference to private driver data */
struct stm32_req_s *head; /* Request list for this endpoint */
struct stm32_req_s *tail;
uint8_t epphy; /* Physical EP address */
uint8_t eptype:2; /* Endpoint type */
uint8_t configured:1; /* 1: Endpoint has been configured */
uint8_t active:1; /* 1: A request is being processed */
uint8_t stalled:1; /* 1: Endpoint is stalled */
uint8_t isin:1; /* 1: IN Endpoint */
uint8_t odd:1; /* 1: Odd frame */
uint8_t zlp:1; /* 1: Transmit a zero-length-packet (IN EPs only) */
};
/* This structure retains the state of the USB device controller */
struct stm32_usbdev_s
{
/* Common device fields. This must be the first thing defined in the
* structure so that it is possible to simply cast from struct usbdev_s
* to struct stm32_usbdev_s.
*/
struct usbdev_s usbdev;
/* The bound device class driver */
struct usbdevclass_driver_s *driver;
/* STM32-specific fields */
uint8_t stalled:1; /* 1: Protocol stalled */
uint8_t selfpowered:1; /* 1: Device is self powered */
uint8_t connected:1; /* 1: Host connected */
uint8_t addressed:1; /* 1: Peripheral address has been set */
uint8_t configured:1; /* 1: Class driver has been configured */
uint8_t wakeup:1; /* 1: Device remote wake-up */
uint8_t dotest:1; /* 1: Test mode selected */
uint8_t setup:1; /* 1: SETUP received */
uint8_t devstate:4; /* See enum stm32_devstate_e */
uint8_t ep0state:4; /* See enum stm32_ep0state_e */
uint8_t testmode:4; /* Selected test mode */
uint8_t epavail:4; /* Bitset of available endpoints */
struct usb_ctrlreq_s ctrlreq; /* Received SETUP request */
uint8_t ep0resp[2]; /* Buffer for EP0 SETUP responses */
/* The endpoint list */
struct stm32_ep_s epin[STM32_NENDPOINTS];
struct stm32_ep_s epout[STM32_NENDPOINTS];
};
/*******************************************************************************
* Private Function Prototypes
*******************************************************************************/
/* Register operations ********************************************************/
#if defined(CONFIG_STM32_USBDEV_REGDEBUG) && defined(CONFIG_DEBUG)
static uint32_t stm32_getreg(uint32_t addr);
static void stm32_putreg(uint32_t val, uint32_t addr);
#else
# define stm32_getreg(addr) getreg32(addr)
# define stm32_putreg(val,addr) putreg32(val,addr)
#endif
/* Request queue operations ****************************************************/
static FAR struct stm32_req_s *stm32_req_remfirst(FAR struct stm32_ep_s *privep);
static bool stm32_req_addlast(FAR struct stm32_ep_s *privep,
FAR struct stm32_req_s *req);
/* Low level data transfers and request operations *****************************/
/* Special endpoint 0 data transfer logic */
static void stm32_ep0in_setupresponse(FAR struct stm32_usbdev_s *priv,
FAR uint8_t *data, uint32_t nbytes);
static inline void stm32_ep0in_transmitzlp(FAR struct stm32_usbdev_s *priv);
static void stm32_ep0in_activate(void);
static void stm32_ep0out_ctrlsetup(FAR struct stm32_usbdev_s *priv);
/* IN request and TxFIFO handling */
static void stm32_txfifo_write(FAR struct stm32_ep_s *privep,
FAR uint8_t *buf, int nbytes);
static void stm32_epin_transfer(FAR struct stm32_ep_s *privep,
FAR uint8_t *buf, int nbytes);
static void stm32_epin_request(FAR struct stm32_usbdev_s *priv,
FAR struct stm32_ep_s *privep);
/* OUT request and RxFIFO handling */
static void stm32_rxfifo_read(FAR struct stm32_ep_s *privep,
FAR uint8_t *dest, uint16_t len);
static void stm32_epout_complete(FAR struct stm32_usbdev_s *priv,
FAR struct stm32_ep_s *privep);
static inline void stm32_epout_receive(FAR struct stm32_ep_s *privep, int bcnt);
static void stm32_epout_request(FAR struct stm32_usbdev_s *priv,
FAR struct stm32_ep_s *privep);
/* General request handling */
static void stm32_ep_flush(FAR struct stm32_ep_s *privep);
static void stm32_req_complete(FAR struct stm32_ep_s *privep, int16_t result);
static void stm32_req_cancel(FAR struct stm32_ep_s *privep,
int16_t status);
/* Interrupt handling **********************************************************/
static struct stm32_ep_s *stm32_ep_findbyaddr(struct stm32_usbdev_s *priv,
uint16_t eplog);
static int stm32_req_dispatch(FAR struct stm32_usbdev_s *priv,
FAR const struct usb_ctrlreq_s *ctrl);
static void stm32_usbreset(FAR struct stm32_usbdev_s *priv);
/* Second level OUT endpoint interrupt processing */
static inline void stm32_ep0out_testmode(FAR struct stm32_usbdev_s *priv,
uint16_t index);
static inline void stm32_ep0out_stdrequest(struct stm32_usbdev_s *priv,
FAR struct stm32_ctrlreq_s *ctrlreq);
static inline void stm32_ep0out_setup(struct stm32_usbdev_s *priv);
static inline void stm32_epout(FAR struct stm32_usbdev_s *priv,
uint8_t epno);
static inline void stm32_epout_interrupt(FAR struct stm32_usbdev_s *priv);
/* Second level IN endpoint interrupt processing */
static inline void stm32_epin_runtestmode(FAR struct stm32_usbdev_s *priv);
static inline void stm32_epin(FAR struct stm32_usbdev_s *priv, uint8_t epno);
static inline void stm32_epin_txfifoempty(FAR struct stm32_usbdev_s *priv, int epno);
static inline void stm32_epin_interrupt(FAR struct stm32_usbdev_s *priv);
/* Other second level interrupt processing */
static inline void stm32_resumeinterrupt(FAR struct stm32_usbdev_s *priv);
static inline void stm32_suspendinterrupt(FAR struct stm32_usbdev_s *priv);
static inline void stm32_rxinterrupt(FAR struct stm32_usbdev_s *priv);
static inline void stm32_enuminterrupt(FAR struct stm32_usbdev_s *priv);
#ifdef CONFIG_USBDEV_ISOCHRONOUS
static inline void stm32_isocininterrupt(FAR struct stm32_usbdev_s *priv);
static inline void stm32_isocoutinterrupt(FAR struct stm32_usbdev_s *priv);
#endif
#ifdef CONFIG_USBDEV_VBUSSENSING
static inline void stm32_sessioninterrupt(FAR struct stm32_usbdev_s *priv);
static inline void stm32_otginterrupt(FAR struct stm32_usbdev_s *priv);
#endif
/* First level interrupt processing */
static int stm32_usbinterrupt(int irq, FAR void *context);
/* Endpoint operations *********************************************************/
/* Global OUT NAK controls */
static void stm32_enablegonak(FAR struct stm32_ep_s *privep);
static void stm32_disablegonak(FAR struct stm32_ep_s *privep);
/* Endpoint configuration */
static int stm32_epout_configure(FAR struct stm32_ep_s *privep,
uint8_t eptype, uint16_t maxpacket);
static int stm32_epin_configure(FAR struct stm32_ep_s *privep,
uint8_t eptype, uint16_t maxpacket);
static int stm32_ep_configure(FAR struct usbdev_ep_s *ep,
FAR const struct usb_epdesc_s *desc, bool last);
static void stm32_ep0_configure(FAR struct stm32_usbdev_s *priv);
/* Endpoint disable */
static void stm32_epout_disable(FAR struct stm32_ep_s *privep);
static void stm32_epin_disable(FAR struct stm32_ep_s *privep);
static int stm32_ep_disable(FAR struct usbdev_ep_s *ep);
/* Endpoint request management */
static FAR struct usbdev_req_s *stm32_ep_allocreq(FAR struct usbdev_ep_s *ep);
static void stm32_ep_freereq(FAR struct usbdev_ep_s *ep,
FAR struct usbdev_req_s *);
/* Endpoint buffer management */
#ifdef CONFIG_ARCH_USBDEV_DMA
static void *stm32_ep_allocbuffer(FAR struct usbdev_ep_s *ep, unsigned bytes);
static void stm32_ep_freebuffer(FAR struct usbdev_ep_s *ep, FAR void *buf);
#endif
/* Endpoint request submission */
static int stm32_ep_submit(FAR struct usbdev_ep_s *ep,
struct usbdev_req_s *req);
/* Endpoint request cancellation */
static int stm32_ep_cancel(FAR struct usbdev_ep_s *ep,
struct usbdev_req_s *req);
/* Stall handling */
static int stm32_epout_setstall(FAR struct stm32_ep_s *privep);
static int stm32_epin_setstall(FAR struct stm32_ep_s *privep);
static int stm32_ep_setstall(FAR struct stm32_ep_s *privep);
static int stm32_ep_clrstall(FAR struct stm32_ep_s *privep);
static int stm32_ep_stall(FAR struct usbdev_ep_s *ep, bool resume);
static void stm32_ep0_stall(FAR struct stm32_usbdev_s *priv);
/* Endpoint allocation */
static FAR struct usbdev_ep_s *stm32_ep_alloc(FAR struct usbdev_s *dev,
uint8_t epno, bool in, uint8_t eptype);
static void stm32_ep_free(FAR struct usbdev_s *dev,
FAR struct usbdev_ep_s *ep);
/* USB device controller operations ********************************************/
static int stm32_getframe(struct usbdev_s *dev);
static int stm32_wakeup(struct usbdev_s *dev);
static int stm32_selfpowered(struct usbdev_s *dev, bool selfpowered);
static int stm32_pullup(struct usbdev_s *dev, bool enable);
static void stm32_setaddress(struct stm32_usbdev_s *priv,
uint16_t address);
static int stm32_txfifo_flush(uint32_t txfnum);
static int stm32_rxfifo_flush(void);
/* Initialization **************************************************************/
static void stm32_swinitialize(FAR struct stm32_usbdev_s *priv);
static void stm32_hwinitialize(FAR struct stm32_usbdev_s *priv);
/*******************************************************************************
* Private Data
*******************************************************************************/
/* Since there is only a single USB interface, all status information can be
* be simply retained in a single global instance.
*/
static struct stm32_usbdev_s g_otgfsdev;
static const struct usbdev_epops_s g_epops =
{
.configure = stm32_ep_configure,
.disable = stm32_ep_disable,
.allocreq = stm32_ep_allocreq,
.freereq = stm32_ep_freereq,
#ifdef CONFIG_ARCH_USBDEV_DMA
.allocbuffer = stm32_ep_allocbuffer,
.freebuffer = stm32_ep_freebuffer,
#endif
.submit = stm32_ep_submit,
.cancel = stm32_ep_cancel,
.stall = stm32_ep_stall,
};
static const struct usbdev_ops_s g_devops =
{
.allocep = stm32_ep_alloc,
.freeep = stm32_ep_free,
.getframe = stm32_getframe,
.wakeup = stm32_wakeup,
.selfpowered = stm32_selfpowered,
.pullup = stm32_pullup,
};
/*******************************************************************************
* Public Data
*******************************************************************************/
/*******************************************************************************
* Private Functions
*******************************************************************************/
/*******************************************************************************
* Name: stm32_getreg
*
* Description:
* Get the contents of an STM32 register
*
*******************************************************************************/
#if defined(CONFIG_STM32_USBDEV_REGDEBUG) && defined(CONFIG_DEBUG)
static uint32_t stm32_getreg(uint32_t addr)
{
static uint32_t prevaddr = 0;
static uint32_t preval = 0;
static uint32_t count = 0;
/* Read the value from the register */
uint32_t val = getreg32(addr);
/* Is this the same value that we read from the same registe last time? Are
* we polling the register? If so, suppress some of the output.
*/
if (addr == prevaddr && val == preval)
{
if (count == 0xffffffff || ++count > 3)
{
if (count == 4)
{
lldbg("...\n");
}
return val;
}
}
/* No this is a new address or value */
else
{
/* Did we print "..." for the previous value? */
if (count > 3)
{
/* Yes.. then show how many times the value repeated */
lldbg("[repeats %d more times]\n", count-3);
}
/* Save the new address, value, and count */
prevaddr = addr;
preval = val;
count = 1;
}
/* Show the register value read */
lldbg("%08x->%08x\n", addr, val);
return val;
}
#endif
/*******************************************************************************
* Name: stm32_putreg
*
* Description:
* Set the contents of an STM32 register to a value
*
*******************************************************************************/
#if defined(CONFIG_STM32_USBDEV_REGDEBUG) && defined(CONFIG_DEBUG)
static void stm32_putreg(uint32_t val, uint32_t addr)
{
/* Show the register value being written */
lldbg("%08x<-%08x\n", addr, val);
/* Write the value */
putreg32(val, addr);
}
#endif
/*******************************************************************************
* Name: stm32_req_remfirst
*
* Description:
* Remove a request from the head of an endpoint request queue
*
*******************************************************************************/
static FAR struct stm32_req_s *stm32_req_remfirst(FAR struct stm32_ep_s *privep)
{
FAR struct stm32_req_s *ret = privep->head;
if (ret)
{
privep->head = ret->flink;
if (!privep->head)
{
privep->tail = NULL;
}
ret->flink = NULL;
}
return ret;
}
/*******************************************************************************
* Name: stm32_req_addlast
*
* Description:
* Add a request to the end of an endpoint request queue
*
*******************************************************************************/
static bool stm32_req_addlast(FAR struct stm32_ep_s *privep,
FAR struct stm32_req_s *req)
{
bool is_empty = !privep->head;
req->flink = NULL;
if (is_empty)
{
privep->head = req;
privep->tail = req;
}
else
{
privep->tail->flink = req;
privep->tail = req;
}
return is_empty;
}
/*******************************************************************************
* Name: stm32_ep0in_setupresponse
*
* Description:
* Schedule a short transfer on Endpoint 0 (IN or OUT)
*
*******************************************************************************/
static void stm32_ep0in_setupresponse(FAR struct stm32_usbdev_s *priv,
FAR uint8_t *buf, uint32_t nbytes)
{
stm32_epin_transfer(&priv->epin[EP0], buf, nbytes);
priv->ep0state = EP0STATE_SETUPRESPONSE;
stm32_ep0out_ctrlsetup(priv);
}
/****************************************************************************
* Name: stm32_ep0in_transmitzlp
*
* Description:
* Send a zero length packet (ZLP) on endpoint 0 IN
*
****************************************************************************/
static inline void stm32_ep0in_transmitzlp(FAR struct stm32_usbdev_s *priv)
{
stm32_ep0in_setupresponse(priv, NULL, 0);
}
/*******************************************************************************
* Name: stm32_ep0in_activate
*
* Description:
* Activate the endpoint 0 IN endpoint.
*
*******************************************************************************/
static void stm32_ep0in_activate(void)
{
uint32_t regval;
/* Set the max packet size of the IN EP. */
regval = stm32_getreg(STM32_OTGFS_DIEPCTL0);
regval &= ~OTGFS_DIEPCTL0_MPSIZ_MASK;
#if CONFIG_USBDEV_EP0_MAXSIZE == 8
regval |= OTGFS_DIEPCTL0_MPSIZ_8;
#elif CONFIG_USBDEV_EP0_MAXSIZE == 16
regval |= OTGFS_DIEPCTL0_MPSIZ_16;
#elif CONFIG_USBDEV_EP0_MAXSIZE == 32
regval |= OTGFS_DIEPCTL0_MPSIZ_32;
#elif CONFIG_USBDEV_EP0_MAXSIZE == 64
regval |= OTGFS_DIEPCTL0_MPSIZ_64;
#else
# error "Unsupported value of CONFIG_USBDEV_EP0_MAXSIZE"
#endif
stm32_putreg(regval, STM32_OTGFS_DIEPCTL0);
/* Clear global IN NAK */
stm32_putreg(OTGFS_DCTL_CGINAK, STM32_OTGFS_DCTL);
}
/*******************************************************************************
* Name: stm32_ep0out_ctrlsetup
*
* Description:
* Setup to receive a SETUP packet.
*
*******************************************************************************/
static void stm32_ep0out_ctrlsetup(FAR struct stm32_usbdev_s *priv)
{
uint32_t regval;
regval = (USB_SIZEOF_CTRLREQ * 3 << OTGFS_DOEPTSIZ0_XFRSIZ_SHIFT) ||
(OTGFS_DOEPTSIZ0_PKTCNT) ||
(3 << OTGFS_DOEPTSIZ0_STUPCNT_SHIFT);
stm32_putreg(regval, STM32_OTGFS_DOEPTSIZ0);
}
/****************************************************************************
* Name: stm32_txfifo_write
*
* Description:
* Send data to the endpoint's TxFIFO.
*
****************************************************************************/
static void stm32_txfifo_write(FAR struct stm32_ep_s *privep,
FAR uint8_t *buf, int nbytes)
{
uint32_t regaddr;
uint32_t regval;
int nwords;
int i;
/* Convert the number of bytes to words */
nwords = (nbytes + 3) >> 2;
/* Get the TxFIFO for this endpoint (same as the endpoint number) */
regaddr = STM32_OTGFS_DFIFO_DEP(privep->epphy);
/* Then transfer each word to the TxFIFO */
for (i = 0; i < nwords; i++)
{
/* Read four bytes from the source buffer (to avoid unaligned accesses)
* and pack these into one 32-bit word (little endian).
*/
regval = (uint32_t)*buf++;
regval |= ((uint32_t)*buf++) << 8;
regval |= ((uint32_t)*buf++) << 16;
regval |= ((uint32_t)*buf++) << 24;
/* Then write the packed data to the TxFIFO */
stm32_putreg(regval, regaddr);
}
}
/****************************************************************************
* Name: stm32_epin_transfer
*
* Description:
* Start the Tx data transfer
*
****************************************************************************/
static void stm32_epin_transfer(FAR struct stm32_ep_s *privep,
FAR uint8_t *buf, int nbytes)
{
uint32_t pktcnt;
uint32_t regval;
/* Read the DIEPSIZx register */
regval = stm32_getreg(STM32_OTGFS_DIEPTSIZ(privep->epphy));
/* Clear the XFRSIZ, PKTCNT, and MCNT field of the DIEPSIZx register */
regval &= ~(OTGFS_DIEPTSIZ_XFRSIZ_MASK | OTGFS_DIEPTSIZ_PKTCNT_MASK |
OTGFS_DIEPTSIZ_MCNT_MASK);
/* Are we sending a zero length packet (ZLP) */
if (nbytes == 0)
{
/* Yes.. leave the transfer size at zero and set the packet count to 1 */
pktcnt = 1;
regval |= (1 << OTGFS_DIEPTSIZ_PKTCNT_SHIFT);
}
else
{
/* No.. Program the transfer size and packet count . First calculate:
*
* xfrsize = The total number of bytes to be sent.
* pktcnt = the number of packets (of maxpacket bytes) required to
* perform the transfer.
*/
pktcnt = ((uint32_t)nbytes + (privep->ep.maxpacket - 1)) / privep->ep.maxpacket;
}
/* Set the XFRSIZ and PKTCNT */
regval |= (pktcnt << OTGFS_DIEPTSIZ_PKTCNT_SHIFT);
regval |= ((uint32_t)nbytes << OTGFS_DIEPTSIZ_PKTCNT_SHIFT);
/* If this is an isconchronous endpoint, then set the multi-count field to
* the PKTCNT as well.
*/
if (privep->eptype == USB_EP_ATTR_XFER_ISOC)
{
regval |= (pktcnt << OTGFS_DIEPTSIZ_MCNT_SHIFT);
}
/* Save DIEPSIZx register value */
stm32_putreg(regval, STM32_OTGFS_DIEPTSIZ(privep->epphy));
/* Read the DIEPCTLx register */
regval = stm32_getreg(STM32_OTGFS_DIEPCTL(privep->epphy));
/* If this is an isochronous endpoint, then set the even/odd frame bit
* the DIEPCTLx register.
*/
if (privep->eptype == USB_EP_ATTR_XFER_ISOC)
{
/* Check bit 0 of the frame number of the received SOF and set the
* even/odd frame to match.
*/
uint32_t status = stm32_getreg(STM32_OTGFS_DSTS);
if ((status & OTGFS_DSTS_SOFFN0) == OTGFS_DSTS_SOFFN_EVEN)
{
regval |= OTGFS_DIEPCTL_SEVNFRM;
}
else
{
regval |= OTGFS_DIEPCTL_SODDFRM;
}
}
/* EP enable, IN data in FIFO */
regval &= ~OTGFS_DIEPCTL_EPDIS;
regval |= (OTGFS_DIEPCTL_SNAK | OTGFS_DIEPCTL_EPENA);
stm32_putreg(regval, STM32_OTGFS_DIEPCTL(privep->epphy));
/* Transfer the data to the TxFIFO. At this point, the caller has already
* assured that there is sufficient space in the TxFIFO to hold the transfer
* we can just blindly continue.
*/
stm32_txfifo_write(privep, buf, nbytes);
}
/****************************************************************************
* Name: stm32_epin_request
*
* Description:
* Begin or continue write request processing.
*
****************************************************************************/
static void stm32_epin_request(FAR struct stm32_usbdev_s *priv,
FAR struct stm32_ep_s *privep)
{
struct stm32_req_s *privreq;
uint32_t regaddr;
uint32_t regval;
uint8_t *buf;
int nbytes;
int nwords;
int bytesleft;
/* We get here when an IN endpoint or Tx FIFO empty interrupt occurs. So
* now we know that there is no TX transfer in progress.
*/
privep->active = false;
/* Check the request from the head of the endpoint request queue */
privreq = stm32_rqpeek(privep);
if (!privreq)
{
usbtrace(TRACE_DEVERROR(STM32_TRACEERR_EPINQEMPTY), privep->epphy);
/* There is no TX transfer in progress and no new pending TX
* requests to send. To stop transmitting any data on a particular
* IN endpoint, the application must set the IN NAK bit. To set this
* bit, the following field must be programmed.
*/
regaddr = STM32_OTGFS_DIEPCTL(privep->epphy);
regval = stm32_getreg(regaddr);
regval |= OTGFS_DIEPCTL_SNAK;
stm32_putreg(regval, regaddr);
/* The endpoint is no longer active */
privep->active = false;
}
ullvdbg("EP%d req=%p: len=%d xfrd=%d nullpkt=%d\n",
privep->epphy, privreq, privreq->req.len,
privreq->req.xfrd, privep->zlp);
/* Loop while there are still bytes to be transferred (or a zero-length-
* packet, ZLP, to be sent). The loop will also be terminated if there
* is insufficient space remaining in the TxFIFO to send a complete
* packet.
*/
while (privreq->req.xfrd < privreq->req.len || privep->zlp)
{
/* Get the number of bytes left to be sent in the request */
bytesleft = privreq->req.len - privreq->req.xfrd;
nbytes = bytesleft;
/* Limit the size of the transfer to one full packet and handle
* zero-length packets (ZLPs).
*/
if (nbytes > 0)
{
/* Either send the maxpacketsize or all of the remaining data in
* the request.
*/
privep->zlp = 0;
if (nbytes >= privep->ep.maxpacket)
{
nbytes = privep->ep.maxpacket;
/* Handle the case where this packet is exactly the
* maxpacketsize. Do we need to send a zero-length packet
* in this case?
*/
if (bytesleft == privep->ep.maxpacket &&
(privreq->req.flags & USBDEV_REQFLAGS_NULLPKT) != 0)
{
privep->zlp = 1;
}
}
}
/* Get the transfer size in 32-bit words */
nwords = (nbytes + 3) >> 2;
/* Get the number of 32-bit words available in the TxFIFO. The
* DXTFSTS indicates the amount of free space available in the
* endpoint TxFIFO. Values are in terms of 32-bit words:
*
* 0: Endpoint TxFIFO is full
* 1: 1 word available
* 2: 2 words available
* n: n words available
*/
regaddr = STM32_OTGFS_DTXFSTS(privep->epphy);
regval = stm32_getreg(regaddr);
/* And terminate the loop if there is insufficient space in the TxFIFO
* hold the entire packet.
*/
if ((regval & OTGFS_DTXFSTS_MASK) < nwords)
{
/* There is insufficent space in the TxFIFO. Wait for a TxFIFO
* empty interrupt and try again.
*/
uint32_t empmsk = stm32_getreg(STM32_OTGFS_DIEPEMPMSK);
empmsk |= OTGFS_DIEPEMPMSK(privep->epphy);
stm32_putreg(empmsk, STM32_OTGFS_DIEPEMPMSK);
break;
}
/* Transfer data to the TxFIFO */
buf = privreq->req.buf + privreq->req.xfrd;
stm32_epin_transfer(privep, buf, nbytes);
/* If it was not before, the OUT endpoint is now actively transferring
* data.
*/
privep->active = true;
/* EP0 is a special case */
if (privep->epphy == EP0)
{
priv->ep0state = EP0STATE_DATA_IN;
}
/* Update for the next time through the loop */
privreq->req.xfrd += nbytes;
}
/* Note that the ZLP, if any, must be sent as a separate transfer. The need
* for a ZLP is indicated by privep->zlp. If all of the bytes were sent
* (including any final null packet) then we are finished with the transfer
*/
if (privreq->req.xfrd >= privreq->req.len && !privep->zlp)
{
usbtrace(TRACE_COMPLETE(privep->epphy), privreq->req.xfrd);
stm32_req_complete(privep, OK);
privep->zlp = 0;
privep->active = false;
}
}
/*******************************************************************************
* Name: stm32_rxfifo_read
*
* Description:
* Read packet from the EP0 RxFIFO.
*
*******************************************************************************/
static void stm32_rxfifo_read(FAR struct stm32_ep_s *privep,
FAR uint8_t *dest, uint16_t len)
{
uint32_t regaddr;
int i;
/* Get the address of the endpoint FIFO */
regaddr = STM32_OTGFS_DFIFO_DEP(privep->epphy);
/* Read 32-bits and write 4 x 8-bits at time (to avoid unaligned accesses) */
for (i = 0; i < len; i += 4)
{
union
{
uint32_t w;
uint8_t b[4];
} data;
/* Read 1 x 32-bits of EP0 packet data */
data.w = stm32_getreg(regaddr);
/* Write 4 x 8-bits of EP0 packet data */
*dest++ = data.b[0];
*dest++ = data.b[1];
*dest++ = data.b[2];
*dest++ = data.b[3];
}
}
/*******************************************************************************
* Name: stm32_epout_complete
*
* Description:
* This function is called when an OUT transfer complete interrupt is
* received. It completes the read request at the head of the endpoint's
* request queue.
*
*******************************************************************************/
static void stm32_epout_complete(FAR struct stm32_usbdev_s *priv,
FAR struct stm32_ep_s *privep)
{
struct stm32_req_s *privreq;
/* Since a transfer just completed, there must be a read request at the head of
* the endpoint request queue.
*/
privreq = stm32_rqpeek(privep);
DEBUGASSERT(privreq);
if (!privreq)
{
/* An OUT transfer completed, but no packet to receive the data. This
* should not happen.
*/
usbtrace(TRACE_DEVERROR(STM32_TRACEERR_EPOUTQEMPTY), privep->epphy);
privep->active = false;
return;
}
ullvdbg("EP%d: len=%d xfrd=%d\n",
privep->epphy, privreq->req.len, privreq->req.xfrd);
/* Return the completed read request to the class driver and mark the state
* IDLE.
*/
usbtrace(TRACE_COMPLETE(privep->epphy), privreq->req.xfrd);
stm32_req_complete(privep, OK);
privep->active = false;
/* Now set up the next read request (if any) */
stm32_epout_request(priv, privep);
}
/*******************************************************************************
* Name: stm32_epout_receive
*
* Description:
* This function is called from the RXFLVL interrupt handler when new incoming
* data is available in the endpoint's RxFIFO. This function will simply
* copy the incoming data into pending requests data buffer.
*
*******************************************************************************/
static inline void stm32_epout_receive(FAR struct stm32_ep_s *privep, int bcnt)
{
struct stm32_req_s *privreq;
uint8_t *dest;
int buflen;
int readlen;
/* Get a reference to the request at the head of the endpoint's request queue */
privreq = stm32_rqpeek(privep);
DEBUGASSERT(privreq);
if (!privreq)
{
/* Incoming data is available in the RxFIFO, but there is no read setup
* to receive the receive the data. In this case, the endpoint should have
* been NAKing but apparently was not. The data is lost!
*/
usbtrace(TRACE_DEVERROR(STM32_TRACEERR_EPOUTQEMPTY), privep->epphy);
privep->active = false;
return;
}
ullvdbg("EP%d: len=%d xfrd=%d\n", privep->epphy, privreq->req.len, privreq->req.xfrd);
usbtrace(TRACE_READ(privep->epphy), bcnt);
/* Get the number of bytes to transfer from the RxFIFO */
buflen = privreq->req.len - privreq->req.xfrd;
DEBUGASSERT(buflen > 0 && buflen <= bcnt);
readlen = MIN(buflen, bcnt);
/* Get the destination of the data transfer */
dest = privreq->req.buf + privreq->req.xfrd;
/* Transfer the data from the RxFIFO to the request's data buffer */
stm32_rxfifo_read(privep, dest, readlen);
/* Update the number of bytes transferred */
privreq->req.xfrd += readlen;
}
/*******************************************************************************
* Name: stm32_epout_request
*
* Description:
* This function is called when either (1) new read request is received, or
* (2) a pending receive request completes. If there is no read in pending,
* then this function will initiate the next OUT (read) operation.
*
*******************************************************************************/
static void stm32_epout_request(FAR struct stm32_usbdev_s *priv,
FAR struct stm32_ep_s *privep)
{
struct stm32_req_s *privreq;
uint32_t regaddr;
uint32_t regval;
uint32_t xfrsize;
uint32_t pktcnt;
/* Make sure that there is not already a pending request request. If there is,
* just return, leaving the newly received request in the request queue.
*/
if (!privep->active)
{
/* Loop until a valid request is found (or the request queue is empty).
* The loop is only need to look at the request queue again is an invalid
* read request is encountered.
*/
for (;;)
{
/* Get a reference to the request at the head of the endpoint's request queue */
privreq = stm32_rqpeek(privep);
if (!privreq)
{
usbtrace(TRACE_DEVERROR(STM32_TRACEERR_EPOUTQEMPTY), privep->epphy);
/* There are no read requests to be setup. Configure the hardware to
* NAK any incoming packets. (This should already be the case. I
* think that the hardware will automatically NAK after a transfer is
* completed until SNAK is cleared).
*/
regaddr = STM32_OTGFS_DOEPCTL(privep->epphy);
regval = stm32_getreg(regaddr);
regval |= OTGFS_DOEPCTL_SNAK;
stm32_putreg(regval, regaddr);
/* This endpoint is no longer actively transferring */
privep->active = false;
return;
}
ullvdbg("EP%d: len=%d\n", privep->epphy, privreq->req.len);
/* Ignore any attempt to receive a zero length packet (this really
* should not happen.
*/
if (privreq->req.len <= 0)
{
usbtrace(TRACE_DEVERROR(STM32_TRACEERR_EPOUTNULLPACKET), 0);
stm32_req_complete(privep, OK);
}
/* Otherwise, we have a usable read request... break out of the loop */
else
{
break;
}
}
/* Setup the pending read into the request buffer. First calculate:
*
* pktcnt = the number of packets (of maxpacket bytes) required to
* perform the transfer.
* xfrsize = The total number of bytes required (in units of
* maxpacket bytes).
*/
pktcnt = (privreq->req.len + (privep->ep.maxpacket - 1)) / privep->ep.maxpacket;
xfrsize = pktcnt * privep->ep.maxpacket;
/* Then setup the hardware to perform this transfer */
regaddr = STM32_OTGFS_DOEPTSIZ(privep->epphy);
regval = stm32_getreg(regaddr);
regval &= ~(OTGFS_DOEPTSIZ_XFRSIZ_MASK | OTGFS_DOEPTSIZ_PKTCNT_MASK);
regval |= (xfrsize << OTGFS_DOEPTSIZ_XFRSIZ_SHIFT);
regval |= (pktcnt << OTGFS_DOEPTSIZ_PKTCNT_SHIFT);
stm32_putreg(regval, regaddr);
/* Then enable the transfer */
regaddr = STM32_OTGFS_DOEPCTL(privep->epphy);
regval = stm32_getreg(regaddr);
/* When an isochronous transfer is enabled the Even/Odd frame bit must
* also be set appropriately.
*/
#ifdef CONFIG_USBDEV_ISOCHRONOUS
if (privep->eptype == USB_EP_ATTR_XFER_ISOC)
{
if (privep->odd)
{
regval |= OTGFS_DOEPCTL_SODDFRM;
}
else
{
regval |= OTGFS_DOEPCTL_SEVNFRM;
}
}
#endif
/* Clearing NAKing and enable the transfer. */
regval |= (OTGFS_DOEPCTL_CNAK | OTGFS_DOEPCTL_EPENA);
stm32_putreg(regval, regaddr);
/* A transfer is now active on this endpoint */
privep->active = true;
/* EP0 is a special case. We need to know when to switch back to
* normal SETUP processing.
*/
if (privep->epphy == EP0)
{
priv->ep0state = EP0STATE_DATA_OUT;
}
}
}
/*******************************************************************************
* Name: stm32_ep_flush
*
* Description:
* Flush any primed descriptors from this ep
*
*******************************************************************************/
static void stm32_ep_flush(struct stm32_ep_s *privep)
{
if (privep->isin)
{
stm32_txfifo_flush(OTGFS_GRSTCTL_TXFNUM_D(privep->epphy));
}
else
{
stm32_rxfifo_flush();
}
}
/*******************************************************************************
* Name: stm32_req_complete
*
* Description:
* Handle termination of the request at the head of the endpoint request queue.
*
*******************************************************************************/
static void stm32_req_complete(struct stm32_ep_s *privep, int16_t result)
{
FAR struct stm32_req_s *privreq;
/* Remove the request at the head of the request list */
privreq = stm32_req_remfirst(privep);
DEBUGASSERT(privreq != NULL);
/* If endpoint 0, temporarily reflect the state of protocol stalled
* in the callback.
*/
bool stalled = privep->stalled;
if (privep->epphy == EP0)
{
privep->stalled = privep->dev->stalled;
}
/* Save the result in the request structure */
privreq->req.result = result;
/* Callback to the request completion handler */
privreq->req.callback(&privep->ep, &privreq->req);
/* Restore the stalled indication */
privep->stalled = stalled;
}
/*******************************************************************************
* Name: stm32_req_cancel
*
* Description:
* Cancel all pending requests for an endpoint
*
*******************************************************************************/
static void stm32_req_cancel(struct stm32_ep_s *privep, int16_t status)
{
if (!stm32_rqempty(privep))
{
stm32_ep_flush(privep);
}
while (!stm32_rqempty(privep))
{
usbtrace(TRACE_COMPLETE(privep->epphy),
(stm32_rqpeek(privep))->req.xfrd);
stm32_req_complete(privep, status);
}
}
/*******************************************************************************
* Name: stm32_ep_findbyaddr
*
* Description:
* Find the physical endpoint structure corresponding to a logic endpoint
* address
*
*******************************************************************************/
static struct stm32_ep_s *stm32_ep_findbyaddr(struct stm32_usbdev_s *priv,
uint16_t eplog)
{
struct stm32_ep_s *privep;
uint8_t epphy = USB_EPNO(eplog);
if (epphy >= STM32_NENDPOINTS)
{
return NULL;
}
/* Is this an IN or an OUT endpoint? */
if (USB_ISEPIN(eplog))
{
privep = &priv->epin[epphy];
}
else
{
privep = &priv->epout[epphy];
}
/* Verify that the endpoint has been configured */
if (!privep->configured)
{
return NULL;
}
/* Return endpoint reference */
DEBUGASSERT(privep->epphy == epphy);
return privep;
}
/*******************************************************************************
* Name: stm32_req_dispatch
*
* Description:
* Provide unhandled setup actions to the class driver. This is logically part
* of the USB interrupt handler.
*
*******************************************************************************/
static int stm32_req_dispatch(struct stm32_usbdev_s *priv,
const struct usb_ctrlreq_s *ctrl)
{
int ret = -EIO;
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_DISPATCH), 0);
if (priv->driver)
{
/* Forward to the control request to the class driver implementation */
ret = CLASS_SETUP(priv->driver, &priv->usbdev, ctrl);
}
if (ret < 0)
{
/* Stall on failure */
usbtrace(TRACE_DEVERROR(STM32_TRACEERR_DISPATCHSTALL), 0);
priv->stalled = true;
}
return ret;
}
/*******************************************************************************
* Name: stm32_usbreset
*
* Description:
* Reset Usb engine
*
*******************************************************************************/
static void stm32_usbreset(struct stm32_usbdev_s *priv)
{
FAR struct stm32_ep_s *privep;
uint32_t regval;
int i;
/* Clear the Remote Wake-up Signaling */
regval = stm32_getreg(STM32_OTGFS_DCTL);
regval &= ~OTGFS_DCTL_RWUSIG;
stm32_putreg(regval, STM32_OTGFS_DCTL);
/* Flush the EP0 Tx FIFO */
stm32_txfifo_flush(OTGFS_GRSTCTL_TXFNUM_D(EP0));
/* Tell the class driver that we are disconnected. The class
* driver should then accept any new configurations.
*/
if (priv->driver)
{
CLASS_DISCONNECT(priv->driver, &priv->usbdev);
}
priv->epavail = STM32_EP_AVAILABLE;
/* Disable all end point interrupts */
for (i = 0; i < STM32_NENDPOINTS ; i++)
{
/* Disable endpoint interrupts */
stm32_putreg(0xff, STM32_OTGFS_DIEPINT(i));
stm32_putreg(0xff, STM32_OTGFS_DOEPINT(i));
/* Return write requests to the class implementation */
privep = &priv->epin[i];
stm32_req_cancel(privep, -ESHUTDOWN);
/* Reset IN endpoint status */
privep->stalled = false;
/* Return read requests to the class implementation */
privep = &priv->epout[i];
stm32_req_cancel(privep, -ESHUTDOWN);
/* Reset endpoint status */
privep->stalled = false;
}
stm32_putreg(0xffffffff, STM32_OTGFS_DAINT);
/* Mask all device endpoint interrupts except EP0 */
regval = (OTGFS_DAINT_IEP(EP0) | OTGFS_DAINT_OEP(EP0));
stm32_putreg(regval, STM32_OTGFS_DAINTMSK);
/* Unmask OUT interrupts */
regval = (OTGFS_DOEPMSK_XFRCM | OTGFS_DOEPMSK_STUPM | OTGFS_DOEPMSK_EPDM);
stm32_putreg(regval, STM32_OTGFS_DOEPMSK);
/* Unmask IN interrupts */
regval = (OTGFS_DIEPMSK_XFRCM | OTGFS_DIEPMSK_EPDM | OTGFS_DIEPMSK_TOM);
stm32_putreg(regval, STM32_OTGFS_DIEPMSK);
/* Reset device address to 0 */
stm32_setaddress(priv, 0);
priv->devstate = DEVSTATE_DEFAULT;
/* Re-configure EP0 */
stm32_ep0_configure(priv);
/* Setup EP0 to receive SETUP packets */
stm32_ep0out_ctrlsetup(priv);
}
/*******************************************************************************
* Name: stm32_ep0out_testmode
*
* Description:
* Select test mode
*
*******************************************************************************/
static inline void stm32_ep0out_testmode(FAR struct stm32_usbdev_s *priv,
uint16_t index)
{
uint32_t regval;
uint8_t testmode;
regval = stm32_getreg(STM32_OTGFS_DCTL);
testmode = index >> 8;
switch (testmode)
{
case 1:
priv->testmode = OTGFS_TESTMODE_J;
break;
case 2:
priv->testmode = OTGFS_TESTMODE_K;
break;
case 3:
priv->testmode = OTGFS_TESTMODE_SE0_NAK;
break;
case 4:
priv->testmode = OTGFS_TESTMODE_PACKET;
break;
case 5:
priv->testmode = OTGFS_TESTMODE_FORCE;
break;
default:
usbtrace(TRACE_DEVERROR(STM32_TRACEERR_BADTESTMODE), testmode);
priv->dotest = false;
priv->testmode = OTGFS_TESTMODE_DISABLED;
priv->stalled = true;
}
priv->dotest = true;
stm32_ep0in_transmitzlp(priv);
}
/*******************************************************************************
* Name: stm32_ep0out_stdrequest
*
* Description:
* Handle a stanard request on EP0. Pick off the things of interest to the
* USB device controller driver; pass what is left to the class driver.
*
*******************************************************************************/
static inline void stm32_ep0out_stdrequest(struct stm32_usbdev_s *priv,
FAR struct stm32_ctrlreq_s *ctrlreq)
{
FAR struct stm32_ep_s *privep;
/* Handle standard request */
switch (ctrlreq->req)
{
case USB_REQ_GETSTATUS:
{
/* type: device-to-host; recipient = device, interface, endpoint
* value: 0
* index: zero interface endpoint
* len: 2; data = status
*/
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_GETSTATUS), 0);
if (!priv->addressed ||
ctrlreq->len != 2 ||
(ctrlreq->type & USB_REQ_DIR_IN) == 0 ||
ctrlreq->value != 0)
{
priv->stalled = true;
}
else
{
switch (ctrlreq->type & USB_REQ_RECIPIENT_MASK)
{
case USB_REQ_RECIPIENT_ENDPOINT:
{
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_EPGETSTATUS), 0);
privep = stm32_ep_findbyaddr(priv, ctrlreq->index);
if (!privep)
{
usbtrace(TRACE_DEVERROR(STM32_TRACEERR_BADEPGETSTATUS), 0);
priv->stalled = true;
}
else
{
if (privep->stalled)
{
priv->ep0resp[0] = (1 << USB_FEATURE_ENDPOINTHALT);
}
else
{
priv->ep0resp[0] = 0; /* Not stalled */
}
priv->ep0resp[1] = 0;
stm32_ep0in_setupresponse(priv, priv->ep0resp, 2);
}
}
break;
case USB_REQ_RECIPIENT_DEVICE:
{
if (ctrlreq->index == 0)
{
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_DEVGETSTATUS), 0);
/* Features: Remote Wakeup and selfpowered */
priv->ep0resp[0] = (priv->selfpowered << USB_FEATURE_SELFPOWERED);
priv->ep0resp[0] |= (priv->wakeup << USB_FEATURE_REMOTEWAKEUP);
priv->ep0resp[1] = 0;
stm32_ep0in_setupresponse(priv, priv->ep0resp, 2);
}
else
{
usbtrace(TRACE_DEVERROR(STM32_TRACEERR_BADDEVGETSTATUS), 0);
priv->stalled = true;
}
}
break;
case USB_REQ_RECIPIENT_INTERFACE:
{
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_IFGETSTATUS), 0);
priv->ep0resp[0] = 0;
priv->ep0resp[1] = 0;
stm32_ep0in_setupresponse(priv, priv->ep0resp, 2);
}
break;
default:
{
usbtrace(TRACE_DEVERROR(STM32_TRACEERR_BADGETSTATUS), 0);
priv->stalled = true;
}
break;
}
}
}
break;
case USB_REQ_CLEARFEATURE:
{
/* type: host-to-device; recipient = device, interface or endpoint
* value: feature selector
* index: zero interface endpoint;
* len: zero, data = none
*/
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_CLEARFEATURE), 0);
if (priv->addressed != 0 && ctrlreq->len == 0)
{
uint8_t recipient = ctrlreq->type & USB_REQ_RECIPIENT_MASK;
if (recipient == USB_REQ_RECIPIENT_ENDPOINT &&
ctrlreq->value == USB_FEATURE_ENDPOINTHALT &&
(privep = stm32_ep_findbyaddr(priv, ctrlreq->index)) != NULL)
{
stm32_ep_clrstall(privep);
stm32_ep0in_transmitzlp(priv);
}
else if (recipient == USB_REQ_RECIPIENT_DEVICE &&
ctrlreq->value == USB_FEATURE_REMOTEWAKEUP)
{
priv->wakeup = 0;
stm32_ep0in_transmitzlp(priv);
}
else
{
/* Actually, I think we could just stall here. */
(void)stm32_req_dispatch(priv, &priv->ctrlreq);
}
}
else
{
usbtrace(TRACE_DEVERROR(STM32_TRACEERR_BADCLEARFEATURE), 0);
priv->stalled = true;
}
}
break;
case USB_REQ_SETFEATURE:
{
/* type: host-to-device; recipient = device, interface, endpoint
* value: feature selector
* index: zero interface endpoint;
* len: 0; data = none
*/
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_SETFEATURE), 0);
if (priv->addressed != 0 && ctrlreq->len == 0)
{
uint8_t recipient = ctrlreq->type & USB_REQ_RECIPIENT_MASK;
if (recipient == USB_REQ_RECIPIENT_ENDPOINT &&
ctrlreq->value == USB_FEATURE_ENDPOINTHALT &&
(privep = stm32_ep_findbyaddr(priv, ctrlreq->index)) != NULL)
{
stm32_ep_setstall(privep);
stm32_ep0in_transmitzlp(priv);
}
else if (recipient == USB_REQ_RECIPIENT_DEVICE &&
ctrlreq->value == USB_FEATURE_REMOTEWAKEUP)
{
priv->wakeup = 1;
stm32_ep0in_transmitzlp(priv);
}
else if (recipient == USB_REQ_RECIPIENT_DEVICE &&
ctrlreq->value == USB_FEATURE_TESTMODE &&
((ctrlreq->index & 0xff) == 0))
{
stm32_ep0out_testmode(priv, ctrlreq->index);
}
else if (priv->configured)
{
/* Actually, I think we could just stall here. */
(void)stm32_req_dispatch(priv, &priv->ctrlreq);
}
else
{
usbtrace(TRACE_DEVERROR(STM32_TRACEERR_BADSETFEATURE), 0);
priv->stalled = true;
}
}
else
{
usbtrace(TRACE_DEVERROR(STM32_TRACEERR_BADSETFEATURE), 0);
priv->stalled = true;
}
}
break;
case USB_REQ_SETADDRESS:
{
/* type: host-to-device; recipient = device
* value: device address
* index: 0
* len: 0; data = none
*/
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_SETADDRESS), ctrlreq->value);
if ((ctrlreq->type &USB_REQ_RECIPIENT_MASK) == USB_REQ_RECIPIENT_DEVICE &&
ctrlreq->index == 0 &&
ctrlreq->len == 0 &&
ctrlreq->value < 128 &&
priv->devstate != DEVSTATE_CONFIGURED)
{
/* Save the address. We cannot actually change to the next address until
* the completion of the status phase.
*/
stm32_setaddress(priv, (uint16_t)priv->ctrlreq.value[0]);
stm32_ep0in_transmitzlp(priv);
}
else
{
usbtrace(TRACE_DEVERROR(STM32_TRACEERR_BADSETADDRESS), 0);
priv->stalled = true;
}
}
break;
case USB_REQ_GETDESCRIPTOR:
/* type: device-to-host; recipient = device
* value: descriptor type and index
* index: 0 or language ID;
* len: descriptor len; data = descriptor
*/
case USB_REQ_SETDESCRIPTOR:
/* type: host-to-device; recipient = device
* value: descriptor type and index
* index: 0 or language ID;
* len: descriptor len; data = descriptor
*/
{
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_GETSETDESC), 0);
if ((ctrlreq->type & USB_REQ_RECIPIENT_MASK) == USB_REQ_RECIPIENT_DEVICE)
{
(void)stm32_req_dispatch(priv, &priv->ctrlreq);
}
else
{
usbtrace(TRACE_DEVERROR(STM32_TRACEERR_BADGETSETDESC), 0);
priv->stalled = true;
}
}
break;
case USB_REQ_GETCONFIGURATION:
/* type: device-to-host; recipient = device
* value: 0;
* index: 0;
* len: 1; data = configuration value
*/
{
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_GETCONFIG), 0);
if (priv->addressed &&
(ctrlreq->type & USB_REQ_RECIPIENT_MASK) == USB_REQ_RECIPIENT_DEVICE &&
ctrlreq->value == 0 &&
ctrlreq->index == 0 &&
ctrlreq->len == 1)
{
(void)stm32_req_dispatch(priv, &priv->ctrlreq);
}
else
{
usbtrace(TRACE_DEVERROR(STM32_TRACEERR_BADGETCONFIG), 0);
priv->stalled = true;
}
}
break;
case USB_REQ_SETCONFIGURATION:
/* type: host-to-device; recipient = device
* value: configuration value
* index: 0;
* len: 0; data = none
*/
{
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_SETCONFIG), 0);
if (priv->addressed &&
(ctrlreq->type & USB_REQ_RECIPIENT_MASK) == USB_REQ_RECIPIENT_DEVICE &&
ctrlreq->index == 0 &&
ctrlreq->len == 0)
{
/* Give the configuration to the class driver */
int ret = stm32_req_dispatch(priv, &priv->ctrlreq);
/* If the class driver accepted the configuration, then mark the
* device state as configured (or not, depending on the
* configuration).
*/
if (ret == OK)
{
uint8_t cfg = (uint8_t)ctrlreq->value;
if (cfg != 0)
{
priv->devstate = DEVSTATE_CONFIGURED;
priv->configured = true;
}
else
{
priv->devstate = DEVSTATE_ADDRESSED;
priv->configured = false;
}
}
}
else
{
usbtrace(TRACE_DEVERROR(STM32_TRACEERR_BADSETCONFIG), 0);
priv->stalled = true;
}
}
break;
case USB_REQ_GETINTERFACE:
/* type: device-to-host; recipient = interface
* value: 0
* index: interface;
* len: 1; data = alt interface
*/
case USB_REQ_SETINTERFACE:
/* type: host-to-device; recipient = interface
* value: alternate setting
* index: interface;
* len: 0; data = none
*/
{
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_GETSETIF), 0);
(void)stm32_req_dispatch(priv, &priv->ctrlreq);
}
break;
case USB_REQ_SYNCHFRAME:
/* type: device-to-host; recipient = endpoint
* value: 0
* index: endpoint;
* len: 2; data = frame number
*/
{
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_SYNCHFRAME), 0);
}
break;
default:
{
usbtrace(TRACE_DEVERROR(STM32_TRACEERR_INVALIDCTRLREQ), 0);
priv->stalled = true;
}
break;
}
}
/*******************************************************************************
* Name: stm32_ep0out_setup
*
* Description:
* USB Ctrl EP Setup Event. This is logically part of the USB interrupt
* handler. This event occurs when a setup packet is receive on EP0 OUT.
*
*******************************************************************************/
static inline void stm32_ep0out_setup(struct stm32_usbdev_s *priv)
{
struct stm32_ctrlreq_s ctrlreq;
/* Verify that a SETUP was received */
if (!priv->setup)
{
usbtrace(TRACE_DEVERROR(STM32_TRACEERR_EP0NOSETUP), priv->ep0state);
return;
}
/* Terminate any pending requests - since all DTDs will have been retired
* because of the setup packet.
*/
stm32_req_cancel(&priv->epout[EP0], -EPROTO);
stm32_req_cancel(&priv->epin[EP0], -EPROTO);
/* Assume NOT stalled */
priv->epout[EP0].stalled = false;
priv->epin[EP0].stalled = false;
priv->stalled = false;
/* Starting a control request - update state */
priv->ep0state = EP0STATE_SETUP;
/* And extract the little-endian 16-bit values to host order */
ctrlreq.type = priv->ctrlreq.type;
ctrlreq.req = priv->ctrlreq.req;
ctrlreq.value = GETUINT16(priv->ctrlreq.value);
ctrlreq.index = GETUINT16(priv->ctrlreq.index);
ctrlreq.len = GETUINT16(priv->ctrlreq.len);
ullvdbg("type=%02x req=%02x value=%04x index=%04x len=%04x\n",
ctrlreq.type, ctrlreq.req, ctrlreq.value, ctrlreq.index, ctrlreq.len);
/* Check for a standard request */
if ((ctrlreq.type & USB_REQ_TYPE_MASK) != USB_REQ_TYPE_STANDARD)
{
/* Dispatch any non-standard requests */
(void)stm32_req_dispatch(priv, &priv->ctrlreq);
}
else
{
/* Handle standard requests. */
stm32_ep0out_stdrequest(priv, &ctrlreq);
}
/* Check if the setup processing resulted in a STALL */
if (priv->stalled)
{
usbtrace(TRACE_DEVERROR(STM32_TRACEERR_EP0SETUPSTALLED), priv->ep0state);
stm32_ep0_stall(priv);
}
/* The SETUP data has been processed */
priv->setup = false;
}
/*******************************************************************************
* Name: stm32_epout
*
* Description:
* This is part of the OUT endpoint interrupt processing. This function
* handles the OUT event for a single endpoint.
*
*******************************************************************************/
static inline void stm32_epout(FAR struct stm32_usbdev_s *priv, uint8_t epno)
{
FAR struct stm32_ep_s *privep;
/* Endpoint 0 is a special case. */
if (epno == 0)
{
privep = &priv->epout[EP0];
/* In the EP0STATE_DATA_OUT state, we are receiving data into the
* request buffer. In that case, we must continue the request
* processing.
*/
if (priv->ep0state == EP0STATE_DATA_OUT)
{
/* Continue processing data from the EP0 OUT request queue */
stm32_epout_complete(priv, privep);
}
/* If we are not actively processing an OUT request, then we
* need to setup to receive the next control request.
*/
if (!privep->active)
{
stm32_ep0out_ctrlsetup(priv);
priv->ep0state = EP0STATE_IDLE;
}
}
/* For other endpoints, the only possibility is that we are continuing
* or finishing an OUT request.
*/
else if (priv->devstate == DEVSTATE_CONFIGURED)
{
stm32_epout_complete(priv, &priv->epout[epno]);
}
}
/*******************************************************************************
* Name: stm32_epout_interrupt
*
* Description:
* USB OUT endpoint interrupt handler. The core generates this interrupt when
* there is an interrupt is pending on one of the OUT endpoints of the core.
* The driver must read the OTGFS DAINT register to determine the exact number
* of the OUT endpoint on which the interrupt occurred, and then read the
* corresponding OTGFS DOEPINTx register to determine the exact cause of the
* interrupt.
*
*******************************************************************************/
static inline void stm32_epout_interrupt(FAR struct stm32_usbdev_s *priv)
{
uint32_t daint;
uint32_t regval;
uint32_t doepint;
int epno;
/* Get the pending, enabled interrupts for the OUT endpoint from the endpoint
* interrupt status register.
*/
regval = stm32_getreg(STM32_OTGFS_DAINT);
regval &= stm32_getreg(STM32_OTGFS_DAINTMSK);
daint = (regval & OTGFS_DAINT_OEP_MASK) >> OTGFS_DAINT_OEP_SHIFT;
/* Process each pending IN endpoint interrupt */
epno = 0;
while (daint)
{
/* Is an OUT interrupt pending for this endpoint? */
if ((daint & 1) != 0)
{
/* Yes.. get the OUT endpoint interrupt status */
doepint = stm32_getreg(STM32_OTGFS_DOEPINT(epno));
doepint &= stm32_getreg(STM32_OTGFS_DOEPMSK);
/* Transfer completed interrupt. This interrupt is trigged when
* stm32_rxinterrupt() removes the last packet data from the RxFIFO.
* In this case, core internally sets the NAK bit for this endpoint to
* prevent it from receiving any more packets.
*/
if ((doepint & OTGFS_DOEPINT_XFRC) != 0)
{
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_EPOUT_XFRC), (uint16_t)diepint);
/* Clear the bit in DOEPINTn for this interrupt */
stm32_putreg(OTGFS_DOEPINT_XFRC, STM32_OTGFS_DOEPINT(epno));
/* Handle the RX transer data ready event */
stm32_epout(priv, epno);
}
/* Endpoint disabled interrupt (ignored because this interrrupt is
* used in polled mode by the endpoint disable logic).
*/
#if 0
if ((doepint & OTGFS_DOEPINT_EPDISD) != 0)
{
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_EPOUT_EPDISD), (uint16_t)diepint);
/* Clear the bit in DOEPINTn for this interrupt */
stm32_putreg(OTGFS_DOEPINT_EPDISD, STM32_OTGFS_DOEPINT(epno));
}
#endif
/* Setup Phase Done (control EPs) */
if ((doepint & OTGFS_DOEPINT_SETUP) != 0)
{
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_EPOUT_SETUP), (uint16_t)diepint);
/* Handle the receipt of the SETUP packet */
stm32_ep0out_setup(priv);
stm32_putreg(OTGFS_DOEPINT_SETUP, STM32_OTGFS_DOEPINT(epno));
}
}
epno++;
daint >>= 1;
}
}
/*******************************************************************************
* Name: stm32_epin_runtestmode
*
* Description:
* Execute the test mode setup by the SET FEATURE request
*
*******************************************************************************/
static inline void stm32_epin_runtestmode(FAR struct stm32_usbdev_s *priv)
{
uint32_t regval = stm32_getreg(STM32_OTGFS_DCTL);
regval &= OTGFS_DCTL_TCTL_MASK;
regval |= (uint32_t)priv->testmode << OTGFS_DCTL_TCTL_SHIFT;
stm32_putreg(regval , STM32_OTGFS_DCTL);
priv->dotest = 0;
priv->testmode = OTGFS_TESTMODE_DISABLED;
}
/*******************************************************************************
* Name: stm32_epin
*
* Description:
* This is part of the IN endpoint interrupt processing. This function
* handles the IN event for a single endpoint.
*
*******************************************************************************/
static inline void stm32_epin(FAR struct stm32_usbdev_s *priv, uint8_t epno)
{
FAR struct stm32_ep_s *privep = &priv->epin[epno];
/* Endpoint 0 is a special case. */
if (epno == 0)
{
/* In the EP0STATE_DATA_IN state, we are sending data from request
* buffer. In that case, we must continue the request processing.
*/
if (priv->ep0state == EP0STATE_DATA_IN)
{
/* Continue processing data from the EP0 OUT request queue */
stm32_epin_request(priv, privep);
}
/* If we are not actively processing an OUT request, then we
* need to setup to receive the next control request.
*/
if (!privep->active)
{
stm32_ep0out_ctrlsetup(priv);
priv->ep0state = EP0STATE_IDLE;
}
/* Test mode is another special case */
if (priv->dotest)
{
stm32_epin_runtestmode(priv);
}
}
/* For other endpoints, the only possibility is that we are continuing
* or finishing an IN request.
*/
else if (priv->devstate == DEVSTATE_CONFIGURED)
{
/* Continue processing data from the EP0 OUT request queue */
stm32_epin_request(priv, privep);
}
}
/****************************************************************************
* Name: stm32_epin_txfifoempty
*
* Description:
* TxFIFO empty interrupt handling
*
****************************************************************************/
static inline void stm32_epin_txfifoempty(FAR struct stm32_usbdev_s *priv, int epno)
{
FAR struct stm32_ep_s *privep = &priv->epin[epno];
/* Continue processing the write request queue. This may mean sending
* more dat from the exisiting request or terminating the current requests
* and (perhaps) starting the IN transfer from the next write request.
*/
stm32_epin_request(priv, privep);
}
/*******************************************************************************
* Name: stm32_epin_interrupt
*
* Description:
* USB IN endpoint interrupt handler. The core generates this interrupt when
* an interrupt is pending on one of the IN endpoints of the core. The driver
* must read the OTGFS DAINT register to determine the exact number of the IN
* endpoint on which the interrupt occurred, and then read the corresponding
* OTGFS DIEPINTx register to determine the exact cause of the interrupt.
*
*******************************************************************************/
static inline void stm32_epin_interrupt(FAR struct stm32_usbdev_s *priv)
{
uint32_t diepint;
uint32_t daint;
uint32_t mask;
uint32_t empty;
int epno;
/* Get the pending, enabled interrupts for the IN endpoint from the endpoint
* interrupt status register.
*/
daint = stm32_getreg(STM32_OTGFS_DAINT);
daint &= stm32_getreg(STM32_OTGFS_DAINTMSK);
daint &= OTGFS_DAINT_IEP_MASK;
/* Process each pending IN endpoint interrupt */
epno = 0;
while (daint)
{
/* Is an IN interrupt pending for this endpoint? */
if ((daint & 1) != 0)
{
/* Get IN interrupt mask register. Bits 0-6 correspond to enabled
* interrupts as will be found in the DIEPINT interrupt status
* register.
*/
mask = stm32_getreg(STM32_OTGFS_DIEPMSK);
/* Check for FIFO not empty. Bits n corresponds to endpoint n.
* That condition corresponds to bit 7 of the DIEPINT interrupt
* status register.
*/
empty = stm32_getreg(STM32_OTGFS_DIEPEMPMSK);
if ((empty & OTGFS_DIEPEMPMSK(epno)) != 0)
{
mask |= OTGFS_DIEPINT_TXFE;
}
/* Now, read the interrupt status and mask out all disabled
* interrupts.
*/
diepint = stm32_getreg(STM32_OTGFS_DIEPINT(epno)) & mask;
/* Decode and process the enabled, pending interrupts */
/* Transfer completed interrupt */
if ((diepint & OTGFS_DIEPINT_XFRC) != 0)
{
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_EPIN_XFRC), (uint16_t)diepint);
empty &= ~OTGFS_DIEPEMPMSK(epno);
stm32_putreg(empty, STM32_OTGFS_DIEPEMPMSK);
stm32_putreg(OTGFS_DIEPINT_XFRC, STM32_OTGFS_DIEPINT(epno));
/* IN complete */
stm32_epin(priv, epno);
}
/* Timeout condition */
if ((diepint & OTGFS_DIEPINT_TOC) != 0)
{
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_EPIN_TOC), (uint16_t)diepint);
stm32_putreg(OTGFS_DIEPINT_TOC, STM32_OTGFS_DIEPINT(epno));
}
/* IN token received when TxFIFO is empty. Applies to non-periodic IN
* endpoints only. This interrupt indicates that an IN token was received
* when the associated TxFIFO (periodic/non-periodic) was empty. This
* interrupt is asserted on the endpoint for which the IN token was
* received.
*/
if ((diepint & OTGFS_DIEPINT_ITTXFE) != 0)
{
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_EPIN_ITTXFE), (uint16_t)diepint);
stm32_epin_request(priv, &priv->epin[epno]);
stm32_putreg(OTGFS_DIEPINT_ITTXFE, STM32_OTGFS_DIEPINT(epno));
}
/* IN endpoint NAK effective (ignored as this used only in polled
* mode)
*/
#if 0
if ((diepint & OTGFS_DIEPINT_INEPNE) != 0)
{
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_EPIN_INEPNE), (uint16_t)diepint);
stm32_putreg(OTGFS_DIEPINT_INEPNE, STM32_OTGFS_DIEPINT(epno));
}
#endif
/* Endpoint disabled interrupt (ignored as this used only in polled
* mode)
*/
#if 0
if ((diepint & OTGFS_DIEPINT_EPDISD) != 0)
{
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_EPIN_EPDISD), (uint16_t)diepint);
stm32_putreg(OTGFS_DIEPINT_EPDISD, STM32_OTGFS_DIEPINT(epno));
}
#endif
/* Transmit FIFO empty */
if ((diepint & OTGFS_DIEPINT_TXFE) != 0)
{
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_EPIN_TXFE), (uint16_t)diepint);
stm32_epin_txfifoempty(priv, epno);
stm32_putreg(OTGFS_DIEPINT_TXFE, STM32_OTGFS_DIEPINT(epno));
}
}
epno++;
daint >>= 1;
}
}
/*******************************************************************************
* Name: stm32_resumeinterrupt
*
* Description:
* Resume/remote wakeup detected interrupt
*
*******************************************************************************/
static inline void stm32_resumeinterrupt(FAR struct stm32_usbdev_s *priv)
{
uint32_t regval;
/* Restart the PHY clock and un-gate USB core clock (HCLK) */
#ifdef CONFIG_USBDEV_LOWPOWER
regval = stm32_getreg(STM32_OTGFS_PCGCCTL);
regval &= ~(OTGFS_PCGCCTL_STPPCLK | OTGFS_PCGCCTL_GATEHCLK);
stm32_putreg(regval, STM32_OTGFS_PCGCCTL);
#endif
/* Clear remote wake-up signaling */
regval = stm32_getreg(STM32_OTGFS_DCTL);
regval &= ~OTGFS_DCTL_RWUSIG;
stm32_putreg(regval, STM32_OTGFS_DCTL);
/* Restore full power -- whatever that means for this particular board */
stm32_usbsuspend((struct usbdev_s *)priv, true);
}
/*******************************************************************************
* Name: stm32_suspendinterrupt
*
* Description:
* USB suspend interrupt
*
*******************************************************************************/
static inline void stm32_suspendinterrupt(FAR struct stm32_usbdev_s *priv)
{
#ifdef CONFIG_USBDEV_LOWPOWER
uint32_t regval;
/* OTGFS_DSTS_SUSPSTS is set as long as the suspend condition is detected
* on USB. Check if we are still have the suspend condition, that we are
* connected to the host, and that we have been configured.
*/
regval = stm32_getreg(STM32_OTGFS_DSTS);
if ((regval & OTGFS_DSTS_SUSPSTS) != 0 &&
priv->connected &&
devstate == DEVSTATE_CONFIGURED)
{
/* Switch off OTG FS clocking. Setting OTGFS_PCGCCTL_STPPCLK stops the
* PHY clock.
*/
regval = stm32_getreg(STM32_OTGFS_PCGCCTL);
regval |= OTGFS_PCGCCTL_STPPCLK;
stm32_putreg(regval, STM32_OTGFS_PCGCCTL);
/* Setting OTGFS_PCGCCTL_GATEHCLK gate HCLK to modules other than
* the AHB Slave and Master and wakeup logic.
*/
regval |= OTGFS_PCGCCTL_GATEHCLK;
stm32_putreg(regval, STM32_OTGFS_PCGCCTL);
}
#endif
/* Let the board-specific logic know that we have entered the suspend
* state
*/
stm32_usbsuspend((FAR struct usbdev_s *)priv, false);
}
/*******************************************************************************
* Name: stm32_rxinterrupt
*
* Description:
* RxFIFO non-empty interrupt. This interrupt indicates that there is at
* least one packet pending to be read from the RxFIFO.
*
*******************************************************************************/
static inline void stm32_rxinterrupt(FAR struct stm32_usbdev_s *priv)
{
FAR struct stm32_ep_s *privep;
uint32_t regval;
int bcnt;
int epphy;
/* Disable the Rx status queue level interrupt */
regval = stm32_getreg(STM32_OTGFS_GINTMSK);
regval &= ~OTGFS_GINT_RXFLVL;
stm32_putreg(regval, STM32_OTGFS_GINTMSK);
/* Get the status from the top of the FIFO */
regval = stm32_getreg(STM32_OTGFS_GRXSTSP);
/* Decode status fields */
epphy = (regval & OTGFS_GRXSTSD_EPNUM_MASK) >> OTGFS_GRXSTSD_EPNUM_SHIFT;
privep = &priv->epout[epphy];
/* Handle the RX event according to the packet status field */
switch (regval & OTGFS_GRXSTSD_PKTSTS_MASK)
{
/* Global OUT NAK. This indicate that the global OUT NAK bit has taken
* effect.
*
* PKTSTS = Global OUT NAK, BCNT = 0, EPNUM = Don�t Care, DPID = Don�t
* Care.
*/
case OTGFS_GRXSTSD_PKTSTS_OUTNAK:
{
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_OUTNAK), 0);
}
break;
/* OUT data packet received.
*
* PKTSTS = DataOUT, BCNT = size of the received data OUT packet,
* EPNUM = EPNUM on which the packet was received, DPID = Actual Data PID.
*/
case OTGFS_GRXSTSD_PKTSTS_OUTRECVD:
{
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_OUTRECVD), epphy);
bcnt = (regval & OTGFS_GRXSTSD_BCNT_MASK) >> OTGFS_GRXSTSD_BCNT_SHIFT;
if (bcnt > 0)
{
stm32_epout_receive(privep, bcnt);
}
}
break;
/* OUT transfer completed. This indicates that an OUT data transfer for
* the specified OUT endpoint has completed. After this entry is popped
* from the receive FIFO, the core asserts a Transfer Completed interrupt
* on the specified OUT endpoint.
*
* PKTSTS = Data OUT Transfer Done, BCNT = 0, EPNUM = OUT EP Num on
* which the data transfer is complete, DPID = Don�t Care.
*/
case OTGFS_GRXSTSD_PKTSTS_OUTDONE:
{
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_OUTDONE), epphy);
}
break;
/* SETUP transaction completed. This indicates that the Setup stage for
* the specified endpoint has completed and the Data stage has started.
* After this entry is popped from the receive FIFO, the core asserts a
* Setup interrupt on the specified control OUT endpoint (triggers an
* interrupt).
*
* PKTSTS = Setup Stage Done, BCNT = 0, EPNUM = Control EP Num,
* DPID = Don�t Care.
*/
case OTGFS_GRXSTSD_PKTSTS_SETUPDONE:
{
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_SETUPDONE), epphy);
}
break;
/* SETUP data packet received. This indicates that a SETUP packet for the
* specified endpoint is now available for reading from the receive FIFO.
*
* PKTSTS = SETUP, BCNT = 8, EPNUM = Control EP Num, DPID = D0.
*/
case OTGFS_GRXSTSD_PKTSTS_SETUPRECVD:
{
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_SETUPRECVD), epphy);
/* Read EP0 setup data. NOTE: If multipe SETUP packets are received,
* the last one overwrites the previous setup packets and only that
* last SETUP packet will be processed.
*/
stm32_rxfifo_read(&priv->epout[EP0], (FAR uint8_t*)&priv->ctrlreq,
USB_SIZEOF_CTRLREQ);
/* The SETUP data has been processed */
priv->setup = false;
}
break;
default:
{
usbtrace(TRACE_DEVERROR(STM32_TRACEERR_INVALIDPARMS),
(regval & OTGFS_GRXSTSD_PKTSTS_MASK) >> OTGFS_GRXSTSD_PKTSTS_SHIFT);
}
break;
}
/* Enable the Rx Status Queue Level interrupt */
regval = stm32_getreg(STM32_OTGFS_GINTMSK);
regval |= OTGFS_GINT_RXFLVL;
stm32_putreg(regval, STM32_OTGFS_GINTMSK);
}
/*******************************************************************************
* Name: stm32_enuminterrupt
*
* Description:
* Enumeration done interrupt
*
*******************************************************************************/
static inline void stm32_enuminterrupt(FAR struct stm32_usbdev_s *priv)
{
uint32_t regval;
/* Activate EP0 */
stm32_ep0in_activate();
/* Set USB turn-around time for the full speed device with internal PHY interface. */
regval = stm32_getreg(STM32_OTGFS_GUSBCFG);
regval &= ~OTGFS_GUSBCFG_TRDT_MASK;
regval |= OTGFS_GUSBCFG_TRDT(5);
stm32_putreg(regval, STM32_OTGFS_GUSBCFG);
}
/*******************************************************************************
* Name: stm32_isocininterrupt
*
* Description:
* Incomplete isochronous IN transfer interrupt. Assertion of the incomplete
* isochronous IN transfer interrupt indicates an incomplete isochronous IN
* transfer on at least one of the isochronous IN endpoints.
*
*******************************************************************************/
#ifdef CONFIG_USBDEV_ISOCHRONOUS
static inline void stm32_isocininterrupt(FAR struct stm32_usbdev_s *priv)
{
int i;
/* The application must read the endpoint control register for all isochronous
* IN endpoints to detect endpoints with incomplete IN data transfers.
*/
for (i = 0; i < STM32_NENDPOINTS; i++)
{
/* Is this an isochronous IN endpoint? */
privep = &priv->epin[i];
if (privep->eptype != USB_EP_ATTR_XFER_ISOC)
{
/* No... keep looking */
continue;
}
/* Is there an active read request on the isochronous OUT endpoint? */
if (!privep->active)
{
/* No.. the endpoint is not actively transmitting data */
continue;
}
/* Check if this is the endpoint that had the incomplete transfer */
regaddr = STM32_OTGFS_DIEPCTL(privep->epphy);
doepctl = stm32_getreg(regaddr);
dsts = stm32_getreg(STM32_OTGFS_DSTS);
/* EONUM = 0:even frame, 1:odd frame
* SOFFN = Frame number of the received SOF
*/
eonum = ((doepctl & OTGFS_DIEPCTL_EONUM) != 0);
soffn = ((dsts & OTGFS_DSTS_SOFFN0) != 0);
if (eonum != soffn)
{
/* Not this endpoint */
continue;
}
/* For isochronous IN endpoints with incomplete transfers,
* the application must discard the data in the memory and
* disable the endpoint.
*/
stm32_req_complete(privep, -EIO);
stm32_epin_disable(privep);
break;
}
}
#endif
/*******************************************************************************
* Name: stm32_isocoutinterrupt
*
* Description:
* Incomplete periodic transfer interrupt
*
*******************************************************************************/
#ifdef CONFIG_USBDEV_ISOCHRONOUS
static inline void stm32_isocoutinterrupt(FAR struct stm32_usbdev_s *priv)
{
FAR struct stm32_ep_s *privep;
FAR struct stm32_req_s *privreq;
uint32_t regaddr;
uint32_t doepctl;
uint32_t dsts;
bool eonum;
bool soffn;
/* When it receives an IISOOXFR interrupt, the application must read the
* control registers of all isochronous OUT endpoints to determine which
* endpoints had an incomplete transfer in the current microframe. An
* endpoint transfer is incomplete if both the following conditions are true:
*
* DOEPCTLx:EONUM = DSTS:SOFFN[0], and
* DOEPCTLx:EPENA = 1
*/
for (i = 0; i < STM32_NENDPOINTS; i++)
{
/* Is this an isochronous OUT endpoint? */
privep = &priv->epout[i];
if (privep->eptype != USB_EP_ATTR_XFER_ISOC)
{
/* No... keep looking */
continue;
}
/* Is there an active read request on the isochronous OUT endpoint? */
if (!privep->active)
{
/* No.. the endpoint is not actively transmitting data */
continue;
}
/* Check if this is the endpoint that had the incomplete transfer */
regaddr = STM32_OTGFS_DOEPCTL(privep->epphy);
doepctl = stm32_getreg(regaddr);
dsts = stm32_getreg(STM32_OTGFS_DSTS);
/* EONUM = 0:even frame, 1:odd frame
* SOFFN = Frame number of the received SOF
*/
eonum = ((doepctl & OTGFS_DOEPCTL_EONUM) != 0);
soffn = ((dsts & OTGFS_DSTS_SOFFN0) != 0);
if (eonum != soffn)
{
/* Not this endpoint */
continue;
}
/* For isochronous OUT endpoints with incomplete transfers,
* the application must discard the data in the memory and
* disable the endpoint.
*/
stm32_req_complete(privep, -EIO);
stm32_epout_disable(privep);
break;
}
}
#endif
/*******************************************************************************
* Name: stm32_sessioninterrupt
*
* Description:
* Session request/new session detected interrupt
*
*******************************************************************************/
#ifdef CONFIG_USBDEV_VBUSSENSING
static inline void stm32_sessioninterrupt(FAR struct stm32_usbdev_s *priv)
{
#warning "Missing logic"
}
#endif
/*******************************************************************************
* Name: stm32_otginterrupt
*
* Description:
* OTG interrupt
*
*******************************************************************************/
#ifdef CONFIG_USBDEV_VBUSSENSING
static inline void stm32_otginterrupt(FAR struct stm32_usbdev_s *priv)
{
uint32_t regval;
/* Check for session end detected */
regval = stm32_getreg(STM32_OTGFS_GOTGINT);
if ((regval & OTGFS_GOTGINT_SEDET) != 0)
{
#warning "Missing logic"
}
/* Clear OTG interrupt */
stm32_putreg(retval, STM32_OTGFS_GOTGINT);
}
#endif
/*******************************************************************************
* Name: stm32_usbinterrupt
*
* Description:
* USB interrupt handler
*
*******************************************************************************/
static int stm32_usbinterrupt(int irq, FAR void *context)
{
/* At present, there is only a single OTG FS device support. Hence it is
* pre-allocated as g_otgfsdev. However, in most code, the private data
* structure will be referenced using the 'priv' pointer (rather than the
* global data) in order to simplify any future support for multiple devices.
*/
FAR struct stm32_usbdev_s *priv = &g_otgfsdev;
uint32_t regval;
/* Assure that we are in device mode */
DEBUGASSERT((stm32_getreg(STM32_OTGFS_GINTSTS) & OTGFS_GINTSTS_CMOD) == OTGFS_GINTSTS_DEVMODE);
/* Get the state of all enabled interrupts. We will do this repeatedly
* some interrupts (like RXFLVL) will generate additional interrupting
* events.
*/
for (;;)
{
/* Get the set of pending, un-masked interrupts */
regval = stm32_getreg(STM32_OTGFS_GINTSTS);
regval &= stm32_getreg(STM32_OTGFS_GINTMSK);
/* Break out of the loop when there are no further pending (and
* unmasked) interrupts to be processes.
*/
if (regval == 0)
{
break;
}
usbtrace(TRACE_INTENTRY(STM32_TRACEINTID_USB), (uint16_t)regval);
/* OUT endpoint interrupt. The core sets this bit to indicate that an
* interrupt is pending on one of the OUT endpoints of the core.
*/
if ((regval & OTGFS_GINT_OEP) != 0)
{
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_EPOUT), (uint16_t)regval);
stm32_epout_interrupt(priv);
stm32_putreg(OTGFS_GINT_OEP, STM32_OTGFS_GINTSTS);
}
/* IN endpoint interrupt. The core sets this bit to indicate that
* an interrupt is pending on one of the IN endpoints of the core.
*/
if ((regval & OTGFS_GINT_IEP) != 0)
{
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_EPIN), (uint16_t)regval);
stm32_epin_interrupt(priv);
stm32_putreg(OTGFS_GINT_IEP, STM32_OTGFS_GINTSTS);
}
/* Host/device mode mismatch error interrupt */
#ifdef CONFIG_DEBUG_USB
if ((regval & OTGFS_GINT_MMIS) != 0)
{
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_MISMATCH), (uint16_t)regval);
stm32_putreg(OTGFS_GINT_MMIS, STM32_OTGFS_GINTSTS);
}
#endif
/* Resume/remote wakeup detected interrupt */
if ((regval & OTGFS_GINT_WKUP) != 0)
{
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_WAKEUP), (uint16_t)regval);
stm32_resumeinterrupt(priv);
stm32_putreg(OTGFS_GINT_WKUP, STM32_OTGFS_GINTSTS);
}
/* USB suspend interrupt */
if ((regval & OTGFS_GINT_USBSUSP) != 0)
{
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_SUSPEND), (uint16_t)regval);
stm32_suspendinterrupt(priv);
stm32_putreg(OTGFS_GINT_USBSUSP, STM32_OTGFS_GINTSTS);
}
/* Start of frame interrupt */
#ifdef CONFIG_USBDEV_SOFINTERRUPT
if ((regval & OTGFS_GINT_SOF) != 0)
{
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_SOF), (uint16_t)regval);
stm32_putreg(OTGFS_GINT_SOF, STM32_OTGFS_GINTSTS);
}
#endif
/* RxFIFO non-empty interrupt. Indicates that there is at least one
* packet pending to be read from the RxFIFO.
*/
if ((regval & OTGFS_GINT_RXFLVL) != 0)
{
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_RXFIFO), (uint16_t)regval);
stm32_rxinterrupt(priv);
stm32_putreg(OTGFS_GINT_RXFLVL, STM32_OTGFS_GINTSTS);
}
/* USB reset interrupt */
if ((regval & OTGFS_GINT_USBRST) != 0)
{
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_DEVRESET), (uint16_t)regval);
/* Perform the device reset */
stm32_usbreset(priv);
usbtrace(TRACE_INTEXIT(STM32_TRACEINTID_USB), 0);
return OK;
}
/* Enumeration done interrupt */
if ((regval & OTGFS_GINT_ENUMDNE) != 0)
{
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_ENUMDNE), (uint16_t)regval);
stm32_enuminterrupt(priv);
stm32_putreg(OTGFS_GINT_ENUMDNE, STM32_OTGFS_GINTSTS);
}
/* Incomplete isochronous IN transfer interrupt. When the core finds
* non-empty any of the isochronous IN endpoint FIFOs scheduled for
* the current frame non-empty, the core generates an IISOIXFR
* interrupt.
*/
#ifdef CONFIG_USBDEV_ISOCHRONOUS
if ((regval & OTGFS_GINT_IISOIXFR) != 0)
{
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_IISOIXFR), (uint16_t)regval);
stm32_isocininterrupt(priv);
stm32_putreg(OTGFS_GINT_IISOIXFR, STM32_OTGFS_GINTSTS);
}
/* Incomplete isochronous OUT transfer. For isochronous OUT
* endpoints, the XFRC interrupt may not always be asserted. If the
* core drops isochronous OUT data packets, the application could fail
* to detect the XFRC interrupt. The incomplete Isochronous OUT data
* interrupt indicates that an XFRC interrupt was not asserted on at
* least one of the isochronous OUT endpoints. At this point, the
* endpoint with the incomplete transfer remains enabled, but no active
* transfers remain in progress on this endpoint on the USB.
*/
if ((regval & OTGFS_GINT_IISOOXFR) != 0)
{
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_IISOOXFR), (uint16_t)regval);
stm32_isocoutinterrupt(priv);
stm32_putreg(OTGFS_GINT_IISOOXFR, STM32_OTGFS_GINTSTS);
}
#endif
/* Session request/new session detected interrupt */
#ifdef CONFIG_USBDEV_VBUSSENSING
if ((regval & OTGFS_GINT_SRQ) != 0)
{
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_SRQ), (uint16_t)regval);
stm32_sessioninterrupt(priv);
stm32_putreg(OTGFS_GINT_SRQ, STM32_OTGFS_GINTSTS);
}
/* OTG interrupt */
if ((regval & OTGFS_GINT_OTG) != 0)
{
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_OTG), (uint16_t)regval);
stm32_otginterrupt(priv);
stm32_putreg(OTGFS_GINT_OTG, STM32_OTGFS_GINTSTS);
}
#endif
}
usbtrace(TRACE_INTEXIT(STM32_TRACEINTID_USB), 0);
return OK;
}
/*******************************************************************************
* Endpoint operations
*******************************************************************************/
/*******************************************************************************
* Name: stm32_enablegonak
*
* Description:
* Enable global OUT NAK mode
*
*******************************************************************************/
static void stm32_enablegonak(FAR struct stm32_ep_s *privep)
{
uint32_t regval;
/* First, make sure that there is no GNOAKEFF interrupt pending. */
stm32_putreg(OTGFS_GINT_GONAKEFF, STM32_OTGFS_GINTSTS);
/* Enable Global OUT NAK mode in the core. */
regval = stm32_getreg(STM32_OTGFS_DCTL);
regval |= OTGFS_DCTL_SGONAK;
stm32_putreg(regval, STM32_OTGFS_DCTL);
/* Wait for the GONAKEFF interrupt that indicates that the OUT NAK
* mode is in effect.
*/
while ((stm32_getreg(STM32_OTGFS_GINTSTS) & OTGFS_GINT_GONAKEFF) == 0);
stm32_putreg(OTGFS_GINT_GONAKEFF, STM32_OTGFS_GINTSTS);
}
/*******************************************************************************
* Name: stm32_disablegonak
*
* Description:
* Disable global OUT NAK mode
*
*******************************************************************************/
static void stm32_disablegonak(FAR struct stm32_ep_s *privep)
{
uint32_t regval;
/* Clear the Global OUT NAK bit to disable global OUT NAK mode */
regval = stm32_getreg(STM32_OTGFS_DCTL);
regval &= ~OTGFS_DCTL_SGONAK;
stm32_putreg(regval, STM32_OTGFS_DCTL);
}
/*******************************************************************************
* Name: stm32_epout_configure
*
* Description:
* Configure an OUT endpoint, making it usable
*
* Input Parameters:
* privep - a pointer to an internal endpoint structure
* eptype - The type of the endpoint
* maxpacket - The max packet size of the endpoint
*
*******************************************************************************/
static int stm32_epout_configure(FAR struct stm32_ep_s *privep, uint8_t eptype,
uint16_t maxpacket)
{
uint32_t mpsiz;
uint32_t regaddr;
uint32_t regval;
usbtrace(TRACE_EPCONFIGURE, privep->epphy);
/* For EP0, the packet size is encoded */
if (privep->epphy == EP0)
{
DEBUGASSERT(eptype == USB_EP_ATTR_XFER_CONTROL);
/* Map the size in bytes to the encoded value in the register */
switch (maxpacket)
{
case 8:
mpsiz = OTGFS_DOEPCTL0_MPSIZ_8;
break;
case 16:
mpsiz = OTGFS_DOEPCTL0_MPSIZ_16;
break;
case 32:
mpsiz = OTGFS_DOEPCTL0_MPSIZ_32;
break;
case 64:
mpsiz = OTGFS_DOEPCTL0_MPSIZ_64;
break;
default:
udbg("Unsupported maxpacket: %d\n", maxpacket);
return -EINVAL;
}
}
/* For other endpoints, the packet size is in bytes */
else
{
mpsiz = (maxpacket << OTGFS_DOEPCTL_MPSIZ_SHIFT);
}
/* If the endpoint is already active don't change the endpoint control
* register.
*/
regaddr = STM32_OTGFS_DOEPCTL(privep->epphy);
regval = stm32_getreg(regaddr);
if ((regval & OTGFS_DOEPCTL_USBAEP) != 0)
{
regval &= ~(OTGFS_DOEPCTL_MPSIZ_MASK | OTGFS_DIEPCTL_EPTYP_MASK | OTGFS_DIEPCTL_TXFNUM_MASK);
regval |= mpsiz;
regval |= (eptype << OTGFS_DOEPCTL_EPTYP_SHIFT);
regval |= (eptype << OTGFS_DIEPCTL_TXFNUM_SHIFT);
regval |= (OTGFS_DOEPCTL_SD0PID | OTGFS_DOEPCTL_USBAEP);
stm32_putreg(regval, regaddr);
/* Save the endpoint configuration */
privep->ep.maxpacket = maxpacket;
privep->eptype = eptype;
privep->stalled = false;
}
/* Enable the interrupt for this endpoint */
regval = stm32_getreg(STM32_OTGFS_DAINTMSK);
regval |= OTGFS_DAINT_OEP(privep->epphy);
stm32_putreg(regval, STM32_OTGFS_DAINTMSK);
return OK;
}
/*******************************************************************************
* Name: stm32_epin_configure
*
* Description:
* Configure an IN endpoint, making it usable
*
* Input Parameters:
* privep - a pointer to an internal endpoint structure
* eptype - The type of the endpoint
* maxpacket - The max packet size of the endpoint
*
*******************************************************************************/
static int stm32_epin_configure(FAR struct stm32_ep_s *privep, uint8_t eptype,
uint16_t maxpacket)
{
uint32_t mpsiz;
uint32_t regaddr;
uint32_t regval;
usbtrace(TRACE_EPCONFIGURE, privep->epphy);
/* For EP0, the packet size is encoded */
if (privep->epphy == EP0)
{
DEBUGASSERT(eptype == USB_EP_ATTR_XFER_CONTROL);
/* Map the size in bytes to the encoded value in the register */
switch (maxpacket)
{
case 8:
mpsiz = OTGFS_DIEPCTL0_MPSIZ_8;
break;
case 16:
mpsiz = OTGFS_DIEPCTL0_MPSIZ_16;
break;
case 32:
mpsiz = OTGFS_DIEPCTL0_MPSIZ_32;
break;
case 64:
mpsiz = OTGFS_DIEPCTL0_MPSIZ_64;
break;
default:
udbg("Unsupported maxpacket: %d\n", maxpacket);
return -EINVAL;
}
}
/* For other endpoints, the packet size is in bytes */
else
{
mpsiz = (maxpacket << OTGFS_DIEPCTL_MPSIZ_SHIFT);
}
/* If the endpoint is already active don't change the endpoint control
* register.
*/
regaddr = STM32_OTGFS_DIEPCTL(privep->epphy);
regval = stm32_getreg(regaddr);
if ((regval & OTGFS_DIEPCTL_USBAEP) != 0)
{
regval &= ~(OTGFS_DIEPCTL_MPSIZ_MASK | OTGFS_DIEPCTL_EPTYP_MASK | OTGFS_DIEPCTL_TXFNUM_MASK);
regval |= mpsiz;
regval |= (eptype << OTGFS_DIEPCTL_EPTYP_SHIFT);
regval |= (eptype << OTGFS_DIEPCTL_TXFNUM_SHIFT);
regval |= (OTGFS_DIEPCTL_SD0PID | OTGFS_DIEPCTL_USBAEP);
stm32_putreg(regval, regaddr);
/* Save the endpoint configuration */
privep->ep.maxpacket = maxpacket;
privep->eptype = eptype;
privep->stalled = false;
}
/* Enable the interrupt for this endpoint */
regval = stm32_getreg(STM32_OTGFS_DAINTMSK);
regval |= OTGFS_DAINT_IEP(privep->epphy);
stm32_putreg(regval, STM32_OTGFS_DAINTMSK);
return OK;
}
/*******************************************************************************
* Name: stm32_ep_configure
*
* Description:
* Configure endpoint, making it usable
*
* Input Parameters:
* ep - the struct usbdev_ep_s instance obtained from allocep()
* desc - A struct usb_epdesc_s instance describing the endpoint
* last - true if this this last endpoint to be configured. Some hardware
* needs to take special action when all of the endpoints have been
* configured.
*
*******************************************************************************/
static int stm32_ep_configure(FAR struct usbdev_ep_s *ep,
FAR const struct usb_epdesc_s *desc,
bool last)
{
FAR struct stm32_ep_s *privep = (FAR struct stm32_ep_s *)ep;
uint16_t maxpacket;
uint8_t eptype;
int ret;
usbtrace(TRACE_EPCONFIGURE, privep->epphy);
DEBUGASSERT(desc->addr == ep->eplog);
/* Initialize EP capabilities */
maxpacket = GETUINT16(desc->mxpacketsize);
eptype = desc->attr & USB_EP_ATTR_XFERTYPE_MASK;
/* Setup Endpoint Control Register */
if (privep->isin)
{
ret = stm32_epin_configure(privep, eptype, maxpacket);
}
else
{
ret = stm32_epout_configure(privep, eptype, maxpacket);
}
return ret;
}
/*******************************************************************************
* Name: stm32_ep0_configure
*
* Description:
* Reset Usb engine
*
*******************************************************************************/
static void stm32_ep0_configure(FAR struct stm32_usbdev_s *priv)
{
/* Enable EP0 IN and OUT */
(void)stm32_epin_configure(&priv->epin[EP0], USB_EP_ATTR_XFER_CONTROL,
CONFIG_USBDEV_EP0_MAXSIZE);
(void)stm32_epout_configure(&priv->epout[EP0], USB_EP_ATTR_XFER_CONTROL,
CONFIG_USBDEV_EP0_MAXSIZE);
}
/*******************************************************************************
* Name: stm32_epout_disable
*
* Description:
* Diable an OUT endpoint will no longer be used
*
*******************************************************************************/
static void stm32_epout_disable(FAR struct stm32_ep_s *privep)
{
uint32_t regaddr;
uint32_t regval;
irqstate_t flags;
usbtrace(TRACE_EPDISABLE, privep->epphy);
/* Is this an IN or an OUT endpoint */
/* Before disabling any OUT endpoint, the application must enable
* Global OUT NAK mode in the core.
*/
flags = irqsave();
stm32_enablegonak(privep);
/* Disable the required OUT endpoint by setting the EPDIS and SNAK bits
* int DOECPTL register.
*/
regaddr = STM32_OTGFS_DOEPCTL(privep->epphy);
regval = stm32_getreg(regaddr);
regval &= ~OTGFS_DOEPCTL_USBAEP;
regval |= (OTGFS_DOEPCTL_EPDIS | OTGFS_DOEPCTL_SNAK);
stm32_putreg(regval, regaddr);
/* Wait for the EPDISD interrupt which indicates that the OUT
* endpoint is completely disabled.
*/
regaddr = STM32_OTGFS_DOEPINT_OFFSET(privep->epphy);
while ((stm32_getreg(regaddr) & OTGFS_DOEPINT_EPDISD) == 0);
/* Then disble the Global OUT NAK mode to continue receiving data
* from other non-disabled OUT endpoints.
*/
stm32_disablegonak(privep);
/* Disable endpoint interrupts */
regval = stm32_getreg(STM32_OTGFS_DAINTMSK);
regval &= ~OTGFS_DAINT_OEP(privep->epphy);
stm32_putreg(regval, STM32_OTGFS_DAINTMSK);
/* Cancel any queued read requests */
stm32_req_cancel(privep, -ESHUTDOWN);
irqrestore(flags);
}
/*******************************************************************************
* Name: stm32_epin_disable
*
* Description:
* Diable an IN endpoint will no longer be used
*
*******************************************************************************/
static void stm32_epin_disable(FAR struct stm32_ep_s *privep)
{
uint32_t regaddr;
uint32_t regval;
irqstate_t flags;
usbtrace(TRACE_EPDISABLE, privep->epphy);
/* Make sure that there is no pending IPEPNE interrupt (because we are
* to poll this bit below).
*/
stm32_putreg(OTGFS_DIEPINT_INEPNE, STM32_OTGFS_DIEPINT(epno));
/* Set the endpoint in NAK mode */
regaddr = STM32_OTGFS_DIEPCTL(privep->epphy);
regval = stm32_getreg(regaddr);
regval &= ~OTGFS_DIEPCTL_USBAEP;
regval |= (OTGFS_DIEPCTL_EPDIS | OTGFS_DIEPCTL_SNAK);
stm32_putreg(regval, regaddr);
/* Wait for the INEPNE interrupt that indicates that we are now in NAK mode */
regaddr = STM32_OTGFS_DIEPINT(privep->epphy);
while ((stm32_getreg(regaddr) & OTGFS_DIEPINT_INEPNE) == 0);
stm32_putreg(OTGFS_DIEPINT_INEPNE, regaddr);
/* Deactivate and disable the endpoint by setting the EPIS and SNAK bits
* the DIEPCTLx register.
*/
flags = irqsave();
regaddr = STM32_OTGFS_DIEPCTL(privep->epphy);
regval = stm32_getreg(regaddr);
regval &= ~OTGFS_DIEPCTL_USBAEP;
regval |= (OTGFS_DIEPCTL_EPDIS | OTGFS_DIEPCTL_SNAK);
stm32_putreg(regval, regaddr);
/* Wait for the EPDISD interrupt which indicates that the IN
* endpoint is completely disabled.
*/
regaddr = STM32_OTGFS_DIEPINT_OFFSET(privep->epphy);
while ((stm32_getreg(regaddr) & OTGFS_DIEPINT_EPDISD) == 0);
/* Flush any data remaining in the TxFIFO */
stm32_txfifo_flush(OTGFS_GRSTCTL_TXFNUM_D(privep->epphy));
/* Disable endpoint interrupts */
regval = stm32_getreg(STM32_OTGFS_DAINTMSK);
regval &= ~OTGFS_DAINT_IEP(privep->epphy);
stm32_putreg(regval, STM32_OTGFS_DAINTMSK);
/* Cancel any queued write requests */
stm32_req_cancel(privep, -ESHUTDOWN);
irqrestore(flags);
}
/*******************************************************************************
* Name: stm32_ep_disable
*
* Description:
* The endpoint will no longer be used
*
*******************************************************************************/
static int stm32_ep_disable(FAR struct usbdev_ep_s *ep)
{
FAR struct stm32_ep_s *privep = (FAR struct stm32_ep_s *)ep;
#ifdef CONFIG_DEBUG
if (!ep)
{
usbtrace(TRACE_DEVERROR(STM32_TRACEERR_INVALIDPARMS), 0);
return -EINVAL;
}
#endif
usbtrace(TRACE_EPDISABLE, privep->epphy);
/* Is this an IN or an OUT endpoint */
if (privep->isin)
{
/* Disable the IN endpoint */
stm32_epin_disable(privep);
}
else
{
/* Disable the OUT endpoint */
stm32_epout_disable(privep);
}
return OK;
}
/*******************************************************************************
* Name: stm32_ep_allocreq
*
* Description:
* Allocate an I/O request
*
*******************************************************************************/
static FAR struct usbdev_req_s *stm32_ep_allocreq(FAR struct usbdev_ep_s *ep)
{
FAR struct stm32_req_s *privreq;
#ifdef CONFIG_DEBUG
if (!ep)
{
usbtrace(TRACE_DEVERROR(STM32_TRACEERR_INVALIDPARMS), 0);
return NULL;
}
#endif
usbtrace(TRACE_EPALLOCREQ, ((FAR struct stm32_ep_s *)ep)->epphy);
privreq = (FAR struct stm32_req_s *)malloc(sizeof(struct stm32_req_s));
if (!privreq)
{
usbtrace(TRACE_DEVERROR(STM32_TRACEERR_ALLOCFAIL), 0);
return NULL;
}
memset(privreq, 0, sizeof(struct stm32_req_s));
return &privreq->req;
}
/*******************************************************************************
* Name: stm32_ep_freereq
*
* Description:
* Free an I/O request
*
*******************************************************************************/
static void stm32_ep_freereq(FAR struct usbdev_ep_s *ep, FAR struct usbdev_req_s *req)
{
FAR struct stm32_req_s *privreq = (FAR struct stm32_req_s *)req;
#ifdef CONFIG_DEBUG
if (!ep || !req)
{
usbtrace(TRACE_DEVERROR(STM32_TRACEERR_INVALIDPARMS), 0);
return;
}
#endif
usbtrace(TRACE_EPFREEREQ, ((FAR struct stm32_ep_s *)ep)->epphy);
free(privreq);
}
/*******************************************************************************
* Name: stm32_ep_allocbuffer
*
* Description:
* Allocate an I/O buffer
*
*******************************************************************************/
#ifdef CONFIG_ARCH_USBDEV_DMA
static void *stm32_ep_allocbuffer(FAR struct usbdev_ep_s *ep, unsigned bytes)
{
usbtrace(TRACE_EPALLOCBUFFER, privep->epphy);
return malloc(bytes)
}
#endif
/*******************************************************************************
* Name: stm32_ep_freebuffer
*
* Description:
* Free an I/O buffer
*
*******************************************************************************/
#ifdef CONFIG_LPC313x_USBDEV_DMA
static void stm32_ep_freebuffer(FAR struct usbdev_ep_s *ep, FAR void *buf)
{
usbtrace(TRACE_EPFREEBUFFER, privep->epphy);
free(buf);
}
#endif
/*******************************************************************************
* Name: stm32_ep_submit
*
* Description:
* Submit an I/O request to the endpoint
*
*******************************************************************************/
static int stm32_ep_submit(FAR struct usbdev_ep_s *ep, FAR struct usbdev_req_s *req)
{
FAR struct stm32_req_s *privreq = (FAR struct stm32_req_s *)req;
FAR struct stm32_ep_s *privep = (FAR struct stm32_ep_s *)ep;
FAR struct stm32_usbdev_s *priv;
irqstate_t flags;
int ret = OK;
#ifdef CONFIG_DEBUG
if (!req || !req->callback || !req->buf || !ep)
{
usbtrace(TRACE_DEVERROR(STM32_TRACEERR_INVALIDPARMS), 0);
ullvdbg("req=%p callback=%p buf=%p ep=%p\n", req, req->callback, req->buf, ep);
return -EINVAL;
}
#endif
usbtrace(TRACE_EPSUBMIT, privep->epphy);
priv = privep->dev;
if (!priv->driver || priv->usbdev.speed == USB_SPEED_UNKNOWN)
{
usbtrace(TRACE_DEVERROR(STM32_TRACEERR_NOTCONFIGURED), priv->usbdev.speed);
return -ESHUTDOWN;
}
/* Handle the request from the class driver */
req->result = -EINPROGRESS;
req->xfrd = 0;
/* Disable Interrupts */
flags = irqsave();
/* If we are stalled, then drop all requests on the floor */
if (privep->stalled)
{
ret = -EBUSY;
}
else
{
/* Add the new request to the request queue for the endpoint */
if (stm32_req_addlast(privep, privreq))
{
/* If a request was added to an IN endpoint, then attempt to send
* the request data buffer now (this will, of course, fail if there
* is already a transmission in progress).
*/
if (privep->isin)
{
usbtrace(TRACE_INREQQUEUED(privep->epphy), privreq->req.len);
stm32_epin_request(priv, privep);
}
/* If the request was added to an OUT endoutput, then attempt to
* setup a read into the request data buffer now (this will, of
* course, fail if there is already a read in place).
*/
else
{
usbtrace(TRACE_OUTREQQUEUED(privep->epphy), privreq->req.len);
stm32_epout_request(priv, privep);
}
}
}
irqrestore(flags);
return ret;
}
/*******************************************************************************
* Name: stm32_ep_cancel
*
* Description:
* Cancel an I/O request previously sent to an endpoint
*
*******************************************************************************/
static int stm32_ep_cancel(FAR struct usbdev_ep_s *ep, FAR struct usbdev_req_s *req)
{
FAR struct stm32_ep_s *privep = (FAR struct stm32_ep_s *)ep;
FAR struct stm32_usbdev_s *priv;
irqstate_t flags;
#ifdef CONFIG_DEBUG
if (!ep || !req)
{
usbtrace(TRACE_DEVERROR(STM32_TRACEERR_INVALIDPARMS), 0);
return -EINVAL;
}
#endif
usbtrace(TRACE_EPCANCEL, privep->epphy);
priv = privep->dev;
flags = irqsave();
/* FIXME: if the request is the first, then we need to flush the EP
* otherwise just remove it from the list
*
* but ... all other implementations cancel all requests ...
*/
stm32_req_cancel(privep, -ESHUTDOWN);
irqrestore(flags);
return OK;
}
/*******************************************************************************
* Name: stm32_epout_setstall
*
* Description:
* Stall an OUT endpoint
*
*******************************************************************************/
static int stm32_epout_setstall(FAR struct stm32_ep_s *privep)
{
uint32_t regaddr;
uint32_t regval;
/* Put the core in the Global OUT NAK mode */
stm32_enablegonak(privep);
/* Disable and STALL the OUT endpoint by setting the EPDIS and STALL bits
* int DOECPTL register.
*/
regaddr = STM32_OTGFS_DOEPCTL(privep->epphy);
regval = stm32_getreg(regaddr);
regval &= ~OTGFS_DOEPCTL_USBAEP;
regval |= (OTGFS_DOEPCTL_EPDIS | OTGFS_DOEPCTL_STALL);
stm32_putreg(regval, regaddr);
/* Wait for the EPDISD interrupt which indicates that the OUT
* endpoint is completely disabled.
*/
regaddr = STM32_OTGFS_DOEPINT_OFFSET(privep->epphy);
while ((stm32_getreg(regaddr) & OTGFS_DOEPINT_EPDISD) == 0);
/* Disable Global OUT NAK mode */
stm32_disablegonak(privep);
/* The endpoint is now stalled */
privep->stalled = true;
return OK;
}
/*******************************************************************************
* Name: stm32_epin_setstall
*
* Description:
* Stall an IN endpoint
*
*******************************************************************************/
static int stm32_epin_setstall(FAR struct stm32_ep_s *privep)
{
uint32_t regaddr;
uint32_t regval;
/* Get the IN endpoint device control register */
regaddr = STM32_OTGFS_DIEPCTL(privep->epphy);
regval = stm32_getreg(regaddr);
/* Is the endpoint enabled? */
if ((regval & OTGFS_DIEPCTL_EPENA) != 0)
{
/* Yes.. the endpoint is enabled, disable it */
regval = OTGFS_DIEPCTL_EPDIS;
}
else
{
regval = 0;
}
/* Then stall the endpoint */
regval |= OTGFS_DIEPCTL_STALL;
stm32_putreg(regval, regaddr);
/* The endpoint is now stalled */
privep->stalled = true;
return OK;
}
/*******************************************************************************
* Name: stm32_ep_setstall
*
* Description:
* Stall an endpoint
*
*******************************************************************************/
static int stm32_ep_setstall(FAR struct stm32_ep_s *privep)
{
usbtrace(TRACE_EPSTALL, privep->epphy);
/* Is this an IN endpoint? */
if (privep->isin == 1)
{
return stm32_epin_setstall(privep);
}
else
{
return stm32_epout_setstall(privep);
}
}
/*******************************************************************************
* Name: stm32_ep_clrstall
*
* Description:
* Resume a stalled endpoint
*
*******************************************************************************/
static int stm32_ep_clrstall(FAR struct stm32_ep_s *privep)
{
uint32_t regaddr;
uint32_t regval;
uint32_t stallbit;
uint32_t data0bit;
usbtrace(TRACE_EPRESUME, privep->epphy);
/* Is this an IN endpoint? */
if (privep->isin == 1)
{
/* Clear the stall bit in the IN endpoint device control register */
regaddr = STM32_OTGFS_DIEPCTL(privep->epphy);
stallbit = OTGFS_DIEPCTL_STALL;
data0bit = OTGFS_DIEPCTL_SD0PID;
}
else
{
/* Clear the stall bit in the IN endpoint device control register */
regaddr = STM32_OTGFS_DOEPCTL(privep->epphy);
stallbit = OTGFS_DOEPCTL_STALL;
data0bit = OTGFS_DOEPCTL_SD0PID;
}
/* Clear the stall bit */
regval = stm32_getreg(regaddr);
regval &= ~stallbit;
/* Set the DATA0 pid for interrupt and bulk endpoints */
if (privep->eptype == USB_EP_ATTR_XFER_INT ||
privep->eptype == USB_EP_ATTR_XFER_BULK)
{
/* Writing this bit sets the DATA0 PID */
regval |= data0bit;
}
stm32_putreg(regval, regaddr);
/* The endpoint is no longer stalled */
privep->stalled = false;
return OK;
}
/*******************************************************************************
* Name: stm32_ep_stall
*
* Description:
* Stall or resume an endpoint
*
*******************************************************************************/
static int stm32_ep_stall(FAR struct usbdev_ep_s *ep, bool resume)
{
FAR struct stm32_ep_s *privep = (FAR struct stm32_ep_s *)ep;
irqstate_t flags;
int ret;
/* Set or clear the stall condition as requested */
flags = irqsave();
if (resume)
{
ret = stm32_ep_clrstall(privep);
}
else
{
ret = stm32_ep_setstall(privep);
}
irqrestore(flags);
return ret;
}
/*******************************************************************************
* Name: stm32_ep0_stall
*
* Description:
* Stall endpoint 0
*
*******************************************************************************/
static void stm32_ep0_stall(FAR struct stm32_usbdev_s *priv)
{
stm32_epin_setstall(&priv->epin[EP0]);
stm32_epout_setstall(&priv->epout[EP0]);
priv->stalled = true;
stm32_ep0out_ctrlsetup(priv);
}
/*******************************************************************************
* Device operations
*******************************************************************************/
/*******************************************************************************
* Name: stm32_ep_alloc
*
* Description:
* Allocate an endpoint matching the parameters.
*
* Input Parameters:
* eplog - 7-bit logical endpoint number (direction bit ignored). Zero means
* that any endpoint matching the other requirements will suffice. The
* assigned endpoint can be found in the eplog field.
* in - true: IN (device-to-host) endpoint requested
* eptype - Endpoint type. One of {USB_EP_ATTR_XFER_ISOC, USB_EP_ATTR_XFER_BULK,
* USB_EP_ATTR_XFER_INT}
*
*******************************************************************************/
static FAR struct usbdev_ep_s *stm32_ep_alloc(FAR struct usbdev_s *dev,
uint8_t eplog, bool in,
uint8_t eptype)
{
FAR struct stm32_usbdev_s *priv = (FAR struct stm32_usbdev_s *)dev;
uint8_t epavail;
irqstate_t flags;
int epphy;
int epno = 0;
usbtrace(TRACE_DEVALLOCEP, (uint16_t)eplog);
/* Ignore any direction bits in the logical address */
epphy = USB_EPNO(eplog);
/* Get the set of available endpoints */
flags = irqsave();
epavail = priv->epavail;
/* A physical address of 0 means that any endpoint will do */
if (epphy > 0)
{
/* Otherwise, we will return the endpoint structure only for the requested
* 'logical' endpoint. All of the other checks will still be performed.
*
* First, verify that the logical endpoint is in the range supported by
* by the hardware.
*/
if (epphy >= STM32_NENDPOINTS)
{
usbtrace(TRACE_DEVERROR(STM32_TRACEERR_BADEPNO), (uint16_t)epphy);
return NULL;
}
/* Remove all of the candidate endpoints from the bitset except for the
* this physical endpoint number.
*/
epavail &= (1 << epphy);
}
/* Is there an available endpoint? */
if (epavail)
{
/* Yes.. Select the lowest numbered endpoint in the set of available
* endpoints.
*/
for (epno = 1; epno < STM32_NENDPOINTS; epno++)
{
uint8_t bit = 1 << epno;
if ((epavail & bit) != 0)
{
/* Mark the endpoint no longer available */
priv->epavail &= ~(1 << epno);
/* And return the pointer to the standard endpoint structure */
irqrestore(flags);
return in ? &priv->epin[epno].ep : &priv->epout[epno].ep;
}
}
/* We should not get here */
}
usbtrace(TRACE_DEVERROR(STM32_TRACEERR_NOEP), (uint16_t)eplog);
irqrestore(flags);
return NULL;
}
/*******************************************************************************
* Name: stm32_ep_free
*
* Description:
* Free the previously allocated endpoint
*
*******************************************************************************/
static void stm32_ep_free(FAR struct usbdev_s *dev, FAR struct usbdev_ep_s *ep)
{
FAR struct stm32_usbdev_s *priv = (FAR struct stm32_usbdev_s *)dev;
FAR struct stm32_ep_s *privep = (FAR struct stm32_ep_s *)ep;
irqstate_t flags;
usbtrace(TRACE_DEVFREEEP, (uint16_t)privep->epphy);
if (priv && privep)
{
/* Mark the endpoint as available */
flags = irqsave();
priv->epavail |= (1 << privep->epphy);
irqrestore(flags);
}
}
/*******************************************************************************
* Name: stm32_getframe
*
* Description:
* Returns the current frame number
*
*******************************************************************************/
static int stm32_getframe(struct usbdev_s *dev)
{
uint32_t regval;
usbtrace(TRACE_DEVGETFRAME, 0);
/* Return the last frame number of the last SOF detected by the hardware */
regval = stm32_getreg(STM32_OTGFS_DSTS);
return (int)((regval & OTGFS_DSTS_SOFFN_MASK) >> OTGFS_DSTS_SOFFN_SHIFT);
}
/*******************************************************************************
* Name: stm32_wakeup
*
* Description:
* Exit suspend mode.
*
*******************************************************************************/
static int stm32_wakeup(struct usbdev_s *dev)
{
FAR struct stm32_usbdev_s *priv = (FAR struct stm32_usbdev_s *)dev;
uint32_t regval;
irqstate_t flags;
usbtrace(TRACE_DEVWAKEUP, 0);
/* Is wakeup enabled? */
flags = irqsave();
if (priv->wakeup)
{
/* Yes... is the core suspended? */
regval = stm32_getreg(STM32_OTGFS_DSTS);
if ((regval & OTGFS_DSTS_SUSPSTS) != 0)
{
/* Re-start the PHY clock and un-gate USB core clock (HCLK) */
#ifdef CONFIG_USBDEV_LOWPOWER
regval = stm32_getreg(STM32_OTGFS_PCGCCTL);
regval &= ~(OTGFS_PCGCCTL_STPPCLK | OTGFS_PCGCCTL_GATEHCLK);
stm32_putreg(regval, STM32_OTGFS_PCGCCTL);
#endif
/* Activate Remote wakeup signaling */
regval = stm32_getreg(STM32_OTGFS_DCTL);
regval |= OTGFS_DCTL_RWUSIG;
stm32_putreg(regval, STM32_OTGFS_DCTL);
up_mdelay(5);
regval &= ~OTGFS_DCTL_RWUSIG;
stm32_putreg(regval, STM32_OTGFS_DCTL);
}
}
irqrestore(flags);
return OK;
}
/*******************************************************************************
* Name: stm32_selfpowered
*
* Description:
* Sets/clears the device selfpowered feature
*
*******************************************************************************/
static int stm32_selfpowered(struct usbdev_s *dev, bool selfpowered)
{
FAR struct stm32_usbdev_s *priv = (FAR struct stm32_usbdev_s *)dev;
usbtrace(TRACE_DEVSELFPOWERED, (uint16_t)selfpowered);
#ifdef CONFIG_DEBUG
if (!dev)
{
usbtrace(TRACE_DEVERROR(STM32_TRACEERR_INVALIDPARMS), 0);
return -ENODEV;
}
#endif
priv->selfpowered = selfpowered;
return OK;
}
/*******************************************************************************
* Name: stm32_pullup
*
* Description:
* Software-controlled connect to/disconnect from USB host
*
*******************************************************************************/
static int stm32_pullup(struct usbdev_s *dev, bool enable)
{
uint32_t regval;
usbtrace(TRACE_DEVPULLUP, (uint16_t)enable);
irqstate_t flags = irqsave();
regval = stm32_getreg(STM32_OTGFS_DCTL);
if (enable)
{
/* Connect the device by clearing the soft disconnect bit in the DCTL
* register
*/
regval &= ~OTGFS_DCTL_SDIS;
}
else
{
/* Connect the device by setting the soft disconnect bit in the DCTL
* register
*/
regval |= OTGFS_DCTL_SDIS;
}
stm32_putreg(regval, STM32_OTGFS_DCTL);
up_mdelay(3);
irqrestore(flags);
return OK;
}
/*******************************************************************************
* Name: stm32_setaddress
*
* Description:
* Set the devices USB address
*
*******************************************************************************/
static void stm32_setaddress(struct stm32_usbdev_s *priv, uint16_t address)
{
uint32_t regval;
/* Set the device address in the DCFG register */
regval = stm32_getreg(STM32_OTGFS_DCFG);
regval &= ~OTGFS_DCFG_DAD_MASK;
regval |= ((uint32_t)address << OTGFS_DCFG_DAD_SHIFT);
stm32_putreg(regval, STM32_OTGFS_DCFG);
/* Are we now addressed? (i.e., do we have a non-NULL device
* address?)
*/
if (address != 0)
{
priv->devstate = DEVSTATE_ADDRESSED;
priv->addressed = true;
}
else
{
priv->devstate = DEVSTATE_DEFAULT;
priv->addressed = false;
}
}
/*******************************************************************************
* Name: stm32_txfifo_flush
*
* Description:
* Flush the specific TX fifo.
*
*******************************************************************************/
static int stm32_txfifo_flush(uint32_t txfnum)
{
uint32_t regval;
uint32_t timeout;
/* Initiate the TX FIFO flush operation */
regval = OTGFS_GRSTCTL_TXFFLSH | txfnum;
stm32_putreg(regval, STM32_OTGFS_GRSTCTL);
/* Wait for the FLUSH to complete */
for (timeout = 0; timeout < STM32_FLUSH_DELAY; timeout++)
{
regval = stm32_getreg(STM32_OTGFS_GRSTCTL);
if ((regval & OTGFS_GRSTCTL_TXFFLSH) == 0)
{
break;
}
}
/* Wait for 3 PHY Clocks */
up_udelay(3);
return OK;
}
/*******************************************************************************
* Name: stm32_rxfifo_flush
*
* Description:
* Flush the RX fifo.
*
*******************************************************************************/
static int stm32_rxfifo_flush(void)
{
uint32_t regval;
uint32_t timeout;
/* Initiate the RX FIFO flush operation */
stm32_putreg(OTGFS_GRSTCTL_RXFFLSH, STM32_OTGFS_GRSTCTL);
/* Wait for the FLUSH to complete */
for (timeout = 0; timeout < STM32_FLUSH_DELAY; timeout++)
{
regval = stm32_getreg(STM32_OTGFS_GRSTCTL);
if ((regval & OTGFS_GRSTCTL_RXFFLSH) == 0)
{
break;
}
}
/* Wait for 3 PHY Clocks */
up_udelay(3);
return OK;
}
/*******************************************************************************
* Name: stm32_swinitialize
*
* Description:
* Initialize all driver data structures.
*
*******************************************************************************/
static void stm32_swinitialize(FAR struct stm32_usbdev_s *priv)
{
FAR struct stm32_ep_s *privep;
int i;
/* Initialize the device state structure */
memset(priv, 0, sizeof(struct stm32_usbdev_s));
priv->usbdev.ops = &g_devops;
priv->usbdev.ep0 = &priv->epin[EP0].ep;
priv->epavail = STM32_EP_AVAILABLE;
/* Initialize the endpoint lists */
for (i = 0; i < STM32_NENDPOINTS; i++)
{
/* Set endpoint operations, reference to driver structure (not
* really necessary because there is only one controller), and
* the physical endpoint number (which is just the index to the
* endpoint).
*/
privep = &priv->epin[i];
privep->ep.ops = &g_epops;
privep->dev = priv;
privep->isin = 1;
/* The index, i, is the physical endpoint address; Map this
* to a logical endpoint address usable by the class driver.
*/
privep->epphy = i;
privep->ep.eplog = STM32_EPPHYIN2LOG(i);
/* Control until endpoint is activated */
privep->eptype = USB_EP_ATTR_XFER_CONTROL;
privep->ep.maxpacket = CONFIG_USBDEV_EP0_MAXSIZE;
}
/* Initialize the endpoint lists */
for (i = 0; i < STM32_NENDPOINTS; i++)
{
/* Set endpoint operations, reference to driver structure (not
* really necessary because there is only one controller), and
* the physical endpoint number (which is just the index to the
* endpoint).
*/
privep = &priv->epout[i];
privep->ep.ops = &g_epops;
privep->dev = priv;
/* The index, i, is the physical endpoint address; Map this
* to a logical endpoint address usable by the class driver.
*/
privep->epphy = i;
privep->ep.eplog = STM32_EPPHYOUT2LOG(i);
/* Control until endpoint is activated */
privep->eptype = USB_EP_ATTR_XFER_CONTROL;
privep->ep.maxpacket = CONFIG_USBDEV_EP0_MAXSIZE;
}
}
/*******************************************************************************
* Name: stm32_hwinitialize
*
* Description:
* Configure the OTG FS core for operation.
*
*******************************************************************************/
static void stm32_hwinitialize(FAR struct stm32_usbdev_s *priv)
{
uint32_t regval;
uint32_t timeout;
uint32_t address;
int i;
/* At startup the core is in FS mode. */
/* Disable the USB global interrupt by clearing GINTMSK in the global OTG
* FS AHB configuration register.
*/
stm32_putreg(0, STM32_OTGFS_GAHBCFG);
/* Common USB OTG core initialization */
/* Reset after a PHY select and set Host mode. First, wait for AHB master
* IDLE state.
*/
for (timeout = 0; timeout < STM32_READY_DELAY; timeout++)
{
up_udelay(3);
regval = stm32_getreg(STM32_OTGFS_GRSTCTL);
if ((regval & OTGFS_GRSTCTL_AHBIDL) != 0)
{
break;
}
}
/* Then perform the core soft reset. */
stm32_putreg(OTGFS_GRSTCTL_CSRST, STM32_OTGFS_GRSTCTL);
for (timeout = 0; timeout < STM32_READY_DELAY; timeout++)
{
regval = stm32_getreg(STM32_OTGFS_GRSTCTL);
if ((regval & OTGFS_GRSTCTL_CSRST) == 0)
{
break;
}
}
/* Wait for 3 PHY Clocks */
up_udelay(3);
/* Deactivate the power down */
regval = (OTGFS_GCCFG_PWRDWN | OTGFS_GCCFG_VBUSASEN | OTGFS_GCCFG_VBUSBSEN);
#ifndef CONFIG_USBDEV_VBUSSENSING
regval |= OTGFS_GCCFG_NOVBUSSENS;
#endif
#ifdef CONFIG_USBDEV_SOFOUTPUT
regval |= OTGFS_GCCFG_SOFOUTEN;
#endif
stm32_putreg(regval, STM32_OTGFS_GCCFG);
up_mdelay(20);
/* Force Device Mode */
regval = stm32_getreg(STM32_OTGFS_GUSBCFG);
regval &= ~OTGFS_GUSBCFG_FHMOD;
regval |= OTGFS_GUSBCFG_FDMOD;
stm32_putreg(regval, STM32_OTGFS_GUSBCFG);
up_mdelay(50);
/* Initialize device mode */
/* Restart the Phy Clock */
stm32_putreg(0, STM32_OTGFS_PCGCCTL);
/* Device configuration register */
regval = stm32_getreg(STM32_OTGFS_DCFG);
regval &= ~OTGFS_DCFG_PFIVL_MASK;
regval |= OTGFS_DCFG_PFIVL_80PCT;
stm32_putreg(regval, STM32_OTGFS_DCFG);
/* Set full speed phy */
regval = stm32_getreg(STM32_OTGFS_DCFG);
regval &= ~OTGFS_DCFG_DSPD_MASK;
regval |= OTGFS_DCFG_DSPD_FS;
stm32_putreg(regval, STM32_OTGFS_DCFG);
/* set Rx FIFO size */
stm32_putreg(CONFIG_USBDEV_RXFIFO_SIZE, STM32_OTGFS_GRXFSIZ);
/* EP0 TX */
address = CONFIG_USBDEV_RXFIFO_SIZE;
regval = (address << OTGFS_DIEPTXF0_TX0FD_SHIFT) ||
(CONFIG_USBDEV_EP0_TXFIFO_SIZE << OTGFS_DIEPTXF0_TX0FSA_SHIFT);
stm32_putreg(regval, STM32_OTGFS_DIEPTXF0);
/* EP1 TX */
address += CONFIG_USBDEV_EP0_TXFIFO_SIZE;
regval = (address << OTGFS_DIEPTXF_INEPTXSA_SHIFT) ||
(CONFIG_USBDEV_EP1_TXFIFO_SIZE << OTGFS_DIEPTXF_INEPTXFD_SHIFT);
stm32_putreg(regval, STM32_OTGFS_DIEPTXF1);
/* EP2 TX */
address += CONFIG_USBDEV_EP1_TXFIFO_SIZE;
regval = (address << OTGFS_DIEPTXF_INEPTXSA_SHIFT) ||
(CONFIG_USBDEV_EP2_TXFIFO_SIZE << OTGFS_DIEPTXF_INEPTXFD_SHIFT);
stm32_putreg(regval, STM32_OTGFS_DIEPTXF2);
/* EP3 TX */
address += CONFIG_USBDEV_EP2_TXFIFO_SIZE;
regval = (address << OTGFS_DIEPTXF_INEPTXSA_SHIFT) ||
(CONFIG_USBDEV_EP3_TXFIFO_SIZE << OTGFS_DIEPTXF_INEPTXFD_SHIFT);
stm32_putreg(regval, STM32_OTGFS_DIEPTXF3);
/* Flush the FIFOs */
stm32_txfifo_flush(OTGFS_GRSTCTL_TXFNUM_DALL);
stm32_rxfifo_flush();
/* Clear all pending Device Interrupts */
stm32_putreg(0, STM32_OTGFS_DIEPMSK);
stm32_putreg(0, STM32_OTGFS_DOEPMSK);
stm32_putreg(0xffffffff, STM32_OTGFS_DAINT);
stm32_putreg(0, STM32_OTGFS_DAINTMSK);
/* Configure all IN endpoints */
for (i = 0; i < STM32_NENDPOINTS; i++)
{
regval = stm32_getreg(STM32_OTGFS_DIEPCTL(i));
if ((regval & OTGFS_DIEPCTL_EPENA) != 0)
{
/* The endpoint is already enabled */
regval = OTGFS_DIEPCTL_EPENA | OTGFS_DIEPCTL_SNAK;
}
else
{
regval = 0;
}
stm32_putreg(regval, STM32_OTGFS_DIEPCTL(i));
stm32_putreg(0, STM32_OTGFS_DIEPTSIZ(i));
stm32_putreg(0xff, STM32_OTGFS_DIEPINT(i));
}
/* Configure all OUT endpoints */
for (i = 0; i < STM32_NENDPOINTS; i++)
{
regval = stm32_getreg(STM32_OTGFS_DOEPCTL(i));
if ((regval & OTGFS_DOEPCTL_EPENA) != 0)
{
/* The endpoint is already enabled */
regval = OTGFS_DOEPCTL_EPENA | OTGFS_DOEPCTL_SNAK;
}
else
{
regval = 0;
}
stm32_putreg(regval, STM32_OTGFS_DOEPCTL(i));
stm32_putreg(0, STM32_OTGFS_DOEPTSIZ(i));
stm32_putreg(0xff, STM32_OTGFS_DOEPINT(i));
}
/* Disable all interrupts. */
stm32_putreg(0, STM32_OTGFS_GINTMSK);
/* Clear any pending USB_OTG Interrupts */
stm32_putreg(0xffffffff, STM32_OTGFS_GOTGINT);
/* Clear any pending interrupts */
stm32_putreg(0xbfffffff, STM32_OTGFS_GINTSTS);
/* Enable the interrupts in the INTMSK */
regval = (OTGFS_GINT_RXFLVL | OTGFS_GINT_USBSUSP | OTGFS_GINT_ENUMDNE |
OTGFS_GINT_IEP | OTGFS_GINT_OEP | regval);
#ifdef CONFIG_USBDEV_ISOCHRONOUS
regval |= (OTGFS_GINT_IISOIXFR | OTGFS_GINT_IISOOXFR);
#endif
#ifdef CONFIG_USBDEV_SOFINTERRUPT
regval |= OTGFS_GINT_SOF;
#endif
#ifdef CONFIG_USBDEV_VBUSSENSING
regval |= (OTGFS_GINT_OTG | OTGFS_GINT_SRQ);
#endif
#ifdef CONFIG_DEBUG_USB
regval |= OTGFS_GINT_MMIS;
#endif
stm32_putreg(regval, STM32_OTGFS_GINTMSK);
/* Ensable the USB global interrupt by setting GINTMSK in the global OTG
* FS AHB configuration register.
*/
stm32_putreg(OTGFS_GAHBCFG_GINTMSK, STM32_OTGFS_GAHBCFG);
}
/*******************************************************************************
* Public Functions
*******************************************************************************/
/*******************************************************************************
* Name: up_usbinitialize
*
* Description:
* Initialize USB hardware.
*
* Assumptions:
* - This function is called very early in the initialization sequence
* - PLL and GIO pin initialization is not performed here but should been in
* the low-level boot logic: PLL1 must be configured for operation at 48MHz
* and P0.23 and PO.31 in PINSEL1 must be configured for Vbus and USB connect
* LED.
*
*******************************************************************************/
void up_usbinitialize(void)
{
/* At present, there is only a single OTG FS device support. Hence it is
* pre-allocated as g_otgfsdev. However, in most code, the private data
* structure will be referenced using the 'priv' pointer (rather than the
* global data) in order to simplify any future support for multiple devices.
*/
FAR struct stm32_usbdev_s *priv = &g_otgfsdev;
int ret;
usbtrace(TRACE_DEVINIT, 0);
/* Here we assume that:
*
* 1. GPIOA and OTG FS peripheral clocking has already been enabled as part
* of the boot sequence.
* 2. Board-specific logic has already enabled other board specific GPIOs
* for things like soft pull-up, VBUS sensing, power controls, and over-
* current detection.
*/
/* Configure OTG FS alternate function pins */
stm32_configgpio(GPIO_OTGFS_DM);
stm32_configgpio(GPIO_OTGFS_DP);
stm32_configgpio(GPIO_OTGFS_ID);
stm32_configgpio(GPIO_OTGFS_SOF);
/* Uninitialize the hardware so that we know that we are starting from a
* known state. */
up_usbuninitialize();
/* Initialie the driver data structure */
stm32_swinitialize(priv);
/* Attach the OTG FS interrupt handler */
ret = irq_attach(STM32_IRQ_OTGFS, stm32_usbinterrupt);
if (ret < 0)
{
udbg("irq_attach failed\n", ret);
goto errout;
}
/* Initialize the USB OTG core */
stm32_hwinitialize(priv);
/* Disconnect device */
stm32_pullup(&priv->usbdev, false);
/* Reset/Re-initialize the USB hardware */
stm32_usbreset(priv);
/* Enable USB controller interrupts at the NVIC */
up_enable_irq(STM32_IRQ_OTGFS);
/* Set the interrrupt priority */
up_prioritize_irq(STM32_IRQ_OTGFS, CONFIG_OTGFS_PRI);
return;
errout:
up_usbuninitialize();
}
/*******************************************************************************
* Name: up_usbuninitialize
*******************************************************************************/
void up_usbuninitialize(void)
{
/* At present, there is only a single OTG FS device support. Hence it is
* pre-allocated as g_otgfsdev. However, in most code, the private data
* structure will be referenced using the 'priv' pointer (rather than the
* global data) in order to simplify any future support for multiple devices.
*/
FAR struct stm32_usbdev_s *priv = &g_otgfsdev;
irqstate_t flags;
int i;
usbtrace(TRACE_DEVUNINIT, 0);
if (priv->driver)
{
usbtrace(TRACE_DEVERROR(STM32_TRACEERR_DRIVERREGISTERED), 0);
usbdev_unregister(priv->driver);
}
/* Disconnect device */
flags = irqsave();
stm32_pullup(&priv->usbdev, false);
priv->usbdev.speed = USB_SPEED_UNKNOWN;
/* Disable and detach IRQs */
up_disable_irq(STM32_IRQ_OTGFS);
irq_detach(STM32_IRQ_OTGFS);
/* Disable all endpoint interrupts */
for (i = 0; i < STM32_NENDPOINTS; i++)
{
stm32_putreg(0xff, STM32_OTGFS_DIEPINT(i));
stm32_putreg(0xff, STM32_OTGFS_DOEPINT(i));
}
stm32_putreg(0, STM32_OTGFS_DIEPMSK);
stm32_putreg(0, STM32_OTGFS_DOEPMSK);
stm32_putreg(0, STM32_OTGFS_DAINTMSK);
stm32_putreg(0xffffffff, STM32_OTGFS_DAINT);
/* Flush the FIFOs */
stm32_txfifo_flush(OTGFS_GRSTCTL_TXFNUM_DALL);
stm32_rxfifo_flush();
/* TODO: Turn off USB power and clocking */
priv->devstate = DEVSTATE_DEFAULT;
irqrestore(flags);
}
/*******************************************************************************
* Name: usbdev_register
*
* Description:
* Register a USB device class driver. The class driver's bind() method will be
* called to bind it to a USB device driver.
*
*******************************************************************************/
int usbdev_register(struct usbdevclass_driver_s *driver)
{
int ret;
usbtrace(TRACE_DEVREGISTER, 0);
#ifdef CONFIG_DEBUG
if (!driver || !driver->ops->bind || !driver->ops->unbind ||
!driver->ops->disconnect || !driver->ops->setup)
{
usbtrace(TRACE_DEVERROR(STM32_TRACEERR_INVALIDPARMS), 0);
return -EINVAL;
}
if (g_otgfsdev.driver)
{
usbtrace(TRACE_DEVERROR(STM32_TRACEERR_DRIVER), 0);
return -EBUSY;
}
#endif
/* First hook up the driver */
g_otgfsdev.driver = driver;
/* Then bind the class driver */
ret = CLASS_BIND(driver, &g_otgfsdev.usbdev);
if (ret)
{
usbtrace(TRACE_DEVERROR(STM32_TRACEERR_BINDFAILED), (uint16_t)-ret);
g_otgfsdev.driver = NULL;
}
else
{
/* Enable USB controller interrupts */
up_enable_irq(STM32_IRQ_OTGFS);
/* FIXME: nothing seems to call DEV_CONNECT(), but we need to set
* the RS bit to enable the controller. It kind of makes sense
* to do this after the class has bound to us...
* GEN: This bug is really in the class driver. It should make the
* soft connect when it is ready to be enumerated. I have added
* that logic to the class drivers but left this logic here.
*/
stm32_pullup(&g_otgfsdev.usbdev, true);
}
return ret;
}
/*******************************************************************************
* Name: usbdev_unregister
*
* Description:
* Un-register usbdev class driver.If the USB device is connected to a USB host,
* it will first disconnect(). The driver is also requested to unbind() and clean
* up any device state, before this procedure finally returns.
*
*******************************************************************************/
int usbdev_unregister(struct usbdevclass_driver_s *driver)
{
usbtrace(TRACE_DEVUNREGISTER, 0);
#ifdef CONFIG_DEBUG
if (driver != g_otgfsdev.driver)
{
usbtrace(TRACE_DEVERROR(STM32_TRACEERR_INVALIDPARMS), 0);
return -EINVAL;
}
#endif
/* Unbind the class driver */
CLASS_UNBIND(driver, &g_otgfsdev.usbdev);
/* Disable USB controller interrupts */
up_disable_irq(STM32_IRQ_OTGFS);
/* Unhook the driver */
g_otgfsdev.driver = NULL;
return OK;
}
#endif /* CONFIG_USBDEV && CONFIG_STM32_OTGFSDEV */