5668 lines
167 KiB
C
5668 lines
167 KiB
C
/****************************************************************************
|
|
* arch/arm/src/stm32/stm32_otghsdev.c
|
|
*
|
|
* Copyright (C) 2012-2014 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/kmalloc.h>
|
|
#include <nuttx/usb/usb.h>
|
|
#include <nuttx/usb/usbdev.h>
|
|
#include <nuttx/usb/usbdev_trace.h>
|
|
|
|
#include <nuttx/irq.h>
|
|
#include <arch/board/board.h>
|
|
|
|
#include "chip.h"
|
|
#include "up_arch.h"
|
|
#include "up_internal.h"
|
|
|
|
#include "stm32_otghs.h"
|
|
|
|
#if defined(CONFIG_USBDEV) && (defined(CONFIG_STM32_OTGHS))
|
|
|
|
/****************************************************************************
|
|
* Pre-processor Definitions
|
|
****************************************************************************/
|
|
/* Configuration ***************************************************************/
|
|
|
|
#ifndef CONFIG_USBDEV_EP0_MAXSIZE
|
|
# define CONFIG_USBDEV_EP0_MAXSIZE 64
|
|
#endif
|
|
|
|
#ifndef CONFIG_USBDEV_SETUP_MAXDATASIZE
|
|
# define CONFIG_USBDEV_SETUP_MAXDATASIZE CONFIG_USBDEV_EP0_MAXSIZE
|
|
#endif
|
|
|
|
#ifndef CONFIG_USBDEV_MAXPOWER
|
|
# define CONFIG_USBDEV_MAXPOWER 100 /* mA */
|
|
#endif
|
|
|
|
#ifndef CONFIG_DEBUG_USB_INFO
|
|
# undef CONFIG_STM32_USBDEV_REGDEBUG
|
|
#endif
|
|
|
|
/* There is 1.25Kb of FIFO memory. The default partitions this memory
|
|
* so that there is a TxFIFO allocated for each endpoint and with more
|
|
* memory provided for the common RxFIFO. A more knowledge-able
|
|
* configuration would not allocate any TxFIFO space to OUT endpoints.
|
|
*/
|
|
|
|
#ifndef CONFIG_USBDEV_RXFIFO_SIZE
|
|
# define CONFIG_USBDEV_RXFIFO_SIZE 512
|
|
#endif
|
|
|
|
#ifndef CONFIG_USBDEV_EP0_TXFIFO_SIZE
|
|
# define CONFIG_USBDEV_EP0_TXFIFO_SIZE 192
|
|
#endif
|
|
|
|
#ifndef CONFIG_USBDEV_EP1_TXFIFO_SIZE
|
|
# define CONFIG_USBDEV_EP1_TXFIFO_SIZE 192
|
|
#endif
|
|
|
|
#ifndef CONFIG_USBDEV_EP2_TXFIFO_SIZE
|
|
# define CONFIG_USBDEV_EP2_TXFIFO_SIZE 192
|
|
#endif
|
|
|
|
#ifndef CONFIG_USBDEV_EP3_TXFIFO_SIZE
|
|
# define CONFIG_USBDEV_EP3_TXFIFO_SIZE 192
|
|
#endif
|
|
|
|
#if (CONFIG_USBDEV_RXFIFO_SIZE + CONFIG_USBDEV_EP0_TXFIFO_SIZE + \
|
|
CONFIG_USBDEV_EP2_TXFIFO_SIZE + CONFIG_USBDEV_EP3_TXFIFO_SIZE) > 1280
|
|
# error "FIFO allocations exceed FIFO memory size"
|
|
#endif
|
|
|
|
/* The actual FIFO addresses that we use must be aligned to 4-byte boundaries;
|
|
* FIFO sizes must be provided in units of 32-bit words.
|
|
*/
|
|
|
|
#define STM32_RXFIFO_BYTES ((CONFIG_USBDEV_RXFIFO_SIZE + 3) & ~3)
|
|
#define STM32_RXFIFO_WORDS ((CONFIG_USBDEV_RXFIFO_SIZE + 3) >> 2)
|
|
|
|
#define STM32_EP0_TXFIFO_BYTES ((CONFIG_USBDEV_EP0_TXFIFO_SIZE + 3) & ~3)
|
|
#define STM32_EP0_TXFIFO_WORDS ((CONFIG_USBDEV_EP0_TXFIFO_SIZE + 3) >> 2)
|
|
|
|
#if STM32_EP0_TXFIFO_WORDS < 16 || STM32_EP0_TXFIFO_WORDS > 256
|
|
# error "CONFIG_USBDEV_EP0_TXFIFO_SIZE is out of range"
|
|
#endif
|
|
|
|
#define STM32_EP1_TXFIFO_BYTES ((CONFIG_USBDEV_EP1_TXFIFO_SIZE + 3) & ~3)
|
|
#define STM32_EP1_TXFIFO_WORDS ((CONFIG_USBDEV_EP1_TXFIFO_SIZE + 3) >> 2)
|
|
|
|
#if STM32_EP1_TXFIFO_WORDS < 16
|
|
# error "CONFIG_USBDEV_EP1_TXFIFO_SIZE is out of range"
|
|
#endif
|
|
|
|
#define STM32_EP2_TXFIFO_BYTES ((CONFIG_USBDEV_EP2_TXFIFO_SIZE + 3) & ~3)
|
|
#define STM32_EP2_TXFIFO_WORDS ((CONFIG_USBDEV_EP2_TXFIFO_SIZE + 3) >> 2)
|
|
|
|
#if STM32_EP2_TXFIFO_WORDS < 16
|
|
# error "CONFIG_USBDEV_EP2_TXFIFO_SIZE is out of range"
|
|
#endif
|
|
|
|
#define STM32_EP3_TXFIFO_BYTES ((CONFIG_USBDEV_EP3_TXFIFO_SIZE + 3) & ~3)
|
|
#define STM32_EP3_TXFIFO_WORDS ((CONFIG_USBDEV_EP3_TXFIFO_SIZE + 3) >> 2)
|
|
|
|
#if STM32_EP3_TXFIFO_WORDS < 16
|
|
# error "CONFIG_USBDEV_EP3_TXFIFO_SIZE is out of range"
|
|
#endif
|
|
|
|
/* Debug ***********************************************************************/
|
|
/* Trace error codes */
|
|
|
|
#define STM32_TRACEERR_ALLOCFAIL 0x01
|
|
#define STM32_TRACEERR_BADCLEARFEATURE 0x02
|
|
#define STM32_TRACEERR_BADDEVGETSTATUS 0x03
|
|
#define STM32_TRACEERR_BADEPNO 0x04
|
|
#define STM32_TRACEERR_BADEPGETSTATUS 0x05
|
|
#define STM32_TRACEERR_BADGETCONFIG 0x06
|
|
#define STM32_TRACEERR_BADGETSETDESC 0x07
|
|
#define STM32_TRACEERR_BADGETSTATUS 0x08
|
|
#define STM32_TRACEERR_BADSETADDRESS 0x09
|
|
#define STM32_TRACEERR_BADSETCONFIG 0x0a
|
|
#define STM32_TRACEERR_BADSETFEATURE 0x0b
|
|
#define STM32_TRACEERR_BADTESTMODE 0x0c
|
|
#define STM32_TRACEERR_BINDFAILED 0x0d
|
|
#define STM32_TRACEERR_DISPATCHSTALL 0x0e
|
|
#define STM32_TRACEERR_DRIVER 0x0f
|
|
#define STM32_TRACEERR_DRIVERREGISTERED 0x10
|
|
#define STM32_TRACEERR_EP0NOSETUP 0x11
|
|
#define STM32_TRACEERR_EP0SETUPSTALLED 0x12
|
|
#define STM32_TRACEERR_EPINNULLPACKET 0x13
|
|
#define STM32_TRACEERR_EPINUNEXPECTED 0x14
|
|
#define STM32_TRACEERR_EPOUTNULLPACKET 0x15
|
|
#define STM32_TRACEERR_EPOUTUNEXPECTED 0x16
|
|
#define STM32_TRACEERR_INVALIDCTRLREQ 0x17
|
|
#define STM32_TRACEERR_INVALIDPARMS 0x18
|
|
#define STM32_TRACEERR_IRQREGISTRATION 0x19
|
|
#define STM32_TRACEERR_NOEP 0x1a
|
|
#define STM32_TRACEERR_NOTCONFIGURED 0x1b
|
|
#define STM32_TRACEERR_EPOUTQEMPTY 0x1c
|
|
#define STM32_TRACEERR_EPINREQEMPTY 0x1d
|
|
#define STM32_TRACEERR_NOOUTSETUP 0x1e
|
|
#define STM32_TRACEERR_POLLTIMEOUT 0x1f
|
|
|
|
/* Trace interrupt codes */
|
|
|
|
#define STM32_TRACEINTID_USB 1 /* USB Interrupt entry/exit */
|
|
#define STM32_TRACEINTID_INTPENDING 2 /* On each pass through the loop */
|
|
|
|
#define STM32_TRACEINTID_EPOUT (10 + 0) /* First level interrupt decode */
|
|
#define STM32_TRACEINTID_EPIN (10 + 1)
|
|
#define STM32_TRACEINTID_MISMATCH (10 + 2)
|
|
#define STM32_TRACEINTID_WAKEUP (10 + 3)
|
|
#define STM32_TRACEINTID_SUSPEND (10 + 4)
|
|
#define STM32_TRACEINTID_SOF (10 + 5)
|
|
#define STM32_TRACEINTID_RXFIFO (10 + 6)
|
|
#define STM32_TRACEINTID_DEVRESET (10 + 7)
|
|
#define STM32_TRACEINTID_ENUMDNE (10 + 8)
|
|
#define STM32_TRACEINTID_IISOIXFR (10 + 9)
|
|
#define STM32_TRACEINTID_IISOOXFR (10 + 10)
|
|
#define STM32_TRACEINTID_SRQ (10 + 11)
|
|
#define STM32_TRACEINTID_OTG (10 + 12)
|
|
|
|
#define STM32_TRACEINTID_EPOUT_XFRC (40 + 0) /* EPOUT second level decode */
|
|
#define STM32_TRACEINTID_EPOUT_EPDISD (40 + 1)
|
|
#define STM32_TRACEINTID_EPOUT_SETUP (40 + 2)
|
|
#define STM32_TRACEINTID_DISPATCH (40 + 3)
|
|
|
|
#define STM32_TRACEINTID_GETSTATUS (50 + 0) /* EPOUT third level decode */
|
|
#define STM32_TRACEINTID_EPGETSTATUS (50 + 1)
|
|
#define STM32_TRACEINTID_DEVGETSTATUS (50 + 2)
|
|
#define STM32_TRACEINTID_IFGETSTATUS (50 + 3)
|
|
#define STM32_TRACEINTID_CLEARFEATURE (50 + 4)
|
|
#define STM32_TRACEINTID_SETFEATURE (50 + 5)
|
|
#define STM32_TRACEINTID_SETADDRESS (50 + 6)
|
|
#define STM32_TRACEINTID_GETSETDESC (50 + 7)
|
|
#define STM32_TRACEINTID_GETCONFIG (50 + 8)
|
|
#define STM32_TRACEINTID_SETCONFIG (50 + 9)
|
|
#define STM32_TRACEINTID_GETSETIF (50 + 10)
|
|
#define STM32_TRACEINTID_SYNCHFRAME (50 + 11)
|
|
|
|
#define STM32_TRACEINTID_EPIN_XFRC (70 + 0) /* EPIN second level decode */
|
|
#define STM32_TRACEINTID_EPIN_TOC (70 + 1)
|
|
#define STM32_TRACEINTID_EPIN_ITTXFE (70 + 2)
|
|
#define STM32_TRACEINTID_EPIN_EPDISD (70 + 3)
|
|
#define STM32_TRACEINTID_EPIN_TXFE (70 + 4)
|
|
|
|
#define STM32_TRACEINTID_EPIN_EMPWAIT (80 + 0) /* EPIN second level decode */
|
|
|
|
#define STM32_TRACEINTID_OUTNAK (90 + 0) /* RXFLVL second level decode */
|
|
#define STM32_TRACEINTID_OUTRECVD (90 + 1)
|
|
#define STM32_TRACEINTID_OUTDONE (90 + 2)
|
|
#define STM32_TRACEINTID_SETUPDONE (90 + 3)
|
|
#define STM32_TRACEINTID_SETUPRECVD (90 + 4)
|
|
|
|
/* 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_OUT, /* OUT SETUP packet received. Waiting for the DATA
|
|
* OUT phase of SETUP Packet to complete before
|
|
* processing a SETUP command (without a USB request):
|
|
* SET: Set in stm32_rxinterrupt() when SETUP OUT
|
|
* packet is received.
|
|
* TESTED: In stm32_ep0out_receive()
|
|
*/
|
|
EP0STATE_SETUP_READY, /* IN SETUP packet received -OR- OUT SETUP packet and
|
|
* accompanying data have been received. Processing
|
|
* of SETUP command will happen soon.
|
|
* SET: (1) stm32_ep0out_receive() when the OUT
|
|
* SETUP data phase completes, or (2)
|
|
* stm32_rxinterrupt() when an IN SETUP is
|
|
* packet received.
|
|
* TESTED: Tested in stm32_epout_interrupt() when
|
|
* SETUP phase is done to see if the SETUP
|
|
* command is ready to be processed. Also
|
|
* tested in stm32_ep0out_setup() just to
|
|
* double-check that we have a SETUP request
|
|
* and any accompanying data.
|
|
*/
|
|
EP0STATE_SETUP_PROCESS, /* SETUP Packet is being processed 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 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 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 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[2]; /* Bitset of available OUT/IN endpoints */
|
|
|
|
/* E0 SETUP data buffering.
|
|
*
|
|
* ctrlreq:
|
|
* The 8-byte SETUP request is received on the EP0 OUT endpoint and is
|
|
* saved.
|
|
*
|
|
* ep0data
|
|
* For OUT SETUP requests, the SETUP data phase must also complete before
|
|
* the SETUP command can be processed. The pack receipt logic will save
|
|
* the accompanying EP0 IN data in ep0data[] before the SETUP command is
|
|
* processed.
|
|
*
|
|
* For IN SETUP requests, the DATA phase will occur AFTER the SETUP
|
|
* control request is processed. In that case, ep0data[] may be used as
|
|
* the response buffer.
|
|
*
|
|
* ep0datlen
|
|
* Length of OUT DATA received in ep0data[] (Not used with OUT data)
|
|
*/
|
|
|
|
struct usb_ctrlreq_s ctrlreq;
|
|
uint8_t ep0data[CONFIG_USBDEV_SETUP_MAXDATASIZE];
|
|
uint16_t ep0datlen;
|
|
|
|
/* The endpoint lists */
|
|
|
|
struct stm32_ep_s epin[STM32_NENDPOINTS];
|
|
struct stm32_ep_s epout[STM32_NENDPOINTS];
|
|
};
|
|
|
|
/****************************************************************************
|
|
* Private Function Prototypes
|
|
****************************************************************************/
|
|
|
|
/* Register operations ********************************************************/
|
|
|
|
#ifdef CONFIG_STM32_USBDEV_REGDEBUG
|
|
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_rxfifo_discard(FAR struct stm32_ep_s *privep, int len);
|
|
static void stm32_epout_complete(FAR struct stm32_usbdev_s *priv,
|
|
FAR struct stm32_ep_s *privep);
|
|
static inline void stm32_ep0out_receive(FAR struct stm32_ep_s *privep, int bcnt);
|
|
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, FAR void *arg);
|
|
|
|
/* 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_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_otghsdev;
|
|
|
|
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_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,
|
|
};
|
|
|
|
/* Device error strings that may be enabled for more descriptive USB trace
|
|
* output.
|
|
*/
|
|
|
|
#ifdef CONFIG_USBDEV_TRACE_STRINGS
|
|
const struct trace_msg_t g_usb_trace_strings_deverror[] =
|
|
{
|
|
TRACE_STR(STM32_TRACEERR_ALLOCFAIL ),
|
|
TRACE_STR(STM32_TRACEERR_BADCLEARFEATURE ),
|
|
TRACE_STR(STM32_TRACEERR_BADDEVGETSTATUS ),
|
|
TRACE_STR(STM32_TRACEERR_BADEPNO ),
|
|
TRACE_STR(STM32_TRACEERR_BADEPGETSTATUS ),
|
|
TRACE_STR(STM32_TRACEERR_BADGETCONFIG ),
|
|
TRACE_STR(STM32_TRACEERR_BADGETSETDESC ),
|
|
TRACE_STR(STM32_TRACEERR_BADGETSTATUS ),
|
|
TRACE_STR(STM32_TRACEERR_BADSETADDRESS ),
|
|
TRACE_STR(STM32_TRACEERR_BADSETCONFIG ),
|
|
TRACE_STR(STM32_TRACEERR_BADSETFEATURE ),
|
|
TRACE_STR(STM32_TRACEERR_BADTESTMODE ),
|
|
TRACE_STR(STM32_TRACEERR_BINDFAILED ),
|
|
TRACE_STR(STM32_TRACEERR_DISPATCHSTALL ),
|
|
TRACE_STR(STM32_TRACEERR_DRIVER ),
|
|
TRACE_STR(STM32_TRACEERR_DRIVERREGISTERED),
|
|
TRACE_STR(STM32_TRACEERR_EP0NOSETUP ),
|
|
TRACE_STR(STM32_TRACEERR_EP0SETUPSTALLED ),
|
|
TRACE_STR(STM32_TRACEERR_EPINNULLPACKET ),
|
|
TRACE_STR(STM32_TRACEERR_EPINUNEXPECTED ),
|
|
TRACE_STR(STM32_TRACEERR_EPOUTNULLPACKET ),
|
|
TRACE_STR(STM32_TRACEERR_EPOUTUNEXPECTED ),
|
|
TRACE_STR(STM32_TRACEERR_INVALIDCTRLREQ ),
|
|
TRACE_STR(STM32_TRACEERR_INVALIDPARMS ),
|
|
TRACE_STR(STM32_TRACEERR_IRQREGISTRATION ),
|
|
TRACE_STR(STM32_TRACEERR_NOEP ),
|
|
TRACE_STR(STM32_TRACEERR_NOTCONFIGURED ),
|
|
TRACE_STR(STM32_TRACEERR_EPOUTQEMPTY ),
|
|
TRACE_STR(STM32_TRACEERR_EPINREQEMPTY ),
|
|
TRACE_STR(STM32_TRACEERR_NOOUTSETUP ),
|
|
TRACE_STR(STM32_TRACEERR_POLLTIMEOUT ),
|
|
TRACE_STR_END
|
|
};
|
|
#endif
|
|
|
|
/* Interrupt event strings that may be enabled for more descriptive USB trace
|
|
* output.
|
|
*/
|
|
|
|
#ifdef CONFIG_USBDEV_TRACE_STRINGS
|
|
const struct trace_msg_t g_usb_trace_strings_intdecode[] =
|
|
{
|
|
TRACE_STR(STM32_TRACEINTID_USB ),
|
|
TRACE_STR(STM32_TRACEINTID_INTPENDING ),
|
|
TRACE_STR(STM32_TRACEINTID_EPOUT ),
|
|
TRACE_STR(STM32_TRACEINTID_EPIN ),
|
|
TRACE_STR(STM32_TRACEINTID_MISMATCH ),
|
|
TRACE_STR(STM32_TRACEINTID_WAKEUP ),
|
|
TRACE_STR(STM32_TRACEINTID_SUSPEND ),
|
|
TRACE_STR(STM32_TRACEINTID_SOF ),
|
|
TRACE_STR(STM32_TRACEINTID_RXFIFO ),
|
|
TRACE_STR(STM32_TRACEINTID_DEVRESET ),
|
|
TRACE_STR(STM32_TRACEINTID_ENUMDNE ),
|
|
TRACE_STR(STM32_TRACEINTID_IISOIXFR ),
|
|
TRACE_STR(STM32_TRACEINTID_IISOOXFR ),
|
|
TRACE_STR(STM32_TRACEINTID_SRQ ),
|
|
TRACE_STR(STM32_TRACEINTID_OTG ),
|
|
TRACE_STR(STM32_TRACEINTID_EPOUT_XFRC ),
|
|
TRACE_STR(STM32_TRACEINTID_EPOUT_EPDISD),
|
|
TRACE_STR(STM32_TRACEINTID_EPOUT_SETUP ),
|
|
TRACE_STR(STM32_TRACEINTID_DISPATCH ),
|
|
TRACE_STR(STM32_TRACEINTID_GETSTATUS ),
|
|
TRACE_STR(STM32_TRACEINTID_EPGETSTATUS ),
|
|
TRACE_STR(STM32_TRACEINTID_DEVGETSTATUS),
|
|
TRACE_STR(STM32_TRACEINTID_IFGETSTATUS ),
|
|
TRACE_STR(STM32_TRACEINTID_CLEARFEATURE),
|
|
TRACE_STR(STM32_TRACEINTID_SETFEATURE ),
|
|
TRACE_STR(STM32_TRACEINTID_SETADDRESS ),
|
|
TRACE_STR(STM32_TRACEINTID_GETSETDESC ),
|
|
TRACE_STR(STM32_TRACEINTID_GETCONFIG ),
|
|
TRACE_STR(STM32_TRACEINTID_SETCONFIG ),
|
|
TRACE_STR(STM32_TRACEINTID_GETSETIF ),
|
|
TRACE_STR(STM32_TRACEINTID_SYNCHFRAME ),
|
|
TRACE_STR(STM32_TRACEINTID_EPIN_XFRC ),
|
|
TRACE_STR(STM32_TRACEINTID_EPIN_TOC ),
|
|
TRACE_STR(STM32_TRACEINTID_EPIN_ITTXFE ),
|
|
TRACE_STR(STM32_TRACEINTID_EPIN_EPDISD ),
|
|
TRACE_STR(STM32_TRACEINTID_EPIN_TXFE ),
|
|
TRACE_STR(STM32_TRACEINTID_EPIN_EMPWAIT),
|
|
TRACE_STR(STM32_TRACEINTID_OUTNAK ),
|
|
TRACE_STR(STM32_TRACEINTID_OUTRECVD ),
|
|
TRACE_STR(STM32_TRACEINTID_OUTDONE ),
|
|
TRACE_STR(STM32_TRACEINTID_SETUPDONE ),
|
|
TRACE_STR(STM32_TRACEINTID_SETUPRECVD ),
|
|
TRACE_STR_END
|
|
};
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Public Data
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Private Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_getreg
|
|
*
|
|
* Description:
|
|
* Get the contents of an STM32 register
|
|
*
|
|
****************************************************************************/
|
|
|
|
#ifdef CONFIG_STM32_USBDEV_REGDEBUG
|
|
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 register 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)
|
|
{
|
|
uinfo("...\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 */
|
|
|
|
uinfo("[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 */
|
|
|
|
uinfo("%08x->%08x\n", addr, val);
|
|
return val;
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_putreg
|
|
*
|
|
* Description:
|
|
* Set the contents of an STM32 register to a value
|
|
*
|
|
****************************************************************************/
|
|
|
|
#ifdef CONFIG_STM32_USBDEV_REGDEBUG
|
|
static void stm32_putreg(uint32_t val, uint32_t addr)
|
|
{
|
|
/* Show the register value being written */
|
|
|
|
uinfo("%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_OTGHS_DIEPCTL0);
|
|
regval &= ~OTGHS_DIEPCTL0_MPSIZ_MASK;
|
|
|
|
#if CONFIG_USBDEV_EP0_MAXSIZE == 8
|
|
regval |= OTGHS_DIEPCTL0_MPSIZ_8;
|
|
#elif CONFIG_USBDEV_EP0_MAXSIZE == 16
|
|
regval |= OTGHS_DIEPCTL0_MPSIZ_16;
|
|
#elif CONFIG_USBDEV_EP0_MAXSIZE == 32
|
|
regval |= OTGHS_DIEPCTL0_MPSIZ_32;
|
|
#elif CONFIG_USBDEV_EP0_MAXSIZE == 64
|
|
regval |= OTGHS_DIEPCTL0_MPSIZ_64;
|
|
#else
|
|
# error "Unsupported value of CONFIG_USBDEV_EP0_MAXSIZE"
|
|
#endif
|
|
|
|
stm32_putreg(regval, STM32_OTGHS_DIEPCTL0);
|
|
|
|
/* Clear global IN NAK */
|
|
|
|
regval = stm32_getreg(STM32_OTGHS_DCTL);
|
|
regval |= OTGHS_DCTL_CGINAK;
|
|
stm32_putreg(regval, STM32_OTGHS_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;
|
|
|
|
/* Setup the hardware to perform the SETUP transfer */
|
|
|
|
regval = (USB_SIZEOF_CTRLREQ * 3 << OTGHS_DOEPTSIZ0_XFRSIZ_SHIFT) |
|
|
(OTGHS_DOEPTSIZ0_PKTCNT) |
|
|
(3 << OTGHS_DOEPTSIZ0_STUPCNT_SHIFT);
|
|
stm32_putreg(regval, STM32_OTGHS_DOEPTSIZ0);
|
|
|
|
/* Then clear NAKing and enable the transfer */
|
|
|
|
regval = stm32_getreg(STM32_OTGHS_DOEPCTL0);
|
|
regval |= (OTGHS_DOEPCTL0_CNAK | OTGHS_DOEPCTL0_EPENA);
|
|
stm32_putreg(regval, STM32_OTGHS_DOEPCTL0);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* 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_OTGHS_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 packet 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_OTGHS_DIEPTSIZ(privep->epphy));
|
|
|
|
/* Clear the XFRSIZ, PKTCNT, and MCNT field of the DIEPSIZx register */
|
|
|
|
regval &= ~(OTGHS_DIEPTSIZ_XFRSIZ_MASK | OTGHS_DIEPTSIZ_PKTCNT_MASK |
|
|
OTGHS_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;
|
|
}
|
|
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 << OTGHS_DIEPTSIZ_PKTCNT_SHIFT);
|
|
regval |= ((uint32_t)nbytes << OTGHS_DIEPTSIZ_XFRSIZ_SHIFT);
|
|
|
|
/* If this is an isochronous endpoint, then set the multi-count field to
|
|
* the PKTCNT as well.
|
|
*/
|
|
|
|
if (privep->eptype == USB_EP_ATTR_XFER_ISOC)
|
|
{
|
|
regval |= (pktcnt << OTGHS_DIEPTSIZ_MCNT_SHIFT);
|
|
}
|
|
|
|
/* Save DIEPSIZx register value */
|
|
|
|
stm32_putreg(regval, STM32_OTGHS_DIEPTSIZ(privep->epphy));
|
|
|
|
/* Read the DIEPCTLx register */
|
|
|
|
regval = stm32_getreg(STM32_OTGHS_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_OTGHS_DSTS);
|
|
if ((status & OTGHS_DSTS_SOFFN0) == OTGHS_DSTS_SOFFN_EVEN)
|
|
{
|
|
regval |= OTGHS_DIEPCTL_SEVNFRM;
|
|
}
|
|
else
|
|
{
|
|
regval |= OTGHS_DIEPCTL_SODDFRM;
|
|
}
|
|
}
|
|
|
|
/* EP enable, IN data in FIFO */
|
|
|
|
regval &= ~OTGHS_DIEPCTL_EPDIS;
|
|
regval |= (OTGHS_DIEPCTL_CNAK | OTGHS_DIEPCTL_EPENA);
|
|
stm32_putreg(regval, STM32_OTGHS_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 in one of four possible ways. From three interrupting
|
|
* events:
|
|
*
|
|
* 1. From stm32_epin as part of the transfer complete interrupt processing
|
|
* This interrupt indicates that the last transfer has completed.
|
|
* 2. As part of the ITTXFE interrupt processing. That interrupt indicates
|
|
* that an IN token was received when the associated TxFIFO was empty.
|
|
* 3. From stm32_epin_txfifoempty as part of the TXFE interrupt processing.
|
|
* The TXFE interrupt is only enabled when the TxFIFO is full and the
|
|
* software must wait for space to become available in the TxFIFO.
|
|
*
|
|
* And this function may be called immediately when the write request is
|
|
* queue to start up the next transaction.
|
|
*
|
|
* 4. From stm32_ep_submit when a new write request is received WHILE the
|
|
* endpoint is not active (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_EPINREQEMPTY), 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_OTGHS_DIEPCTL(privep->epphy);
|
|
regval = stm32_getreg(regaddr);
|
|
regval |= OTGHS_DIEPCTL_SNAK;
|
|
stm32_putreg(regval, regaddr);
|
|
|
|
/* The endpoint is no longer active */
|
|
|
|
privep->active = false;
|
|
return;
|
|
}
|
|
|
|
uinfo("EP%d req=%p: len=%d xfrd=%d zlp=%d\n",
|
|
privep->epphy, privreq, privreq->req.len,
|
|
privreq->req.xfrd, privep->zlp);
|
|
|
|
/* Check for a special case: If we are just starting a request (xfrd==0) and
|
|
* the class driver is trying to send a zero-length packet (len==0). Then set
|
|
* the ZLP flag so that the packet will be sent.
|
|
*/
|
|
|
|
if (privreq->req.len == 0)
|
|
{
|
|
/* The ZLP flag is set TRUE whenever we want to force the driver to
|
|
* send a zero-length-packet on the next pass through the loop (below).
|
|
* The flag is cleared whenever a packet is sent in the loop below.
|
|
*/
|
|
|
|
privep->zlp = true;
|
|
}
|
|
|
|
/* Add one more packet to the TxFIFO. We will wait for the transfer
|
|
* complete event before we add the next packet (or part of a packet
|
|
* to the TxFIFO).
|
|
*
|
|
* The documentation says that we can can multiple packets to the TxFIFO,
|
|
* but it seems that we need to get the transfer complete event before
|
|
* we can add the next (or maybe I have got something wrong?)
|
|
*/
|
|
|
|
#if 0
|
|
while (privreq->req.xfrd < privreq->req.len || privep->zlp)
|
|
#else
|
|
if (privreq->req.xfrd < privreq->req.len || privep->zlp)
|
|
#endif
|
|
{
|
|
/* Get the number of bytes left to be sent in the request */
|
|
|
|
bytesleft = privreq->req.len - privreq->req.xfrd;
|
|
nbytes = bytesleft;
|
|
|
|
/* Assume no zero-length-packet on the next pass through this loop */
|
|
|
|
privep->zlp = false;
|
|
|
|
/* 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.
|
|
*/
|
|
|
|
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)
|
|
{
|
|
/* The ZLP flag is set TRUE whenever we want to force
|
|
* the driver to send a zero-length-packet on the next
|
|
* pass through this loop. The flag is cleared (above)
|
|
* whenever we are committed to sending any packet and
|
|
* set here when we want to force one more pass through
|
|
* the loop.
|
|
*/
|
|
|
|
privep->zlp = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Get the transfer size in 32-bit words */
|
|
|
|
nwords = (nbytes + 3) >> 2;
|
|
|
|
/* Get the number of 32-bit words available in the TxFIFO. The
|
|
* DXTHSTS 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_OTGHS_DTXFSTS(privep->epphy);
|
|
|
|
/* Check for space in the TxFIFO. If space in the TxFIFO is not
|
|
* available, then set up an interrupt to resume the transfer when
|
|
* the TxFIFO is empty.
|
|
*/
|
|
|
|
regval = stm32_getreg(regaddr);
|
|
if ((int)(regval & OTGHS_DTXFSTS_MASK) < nwords)
|
|
{
|
|
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_EPIN_EMPWAIT), (uint16_t)regval);
|
|
|
|
/* There is insufficient space in the TxFIFO. Wait for a TxFIFO
|
|
* empty interrupt and try again.
|
|
*/
|
|
|
|
uint32_t empmsk = stm32_getreg(STM32_OTGHS_DIEPEMPMSK);
|
|
empmsk |= OTGHS_DIEPEMPMSK(privep->epphy);
|
|
stm32_putreg(empmsk, STM32_OTGHS_DIEPEMPMSK);
|
|
|
|
/* Terminate the transfer. We will try again when the TxFIFO empty
|
|
* interrupt is received.
|
|
*/
|
|
|
|
return;
|
|
}
|
|
|
|
/* 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);
|
|
|
|
/* We are finished with the request (although the transfer has not
|
|
* yet completed).
|
|
*/
|
|
|
|
stm32_req_complete(privep, OK);
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_rxfifo_read
|
|
*
|
|
* Description:
|
|
* Read packet from the RxFIFO into a read request.
|
|
*
|
|
****************************************************************************/
|
|
|
|
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 RxFIFO. Note: there is only one RxFIFO so
|
|
* we might as well use the address associated with EP0.
|
|
*/
|
|
|
|
regaddr = STM32_OTGHS_DFIFO_DEP(EP0);
|
|
|
|
/* 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_rxfifo_discard
|
|
*
|
|
* Description:
|
|
* Discard packet data from the RxFIFO.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void stm32_rxfifo_discard(FAR struct stm32_ep_s *privep, int len)
|
|
{
|
|
if (len > 0)
|
|
{
|
|
uint32_t regaddr;
|
|
int i;
|
|
|
|
/* Get the address of the RxFIFO Note: there is only one RxFIFO so
|
|
* we might as well use the address associated with EP0.
|
|
*/
|
|
|
|
regaddr = STM32_OTGHS_DFIFO_DEP(EP0);
|
|
|
|
/* Read 32-bits at time */
|
|
|
|
for (i = 0; i < len; i += 4)
|
|
{
|
|
volatile uint32_t data = stm32_getreg(regaddr);
|
|
(void)data;
|
|
}
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* 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;
|
|
}
|
|
|
|
uinfo("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_ep0out_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 request's data buffer.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static inline void stm32_ep0out_receive(FAR struct stm32_ep_s *privep, int bcnt)
|
|
{
|
|
FAR struct stm32_usbdev_s *priv;
|
|
|
|
/* Sanity Checking */
|
|
|
|
DEBUGASSERT(privep && privep->ep.priv);
|
|
priv = (FAR struct stm32_usbdev_s *)privep->ep.priv;
|
|
|
|
uinfo("EP0: bcnt=%d\n", bcnt);
|
|
usbtrace(TRACE_READ(EP0), bcnt);
|
|
|
|
/* Verify that an OUT SETUP request as received before this data was
|
|
* received in the RxFIFO.
|
|
*/
|
|
|
|
if (priv->ep0state == EP0STATE_SETUP_OUT)
|
|
{
|
|
/* Read the data into our special buffer for SETUP data */
|
|
|
|
int readlen = MIN(CONFIG_USBDEV_SETUP_MAXDATASIZE, bcnt);
|
|
stm32_rxfifo_read(privep, priv->ep0data, readlen);
|
|
|
|
/* Do we have to discard any excess bytes? */
|
|
|
|
stm32_rxfifo_discard(privep, bcnt - readlen);
|
|
|
|
/* Now we can process the setup command */
|
|
|
|
privep->active = false;
|
|
priv->ep0state = EP0STATE_SETUP_READY;
|
|
priv->ep0datlen = readlen;
|
|
|
|
stm32_ep0out_setup(priv);
|
|
}
|
|
else
|
|
{
|
|
/* This is an error. We don't have any idea what to do with the EP0
|
|
* data in this case. Just read and discard it so that the RxFIFO
|
|
* does not become constipated.
|
|
*/
|
|
|
|
usbtrace(TRACE_DEVERROR(STM32_TRACEERR_NOOUTSETUP), priv->ep0state);
|
|
stm32_rxfifo_discard(privep, bcnt);
|
|
privep->active = false;
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* 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 request's 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);
|
|
if (!privreq)
|
|
{
|
|
/* Incoming data is available in the RxFIFO, but there is no read setup
|
|
* to receive the receive the data. This should not happen for data
|
|
* endpoints; those endpoints should have been NAKing any OUT data tokens.
|
|
*
|
|
* We should get here normally on OUT data phase following an OUT
|
|
* SETUP command. EP0 data will still receive data in this case and it
|
|
* should not be NAKing.
|
|
*/
|
|
|
|
if (privep->epphy == 0)
|
|
{
|
|
stm32_ep0out_receive(privep, bcnt);
|
|
}
|
|
else
|
|
{
|
|
/* Otherwise, the data is lost. This really should not happen if
|
|
* NAKing is working as expected.
|
|
*/
|
|
|
|
usbtrace(TRACE_DEVERROR(STM32_TRACEERR_EPOUTQEMPTY), privep->epphy);
|
|
|
|
/* Discard the data in the RxFIFO */
|
|
|
|
stm32_rxfifo_discard(privep, bcnt);
|
|
}
|
|
|
|
privep->active = false;
|
|
return;
|
|
}
|
|
|
|
uinfo("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);
|
|
|
|
/* If there were more bytes in the RxFIFO than could be held in the read
|
|
* request, then we will have to discard those.
|
|
*/
|
|
|
|
stm32_rxfifo_discard(privep, bcnt - 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_OTGHS_DOEPCTL(privep->epphy);
|
|
regval = stm32_getreg(regaddr);
|
|
regval |= OTGHS_DOEPCTL_SNAK;
|
|
stm32_putreg(regval, regaddr);
|
|
|
|
/* This endpoint is no longer actively transferring */
|
|
|
|
privep->active = false;
|
|
return;
|
|
}
|
|
|
|
uinfo("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_OTGHS_DOEPTSIZ(privep->epphy);
|
|
regval = stm32_getreg(regaddr);
|
|
regval &= ~(OTGHS_DOEPTSIZ_XFRSIZ_MASK | OTGHS_DOEPTSIZ_PKTCNT_MASK);
|
|
regval |= (xfrsize << OTGHS_DOEPTSIZ_XFRSIZ_SHIFT);
|
|
regval |= (pktcnt << OTGHS_DOEPTSIZ_PKTCNT_SHIFT);
|
|
stm32_putreg(regval, regaddr);
|
|
|
|
/* Then enable the transfer */
|
|
|
|
regaddr = STM32_OTGHS_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 |= OTGHS_DOEPCTL_SODDFRM;
|
|
}
|
|
else
|
|
{
|
|
regval |= OTGHS_DOEPCTL_SEVNFRM;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* Clearing NAKing and enable the transfer. */
|
|
|
|
regval |= (OTGHS_DOEPCTL_CNAK | OTGHS_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(OTGHS_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];
|
|
}
|
|
|
|
/* 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,
|
|
priv->ep0data, priv->ep0datlen);
|
|
}
|
|
|
|
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_OTGHS_DCTL);
|
|
regval &= ~OTGHS_DCTL_RWUSIG;
|
|
stm32_putreg(regval, STM32_OTGHS_DCTL);
|
|
|
|
/* Flush the EP0 Tx FIFO */
|
|
|
|
stm32_txfifo_flush(OTGHS_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);
|
|
}
|
|
|
|
/* Mark all endpoints as available */
|
|
|
|
priv->epavail[0] = STM32_EP_AVAILABLE;
|
|
priv->epavail[1] = STM32_EP_AVAILABLE;
|
|
|
|
/* Disable all end point interrupts */
|
|
|
|
for (i = 0; i < STM32_NENDPOINTS ; i++)
|
|
{
|
|
/* Disable endpoint interrupts */
|
|
|
|
stm32_putreg(0xff, STM32_OTGHS_DIEPINT(i));
|
|
stm32_putreg(0xff, STM32_OTGHS_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_OTGHS_DAINT);
|
|
|
|
/* Mask all device endpoint interrupts except EP0 */
|
|
|
|
regval = (OTGHS_DAINT_IEP(EP0) | OTGHS_DAINT_OEP(EP0));
|
|
stm32_putreg(regval, STM32_OTGHS_DAINTMSK);
|
|
|
|
/* Unmask OUT interrupts */
|
|
|
|
regval = (OTGHS_DOEPMSK_XFRCM | OTGHS_DOEPMSK_STUPM | OTGHS_DOEPMSK_EPDM);
|
|
stm32_putreg(regval, STM32_OTGHS_DOEPMSK);
|
|
|
|
/* Unmask IN interrupts */
|
|
|
|
regval = (OTGHS_DIEPMSK_XFRCM | OTGHS_DIEPMSK_EPDM | OTGHS_DIEPMSK_TOM);
|
|
stm32_putreg(regval, STM32_OTGHS_DIEPMSK);
|
|
|
|
/* Reset device address to 0 */
|
|
|
|
stm32_setaddress(priv, 0);
|
|
priv->devstate = DEVSTATE_DEFAULT;
|
|
priv->usbdev.speed = USB_SPEED_FULL;
|
|
|
|
/* 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)
|
|
{
|
|
uint8_t testmode;
|
|
|
|
testmode = index >> 8;
|
|
switch (testmode)
|
|
{
|
|
case 1:
|
|
priv->testmode = OTGHS_TESTMODE_J;
|
|
break;
|
|
|
|
case 2:
|
|
priv->testmode = OTGHS_TESTMODE_K;
|
|
break;
|
|
|
|
case 3:
|
|
priv->testmode = OTGHS_TESTMODE_SE0_NAK;
|
|
break;
|
|
|
|
case 4:
|
|
priv->testmode = OTGHS_TESTMODE_PACKET;
|
|
break;
|
|
|
|
case 5:
|
|
priv->testmode = OTGHS_TESTMODE_FORCE;
|
|
break;
|
|
|
|
default:
|
|
usbtrace(TRACE_DEVERROR(STM32_TRACEERR_BADTESTMODE), testmode);
|
|
priv->dotest = false;
|
|
priv->testmode = OTGHS_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 ||
|
|
USB_REQ_ISOUT(ctrlreq->type) ||
|
|
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->ep0data[0] = (1 << USB_FEATURE_ENDPOINTHALT);
|
|
}
|
|
else
|
|
{
|
|
priv->ep0data[0] = 0; /* Not stalled */
|
|
}
|
|
|
|
priv->ep0data[1] = 0;
|
|
stm32_ep0in_setupresponse(priv, priv->ep0data, 2);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case USB_REQ_RECIPIENT_DEVICE:
|
|
{
|
|
if (ctrlreq->index == 0)
|
|
{
|
|
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_DEVGETSTATUS), 0);
|
|
|
|
/* Features: Remote Wakeup and self-powered */
|
|
|
|
priv->ep0data[0] = (priv->selfpowered << USB_FEATURE_SELFPOWERED);
|
|
priv->ep0data[0] |= (priv->wakeup << USB_FEATURE_REMOTEWAKEUP);
|
|
priv->ep0data[1] = 0;
|
|
|
|
stm32_ep0in_setupresponse(priv, priv->ep0data, 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->ep0data[0] = 0;
|
|
priv->ep0data[1] = 0;
|
|
|
|
stm32_ep0in_setupresponse(priv, priv->ep0data, 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->ep0state != EP0STATE_SETUP_READY)
|
|
{
|
|
usbtrace(TRACE_DEVERROR(STM32_TRACEERR_EP0NOSETUP), priv->ep0state);
|
|
return;
|
|
}
|
|
|
|
/* Terminate any pending requests */
|
|
|
|
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 to process a control request - update state */
|
|
|
|
priv->ep0state = EP0STATE_SETUP_PROCESS;
|
|
|
|
/* 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);
|
|
|
|
uinfo("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);
|
|
}
|
|
|
|
/* Reset state/data associated with thie SETUP request */
|
|
|
|
priv->ep0datlen = 0;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* 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 OTGHS DAINT register to determine the exact number
|
|
* of the OUT endpoint on which the interrupt occurred, and then read the
|
|
* corresponding OTGHS 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_OTGHS_DAINT);
|
|
regval &= stm32_getreg(STM32_OTGHS_DAINTMSK);
|
|
daint = (regval & OTGHS_DAINT_OEP_MASK) >> OTGHS_DAINT_OEP_SHIFT;
|
|
|
|
if (daint == 0)
|
|
{
|
|
/* We got an interrupt, but there is no unmasked endpoint that caused
|
|
* it ?! When this happens, the interrupt flag never gets cleared and
|
|
* we are stuck in infinite interrupt loop.
|
|
*
|
|
* This shouldn't happen if we are diligent about handling timing
|
|
* issues when masking endpoint interrupts. However, this workaround
|
|
* avoids infinite loop and allows operation to continue normally. It
|
|
* works by clearing each endpoint flags, masked or not.
|
|
*/
|
|
|
|
regval = stm32_getreg(STM32_OTGHS_DAINT);
|
|
daint = (regval & OTGHS_DAINT_OEP_MASK) >> OTGHS_DAINT_OEP_SHIFT;
|
|
|
|
usbtrace(TRACE_DEVERROR(STM32_TRACEERR_EPOUTUNEXPECTED),
|
|
(uint16_t)regval);
|
|
|
|
epno = 0;
|
|
while (daint)
|
|
{
|
|
if ((daint & 1) != 0)
|
|
{
|
|
regval = stm32_getreg(STM32_OTGHS_DOEPINT(epno));
|
|
uinfo("DOEPINT(%d) = %08x\n", epno, regval);
|
|
stm32_putreg(0xFF, STM32_OTGHS_DOEPINT(epno));
|
|
}
|
|
|
|
epno++;
|
|
daint >>= 1;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
/* 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_OTGHS_DOEPINT(epno));
|
|
doepint &= stm32_getreg(STM32_OTGHS_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 & OTGHS_DOEPINT_XFRC) != 0)
|
|
{
|
|
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_EPOUT_XFRC), (uint16_t)doepint);
|
|
|
|
/* Clear the bit in DOEPINTn for this interrupt */
|
|
|
|
stm32_putreg(OTGHS_DOEPINT_XFRC, STM32_OTGHS_DOEPINT(epno));
|
|
|
|
/* Handle the RX transfer data ready event */
|
|
|
|
stm32_epout(priv, epno);
|
|
}
|
|
|
|
/* Endpoint disabled interrupt (ignored because this interrupt is
|
|
* used in polled mode by the endpoint disable logic).
|
|
*/
|
|
#if 1
|
|
/* REVISIT: */
|
|
if ((doepint & OTGHS_DOEPINT_EPDISD) != 0)
|
|
{
|
|
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_EPOUT_EPDISD), (uint16_t)doepint);
|
|
|
|
/* Clear the bit in DOEPINTn for this interrupt */
|
|
|
|
stm32_putreg(OTGHS_DOEPINT_EPDISD, STM32_OTGHS_DOEPINT(epno));
|
|
}
|
|
#endif
|
|
/* Setup Phase Done (control EPs) */
|
|
|
|
if ((doepint & OTGHS_DOEPINT_SETUP) != 0)
|
|
{
|
|
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_EPOUT_SETUP), priv->ep0state);
|
|
|
|
/* Handle the receipt of the IN SETUP packets now (OUT setup
|
|
* packet processing may be delayed until the accompanying
|
|
* OUT DATA is received)
|
|
*/
|
|
|
|
if (priv->ep0state == EP0STATE_SETUP_READY)
|
|
{
|
|
stm32_ep0out_setup(priv);
|
|
}
|
|
stm32_putreg(OTGHS_DOEPINT_SETUP, STM32_OTGHS_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_OTGHS_DCTL);
|
|
regval &= OTGHS_DCTL_TCTL_MASK;
|
|
regval |= (uint32_t)priv->testmode << OTGHS_DCTL_TCTL_SHIFT;
|
|
stm32_putreg(regval , STM32_OTGHS_DCTL);
|
|
|
|
priv->dotest = 0;
|
|
priv->testmode = OTGHS_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 endpoint write 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 data from the existing 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 OTGHS DAINT register to determine the exact number of the IN
|
|
* endpoint on which the interrupt occurred, and then read the corresponding
|
|
* OTGHS 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_OTGHS_DAINT);
|
|
daint &= stm32_getreg(STM32_OTGHS_DAINTMSK);
|
|
daint &= OTGHS_DAINT_IEP_MASK;
|
|
|
|
if (daint == 0)
|
|
{
|
|
/* We got an interrupt, but there is no unmasked endpoint that caused
|
|
* it ?! When this happens, the interrupt flag never gets cleared and
|
|
* we are stuck in infinite interrupt loop.
|
|
*
|
|
* This shouldn't happen if we are diligent about handling timing
|
|
* issues when masking endpoint interrupts. However, this workaround
|
|
* avoids infinite loop and allows operation to continue normally. It
|
|
* works by clearing each endpoint flags, masked or not.
|
|
*/
|
|
|
|
daint = stm32_getreg(STM32_OTGHS_DAINT);
|
|
usbtrace(TRACE_DEVERROR(STM32_TRACEERR_EPINUNEXPECTED),
|
|
(uint16_t)daint);
|
|
|
|
daint &= OTGHS_DAINT_IEP_MASK;
|
|
epno = 0;
|
|
|
|
while (daint)
|
|
{
|
|
if ((daint & 1) != 0)
|
|
{
|
|
uinfo("DIEPINT(%d) = %08x\n",
|
|
epno, stm32_getreg(STM32_OTGHS_DIEPINT(epno)));
|
|
stm32_putreg(0xFF, STM32_OTGHS_DIEPINT(epno));
|
|
}
|
|
|
|
epno++;
|
|
daint >>= 1;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
/* 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_OTGHS_DIEPMSK);
|
|
|
|
/* Check if the TxFIFO not empty interrupt is enabled for this
|
|
* endpoint in the DIEPMSK register. Bits n corresponds to
|
|
* endpoint n in the register. That condition corresponds to
|
|
* bit 7 of the DIEPINT interrupt status register. There is
|
|
* no TXFE bit in the mask register, so we fake one here.
|
|
*/
|
|
|
|
empty = stm32_getreg(STM32_OTGHS_DIEPEMPMSK);
|
|
if ((empty & OTGHS_DIEPEMPMSK(epno)) != 0)
|
|
{
|
|
mask |= OTGHS_DIEPINT_TXFE;
|
|
}
|
|
|
|
/* Now, read the interrupt status and mask out all disabled
|
|
* interrupts.
|
|
*/
|
|
|
|
diepint = stm32_getreg(STM32_OTGHS_DIEPINT(epno)) & mask;
|
|
|
|
/* Decode and process the enabled, pending interrupts */
|
|
/* Transfer completed interrupt */
|
|
|
|
if ((diepint & OTGHS_DIEPINT_XFRC) != 0)
|
|
{
|
|
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_EPIN_XFRC),
|
|
(uint16_t)diepint);
|
|
|
|
/* It is possible that logic may be waiting for a the
|
|
* TxFIFO to become empty. We disable the TxFIFO empty
|
|
* interrupt here; it will be re-enabled if there is still
|
|
* insufficient space in the TxFIFO.
|
|
*/
|
|
|
|
empty &= ~OTGHS_DIEPEMPMSK(epno);
|
|
stm32_putreg(empty, STM32_OTGHS_DIEPEMPMSK);
|
|
stm32_putreg(OTGHS_DIEPINT_XFRC, STM32_OTGHS_DIEPINT(epno));
|
|
|
|
/* IN transfer complete */
|
|
|
|
stm32_epin(priv, epno);
|
|
}
|
|
|
|
/* Timeout condition */
|
|
|
|
if ((diepint & OTGHS_DIEPINT_TOC) != 0)
|
|
{
|
|
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_EPIN_TOC), (uint16_t)diepint);
|
|
stm32_putreg(OTGHS_DIEPINT_TOC, STM32_OTGHS_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 & OTGHS_DIEPINT_ITTXFE) != 0)
|
|
{
|
|
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_EPIN_ITTXFE), (uint16_t)diepint);
|
|
stm32_epin_request(priv, &priv->epin[epno]);
|
|
stm32_putreg(OTGHS_DIEPINT_ITTXFE, STM32_OTGHS_DIEPINT(epno));
|
|
}
|
|
|
|
/* IN endpoint NAK effective (ignored as this used only in polled
|
|
* mode)
|
|
*/
|
|
#if 0
|
|
if ((diepint & OTGHS_DIEPINT_INEPNE) != 0)
|
|
{
|
|
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_EPIN_INEPNE), (uint16_t)diepint);
|
|
stm32_putreg(OTGHS_DIEPINT_INEPNE, STM32_OTGHS_DIEPINT(epno));
|
|
}
|
|
#endif
|
|
/* Endpoint disabled interrupt (ignored as this used only in polled
|
|
* mode)
|
|
*/
|
|
#if 0
|
|
if ((diepint & OTGHS_DIEPINT_EPDISD) != 0)
|
|
{
|
|
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_EPIN_EPDISD), (uint16_t)diepint);
|
|
stm32_putreg(OTGHS_DIEPINT_EPDISD, STM32_OTGHS_DIEPINT(epno));
|
|
}
|
|
#endif
|
|
/* Transmit FIFO empty */
|
|
|
|
if ((diepint & OTGHS_DIEPINT_TXFE) != 0)
|
|
{
|
|
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_EPIN_TXFE), (uint16_t)diepint);
|
|
|
|
/* If we were waiting for TxFIFO to become empty, the we might have both
|
|
* XFRC and TXFE interrupts pending. Since we do the same thing for both
|
|
* cases, ignore the TXFE if we have already processed the XFRC.
|
|
*/
|
|
|
|
if ((diepint & OTGHS_DIEPINT_XFRC) == 0)
|
|
{
|
|
/* Mask further FIFO empty interrupts. This will be re-enabled
|
|
* whenever we need to wait for a FIFO event.
|
|
*/
|
|
|
|
empty &= ~OTGHS_DIEPEMPMSK(epno);
|
|
stm32_putreg(empty, STM32_OTGHS_DIEPEMPMSK);
|
|
|
|
/* Handle TxFIFO empty */
|
|
|
|
stm32_epin_txfifoempty(priv, epno);
|
|
}
|
|
|
|
/* Clear the pending TxFIFO empty interrupt */
|
|
|
|
stm32_putreg(OTGHS_DIEPINT_TXFE, STM32_OTGHS_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_OTGHS_PCGCCTL);
|
|
regval &= ~(OTGHS_PCGCCTL_STPPCLK | OTGHS_PCGCCTL_GATEHCLK);
|
|
stm32_putreg(regval, STM32_OTGHS_PCGCCTL);
|
|
#endif
|
|
|
|
/* Clear remote wake-up signaling */
|
|
|
|
regval = stm32_getreg(STM32_OTGHS_DCTL);
|
|
regval &= ~OTGHS_DCTL_RWUSIG;
|
|
stm32_putreg(regval, STM32_OTGHS_DCTL);
|
|
|
|
/* Restore full power -- whatever that means for this particular board */
|
|
|
|
stm32_usbsuspend((struct usbdev_s *)priv, true);
|
|
|
|
/* Notify the class driver of the resume event */
|
|
|
|
if (priv->driver)
|
|
{
|
|
CLASS_RESUME(priv->driver, &priv->usbdev);
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* 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;
|
|
#endif
|
|
|
|
/* Notify the class driver of the suspend event */
|
|
|
|
if (priv->driver)
|
|
{
|
|
CLASS_SUSPEND(priv->driver, &priv->usbdev);
|
|
}
|
|
|
|
#ifdef CONFIG_USBDEV_LOWPOWER
|
|
/* OTGHS_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_OTGHS_DSTS);
|
|
|
|
if ((regval & OTGHS_DSTS_SUSPSTS) != 0 && devstate == DEVSTATE_CONFIGURED)
|
|
{
|
|
/* Switch off OTG HS clocking. Setting OTGHS_PCGCCTL_STPPCLK stops the
|
|
* PHY clock.
|
|
*/
|
|
|
|
regval = stm32_getreg(STM32_OTGHS_PCGCCTL);
|
|
regval |= OTGHS_PCGCCTL_STPPCLK;
|
|
stm32_putreg(regval, STM32_OTGHS_PCGCCTL);
|
|
|
|
/* Setting OTGHS_PCGCCTL_GATEHCLK gate HCLK to modules other than
|
|
* the AHB Slave and Master and wakeup logic.
|
|
*/
|
|
|
|
regval |= OTGHS_PCGCCTL_GATEHCLK;
|
|
stm32_putreg(regval, STM32_OTGHS_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_OTGHS_GINTMSK);
|
|
regval &= ~OTGHS_GINT_RXFLVL;
|
|
stm32_putreg(regval, STM32_OTGHS_GINTMSK);
|
|
|
|
/* Get the status from the top of the FIFO */
|
|
|
|
regval = stm32_getreg(STM32_OTGHS_GRXSTSP);
|
|
|
|
/* Decode status fields */
|
|
|
|
epphy = (regval & OTGHS_GRXSTSD_EPNUM_MASK) >> OTGHS_GRXSTSD_EPNUM_SHIFT;
|
|
|
|
if (epphy < STM32_NENDPOINTS)
|
|
{
|
|
privep = &priv->epout[epphy];
|
|
|
|
/* Handle the RX event according to the packet status field */
|
|
|
|
switch (regval & OTGHS_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 OTGHS_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 OTGHS_GRXSTSD_PKTSTS_OUTRECVD:
|
|
{
|
|
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_OUTRECVD), epphy);
|
|
bcnt = (regval & OTGHS_GRXSTSD_BCNT_MASK) >> OTGHS_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 OTGHS_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 OTGHS_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 OTGHS_GRXSTSD_PKTSTS_SETUPRECVD:
|
|
{
|
|
uint16_t datlen;
|
|
|
|
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_SETUPRECVD), epphy);
|
|
|
|
/* Read EP0 setup data. NOTE: If multiple 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);
|
|
|
|
/* Was this an IN or an OUT SETUP packet. If it is an OUT SETUP,
|
|
* then we need to wait for the completion of the data phase to
|
|
* process the setup command. If it is an IN SETUP packet, then
|
|
* we must processing the command BEFORE we enter the DATA phase.
|
|
*
|
|
* If the data associated with the OUT SETUP packet is zero length,
|
|
* then, of course, we don't need to wait.
|
|
*/
|
|
|
|
datlen = GETUINT16(priv->ctrlreq.len);
|
|
if (USB_REQ_ISOUT(priv->ctrlreq.type) && datlen > 0)
|
|
{
|
|
/* Clear NAKSTS so that we can receive the data */
|
|
|
|
regval = stm32_getreg(STM32_OTGHS_DOEPCTL0);
|
|
regval |= OTGHS_DOEPCTL0_CNAK;
|
|
stm32_putreg(regval, STM32_OTGHS_DOEPCTL0);
|
|
|
|
/* Wait for the data phase. */
|
|
|
|
priv->ep0state = EP0STATE_SETUP_OUT;
|
|
}
|
|
else
|
|
{
|
|
/* We can process the setup data as soon as SETUP done word is
|
|
* popped of the RxFIFO.
|
|
*/
|
|
|
|
priv->ep0state = EP0STATE_SETUP_READY;
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
{
|
|
usbtrace(TRACE_DEVERROR(STM32_TRACEERR_INVALIDPARMS),
|
|
(regval & OTGHS_GRXSTSD_PKTSTS_MASK) >> OTGHS_GRXSTSD_PKTSTS_SHIFT);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Enable the Rx Status Queue Level interrupt */
|
|
|
|
regval = stm32_getreg(STM32_OTGHS_GINTMSK);
|
|
regval |= OTGHS_GINT_RXFLVL;
|
|
stm32_putreg(regval, STM32_OTGHS_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_OTGHS_GUSBCFG);
|
|
regval &= ~OTGHS_GUSBCFG_TRDT_MASK;
|
|
regval |= OTGHS_GUSBCFG_TRDT(5);
|
|
stm32_putreg(regval, STM32_OTGHS_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_OTGHS_DIEPCTL(privep->epphy);
|
|
doepctl = stm32_getreg(regaddr);
|
|
dsts = stm32_getreg(STM32_OTGHS_DSTS);
|
|
|
|
/* EONUM = 0:even frame, 1:odd frame
|
|
* SOFFN = Frame number of the received SOF
|
|
*/
|
|
|
|
eonum = ((doepctl & OTGHS_DIEPCTL_EONUM) != 0);
|
|
soffn = ((dsts & OTGHS_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);
|
|
#warning "Will clear OTGHS_DIEPCTL_USBAEP too"
|
|
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_OTGHS_DOEPCTL(privep->epphy);
|
|
doepctl = stm32_getreg(regaddr);
|
|
dsts = stm32_getreg(STM32_OTGHS_DSTS);
|
|
|
|
/* EONUM = 0:even frame, 1:odd frame
|
|
* SOFFN = Frame number of the received SOF
|
|
*/
|
|
|
|
eonum = ((doepctl & OTGHS_DOEPCTL_EONUM) != 0);
|
|
soffn = ((dsts & OTGHS_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);
|
|
#warning "Will clear OTGHS_DOEPCTL_USBAEP too"
|
|
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_OTGHS_GOTGINT);
|
|
if ((regval & OTGHS_GOTGINT_SEDET) != 0)
|
|
{
|
|
#warning "Missing logic"
|
|
}
|
|
|
|
/* Clear OTG interrupt */
|
|
|
|
stm32_putreg(retval, STM32_OTGHS_GOTGINT);
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_usbinterrupt
|
|
*
|
|
* Description:
|
|
* USB interrupt handler
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int stm32_usbinterrupt(int irq, FAR void *context, FAR void *arg)
|
|
{
|
|
/* At present, there is only a single OTG HS device support. Hence it is
|
|
* pre-allocated as g_otghsdev. 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_otghsdev;
|
|
uint32_t regval;
|
|
|
|
usbtrace(TRACE_INTENTRY(STM32_TRACEINTID_USB), 0);
|
|
|
|
/* Assure that we are in device mode */
|
|
|
|
DEBUGASSERT((stm32_getreg(STM32_OTGHS_GINTSTS) & OTGHS_GINTSTS_CMOD) == OTGHS_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_OTGHS_GINTSTS);
|
|
regval &= stm32_getreg(STM32_OTGHS_GINTMSK);
|
|
|
|
/* Break out of the loop when there are no further pending (and
|
|
* unmasked) interrupts to be processes.
|
|
*/
|
|
|
|
if (regval == 0)
|
|
{
|
|
break;
|
|
}
|
|
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_INTPENDING), (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 & OTGHS_GINT_OEP) != 0)
|
|
{
|
|
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_EPOUT), (uint16_t)regval);
|
|
stm32_epout_interrupt(priv);
|
|
stm32_putreg(OTGHS_GINT_OEP, STM32_OTGHS_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 & OTGHS_GINT_IEP) != 0)
|
|
{
|
|
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_EPIN), (uint16_t)regval);
|
|
stm32_epin_interrupt(priv);
|
|
stm32_putreg(OTGHS_GINT_IEP, STM32_OTGHS_GINTSTS);
|
|
}
|
|
|
|
/* Host/device mode mismatch error interrupt */
|
|
|
|
#ifdef CONFIG_DEBUG_USB
|
|
if ((regval & OTGHS_GINT_MMIS) != 0)
|
|
{
|
|
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_MISMATCH), (uint16_t)regval);
|
|
stm32_putreg(OTGHS_GINT_MMIS, STM32_OTGHS_GINTSTS);
|
|
}
|
|
#endif
|
|
|
|
/* Resume/remote wakeup detected interrupt */
|
|
|
|
if ((regval & OTGHS_GINT_WKUP) != 0)
|
|
{
|
|
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_WAKEUP), (uint16_t)regval);
|
|
stm32_resumeinterrupt(priv);
|
|
stm32_putreg(OTGHS_GINT_WKUP, STM32_OTGHS_GINTSTS);
|
|
}
|
|
|
|
/* USB suspend interrupt */
|
|
|
|
if ((regval & OTGHS_GINT_USBSUSP) != 0)
|
|
{
|
|
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_SUSPEND), (uint16_t)regval);
|
|
stm32_suspendinterrupt(priv);
|
|
stm32_putreg(OTGHS_GINT_USBSUSP, STM32_OTGHS_GINTSTS);
|
|
}
|
|
|
|
/* Start of frame interrupt */
|
|
|
|
#ifdef CONFIG_USBDEV_SOFINTERRUPT
|
|
if ((regval & OTGHS_GINT_SOF) != 0)
|
|
{
|
|
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_SOF), (uint16_t)regval);
|
|
stm32_putreg(OTGHS_GINT_SOF, STM32_OTGHS_GINTSTS);
|
|
}
|
|
#endif
|
|
|
|
/* RxFIFO non-empty interrupt. Indicates that there is at least one
|
|
* packet pending to be read from the RxFIFO.
|
|
*/
|
|
|
|
if ((regval & OTGHS_GINT_RXFLVL) != 0)
|
|
{
|
|
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_RXFIFO), (uint16_t)regval);
|
|
stm32_rxinterrupt(priv);
|
|
stm32_putreg(OTGHS_GINT_RXFLVL, STM32_OTGHS_GINTSTS);
|
|
}
|
|
|
|
/* USB reset interrupt */
|
|
|
|
if ((regval & OTGHS_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);
|
|
stm32_putreg(OTGHS_GINT_USBRST, STM32_OTGHS_GINTSTS);
|
|
return OK;
|
|
}
|
|
|
|
/* Enumeration done interrupt */
|
|
|
|
if ((regval & OTGHS_GINT_ENUMDNE) != 0)
|
|
{
|
|
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_ENUMDNE), (uint16_t)regval);
|
|
stm32_enuminterrupt(priv);
|
|
stm32_putreg(OTGHS_GINT_ENUMDNE, STM32_OTGHS_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 & OTGHS_GINT_IISOIXFR) != 0)
|
|
{
|
|
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_IISOIXFR), (uint16_t)regval);
|
|
stm32_isocininterrupt(priv);
|
|
stm32_putreg(OTGHS_GINT_IISOIXFR, STM32_OTGHS_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 & OTGHS_GINT_IISOOXFR) != 0)
|
|
{
|
|
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_IISOOXFR), (uint16_t)regval);
|
|
stm32_isocoutinterrupt(priv);
|
|
stm32_putreg(OTGHS_GINT_IISOOXFR, STM32_OTGHS_GINTSTS);
|
|
}
|
|
#endif
|
|
|
|
/* Session request/new session detected interrupt */
|
|
|
|
#ifdef CONFIG_USBDEV_VBUSSENSING
|
|
if ((regval & OTGHS_GINT_SRQ) != 0)
|
|
{
|
|
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_SRQ), (uint16_t)regval);
|
|
stm32_sessioninterrupt(priv);
|
|
stm32_putreg(OTGHS_GINT_SRQ, STM32_OTGHS_GINTSTS);
|
|
}
|
|
|
|
/* OTG interrupt */
|
|
|
|
if ((regval & OTGHS_GINT_OTG) != 0)
|
|
{
|
|
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_OTG), (uint16_t)regval);
|
|
stm32_otginterrupt(priv);
|
|
stm32_putreg(OTGHS_GINT_OTG, STM32_OTGHS_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. */
|
|
|
|
#if 0
|
|
stm32_putreg(OTGHS_GINT_GONAKEFF, STM32_OTGHS_GINTSTS);
|
|
#endif
|
|
|
|
/* Enable Global OUT NAK mode in the core. */
|
|
|
|
regval = stm32_getreg(STM32_OTGHS_DCTL);
|
|
regval |= OTGHS_DCTL_SGONAK;
|
|
stm32_putreg(regval, STM32_OTGHS_DCTL);
|
|
|
|
#if 0
|
|
/* Wait for the GONAKEFF interrupt that indicates that the OUT NAK
|
|
* mode is in effect. When the interrupt handler pops the OUTNAK word
|
|
* from the RxFIFO, the core sets the GONAKEFF interrupt.
|
|
*/
|
|
|
|
while ((stm32_getreg(STM32_OTGHS_GINTSTS) & OTGHS_GINT_GONAKEFF) == 0);
|
|
stm32_putreg(OTGHS_GINT_GONAKEFF, STM32_OTGHS_GINTSTS);
|
|
|
|
#else
|
|
/* Since we are in the interrupt handler, we cannot wait inline for the
|
|
* GONAKEFF because it cannot occur until service the RXFLVL global interrupt
|
|
* and pop the OUTNAK word from the RxFIFO.
|
|
*
|
|
* Perhaps it is sufficient to wait for Global OUT NAK status to be reported
|
|
* in OTGHS DCTL register?
|
|
*/
|
|
|
|
while ((stm32_getreg(STM32_OTGHS_DCTL) & OTGHS_DCTL_GONSTS) == 0);
|
|
#endif
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_disablegonak
|
|
*
|
|
* Description:
|
|
* Disable global OUT NAK mode
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void stm32_disablegonak(FAR struct stm32_ep_s *privep)
|
|
{
|
|
uint32_t regval;
|
|
|
|
/* Set the "Clear the Global OUT NAK bit" to disable global OUT NAK mode */
|
|
|
|
regval = stm32_getreg(STM32_OTGHS_DCTL);
|
|
regval |= OTGHS_DCTL_CGONAK;
|
|
stm32_putreg(regval, STM32_OTGHS_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 = OTGHS_DOEPCTL0_MPSIZ_8;
|
|
break;
|
|
|
|
case 16:
|
|
mpsiz = OTGHS_DOEPCTL0_MPSIZ_16;
|
|
break;
|
|
|
|
case 32:
|
|
mpsiz = OTGHS_DOEPCTL0_MPSIZ_32;
|
|
break;
|
|
|
|
case 64:
|
|
mpsiz = OTGHS_DOEPCTL0_MPSIZ_64;
|
|
break;
|
|
|
|
default:
|
|
uerr("ERROR: Unsupported maxpacket: %d\n", maxpacket);
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
/* For other endpoints, the packet size is in bytes */
|
|
|
|
else
|
|
{
|
|
mpsiz = (maxpacket << OTGHS_DOEPCTL_MPSIZ_SHIFT);
|
|
}
|
|
|
|
/* If the endpoint is already active don't change the endpoint control
|
|
* register.
|
|
*/
|
|
|
|
regaddr = STM32_OTGHS_DOEPCTL(privep->epphy);
|
|
regval = stm32_getreg(regaddr);
|
|
if ((regval & OTGHS_DOEPCTL_USBAEP) == 0)
|
|
{
|
|
if (regval & OTGHS_DOEPCTL_NAKSTS)
|
|
{
|
|
regval |= OTGHS_DOEPCTL_CNAK;
|
|
}
|
|
|
|
regval &= ~(OTGHS_DOEPCTL_MPSIZ_MASK | OTGHS_DOEPCTL_EPTYP_MASK);
|
|
regval |= mpsiz;
|
|
regval |= (eptype << OTGHS_DOEPCTL_EPTYP_SHIFT);
|
|
regval |= (OTGHS_DOEPCTL_SD0PID | OTGHS_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_OTGHS_DAINTMSK);
|
|
regval |= OTGHS_DAINT_OEP(privep->epphy);
|
|
stm32_putreg(regval, STM32_OTGHS_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 = OTGHS_DIEPCTL0_MPSIZ_8;
|
|
break;
|
|
|
|
case 16:
|
|
mpsiz = OTGHS_DIEPCTL0_MPSIZ_16;
|
|
break;
|
|
|
|
case 32:
|
|
mpsiz = OTGHS_DIEPCTL0_MPSIZ_32;
|
|
break;
|
|
|
|
case 64:
|
|
mpsiz = OTGHS_DIEPCTL0_MPSIZ_64;
|
|
break;
|
|
|
|
default:
|
|
uerr("ERROR: Unsupported maxpacket: %d\n", maxpacket);
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
/* For other endpoints, the packet size is in bytes */
|
|
|
|
else
|
|
{
|
|
mpsiz = (maxpacket << OTGHS_DIEPCTL_MPSIZ_SHIFT);
|
|
}
|
|
|
|
|
|
/* If the endpoint is already active don't change the endpoint control
|
|
* register.
|
|
*/
|
|
|
|
regaddr = STM32_OTGHS_DIEPCTL(privep->epphy);
|
|
regval = stm32_getreg(regaddr);
|
|
if ((regval & OTGHS_DIEPCTL_USBAEP) == 0)
|
|
{
|
|
if (regval & OTGHS_DIEPCTL_NAKSTS)
|
|
{
|
|
regval |= OTGHS_DIEPCTL_CNAK;
|
|
}
|
|
|
|
regval &= ~(OTGHS_DIEPCTL_MPSIZ_MASK | OTGHS_DIEPCTL_EPTYP_MASK | OTGHS_DIEPCTL_TXFNUM_MASK);
|
|
regval |= mpsiz;
|
|
regval |= (eptype << OTGHS_DIEPCTL_EPTYP_SHIFT);
|
|
regval |= (eptype << OTGHS_DIEPCTL_TXFNUM_SHIFT);
|
|
regval |= (OTGHS_DIEPCTL_SD0PID | OTGHS_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_OTGHS_DAINTMSK);
|
|
regval |= OTGHS_DAINT_IEP(privep->epphy);
|
|
stm32_putreg(regval, STM32_OTGHS_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 = enter_critical_section();
|
|
stm32_enablegonak(privep);
|
|
|
|
/* Disable the required OUT endpoint by setting the EPDIS and SNAK bits
|
|
* int DOECPTL register.
|
|
*/
|
|
|
|
regaddr = STM32_OTGHS_DOEPCTL(privep->epphy);
|
|
regval = stm32_getreg(regaddr);
|
|
regval &= ~OTGHS_DOEPCTL_USBAEP;
|
|
regval |= (OTGHS_DOEPCTL_EPDIS | OTGHS_DOEPCTL_SNAK);
|
|
stm32_putreg(regval, regaddr);
|
|
|
|
/* Wait for the EPDISD interrupt which indicates that the OUT
|
|
* endpoint is completely disabled.
|
|
*/
|
|
|
|
#if 0 /* Doesn't happen */
|
|
regaddr = STM32_OTGHS_DOEPINT(privep->epphy);
|
|
while ((stm32_getreg(regaddr) & OTGHS_DOEPINT_EPDISD) == 0);
|
|
#else
|
|
/* REVISIT: */
|
|
up_udelay(10);
|
|
#endif
|
|
|
|
/* Clear the EPDISD interrupt indication */
|
|
|
|
stm32_putreg(OTGHS_DOEPINT_EPDISD, STM32_OTGHS_DOEPINT(privep->epphy));
|
|
|
|
/* Then disable 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_OTGHS_DAINTMSK);
|
|
regval &= ~OTGHS_DAINT_OEP(privep->epphy);
|
|
stm32_putreg(regval, STM32_OTGHS_DAINTMSK);
|
|
|
|
/* Cancel any queued read requests */
|
|
|
|
stm32_req_cancel(privep, -ESHUTDOWN);
|
|
|
|
leave_critical_section(flags);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_epin_disable
|
|
*
|
|
* Description:
|
|
* Disable an IN endpoint when it 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);
|
|
|
|
/* After USB reset, the endpoint will already be deactivated by the
|
|
* hardware. Trying to disable again will just hang in the wait.
|
|
*/
|
|
|
|
regaddr = STM32_OTGHS_DIEPCTL(privep->epphy);
|
|
regval = stm32_getreg(regaddr);
|
|
if ((regval & OTGHS_DIEPCTL_USBAEP) == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
/* This INEPNE wait logic is suggested by reference manual, but seems
|
|
* to get stuck to infinite loop.
|
|
*/
|
|
|
|
#if 0
|
|
/* Make sure that there is no pending IPEPNE interrupt (because we are
|
|
* to poll this bit below).
|
|
*/
|
|
|
|
stm32_putreg(OTGHS_DIEPINT_INEPNE, STM32_OTGHS_DIEPINT(privep->epphy));
|
|
|
|
/* Set the endpoint in NAK mode */
|
|
|
|
regaddr = STM32_OTGHS_DIEPCTL(privep->epphy);
|
|
regval = stm32_getreg(regaddr);
|
|
regval &= ~OTGHS_DIEPCTL_USBAEP;
|
|
regval |= (OTGHS_DIEPCTL_EPDIS | OTGHS_DIEPCTL_SNAK);
|
|
stm32_putreg(regval, regaddr);
|
|
|
|
/* Wait for the INEPNE interrupt that indicates that we are now in NAK mode */
|
|
|
|
regaddr = STM32_OTGHS_DIEPINT(privep->epphy);
|
|
while ((stm32_getreg(regaddr) & OTGHS_DIEPINT_INEPNE) == 0);
|
|
|
|
/* Clear the INEPNE interrupt indication */
|
|
|
|
stm32_putreg(OTGHS_DIEPINT_INEPNE, regaddr);
|
|
#endif
|
|
|
|
/* Deactivate and disable the endpoint by setting the EPDIS and SNAK bits
|
|
* the DIEPCTLx register.
|
|
*/
|
|
|
|
flags = enter_critical_section();
|
|
regaddr = STM32_OTGHS_DIEPCTL(privep->epphy);
|
|
regval = stm32_getreg(regaddr);
|
|
regval &= ~OTGHS_DIEPCTL_USBAEP;
|
|
regval |= (OTGHS_DIEPCTL_EPDIS | OTGHS_DIEPCTL_SNAK);
|
|
stm32_putreg(regval, regaddr);
|
|
|
|
/* Wait for the EPDISD interrupt which indicates that the IN
|
|
* endpoint is completely disabled.
|
|
*/
|
|
|
|
regaddr = STM32_OTGHS_DIEPINT(privep->epphy);
|
|
while ((stm32_getreg(regaddr) & OTGHS_DIEPINT_EPDISD) == 0);
|
|
|
|
/* Clear the EPDISD interrupt indication */
|
|
|
|
stm32_putreg(OTGHS_DIEPINT_EPDISD, stm32_getreg(regaddr));
|
|
|
|
/* Flush any data remaining in the TxFIFO */
|
|
|
|
stm32_txfifo_flush(OTGHS_GRSTCTL_TXFNUM_D(privep->epphy));
|
|
|
|
/* Disable endpoint interrupts */
|
|
|
|
regval = stm32_getreg(STM32_OTGHS_DAINTMSK);
|
|
regval &= ~OTGHS_DAINT_IEP(privep->epphy);
|
|
stm32_putreg(regval, STM32_OTGHS_DAINTMSK);
|
|
|
|
/* Cancel any queued write requests */
|
|
|
|
stm32_req_cancel(privep, -ESHUTDOWN);
|
|
leave_critical_section(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_FEATURES
|
|
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_FEATURES
|
|
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 *)kmm_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_FEATURES
|
|
if (!ep || !req)
|
|
{
|
|
usbtrace(TRACE_DEVERROR(STM32_TRACEERR_INVALIDPARMS), 0);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
usbtrace(TRACE_EPFREEREQ, ((FAR struct stm32_ep_s *)ep)->epphy);
|
|
kmm_free(privreq);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_ep_allocbuffer
|
|
*
|
|
* Description:
|
|
* Allocate an I/O buffer
|
|
*
|
|
****************************************************************************/
|
|
|
|
#ifdef CONFIG_USBDEV_DMA
|
|
static void *stm32_ep_allocbuffer(FAR struct usbdev_ep_s *ep, unsigned bytes)
|
|
{
|
|
usbtrace(TRACE_EPALLOCBUFFER, privep->epphy);
|
|
|
|
#ifdef CONFIG_USBDEV_DMAMEMORY
|
|
return usbdev_dma_alloc(bytes);
|
|
#else
|
|
return kmm_malloc(bytes);
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_ep_freebuffer
|
|
*
|
|
* Description:
|
|
* Free an I/O buffer
|
|
*
|
|
****************************************************************************/
|
|
|
|
#ifdef CONFIG_USBDEV_DMA
|
|
static void stm32_ep_freebuffer(FAR struct usbdev_ep_s *ep, FAR void *buf)
|
|
{
|
|
usbtrace(TRACE_EPFREEBUFFER, privep->epphy);
|
|
|
|
#ifdef CONFIG_USBDEV_DMAMEMORY
|
|
usbdev_dma_free(buf);
|
|
#else
|
|
kmm_free(buf);
|
|
#endif
|
|
}
|
|
#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;
|
|
|
|
/* Some sanity checking */
|
|
|
|
#ifdef CONFIG_DEBUG_FEATURES
|
|
if (!req || !req->callback || !req->buf || !ep)
|
|
{
|
|
usbtrace(TRACE_DEVERROR(STM32_TRACEERR_INVALIDPARMS), 0);
|
|
uinfo("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;
|
|
|
|
#ifdef CONFIG_DEBUG_FEATURES
|
|
if (!priv->driver)
|
|
{
|
|
usbtrace(TRACE_DEVERROR(STM32_TRACEERR_NOTCONFIGURED), priv->usbdev.speed);
|
|
return -ESHUTDOWN;
|
|
}
|
|
#endif
|
|
|
|
/* Handle the request from the class driver */
|
|
|
|
req->result = -EINPROGRESS;
|
|
req->xfrd = 0;
|
|
|
|
/* Disable Interrupts */
|
|
|
|
flags = enter_critical_section();
|
|
|
|
/* 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) && !privep->active)
|
|
{
|
|
/* If a request was added to an IN endpoint, then attempt to send
|
|
* the request data buffer now.
|
|
*/
|
|
|
|
if (privep->isin)
|
|
{
|
|
usbtrace(TRACE_INREQQUEUED(privep->epphy), privreq->req.len);
|
|
|
|
/* If the endpoint is not busy with another write request,
|
|
* then process the newly received write request now.
|
|
*/
|
|
|
|
if (!privep->active)
|
|
{
|
|
stm32_epin_request(priv, privep);
|
|
}
|
|
}
|
|
|
|
/* If the request was added to an OUT endpoint, 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);
|
|
}
|
|
}
|
|
}
|
|
|
|
leave_critical_section(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;
|
|
irqstate_t flags;
|
|
|
|
#ifdef CONFIG_DEBUG_FEATURES
|
|
if (!ep || !req)
|
|
{
|
|
usbtrace(TRACE_DEVERROR(STM32_TRACEERR_INVALIDPARMS), 0);
|
|
return -EINVAL;
|
|
}
|
|
#endif
|
|
|
|
usbtrace(TRACE_EPCANCEL, privep->epphy);
|
|
|
|
flags = enter_critical_section();
|
|
|
|
/* 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);
|
|
leave_critical_section(flags);
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_epout_setstall
|
|
*
|
|
* Description:
|
|
* Stall an OUT endpoint
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int stm32_epout_setstall(FAR struct stm32_ep_s *privep)
|
|
{
|
|
#if 1
|
|
/* This implementation follows the requirements from the STM32 F4 reference
|
|
* manual.
|
|
*/
|
|
|
|
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
|
|
* in the DOECPTL register.
|
|
*/
|
|
|
|
regaddr = STM32_OTGHS_DOEPCTL(privep->epphy);
|
|
regval = stm32_getreg(regaddr);
|
|
regval |= (OTGHS_DOEPCTL_EPDIS | OTGHS_DOEPCTL_STALL);
|
|
stm32_putreg(regval, regaddr);
|
|
|
|
/* Wait for the EPDISD interrupt which indicates that the OUT
|
|
* endpoint is completely disabled.
|
|
*/
|
|
|
|
#if 0 /* Doesn't happen */
|
|
regaddr = STM32_OTGHS_DOEPINT(privep->epphy);
|
|
while ((stm32_getreg(regaddr) & OTGHS_DOEPINT_EPDISD) == 0);
|
|
#else
|
|
/* REVISIT: */
|
|
up_udelay(10);
|
|
#endif
|
|
|
|
/* Disable Global OUT NAK mode */
|
|
|
|
stm32_disablegonak(privep);
|
|
|
|
/* The endpoint is now stalled */
|
|
|
|
privep->stalled = true;
|
|
return OK;
|
|
#else
|
|
/* This implementation follows the STMicro code example. */
|
|
/* REVISIT: */
|
|
|
|
uint32_t regaddr;
|
|
uint32_t regval;
|
|
|
|
/* Stall the OUT endpoint by setting the STALL bit in the DOECPTL register. */
|
|
|
|
regaddr = STM32_OTGHS_DOEPCTL(privep->epphy);
|
|
regval = stm32_getreg(regaddr);
|
|
regval |= OTGHS_DOEPCTL_STALL;
|
|
stm32_putreg(regval, regaddr);
|
|
|
|
/* The endpoint is now stalled */
|
|
|
|
privep->stalled = true;
|
|
return OK;
|
|
#endif
|
|
}
|
|
|
|
/****************************************************************************
|
|
* 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_OTGHS_DIEPCTL(privep->epphy);
|
|
regval = stm32_getreg(regaddr);
|
|
|
|
/* Then stall the endpoint */
|
|
|
|
regval |= OTGHS_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_OTGHS_DIEPCTL(privep->epphy);
|
|
stallbit = OTGHS_DIEPCTL_STALL;
|
|
data0bit = OTGHS_DIEPCTL_SD0PID;
|
|
}
|
|
else
|
|
{
|
|
/* Clear the stall bit in the IN endpoint device control register */
|
|
|
|
regaddr = STM32_OTGHS_DOEPCTL(privep->epphy);
|
|
stallbit = OTGHS_DOEPCTL_STALL;
|
|
data0bit = OTGHS_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 = enter_critical_section();
|
|
if (resume)
|
|
{
|
|
ret = stm32_ep_clrstall(privep);
|
|
}
|
|
else
|
|
{
|
|
ret = stm32_ep_setstall(privep);
|
|
}
|
|
leave_critical_section(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 depending on the direction */
|
|
|
|
flags = enter_critical_section();
|
|
epavail = priv->epavail[in];
|
|
|
|
/* 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[in] &= ~(1 << epno);
|
|
|
|
/* And return the pointer to the standard endpoint structure */
|
|
|
|
leave_critical_section(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);
|
|
leave_critical_section(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 = enter_critical_section();
|
|
priv->epavail[privep->isin] |= (1 << privep->epphy);
|
|
leave_critical_section(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_OTGHS_DSTS);
|
|
return (int)((regval & OTGHS_DSTS_SOFFN_MASK) >> OTGHS_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 = enter_critical_section();
|
|
if (priv->wakeup)
|
|
{
|
|
/* Yes... is the core suspended? */
|
|
|
|
regval = stm32_getreg(STM32_OTGHS_DSTS);
|
|
if ((regval & OTGHS_DSTS_SUSPSTS) != 0)
|
|
{
|
|
/* Re-start the PHY clock and un-gate USB core clock (HCLK) */
|
|
|
|
#ifdef CONFIG_USBDEV_LOWPOWER
|
|
regval = stm32_getreg(STM32_OTGHS_PCGCCTL);
|
|
regval &= ~(OTGHS_PCGCCTL_STPPCLK | OTGHS_PCGCCTL_GATEHCLK);
|
|
stm32_putreg(regval, STM32_OTGHS_PCGCCTL);
|
|
#endif
|
|
/* Activate Remote wakeup signaling */
|
|
|
|
regval = stm32_getreg(STM32_OTGHS_DCTL);
|
|
regval |= OTGHS_DCTL_RWUSIG;
|
|
stm32_putreg(regval, STM32_OTGHS_DCTL);
|
|
up_mdelay(5);
|
|
regval &= ~OTGHS_DCTL_RWUSIG;
|
|
stm32_putreg(regval, STM32_OTGHS_DCTL);
|
|
}
|
|
}
|
|
|
|
leave_critical_section(flags);
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_selfpowered
|
|
*
|
|
* Description:
|
|
* Sets/clears the device self-powered 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_FEATURES
|
|
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 = enter_critical_section();
|
|
regval = stm32_getreg(STM32_OTGHS_DCTL);
|
|
if (enable)
|
|
{
|
|
/* Connect the device by clearing the soft disconnect bit in the DCTL
|
|
* register
|
|
*/
|
|
|
|
regval &= ~OTGHS_DCTL_SDIS;
|
|
}
|
|
else
|
|
{
|
|
/* Connect the device by setting the soft disconnect bit in the DCTL
|
|
* register
|
|
*/
|
|
|
|
regval |= OTGHS_DCTL_SDIS;
|
|
}
|
|
|
|
stm32_putreg(regval, STM32_OTGHS_DCTL);
|
|
leave_critical_section(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_OTGHS_DCFG);
|
|
regval &= ~OTGHS_DCFG_DAD_MASK;
|
|
regval |= ((uint32_t)address << OTGHS_DCFG_DAD_SHIFT);
|
|
stm32_putreg(regval, STM32_OTGHS_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 = OTGHS_GRSTCTL_TXFFLSH | txfnum;
|
|
stm32_putreg(regval, STM32_OTGHS_GRSTCTL);
|
|
|
|
/* Wait for the FLUSH to complete */
|
|
|
|
for (timeout = 0; timeout < STM32_FLUSH_DELAY; timeout++)
|
|
{
|
|
regval = stm32_getreg(STM32_OTGHS_GRSTCTL);
|
|
if ((regval & OTGHS_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(OTGHS_GRSTCTL_RXFFLSH, STM32_OTGHS_GRSTCTL);
|
|
|
|
/* Wait for the FLUSH to complete */
|
|
|
|
for (timeout = 0; timeout < STM32_FLUSH_DELAY; timeout++)
|
|
{
|
|
regval = stm32_getreg(STM32_OTGHS_GRSTCTL);
|
|
if ((regval & OTGHS_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[0] = STM32_EP_AVAILABLE;
|
|
priv->epavail[1] = STM32_EP_AVAILABLE;
|
|
|
|
priv->epin[EP0].ep.priv = priv;
|
|
priv->epout[EP0].ep.priv = priv;
|
|
|
|
/* 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 HS 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 start-up the core is in HS mode. */
|
|
|
|
/* Disable global interrupts by clearing the GINTMASK bit in the GAHBCFG
|
|
* register; Set the TXFELVL bit in the GAHBCFG register so that TxFIFO
|
|
* interrupts will occur when the TxFIFO is truly empty (not just half full).
|
|
*/
|
|
|
|
stm32_putreg(OTGHS_GAHBCFG_TXFELVL, STM32_OTGHS_GAHBCFG);
|
|
|
|
/* Set the PHYSEL bit in the GUSBCFG register to select the OTG HS serial
|
|
* transceiver: "This bit is always 1 with write-only access"
|
|
*/
|
|
|
|
regval = stm32_getreg(STM32_OTGHS_GUSBCFG);
|
|
regval |= OTGHS_GUSBCFG_PHYSEL;
|
|
stm32_putreg(regval, STM32_OTGHS_GUSBCFG);
|
|
|
|
/* 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_OTGHS_GRSTCTL);
|
|
if ((regval & OTGHS_GRSTCTL_AHBIDL) != 0)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Then perform the core soft reset. */
|
|
|
|
stm32_putreg(OTGHS_GRSTCTL_CSRST, STM32_OTGHS_GRSTCTL);
|
|
for (timeout = 0; timeout < STM32_READY_DELAY; timeout++)
|
|
{
|
|
regval = stm32_getreg(STM32_OTGHS_GRSTCTL);
|
|
if ((regval & OTGHS_GRSTCTL_CSRST) == 0)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Wait for 3 PHY Clocks */
|
|
|
|
up_udelay(3);
|
|
|
|
/* Deactivate the power down */
|
|
|
|
regval = (OTGHS_GCCFG_PWRDWN | OTGHS_GCCFG_VBUSASEN | OTGHS_GCCFG_VBUSBSEN);
|
|
#ifndef CONFIG_USBDEV_VBUSSENSING
|
|
regval |= OTGHS_GCCFG_NOVBUSSENS;
|
|
#endif
|
|
#ifdef CONFIG_STM32_OTGHS_SOFOUTPUT
|
|
regval |= OTGHS_GCCFG_SOFOUTEN;
|
|
#endif
|
|
stm32_putreg(regval, STM32_OTGHS_GCCFG);
|
|
up_mdelay(20);
|
|
|
|
/* Force Device Mode */
|
|
|
|
regval = stm32_getreg(STM32_OTGHS_GUSBCFG);
|
|
regval &= ~OTGHS_GUSBCFG_FHMOD;
|
|
regval |= OTGHS_GUSBCFG_FDMOD;
|
|
stm32_putreg(regval, STM32_OTGHS_GUSBCFG);
|
|
up_mdelay(50);
|
|
|
|
/* Initialize device mode */
|
|
/* Restart the PHY Clock */
|
|
|
|
stm32_putreg(0, STM32_OTGHS_PCGCCTL);
|
|
|
|
/* Device configuration register */
|
|
|
|
regval = stm32_getreg(STM32_OTGHS_DCFG);
|
|
regval &= ~OTGHS_DCFG_PFIVL_MASK;
|
|
regval |= OTGHS_DCFG_PFIVL_80PCT;
|
|
stm32_putreg(regval, STM32_OTGHS_DCFG);
|
|
|
|
/* Set full speed PHY */
|
|
|
|
regval = stm32_getreg(STM32_OTGHS_DCFG);
|
|
regval &= ~OTGHS_DCFG_DSPD_MASK;
|
|
regval |= OTGHS_DCFG_DSPD_FS;
|
|
stm32_putreg(regval, STM32_OTGHS_DCFG);
|
|
|
|
/* Set Rx FIFO size */
|
|
|
|
stm32_putreg(STM32_RXFIFO_WORDS, STM32_OTGHS_GRXFSIZ);
|
|
|
|
/* EP0 TX */
|
|
|
|
address = STM32_RXFIFO_WORDS;
|
|
regval = (address << OTGHS_DIEPTXF0_TX0FD_SHIFT) |
|
|
(STM32_EP0_TXFIFO_WORDS << OTGHS_DIEPTXF0_TX0FSA_SHIFT);
|
|
stm32_putreg(regval, STM32_OTGHS_DIEPTXF0);
|
|
|
|
/* EP1 TX */
|
|
|
|
address += STM32_EP0_TXFIFO_WORDS;
|
|
regval = (address << OTGHS_DIEPTXF_INEPTXSA_SHIFT) |
|
|
(STM32_EP1_TXFIFO_WORDS << OTGHS_DIEPTXF_INEPTXFD_SHIFT);
|
|
stm32_putreg(regval, STM32_OTGHS_DIEPTXF1);
|
|
|
|
/* EP2 TX */
|
|
|
|
address += STM32_EP1_TXFIFO_WORDS;
|
|
regval = (address << OTGHS_DIEPTXF_INEPTXSA_SHIFT) |
|
|
(STM32_EP2_TXFIFO_WORDS << OTGHS_DIEPTXF_INEPTXFD_SHIFT);
|
|
stm32_putreg(regval, STM32_OTGHS_DIEPTXF2);
|
|
|
|
/* EP3 TX */
|
|
|
|
address += STM32_EP2_TXFIFO_WORDS;
|
|
regval = (address << OTGHS_DIEPTXF_INEPTXSA_SHIFT) |
|
|
(STM32_EP3_TXFIFO_WORDS << OTGHS_DIEPTXF_INEPTXFD_SHIFT);
|
|
stm32_putreg(regval, STM32_OTGHS_DIEPTXF3);
|
|
|
|
/* Flush the FIFOs */
|
|
|
|
stm32_txfifo_flush(OTGHS_GRSTCTL_TXFNUM_DALL);
|
|
stm32_rxfifo_flush();
|
|
|
|
/* Clear all pending Device Interrupts */
|
|
|
|
stm32_putreg(0, STM32_OTGHS_DIEPMSK);
|
|
stm32_putreg(0, STM32_OTGHS_DOEPMSK);
|
|
stm32_putreg(0, STM32_OTGHS_DIEPEMPMSK);
|
|
stm32_putreg(0xffffffff, STM32_OTGHS_DAINT);
|
|
stm32_putreg(0, STM32_OTGHS_DAINTMSK);
|
|
|
|
/* Configure all IN endpoints */
|
|
|
|
for (i = 0; i < STM32_NENDPOINTS; i++)
|
|
{
|
|
regval = stm32_getreg(STM32_OTGHS_DIEPCTL(i));
|
|
if ((regval & OTGHS_DIEPCTL_EPENA) != 0)
|
|
{
|
|
/* The endpoint is already enabled */
|
|
|
|
regval = OTGHS_DIEPCTL_EPENA | OTGHS_DIEPCTL_SNAK;
|
|
}
|
|
else
|
|
{
|
|
regval = 0;
|
|
}
|
|
|
|
stm32_putreg(regval, STM32_OTGHS_DIEPCTL(i));
|
|
stm32_putreg(0, STM32_OTGHS_DIEPTSIZ(i));
|
|
stm32_putreg(0xff, STM32_OTGHS_DIEPINT(i));
|
|
}
|
|
|
|
/* Configure all OUT endpoints */
|
|
|
|
for (i = 0; i < STM32_NENDPOINTS; i++)
|
|
{
|
|
regval = stm32_getreg(STM32_OTGHS_DOEPCTL(i));
|
|
if ((regval & OTGHS_DOEPCTL_EPENA) != 0)
|
|
{
|
|
/* The endpoint is already enabled */
|
|
|
|
regval = OTGHS_DOEPCTL_EPENA | OTGHS_DOEPCTL_SNAK;
|
|
}
|
|
else
|
|
{
|
|
regval = 0;
|
|
}
|
|
|
|
stm32_putreg(regval, STM32_OTGHS_DOEPCTL(i));
|
|
stm32_putreg(0, STM32_OTGHS_DOEPTSIZ(i));
|
|
stm32_putreg(0xff, STM32_OTGHS_DOEPINT(i));
|
|
}
|
|
|
|
/* Disable all interrupts. */
|
|
|
|
stm32_putreg(0, STM32_OTGHS_GINTMSK);
|
|
|
|
/* Clear any pending USB_OTG Interrupts */
|
|
|
|
stm32_putreg(0xffffffff, STM32_OTGHS_GOTGINT);
|
|
|
|
/* Clear any pending interrupts */
|
|
|
|
stm32_putreg(0xbfffffff, STM32_OTGHS_GINTSTS);
|
|
|
|
#ifndef BOARD_ENABLE_USBOTG_HSULPI
|
|
/* Disable the ULPI Clock enable in RCC AHB1 Register. This must
|
|
* be done because if both the ULPI and the FS PHY clock enable bits
|
|
* are set at the same time, the ARM never awakens from WFI due to
|
|
* some bug / errata in the chip.
|
|
*/
|
|
|
|
regval = stm32_getreg(STM32_RCC_AHB1LPENR);
|
|
regval &= ~RCC_AHB1ENR_OTGHSULPIEN;
|
|
stm32_putreg(regval, STM32_RCC_AHB1LPENR);
|
|
#endif
|
|
|
|
/* Enable the interrupts in the INTMSK */
|
|
|
|
regval = (OTGHS_GINT_RXFLVL | OTGHS_GINT_USBSUSP | OTGHS_GINT_ENUMDNE |
|
|
OTGHS_GINT_IEP | OTGHS_GINT_OEP | OTGHS_GINT_USBRST);
|
|
|
|
#ifdef CONFIG_USBDEV_ISOCHRONOUS
|
|
regval |= (OTGHS_GINT_IISOIXFR | OTGHS_GINT_IISOOXFR);
|
|
#endif
|
|
|
|
#ifdef CONFIG_USBDEV_SOFINTERRUPT
|
|
regval |= OTGHS_GINT_SOF;
|
|
#endif
|
|
|
|
#ifdef CONFIG_USBDEV_VBUSSENSING
|
|
regval |= (OTGHS_GINT_OTG | OTGHS_GINT_SRQ);
|
|
#endif
|
|
|
|
#ifdef CONFIG_DEBUG_USB
|
|
regval |= OTGHS_GINT_MMIS;
|
|
#endif
|
|
|
|
stm32_putreg(regval, STM32_OTGHS_GINTMSK);
|
|
|
|
/* Enable the USB global interrupt by setting GINTMSK in the global OTG
|
|
* HS AHB configuration register; Set the TXFELVL bit in the GAHBCFG
|
|
* register so that TxFIFO interrupts will occur when the TxFIFO is truly
|
|
* empty (not just half full).
|
|
*/
|
|
|
|
stm32_putreg(OTGHS_GAHBCFG_GINTMSK | OTGHS_GAHBCFG_TXFELVL,
|
|
STM32_OTGHS_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 HS device support. Hence it is
|
|
* pre-allocated as g_otghsdev. 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_otghsdev;
|
|
int ret;
|
|
|
|
usbtrace(TRACE_DEVINIT, 0);
|
|
|
|
/* Here we assume that:
|
|
*
|
|
* 1. GPIOA and OTG HS 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 HS alternate function pins
|
|
*
|
|
* PIN* SIGNAL DIRECTION
|
|
* ---- ----------- ----------
|
|
* PA8 OTG_HS_SOF SOF clock output
|
|
* PA9 OTG_HS_VBUS VBUS input for device, Driven by external regulator by
|
|
* host (not an alternate function)
|
|
* PA10 OTG_HS_ID OTG ID pin (only needed in Dual mode)
|
|
* PA11 OTG_HS_DM D- I/O
|
|
* PA12 OTG_HS_DP D+ I/O
|
|
*
|
|
* *Pins may vary from device-to-device.
|
|
*/
|
|
|
|
stm32_configgpio(GPIO_OTGHS_DM);
|
|
stm32_configgpio(GPIO_OTGHS_DP);
|
|
stm32_configgpio(GPIO_OTGHS_ID); /* Only needed for OTG */
|
|
|
|
/* SOF output pin configuration is configurable. */
|
|
|
|
#ifdef CONFIG_STM32_OTGHS_SOFOUTPUT
|
|
stm32_configgpio(GPIO_OTGHS_SOF);
|
|
#endif
|
|
|
|
/* 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 HS interrupt handler */
|
|
|
|
ret = irq_attach(STM32_IRQ_OTGHS, stm32_usbinterrupt, NULL);
|
|
if (ret < 0)
|
|
{
|
|
uerr("ERROR: 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_OTGHS);
|
|
|
|
#ifdef CONFIG_ARCH_IRQPRIO
|
|
/* Set the interrupt priority */
|
|
|
|
up_prioritize_irq(STM32_IRQ_OTGHS, CONFIG_OTGHS_PRI);
|
|
#endif
|
|
return;
|
|
|
|
errout:
|
|
up_usbuninitialize();
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: up_usbhsuninitialize
|
|
****************************************************************************/
|
|
|
|
void up_usbuninitialize(void)
|
|
{
|
|
/* At present, there is only a single OTG HS device support. Hence it is
|
|
* pre-allocated as g_otghsdev. 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_otghsdev;
|
|
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 = enter_critical_section();
|
|
stm32_pullup(&priv->usbdev, false);
|
|
priv->usbdev.speed = USB_SPEED_UNKNOWN;
|
|
|
|
/* Disable and detach IRQs */
|
|
|
|
up_disable_irq(STM32_IRQ_OTGHS);
|
|
irq_detach(STM32_IRQ_OTGHS);
|
|
|
|
/* Disable all endpoint interrupts */
|
|
|
|
for (i = 0; i < STM32_NENDPOINTS; i++)
|
|
{
|
|
stm32_putreg(0xff, STM32_OTGHS_DIEPINT(i));
|
|
stm32_putreg(0xff, STM32_OTGHS_DOEPINT(i));
|
|
}
|
|
|
|
stm32_putreg(0, STM32_OTGHS_DIEPMSK);
|
|
stm32_putreg(0, STM32_OTGHS_DOEPMSK);
|
|
stm32_putreg(0, STM32_OTGHS_DIEPEMPMSK);
|
|
stm32_putreg(0, STM32_OTGHS_DAINTMSK);
|
|
stm32_putreg(0xffffffff, STM32_OTGHS_DAINT);
|
|
|
|
/* Flush the FIFOs */
|
|
|
|
stm32_txfifo_flush(OTGHS_GRSTCTL_TXFNUM_DALL);
|
|
stm32_rxfifo_flush();
|
|
|
|
/* TODO: Turn off USB power and clocking */
|
|
|
|
priv->devstate = DEVSTATE_DEFAULT;
|
|
leave_critical_section(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)
|
|
{
|
|
/* At present, there is only a single OTG HS device support. Hence it is
|
|
* pre-allocated as g_otghsdev. 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_otghsdev;
|
|
int ret;
|
|
|
|
usbtrace(TRACE_DEVREGISTER, 0);
|
|
|
|
#ifdef CONFIG_DEBUG_FEATURES
|
|
if (!driver || !driver->ops->bind || !driver->ops->unbind ||
|
|
!driver->ops->disconnect || !driver->ops->setup)
|
|
{
|
|
usbtrace(TRACE_DEVERROR(STM32_TRACEERR_INVALIDPARMS), 0);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (priv->driver)
|
|
{
|
|
usbtrace(TRACE_DEVERROR(STM32_TRACEERR_DRIVER), 0);
|
|
return -EBUSY;
|
|
}
|
|
#endif
|
|
|
|
/* First hook up the driver */
|
|
|
|
priv->driver = driver;
|
|
|
|
/* Then bind the class driver */
|
|
|
|
ret = CLASS_BIND(driver, &priv->usbdev);
|
|
if (ret)
|
|
{
|
|
usbtrace(TRACE_DEVERROR(STM32_TRACEERR_BINDFAILED), (uint16_t)-ret);
|
|
priv->driver = NULL;
|
|
}
|
|
else
|
|
{
|
|
/* Enable USB controller interrupts */
|
|
|
|
up_enable_irq(STM32_IRQ_OTGHS);
|
|
|
|
/* 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(&priv->usbdev, true);
|
|
priv->usbdev.speed = USB_SPEED_FULL;
|
|
}
|
|
|
|
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)
|
|
{
|
|
/* At present, there is only a single OTG HS device support. Hence it is
|
|
* pre-allocated as g_otghsdev. 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_otghsdev;
|
|
irqstate_t flags;
|
|
|
|
usbtrace(TRACE_DEVUNREGISTER, 0);
|
|
|
|
#ifdef CONFIG_DEBUG_FEATURES
|
|
if (driver != priv->driver)
|
|
{
|
|
usbtrace(TRACE_DEVERROR(STM32_TRACEERR_INVALIDPARMS), 0);
|
|
return -EINVAL;
|
|
}
|
|
#endif
|
|
|
|
/* Reset the hardware and cancel all requests. All requests must be
|
|
* canceled while the class driver is still bound.
|
|
*/
|
|
|
|
flags = enter_critical_section();
|
|
stm32_usbreset(priv);
|
|
leave_critical_section(flags);
|
|
|
|
/* Unbind the class driver */
|
|
|
|
CLASS_UNBIND(driver, &priv->usbdev);
|
|
|
|
/* Disable USB controller interrupts */
|
|
|
|
flags = enter_critical_section();
|
|
up_disable_irq(STM32_IRQ_OTGHS);
|
|
|
|
/* Disconnect device */
|
|
|
|
stm32_pullup(&priv->usbdev, false);
|
|
|
|
/* Unhook the driver */
|
|
|
|
priv->driver = NULL;
|
|
leave_critical_section(flags);
|
|
|
|
return OK;
|
|
}
|
|
|
|
#endif /* CONFIG_USBDEV && CONFIG_STM32_OTGHSDEV */
|