* arch/arm/src/stm32/stm32_otghsdev.c
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <sys/param.h>
#include <sys/types.h>
#include <inttypes.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include <debug.h>
#include <nuttx/arch.h>
#include <nuttx/kmalloc.h>
#include <nuttx/spinlock.h>
#include <nuttx/usb/usb.h>
#include <nuttx/usb/usbdev.h>
#include <nuttx/usb/usbdev_trace.h>
#include <arch/board/board.h>
#include "chip.h"
#include "arm_internal.h"
#include "stm32_otghs.h"
#include "stm32_rcc.h"
#if defined(CONFIG_USBDEV) && (defined(CONFIG_STM32_OTGHS))
* Pre-processor Definitions
****************************************************************************/
#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
#endif
#ifndef CONFIG_DEBUG_USB_INFO
# undef CONFIG_STM32_USBDEV_REGDEBUG
#endif
* 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
* 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
#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
#define STM32_TRACEINTID_USB 1
#define STM32_TRACEINTID_INTPENDING 2
#define STM32_TRACEINTID_EPOUT (10 + 0)
#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)
#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)
#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)
#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)
#define STM32_TRACEINTID_OUTNAK (90 + 0)
#define STM32_TRACEINTID_OUTRECVD (90 + 1)
#define STM32_TRACEINTID_OUTDONE (90 + 2)
#define STM32_TRACEINTID_SETUPDONE (90 + 3)
#define STM32_TRACEINTID_SETUPRECVD (90 + 4)
#define STM32_EPPHYIN2LOG(epphy) ((uint8_t)(epphy)|USB_DIR_IN)
#define STM32_EPPHYOUT2LOG(epphy) ((uint8_t)(epphy)|USB_DIR_OUT)
#define EP0 (0)
#define STM32_EP_AVAILABLE (0x0e)
#define STM32_MAXPACKET (64)
#define STM32_READY_DELAY 200000
#define STM32_FLUSH_DELAY 200000
#define stm32_rqempty(ep) ((ep)->head == NULL)
#define stm32_rqpeek(ep) ((ep)->head)
* Private Types
****************************************************************************/
enum stm32_devstate_e
{
DEVSTATE_DEFAULT = 0,
* 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,
* 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,
* 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.
*/
};
enum stm32_ep0state_e
{
EP0STATE_IDLE = 0,
* epsubmit:
* SET: In stm32_epin() and stm32_epout() when
* we revert from request processing to
* SETUP processing.
* TESTED: Never
*/
EP0STATE_SETUP_OUT,
* 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,
* 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,
* SET: When SETUP packet received in EP0 OUT
* TESTED: Never
*/
EP0STATE_SETUPRESPONSE,
* SET: When SETUP response is sent by
* stm32_ep0in_setupresponse()
* TESTED: Never
*/
EP0STATE_DATA_IN,
* 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
* 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
*/
};
struct stm32_ctrlreq_s
{
uint8_t type;
uint8_t req;
uint16_t value;
uint16_t index;
uint16_t len;
};
struct stm32_req_s
{
struct usbdev_req_s req;
struct stm32_req_s *flink;
};
struct stm32_ep_s
{
* structure so that it is possible to simply cast from struct usbdev_ep_s
* to struct stm32_ep_s.
*/
struct usbdev_ep_s ep;
struct stm32_usbdev_s *dev;
struct stm32_req_s *head;
struct stm32_req_s *tail;
uint8_t epphy;
uint8_t eptype:2;
uint8_t active:1;
uint8_t stalled:1;
uint8_t isin:1;
uint8_t odd:1;
uint8_t zlp:1;
};
struct stm32_usbdev_s
{
* structure so that it is possible to simply cast from struct usbdev_s
* to struct stm32_usbdev_s.
*/
struct usbdev_s usbdev;
struct usbdevclass_driver_s *driver;
uint8_t stalled:1;
uint8_t selfpowered:1;
uint8_t addressed:1;
uint8_t configured:1;
uint8_t wakeup:1;
uint8_t dotest:1;
uint8_t devstate:4;
uint8_t ep0state:4;
uint8_t testmode:4;
uint8_t epavail[2];
*
* 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;
struct stm32_ep_s epin[STM32_NENDPOINTS];
struct stm32_ep_s epout[STM32_NENDPOINTS];
};
* Private Function Prototypes
****************************************************************************/
#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
static struct stm32_req_s *stm32_req_remfirst(
struct stm32_ep_s *privep);
static bool stm32_req_addlast(struct stm32_ep_s *privep,
struct stm32_req_s *req);
static void stm32_ep0in_setupresponse(struct stm32_usbdev_s *priv,
uint8_t *data, uint32_t nbytes);
static inline void stm32_ep0in_transmitzlp(struct stm32_usbdev_s *priv);
static void stm32_ep0in_activate(void);
static void stm32_ep0out_ctrlsetup(struct stm32_usbdev_s *priv);
static void stm32_txfifo_write(struct stm32_ep_s *privep,
uint8_t *buf, int nbytes);
static void stm32_epin_transfer(struct stm32_ep_s *privep,
uint8_t *buf, int nbytes);
static void stm32_epin_request(struct stm32_usbdev_s *priv,
struct stm32_ep_s *privep);
static void stm32_rxfifo_read(struct stm32_ep_s *privep,
uint8_t *dest, uint16_t len);
static void stm32_rxfifo_discard(struct stm32_ep_s *privep,
int len);
static void stm32_epout_complete(struct stm32_usbdev_s *priv,
struct stm32_ep_s *privep);
static inline void stm32_ep0out_receive(struct stm32_ep_s *privep,
int bcnt);
static inline void stm32_epout_receive(struct stm32_ep_s *privep,
int bcnt);
static void stm32_epout_request(struct stm32_usbdev_s *priv,
struct stm32_ep_s *privep);
static void stm32_ep_flush(struct stm32_ep_s *privep);
static void stm32_req_complete(struct stm32_ep_s *privep,
int16_t result);
static void stm32_req_cancel(struct stm32_ep_s *privep,
int16_t status);
static struct stm32_ep_s *stm32_ep_findbyaddr(
struct stm32_usbdev_s *priv, uint16_t eplog);
static int stm32_req_dispatch(struct stm32_usbdev_s *priv,
const struct usb_ctrlreq_s *ctrl);
static void stm32_usbreset(struct stm32_usbdev_s *priv);
static inline void stm32_ep0out_testmode(struct stm32_usbdev_s *priv,
uint16_t index);
static inline void stm32_ep0out_stdrequest(struct stm32_usbdev_s *priv,
struct stm32_ctrlreq_s *ctrlreq);
static inline void stm32_ep0out_setup(struct stm32_usbdev_s *priv);
static inline void stm32_epout(struct stm32_usbdev_s *priv,
uint8_t epno);
static inline void stm32_epout_interrupt(struct stm32_usbdev_s *priv);
static inline void stm32_epin_runtestmode(struct stm32_usbdev_s *priv);
static inline void stm32_epin(struct stm32_usbdev_s *priv, uint8_t epno);
static inline void stm32_epin_txfifoempty(struct stm32_usbdev_s *priv,
int epno);
static inline void stm32_epin_interrupt(struct stm32_usbdev_s *priv);
static inline void stm32_resumeinterrupt(struct stm32_usbdev_s *priv);
static inline void stm32_suspendinterrupt(struct stm32_usbdev_s *priv);
static inline void stm32_rxinterrupt(struct stm32_usbdev_s *priv);
static inline void stm32_enuminterrupt(struct stm32_usbdev_s *priv);
#ifdef CONFIG_USBDEV_ISOCHRONOUS
static inline void stm32_isocininterrupt(struct stm32_usbdev_s *priv);
static inline void stm32_isocoutinterrupt(struct stm32_usbdev_s *priv);
#endif
#ifdef CONFIG_USBDEV_VBUSSENSING
static inline void stm32_sessioninterrupt(struct stm32_usbdev_s *priv);
static inline void stm32_otginterrupt(struct stm32_usbdev_s *priv);
#endif
static int stm32_usbinterrupt(int irq, void *context,
void *arg);
static void stm32_enablegonak(struct stm32_ep_s *privep);
static void stm32_disablegonak(struct stm32_ep_s *privep);
static int stm32_epout_configure(struct stm32_ep_s *privep,
uint8_t eptype, uint16_t maxpacket);
static int stm32_epin_configure(struct stm32_ep_s *privep,
uint8_t eptype, uint16_t maxpacket);
static int stm32_ep_configure(struct usbdev_ep_s *ep,
const struct usb_epdesc_s *desc, bool last);
static void stm32_ep0_configure(struct stm32_usbdev_s *priv);
static void stm32_epout_disable(struct stm32_ep_s *privep);
static void stm32_epin_disable(struct stm32_ep_s *privep);
static int stm32_ep_disable(struct usbdev_ep_s *ep);
static struct usbdev_req_s *stm32_ep_allocreq(
struct usbdev_ep_s *ep);
static void stm32_ep_freereq(struct usbdev_ep_s *ep,
struct usbdev_req_s *);
#ifdef CONFIG_USBDEV_DMA
static void *stm32_ep_allocbuffer(struct usbdev_ep_s *ep,
uint16_t bytes);
static void stm32_ep_freebuffer(struct usbdev_ep_s *ep,
void *buf);
#endif
static int stm32_ep_submit(struct usbdev_ep_s *ep,
struct usbdev_req_s *req);
static int stm32_ep_cancel(struct usbdev_ep_s *ep,
struct usbdev_req_s *req);
static int stm32_epout_setstall(struct stm32_ep_s *privep);
static int stm32_epin_setstall(struct stm32_ep_s *privep);
static int stm32_ep_setstall(struct stm32_ep_s *privep);
static int stm32_ep_clrstall(struct stm32_ep_s *privep);
static int stm32_ep_stall(struct usbdev_ep_s *ep, bool resume);
static void stm32_ep0_stall(struct stm32_usbdev_s *priv);
static struct usbdev_ep_s *stm32_ep_alloc(struct usbdev_s *dev,
uint8_t epno, bool in, uint8_t eptype);
static void stm32_ep_free(struct usbdev_s *dev,
struct usbdev_ep_s *ep);
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);
static void stm32_swinitialize(struct stm32_usbdev_s *priv);
static void stm32_hwinitialize(struct stm32_usbdev_s *priv);
* Private Data
****************************************************************************/
* 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,
};
* 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
* 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;
uint32_t val = getreg32(addr);
* 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;
}
}
else
{
if (count > 3)
{
uinfo("[repeats %d more times]\n", count - 3);
}
prevaddr = addr;
preval = val;
count = 1;
}
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)
{
uinfo("%08x<-%08x\n", addr, val);
putreg32(val, addr);
}
#endif
* Name: stm32_req_remfirst
*
* Description:
* Remove a request from the head of an endpoint request queue
*
****************************************************************************/
static struct stm32_req_s *stm32_req_remfirst(
struct stm32_ep_s *privep)
{
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(struct stm32_ep_s *privep,
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(struct stm32_usbdev_s *priv,
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(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;
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);
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(struct stm32_usbdev_s *priv)
{
uint32_t regval;
regval = (USB_SIZEOF_CTRLREQ * 3 << OTGHS_DOEPTSIZ0_XFRSIZ_SHIFT) |
(OTGHS_DOEPTSIZ0_PKTCNT) |
(3 << OTGHS_DOEPTSIZ0_STUPCNT_SHIFT);
stm32_putreg(regval, STM32_OTGHS_DOEPTSIZ0);
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(struct stm32_ep_s *privep,
uint8_t *buf, int nbytes)
{
uint32_t regaddr;
uint32_t regval;
int nwords;
int i;
nwords = (nbytes + 3) >> 2;
regaddr = STM32_OTGHS_DFIFO_DEP(privep->epphy);
for (i = 0; i < nwords; i++)
{
* 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;
stm32_putreg(regval, regaddr);
}
}
* Name: stm32_epin_transfer
*
* Description:
* Start the Tx data transfer
*
****************************************************************************/
static void stm32_epin_transfer(struct stm32_ep_s *privep,
uint8_t *buf, int nbytes)
{
uint32_t pktcnt;
uint32_t regval;
regval = stm32_getreg(STM32_OTGHS_DIEPTSIZ(privep->epphy));
regval &= ~(OTGHS_DIEPTSIZ_XFRSIZ_MASK | OTGHS_DIEPTSIZ_PKTCNT_MASK |
OTGHS_DIEPTSIZ_MCNT_MASK);
if (nbytes == 0)
{
* leave the transfer size at zero and set the packet count to 1
*/
pktcnt = 1;
}
else
{
*
* 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;
}
regval |= (pktcnt << OTGHS_DIEPTSIZ_PKTCNT_SHIFT);
regval |= ((uint32_t)nbytes << OTGHS_DIEPTSIZ_XFRSIZ_SHIFT);
* the PKTCNT as well.
*/
if (privep->eptype == USB_EP_ATTR_XFER_ISOC)
{
regval |= (pktcnt << OTGHS_DIEPTSIZ_MCNT_SHIFT);
}
stm32_putreg(regval, STM32_OTGHS_DIEPTSIZ(privep->epphy));
regval = stm32_getreg(STM32_OTGHS_DIEPCTL(privep->epphy));
* the DIEPCTLx register.
*/
if (privep->eptype == USB_EP_ATTR_XFER_ISOC)
{
* 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;
}
}
regval &= ~OTGHS_DIEPCTL_EPDIS;
regval |= (OTGHS_DIEPCTL_CNAK | OTGHS_DIEPCTL_EPENA);
stm32_putreg(regval, STM32_OTGHS_DIEPCTL(privep->epphy));
* 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(struct stm32_usbdev_s *priv,
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;
* 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).
*/
privreq = stm32_rqpeek(privep);
if (!privreq)
{
usbtrace(TRACE_DEVERROR(STM32_TRACEERR_EPINREQEMPTY), privep->epphy);
* 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);
privep->active = false;
return;
}
uinfo("EP%d req=%p: len=%zu xfrd=%zu zlp=%d\n",
privep->epphy, privreq, privreq->req.len,
privreq->req.xfrd, privep->zlp);
* 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)
{
* 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;
}
* 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
{
bytesleft = privreq->req.len - privreq->req.xfrd;
nbytes = bytesleft;
privep->zlp = false;
* zero-length packets (ZLPs).
*/
if (nbytes > 0)
{
* the request.
*/
if (nbytes >= privep->ep.maxpacket)
{
nbytes = privep->ep.maxpacket;
* 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 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;
}
}
}
nwords = (nbytes + 3) >> 2;
* 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);
* 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);
* empty interrupt and try again.
*/
uint32_t empmsk = stm32_getreg(STM32_OTGHS_DIEPEMPMSK);
empmsk |= OTGHS_DIEPEMPMSK(privep->epphy);
stm32_putreg(empmsk, STM32_OTGHS_DIEPEMPMSK);
#ifdef CONFIG_DEBUG_FEATURES
* request. If not, raise an assertion here.
*/
regval = stm32_getreg(STM32_OTGHS_DIEPTXF(privep->epphy));
regval &= OTGHS_DIEPTXF_INEPTXFD_MASK;
regval >>= OTGHS_DIEPTXF_INEPTXFD_SHIFT;
uerr("EP%" PRId8 " TXLEN=%" PRId32 " nwords=%d\n",
privep->epphy, regval, nwords);
DEBUGASSERT(regval >= nwords);
#endif
* interrupt is received.
*/
return;
}
buf = privreq->req.buf + privreq->req.xfrd;
stm32_epin_transfer(privep, buf, nbytes);
* data.
*/
privep->active = true;
if (privep->epphy == EP0)
{
priv->ep0state = EP0STATE_DATA_IN;
}
privreq->req.xfrd += nbytes;
}
* 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);
* 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(struct stm32_ep_s *privep,
uint8_t *dest, uint16_t len)
{
uint32_t regaddr;
int i;
* we might as well use the address associated with EP0.
*/
regaddr = STM32_OTGHS_DFIFO_DEP(EP0);
* (to avoid unaligned accesses)
*/
for (i = 0; i < len; i += 4)
{
union
{
uint32_t w;
uint8_t b[4];
} data;
data.w = stm32_getreg(regaddr);
*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(struct stm32_ep_s *privep, int len)
{
if (len > 0)
{
uint32_t regaddr;
int i;
* we might as well use the address associated with EP0.
*/
regaddr = STM32_OTGHS_DFIFO_DEP(EP0);
for (i = 0; i < len; i += 4)
{
volatile uint32_t data = stm32_getreg(regaddr);
UNUSED(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(struct stm32_usbdev_s *priv,
struct stm32_ep_s *privep)
{
struct stm32_req_s *privreq;
* head of the endpoint request queue.
*/
privreq = stm32_rqpeek(privep);
DEBUGASSERT(privreq);
if (!privreq)
{
* should not happen.
*/
usbtrace(TRACE_DEVERROR(STM32_TRACEERR_EPOUTQEMPTY), privep->epphy);
privep->active = false;
return;
}
uinfo("EP%d: len=%zu xfrd=%zu\n",
privep->epphy, privreq->req.len, privreq->req.xfrd);
* IDLE.
*/
usbtrace(TRACE_COMPLETE(privep->epphy), privreq->req.xfrd);
stm32_req_complete(privep, OK);
privep->active = false;
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(struct stm32_ep_s *privep,
int bcnt)
{
struct stm32_usbdev_s *priv;
DEBUGASSERT(privep && privep->dev);
priv = (struct stm32_usbdev_s *)privep->dev;
uinfo("EP0: bcnt=%d\n", bcnt);
usbtrace(TRACE_READ(EP0), bcnt);
* received in the RxFIFO.
*/
if (priv->ep0state == EP0STATE_SETUP_OUT)
{
int readlen = MIN(CONFIG_USBDEV_SETUP_MAXDATASIZE, bcnt);
stm32_rxfifo_read(privep, priv->ep0data, readlen);
stm32_rxfifo_discard(privep, bcnt - readlen);
privep->active = false;
priv->ep0state = EP0STATE_SETUP_READY;
priv->ep0datlen = readlen;
stm32_ep0out_setup(priv);
}
else
{
* 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(struct stm32_ep_s *privep,
int bcnt)
{
struct stm32_req_s *privreq;
uint8_t *dest;
int buflen;
int readlen;
* queue.
*/
privreq = stm32_rqpeek(privep);
if (!privreq)
{
* 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
{
* NAKing is working as expected.
*/
usbtrace(
TRACE_DEVERROR(STM32_TRACEERR_EPOUTQEMPTY), privep->epphy);
stm32_rxfifo_discard(privep, bcnt);
}
privep->active = false;
return;
}
uinfo("EP%d: len=%zu xfrd=%zu\n",
privep->epphy, privreq->req.len, privreq->req.xfrd);
usbtrace(TRACE_READ(privep->epphy), bcnt);
buflen = privreq->req.len - privreq->req.xfrd;
DEBUGASSERT(buflen > 0 && buflen >= bcnt);
readlen = MIN(buflen, bcnt);
dest = privreq->req.buf + privreq->req.xfrd;
stm32_rxfifo_read(privep, dest, readlen);
* request, then we will have to discard those.
*/
stm32_rxfifo_discard(privep, bcnt - readlen);
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(struct stm32_usbdev_s *priv,
struct stm32_ep_s *privep)
{
struct stm32_req_s *privreq;
uint32_t regaddr;
uint32_t regval;
uint32_t xfrsize;
uint32_t pktcnt;
* is, just return, leaving the newly received request in the request
* queue.
*/
if (!privep->active)
{
* The loop is only need to look at the request queue again is an
* invalid read request is encountered.
*/
for (; ; )
{
* request queue
*/
privreq = stm32_rqpeek(privep);
if (!privreq)
{
usbtrace(
TRACE_DEVERROR(STM32_TRACEERR_EPOUTQEMPTY), privep->epphy);
* 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);
privep->active = false;
return;
}
uinfo("EP%d: len=%d\n", privep->epphy, privreq->req.len);
* should not happen.
*/
if (privreq->req.len <= 0)
{
usbtrace(TRACE_DEVERROR(STM32_TRACEERR_EPOUTNULLPACKET), 0);
stm32_req_complete(privep, OK);
}
* break out of the loop
*/
else
{
break;
}
}
*
* 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;
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);
regaddr = STM32_OTGHS_DOEPCTL(privep->epphy);
regval = stm32_getreg(regaddr);
* 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
regval |= (OTGHS_DOEPCTL_CNAK | OTGHS_DOEPCTL_EPENA);
stm32_putreg(regval, regaddr);
privep->active = true;
* 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)
{
struct stm32_req_s *privreq;
privreq = stm32_req_remfirst(privep);
DEBUGASSERT(privreq != NULL);
* in the callback.
*/
bool stalled = privep->stalled;
if (privep->epphy == EP0)
{
privep->stalled = privep->dev->stalled;
}
privreq->req.result = result;
privreq->req.callback(&privep->ep, &privreq->req);
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;
}
if (USB_ISEPIN(eplog))
{
privep = &priv->epin[epphy];
}
else
{
privep = &priv->epout[epphy];
}
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)
{
ret = CLASS_SETUP(priv->driver, &priv->usbdev, ctrl,
priv->ep0data, priv->ep0datlen);
}
if (ret < 0)
{
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)
{
struct stm32_ep_s *privep;
uint32_t regval;
int i;
regval = stm32_getreg(STM32_OTGHS_DCTL);
regval &= ~OTGHS_DCTL_RWUSIG;
stm32_putreg(regval, STM32_OTGHS_DCTL);
stm32_txfifo_flush(OTGHS_GRSTCTL_TXFNUM_D(EP0));
* driver should then accept any new configurations.
*/
if (priv->driver)
{
CLASS_DISCONNECT(priv->driver, &priv->usbdev);
}
priv->epavail[0] = STM32_EP_AVAILABLE;
priv->epavail[1] = STM32_EP_AVAILABLE;
for (i = 0; i < STM32_NENDPOINTS ; i++)
{
stm32_putreg(0xff, STM32_OTGHS_DIEPINT(i));
stm32_putreg(0xff, STM32_OTGHS_DOEPINT(i));
privep = &priv->epin[i];
stm32_req_cancel(privep, -ESHUTDOWN);
privep->stalled = false;
privep = &priv->epout[i];
stm32_req_cancel(privep, -ESHUTDOWN);
privep->stalled = false;
}
stm32_putreg(0xffffffff, STM32_OTGHS_DAINT);
regval = (OTGHS_DAINT_IEP(EP0) | OTGHS_DAINT_OEP(EP0));
stm32_putreg(regval, STM32_OTGHS_DAINTMSK);
regval = (OTGHS_DOEPMSK_XFRCM | OTGHS_DOEPMSK_STUPM | OTGHS_DOEPMSK_EPDM);
stm32_putreg(regval, STM32_OTGHS_DOEPMSK);
regval = (OTGHS_DIEPMSK_XFRCM | OTGHS_DIEPMSK_EPDM | OTGHS_DIEPMSK_TOM);
stm32_putreg(regval, STM32_OTGHS_DIEPMSK);
stm32_setaddress(priv, 0);
priv->devstate = DEVSTATE_DEFAULT;
priv->usbdev.speed = USB_SPEED_FULL;
stm32_ep0_configure(priv);
stm32_ep0out_ctrlsetup(priv);
}
* Name: stm32_ep0out_testmode
*
* Description:
* Select test mode
*
****************************************************************************/
static inline void stm32_ep0out_testmode(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,
struct stm32_ctrlreq_s *ctrlreq)
{
struct stm32_ep_s *privep;
switch (ctrlreq->req)
{
case USB_REQ_GETSTATUS:
{
* 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;
}
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);
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:
{
* 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
{
stm32_req_dispatch(priv, &priv->ctrlreq);
}
}
else
{
usbtrace(TRACE_DEVERROR(STM32_TRACEERR_BADCLEARFEATURE), 0);
priv->stalled = true;
}
}
break;
case USB_REQ_SETFEATURE:
{
* 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)
{
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:
{
* 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)
{
* 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:
* value: descriptor type and index
* index: 0 or language ID;
* len: descriptor len; data = descriptor
*/
case USB_REQ_SETDESCRIPTOR:
* 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 ||
(ctrlreq->type & USB_REQ_RECIPIENT_MASK) ==
USB_REQ_RECIPIENT_INTERFACE)
{
stm32_req_dispatch(priv, &priv->ctrlreq);
}
else
{
usbtrace(TRACE_DEVERROR(STM32_TRACEERR_BADGETSETDESC), 0);
priv->stalled = true;
}
}
break;
case USB_REQ_GETCONFIGURATION:
* 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)
{
stm32_req_dispatch(priv, &priv->ctrlreq);
}
else
{
usbtrace(TRACE_DEVERROR(STM32_TRACEERR_BADGETCONFIG), 0);
priv->stalled = true;
}
}
break;
case USB_REQ_SETCONFIGURATION:
* 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)
{
int ret = stm32_req_dispatch(priv, &priv->ctrlreq);
* 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:
* value: 0
* index: interface;
* len: 1; data = alt interface
*/
case USB_REQ_SETINTERFACE:
* value: alternate setting
* index: interface;
* len: 0; data = none
*/
{
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_GETSETIF), 0);
stm32_req_dispatch(priv, &priv->ctrlreq);
}
break;
case USB_REQ_SYNCHFRAME:
* 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;
if (priv->ep0state != EP0STATE_SETUP_READY)
{
usbtrace(TRACE_DEVERROR(STM32_TRACEERR_EP0NOSETUP), priv->ep0state);
return;
}
stm32_req_cancel(&priv->epout[EP0], -EPROTO);
stm32_req_cancel(&priv->epin[EP0], -EPROTO);
priv->epout[EP0].stalled = false;
priv->epin[EP0].stalled = false;
priv->stalled = false;
priv->ep0state = EP0STATE_SETUP_PROCESS;
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);
if ((ctrlreq.type & USB_REQ_TYPE_MASK) != USB_REQ_TYPE_STANDARD)
{
stm32_req_dispatch(priv, &priv->ctrlreq);
}
else
{
stm32_ep0out_stdrequest(priv, &ctrlreq);
}
if (priv->stalled)
{
usbtrace(TRACE_DEVERROR(STM32_TRACEERR_EP0SETUPSTALLED),
priv->ep0state);
stm32_ep0_stall(priv);
}
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(struct stm32_usbdev_s *priv, uint8_t epno)
{
struct stm32_ep_s *privep;
if (epno == 0)
{
privep = &priv->epout[EP0];
* request buffer. In that case, we must continue the request
* processing.
*/
if (priv->ep0state == EP0STATE_DATA_OUT)
{
stm32_epout_complete(priv, privep);
* need to setup to receive the next control request.
*/
if (!privep->active)
{
stm32_ep0out_ctrlsetup(priv);
priv->ep0state = EP0STATE_IDLE;
}
}
}
* 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(struct stm32_usbdev_s *priv)
{
uint32_t daint;
uint32_t regval;
uint32_t doepint;
int epno;
* 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)
{
* 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) = %08" PRIx32 "\n", epno, regval);
stm32_putreg(0xff, STM32_OTGHS_DOEPINT(epno));
}
epno++;
daint >>= 1;
}
return;
}
epno = 0;
while (daint)
{
if ((daint & 1) != 0)
{
doepint = stm32_getreg(STM32_OTGHS_DOEPINT(epno));
doepint &= stm32_getreg(STM32_OTGHS_DOEPMSK);
* 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);
stm32_putreg(OTGHS_DOEPINT_XFRC, STM32_OTGHS_DOEPINT(epno));
stm32_epout(priv, epno);
}
* used in polled mode by the endpoint disable logic).
*/
#if 1
if ((doepint & OTGHS_DOEPINT_EPDISD) != 0)
{
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_EPOUT_EPDISD),
(uint16_t)doepint);
stm32_putreg(OTGHS_DOEPINT_EPDISD, STM32_OTGHS_DOEPINT(epno));
}
#endif
if ((doepint & OTGHS_DOEPINT_SETUP) != 0)
{
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_EPOUT_SETUP),
priv->ep0state);
* 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(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(struct stm32_usbdev_s *priv, uint8_t epno)
{
struct stm32_ep_s *privep = &priv->epin[epno];
if (epno == 0)
{
* buffer. In that case, we must continue the request processing.
*/
if (priv->ep0state == EP0STATE_DATA_IN)
{
stm32_epin_request(priv, privep);
* need to setup to receive the next control request.
*/
if (!privep->active)
{
stm32_ep0out_ctrlsetup(priv);
priv->ep0state = EP0STATE_IDLE;
}
}
if (priv->dotest)
{
stm32_epin_runtestmode(priv);
}
}
* or finishing an IN request.
*/
else if (priv->devstate == DEVSTATE_CONFIGURED)
{
stm32_epin_request(priv, privep);
}
}
* Name: stm32_epin_txfifoempty
*
* Description:
* TxFIFO empty interrupt handling
*
****************************************************************************/
static inline void stm32_epin_txfifoempty(struct stm32_usbdev_s *priv,
int epno)
{
struct stm32_ep_s *privep = &priv->epin[epno];
* 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(struct stm32_usbdev_s *priv)
{
uint32_t diepint;
uint32_t daint;
uint32_t mask;
uint32_t empty;
int epno;
* endpoint interrupt status register.
*/
daint = stm32_getreg(STM32_OTGHS_DAINT);
daint &= stm32_getreg(STM32_OTGHS_DAINTMSK);
daint &= OTGHS_DAINT_IEP_MASK;
if (daint == 0)
{
* 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) = %08" PRIx32 "\n",
epno, stm32_getreg(STM32_OTGHS_DIEPINT(epno)));
stm32_putreg(0xff, STM32_OTGHS_DIEPINT(epno));
}
epno++;
daint >>= 1;
}
return;
}
epno = 0;
while (daint)
{
if ((daint & 1) != 0)
{
* interrupts as will be found in the DIEPINT interrupt status
* register.
*/
mask = stm32_getreg(STM32_OTGHS_DIEPMSK);
* 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;
}
* interrupts.
*/
diepint = stm32_getreg(STM32_OTGHS_DIEPINT(epno)) & mask;
if ((diepint & OTGHS_DIEPINT_XFRC) != 0)
{
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_EPIN_XFRC),
(uint16_t)diepint);
* 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));
stm32_epin(priv, epno);
}
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 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));
}
* 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
* 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
if ((diepint & OTGHS_DIEPINT_TXFE) != 0)
{
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_EPIN_TXFE),
(uint16_t)diepint);
* 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)
{
* re-enabled whenever we need to wait for a FIFO event.
*/
empty &= ~OTGHS_DIEPEMPMSK(epno);
stm32_putreg(empty, STM32_OTGHS_DIEPEMPMSK);
stm32_epin_txfifoempty(priv, epno);
}
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(struct stm32_usbdev_s *priv)
{
uint32_t regval;
#ifdef CONFIG_USBDEV_LOWPOWER
regval = stm32_getreg(STM32_OTGHS_PCGCCTL);
regval &= ~(OTGHS_PCGCCTL_STPPCLK | OTGHS_PCGCCTL_GATEHCLK);
stm32_putreg(regval, STM32_OTGHS_PCGCCTL);
#endif
regval = stm32_getreg(STM32_OTGHS_DCTL);
regval &= ~OTGHS_DCTL_RWUSIG;
stm32_putreg(regval, STM32_OTGHS_DCTL);
stm32_usbsuspend((struct usbdev_s *)priv, true);
if (priv->driver)
{
CLASS_RESUME(priv->driver, &priv->usbdev);
}
}
* Name: stm32_suspendinterrupt
*
* Description:
* USB suspend interrupt
*
****************************************************************************/
static inline void stm32_suspendinterrupt(struct stm32_usbdev_s *priv)
{
#ifdef CONFIG_USBDEV_LOWPOWER
uint32_t regval;
#endif
if (priv->driver)
{
CLASS_SUSPEND(priv->driver, &priv->usbdev);
}
#ifdef CONFIG_USBDEV_LOWPOWER
* 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)
{
* PHY clock.
*/
regval = stm32_getreg(STM32_OTGHS_PCGCCTL);
regval |= OTGHS_PCGCCTL_STPPCLK;
stm32_putreg(regval, STM32_OTGHS_PCGCCTL);
* the AHB Slave and Master and wakeup logic.
*/
regval |= OTGHS_PCGCCTL_GATEHCLK;
stm32_putreg(regval, STM32_OTGHS_PCGCCTL);
}
#endif
* state
*/
stm32_usbsuspend((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(struct stm32_usbdev_s *priv)
{
struct stm32_ep_s *privep;
uint32_t regval;
int bcnt;
int epphy;
regval = stm32_getreg(STM32_OTGHS_GINTMSK);
regval &= ~OTGHS_GINT_RXFLVL;
stm32_putreg(regval, STM32_OTGHS_GINTMSK);
regval = stm32_getreg(STM32_OTGHS_GRXSTSP);
epphy = (regval & OTGHS_GRXSTSD_EPNUM_MASK) >> OTGHS_GRXSTSD_EPNUM_SHIFT;
if (epphy < STM32_NENDPOINTS)
{
privep = &priv->epout[epphy];
switch (regval & OTGHS_GRXSTSD_PKTSTS_MASK)
{
* 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;
*
* 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;
* 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;
* 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;
* 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);
* received, the last one overwrites the previous setup packets
* and only that last SETUP packet will be processed.
*/
stm32_rxfifo_read(&priv->epout[EP0],
(uint8_t *)&priv->ctrlreq,
USB_SIZEOF_CTRLREQ);
* 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)
{
regval = stm32_getreg(STM32_OTGHS_DOEPCTL0);
regval |= OTGHS_DOEPCTL0_CNAK;
stm32_putreg(regval, STM32_OTGHS_DOEPCTL0);
priv->ep0state = EP0STATE_SETUP_OUT;
}
else
{
* 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;
}
}
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(struct stm32_usbdev_s *priv)
{
uint32_t regval;
stm32_ep0in_activate();
* 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(struct stm32_usbdev_s *priv)
{
int i;
* isochronous IN endpoints to detect endpoints with incomplete IN data
* transfers.
*/
for (i = 0; i < STM32_NENDPOINTS; i++)
{
privep = &priv->epin[i];
if (privep->eptype != USB_EP_ATTR_XFER_ISOC)
{
continue;
}
if (!privep->active)
{
continue;
}
regaddr = STM32_OTGHS_DIEPCTL(privep->epphy);
doepctl = stm32_getreg(regaddr);
dsts = stm32_getreg(STM32_OTGHS_DSTS);
* SOFFN = Frame number of the received SOF
*/
eonum = ((doepctl & OTGHS_DIEPCTL_EONUM) != 0);
soffn = ((dsts & OTGHS_DSTS_SOFFN0) != 0);
if (eonum != soffn)
{
continue;
}
* 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(struct stm32_usbdev_s *priv)
{
struct stm32_ep_s *privep;
struct stm32_req_s *privreq;
uint32_t regaddr;
uint32_t doepctl;
uint32_t dsts;
bool eonum;
bool soffn;
* 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++)
{
privep = &priv->epout[i];
if (privep->eptype != USB_EP_ATTR_XFER_ISOC)
{
continue;
}
if (!privep->active)
{
continue;
}
regaddr = STM32_OTGHS_DOEPCTL(privep->epphy);
doepctl = stm32_getreg(regaddr);
dsts = stm32_getreg(STM32_OTGHS_DSTS);
* SOFFN = Frame number of the received SOF
*/
eonum = ((doepctl & OTGHS_DOEPCTL_EONUM) != 0);
soffn = ((dsts & OTGHS_DSTS_SOFFN0) != 0);
if (eonum != soffn)
{
continue;
}
* 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(struct stm32_usbdev_s *priv)
{
#warning "Missing logic"
}
#endif
* Name: stm32_otginterrupt
*
* Description:
* OTG interrupt
*
****************************************************************************/
#ifdef CONFIG_USBDEV_VBUSSENSING
static inline void stm32_otginterrupt(struct stm32_usbdev_s *priv)
{
uint32_t regval;
regval = stm32_getreg(STM32_OTGHS_GOTGINT);
if ((regval & OTGHS_GOTGINT_SEDET) != 0)
{
#warning "Missing logic"
}
stm32_putreg(retval, STM32_OTGHS_GOTGINT);
}
#endif
* Name: stm32_usbinterrupt
*
* Description:
* USB interrupt handler
*
****************************************************************************/
static int stm32_usbinterrupt(int irq, void *context, void *arg)
{
* 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.
*/
struct stm32_usbdev_s *priv = &g_otghsdev;
uint32_t regval;
usbtrace(TRACE_INTENTRY(STM32_TRACEINTID_USB), 0);
DEBUGASSERT((stm32_getreg(STM32_OTGHS_GINTSTS) & OTGHS_GINTSTS_CMOD)
== OTGHS_GINTSTS_DEVMODE);
* some interrupts (like RXFLVL) will generate additional interrupting
* events.
*/
for (; ; )
{
regval = stm32_getreg(STM32_OTGHS_GINTSTS);
regval &= stm32_getreg(STM32_OTGHS_GINTMSK);
* unmasked) interrupts to be processes.
*/
if (regval == 0)
{
break;
}
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_INTPENDING),
(uint16_t)regval);
* 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);
}
* 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);
}
#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
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);
}
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);
}
#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
* 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);
}
if ((regval & OTGHS_GINT_USBRST) != 0)
{
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_DEVRESET),
(uint16_t)regval);
stm32_usbreset(priv);
usbtrace(TRACE_INTEXIT(STM32_TRACEINTID_USB), 0);
stm32_putreg(OTGHS_GINT_USBRST, STM32_OTGHS_GINTSTS);
return OK;
}
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);
}
* 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);
}
* 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
#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);
}
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(struct stm32_ep_s *privep)
{
uint32_t regval;
#if 0
stm32_putreg(OTGHS_GINT_GONAKEFF, STM32_OTGHS_GINTSTS);
#endif
regval = stm32_getreg(STM32_OTGHS_DCTL);
regval |= OTGHS_DCTL_SGONAK;
stm32_putreg(regval, STM32_OTGHS_DCTL);
#if 0
* 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
* 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(struct stm32_ep_s *privep)
{
uint32_t regval;
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(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);
if (privep->epphy == EP0)
{
DEBUGASSERT(eptype == USB_EP_ATTR_XFER_CONTROL);
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;
}
}
else
{
mpsiz = (maxpacket << OTGHS_DOEPCTL_MPSIZ_SHIFT);
}
* 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);
privep->ep.maxpacket = maxpacket;
privep->eptype = eptype;
privep->stalled = false;
}
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(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);
if (privep->epphy == EP0)
{
DEBUGASSERT(eptype == USB_EP_ATTR_XFER_CONTROL);
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;
}
}
else
{
mpsiz = (maxpacket << OTGHS_DIEPCTL_MPSIZ_SHIFT);
}
* 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 |= (privep->epphy << OTGHS_DIEPCTL_TXFNUM_SHIFT);
regval |= (OTGHS_DIEPCTL_SD0PID | OTGHS_DIEPCTL_USBAEP);
stm32_putreg(regval, regaddr);
privep->ep.maxpacket = maxpacket;
privep->eptype = eptype;
privep->stalled = false;
}
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(struct usbdev_ep_s *ep,
const struct usb_epdesc_s *desc,
bool last)
{
struct stm32_ep_s *privep = (struct stm32_ep_s *)ep;
uint16_t maxpacket;
uint8_t eptype;
int ret;
usbtrace(TRACE_EPCONFIGURE, privep->epphy);
DEBUGASSERT(desc->addr == ep->eplog);
maxpacket = GETUINT16(desc->mxpacketsize);
eptype = desc->attr & USB_EP_ATTR_XFERTYPE_MASK;
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(struct stm32_usbdev_s *priv)
{
stm32_epin_configure(&priv->epin[EP0], USB_EP_ATTR_XFER_CONTROL,
CONFIG_USBDEV_EP0_MAXSIZE);
stm32_epout_configure(&priv->epout[EP0], USB_EP_ATTR_XFER_CONTROL,
CONFIG_USBDEV_EP0_MAXSIZE);
}
* Name: stm32_epout_disable
*
* Description:
* Disable an OUT endpoint will no longer be used
*
****************************************************************************/
static void stm32_epout_disable(struct stm32_ep_s *privep)
{
uint32_t regaddr;
uint32_t regval;
irqstate_t flags;
usbtrace(TRACE_EPDISABLE, privep->epphy);
* Global OUT NAK mode in the core.
*/
flags = enter_critical_section();
stm32_enablegonak(privep);
* 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);
* endpoint is completely disabled.
*/
#if 0
regaddr = STM32_OTGHS_DOEPINT(privep->epphy);
while ((stm32_getreg(regaddr) & OTGHS_DOEPINT_EPDISD) == 0);
#else
up_udelay(10);
#endif
stm32_putreg(OTGHS_DOEPINT_EPDISD, STM32_OTGHS_DOEPINT(privep->epphy));
* from other non-disabled OUT endpoints.
*/
stm32_disablegonak(privep);
regval = stm32_getreg(STM32_OTGHS_DAINTMSK);
regval &= ~OTGHS_DAINT_OEP(privep->epphy);
stm32_putreg(regval, STM32_OTGHS_DAINTMSK);
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(struct stm32_ep_s *privep)
{
uint32_t regaddr;
uint32_t regval;
irqstate_t flags;
usbtrace(TRACE_EPDISABLE, privep->epphy);
* 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;
}
* to get stuck to infinite loop.
*/
#if 0
* to poll this bit below).
*/
stm32_putreg(OTGHS_DIEPINT_INEPNE, STM32_OTGHS_DIEPINT(privep->epphy));
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);
*/
regaddr = STM32_OTGHS_DIEPINT(privep->epphy);
while ((stm32_getreg(regaddr) & OTGHS_DIEPINT_INEPNE) == 0);
stm32_putreg(OTGHS_DIEPINT_INEPNE, regaddr);
#endif
* 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);
* endpoint is completely disabled.
*/
regaddr = STM32_OTGHS_DIEPINT(privep->epphy);
while ((stm32_getreg(regaddr) & OTGHS_DIEPINT_EPDISD) == 0);
stm32_putreg(OTGHS_DIEPINT_EPDISD, stm32_getreg(regaddr));
stm32_txfifo_flush(OTGHS_GRSTCTL_TXFNUM_D(privep->epphy));
regval = stm32_getreg(STM32_OTGHS_DAINTMSK);
regval &= ~OTGHS_DAINT_IEP(privep->epphy);
stm32_putreg(regval, STM32_OTGHS_DAINTMSK);
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(struct usbdev_ep_s *ep)
{
struct stm32_ep_s *privep = (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);
if (privep->isin)
{
stm32_epin_disable(privep);
}
else
{
stm32_epout_disable(privep);
}
return OK;
}
* Name: stm32_ep_allocreq
*
* Description:
* Allocate an I/O request
*
****************************************************************************/
static struct usbdev_req_s *stm32_ep_allocreq(struct usbdev_ep_s *ep)
{
struct stm32_req_s *privreq;
#ifdef CONFIG_DEBUG_FEATURES
if (!ep)
{
usbtrace(TRACE_DEVERROR(STM32_TRACEERR_INVALIDPARMS), 0);
return NULL;
}
#endif
usbtrace(TRACE_EPALLOCREQ, ((struct stm32_ep_s *)ep)->epphy);
privreq = 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(struct usbdev_ep_s *ep,
struct usbdev_req_s *req)
{
struct stm32_req_s *privreq = (struct stm32_req_s *)req;
#ifdef CONFIG_DEBUG_FEATURES
if (!ep || !req)
{
usbtrace(TRACE_DEVERROR(STM32_TRACEERR_INVALIDPARMS), 0);
return;
}
#endif
usbtrace(TRACE_EPFREEREQ, ((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(struct usbdev_ep_s *ep, uint16_t bytes)
{
usbtrace(TRACE_EPALLOCBUFFER, ((struct stm32_ep_s *)ep)->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(struct usbdev_ep_s *ep, void *buf)
{
usbtrace(TRACE_EPFREEBUFFER, ((struct stm32_ep_s *)ep)->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(struct usbdev_ep_s *ep,
struct usbdev_req_s *req)
{
struct stm32_req_s *privreq = (struct stm32_req_s *)req;
struct stm32_ep_s *privep = (struct stm32_ep_s *)ep;
struct stm32_usbdev_s *priv;
irqstate_t flags;
int ret = OK;
#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
req->result = -EINPROGRESS;
req->xfrd = 0;
flags = enter_critical_section();
if (privep->stalled)
{
ret = -EBUSY;
}
else
{
if (stm32_req_addlast(privep, privreq) && !privep->active)
{
* the request data buffer now.
*/
if (privep->isin)
{
usbtrace(TRACE_INREQQUEUED(privep->epphy), privreq->req.len);
* then process the newly received write request now.
*/
if (!privep->active)
{
stm32_epin_request(priv, privep);
}
}
* 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(struct usbdev_ep_s *ep,
struct usbdev_req_s *req)
{
struct stm32_ep_s *privep = (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();
* 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(struct stm32_ep_s *privep)
{
#if 1
* manual.
*/
uint32_t regaddr;
uint32_t regval;
stm32_enablegonak(privep);
* 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);
* endpoint is completely disabled.
*/
#if 0
regaddr = STM32_OTGHS_DOEPINT(privep->epphy);
while ((stm32_getreg(regaddr) & OTGHS_DOEPINT_EPDISD) == 0);
#else
up_udelay(10);
#endif
stm32_disablegonak(privep);
privep->stalled = true;
return OK;
#else
uint32_t regaddr;
uint32_t regval;
*/
regaddr = STM32_OTGHS_DOEPCTL(privep->epphy);
regval = stm32_getreg(regaddr);
regval |= OTGHS_DOEPCTL_STALL;
stm32_putreg(regval, regaddr);
privep->stalled = true;
return OK;
#endif
}
* Name: stm32_epin_setstall
*
* Description:
* Stall an IN endpoint
*
****************************************************************************/
static int stm32_epin_setstall(struct stm32_ep_s *privep)
{
uint32_t regaddr;
uint32_t regval;
regaddr = STM32_OTGHS_DIEPCTL(privep->epphy);
regval = stm32_getreg(regaddr);
regval |= OTGHS_DIEPCTL_STALL;
stm32_putreg(regval, regaddr);
privep->stalled = true;
return OK;
}
* Name: stm32_ep_setstall
*
* Description:
* Stall an endpoint
*
****************************************************************************/
static int stm32_ep_setstall(struct stm32_ep_s *privep)
{
usbtrace(TRACE_EPSTALL, privep->epphy);
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(struct stm32_ep_s *privep)
{
uint32_t regaddr;
uint32_t regval;
uint32_t stallbit;
uint32_t data0bit;
usbtrace(TRACE_EPRESUME, privep->epphy);
if (privep->isin == 1)
{
regaddr = STM32_OTGHS_DIEPCTL(privep->epphy);
stallbit = OTGHS_DIEPCTL_STALL;
data0bit = OTGHS_DIEPCTL_SD0PID;
}
else
{
regaddr = STM32_OTGHS_DOEPCTL(privep->epphy);
stallbit = OTGHS_DOEPCTL_STALL;
data0bit = OTGHS_DOEPCTL_SD0PID;
}
regval = stm32_getreg(regaddr);
regval &= ~stallbit;
if (privep->eptype == USB_EP_ATTR_XFER_INT ||
privep->eptype == USB_EP_ATTR_XFER_BULK)
{
regval |= data0bit;
}
stm32_putreg(regval, regaddr);
privep->stalled = false;
return OK;
}
* Name: stm32_ep_stall
*
* Description:
* Stall or resume an endpoint
*
****************************************************************************/
static int stm32_ep_stall(struct usbdev_ep_s *ep, bool resume)
{
struct stm32_ep_s *privep = (struct stm32_ep_s *)ep;
irqstate_t flags;
int ret;
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(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 struct usbdev_ep_s *stm32_ep_alloc(struct usbdev_s *dev,
uint8_t eplog, bool in,
uint8_t eptype)
{
struct stm32_usbdev_s *priv = (struct stm32_usbdev_s *)dev;
uint8_t epavail;
irqstate_t flags;
int epphy;
int epno = 0;
usbtrace(TRACE_DEVALLOCEP, (uint16_t)eplog);
epphy = USB_EPNO(eplog);
flags = enter_critical_section();
epavail = priv->epavail[in];
if (epphy > 0)
{
* 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;
}
* this physical endpoint number.
*/
epavail &= (1 << epphy);
}
if (epavail)
{
* endpoints.
*/
for (epno = 1; epno < STM32_NENDPOINTS; epno++)
{
uint8_t bit = 1 << epno;
if ((epavail & bit) != 0)
{
priv->epavail[in] &= ~(1 << epno);
leave_critical_section(flags);
return in ? &priv->epin[epno].ep : &priv->epout[epno].ep;
}
}
}
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(struct usbdev_s *dev,
struct usbdev_ep_s *ep)
{
struct stm32_usbdev_s *priv = (struct stm32_usbdev_s *)dev;
struct stm32_ep_s *privep = (struct stm32_ep_s *)ep;
irqstate_t flags;
usbtrace(TRACE_DEVFREEEP, (uint16_t)privep->epphy);
if (priv && privep)
{
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);
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)
{
struct stm32_usbdev_s *priv = (struct stm32_usbdev_s *)dev;
uint32_t regval;
irqstate_t flags;
usbtrace(TRACE_DEVWAKEUP, 0);
flags = enter_critical_section();
if (priv->wakeup)
{
regval = stm32_getreg(STM32_OTGHS_DSTS);
if ((regval & OTGHS_DSTS_SUSPSTS) != 0)
{
#ifdef CONFIG_USBDEV_LOWPOWER
regval = stm32_getreg(STM32_OTGHS_PCGCCTL);
regval &= ~(OTGHS_PCGCCTL_STPPCLK | OTGHS_PCGCCTL_GATEHCLK);
stm32_putreg(regval, STM32_OTGHS_PCGCCTL);
#endif
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)
{
struct stm32_usbdev_s *priv = (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)
{
* register
*/
regval &= ~OTGHS_DCTL_SDIS;
}
else
{
* 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;
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);
* 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;
regval = OTGHS_GRSTCTL_TXFFLSH | txfnum;
stm32_putreg(regval, STM32_OTGHS_GRSTCTL);
for (timeout = 0; timeout < STM32_FLUSH_DELAY; timeout++)
{
regval = stm32_getreg(STM32_OTGHS_GRSTCTL);
if ((regval & OTGHS_GRSTCTL_TXFFLSH) == 0)
{
break;
}
}
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;
stm32_putreg(OTGHS_GRSTCTL_RXFFLSH, STM32_OTGHS_GRSTCTL);
for (timeout = 0; timeout < STM32_FLUSH_DELAY; timeout++)
{
regval = stm32_getreg(STM32_OTGHS_GRSTCTL);
if ((regval & OTGHS_GRSTCTL_RXFFLSH) == 0)
{
break;
}
}
up_udelay(3);
return OK;
}
* Name: stm32_swinitialize
*
* Description:
* Initialize all driver data structures.
*
****************************************************************************/
static void stm32_swinitialize(struct stm32_usbdev_s *priv)
{
struct stm32_ep_s *privep;
int i;
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;
for (i = 0; i < STM32_NENDPOINTS; i++)
{
* 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;
* to a logical endpoint address usable by the class driver.
*/
privep->epphy = i;
privep->ep.eplog = STM32_EPPHYIN2LOG(i);
privep->eptype = USB_EP_ATTR_XFER_CONTROL;
privep->ep.maxpacket = CONFIG_USBDEV_EP0_MAXSIZE;
}
for (i = 0; i < STM32_NENDPOINTS; i++)
{
* 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;
* to a logical endpoint address usable by the class driver.
*/
privep->epphy = i;
privep->ep.eplog = STM32_EPPHYOUT2LOG(i);
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(struct stm32_usbdev_s *priv)
{
uint32_t regval;
uint32_t timeout;
uint32_t address;
int i;
* 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);
* 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);
* 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;
}
}
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;
}
}
up_udelay(3);
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);
regval = stm32_getreg(STM32_OTGHS_GUSBCFG);
regval &= ~OTGHS_GUSBCFG_FHMOD;
regval |= OTGHS_GUSBCFG_FDMOD;
stm32_putreg(regval, STM32_OTGHS_GUSBCFG);
up_mdelay(50);
stm32_putreg(0, STM32_OTGHS_PCGCCTL);
regval = stm32_getreg(STM32_OTGHS_DCFG);
regval &= ~OTGHS_DCFG_PFIVL_MASK;
regval |= OTGHS_DCFG_PFIVL_80PCT;
stm32_putreg(regval, STM32_OTGHS_DCFG);
regval = stm32_getreg(STM32_OTGHS_DCFG);
regval &= ~OTGHS_DCFG_DSPD_MASK;
regval |= OTGHS_DCFG_DSPD_FS;
stm32_putreg(regval, STM32_OTGHS_DCFG);
stm32_putreg(STM32_RXFIFO_WORDS, STM32_OTGHS_GRXFSIZ);
address = STM32_RXFIFO_WORDS;
regval = (address << OTGHS_DIEPTXF0_TX0FD_SHIFT) |
(STM32_EP0_TXFIFO_WORDS << OTGHS_DIEPTXF0_TX0FSA_SHIFT);
stm32_putreg(regval, STM32_OTGHS_DIEPTXF0);
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);
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);
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);
stm32_txfifo_flush(OTGHS_GRSTCTL_TXFNUM_DALL);
stm32_rxfifo_flush();
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);
for (i = 0; i < STM32_NENDPOINTS; i++)
{
regval = stm32_getreg(STM32_OTGHS_DIEPCTL(i));
if ((regval & OTGHS_DIEPCTL_EPENA) != 0)
{
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));
}
for (i = 0; i < STM32_NENDPOINTS; i++)
{
regval = stm32_getreg(STM32_OTGHS_DOEPCTL(i));
if ((regval & OTGHS_DOEPCTL_EPENA) != 0)
{
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));
}
stm32_putreg(0, STM32_OTGHS_GINTMSK);
stm32_putreg(0xffffffff, STM32_OTGHS_GOTGINT);
stm32_putreg(0xbfffffff, STM32_OTGHS_GINTSTS);
#ifndef BOARD_ENABLE_USBOTG_HSULPI
* 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
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);
* 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: arm_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 arm_usbinitialize(void)
{
* 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.
*/
struct stm32_usbdev_s *priv = &g_otghsdev;
int ret;
usbtrace(TRACE_DEVINIT, 0);
*
* 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.
*/
*
* 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);
#ifdef CONFIG_STM32_OTGHS_SOFOUTPUT
stm32_configgpio(GPIO_OTGHS_SOF);
#endif
* known state.
*/
arm_usbuninitialize();
stm32_swinitialize(priv);
ret = irq_attach(STM32_IRQ_OTGHS, stm32_usbinterrupt, NULL);
if (ret < 0)
{
uerr("ERROR: irq_attach failed: %d\n", ret);
goto errout;
}
stm32_hwinitialize(priv);
stm32_pullup(&priv->usbdev, false);
stm32_usbreset(priv);
up_enable_irq(STM32_IRQ_OTGHS);
return;
errout:
arm_usbuninitialize();
}
* Name: up_usbhsuninitialize
****************************************************************************/
void arm_usbuninitialize(void)
{
* 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.
*/
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);
}
flags = enter_critical_section();
stm32_pullup(&priv->usbdev, false);
priv->usbdev.speed = USB_SPEED_UNKNOWN;
up_disable_irq(STM32_IRQ_OTGHS);
irq_detach(STM32_IRQ_OTGHS);
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);
stm32_txfifo_flush(OTGHS_GRSTCTL_TXFNUM_DALL);
stm32_rxfifo_flush();
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)
{
* 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.
*/
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
priv->driver = driver;
ret = CLASS_BIND(driver, &priv->usbdev);
if (ret)
{
usbtrace(TRACE_DEVERROR(STM32_TRACEERR_BINDFAILED), (uint16_t)-ret);
priv->driver = NULL;
}
else
{
up_enable_irq(STM32_IRQ_OTGHS);
* 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)
{
* 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.
*/
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
* canceled while the class driver is still bound.
*/
flags = enter_critical_section();
stm32_usbreset(priv);
leave_critical_section(flags);
CLASS_UNBIND(driver, &priv->usbdev);
flags = enter_critical_section();
up_disable_irq(STM32_IRQ_OTGHS);
stm32_pullup(&priv->usbdev, false);
priv->driver = NULL;
leave_critical_section(flags);
return OK;
}
#endif